using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("TextureToolbox")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("TextureToolbox")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f842a2ba-fbe0-4591-bd6f-16c316cedf7b")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace TextureToolbox; public static class MaterialProcessor { public static void Process(Material mat) { if (!Object.op_Implicit((Object)(object)mat)) { return; } int instanceID = ((Object)mat).GetInstanceID(); if (OptimizationCore.ProcessedMaterialIDs.Contains(instanceID)) { return; } string text = null; int num = OptimizationCore.TextureSlots.Length; for (int i = 0; i < num; i++) { int num2 = OptimizationCore.TextureSlots[i]; if (!mat.HasProperty(num2)) { continue; } Texture texture = mat.GetTexture(num2); if (!Object.op_Implicit((Object)(object)texture)) { continue; } long key = ((long)instanceID << 32) | (uint)num2; if (OptimizationCore.MatSlotToCustomTexture.TryGetValue(key, out var value)) { if ((Object)(object)value != (Object)null) { mat.SetTexture(num2, (Texture)(object)value); if (num2 == OptimizationCore.EmissionMapID) { mat.EnableKeyword("_EMISSION"); } } continue; } if (text == null) { text = OptimizationCore.CleanName(((Object)mat).name); } string slotName = GetSlotName(num2); if (Plugin.TryGetAdvancedMatch(text, slotName, texture, out var custom)) { mat.SetTexture(num2, (Texture)(object)custom); if (num2 == OptimizationCore.EmissionMapID) { mat.EnableKeyword("_EMISSION"); } OptimizationCore.MatSlotToCustomTexture[key] = custom; } else { OptimizationCore.MatSlotToCustomTexture[key] = null; } } OptimizationCore.ProcessedMaterialIDs.Add(instanceID); } private static string GetSlotName(int slotID) { if (slotID == OptimizationCore.TextureSlots[0]) { return "_MainTex"; } if (slotID == OptimizationCore.TextureSlots[1]) { return "_BaseMap"; } if (slotID == OptimizationCore.TextureSlots[2]) { return "_EmissionMap"; } return ""; } } public static class MeshRendererHandler { private static readonly List _matsBuffer = new List(16); public static void Process(MeshRenderer renderer) { if (!Object.op_Implicit((Object)(object)renderer)) { return; } ((Renderer)renderer).GetSharedMaterials(_matsBuffer); for (int i = 0; i < _matsBuffer.Count; i++) { if (Object.op_Implicit((Object)(object)_matsBuffer[i])) { MaterialProcessor.Process(_matsBuffer[i]); } } _matsBuffer.Clear(); } } public static class OptimizationCore { public static readonly int[] TextureSlots = new int[3] { Shader.PropertyToID("_MainTex"), Shader.PropertyToID("_BaseMap"), Shader.PropertyToID("_EmissionMap") }; public static readonly int EmissionMapID = Shader.PropertyToID("_EmissionMap"); public static readonly HashSet ProcessedMaterialIDs = new HashSet(); public static readonly Dictionary MatSlotToCustomTexture = new Dictionary(); public static readonly Dictionary TextureIDToCustomTexture = new Dictionary(); public static readonly HashSet CustomTextureIDs = new HashSet(); public static void Init() { SceneManager.sceneLoaded += OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { ProcessedMaterialIDs.Clear(); } public static void ClearAllCaches() { ProcessedMaterialIDs.Clear(); MatSlotToCustomTexture.Clear(); TextureIDToCustomTexture.Clear(); CustomTextureIDs.Clear(); } public static string CleanName(string name) { if (string.IsNullOrEmpty(name)) { return name; } if (name.EndsWith(" (Instance)")) { return name.Substring(0, name.Length - 11); } if (name.EndsWith("(Instance)")) { return name.Substring(0, name.Length - 10); } return name; } } public static class RendererManager { [CompilerGenerated] private sealed class d__0 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; private Renderer[] 5__1; private int 5__2; private int 5__3; private Renderer 5__4; private MeshRenderer 5__5; private SkinnedMeshRenderer 5__6; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__0(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__4 = null; 5__5 = null; 5__6 = null; <>1__state = -2; } private bool MoveNext() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(3f); <>1__state = 1; return true; case 1: <>1__state = -1; goto IL_01ae; case 2: <>1__state = -1; goto IL_014c; case 3: { <>1__state = -1; 5__1 = null; goto IL_01ae; } IL_01ae: 5__1 = Object.FindObjectsOfType(); 5__2 = 0; 5__3 = 0; goto IL_016d; IL_016d: if (5__3 < 5__1.Length) { 5__4 = 5__1[5__3]; if (!Object.op_Implicit((Object)(object)5__4)) { goto IL_015b; } ref MeshRenderer reference = ref 5__5; Renderer obj = 5__4; reference = (MeshRenderer)(object)((obj is MeshRenderer) ? obj : null); if (5__5 != null) { MeshRendererHandler.Process(5__5); } else { ref SkinnedMeshRenderer reference2 = ref 5__6; Renderer obj2 = 5__4; reference2 = (SkinnedMeshRenderer)(object)((obj2 is SkinnedMeshRenderer) ? obj2 : null); if (5__6 != null) { SkinnedMeshHandler.Process(5__6); } 5__6 = null; } 5__2++; if (5__2 >= 30) { 5__2 = 0; <>2__current = null; <>1__state = 2; return true; } goto IL_014c; } <>2__current = (object)new WaitForSeconds(10f); <>1__state = 3; return true; IL_014c: 5__4 = null; 5__5 = null; goto IL_015b; IL_015b: 5__3++; goto IL_016d; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [IteratorStateMachine(typeof(d__0))] public static IEnumerator FastSelfHealingSweep() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__0(0); } } public static class SkinnedMeshHandler { private static readonly List _matsBuffer = new List(16); public static void Process(SkinnedMeshRenderer renderer) { if (!Object.op_Implicit((Object)(object)renderer)) { return; } ((Renderer)renderer).GetSharedMaterials(_matsBuffer); for (int i = 0; i < _matsBuffer.Count; i++) { if (Object.op_Implicit((Object)(object)_matsBuffer[i])) { MaterialProcessor.Process(_matsBuffer[i]); } } _matsBuffer.Clear(); } } public static class SpawnHooks { private static readonly List _rendererBuffer = new List(128); [HarmonyPatch(typeof(Object), "Instantiate", new Type[] { typeof(Object) })] [HarmonyPostfix] public static void Postfix_Instantiate1(ref Object __result) { ProcessSpawn(__result); } [HarmonyPatch(typeof(Object), "Instantiate", new Type[] { typeof(Object), typeof(Transform) })] [HarmonyPostfix] public static void Postfix_Instantiate2(ref Object __result) { ProcessSpawn(__result); } [HarmonyPatch(typeof(Object), "Instantiate", new Type[] { typeof(Object), typeof(Transform), typeof(bool) })] [HarmonyPostfix] public static void Postfix_Instantiate3(ref Object __result) { ProcessSpawn(__result); } [HarmonyPatch(typeof(Object), "Instantiate", new Type[] { typeof(Object), typeof(Vector3), typeof(Quaternion) })] [HarmonyPostfix] public static void Postfix_Instantiate4(ref Object __result) { ProcessSpawn(__result); } [HarmonyPatch(typeof(Object), "Instantiate", new Type[] { typeof(Object), typeof(Vector3), typeof(Quaternion), typeof(Transform) })] [HarmonyPostfix] public static void Postfix_Instantiate5(ref Object __result) { ProcessSpawn(__result); } private static void ProcessSpawn(Object __result) { if (__result == (Object)null) { return; } GameObject val = (GameObject)(object)((__result is GameObject) ? __result : null); if ((Object)(object)val == (Object)null) { Component val2 = (Component)(object)((__result is Component) ? __result : null); if (val2 != null) { val = val2.gameObject; } } if (!((Object)(object)val != (Object)null)) { return; } val.GetComponentsInChildren(true, _rendererBuffer); for (int i = 0; i < _rendererBuffer.Count; i++) { Renderer val3 = _rendererBuffer[i]; MeshRenderer val4 = (MeshRenderer)(object)((val3 is MeshRenderer) ? val3 : null); if (val4 != null) { MeshRendererHandler.Process(val4); continue; } SkinnedMeshRenderer val5 = (SkinnedMeshRenderer)(object)((val3 is SkinnedMeshRenderer) ? val3 : null); if (val5 != null) { SkinnedMeshHandler.Process(val5); } } _rendererBuffer.Clear(); } } public static class StateChangeHooks { [HarmonyPatch(typeof(Material), "SetTexture", new Type[] { typeof(int), typeof(Texture) })] [HarmonyPrefix] public static void Prefix_SetTextureInt(Material __instance, int nameID, ref Texture value) { if (!Object.op_Implicit((Object)(object)value) || OptimizationCore.CustomTextureIDs.Contains(((Object)value).GetInstanceID())) { return; } int instanceID = ((Object)__instance).GetInstanceID(); long key = ((long)instanceID << 32) | (uint)nameID; if (Plugin.TryGetSuperMatch(value, out var custom)) { value = (Texture)(object)custom; OptimizationCore.MatSlotToCustomTexture[key] = custom; return; } string matName = OptimizationCore.CleanName(((Object)__instance).name); string slotName = GetSlotName(nameID); if (Plugin.TryGetAdvancedMatch(matName, slotName, value, out var custom2)) { value = (Texture)(object)custom2; OptimizationCore.MatSlotToCustomTexture[key] = custom2; } } [HarmonyPatch(typeof(Material), "SetTexture", new Type[] { typeof(string), typeof(Texture) })] [HarmonyPrefix] public static void Prefix_SetTextureString(Material __instance, string name, ref Texture value) { if (!Object.op_Implicit((Object)(object)value) || OptimizationCore.CustomTextureIDs.Contains(((Object)value).GetInstanceID())) { return; } int num = Shader.PropertyToID(name); int instanceID = ((Object)__instance).GetInstanceID(); long key = ((long)instanceID << 32) | (uint)num; if (Plugin.TryGetSuperMatch(value, out var custom)) { value = (Texture)(object)custom; OptimizationCore.MatSlotToCustomTexture[key] = custom; return; } string matName = OptimizationCore.CleanName(((Object)__instance).name); if (Plugin.TryGetAdvancedMatch(matName, name, value, out var custom2)) { value = (Texture)(object)custom2; OptimizationCore.MatSlotToCustomTexture[key] = custom2; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPrefix] public static void Prefix_MainTexture(Material __instance, ref Texture value) { Prefix_SetTextureInt(__instance, OptimizationCore.TextureSlots[0], ref value); } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void Postfix_SetMaterial(Material value) { if (Object.op_Implicit((Object)(object)value)) { MaterialProcessor.Process(value); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void Postfix_SetSharedMaterial(Material value) { if (Object.op_Implicit((Object)(object)value)) { MaterialProcessor.Process(value); } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void Postfix_SetMaterials(Material[] value) { if (value == null) { return; } for (int i = 0; i < value.Length; i++) { if (Object.op_Implicit((Object)(object)value[i])) { MaterialProcessor.Process(value[i]); } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPostfix] public static void Postfix_SetSharedMaterials(Material[] value) { if (value == null) { return; } for (int i = 0; i < value.Length; i++) { if (Object.op_Implicit((Object)(object)value[i])) { MaterialProcessor.Process(value[i]); } } } private static string GetSlotName(int slotID) { if (slotID == OptimizationCore.TextureSlots[0]) { return "_MainTex"; } if (slotID == OptimizationCore.TextureSlots[1]) { return "_BaseMap"; } if (slotID == OptimizationCore.TextureSlots[2]) { return "_EmissionMap"; } return ""; } } public static class UIHooks { [HarmonyPatch(typeof(CanvasRenderer), "SetTexture")] [HarmonyPrefix] public static void Prefix_SetUITexture(ref Texture texture) { if (Object.op_Implicit((Object)(object)texture) && Plugin.TryGetSuperMatch(texture, out var custom)) { texture = (Texture)(object)custom; } } } public static class WorldHooks { [HarmonyPatch(typeof(Resources), "Load", new Type[] { typeof(string), typeof(Type) })] [HarmonyPostfix] public static void Postfix_ResourcesLoad(ref Object __result) { Object obj = __result; Texture val = (Texture)(object)((obj is Texture) ? obj : null); if (val != null && Plugin.TryGetSuperMatch(val, out var custom)) { __result = (Object)(object)custom; } } [HarmonyPatch(typeof(AssetBundle), "LoadAsset", new Type[] { typeof(string), typeof(Type) })] [HarmonyPostfix] public static void Postfix_AssetBundleLoad(ref Object __result) { Object obj = __result; Texture val = (Texture)(object)((obj is Texture) ? obj : null); if (val != null && Plugin.TryGetSuperMatch(val, out var custom)) { __result = (Object)(object)custom; } } } [BepInPlugin("com.thehalfbunny.texturetoolbox", "TextureToolbox", "2.8.0")] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class d__10 : IEnumerator, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Plugin <>4__this; private Stopwatch 5__1; private (string name, byte[] data) 5__2; private Texture2D 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__2 = default((string, byte[])); 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__1 = new Stopwatch(); break; case 1: <>1__state = -1; break; } if (!_isDiskReadingComplete || !_imageLoadQueue.IsEmpty) { 5__1.Restart(); while (5__1.ElapsedMilliseconds < 5 && _imageLoadQueue.TryDequeue(out 5__2)) { 5__3 = new Texture2D(2, 2, (TextureFormat)4, true); if (ImageConversion.LoadImage(5__3, 5__2.data)) { ((Object)5__3).name = 5__2.name; ((Texture)5__3).filterMode = (FilterMode)0; 5__3.Apply(true, true); TextureVault[((Object)5__3).name] = 5__3; OptimizationCore.CustomTextureIDs.Add(((Object)5__3).GetInstanceID()); } else { Object.Destroy((Object)(object)5__3); } 5__3 = null; } <>2__current = null; <>1__state = 1; return true; } _isAsyncLoading = false; Log.LogMessage((object)$"Async Vaulting Complete. Vaulted {TextureVault.Count} textures."); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static Dictionary TextureVault = new Dictionary(StringComparer.OrdinalIgnoreCase); public static string ImagePath; public static ManualLogSource Log; private static ConcurrentQueue<(string name, byte[] data)> _imageLoadQueue = new ConcurrentQueue<(string, byte[])>(); private static bool _isAsyncLoading = false; private static bool _isDiskReadingComplete = false; private void Awake() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"TextureToolbox loaded!"); LocateCustomImagesFolder(); OptimizationCore.Init(); LoadAllImagesAsync(); ((MonoBehaviour)this).StartCoroutine(RendererManager.FastSelfHealingSweep()); Harmony val = new Harmony("com.thehalfbunny.texturetoolbox.hooks"); try { val.PatchAll(typeof(WorldHooks)); val.PatchAll(typeof(UIHooks)); val.PatchAll(typeof(SpawnHooks)); val.PatchAll(typeof(StateChangeHooks)); Log.LogInfo((object)"Engine Online."); } catch (Exception ex) { Log.LogError((object)("Harmony Failure: " + ex.Message)); } } private void LocateCustomImagesFolder() { try { string configPath = Paths.ConfigPath; string text = Path.Combine(configPath, "CustomImages"); if (Directory.Exists(text)) { ImagePath = text; Log.LogInfo((object)("Detected CustomImages at: " + ImagePath)); } else { Directory.CreateDirectory(text); ImagePath = text; Log.LogInfo((object)("Created CustomImages folder in Configs: " + ImagePath)); } } catch (Exception ex) { Log.LogError((object)("Error accessing Configs directory: " + ex.Message)); ImagePath = Path.Combine(Paths.PluginPath, "CustomImages"); } } private void Update() { if (Input.GetKeyDown((KeyCode)291)) { if (!_isAsyncLoading) { Log.LogWarning((object)"F10 Pressed: Flushing Cache & Async Reloading..."); LoadAllImagesAsync(); } else { Log.LogWarning((object)"Already loading images, please wait..."); } } } public void LoadAllImagesAsync() { if (string.IsNullOrEmpty(ImagePath) || !Directory.Exists(ImagePath)) { return; } _isAsyncLoading = true; _isDiskReadingComplete = false; foreach (Texture2D value in TextureVault.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)value); } } TextureVault.Clear(); OptimizationCore.ClearAllCaches(); string[] files = Directory.GetFiles(ImagePath, "*.png", SearchOption.AllDirectories); Task.Run(delegate { for (int i = 0; i < files.Length; i++) { try { string path = files[i]; byte[] item = File.ReadAllBytes(path); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); _imageLoadQueue.Enqueue((fileNameWithoutExtension, item)); } catch (Exception ex) { Log.LogWarning((object)("Failed to read " + files[i] + ": " + ex.Message)); } } _isDiskReadingComplete = true; }); ((MonoBehaviour)this).StartCoroutine(ProcessImageQueue()); } [IteratorStateMachine(typeof(d__10))] private IEnumerator ProcessImageQueue() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__10(0) { <>4__this = this }; } public static bool TryGetAdvancedMatch(string matName, string slotName, Texture orig, out Texture2D custom) { custom = null; if (Object.op_Implicit((Object)(object)orig) && TryGetSuperMatch(orig, out custom)) { return true; } if (!string.IsNullOrEmpty(matName)) { string key = matName + "_" + slotName; if (TextureVault.TryGetValue(key, out custom)) { return true; } if (slotName == "_MainTex" || slotName == "_BaseMap") { string key2 = "MAT_" + matName; if (TextureVault.TryGetValue(key2, out custom)) { return true; } } } return false; } public static bool TryGetSuperMatch(Texture orig, out Texture2D custom) { custom = null; if (!Object.op_Implicit((Object)(object)orig)) { return false; } int instanceID = ((Object)orig).GetInstanceID(); if (OptimizationCore.TextureIDToCustomTexture.TryGetValue(instanceID, out custom)) { return (Object)(object)custom != (Object)null; } string key = OptimizationCore.CleanName(((Object)orig).name); if (TextureVault.TryGetValue(key, out custom)) { OptimizationCore.TextureIDToCustomTexture[instanceID] = custom; return true; } OptimizationCore.TextureIDToCustomTexture[instanceID] = null; return false; } }