using System; using System.Collections.Generic; using System.IO; using System.Net; using UnityEngine; using Object = UnityEngine.Object; namespace Webserver.UrlHandlers { public class ItemIconHandler : AbsHandler { private readonly Dictionary icons = new Dictionary (); private readonly bool logMissingFiles; private bool loaded; static ItemIconHandler () { Instance = null; } public ItemIconHandler (bool _logMissingFiles, string _moduleName = null) : base (_moduleName) { logMissingFiles = _logMissingFiles; Instance = this; } public static ItemIconHandler Instance { get; private set; } public override void HandleRequest (RequestContext _context) { if (!loaded) { _context.Response.StatusCode = (int) HttpStatusCode.InternalServerError; Log.Out ("[Web] IconHandler: Icons not loaded"); return; } string requestFileName = _context.RequestPath.Remove (0, urlBasePath.Length); requestFileName = requestFileName.Remove (requestFileName.LastIndexOf ('.')); if (icons.ContainsKey (requestFileName) && _context.RequestPath.EndsWith (".png", StringComparison.OrdinalIgnoreCase)) { _context.Response.ContentType = MimeType.GetMimeType (".png"); byte[] itemIconData = icons [requestFileName]; _context.Response.ContentLength64 = itemIconData.Length; _context.Response.OutputStream.Write (itemIconData, 0, itemIconData.Length); } else { _context.Response.StatusCode = (int) HttpStatusCode.NotFound; if (logMissingFiles) { Log.Out ($"[Web] IconHandler: FileNotFound: \"{_context.RequestPath}\" "); } } } private class LoadingStats { public int Files; public int Tints; public readonly MicroStopwatch MswTotal = new MicroStopwatch (false); public readonly MicroStopwatch MswLoading = new MicroStopwatch (false); public readonly MicroStopwatch MswEncoding = new MicroStopwatch (false); public readonly MicroStopwatch MswTinting = new MicroStopwatch (false); } public bool LoadIcons () { lock (icons) { if (loaded) { return true; } LoadingStats stats = new LoadingStats (); stats?.MswTotal.Start (); // Get list of used tints for all items Dictionary> tintedIcons = new Dictionary> (); foreach (ItemClass ic in ItemClass.list) { if (ic == null) { continue; } Color tintColor = ic.GetIconTint (); if (tintColor == Color.white) { continue; } string name = ic.GetIconName (); if (!tintedIcons.TryGetValue (name, out List tintsList)) { tintsList = new List (); tintedIcons.Add (name, tintsList); } tintsList.Add (tintColor); } try { loadIconsFromFolder (GameIO.GetGameDir ("Data/ItemIcons"), tintedIcons, stats); } catch (Exception e) { Log.Error ("[Web] Failed loading icons from base game"); Log.Exception (e); } // Load icons from mods foreach (Mod mod in ModManager.GetLoadedMods ()) { try { string modIconsPath = $"{mod.Path}/ItemIcons"; loadIconsFromFolder (modIconsPath, tintedIcons, stats); } catch (Exception e) { Log.Error ($"[Web] Failed loading icons from mod {mod.Name}"); Log.Exception (e); } } loaded = true; if (stats == null) { Log.Out ($"[Web] IconHandler: Loaded {icons.Count} icons"); } else { stats?.MswTotal.Stop (); Log.Out ($"[Web] IconHandler: Loaded {icons.Count} icons ({stats.Files} source images with {stats.Tints} tints applied)"); Log.Out ($"[Web] IconHandler: Total time {stats.MswTotal.ElapsedMilliseconds} ms, loading files {stats.MswLoading.ElapsedMilliseconds} ms, tinting files {stats.MswTinting.ElapsedMilliseconds} ms, encoding files {stats.MswEncoding.ElapsedMilliseconds} ms"); int totalSize = 0; foreach ((string _, byte[] iconData) in icons) { totalSize += iconData.Length; } Log.Out ($"[Web] IconHandler: Cached {totalSize / 1024} KiB"); } return true; } } private void loadIconsFromFolder (string _path, Dictionary> _tintedIcons, LoadingStats _stats) { if (!Directory.Exists (_path)) { return; } foreach (string file in Directory.GetFiles (_path)) { try { if (!file.EndsWith (".png", StringComparison.OrdinalIgnoreCase)) { continue; } string name = Path.GetFileNameWithoutExtension (file); Texture2D tex = new Texture2D (1, 1, TextureFormat.ARGB32, false); _stats?.MswLoading.Start (); byte[] sourceBytes = File.ReadAllBytes (file); if (!tex.LoadImage (sourceBytes)) { _stats?.MswLoading.Stop (); continue; } _stats?.MswLoading.Stop (); AddIcon (name, sourceBytes, tex, _tintedIcons, _stats); Object.Destroy (tex); } catch (Exception e) { Log.Exception (e); } } } private void AddIcon (string _name, byte[] _sourceBytes, Texture2D _tex, Dictionary> _tintedIcons, LoadingStats _stats) { _stats?.MswEncoding.Start (); icons [$"{_name}__FFFFFF"] = _sourceBytes; _stats?.MswEncoding.Stop (); if (_stats != null) { _stats.Files++; } if (!_tintedIcons.TryGetValue (_name, out List tintsList)) { return; } foreach (Color c in tintsList) { string tintedName = $"{_name}__{c.ToHexCode ()}"; if (icons.ContainsKey (tintedName)) { continue; } Texture2D tintedTex = new Texture2D (_tex.width, _tex.height, TextureFormat.ARGB32, false); _stats?.MswTinting.Start (); TextureUtils.ApplyTint (_tex, tintedTex, c); _stats?.MswTinting.Stop (); _stats?.MswEncoding.Start (); icons [tintedName] = tintedTex.EncodeToPNG (); _stats?.MswEncoding.Stop (); Object.Destroy (tintedTex); if (_stats != null) { _stats.Tints++; } } } } }