Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of TextureSwapper Mono v0.2.0
Mods/TextureSwapper_Mono.dll
Decompiled 4 days agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using TextureSwapper; using TextureSwapper.Config; using TextureSwapper.IO; using TextureSwapper.Runtime; using TextureSwapper.UI; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "TextureSwapper", "0.2.0", "Bars", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TextureSwapper_Mono")] [assembly: AssemblyConfiguration("Mono")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3b3f92c400d4caf495d98ad692c946a4c5e62d8e")] [assembly: AssemblyProduct("TextureSwapper_Mono")] [assembly: AssemblyTitle("TextureSwapper_Mono")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace TextureSwapper { public class Core : MelonMod { [CompilerGenerated] private sealed class <DelayedRegistration>d__41 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delaySeconds; public Core <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedRegistration>d__41(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (Preferences.DebugEnabled) { ((MelonBase)<>4__this).LoggerInstance.Msg($"Waiting {delaySeconds}s for change monitor registration..."); } <>2__current = (object)new WaitForSeconds(delaySeconds); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)<>4__this.MaterialChangeMonitor != (Object)null) { if (Preferences.DebugEnabled) { ((MelonBase)<>4__this).LoggerInstance.Msg("Executing change monitor registration"); } <>4__this.MaterialChangeMonitor.ScanAndRegisterAll(); } 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(); } } [CompilerGenerated] private sealed class <DelayedSceneScan>d__40 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float delaySeconds; public string scanType; public Core <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedSceneScan>d__40(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (Preferences.DebugEnabled) { ((MelonBase)<>4__this).LoggerInstance.Msg($"Waiting {delaySeconds}s for {scanType} scan..."); } <>2__current = (object)new WaitForSeconds(delaySeconds); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)<>4__this.MaterialScanner != (Object)null) { if (Preferences.DebugEnabled) { ((MelonBase)<>4__this).LoggerInstance.Msg("Executing " + scanType + " material scan"); } <>4__this.MaterialScanner.RequestFullRescan(); } if ((Object)(object)<>4__this.UITextureScanner != (Object)null) { if (Preferences.DebugEnabled) { ((MelonBase)<>4__this).LoggerInstance.Msg("Executing " + scanType + " UI texture scan"); } <>4__this.UITextureScanner.RequestFullRescan(); } if ((Object)(object)<>4__this.MaterialChangeMonitor != (Object)null && scanType == "Initial") { MelonCoroutines.Start(<>4__this.DelayedRegistration(2f)); } 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(); } } private GameObject managerGameObject; private bool isInitialized; public static Core Instance { get; private set; } public static string ModFolderPath { get; private set; } public static string ExportsFolderPath { get; private set; } internal ReplacementIndex ReplacementIndex { get; private set; } internal MaterialScanner MaterialScanner { get; private set; } internal UITextureScanner UITextureScanner { get; private set; } internal MaterialChangeMonitor MaterialChangeMonitor { get; private set; } internal InspectorUI InspectorUI { get; private set; } internal FileWatcher FileWatcher { get; private set; } public override void OnInitializeMelon() { Instance = this; Preferences.Initialize(); try { ModFolderPath = Path.Combine(MelonEnvironment.ModsDirectory, "TextureSwapper"); ExportsFolderPath = Path.Combine(ModFolderPath, "_Exports"); Directory.CreateDirectory(ModFolderPath); Directory.CreateDirectory(ExportsFolderPath); ((MelonBase)this).LoggerInstance.Msg("TextureSwapper ready. Waiting for Menu scene to initialize."); if (Preferences.DebugEnabled) { ((MelonBase)this).LoggerInstance.Msg("[Debug] ModsDir=" + MelonEnvironment.ModsDirectory); ((MelonBase)this).LoggerInstance.Msg("[Debug] ModFolderPath=" + ModFolderPath); ((MelonBase)this).LoggerInstance.Msg("[Debug] ExportsFolderPath=" + ExportsFolderPath); } } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Basic setup error: " + ex.Message); } } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown if (!Preferences.Enabled.Value) { return; } if (Preferences.DebugEnabled) { ((MelonBase)this).LoggerInstance.Msg($"[Debug] SceneInitialized: {sceneName} ({buildIndex})"); } if (!isInitialized && sceneName == "Menu") { try { managerGameObject = new GameObject("TextureSwapper_Manager"); Object.DontDestroyOnLoad((Object)(object)managerGameObject); ReplacementIndex = new ReplacementIndex(ModFolderPath); MaterialScanner = managerGameObject.AddComponent<MaterialScanner>(); UITextureScanner = managerGameObject.AddComponent<UITextureScanner>(); MaterialChangeMonitor = managerGameObject.AddComponent<MaterialChangeMonitor>(); InspectorUI = managerGameObject.AddComponent<InspectorUI>(); if ((Object)(object)MaterialScanner != (Object)null) { MaterialScanner.Initialize(ReplacementIndex, MaterialChangeMonitor); } if ((Object)(object)UITextureScanner != (Object)null) { UITextureScanner.Initialize(ReplacementIndex, MaterialChangeMonitor); } if ((Object)(object)MaterialChangeMonitor != (Object)null) { MaterialChangeMonitor.Initialize(ReplacementIndex); } if ((Object)(object)InspectorUI != (Object)null) { InspectorUI.Initialize(ReplacementIndex, MaterialScanner); } FileWatcher = new FileWatcher(ModFolderPath); FileWatcher.Start(); ((MelonBase)this).LoggerInstance.Msg("Initialized. Mod folder: " + ModFolderPath); isInitialized = true; MelonCoroutines.Start(DelayedSceneScan(1f, "Initial")); return; } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Error("Initialization error: " + ex.Message); ((MelonBase)this).LoggerInstance.Error(ex.StackTrace); return; } } if ((Object)(object)MaterialScanner != (Object)null || (Object)(object)UITextureScanner != (Object)null) { MelonCoroutines.Start(DelayedSceneScan(0.5f, "Scene change")); } } [IteratorStateMachine(typeof(<DelayedSceneScan>d__40))] private IEnumerator DelayedSceneScan(float delaySeconds, string scanType) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedSceneScan>d__40(0) { <>4__this = this, delaySeconds = delaySeconds, scanType = scanType }; } [IteratorStateMachine(typeof(<DelayedRegistration>d__41))] private IEnumerator DelayedRegistration(float delaySeconds) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedRegistration>d__41(0) { <>4__this = this, delaySeconds = delaySeconds }; } public override void OnUpdate() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) if (!Preferences.Enabled.Value || !isInitialized) { return; } List<string> list = FileWatcher?.DrainChangedPaths(); if (list != null && list.Count > 0 && ReplacementIndex != null) { if (Preferences.DebugEnabled) { ((MelonBase)this).LoggerInstance.Msg($"[Debug] File changes detected: {list.Count}"); } ReplacementIndex.ReloadChangedFiles(list); if ((Object)(object)MaterialScanner != (Object)null) { MaterialScanner.RequestFullRescan(); } if ((Object)(object)UITextureScanner != (Object)null) { UITextureScanner.HandleFileChanges(list); } } if ((Object)(object)InspectorUI != (Object)null && Input.GetKeyDown(Preferences.InspectorToggleKey)) { InspectorUI.ToggleVisibility(); if (Preferences.DebugEnabled) { ((MelonBase)this).LoggerInstance.Msg("[Debug] Inspector toggle pressed"); } } if ((Object)(object)InspectorUI != (Object)null && Input.GetKeyDown(Preferences.ExportKey)) { InspectorUI.ExportCurrentSelection(); if (Preferences.DebugEnabled) { ((MelonBase)this).LoggerInstance.Msg("[Debug] Export hotkey pressed"); } } } } } namespace TextureSwapper.IO { public sealed class FileWatcher : IDisposable { private readonly string directory; private readonly FileSystemWatcher watcherPng; private readonly FileSystemWatcher watcherJpg; private readonly FileSystemWatcher watcherJpeg; private readonly ConcurrentQueue<string> changedQueue = new ConcurrentQueue<string>(); private volatile bool started; public FileWatcher(string directory) { this.directory = directory; watcherPng = CreateWatcher("*.png"); watcherJpg = CreateWatcher("*.jpg"); watcherJpeg = CreateWatcher("*.jpeg"); } public void Start() { if (started) { return; } started = true; if (Preferences.LiveReload.Value) { Enable(watcherPng); Enable(watcherJpg); Enable(watcherJpeg); if (Preferences.DebugEnabled) { MelonLogger.Msg("[FileWatcher] Started watching: " + directory); } } } private FileSystemWatcher CreateWatcher(string filter) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(directory, filter) { IncludeSubdirectories = true, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; fileSystemWatcher.Changed += OnChanged; fileSystemWatcher.Created += OnChanged; fileSystemWatcher.Deleted += OnChanged; fileSystemWatcher.Renamed += OnRenamed; return fileSystemWatcher; } private void Enable(FileSystemWatcher fsw) { try { fsw.EnableRaisingEvents = true; } catch { } } private void OnChanged(object sender, FileSystemEventArgs e) { if (!string.IsNullOrEmpty(e.FullPath)) { changedQueue.Enqueue(e.FullPath); if (Preferences.DebugEnabled) { MelonLogger.Msg($"[FileWatcher] {e.ChangeType}: {e.FullPath}"); } } } private void OnRenamed(object sender, RenamedEventArgs e) { if (!string.IsNullOrEmpty(e.OldFullPath)) { changedQueue.Enqueue(e.OldFullPath); } if (!string.IsNullOrEmpty(e.FullPath)) { changedQueue.Enqueue(e.FullPath); } if (Preferences.DebugEnabled) { MelonLogger.Msg("[FileWatcher] Renamed: " + e.OldFullPath + " -> " + e.FullPath); } } public List<string> DrainChangedPaths() { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string result; while (changedQueue.TryDequeue(out result)) { hashSet.Add(result); } if (hashSet.Count == 0) { return null; } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[FileWatcher] Drained {hashSet.Count} path(s)"); } return new List<string>(hashSet); } public void Dispose() { try { watcherPng?.Dispose(); } catch { } try { watcherJpg?.Dispose(); } catch { } try { watcherJpeg?.Dispose(); } catch { } } } public static class ExportService { public static void ExportRenderer(GameObject target) { if ((Object)(object)target == (Object)null) { return; } Renderer componentInChildren = target.GetComponentInChildren<Renderer>(); if ((Object)(object)componentInChildren == (Object)null) { return; } Material[] sharedMaterials = componentInChildren.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Target: " + ((Object)target).name); for (int i = 0; i < sharedMaterials.Length; i++) { Material val = sharedMaterials[i]; if ((Object)(object)val == (Object)null) { continue; } stringBuilder.AppendLine($"Material[{i}]: {((Object)val).name}"); for (int j = 0; j < PropertyNames.TexturePropertyNames.Length; j++) { string text = PropertyNames.TexturePropertyNames[j]; if (val.HasProperty(text)) { Texture texture = val.GetTexture(text); Texture2D val2 = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (!((Object)(object)val2 == (Object)null)) { stringBuilder.AppendLine(" " + text + " → " + ((Object)val2).name); } } } } try { string path = $"{Sanitize(((Object)target).name)}_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; string text2 = Path.Combine(Core.ExportsFolderPath, path); File.WriteAllText(text2, stringBuilder.ToString()); MelonLogger.Msg("Exported: " + text2); if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ExportService] Exported target='{((Object)target).name}' materials={sharedMaterials.Length}"); } } catch (Exception ex) { MelonLogger.Error("Export failed: " + ex.Message); } } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return "Object"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { s = s.Replace(oldChar, '_'); } return s; } } } namespace TextureSwapper.UI { public sealed class InspectorUI : MonoBehaviour { private bool visible; private Camera mainCamera; private ReplacementIndex index; private MaterialScanner scanner; private GameObject currentTarget; private string lastCopy; private Vector2 scrollPosition; private float lastInspectTime = 0f; private readonly float inspectInterval = 0.12f; private GameObject lastTarget; private readonly List<Renderer> cachedRenderableRenderers = new List<Renderer>(16); private readonly GUIStyle headerStyle = new GUIStyle(); private readonly GUIStyle smallStyle = new GUIStyle(); public void Initialize(ReplacementIndex index, MaterialScanner scanner) { this.index = index; this.scanner = scanner; mainCamera = Camera.main; ConfigureStyles(); } public void ToggleVisibility() { visible = !visible; } private void ConfigureStyles() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) headerStyle.fontSize = 16; headerStyle.normal.textColor = Color.white; smallStyle.fontSize = 12; smallStyle.normal.textColor = Color.white; } private void Update() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) if (!visible) { return; } if ((Object)(object)mainCamera == (Object)null) { mainCamera = Camera.main; } if ((Object)(object)mainCamera == (Object)null) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup - lastInspectTime < inspectInterval) { return; } lastInspectTime = realtimeSinceStartup; Ray ray = default(Ray); ((Ray)(ref ray))..ctor(((Component)mainCamera).transform.position, ((Component)mainCamera).transform.forward); currentTarget = FindTargetFromRaycast(ray); if (!((Object)(object)currentTarget != (Object)(object)lastTarget)) { return; } cachedRenderableRenderers.Clear(); if ((Object)(object)currentTarget != (Object)null) { Renderer[] componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } bool flag = false; Material[] array2 = sharedMaterials; foreach (Material val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } for (int k = 0; k < PropertyNames.TexturePropertyNames.Length; k++) { string text = PropertyNames.TexturePropertyNames[k]; if (val2.HasProperty(text) && (Object)(object)val2.GetTexture(text) != (Object)null) { flag = true; break; } } if (flag) { cachedRenderableRenderers.Add(val); break; } } } } } lastTarget = currentTarget; } private GameObject FindTargetFromRaycast(Ray ray) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) RaycastHit[] array = Physics.RaycastAll(ray, 4f); if (array.Length == 0) { return null; } RaycastHit[] array2 = array; for (int i = 0; i < array2.Length; i++) { RaycastHit val = array2[i]; if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) { continue; } GameObject gameObject = ((Component)((RaycastHit)(ref val)).collider).gameObject; if (!((Object)gameObject).name.ToLower().Contains("collider")) { if (!ShouldSkipObject(gameObject)) { return gameObject; } GameObject val2 = FindBetterAlternative(gameObject); if ((Object)(object)val2 != (Object)null) { return val2; } } } return null; } private bool ShouldSkipObject(GameObject obj) { if ((Object)(object)obj == (Object)null) { return true; } Renderer component = obj.GetComponent<Renderer>(); if ((Object)(object)component == (Object)null) { return false; } Material[] sharedMaterials = component.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length != 1) { return false; } Material val = sharedMaterials[0]; if ((Object)(object)val == (Object)null) { return false; } return ((Object)val).name == "Lit"; } private GameObject FindBetterAlternative(GameObject original) { if ((Object)(object)original == (Object)null) { return null; } Transform parent = original.transform.parent; if ((Object)(object)parent != (Object)null) { GameObject gameObject = ((Component)parent).gameObject; if (!ShouldSkipObject(gameObject)) { return gameObject; } } Renderer[] componentsInChildren = original.GetComponentsInChildren<Renderer>(false); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { GameObject gameObject2 = ((Component)val).gameObject; if (!((Object)(object)gameObject2 == (Object)(object)original) && !ShouldSkipObject(gameObject2)) { return gameObject2; } } if ((Object)(object)parent != (Object)null) { for (int j = 0; j < parent.childCount; j++) { GameObject gameObject3 = ((Component)parent.GetChild(j)).gameObject; if (!((Object)(object)gameObject3 == (Object)(object)original) && !ShouldSkipObject(gameObject3)) { return gameObject3; } } } return null; } private void OnGUI() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) if (!visible) { return; } Rect val = default(Rect); ((Rect)(ref val))..ctor(20f, 20f, 600f, (float)(Screen.height - 40)); GUI.color = Color.black; GUI.DrawTexture(new Rect(((Rect)(ref val)).x - 2f, ((Rect)(ref val)).y - 2f, ((Rect)(ref val)).width + 4f, ((Rect)(ref val)).height + 4f), (Texture)(object)Texture2D.whiteTexture); GUI.color = new Color(0.12f, 0.12f, 0.12f, 1f); GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 10f, ((Rect)(ref val)).y + 10f, ((Rect)(ref val)).width - 20f, ((Rect)(ref val)).height - 20f)); scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true, Array.Empty<GUILayoutOption>()); GUILayout.Label("TextureSwapper Inspector", headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(6f); if ((Object)(object)currentTarget == (Object)null) { GUILayout.Label("Look at an object to inspect its materials.", smallStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } GUILayout.Label("Target: " + GetPath(currentTarget), smallStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.Label("Drop files named by material or material__Property: e.g. MyMat.png or MyMat__EmissionMap.png", smallStyle, Array.Empty<GUILayoutOption>()); GUILayout.Label("For UI elements: SpriteName.png or TextureName.png", smallStyle, Array.Empty<GUILayoutOption>()); Renderer[] componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { GUILayout.Label("No Renderers found in hierarchy.", smallStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } bool flag = false; foreach (Renderer val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null) { continue; } Material[] sharedMaterials = val2.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } flag = true; string relativePath = GetRelativePath(currentTarget, ((Component)val2).gameObject); if (relativePath != ".") { GUILayout.Space(6f); GUILayout.Label("Object: " + relativePath, headerStyle, Array.Empty<GUILayoutOption>()); } for (int j = 0; j < sharedMaterials.Length; j++) { Material val3 = sharedMaterials[j]; if ((Object)(object)val3 == (Object)null) { continue; } GUILayout.Space(4f); GUILayout.Label($"Material [{j}]: {((Object)val3).name}", smallStyle, Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Copy Material Name", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { GUIUtility.systemCopyBuffer = ((Object)val3).name; lastCopy = ((Object)val3).name; } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Suggestion: Mods/TextureSwapper/" + ((Object)val3).name + ".png", smallStyle, Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Copy", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) })) { GUIUtility.systemCopyBuffer = ((Object)val3).name + ".png"; lastCopy = ((Object)val3).name + ".png"; } GUILayout.EndHorizontal(); for (int k = 0; k < PropertyNames.TexturePropertyNames.Length; k++) { string text = PropertyNames.TexturePropertyNames[k]; if (!val3.HasProperty(text)) { continue; } Texture texture = val3.GetTexture(text); Texture2D val4 = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (!((Object)(object)val4 == (Object)null)) { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label(text + " → " + (((Object)(object)val4 != (Object)null) ? ((Object)val4).name : "<null>"), smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(420f) }); if (GUILayout.Button("Copy Tex Name", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }) && (Object)(object)val4 != (Object)null) { GUIUtility.systemCopyBuffer = ((Object)val4).name; lastCopy = ((Object)val4).name; } if (GUILayout.Button("Copy Mat__Prop", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { string text3 = (GUIUtility.systemCopyBuffer = ((Object)val3).name + "__" + text + ".png"); lastCopy = text3; } GUILayout.EndHorizontal(); } } } } if (!flag) { GUILayout.Label("No materials found in hierarchy.", smallStyle, Array.Empty<GUILayoutOption>()); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } Image[] componentsInChildren2 = currentTarget.GetComponentsInChildren<Image>(true); RawImage[] componentsInChildren3 = currentTarget.GetComponentsInChildren<RawImage>(true); if ((componentsInChildren2 != null && componentsInChildren2.Length != 0) || (componentsInChildren3 != null && componentsInChildren3.Length != 0)) { GUILayout.Space(10f); GUILayout.Label("UI Elements", headerStyle, Array.Empty<GUILayoutOption>()); GUILayout.Space(4f); GUILayout.Label("Drop files named by sprite/texture: e.g. MySprite.png or MyTexture.png", smallStyle, Array.Empty<GUILayoutOption>()); if (componentsInChildren2 != null && componentsInChildren2.Length != 0) { for (int l = 0; l < componentsInChildren2.Length; l++) { Image val5 = componentsInChildren2[l]; if ((Object)(object)val5 == (Object)null) { continue; } Sprite sprite = val5.sprite; if (!((Object)(object)sprite == (Object)null)) { GUILayout.Space(4f); GUILayout.Label($"Image [{l}]: {((Object)((Component)val5).gameObject).name}", smallStyle, Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Copy Sprite Name", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { GUIUtility.systemCopyBuffer = ((Object)sprite).name; lastCopy = ((Object)sprite).name; } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Sprite: " + (((Object)(object)sprite != (Object)null) ? ((Object)sprite).name : "<null>"), smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(350f) }); if (GUILayout.Button("Copy Sprite__File", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) })) { string text5 = (GUIUtility.systemCopyBuffer = ((Object)sprite).name + ".png"); lastCopy = text5; } GUILayout.EndHorizontal(); } } } if (componentsInChildren3 != null && componentsInChildren3.Length != 0) { for (int m = 0; m < componentsInChildren3.Length; m++) { RawImage val6 = componentsInChildren3[m]; if ((Object)(object)val6 == (Object)null) { continue; } Texture texture2 = val6.texture; Texture2D val7 = (Texture2D)(object)((texture2 is Texture2D) ? texture2 : null); if (!((Object)(object)val7 == (Object)null)) { GUILayout.Space(4f); GUILayout.Label($"RawImage [{m}]: {((Object)((Component)val6).gameObject).name}", smallStyle, Array.Empty<GUILayoutOption>()); if (GUILayout.Button("Copy Texture Name", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(140f) })) { GUIUtility.systemCopyBuffer = ((Object)val7).name; lastCopy = ((Object)val7).name; } GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUILayout.Label("Texture: " + (((Object)(object)val7 != (Object)null) ? ((Object)val7).name : "<null>"), smallStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(350f) }); if (GUILayout.Button("Copy Texture__File", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) })) { string text7 = (GUIUtility.systemCopyBuffer = ((Object)val7).name + ".png"); lastCopy = text7; } GUILayout.EndHorizontal(); } } } } if (!string.IsNullOrEmpty(lastCopy)) { GUILayout.Space(4f); GUILayout.Label("Copied: " + lastCopy, smallStyle, Array.Empty<GUILayoutOption>()); } GUILayout.EndScrollView(); GUILayout.EndArea(); } public void ExportCurrentSelection() { if ((Object)(object)currentTarget == (Object)null) { return; } Renderer[] componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); Image[] componentsInChildren2 = currentTarget.GetComponentsInChildren<Image>(true); RawImage[] componentsInChildren3 = currentTarget.GetComponentsInChildren<RawImage>(true); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Target: " + GetPath(currentTarget)); stringBuilder.AppendLine($"Renderer Count: {((componentsInChildren != null) ? componentsInChildren.Length : 0)}"); stringBuilder.AppendLine($"Image Count: {((componentsInChildren2 != null) ? componentsInChildren2.Length : 0)}"); stringBuilder.AppendLine($"RawImage Count: {((componentsInChildren3 != null) ? componentsInChildren3.Length : 0)}"); stringBuilder.AppendLine(); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } Material[] sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { continue; } string relativePath = GetRelativePath(currentTarget, ((Component)val).gameObject); stringBuilder.AppendLine("Object: " + relativePath); for (int j = 0; j < sharedMaterials.Length; j++) { Material val2 = sharedMaterials[j]; if ((Object)(object)val2 == (Object)null) { continue; } stringBuilder.AppendLine($" Material[{j}]: {((Object)val2).name}"); for (int k = 0; k < PropertyNames.TexturePropertyNames.Length; k++) { string text = PropertyNames.TexturePropertyNames[k]; if (val2.HasProperty(text)) { Texture texture = val2.GetTexture(text); Texture2D val3 = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (!((Object)(object)val3 == (Object)null)) { stringBuilder.AppendLine(" " + text + " → " + ((Object)val3).name); stringBuilder.AppendLine(" Suggestion: Mods/TextureSwapper/" + ((Object)val3).name + ".png"); } } } } stringBuilder.AppendLine(); } if (componentsInChildren2 != null && componentsInChildren2.Length != 0) { stringBuilder.AppendLine("=== UI IMAGES ==="); foreach (Image val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null)) { Sprite sprite = val4.sprite; string relativePath2 = GetRelativePath(currentTarget, ((Component)val4).gameObject); stringBuilder.AppendLine("Image: " + relativePath2); if ((Object)(object)sprite != (Object)null) { stringBuilder.AppendLine(" Sprite: " + ((Object)sprite).name); stringBuilder.AppendLine(" Suggestion: Mods/TextureSwapper/" + ((Object)sprite).name + ".png"); } else { stringBuilder.AppendLine(" Sprite: <null>"); } stringBuilder.AppendLine(); } } } if (componentsInChildren3 != null && componentsInChildren3.Length != 0) { stringBuilder.AppendLine("=== UI RAW IMAGES ==="); foreach (RawImage val5 in componentsInChildren3) { if (!((Object)(object)val5 == (Object)null)) { Texture texture2 = val5.texture; Texture2D val6 = (Texture2D)(object)((texture2 is Texture2D) ? texture2 : null); string relativePath3 = GetRelativePath(currentTarget, ((Component)val5).gameObject); stringBuilder.AppendLine("RawImage: " + relativePath3); if ((Object)(object)val6 != (Object)null) { stringBuilder.AppendLine(" Texture: " + ((Object)val6).name); stringBuilder.AppendLine(" Suggestion: Mods/TextureSwapper/" + ((Object)val6).name + ".png"); } else { stringBuilder.AppendLine(" Texture: <null>"); } stringBuilder.AppendLine(); } } } try { string path = $"{Sanitize(((Object)currentTarget).name)}_{DateTime.Now:yyyyMMdd_HHmmss}.txt"; string text2 = Path.Combine(Core.ExportsFolderPath, path); File.WriteAllText(text2, stringBuilder.ToString()); MelonLogger.Msg("Exported: " + text2); } catch (Exception ex) { MelonLogger.Error("Export failed: " + ex.Message); } } private static string GetPath(GameObject go) { if ((Object)(object)go == (Object)null) { return "<null>"; } string text = ((Object)go).name; Transform val = go.transform; while ((Object)(object)val.parent != (Object)null) { text = ((Object)val.parent).name + "/" + text; val = val.parent; } return text; } private static string Sanitize(string s) { if (string.IsNullOrEmpty(s)) { return "Object"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { s = s.Replace(oldChar, '_'); } return s; } private static string GetRelativePath(GameObject root, GameObject child) { if ((Object)(object)root == (Object)null || (Object)(object)child == (Object)null) { return "?"; } if ((Object)(object)root == (Object)(object)child) { return "."; } StringBuilder stringBuilder = new StringBuilder(); Transform val = child.transform; while ((Object)(object)val != (Object)null && (Object)(object)((Component)val).gameObject != (Object)(object)root) { if (stringBuilder.Length > 0) { stringBuilder.Insert(0, "/"); } stringBuilder.Insert(0, ((Object)val).name); val = val.parent; } if ((Object)(object)val == (Object)null) { return "?"; } return (stringBuilder.Length > 0) ? stringBuilder.ToString() : "."; } } } namespace TextureSwapper.Runtime { public sealed class MaterialChangeMonitor : MonoBehaviour { private readonly Dictionary<Renderer, Material[]> originalMaterials = new Dictionary<Renderer, Material[]>(128); private readonly Dictionary<Image, Sprite> originalSprites = new Dictionary<Image, Sprite>(64); private readonly Dictionary<RawImage, Texture> originalTextures = new Dictionary<RawImage, Texture>(32); private readonly List<Renderer> monitoredRenderers = new List<Renderer>(128); private readonly List<Image> monitoredImages = new List<Image>(64); private readonly List<RawImage> monitoredRawImages = new List<RawImage>(32); private readonly HashSet<Renderer> renderersWithChanges = new HashSet<Renderer>(); private readonly HashSet<Image> imagesWithChanges = new HashSet<Image>(); private readonly HashSet<RawImage> rawImagesWithChanges = new HashSet<RawImage>(); private ReplacementIndex replacementIndex; private float nextCheckTime; private float checkInterval; private bool isInitialized; public void Initialize(ReplacementIndex index) { replacementIndex = index; checkInterval = Preferences.DynamicChangeCheckInterval?.Value ?? 5f; isInitialized = true; if (Preferences.DebugEnabled) { MelonLogger.Msg($"[MaterialChangeMonitor] Initialized with check interval: {checkInterval}s"); } } private void Update() { if (Preferences.Enabled.Value && isInitialized && replacementIndex != null && Preferences.MonitorDynamicChanges.Value && Time.time >= nextCheckTime) { CheckForMaterialChanges(); CheckForImageChanges(); CheckForRawImageChanges(); ProcessMaterialChanges(); ProcessImageChanges(); ProcessRawImageChanges(); nextCheckTime = Time.time + checkInterval; } } private void CheckForMaterialChanges() { renderersWithChanges.Clear(); foreach (KeyValuePair<Renderer, Material[]> originalMaterial in originalMaterials) { Renderer key = originalMaterial.Key; Material[] value = originalMaterial.Value; if ((Object)(object)key == (Object)null) { continue; } Material[] sharedMaterials = key.sharedMaterials; if (sharedMaterials == null || value == null) { continue; } if (sharedMaterials.Length != value.Length) { renderersWithChanges.Add(key); continue; } for (int i = 0; i < sharedMaterials.Length; i++) { if (i >= value.Length || (Object)(object)sharedMaterials[i] != (Object)(object)value[i] || ((Object)(object)sharedMaterials[i] != (Object)null && (Object)(object)value[i] != (Object)null && ((Object)sharedMaterials[i]).name != ((Object)value[i]).name)) { renderersWithChanges.Add(key); break; } } } } private void CheckForImageChanges() { imagesWithChanges.Clear(); foreach (KeyValuePair<Image, Sprite> originalSprite in originalSprites) { Image key = originalSprite.Key; Sprite value = originalSprite.Value; if (!((Object)(object)key == (Object)null)) { Sprite sprite = key.sprite; if ((Object)(object)sprite != (Object)(object)value || ((Object)(object)sprite != (Object)null && (Object)(object)value != (Object)null && ((Object)sprite).name != ((Object)value).name)) { imagesWithChanges.Add(key); } } } } private void CheckForRawImageChanges() { rawImagesWithChanges.Clear(); foreach (KeyValuePair<RawImage, Texture> originalTexture in originalTextures) { RawImage key = originalTexture.Key; Texture value = originalTexture.Value; if (!((Object)(object)key == (Object)null)) { Texture texture = key.texture; if ((Object)(object)texture != (Object)(object)value || ((Object)(object)texture != (Object)null && (Object)(object)value != (Object)null && ((Object)texture).name != ((Object)value).name)) { rawImagesWithChanges.Add(key); } } } } private void ProcessMaterialChanges() { foreach (Renderer renderersWithChange in renderersWithChanges) { if ((Object)(object)renderersWithChange == (Object)null) { continue; } Material[] sharedMaterials = renderersWithChange.sharedMaterials; if (sharedMaterials == null) { continue; } bool flag = false; foreach (Material val in sharedMaterials) { if ((Object)(object)val == (Object)null) { continue; } bool flag2 = false; flag2 |= ReplacementApplicator.ApplyByMaterialMapping(renderersWithChange, val, replacementIndex); if (!flag2) { flag2 |= ReplacementApplicator.ApplyAllTextureProperties(val, replacementIndex); } if (flag2) { flag = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Reapplied replacements to material '" + ((Object)val).name + "' on '" + ((Object)((Component)renderersWithChange).gameObject).name + "' after material change"); } } } if (flag) { originalMaterials[renderersWithChange] = sharedMaterials.ToArray(); } } } private void ProcessImageChanges() { foreach (Image imagesWithChange in imagesWithChanges) { if (!((Object)(object)imagesWithChange == (Object)null) && ReplacementApplicator.ApplyToImage(imagesWithChange, replacementIndex)) { originalSprites[imagesWithChange] = imagesWithChange.sprite; if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Reapplied replacements to Image '" + ((Object)((Component)imagesWithChange).gameObject).name + "' after sprite change"); } } } } private void ProcessRawImageChanges() { foreach (RawImage rawImagesWithChange in rawImagesWithChanges) { if (!((Object)(object)rawImagesWithChange == (Object)null) && ReplacementApplicator.ApplyToRawImage(rawImagesWithChange, replacementIndex)) { originalTextures[rawImagesWithChange] = rawImagesWithChange.texture; if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Reapplied replacements to RawImage '" + ((Object)((Component)rawImagesWithChange).gameObject).name + "' after texture change"); } } } } public void RegisterRenderer(Renderer renderer) { if ((Object)(object)renderer == (Object)null || !isInitialized || !Preferences.MonitorDynamicChanges.Value || originalMaterials.ContainsKey(renderer)) { return; } Material[] sharedMaterials = renderer.sharedMaterials; if (sharedMaterials != null) { originalMaterials[renderer] = sharedMaterials.ToArray(); monitoredRenderers.Add(renderer); if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Registered renderer: " + ((Object)((Component)renderer).gameObject).name); } } } public void RegisterImage(Image image) { if ((Object)(object)image == (Object)null || !isInitialized || !Preferences.MonitorDynamicChanges.Value || originalSprites.ContainsKey(image)) { return; } Sprite sprite = image.sprite; if ((Object)(object)sprite != (Object)null) { originalSprites[image] = sprite; monitoredImages.Add(image); if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Registered image: " + ((Object)((Component)image).gameObject).name); } } } public void RegisterRawImage(RawImage rawImage) { if ((Object)(object)rawImage == (Object)null || !isInitialized || !Preferences.MonitorDynamicChanges.Value || originalTextures.ContainsKey(rawImage)) { return; } Texture texture = rawImage.texture; if ((Object)(object)texture != (Object)null) { originalTextures[rawImage] = texture; monitoredRawImages.Add(rawImage); if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Registered raw image: " + ((Object)((Component)rawImage).gameObject).name); } } } public void CleanupDestroyedObjects() { List<Renderer> list = originalMaterials.Keys.Where((Renderer r) => (Object)(object)r == (Object)null).ToList(); foreach (Renderer item in list) { originalMaterials.Remove(item); monitoredRenderers.Remove(item); } List<Image> list2 = originalSprites.Keys.Where((Image i) => (Object)(object)i == (Object)null).ToList(); foreach (Image item2 in list2) { originalSprites.Remove(item2); monitoredImages.Remove(item2); } List<RawImage> list3 = originalTextures.Keys.Where((RawImage r) => (Object)(object)r == (Object)null).ToList(); foreach (RawImage item3 in list3) { originalTextures.Remove(item3); monitoredRawImages.Remove(item3); } } public void ScanAndRegisterAll() { if (!isInitialized || !Preferences.MonitorDynamicChanges.Value) { return; } Renderer[] array = Object.FindObjectsOfType<Renderer>(true); Renderer[] array2 = array; foreach (Renderer val in array2) { if ((Object)(object)val != (Object)null && val.sharedMaterials != null && val.sharedMaterials.Length != 0) { RegisterRenderer(val); } } Image[] array3 = Object.FindObjectsOfType<Image>(true); Image[] array4 = array3; foreach (Image val2 in array4) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2.sprite != (Object)null) { RegisterImage(val2); } } RawImage[] array5 = Object.FindObjectsOfType<RawImage>(true); RawImage[] array6 = array5; foreach (RawImage val3 in array6) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.texture != (Object)null) { RegisterRawImage(val3); } } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[MaterialChangeMonitor] Registered {monitoredRenderers.Count} renderers, {monitoredImages.Count} images, {monitoredRawImages.Count} raw images for monitoring"); } } } public static class PropertyNames { public static readonly string[] TexturePropertyNames = new string[17] { "_BaseMap", "_MainTex", "_BaseColorMap", "_BumpMap", "_NormalMap", "_MetallicGlossMap", "_MetallicSpecGlossMap", "_SpecGlossMap", "_ParallaxMap", "_OcclusionMap", "_DetailAlbedoMap", "_DetailNormalMap", "_EmissionMap", "_EmissiveColorMap", "_SpecularGlossMap", "_SmoothnessTexture", "_DetailMask" }; public static readonly string[] EmissionPropertyNames = new string[2] { "_EmissionMap", "_EmissiveColorMap" }; } public static class ReplacementApplicator { public static bool ApplyAllTextureProperties(Material material, ReplacementIndex index) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) bool result = false; if ((Object)(object)material == (Object)null) { return false; } string[] texturePropertyNames = PropertyNames.TexturePropertyNames; foreach (string text in texturePropertyNames) { if (!material.HasProperty(text)) { continue; } Texture texture = material.GetTexture(text); Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if ((Object)(object)val == (Object)null || !index.TryGetByTextureName(((Object)val).name, out var texture2)) { continue; } Vector2 textureScale = material.GetTextureScale(text); Vector2 textureOffset = material.GetTextureOffset(text); material.SetTexture(text, (Texture)(object)texture2); material.SetTextureScale(text, textureScale); material.SetTextureOffset(text, textureOffset); material.enabledKeywords = null; material.color = Color.white; for (int j = 0; j < PropertyNames.EmissionPropertyNames.Length; j++) { if (text == PropertyNames.EmissionPropertyNames[j]) { material.EnableKeyword("_EMISSION"); break; } } result = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[ApplyAllTextureProperties] " + ((Object)material).name + " set " + text + " via texture-name mapping"); } } return result; } public static bool ApplyByMaterialMapping(Renderer renderer, Material material, ReplacementIndex index) { if ((Object)(object)renderer == (Object)null || (Object)(object)material == (Object)null || index == null) { return false; } bool result = false; for (int i = 0; i < PropertyNames.TexturePropertyNames.Length; i++) { string text = PropertyNames.TexturePropertyNames[i]; if (material.HasProperty(text) && index.TryGetByMaterialProperty(((Object)material).name, text, out var texture) && (Object)(object)texture != (Object)null) { ApplyTexturePreserveTiling(material, text, texture); if (Preferences.DebugEnabled) { MelonLogger.Msg("[ApplyByMaterialMapping] " + ((Object)((Component)renderer).gameObject).name + "." + ((Object)material).name + " set " + text + " via material__property mapping"); } result = true; } } if (index.TryGetByMaterialName(((Object)material).name, out var texture2) && (Object)(object)texture2 != (Object)null) { string text2 = FindPrimaryTextureProperty(material); if (text2 != null) { ApplyTexturePreserveTiling(material, text2, texture2); if (Preferences.DebugEnabled) { MelonLogger.Msg("[ApplyByMaterialMapping] " + ((Object)((Component)renderer).gameObject).name + "." + ((Object)material).name + " set " + text2 + " via material mapping"); } result = true; } } return result; } private static void ApplyTexturePreserveTiling(Material material, string prop, Texture2D replacement) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Vector2 textureScale = material.GetTextureScale(prop); Vector2 textureOffset = material.GetTextureOffset(prop); int num = Shader.PropertyToID(prop); material.SetTexture(num, (Texture)(object)replacement); material.SetTextureScale(num, textureScale); material.SetTextureOffset(num, textureOffset); material.enabledKeywords = null; material.color = Color.white; for (int i = 0; i < PropertyNames.EmissionPropertyNames.Length; i++) { if (prop == PropertyNames.EmissionPropertyNames[i]) { material.EnableKeyword("_EMISSION"); break; } } } private static string FindPrimaryTextureProperty(Material material) { if (material.HasProperty("_BaseMap")) { return "_BaseMap"; } if (material.HasProperty("_MainTex")) { return "_MainTex"; } for (int i = 0; i < PropertyNames.TexturePropertyNames.Length; i++) { string text = PropertyNames.TexturePropertyNames[i]; if (material.HasProperty(text)) { return text; } } return null; } public static bool ApplyToImage(Image image, ReplacementIndex index) { if ((Object)(object)image == (Object)null || index == null) { return false; } bool result = false; Sprite sprite = image.sprite; if ((Object)(object)sprite == (Object)null) { return false; } Texture2D texture = sprite.texture; if ((Object)(object)texture == (Object)null) { return false; } Texture2D texture2 = null; string text = ""; if (index.TryGetByTextureName(((Object)sprite).name, out texture2)) { text = "sprite-name mapping: " + ((Object)sprite).name; } else if (index.TryGetByTextureName(((Object)texture).name, out texture2)) { text = "texture-name mapping: " + ((Object)texture).name; } if ((Object)(object)texture2 != (Object)null) { try { Sprite val = CreateCompatibleSprite(texture2, sprite); ((Object)val).name = ((Object)sprite).name; image.sprite = val; ForceUIUpdate(image); result = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[ApplyToImage] " + ((Object)((Component)image).gameObject).name + " set sprite via " + text); } } catch (Exception ex) { MelonLogger.Error("[ApplyToImage] Failed to create replacement sprite for " + ((Object)((Component)image).gameObject).name + ": " + ex.Message); } } return result; } private static void ForceUIUpdate(Image image) { if ((Object)(object)image == (Object)null) { return; } try { Canvas componentInParent = ((Component)image).GetComponentInParent<Canvas>(); if ((Object)(object)componentInParent != (Object)null) { MelonPreferences_Entry<bool> aggressiveUIUpdates = Preferences.AggressiveUIUpdates; if (aggressiveUIUpdates != null && aggressiveUIUpdates.Value) { ((Behaviour)componentInParent).enabled = false; ((Behaviour)componentInParent).enabled = true; } else { Canvas.ForceUpdateCanvases(); } } LayoutElement component = ((Component)image).GetComponent<LayoutElement>(); if ((Object)(object)component != (Object)null) { Transform transform = ((Component)component).transform; LayoutRebuilder.ForceRebuildLayoutImmediate((RectTransform)(object)((transform is RectTransform) ? transform : null)); } ((Graphic)image).SetAllDirty(); ((Graphic)image).Rebuild((CanvasUpdate)3); } catch (Exception ex) { if (Preferences.DebugEnabled) { MelonLogger.Warning("[ForceUIUpdate] Error updating UI for " + ((Object)((Component)image).gameObject).name + ": " + ex.Message); } } } private static Sprite CreateCompatibleSprite(Texture2D replacement, Sprite originalSprite) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); if (((Texture)replacement).width >= ((Texture)originalSprite.texture).width && ((Texture)replacement).height >= ((Texture)originalSprite.texture).height) { Rect rect = originalSprite.rect; float x = ((Rect)(ref rect)).x; float num = ((Texture)replacement).width; rect = originalSprite.rect; float num2 = Mathf.Min(x, num - ((Rect)(ref rect)).width); rect = originalSprite.rect; float y = ((Rect)(ref rect)).y; float num3 = ((Texture)replacement).height; rect = originalSprite.rect; float num4 = Mathf.Min(y, num3 - ((Rect)(ref rect)).height); rect = originalSprite.rect; float num5 = Mathf.Min(((Rect)(ref rect)).width, (float)((Texture)replacement).width); rect = originalSprite.rect; float num6 = Mathf.Min(((Rect)(ref rect)).height, (float)((Texture)replacement).height); if (num5 > 0f && num6 > 0f) { ((Rect)(ref val))..ctor(num2, num4, num5, num6); } else { ((Rect)(ref val))..ctor(0f, 0f, (float)((Texture)replacement).width, (float)((Texture)replacement).height); } } else { ((Rect)(ref val))..ctor(0f, 0f, (float)((Texture)replacement).width, (float)((Texture)replacement).height); } return Sprite.Create(replacement, val, originalSprite.pivot, originalSprite.pixelsPerUnit, 0u, (SpriteMeshType)0, originalSprite.border, false); } public static bool ApplyToRawImage(RawImage rawImage, ReplacementIndex index) { if ((Object)(object)rawImage == (Object)null || index == null) { return false; } bool result = false; Texture texture = rawImage.texture; Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if ((Object)(object)val == (Object)null) { return false; } if (index.TryGetByTextureName(((Object)val).name, out var texture2)) { rawImage.texture = (Texture)(object)texture2; result = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[ApplyToRawImage] " + ((Object)((Component)rawImage).gameObject).name + " set texture via texture-name mapping: " + ((Object)val).name); } } return result; } } public sealed class ReplacementIndex { private sealed class LoadedTexture { public string FilePath; public Texture2D Texture; } private readonly string baseDirectory; private readonly string exportsDirectory; private readonly Dictionary<string, LoadedTexture> keyToTexture; private readonly Dictionary<string, LoadedTexture> materialNameToTexture; private readonly Dictionary<(string materialName, string property), LoadedTexture> materialPropertyToTexture; private static readonly HashSet<string> SupportedExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".png", ".jpg", ".jpeg" }; public bool HasAnyEntries => (keyToTexture != null && keyToTexture.Count > 0) || (materialNameToTexture != null && materialNameToTexture.Count > 0) || (materialPropertyToTexture != null && materialPropertyToTexture.Count > 0); private static string NormalizeMaterialName(string name) { if (string.IsNullOrEmpty(name)) { return name; } string text = name.Trim(); if (text.EndsWith(" (Instance)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - " (Instance)".Length); } if (text.EndsWith(" (Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - " (Clone)".Length); } return text.Trim(); } public ReplacementIndex(string baseDirectory) { this.baseDirectory = baseDirectory; exportsDirectory = Path.Combine(baseDirectory, "_Exports"); Directory.CreateDirectory(this.baseDirectory); Directory.CreateDirectory(exportsDirectory); keyToTexture = new Dictionary<string, LoadedTexture>(StringComparer.OrdinalIgnoreCase); materialNameToTexture = new Dictionary<string, LoadedTexture>(StringComparer.OrdinalIgnoreCase); materialPropertyToTexture = new Dictionary<(string, string), LoadedTexture>(); LoadAll(); } private static bool IsTextureValid(Texture2D texture) { return (Object)(object)texture != (Object)null && texture != null; } private bool ValidateOrReloadTexture(LoadedTexture loadedTexture) { if (loadedTexture == null) { return false; } if (IsTextureValid(loadedTexture.Texture)) { return true; } if (string.IsNullOrEmpty(loadedTexture.FilePath) || !File.Exists(loadedTexture.FilePath)) { return false; } try { Texture2D val = LoadTextureFromFile(loadedTexture.FilePath); if ((Object)(object)val != (Object)null) { loadedTexture.Texture = val; if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Reloaded destroyed texture from '" + loadedTexture.FilePath + "'"); } return true; } } catch (Exception ex) { MelonLogger.Error("[ReplacementIndex] Failed to reload texture from '" + loadedTexture.FilePath + "': " + ex.Message); } return false; } public bool TryGetByTextureName(string textureName, out Texture2D texture) { texture = null; if (string.IsNullOrEmpty(textureName)) { return false; } if (keyToTexture.TryGetValue(textureName, out var value) && ValidateOrReloadTexture(value)) { texture = value.Texture; return true; } return false; } public bool HasTexture(string textureName) { if (string.IsNullOrEmpty(textureName)) { return false; } return keyToTexture.ContainsKey(textureName); } public void ReloadChangedFiles(ICollection<string> filePaths) { if (filePaths == null || filePaths.Count == 0) { return; } foreach (string filePath in filePaths) { if (string.IsNullOrEmpty(filePath)) { continue; } string extension = Path.GetExtension(filePath); if (!SupportedExtensions.Contains(extension)) { continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); if (!File.Exists(filePath)) { if (keyToTexture.TryGetValue(fileNameWithoutExtension, out var value)) { if ((Object)(object)value.Texture != (Object)null) { Object.Destroy((Object)(object)value.Texture); } keyToTexture.Remove(fileNameWithoutExtension); } string[] array = fileNameWithoutExtension.Split(new string[1] { "__" }, StringSplitOptions.None); if (array.Length == 1) { string key = NormalizeMaterialName(array[0]); materialNameToTexture.Remove(key); } else if (array.Length >= 2) { string item = NormalizeMaterialName(array[0]); string item2 = array[1]; materialPropertyToTexture.Remove((item, item2)); } if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Deleted mapping for '" + fileNameWithoutExtension + "'"); } } else { LoadOrReplace(fileNameWithoutExtension, filePath); if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Loaded/Updated '" + fileNameWithoutExtension + "' from '" + filePath + "'"); } } } } private void LoadAll() { if (!Directory.Exists(baseDirectory)) { return; } IEnumerable<string> enumerable = Directory.EnumerateFiles(baseDirectory, "*.*", SearchOption.AllDirectories); foreach (string item in enumerable) { if (item.IndexOf("_Exports", StringComparison.OrdinalIgnoreCase) >= 0) { continue; } string extension = Path.GetExtension(item); if (SupportedExtensions.Contains(extension)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); LoadOrReplace(fileNameWithoutExtension, item); if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Preloaded '" + fileNameWithoutExtension + "' from '" + item + "'"); } } } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Finished loading. Keys={keyToTexture.Count} materialNames={materialNameToTexture.Count} materialProperties={materialPropertyToTexture.Count}"); } } private void LoadOrReplace(string key, string filePath) { try { Texture2D val = LoadTextureFromFile(filePath); if ((Object)(object)val == (Object)null) { return; } if (keyToTexture.TryGetValue(key, out var value) && value != null) { if ((Object)(object)value.Texture != (Object)null) { Object.Destroy((Object)(object)value.Texture); } value.FilePath = filePath; value.Texture = val; } else { keyToTexture[key] = new LoadedTexture { FilePath = filePath, Texture = val }; } LoadedTexture value2 = keyToTexture[key]; string[] array = key.Split(new string[1] { "__" }, StringSplitOptions.None); if (array.Length == 1) { string key2 = NormalizeMaterialName(array[0]); materialNameToTexture[key2] = value2; } else if (array.Length >= 2) { string item = NormalizeMaterialName(array[0]); string item2 = array[1]; materialPropertyToTexture[(item, item2)] = value2; } if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Indexed key='" + key + "' mat='" + ((array.Length != 0) ? NormalizeMaterialName(array[0]) : "-") + "' prop='" + ((array.Length > 1) ? array[1] : "-") + "'"); } } catch (Exception ex) { MelonLogger.Error("[ReplacementIndex] Failed to load '" + filePath + "': " + ex.Message); } } private static Texture2D LoadTextureFromFile(string filePath) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown try { byte[] array = File.ReadAllBytes(filePath); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true); if (!ImageConversion.LoadImage(val, array, false)) { Object.Destroy((Object)(object)val); return null; } ((Object)val).name = Path.GetFileNameWithoutExtension(filePath); ((Texture)val).wrapMode = (TextureWrapMode)0; ((Texture)val).filterMode = (FilterMode)1; Object.DontDestroyOnLoad((Object)(object)val); return val; } catch (Exception ex) { MelonLogger.Error("[ReplacementIndex] Error reading '" + filePath + "': " + ex.Message); return null; } } public bool TryGetByMaterialName(string materialName, out Texture2D texture) { texture = null; if (string.IsNullOrEmpty(materialName)) { return false; } string key = NormalizeMaterialName(materialName); if (materialNameToTexture.TryGetValue(key, out var value) && ValidateOrReloadTexture(value)) { texture = value.Texture; return true; } return false; } public bool TryGetByMaterialProperty(string materialName, string propertyName, out Texture2D texture) { texture = null; if (string.IsNullOrEmpty(materialName) || string.IsNullOrEmpty(propertyName)) { return false; } string item = NormalizeMaterialName(materialName); if (materialPropertyToTexture.TryGetValue((item, propertyName), out var value) && ValidateOrReloadTexture(value)) { texture = value.Texture; return true; } return false; } public void ValidateAndReloadAllTextures() { int num = 0; int num2 = 0; List<string> list = new List<string>(); foreach (KeyValuePair<string, LoadedTexture> item in keyToTexture) { num2++; bool flag = IsTextureValid(item.Value?.Texture); if (!ValidateOrReloadTexture(item.Value)) { list.Add(item.Key); } else if (!flag && IsTextureValid(item.Value.Texture)) { num++; } } foreach (string item2 in list) { keyToTexture.Remove(item2); } List<string> list2 = new List<string>(); foreach (KeyValuePair<string, LoadedTexture> item3 in materialNameToTexture) { num2++; bool flag2 = IsTextureValid(item3.Value?.Texture); if (!ValidateOrReloadTexture(item3.Value)) { list2.Add(item3.Key); } else if (!flag2 && IsTextureValid(item3.Value.Texture)) { num++; } } foreach (string item4 in list2) { materialNameToTexture.Remove(item4); } List<(string, string)> list3 = new List<(string, string)>(); foreach (KeyValuePair<(string, string), LoadedTexture> item5 in materialPropertyToTexture) { num2++; bool flag3 = IsTextureValid(item5.Value?.Texture); if (!ValidateOrReloadTexture(item5.Value)) { list3.Add(item5.Key); } else if (!flag3 && IsTextureValid(item5.Value.Texture)) { num++; } } foreach (var item6 in list3) { materialPropertyToTexture.Remove(item6); } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Validated {num2} textures, reloaded {num}, removed {list.Count + list2.Count + list3.Count} invalid entries"); } } } public sealed class MaterialScanner : MonoBehaviour { private readonly List<Renderer> rendererBuffer = new List<Renderer>(256); private readonly HashSet<int> processedMaterialIds = new HashSet<int>(); private ReplacementIndex replacementIndex; private MaterialChangeMonitor materialChangeMonitor; private bool fullRescanRequested; private int lastRendererIndex; private float nextAllowedRescanTime; private readonly List<Camera> cameraCache = new List<Camera>(4); private bool scanningInProgress; private int totalRenderersFound; private int totalRenderersProcessed; private static bool ShouldSkipRenderer(Renderer renderer) { if ((Object)(object)renderer == (Object)null) { return true; } GameObject gameObject = ((Component)renderer).gameObject; if ((Object)(object)gameObject != (Object)null) { string name = ((Object)gameObject).name; if (!string.IsNullOrEmpty(name) && name.ToLowerInvariant().Contains("collider")) { return true; } } Material[] sharedMaterials = renderer.sharedMaterials; if (sharedMaterials == null || sharedMaterials.Length == 0) { return true; } return false; } private static bool IsRendererActive(Renderer renderer) { if ((Object)(object)renderer == (Object)null) { return false; } GameObject gameObject = ((Component)renderer).gameObject; return (Object)(object)gameObject != (Object)null && gameObject.activeInHierarchy && renderer.enabled; } private bool IsRendererVisible(Renderer renderer) { return (Object)(object)renderer != (Object)null && renderer.isVisible; } private float SquaredDistanceToNearestCamera(Vector3 position) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) float num = float.MaxValue; for (int i = 0; i < cameraCache.Count; i++) { Camera val = cameraCache[i]; if (!((Object)(object)val == (Object)null) && ((Behaviour)val).enabled) { Vector3 val2 = ((Component)val).transform.position - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; } } } return (num == float.MaxValue) ? 0f : num; } public void Initialize(ReplacementIndex index, MaterialChangeMonitor monitor = null) { replacementIndex = index; materialChangeMonitor = monitor; fullRescanRequested = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialScanner] Initialize and request full rescan"); } } public void RequestFullRescan() { fullRescanRequested = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialScanner] Full rescan requested"); } } private void LateUpdate() { if (Preferences.Enabled.Value && replacementIndex != null) { float num = Preferences.RescanIntervalSeconds?.Value ?? 0f; if (num > 0f) { HandlePeriodicRescan(); } if (fullRescanRequested) { StartFullRescan(); } if (scanningInProgress && rendererBuffer.Count > 0) { ProcessRendererBatch(); } } } private void HandlePeriodicRescan() { float num = Preferences.RescanIntervalSeconds?.Value ?? 0f; if (num > 0f && Time.unscaledTime >= nextAllowedRescanTime) { fullRescanRequested = true; nextAllowedRescanTime = Time.unscaledTime + num; } } private void StartFullRescan() { processedMaterialIds.Clear(); rendererBuffer.Clear(); lastRendererIndex = 0; totalRenderersProcessed = 0; if (replacementIndex != null) { replacementIndex.ValidateAndReloadAllTextures(); } RefreshCameraCache(); DiscoverAndFilterRenderers(); scanningInProgress = rendererBuffer.Count > 0; fullRescanRequested = false; if (Preferences.DebugEnabled) { string arg = ((cameraCache.Count > 0 && cameraCache.Exists((Camera c) => (Object)(object)c != (Object)null && ((Behaviour)c).enabled && ((Component)c).gameObject.activeInHierarchy)) ? $"cameras={cameraCache.Count}" : "no active cameras, skipping vis/dist filters"); MelonLogger.Msg($"[MaterialScanner] Started full rescan: found {totalRenderersFound} renderers, {rendererBuffer.Count} after filters ({arg})"); } } private void RefreshCameraCache() { cameraCache.Clear(); Camera[] allCameras = Camera.allCameras; if (allCameras != null) { cameraCache.AddRange(allCameras); } } private void DiscoverAndFilterRenderers() { //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) Renderer[] array = Object.FindObjectsOfType<Renderer>(true); totalRenderersFound = ((array != null) ? array.Length : 0); if (array == null || array.Length == 0) { return; } bool flag = cameraCache.Count > 0 && cameraCache.Exists((Camera c) => (Object)(object)c != (Object)null && ((Behaviour)c).enabled && ((Component)c).gameObject.activeInHierarchy); bool flag2 = (Preferences.OnlyScanVisible?.Value ?? false) && flag; MelonPreferences_Entry<float> maxScanDistance = Preferences.MaxScanDistance; bool flag3 = maxScanDistance != null && maxScanDistance.Value > 0f && flag; bool flag4 = Preferences.OnlyScanActive?.Value ?? false; float num = (flag3 ? (Preferences.MaxScanDistance.Value * Preferences.MaxScanDistance.Value) : 0f); if (rendererBuffer.Capacity < array.Length) { rendererBuffer.Capacity = array.Length; } foreach (Renderer val in array) { if (ShouldSkipRenderer(val) || (flag4 && !IsRendererActive(val)) || (flag2 && !IsRendererVisible(val))) { continue; } if (flag3) { Bounds bounds = val.bounds; float num2 = SquaredDistanceToNearestCamera(((Bounds)(ref bounds)).center); if (num2 > num) { continue; } } rendererBuffer.Add(val); } } private void ProcessRendererBatch() { int num = Mathf.Max(8, Preferences.ScanBatchSize.Value); int num2 = Mathf.Min(rendererBuffer.Count, lastRendererIndex + num); int num3 = 0; for (int i = lastRendererIndex; i < num2; i++) { Renderer renderer = rendererBuffer[i]; if (ProcessSingleRenderer(renderer)) { num3++; totalRenderersProcessed++; } } lastRendererIndex = num2; bool flag = lastRendererIndex >= rendererBuffer.Count; if (flag) { CompleteScanPass(); } if (Preferences.DebugEnabled && num3 > 0) { float num4 = (flag ? 100f : ((float)lastRendererIndex / (float)rendererBuffer.Count * 100f)); MelonLogger.Msg($"[MaterialScanner] Processed batch: {num3} renderers, progress: {num4:F1}% ({lastRendererIndex}/{rendererBuffer.Count})"); } } private bool ProcessSingleRenderer(Renderer renderer) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)renderer == (Object)null) { return false; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value && !IsRendererActive(renderer)) { return false; } MelonPreferences_Entry<bool> onlyScanVisible = Preferences.OnlyScanVisible; if (onlyScanVisible != null && onlyScanVisible.Value && !IsRendererVisible(renderer)) { return false; } float num = Preferences.MaxScanDistance?.Value ?? 0f; if (num > 0f) { Bounds bounds = renderer.bounds; float num2 = SquaredDistanceToNearestCamera(((Bounds)(ref bounds)).center); if (num2 > num * num) { return false; } } Material[] sharedMaterials = renderer.sharedMaterials; if (sharedMaterials == null) { return false; } bool result = false; foreach (Material val in sharedMaterials) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (!processedMaterialIds.Contains(instanceID)) { bool flag = false; flag |= ReplacementApplicator.ApplyByMaterialMapping(renderer, val, replacementIndex); if (!flag) { flag |= ReplacementApplicator.ApplyAllTextureProperties(val, replacementIndex); } if (flag && Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialScanner] Applied replacements to material '" + ((Object)val).name + "' on '" + ((Object)((Component)renderer).gameObject).name + "'"); } if (flag && (Object)(object)materialChangeMonitor != (Object)null) { materialChangeMonitor.RegisterRenderer(renderer); } processedMaterialIds.Add(instanceID); result = true; } } return result; } private void CompleteScanPass() { scanningInProgress = false; lastRendererIndex = 0; if (Preferences.DebugEnabled) { MelonLogger.Msg($"[MaterialScanner] Completed scan pass: processed {totalRenderersProcessed}/{rendererBuffer.Count} renderers"); } rendererBuffer.Clear(); processedMaterialIds.Clear(); } } public sealed class UITextureScanner : MonoBehaviour { private readonly List<Image> imageBuffer = new List<Image>(128); private readonly List<RawImage> rawImageBuffer = new List<RawImage>(64); private readonly HashSet<int> processedImageIds = new HashSet<int>(); private readonly HashSet<int> processedRawImageIds = new HashSet<int>(); private ReplacementIndex replacementIndex; private MaterialChangeMonitor materialChangeMonitor; private bool fullRescanRequested; private int lastImageIndex; private int lastRawImageIndex; private float nextAllowedRescanTime; private readonly List<Canvas> canvasCache = new List<Canvas>(8); private bool scanningInProgress; private int totalImagesFound; private int totalRawImagesFound; private int totalImagesProcessed; private int totalRawImagesProcessed; private static bool ShouldSkipImage(Image image) { if ((Object)(object)image == (Object)null) { return true; } if ((Object)(object)image.sprite == (Object)null) { return true; } if ((Object)(object)image.sprite.texture == (Object)null) { return true; } GameObject gameObject = ((Component)image).gameObject; if ((Object)(object)gameObject != (Object)null) { string name = ((Object)gameObject).name; if (!string.IsNullOrEmpty(name) && name.ToLowerInvariant().Contains("collider")) { return true; } } return false; } private static bool ShouldSkipRawImage(RawImage rawImage) { if ((Object)(object)rawImage == (Object)null) { return true; } if ((Object)(object)rawImage.texture == (Object)null) { return true; } GameObject gameObject = ((Component)rawImage).gameObject; if ((Object)(object)gameObject != (Object)null) { string name = ((Object)gameObject).name; if (!string.IsNullOrEmpty(name) && name.ToLowerInvariant().Contains("collider")) { return true; } } return false; } private static bool IsImageActive(Image image) { if ((Object)(object)image == (Object)null) { return false; } GameObject gameObject = ((Component)image).gameObject; return (Object)(object)gameObject != (Object)null && gameObject.activeInHierarchy && ((Behaviour)image).enabled; } private static bool IsRawImageActive(RawImage rawImage) { if ((Object)(object)rawImage == (Object)null) { return false; } GameObject gameObject = ((Component)rawImage).gameObject; return (Object)(object)gameObject != (Object)null && gameObject.activeInHierarchy && ((Behaviour)rawImage).enabled; } private static bool IsCanvasActive(Canvas canvas) { if ((Object)(object)canvas == (Object)null) { return false; } GameObject gameObject = ((Component)canvas).gameObject; return (Object)(object)gameObject != (Object)null && gameObject.activeInHierarchy && ((Behaviour)canvas).enabled; } public void Initialize(ReplacementIndex index, MaterialChangeMonitor monitor = null) { replacementIndex = index; materialChangeMonitor = monitor; fullRescanRequested = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[UITextureScanner] Initialize and request full rescan"); } } public void RequestFullRescan() { fullRescanRequested = true; if (Preferences.DebugEnabled) { MelonLogger.Msg("[UITextureScanner] Full rescan requested"); } } private void LateUpdate() { if (Preferences.Enabled.Value && replacementIndex != null && Preferences.ScanUIElements.Value) { float num = Preferences.RescanIntervalSeconds?.Value ?? 0f; if (num > 0f) { HandlePeriodicRescan(); } if (fullRescanRequested) { StartFullRescan(); } if (scanningInProgress && (imageBuffer.Count > 0 || rawImageBuffer.Count > 0)) { ProcessImageBatch(); } } } public void HandleFileChanges(ICollection<string> changedPaths) { if (changedPaths == null || changedPaths.Count == 0 || !Preferences.Enabled.Value || !Preferences.ScanUIElements.Value) { return; } bool flag = false; foreach (string changedPath in changedPaths) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(changedPath); if (HasReplacementForSprite(fileNameWithoutExtension)) { flag = true; break; } } if (flag) { if (Preferences.DebugEnabled) { MelonLogger.Msg("[UITextureScanner] File changes detected, requesting UI rescan"); } RequestFullRescan(); } } private void HandlePeriodicRescan() { float num = Preferences.RescanIntervalSeconds?.Value ?? 0f; if (num > 0f && Time.unscaledTime >= nextAllowedRescanTime) { fullRescanRequested = true; nextAllowedRescanTime = Time.unscaledTime + num; } } private void StartFullRescan() { processedImageIds.Clear(); processedRawImageIds.Clear(); imageBuffer.Clear(); rawImageBuffer.Clear(); lastImageIndex = 0; lastRawImageIndex = 0; totalImagesProcessed = 0; totalRawImagesProcessed = 0; if (replacementIndex != null) { replacementIndex.ValidateAndReloadAllTextures(); } RefreshCanvasCache(); DiscoverAndFilterImages(); scanningInProgress = imageBuffer.Count > 0 || rawImageBuffer.Count > 0; fullRescanRequested = false; if (!Preferences.DebugEnabled) { return; } string text = ((canvasCache.Count > 0 && canvasCache.Exists((Canvas c) => (Object)(object)c != (Object)null && ((Behaviour)c).enabled && ((Component)c).gameObject.activeInHierarchy)) ? $"canvases={canvasCache.Count}" : "no active canvases"); MelonLogger.Msg($"[UITextureScanner] Started full rescan: found {totalImagesFound} images, {totalRawImagesFound} raw images, {imageBuffer.Count + rawImageBuffer.Count} after filters ({text})"); if (imageBuffer.Count > 0) { Image val = imageBuffer[0]; if ((Object)(object)val != (Object)null && (Object)(object)val.sprite != (Object)null) { string[] obj = new string[7] { "[UITextureScanner] Sample Image: '", ((Object)((Component)val).gameObject).name, "' with sprite '", ((Object)val.sprite).name, "' (texture: ", null, null }; Texture2D texture = val.sprite.texture; obj[5] = ((texture != null) ? ((Object)texture).name : null) ?? "null"; obj[6] = ")"; MelonLogger.Msg(string.Concat(obj)); } } } private void RefreshCanvasCache() { canvasCache.Clear(); Canvas[] array = Object.FindObjectsOfType<Canvas>(true); if (array != null) { canvasCache.AddRange(array); } } private void DiscoverAndFilterImages() { Image[] array = Object.FindObjectsOfType<Image>(true); totalImagesFound = ((array != null) ? array.Length : 0); if (array != null && array.Length != 0) { if (imageBuffer.Capacity < array.Length) { imageBuffer.Capacity = array.Length; } foreach (Image val in array) { if (!ShouldSkipImage(val) && ShouldIncludeImage(val)) { imageBuffer.Add(val); } } } RawImage[] array2 = Object.FindObjectsOfType<RawImage>(true); totalRawImagesFound = ((array2 != null) ? array2.Length : 0); if (array2 == null || array2.Length == 0) { return; } if (rawImageBuffer.Capacity < array2.Length) { rawImageBuffer.Capacity = array2.Length; } foreach (RawImage val2 in array2) { if (!ShouldSkipRawImage(val2) && ShouldIncludeRawImage(val2)) { rawImageBuffer.Add(val2); } } } private bool ShouldIncludeImage(Image image) { if ((Object)(object)image == (Object)null) { return false; } if ((Object)(object)image.sprite == (Object)null) { return false; } if ((Object)(object)image.sprite.texture == (Object)null) { return false; } Canvas componentInParent = ((Component)image).GetComponentInParent<Canvas>(); if ((Object)(object)componentInParent == (Object)null) { return false; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value) { if (!((Component)componentInParent).gameObject.activeInHierarchy || !((Behaviour)componentInParent).enabled) { return false; } if (!((Component)image).gameObject.activeInHierarchy || !((Behaviour)image).enabled) { return false; } } if (((Object)image.sprite).name.Contains("(Clone)") && !HasReplacementForSprite(((Object)image.sprite).name)) { string spriteName = ((Object)image.sprite).name.Replace("(Clone)", "").Trim(); if (!HasReplacementForSprite(spriteName)) { return false; } } return true; } private bool ShouldIncludeRawImage(RawImage rawImage) { if ((Object)(object)rawImage == (Object)null) { return false; } if ((Object)(object)rawImage.texture == (Object)null) { return false; } Canvas componentInParent = ((Component)rawImage).GetComponentInParent<Canvas>(); if ((Object)(object)componentInParent == (Object)null) { return false; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value) { if (!((Component)componentInParent).gameObject.activeInHierarchy || !((Behaviour)componentInParent).enabled) { return false; } if (!((Component)rawImage).gameObject.activeInHierarchy || !((Behaviour)rawImage).enabled) { return false; } } return true; } private bool HasReplacementForSprite(string spriteName) { if (replacementIndex == null) { return false; } return replacementIndex.HasTexture(spriteName); } private void ProcessImageBatch() { int num = Mathf.Max(16, Preferences.ScanBatchSize.Value / 2); int num2 = 0; int num3 = Mathf.Min(imageBuffer.Count, lastImageIndex + num); for (int i = lastImageIndex; i < num3; i++) { Image image = imageBuffer[i]; if (ProcessSingleImage(image)) { num2++; totalImagesProcessed++; } } lastImageIndex = num3; int num4 = Mathf.Min(rawImageBuffer.Count, lastRawImageIndex + num); for (int j = lastRawImageIndex; j < num4; j++) { RawImage rawImage = rawImageBuffer[j]; if (ProcessSingleRawImage(rawImage)) { num2++; totalRawImagesProcessed++; } } lastRawImageIndex = num4; bool flag = lastImageIndex >= imageBuffer.Count; bool flag2 = lastRawImageIndex >= rawImageBuffer.Count; bool flag3 = flag && flag2; if (flag3) { CompleteScanPass(); } if (Preferences.DebugEnabled && num2 > 0) { float num5 = (flag3 ? 100f : ((float)(lastImageIndex + lastRawImageIndex) / (float)(imageBuffer.Count + rawImageBuffer.Count) * 100f)); MelonLogger.Msg($"[UITextureScanner] Processed batch: {num2} UI elements, progress: {num5:F1}% ({lastImageIndex + lastRawImageIndex}/{imageBuffer.Count + rawImageBuffer.Count})"); } } private bool ProcessSingleImage(Image image) { if ((Object)(object)image == (Object)null) { return false; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value && !IsImageActive(image)) { return false; } Sprite sprite = image.sprite; if ((Object)(object)sprite == (Object)null) { return false; } Texture2D texture = sprite.texture; if ((Object)(object)texture == (Object)null) { return false; } int instanceID = ((Object)image).GetInstanceID(); if (processedImageIds.Contains(instanceID)) { return false; } bool flag = false; try { flag = ReplacementApplicator.ApplyToImage(image, replacementIndex); } catch (Exception ex) { if (Preferences.DebugEnabled) { MelonLogger.Error("[UITextureScanner] Error processing Image '" + ((Object)((Component)image).gameObject).name + "': " + ex.Message); } return false; } if (flag && Preferences.DebugEnabled) { MelonLogger.Msg("[UITextureScanner] Applied replacements to Image '" + ((Object)((Component)image).gameObject).name + "' with sprite '" + ((Object)sprite).name + "'"); } if (flag && (Object)(object)materialChangeMonitor != (Object)null) { try { materialChangeMonitor.RegisterImage(image); } catch (Exception ex2) { if (Preferences.DebugEnabled) { MelonLogger.Error("[UITextureScanner] Error registering Image '" + ((Object)((Component)image).gameObject).name + "' with monitor: " + ex2.Message); } } } processedImageIds.Add(instanceID); return flag; } private bool ProcessSingleRawImage(RawImage rawImage) { if ((Object)(object)rawImage == (Object)null) { return false; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value && !IsRawImageActive(rawImage)) { return false; } Texture texture = rawImage.texture; Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if ((Object)(object)val == (Object)null) { return false; } int instanceID = ((Object)rawImage).GetInstanceID(); if (processedRawImageIds.Contains(instanceID)) { return false; } bool flag = false; flag |= ReplacementApplicator.ApplyToRawImage(rawImage, replacementIndex); if (flag && Preferences.DebugEnabled) { MelonLogger.Msg("[UITextureScanner] Applied replacements to RawImage '" + ((Object)((Component)rawImage).gameObject).name + "' with texture '" + ((Object)val).name + "'"); } if (flag && (Object)(object)materialChangeMonitor != (Object)null) { materialChangeMonitor.RegisterRawImage(rawImage); } processedRawImageIds.Add(instanceID); return flag; } private void CompleteScanPass() { scanningInProgress = false; lastImageIndex = 0; lastRawImageIndex = 0; if (Preferences.DebugEnabled) { MelonLogger.Msg($"[UITextureScanner] Completed scan pass: processed {totalImagesProcessed}/{imageBuffer.Count} images, {totalRawImagesProcessed}/{rawImageBuffer.Count} raw images"); } imageBuffer.Clear(); rawImageBuffer.Clear(); processedImageIds.Clear(); processedRawImageIds.Clear(); } } } namespace TextureSwapper.Config { public static class Preferences { public static MelonPreferences_Category Category { get; private set; } public static MelonPreferences_Entry<bool> Enabled { get; private set; } public static MelonPreferences_Entry<bool> LiveReload { get; private set; } public static MelonPreferences_Entry<string> InspectorKeyPref { get; private set; } public static MelonPreferences_Entry<string> ExportKeyPref { get; private set; } public static MelonPreferences_Entry<int> ScanBatchSize { get; private set; } public static MelonPreferences_Entry<bool> DebugLogging { get; private set; } public static MelonPreferences_Entry<bool> OnlyScanActive { get; private set; } public static MelonPreferences_Entry<bool> OnlyScanVisible { get; private set; } public static MelonPreferences_Entry<float> MaxScanDistance { get; private set; } public static MelonPreferences_Entry<float> RescanIntervalSeconds { get; private set; } public static MelonPreferences_Entry<bool> MonitorDynamicChanges { get; private set; } public static MelonPreferences_Entry<bool> ScanUIElements { get; private set; } public static MelonPreferences_Entry<float> DynamicChangeCheckInterval { get; private set; } public static MelonPreferences_Entry<bool> AggressiveUIUpdates { get; private set; } public static KeyCode InspectorToggleKey => ParseKeyCode(InspectorKeyPref.Value, (KeyCode)288); public static KeyCode ExportKey => ParseKeyCode(ExportKeyPref.Value, (KeyCode)289); public static bool DebugEnabled => DebugLogging != null && DebugLogging.Value; public static void Initialize() { Category = MelonPreferences.CreateCategory("TextureSwapper"); Enabled = Category.CreateEntry<bool>("Enabled", true, "Enable the TextureSwapper mod", (string)null, false, false, (ValueValidator)null, (string)null); LiveReload = Category.CreateEntry<bool>("LiveReload", true, "Auto-reload textures when files change", (string)null, false, false, (ValueValidator)null, (string)null); InspectorKeyPref = Category.CreateEntry<string>("InspectorKey", "F8", "Toggle inspector overlay key", (string)null, false, false, (ValueValidator)null, (string)null); ExportKeyPref = Category.CreateEntry<string>("ExportKey", "F9", "Export current target info key", (string)null, false, false, (ValueValidator)null, (string)null); ScanBatchSize = Category.CreateEntry<int>("ScanBatchSize", 96, "Renderer scan batch size per frame", (string)null, false, false, (ValueValidator)null, (string)null); DebugLogging = Category.CreateEntry<bool>("DebugLogging", false, "Enable verbose debug logging for troubleshooting", (string)null, false, false, (ValueValidator)null, (string)null); OnlyScanActive = Category.CreateEntry<bool>("OnlyScanActive", true, "Scan only active renderers (exclude inactive)", (string)null, false, false, (ValueValidator)null, (string)null); OnlyScanVisible = Category.CreateEntry<bool>("OnlyScanVisible", false, "Scan only renderers currently visible to a camera", (string)null, false, false, (ValueValidator)null, (string)null); MaxScanDistance = Category.CreateEntry<float>("MaxScanDistance", 500f, "Max distance from camera for scanning (0 = unlimited)", (string)null, false, false, (ValueValidator)null, (string)null); RescanIntervalSeconds = Category.CreateEntry<float>("RescanIntervalSeconds", 0f, "Periodic rescan interval in seconds (0 = scan only on scene changes and file changes - recommended for performance)", (string)null, false, false, (ValueValidator)null, (string)null); MonitorDynamicChanges = Category.CreateEntry<bool>("MonitorDynamicChanges", true, "Monitor objects for dynamic material/sprite changes (recommended for day/night cycles)", (string)null, false, false, (ValueValidator)null, (string)null); ScanUIElements = Category.CreateEntry<bool>("ScanUIElements", true, "Scan and replace UI elements (Images, RawImages)", (string)null, false, false, (ValueValidator)null, (string)null); DynamicChangeCheckInterval = Category.CreateEntry<float>("DynamicChangeCheckInterval", 5f, "How often to check for dynamic material/sprite changes (seconds, lower = more responsive but higher CPU)", (string)null, false, false, (ValueValidator)null, (string)null); AggressiveUIUpdates = Category.CreateEntry<bool>("AggressiveUIUpdates", false, "Use aggressive UI update methods (better compatibility but higher CPU usage)", (string)null, false, false, (ValueValidator)null, (string)null); } private static KeyCode ParseKeyCode(string value, KeyCode fallback) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrWhiteSpace(value)) { return fallback; } if (Enum.TryParse<KeyCode>(value.Trim(), ignoreCase: true, out KeyCode result)) { return result; } return fallback; } } }