Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of TextureSwapper Il2Cpp v0.3.0
Mods/TextureSwapper_Il2cpp.dll
Decompiled a month ago
            The result has been truncated due to the large size, download it to view full contents!
        
        
        using 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 System.Threading; using Il2CppInterop.Runtime.Attributes; using Il2CppInterop.Runtime.InteropTypes.Arrays; 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.3.0", "Bars", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TextureSwapper_Il2cpp")] [assembly: AssemblyConfiguration("Il2cpp")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+3b3f92c400d4caf495d98ad692c946a4c5e62d8e")] [assembly: AssemblyProduct("TextureSwapper_Il2cpp")] [assembly: AssemblyTitle("TextureSwapper_Il2cpp")] [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"); } } if (Input.GetKey((KeyCode)306) && Input.GetKeyDown((KeyCode)109) && ReplacementIndex != null) { ((MelonBase)this).LoggerInstance.Msg("[TextureSwapper] Memory usage report:"); ReplacementIndex.LogCurrentMemoryUsage(); } } } } 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; } Il2CppReferenceArray<Material> sharedMaterials = componentInChildren.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length == 0) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Target: " + ((Object)target).name); for (int i = 0; i < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; i++) { Material val = ((Il2CppArrayBase<Material>)(object)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={((Il2CppArrayBase<Material>)(object)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 { [RegisterTypeInIl2Cpp] 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 InspectorUI(IntPtr ptr) : base(ptr) { }//IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown [HideFromIl2Cpp] public void Initialize(ReplacementIndex index, MaterialScanner scanner) { this.index = index; this.scanner = scanner; mainCamera = Camera.main; ConfigureStyles(); } [HideFromIl2Cpp] 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) { Il2CppArrayBase<Renderer> componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); if (componentsInChildren != null && componentsInChildren.Length > 0) { foreach (Renderer item in componentsInChildren) { if ((Object)(object)item == (Object)null) { continue; } Il2CppReferenceArray<Material> sharedMaterials = item.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length == 0) { continue; } bool flag = false; foreach (Material item2 in (Il2CppArrayBase<Material>)(object)sharedMaterials) { if ((Object)(object)item2 == (Object)null) { continue; } for (int i = 0; i < PropertyNames.TexturePropertyNames.Length; i++) { string text = PropertyNames.TexturePropertyNames[i]; if (item2.HasProperty(text) && (Object)(object)item2.GetTexture(text) != (Object)null) { flag = true; break; } } if (!flag) { continue; } cachedRenderableRenderers.Add(item); break; } } } } lastTarget = currentTarget; } private GameObject FindTargetFromRaycast(Ray ray) { //IL_0001: 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) //IL_0035: Unknown result type (might be due to invalid IL or missing references) RaycastHit[] array = Il2CppArrayBase<RaycastHit>.op_Implicit((Il2CppArrayBase<RaycastHit>)(object)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; } Il2CppReferenceArray<Material> sharedMaterials = component.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length != 1) { return false; } Material val = ((Il2CppArrayBase<Material>)(object)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; } } Il2CppArrayBase<Renderer> componentsInChildren = original.GetComponentsInChildren<Renderer>(false); foreach (Renderer item in componentsInChildren) { GameObject gameObject2 = ((Component)item).gameObject; if ((Object)(object)gameObject2 == (Object)(object)original || ShouldSkipObject(gameObject2)) { continue; } return gameObject2; } if ((Object)(object)parent != (Object)null) { for (int i = 0; i < parent.childCount; i++) { GameObject gameObject3 = ((Component)parent.GetChild(i)).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, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.Space(6f); if ((Object)(object)currentTarget == (Object)null) { GUILayout.Label("Look at an object to inspect its materials.", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } GUILayout.Label("Target: " + GetPath(currentTarget), smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.Space(4f); GUILayout.Label("Drop files named by material or material__Property: e.g. MyMat.png or MyMat__EmissionMap.png", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.Label("For UI elements: SpriteName.png or TextureName.png", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); Il2CppArrayBase<Renderer> componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); if (componentsInChildren == null || componentsInChildren.Length == 0) { GUILayout.Label("No Renderers found in hierarchy.", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } bool flag = false; for (int i = 0; i < componentsInChildren.Length; i++) { Renderer val2 = componentsInChildren[i]; if ((Object)(object)val2 == (Object)null) { continue; } Il2CppReferenceArray<Material> sharedMaterials = val2.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length == 0) { continue; } flag = true; string relativePath = GetRelativePath(currentTarget, ((Component)val2).gameObject); if (relativePath != ".") { GUILayout.Space(6f); GUILayout.Label("Object: " + relativePath, headerStyle, (Il2CppReferenceArray<GUILayoutOption>)null); } for (int j = 0; j < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; j++) { Material val3 = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[j]; if ((Object)(object)val3 == (Object)null) { continue; } GUILayout.Space(4f); GUILayout.Label($"Material [{j}]: {((Object)val3).name}", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); 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((Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.Label("Suggestion: Mods/TextureSwapper/" + ((Object)val3).name + ".png", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); 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((Il2CppReferenceArray<GUILayoutOption>)null); 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, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.EndScrollView(); GUILayout.EndArea(); return; } Il2CppArrayBase<Image> componentsInChildren2 = currentTarget.GetComponentsInChildren<Image>(true); Il2CppArrayBase<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, (Il2CppReferenceArray<GUILayoutOption>)null); GUILayout.Space(4f); GUILayout.Label("Drop files named by sprite/texture: e.g. MySprite.png or MyTexture.png", smallStyle, (Il2CppReferenceArray<GUILayoutOption>)null); 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, (Il2CppReferenceArray<GUILayoutOption>)null); 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((Il2CppReferenceArray<GUILayoutOption>)null); 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, (Il2CppReferenceArray<GUILayoutOption>)null); 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((Il2CppReferenceArray<GUILayoutOption>)null); 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, (Il2CppReferenceArray<GUILayoutOption>)null); } GUILayout.EndScrollView(); GUILayout.EndArea(); } [HideFromIl2Cpp] public void ExportCurrentSelection() { if ((Object)(object)currentTarget == (Object)null) { return; } Il2CppArrayBase<Renderer> componentsInChildren = currentTarget.GetComponentsInChildren<Renderer>(true); Il2CppArrayBase<Image> componentsInChildren2 = currentTarget.GetComponentsInChildren<Image>(true); Il2CppArrayBase<RawImage> componentsInChildren3 = currentTarget.GetComponentsInChildren<RawImage>(true); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Target: " + GetPath(currentTarget)); stringBuilder.AppendLine($"Renderer Count: {componentsInChildren?.Length ?? 0}"); stringBuilder.AppendLine($"Image Count: {componentsInChildren2?.Length ?? 0}"); stringBuilder.AppendLine($"RawImage Count: {componentsInChildren3?.Length ?? 0}"); stringBuilder.AppendLine(); for (int i = 0; i < componentsInChildren.Length; i++) { Renderer val = componentsInChildren[i]; if ((Object)(object)val == (Object)null) { continue; } Il2CppReferenceArray<Material> sharedMaterials = val.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length == 0) { continue; } string relativePath = GetRelativePath(currentTarget, ((Component)val).gameObject); stringBuilder.AppendLine("Object: " + relativePath); for (int j = 0; j < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; j++) { Material val2 = ((Il2CppArrayBase<Material>)(object)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 ==="); for (int l = 0; l < componentsInChildren2.Length; l++) { Image val4 = componentsInChildren2[l]; 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 ==="); for (int m = 0; m < componentsInChildren3.Length; m++) { RawImage val5 = componentsInChildren3[m]; 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 { [RegisterTypeInIl2Cpp] 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 MaterialChangeMonitor(IntPtr ptr) : base(ptr) { } [HideFromIl2Cpp] 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(); ProcessMaterialChanges(); ProcessImageChanges(); DiscoverAndRegisterNewUI(); 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; } Il2CppReferenceArray<Material> sharedMaterials = key.sharedMaterials; if (sharedMaterials == null || value == null) { continue; } if (((Il2CppArrayBase<Material>)(object)sharedMaterials).Length != value.Length) { renderersWithChanges.Add(key); continue; } for (int i = 0; i < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; i++) { if (i >= value.Length || (Object)(object)((Il2CppArrayBase<Material>)(object)sharedMaterials)[i] != (Object)(object)value[i] || ((Object)(object)((Il2CppArrayBase<Material>)(object)sharedMaterials)[i] != (Object)null && (Object)(object)value[i] != (Object)null && ((Object)((Il2CppArrayBase<Material>)(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; } Il2CppReferenceArray<Material> sharedMaterials = renderersWithChange.sharedMaterials; if (sharedMaterials == null) { continue; } bool flag = false; for (int i = 0; i < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; i++) { Material val = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[i]; 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] = ((IEnumerable<Material>)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; } Il2CppReferenceArray<Material> sharedMaterials = renderer.sharedMaterials; if (sharedMaterials != null) { originalMaterials[renderer] = ((IEnumerable<Material>)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; } Il2CppArrayBase<Renderer> val = Object.FindObjectsOfType<Renderer>(true); foreach (Renderer item in val) { if ((Object)(object)item != (Object)null && item.sharedMaterials != null && ((Il2CppArrayBase<Material>)(object)item.sharedMaterials).Length > 0) { RegisterRenderer(item); } } Il2CppArrayBase<Image> val2 = Object.FindObjectsOfType<Image>(true); foreach (Image item2 in val2) { if ((Object)(object)item2 != (Object)null && (Object)(object)item2.sprite != (Object)null) { RegisterImage(item2); } } Il2CppArrayBase<RawImage> val3 = Object.FindObjectsOfType<RawImage>(true); foreach (RawImage item3 in val3) { if ((Object)(object)item3 != (Object)null && (Object)(object)item3.texture != (Object)null) { RegisterRawImage(item3); } } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[MaterialChangeMonitor] Registered {monitoredRenderers.Count} renderers, {monitoredImages.Count} images, {monitoredRawImages.Count} raw images for monitoring"); } } private void DiscoverAndRegisterNewUI() { Il2CppArrayBase<Image> val = Object.FindObjectsOfType<Image>(true); if (val == null) { return; } for (int i = 0; i < val.Length; i++) { Image val2 = val[i]; if ((Object)(object)val2 == (Object)null || originalSprites.ContainsKey(val2) || (Object)(object)val2.sprite == (Object)null) { continue; } MelonPreferences_Entry<bool> onlyScanActive = Preferences.OnlyScanActive; if (onlyScanActive != null && onlyScanActive.Value && (!((Component)val2).gameObject.activeInHierarchy || !((Behaviour)val2).enabled)) { continue; } RegisterImage(val2); try { if (ReplacementApplicator.ApplyToImage(val2, replacementIndex) && Preferences.DebugEnabled) { MelonLogger.Msg("[MaterialChangeMonitor] Applied replacements to newly discovered Image '" + ((Object)((Component)val2).gameObject).name + "'"); } } catch (Exception ex) { if (Preferences.DebugEnabled) { GameObject gameObject = ((Component)val2).gameObject; MelonLogger.Warning("[MaterialChangeMonitor] Error applying to new Image '" + ((gameObject != null) ? ((Object)gameObject).name : null) + "': " + ex.Message); } } } } } 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_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: 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_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: 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; } Texture texture3 = material.GetTexture(text); Texture2D val2 = (Texture2D)(object)((texture3 is Texture2D) ? texture3 : null); if ((Object)(object)val2 == (Object)(object)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 bool ApplyTexturePreserveTiling(Material material, string prop, Texture2D replacement) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Texture texture = material.GetTexture(prop); Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if ((Object)(object)val == (Object)(object)replacement) { return false; } 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; } } return true; } 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) { if ((Object)(object)sprite != (Object)null && (Object)(object)sprite.texture == (Object)(object)texture2) { return false; } 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)) { if ((Object)(object)rawImage.texture == (Object)(object)texture2) { return false; } 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; public long EstimatedMemoryBytes; public TextureFormat OriginalFormat; public bool IsCompressed; } private struct TextureFormatInfo { public TextureFormat Format; public bool CanCompress; public string Reason; } 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 long totalMemoryUsageBytes = 0L; 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)) { if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Base directory does not exist: " + baseDirectory); } return; } int num = 0; int num2 = 0; while (num2 < 3 && num == 0) { num2++; if (Preferences.DebugEnabled && num2 > 1) { MelonLogger.Msg($"[ReplacementIndex] Load attempt {num2}/{3}"); } try { List<string> list = Directory.EnumerateFiles(baseDirectory, "*.*", SearchOption.AllDirectories).ToList(); if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Found {list.Count} total files in directory"); } foreach (string item in list) { try { if (item.IndexOf("_Exports", StringComparison.OrdinalIgnoreCase) >= 0) { continue; } string extension = Path.GetExtension(item); if (!SupportedExtensions.Contains(extension)) { continue; } if (!File.Exists(item)) { if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] File no longer exists: " + item); } continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); LoadOrReplace(fileNameWithoutExtension, item); num++; if (Preferences.DebugEnabled) { MelonLogger.Msg("[ReplacementIndex] Preloaded '" + fileNameWithoutExtension + "' from '" + item + "'"); } } catch (Exception ex) { MelonLogger.Error("[ReplacementIndex] Error processing file '" + item + "': " + ex.Message); } } } catch (Exception ex2) { MelonLogger.Error("[ReplacementIndex] Error enumerating files in '" + baseDirectory + "': " + ex2.Message); if (num2 < 3) { Thread.Sleep(500); } } if (num == 0 && num2 < 3) { if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] No files loaded on attempt {num2}, retrying..."); } Thread.Sleep(1000); } } if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Finished loading after {num2} attempt(s). Loaded {num} files. Keys={keyToTexture.Count} materialNames={materialNameToTexture.Count} materialProperties={materialPropertyToTexture.Count}"); LogMemoryUsage(); } else if (num > 0) { MelonLogger.Msg($"[ReplacementIndex] Loaded {num} texture replacement files"); LogMemoryUsage(); } if (num == 0) { MelonLogger.Msg("[ReplacementIndex] Warning: No texture files found in '" + baseDirectory + "'. Make sure your .png/.jpg/.jpeg files are in the TextureSwapper mod folder."); } } private void LoadOrReplace(string key, string filePath) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) try { Texture2D val = LoadTextureFromFile(filePath); if ((Object)(object)val == (Object)null) { return; } long num = 0L; if (keyToTexture.TryGetValue(key, out var value) && value != null) { if ((Object)(object)value.Texture != (Object)null) { num = value.EstimatedMemoryBytes; Object.Destroy((Object)(object)value.Texture); } value.FilePath = filePath; value.Texture = val; } else { keyToTexture[key] = new LoadedTexture { FilePath = filePath, Texture = val }; } LoadedTexture loadedTexture = keyToTexture[key]; loadedTexture.EstimatedMemoryBytes = EstimateTextureMemoryUsage(val); loadedTexture.OriginalFormat = val.format; loadedTexture.IsCompressed = IsCompressedFormat(val.format); totalMemoryUsageBytes = totalMemoryUsageBytes - num + loadedTexture.EstimatedMemoryBytes; string[] array = key.Split(new string[1] { "__" }, StringSplitOptions.None); if (array.Length == 1) { string key2 = NormalizeMaterialName(array[0]); materialNameToTexture[key2] = loadedTexture; } else if (array.Length >= 2) { string item = NormalizeMaterialName(array[0]); string item2 = array[1]; materialPropertyToTexture[(item, item2)] = loadedTexture; } 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_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) try { byte[] array = File.ReadAllBytes(filePath); TextureFormatInfo textureFormatInfo = DetermineTextureFormat(array, filePath); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true); if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false)) { Object.Destroy((Object)(object)val); val = new Texture2D(2, 2, (TextureFormat)4, true); if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array), false)) { Object.Destroy((Object)(object)val); return null; } } val = ApplyTextureSizeLimit(val, filePath); if ((Object)(object)val == (Object)null) { return null; } MelonPreferences_Entry<bool> useDXTCompression = Preferences.UseDXTCompression; if (useDXTCompression != null && useDXTCompression.Value && textureFormatInfo.CanCompress) { try { bool flag = ((Texture)val).width >= 4 && ((Texture)val).height >= 4 && ((Texture)val).width % 4 == 0 && ((Texture)val).height % 4 == 0; bool flag2 = SystemInfo.SupportsTextureFormat((TextureFormat)10); bool flag3 = SystemInfo.SupportsTextureFormat((TextureFormat)12); bool flag4 = flag2 || flag3; if (flag && flag4) { val.Compress(true); if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Compressed texture '{filePath}' using {val.format}"); } } else if (Preferences.DebugEnabled) { string text = ((!flag) ? "invalid size for DXT (must be multiples of 4 and >= 4x4)" : "DXT not supported on this platform"); MelonLogger.Msg("[ReplacementIndex] Skipped compression for '" + filePath + "': " + text); } } catch (Exception ex) { if (Preferences.DebugEnabled) { MelonLogger.Warning("[ReplacementIndex] Compression failed for '" + filePath + "': " + ex.Message); } } } ((Object)val).name = Path.GetFileNameWithoutExtension(filePath); ((Texture)val).wrapMode = (TextureWrapMode)0; ((Texture)val).filterMode = (FilterMode)1; Object.DontDestroyOnLoad((Object)(object)val); if (Preferences.DebugEnabled) { string text2 = (IsCompressedFormat(val.format) ? "compressed" : "uncompressed"); MelonLogger.Msg($"[ReplacementIndex] Loaded '{filePath}' as {val.format} ({text2}) - {((Texture)val).width}x{((Texture)val).height}"); } return val; } catch (Exception ex2) { MelonLogger.Error("[ReplacementIndex] Error reading '" + filePath + "': " + ex2.Message); return null; } } private static TextureFormatInfo DetermineTextureFormat(byte[] imageData, string filePath) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) MelonPreferences_Entry<bool> useDXTCompression = Preferences.UseDXTCompression; TextureFormatInfo result; if (useDXTCompression == null || !useDXTCompression.Value) { result = default(TextureFormatInfo); result.Format = (TextureFormat)4; result.CanCompress = false; result.Reason = "DXT compression disabled"; return result; } string text = Path.GetFileNameWithoutExtension(filePath).ToLowerInvariant(); bool flag = text.Contains("normal") || text.Contains("bump") || text.Contains("_n") || text.EndsWith("n") || text.Contains("bumpmap") || text.Contains("normalmap"); bool flag2 = HasSignificantAlpha(imageData); if (flag) { result = default(TextureFormatInfo); result.Format = (TextureFormat)12; result.CanCompress = true; result.Reason = "normal map detected"; return result; } if (flag2) { result = default(TextureFormatInfo); result.Format = (TextureFormat)12; result.CanCompress = true; result.Reason = "alpha channel detected"; return result; } result = default(TextureFormatInfo); result.Format = (TextureFormat)10; result.CanCompress = true; result.Reason = "opaque image"; return result; } private static bool HasSignificantAlpha(byte[] imageData) { if (imageData.Length > 8 && imageData[0] == 137 && imageData[1] == 80 && imageData[2] == 78 && imageData[3] == 71) { for (int i = 8; i < Math.Min(imageData.Length - 4, 100); i++) { if (imageData[i] == 73 && imageData[i + 1] == 72 && imageData[i + 2] == 68 && imageData[i + 3] == 82 && i + 12 < imageData.Length) { byte b = imageData[i + 12]; return b == 4 || b == 6; } } } return false; } private static Texture2D ApplyTextureSizeLimit(Texture2D tex, string filePath) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)tex == (Object)null) { return null; } int num = Preferences.MaxTextureSize?.Value ?? 2048; if (num <= 0) { return tex; } if (((Texture)tex).width <= num && ((Texture)tex).height <= num) { return tex; } try { float num2 = (float)((Texture)tex).width / (float)((Texture)tex).height; int num3; int num4; if (((Texture)tex).width > ((Texture)tex).height) { num3 = num; num4 = Mathf.RoundToInt((float)num / num2); } else { num4 = num; num3 = Mathf.RoundToInt((float)num * num2); } num3 = Mathf.Max(1, num3); num4 = Mathf.Max(1, num4); Texture2D val = new Texture2D(num3, num4, tex.format, true); Color[] array = Il2CppArrayBase<Color>.op_Implicit((Il2CppArrayBase<Color>)(object)tex.GetPixels()); Color[] array2 = (Color[])(object)new Color[num3 * num4]; for (int i = 0; i < num4; i++) { for (int j = 0; j < num3; j++) { float num5 = (float)j / (float)(num3 - 1); float num6 = (float)i / (float)(num4 - 1); int num7 = Mathf.RoundToInt(num5 * (float)(((Texture)tex).width - 1)); int num8 = Mathf.RoundToInt(num6 * (float)(((Texture)tex).height - 1)); array2[i * num3 + j] = array[num8 * ((Texture)tex).width + num7]; } } val.SetPixels(Il2CppStructArray<Color>.op_Implicit(array2)); val.Apply(); ((Object)val).name = ((Object)tex).name; ((Texture)val).wrapMode = ((Texture)tex).wrapMode; ((Texture)val).filterMode = ((Texture)tex).filterMode; Object.Destroy((Object)(object)tex); if (Preferences.DebugEnabled) { MelonLogger.Msg($"[ReplacementIndex] Resized texture '{filePath}' from {((Texture)tex).width}x{((Texture)tex).height} to {num3}x{num4}"); } return val; } catch (Exception ex) { MelonLogger.Error("[ReplacementIndex] Failed to resize texture '" + filePath + "': " + ex.Message); return tex; } } 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"); } } private static long EstimateTextureMemoryUsage(Texture2D texture) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected I4, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Invalid comparison between Unknown and I4 //IL_009c: 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) //IL_00b5: Expected I4, but got Unknown if ((Object)(object)texture == (Object)null) { return 0L; } int num = Math.Max(1, ((Texture)texture).mipmapCount); TextureFormat format = texture.format; if (1 == 0) { } int? num2 = (format - 1) switch { 3 => 4, 4 => 4, 2 => 3, 0 => 1, _ => null, }; if (1 == 0) { } int? num3 = num2; if (1 == 0) { } (int, int, int)? tuple = (((int)format == 10) ? new(int, int, int)?((4, 4, 8)) : (((int)format == 12) ? new(int, int, int)?((4, 4, 16)) : ((format - 24) switch { 2 => (4, 4, 8), 3 => (4, 4, 16), 0 => (4, 4, 16), 1 => (4, 4, 16), _ => null, }))); if (1 == 0) { } (int, int, int)? tuple2 = tuple; long num4 = 0L; int num5 = Math.Max(1, ((Texture)texture).width); int num6 = Math.Max(1, ((Texture)texture).height); for (int i = 0; i < num; i++) { if (num3.HasValue) { num4 += (long)num5 * (long)num6 * num3.Value; } else if (tuple2.HasValue) { int item = tuple2.Value.Item1; int item2 = tuple2.Value.Item2; int item3 = tuple2.Value.Item3; int num7 = Math.Max(1, (num5 + item - 1) / item); int num8 = Math.Max(1, (num6 + item2 - 1) / item2); num4 += (long)num7 * (long)num8 * item3; } else { num4 += (long)num5 * (long)num6 * 4; } num5 = Math.Max(1, num5 >> 1); num6 = Math.Max(1, num6 >> 1); } return num4; } private static bool IsCompressedFormat(TextureFormat format) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 if ((int)format == 10 || (int)format == 12 || format - 24 <= 3) { return true; } return false; } private void LogMemoryUsage() { if (keyToTexture.Count == 0) { return; } long num = 0L; long num2 = 0L; int num3 = 0; int num4 = 0; foreach (KeyValuePair<string, LoadedTexture> item in keyToTexture) { LoadedTexture value = item.Value; if (!((Object)(object)value?.Texture == (Object)null)) { if (value.IsCompressed) { num += value.EstimatedMemoryBytes; num3++; } else { num2 += value.EstimatedMemoryBytes; num4++; } } } MelonPreferences_Entry<bool> useDXTCompression = Preferences.UseDXTCompression; string text = ((useDXTCompression != null && useDXTCompression.Value) ? "enabled" : "disabled"); double num5 = (double)totalMemoryUsageBytes / 1048576.0; MelonLogger.Msg($"[ReplacementIndex] Memory usage: {num5:F1} MB total ({num3} compressed: {(double)num / 1048576.0:F1} MB, {num4} uncompressed: {(double)num2 / 1048576.0:F1} MB) - DXT compression {text}"); } public void LogCurrentMemoryUsage() { LogMemoryUsage(); } } [RegisterTypeInIl2Cpp] 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; public MaterialScanner(IntPtr ptr) : base(ptr) { } 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; } } Il2CppReferenceArray<Material> sharedMaterials = renderer.sharedMaterials; if (sharedMaterials == null || ((Il2CppArrayBase<Material>)(object)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; } [HideFromIl2Cpp] 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"); } } [HideFromIl2Cpp] 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(); Il2CppReferenceArray<Camera> allCameras = Camera.allCameras; if (allCameras != null) { cameraCache.AddRange((IEnumerable<Camera>)allCameras); } } private void DiscoverAndFilterRenderers() { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) Il2CppArrayBase<Renderer> val = Object.FindObjectsOfType<Renderer>(true); totalRenderersFound = val?.Length ?? 0; if (val == null || val.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 < val.Length) { rendererBuffer.Capacity = val.Length; } for (int i = 0; i < val.Length; i++) { Renderer val2 = val[i]; if (ShouldSkipRenderer(val2) || (flag4 && !IsRendererActive(val2)) || (flag2 && !IsRendererVisible(val2))) { continue; } if (flag3) { Bounds bounds = val2.bounds; float num2 = SquaredDistanceToNearestCamera(((Bounds)(ref bounds)).center); if (num2 > num) { continue; } } rendererBuffer.Add(val2); } } 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; } } Il2CppReferenceArray<Material> sharedMaterials = renderer.sharedMaterials; if (sharedMaterials == null) { return false; } bool result = false; for (int i = 0; i < ((Il2CppArrayBase<Material>)(object)sharedMaterials).Length; i++) { Material val = ((Il2CppArrayBase<Material>)(object)sharedMaterials)[i]; 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(); } } [RegisterTypeInIl2Cpp] 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; public UITextureScanner(IntPtr ptr) : base(ptr) { } 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; } GameObje