Decompiled source of CustomTextureReplacer v1.7.0
CustomTextureReplacer.dll
Decompiled 2 weeks 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.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Experimental.Rendering; using UnityEngine.SceneManagement; using UnityEngine.U2D; 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: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("CustomTextureReplacer")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+17911c2ed48b158a6086bf0a3532610c3e64c3f7")] [assembly: AssemblyProduct("CustomTextureReplacer")] [assembly: AssemblyTitle("CustomTextureReplacer")] [assembly: AssemblyVersion("1.0.0.0")] [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 CustomTextureReplacer { [BepInPlugin("com.duckieray.cardshop.customtextures", "Custom Texture Replacer", "1.7.0")] public class CustomTextureReplacer : BaseUnityPlugin { private void Awake() { //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) //IL_003f: Expected O, but got Unknown if ((Object)(object)ReplacerController.Instance != (Object)null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"[CustomTextureReplacer] Controller already initialised"); Object.Destroy((Object)(object)this); return; } GameObject val = new GameObject("CustomTextureReplacerController") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)val); ReplacerController replacerController = val.AddComponent<ReplacerController>(); replacerController.Initialise(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config); ((BaseUnityPlugin)this).Logger.LogInfo((object)"[CustomTextureReplacer] Controller created."); } } internal class ReplacerController : MonoBehaviour { private enum FolderPriorityMode { LastModified, PreferredFolder, FolderOrder } private struct TextureCandidate { public string Path; public DateTime TimestampUtc; public int FolderIndex; public long EventOrder; } internal sealed class MeshOverrideData { public string TargetName; public string MeshAssetName; public string MeshSelectionHint; public bool AutoSelectMesh; public bool AutoSelectMaterials; public string[] MaterialSelectionHints = Array.Empty<string>(); public string SourceBundlePath; public Mesh MeshInstance; public bool ApplyToMeshFilter; public bool ApplyToSkinnedMeshRenderer; public bool ApplyToMeshCollider; public int DesiredInstanceCount = -1; public int DesiredShelfCapacity = -1; public bool ShelfAllocationsDirty = true; public CardTextOverride CardOverride; public List<MaterialOverrideData> MaterialOverrides { get; } = new List<MaterialOverrideData>(); public HashSet<int> InstanceIds { get; } = new HashSet<int>(); public List<WeakReference<GameObject>> InstanceReferences { get; } = new List<WeakReference<GameObject>>(); public HashSet<int> HiddenInstanceIds { get; } = new HashSet<int>(); public Dictionary<int, ShelfSlotState> ShelfSlots { get; } = new Dictionary<int, ShelfSlotState>(); } internal sealed class ShelfSlotState { public int DefaultCapacity = -1; public int DesiredCapacity = -1; public int LastAnchorCount = -1; public int LastItemCount = -1; } internal sealed class MaterialOverrideData { public int Slot; public string MaterialAssetName; public string SourceBundlePath; public Material MaterialInstance; public List<MaterialTextureAssignment> TextureAssignments { get; } = new List<MaterialTextureAssignment>(); } internal sealed class MaterialTextureAssignment { public string PropertyName; public string FilePath; public Texture Texture; } internal sealed class PersistentSpriteOverride { public string SpriteName; public string TargetTextureName; public RectInt Region; public int Width; public int Height; public Texture2D Replacement; } private static class CardUIBindings { internal static readonly FieldInfo CardDataField = AccessTools.Field(typeof(CardUI), "m_CardData"); internal static readonly FieldInfo MonsterDataField = AccessTools.Field(typeof(CardUI), "m_MonsterData"); internal static readonly FieldInfo MonsterNameTextField = AccessTools.Field(typeof(CardUI), "m_MonsterNameText"); internal static readonly FieldInfo NumberTextField = AccessTools.Field(typeof(CardUI), "m_NumberText"); internal static readonly FieldInfo SubtitleTextField = AccessTools.Field(typeof(CardUI), "m_ChampionText"); internal static readonly FieldInfo DescriptionTextField = AccessTools.Field(typeof(CardUI), "m_DescriptionText"); internal static readonly FieldInfo ArtistTextField = AccessTools.Field(typeof(CardUI), "m_ArtistText"); internal static readonly FieldInfo RarityTextField = AccessTools.Field(typeof(CardUI), "m_RarityText"); internal static readonly FieldInfo FameTextField = AccessTools.Field(typeof(CardUI), "m_FameText"); internal static readonly FieldInfo Stat1TextField = AccessTools.Field(typeof(CardUI), "m_Stat1Text"); internal static readonly FieldInfo Stat2TextField = AccessTools.Field(typeof(CardUI), "m_Stat2Text"); internal static readonly FieldInfo Stat3TextField = AccessTools.Field(typeof(CardUI), "m_Stat3Text"); internal static readonly FieldInfo Stat4TextField = AccessTools.Field(typeof(CardUI), "m_Stat4Text"); internal static readonly FieldInfo EvoPreviousStageNameTextField = AccessTools.Field(typeof(CardUI), "m_EvoPreviousStageNameText"); internal static readonly FieldInfo EvoGroupField = AccessTools.Field(typeof(CardUI), "m_EvoGrp"); } private static class CheckPricePanelUIBindings { internal static readonly FieldInfo NameTextField = AccessTools.Field(typeof(CheckPricePanelUI), "m_NameText"); internal static readonly FieldInfo CardUIField = AccessTools.Field(typeof(CheckPricePanelUI), "m_CardUI"); } private static class CheckoutItemBarBindings { internal static readonly FieldInfo NameTextField = AccessTools.Field(typeof(UI_CheckoutItemBar), "m_NameText"); } private static class ItemPriceGraphScreenBindings { internal static readonly FieldInfo CardNameTextField = AccessTools.Field(typeof(ItemPriceGraphScreen), "m_CardName"); internal static readonly FieldInfo CardUIField = AccessTools.Field(typeof(ItemPriceGraphScreen), "m_CardUI"); } internal class CardTextOverride { public string Id; public string[] Aliases; public string DisplayName; public string Subtitle; public string CardNumber; public string Description; public string EvolvesFrom; public string Artist; public string Stat1; public string Stat2; public string Stat3; public string Stat4; public string Rarity; public string Fame; public bool IsMeshOverride; } [CompilerGenerated] private sealed class <ApplyReplacementsNextFrame>d__150 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public string sceneName; public ReplacerController <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ApplyReplacementsNextFrame>d__150(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = null; <>1__state = 2; return true; case 2: <>1__state = -1; <>4__this._logger.LogInfo((object)("[CustomTextureReplacer] Scene '" + sceneName + "' loaded. Reapplying replacements.")); <>4__this.SafeAppendDebug("ApplyReplacementsNextFrame for " + sceneName); <>4__this.ReplaceAllTextures(); if (!<>4__this._hasDumpedAfterSceneLoad && <>4__this._enableAutoDumps != null && <>4__this._enableAutoDumps.Value) { <>4__this._hasDumpedAfterSceneLoad = true; <>4__this.SafeAppendDebug("Dumping after scene load."); <>4__this.DumpAllTextures(); <>4__this.DumpAllMeshes(); } 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 <EnumerateNearbyTextComponents>d__219 : IEnumerable<TMP_Text>, IEnumerable, IEnumerator<TMP_Text>, IDisposable, IEnumerator { private int <>1__state; private TMP_Text <>2__current; private int <>l__initialThreadId; private GameObject instance; public GameObject <>3__instance; public ReplacerController <>4__this; private HashSet<int> <visited>5__1; private Transform <current>5__2; private int <depth>5__3; private TMP_Text[] <buffer>5__4; private TMP_Text[] <>s__5; private int <>s__6; private TMP_Text <text>5__7; private int <id>5__8; TMP_Text IEnumerator<TMP_Text>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnumerateNearbyTextComponents>d__219(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { <visited>5__1 = null; <current>5__2 = null; <buffer>5__4 = null; <>s__5 = null; <text>5__7 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; goto IL_0102; } <>1__state = -1; if ((Object)(object)instance == (Object)null) { return false; } <visited>5__1 = new HashSet<int>(); <current>5__2 = instance.transform; <depth>5__3 = 0; goto IL_015d; IL_0102: <text>5__7 = null; goto IL_010a; IL_015d: if ((Object)(object)<current>5__2 != (Object)null && <depth>5__3 < 12) { try { <buffer>5__4 = ((Component)<current>5__2).GetComponentsInChildren<TMP_Text>(true); } catch { <buffer>5__4 = Array.Empty<TMP_Text>(); } <>s__5 = <buffer>5__4; <>s__6 = 0; goto IL_0118; } return false; IL_0118: if (<>s__6 < <>s__5.Length) { <text>5__7 = <>s__5[<>s__6]; if ((Object)(object)<text>5__7 == (Object)null) { goto IL_010a; } <id>5__8 = ((Object)<text>5__7).GetInstanceID(); if (<visited>5__1.Add(<id>5__8)) { <>2__current = <text>5__7; <>1__state = 1; return true; } goto IL_0102; } <>s__5 = null; <current>5__2 = <current>5__2.parent; <depth>5__3++; <buffer>5__4 = null; goto IL_015d; IL_010a: <>s__6++; goto IL_0118; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<TMP_Text> IEnumerable<TMP_Text>.GetEnumerator() { <EnumerateNearbyTextComponents>d__219 <EnumerateNearbyTextComponents>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <EnumerateNearbyTextComponents>d__ = this; } else { <EnumerateNearbyTextComponents>d__ = new <EnumerateNearbyTextComponents>d__219(0) { <>4__this = <>4__this }; } <EnumerateNearbyTextComponents>d__.instance = <>3__instance; return <EnumerateNearbyTextComponents>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<TMP_Text>)this).GetEnumerator(); } } [CompilerGenerated] private sealed class <EnumerateStringValues>d__179 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IDisposable, IEnumerator { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private object value; public object <>3__value; private string <s>5__1; private IList <list>5__2; private string <fallback>5__3; private IEnumerator <>s__4; private object <item>5__5; private string <text>5__6; string IEnumerator<string>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <EnumerateStringValues>d__179(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 2) { try { } finally { <>m__Finally1(); } } <s>5__1 = null; <list>5__2 = null; <fallback>5__3 = null; <>s__4 = null; <item>5__5 = null; <text>5__6 = null; <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (value == null) { return false; } <s>5__1 = value as string; if (<s>5__1 != null) { <>2__current = <s>5__1; <>1__state = 1; return true; } <list>5__2 = value as IList; if (<list>5__2 != null) { <>s__4 = <list>5__2.GetEnumerator(); <>1__state = -3; goto IL_0150; } <fallback>5__3 = value.ToString(); if (!string.IsNullOrWhiteSpace(<fallback>5__3)) { <>2__current = <fallback>5__3; <>1__state = 3; return true; } break; case 1: <>1__state = -1; return false; case 2: <>1__state = -3; goto IL_0141; case 3: { <>1__state = -1; break; } IL_0141: <text>5__6 = null; <item>5__5 = null; goto IL_0150; IL_0150: do { if (<>s__4.MoveNext()) { <item>5__5 = <>s__4.Current; continue; } <>m__Finally1(); <>s__4 = null; return false; } while (<item>5__5 == null); <text>5__6 = <item>5__5.ToString(); if (!string.IsNullOrWhiteSpace(<text>5__6)) { <>2__current = <text>5__6; <>1__state = 2; return true; } goto IL_0141; } return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<>s__4 is IDisposable disposable) { disposable.Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator<string> IEnumerable<string>.GetEnumerator() { <EnumerateStringValues>d__179 <EnumerateStringValues>d__; if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; <EnumerateStringValues>d__ = this; } else { <EnumerateStringValues>d__ = new <EnumerateStringValues>d__179(0); } <EnumerateStringValues>d__.value = <>3__value; return <EnumerateStringValues>d__; } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<string>)this).GetEnumerator(); } } [CompilerGenerated] private sealed class <InitialDump>d__149 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public ReplacerController <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitialDump>d__149(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(1f); <>1__state = 1; return true; case 1: <>1__state = -1; if (<>4__this._enableAutoDumps == null || !<>4__this._enableAutoDumps.Value) { return false; } <>4__this._logger.LogInfo((object)"[CustomTextureReplacer] Performing initial texture dump."); <>4__this.SafeAppendDebug("Initial dump coroutine running."); <>4__this.DumpAllTextures(); <>4__this.DumpAllSprites(); <>4__this.DumpAllMeshes(); 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 <ReapplySpriteAtlasDeferred>d__271 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public SpriteAtlas atlas; public string identifier; public float initialDelay; public ReplacerController <>4__this; private int <attempt>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <ReapplySpriteAtlasDeferred>d__271(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (initialDelay > 0f) { <>2__current = (object)new WaitForSecondsRealtime(initialDelay); <>1__state = 1; return true; } goto IL_0069; case 1: <>1__state = -1; goto IL_0069; case 2: <>1__state = -1; <>2__current = (object)new WaitForSecondsRealtime(0.1f); <>1__state = 3; return true; case 3: { <>1__state = -1; <attempt>5__1++; break; } IL_0069: <attempt>5__1 = 0; break; } if (<attempt>5__1 < 6 && !((Object)(object)atlas == (Object)null)) { <>4__this.ReapplyPersistentSpriteOverridesForAtlas(atlas); <>4__this.RequestReapply($"SpriteAtlasRetry{<attempt>5__1}"); <>2__current = null; <>1__state = 2; return true; } <>4__this._pendingSpriteAtlasReapply.Remove(identifier); 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 const string HarmonyId = "com.duckieray.cardshop.customtextures.harmony"; private const float ScanIntervalSeconds = 2f; private const float MeshOverrideRetryWindowSeconds = 10f; private const float MeshOverrideRescanIntervalApplied = 0.25f; private const float MeshOverrideRescanIntervalPending = 0.5f; private const float MeshOverrideRescanMinimumDelay = 0.12f; private const int MeshLabelSearchParentDepth = 12; private static readonly Type UIImageType = typeof(Image); private static readonly PropertyInfo UIImageSpriteProperty = UIImageType.GetProperty("sprite", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo UIImageOverrideSpriteProperty = UIImageType.GetProperty("overrideSprite", BindingFlags.Instance | BindingFlags.Public); private static readonly Type UIRawImageType = typeof(RawImage); private static readonly PropertyInfo UIRawImageTextureProperty = UIRawImageType.GetProperty("texture", BindingFlags.Instance | BindingFlags.Public); private static readonly string[] RendererTextureProperties = new string[7] { "_MainTex", "_BaseMap", "_BaseColorMap", "_BaseTex", "_DiffuseTex", "_EmissionMap", "_AlbedoTex" }; private readonly Dictionary<string, Texture2D> _customTextures = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<int> _knownTextureIds = new HashSet<int>(); private readonly HashSet<int> _customTextureIds = new HashSet<int>(); private readonly HashSet<int> _collectionScratch = new HashSet<int>(); private readonly HashSet<int> _spriteScratch = new HashSet<int>(); private readonly List<Texture2D> _textureBuffer = new List<Texture2D>(512); private readonly List<Sprite> _spriteBuffer = new List<Sprite>(512); private readonly List<string> _newTextureNames = new List<string>(64); private readonly Dictionary<Texture, Texture> _textureOverrides = new Dictionary<Texture, Texture>(); private readonly Dictionary<string, Texture> _textureOverridesByName = new Dictionary<string, Texture>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<Sprite, Sprite> _spriteOverrides = new Dictionary<Sprite, Sprite>(); private readonly Dictionary<string, Sprite> _spriteOverridesByName = new Dictionary<string, Sprite>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<Sprite, Texture2D> _spriteOverrideTextures = new Dictionary<Sprite, Texture2D>(); private readonly HashSet<int> _imageOverrideInstances = new HashSet<int>(); private readonly HashSet<int> _rawImageOverrideInstances = new HashSet<int>(); private bool _applyingImageOverride; private bool _applyingRawImageOverride; private bool _applyingLabelOverride; private readonly Dictionary<int, string> _meshInstanceTargetByComponent = new Dictionary<int, string>(); private readonly Dictionary<int, string> _shelfMeshNameCache = new Dictionary<int, string>(); private readonly HashSet<string> _loggedShelfCapacityWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _loggedMeshLabelCandidates = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly FieldInfo ShelfCompartmentItemTypeField = AccessTools.Field(typeof(ShelfCompartment), "m_ItemType"); private static readonly FieldInfo ShelfCompartmentItemPosListField = AccessTools.Field(typeof(ShelfCompartment), "m_ItemPosList"); internal static readonly Type InventoryBaseType = AccessTools.TypeByName("InventoryBase"); internal static readonly Type ItemTypeEnumType = AccessTools.TypeByName("EItemType"); internal static readonly MethodInfo InventoryGetItemMeshDataMethod = ((InventoryBaseType != null && ItemTypeEnumType != null) ? AccessTools.Method(InventoryBaseType, "GetItemMeshData", new Type[1] { ItemTypeEnumType }, (Type[])null) : null); internal static readonly Type ItemMeshDataType = AccessTools.TypeByName("ItemMeshData"); internal static readonly FieldInfo ItemMeshDataMeshField = ((ItemMeshDataType != null) ? AccessTools.Field(ItemMeshDataType, "mesh") : null); internal static readonly FieldInfo ShelfCompartmentSizeXField = AccessTools.Field(typeof(ShelfCompartment), "m_SizeX"); internal static readonly FieldInfo ShelfCompartmentCurrentItemSizeXField = AccessTools.Field(typeof(ShelfCompartment), "m_CurrentItemSizeX"); internal static readonly FieldInfo ShelfCompartmentSizeYField = AccessTools.Field(typeof(ShelfCompartment), "m_SizeY"); internal static readonly FieldInfo ShelfCompartmentSizeZField = AccessTools.Field(typeof(ShelfCompartment), "m_SizeZ"); internal static readonly FieldInfo ShelfCompartmentCurrentItemSizeYField = AccessTools.Field(typeof(ShelfCompartment), "m_CurrentItemSizeY"); internal static readonly FieldInfo ShelfCompartmentCurrentItemSizeZField = AccessTools.Field(typeof(ShelfCompartment), "m_CurrentItemSizeZ"); internal static readonly FieldInfo ShelfCompartmentMaxItemXField = AccessTools.Field(typeof(ShelfCompartment), "m_MaxItemX"); internal static readonly FieldInfo ShelfCompartmentMaxItemYField = AccessTools.Field(typeof(ShelfCompartment), "m_MaxItemY"); internal static readonly FieldInfo ShelfCompartmentMaxItemZField = AccessTools.Field(typeof(ShelfCompartment), "m_MaxItemZ"); internal static readonly FieldInfo ShelfCompartmentMaxItemCountField = AccessTools.Field(typeof(ShelfCompartment), "m_MaxItemCount"); internal static readonly FieldInfo ShelfCompartmentTransformPosListField = AccessTools.Field(typeof(ShelfCompartment), "m_PosList"); internal static readonly FieldInfo ShelfCompartmentStoredItemListGrpField = AccessTools.Field(typeof(ShelfCompartment), "m_StoredItemListGrp"); private readonly Dictionary<string, PersistentSpriteOverride> _spritePersistence = new Dictionary<string, PersistentSpriteOverride>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, List<PersistentSpriteOverride>> _spritePersistenceByTexture = new Dictionary<string, List<PersistentSpriteOverride>>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<Texture2D> _generatedTextures = new HashSet<Texture2D>(); private readonly HashSet<Sprite> _generatedSprites = new HashSet<Sprite>(); private readonly Dictionary<string, CardTextOverride> _cardOverrides = new Dictionary<string, CardTextOverride>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, CardTextOverride> _capturedCardData = new Dictionary<string, CardTextOverride>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, CardTextOverride> _meshCardOverrides = new Dictionary<string, CardTextOverride>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, MeshOverrideData> _meshOverridesByTarget = new Dictionary<string, MeshOverrideData>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _meshTextureFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<int, WeakReference<MeshFilter>> _meshFilterReferences = new Dictionary<int, WeakReference<MeshFilter>>(); private readonly Dictionary<int, Mesh> _meshFilterOriginalMeshes = new Dictionary<int, Mesh>(); private readonly HashSet<int> _meshFilterOverrideIds = new HashSet<int>(); private readonly Dictionary<int, WeakReference<SkinnedMeshRenderer>> _skinnedRendererReferences = new Dictionary<int, WeakReference<SkinnedMeshRenderer>>(); private readonly Dictionary<int, Mesh> _skinnedOriginalMeshes = new Dictionary<int, Mesh>(); private readonly HashSet<int> _skinnedRendererOverrideIds = new HashSet<int>(); private readonly Dictionary<int, WeakReference<MeshCollider>> _meshColliderReferences = new Dictionary<int, WeakReference<MeshCollider>>(); private readonly Dictionary<int, Mesh> _meshColliderOriginalMeshes = new Dictionary<int, Mesh>(); private readonly HashSet<int> _meshColliderOverrideIds = new HashSet<int>(); private readonly Dictionary<int, WeakReference<Renderer>> _rendererReferences = new Dictionary<int, WeakReference<Renderer>>(); private readonly Dictionary<int, Material[]> _rendererOriginalMaterials = new Dictionary<int, Material[]>(); private readonly HashSet<int> _rendererOverrideIds = new HashSet<int>(); private readonly Dictionary<int, string> _lastAppliedMeshLabels = new Dictionary<int, string>(); private readonly Dictionary<int, WeakReference<TMP_Text>> _meshLabelTextRefs = new Dictionary<int, WeakReference<TMP_Text>>(); private readonly List<int> _meshLabelCleanupScratch = new List<int>(64); private int _meshLabelCleanupBudget = 64; private readonly Dictionary<string, AssetBundle> _meshAssetBundles = new Dictionary<string, AssetBundle>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, FileSystemWatcher> _meshBundleWatchers = new Dictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _pendingSpriteAtlasReapply = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private bool _overridesDirty; private readonly MaterialPropertyBlock _propertyBlock = new MaterialPropertyBlock(); private readonly Dictionary<string, DateTime> _fileEventTimes = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, long> _fileEventOrders = new Dictionary<string, long>(StringComparer.OrdinalIgnoreCase); private long _fileEventCounter; private Sprite[] _spriteArray = Array.Empty<Sprite>(); private ManualLogSource _logger; private ConfigEntry<bool> _logNewTextureNames; private ConfigEntry<bool> _logAssetLoads; private ConfigEntry<bool> _enableDebugLog; private ConfigEntry<bool> _enableAutoDumps; private ConfigEntry<bool> _suppressRectTransformWarning; private readonly List<string> _textureFolders = new List<string>(); private string _dumpFile = string.Empty; private string _dumpTriggerFile = string.Empty; private string _refreshTriggerFile = string.Empty; private string _spriteDumpFile = string.Empty; private string _spriteDumpTriggerFile = string.Empty; private string _debugLogFile = string.Empty; private string _exportFolder = string.Empty; private string _meshDumpFile = string.Empty; private string _meshDumpTriggerFile = string.Empty; private readonly List<FileSystemWatcher> _watchers = new List<FileSystemWatcher>(); private ConfigEntry<FolderPriorityMode> _folderPriorityMode; private ConfigEntry<string> _preferredFolderHint; private string[] _preferredFolderTokens = Array.Empty<string>(); private static readonly char[] PreferredFolderSeparators = new char[3] { ';', ',', '|' }; private string _cardOverridesPath = string.Empty; private string _meshOverridesPath = string.Empty; private bool _cardOverridesReloadRequested; private bool _meshOverridesReloadRequested; private FileSystemWatcher _cardOverridesWatcher; private FileSystemWatcher _meshOverridesWatcher; private Harmony _harmony; private volatile bool _reloadRequested; private bool _hasDumpedAfterSceneLoad; private bool _pendingDump; private bool _reapplyRequested; private bool _loggedHeartbeat; private float _nextScanTime; private bool _meshOverridesDirty; private float _nextMeshOverrideScanTime; private float _meshOverrideRetryDeadline; private bool _meshOverrideRetryLogged; internal static ReplacerController Instance { get; private set; } internal void Initialise(ManualLogSource logger, ConfigFile config) { //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Expected O, but got Unknown if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this) { logger.LogWarning((object)"[CustomTextureReplacer] Duplicate controller detected, destroying new instance."); Object.Destroy((Object)(object)((Component)this).gameObject); return; } Instance = this; _logger = logger; _logNewTextureNames = config.Bind<bool>("Debug", "LogNewTextureNames", true, "Log the names of textures discovered during runtime scans."); _logAssetLoads = config.Bind<bool>("Debug", "LogAssetLoads", true, "Log texture and sprite loads coming from Resources/AssetBundle APIs."); _enableDebugLog = config.Bind<bool>("Debug", "EnableDebugLogFile", false, "Write CustomTextureReplacer.debug.log for troubleshooting. Disable to avoid extra disk writes."); _enableAutoDumps = config.Bind<bool>("Debug", "EnableAutomaticDumps", false, "Automatically dump texture/sprite/mesh lists after loading. Disable to avoid large writes at start-up."); _suppressRectTransformWarning = config.Bind<bool>("Debug", "SuppressRectTransformParentWarning", true, "Suppress the noisy 'Parent of RectTransform is being set with parent property' warnings Unity emits."); _folderPriorityMode = config.Bind<FolderPriorityMode>("General", "FolderPriorityMode", FolderPriorityMode.LastModified, "Determines which texture file wins when duplicates exist across folders. Options: LastModified, PreferredFolder, FolderOrder."); _preferredFolderHint = config.Bind<string>("General", "PreferredFolderHint", string.Empty, "When FolderPriorityMode=PreferredFolder, provide folder names or path fragments (separated by ';' ',' or '|') to prioritise."); UpdatePreferredFolderTokens(); _folderPriorityMode.SettingChanged += delegate { SafeAppendDebug("FolderPriorityMode changed via config."); _reloadRequested = true; }; _preferredFolderHint.SettingChanged += delegate { UpdatePreferredFolderTokens(); SafeAppendDebug("PreferredFolderHint changed via config."); _reloadRequested = true; }; DiscoverTextureFolders(logDetails: true); ResolveCardOverridesPath(); LoadCardOverrides(logDetails: true); SetupCardOverridesWatcher(); _dumpFile = Path.Combine(Paths.PluginPath, "TexturesList.txt"); _dumpTriggerFile = Path.Combine(Paths.PluginPath, "CustomTextures.dump.now"); _refreshTriggerFile = Path.Combine(Paths.PluginPath, "CustomTextures.refresh.now"); _spriteDumpFile = Path.Combine(Paths.PluginPath, "SpritesList.txt"); _spriteDumpTriggerFile = Path.Combine(Paths.PluginPath, "SpritesList.dump.now"); _debugLogFile = Path.Combine(Paths.PluginPath, "CustomTextureReplacer.debug.log"); _exportFolder = Path.Combine(Paths.PluginPath, "ExportedTextures"); Directory.CreateDirectory(_exportFolder); _meshDumpFile = Path.Combine(Paths.PluginPath, "MeshesList.txt"); _meshDumpTriggerFile = Path.Combine(Paths.PluginPath, "MeshesList.dump.now"); SafeAppendDebug("Controller initialised."); _harmony = new Harmony("com.duckieray.cardshop.customtextures.harmony"); _harmony.PatchAll(typeof(ReplacerController).Assembly); SceneManager.sceneLoaded += OnSceneLoaded; SpriteAtlasManager.atlasRegistered -= OnSpriteAtlasRegistered; SpriteAtlasManager.atlasRegistered += OnSpriteAtlasRegistered; ReloadCustomTextures(); ReapplyExistingSpriteAtlases("Initialise"); ScheduleMeshOverrideReapply(0f); if (_enableAutoDumps != null && _enableAutoDumps.Value) { ((MonoBehaviour)this).StartCoroutine(InitialDump()); } _nextScanTime = Time.realtimeSinceStartup + 2f; } private void OnDestroy() { SafeAppendDebug("Controller OnDestroy."); SceneManager.sceneLoaded -= OnSceneLoaded; SpriteAtlasManager.atlasRegistered -= OnSpriteAtlasRegistered; foreach (FileSystemWatcher watcher in _watchers) { try { watcher.EnableRaisingEvents = false; watcher.Created -= OnTextureFileChanged; watcher.Changed -= OnTextureFileChanged; watcher.Deleted -= OnTextureFileChanged; watcher.Renamed -= OnTextureFileRenamed; watcher.Dispose(); } catch { } } _watchers.Clear(); if (_cardOverridesWatcher != null) { try { _cardOverridesWatcher.EnableRaisingEvents = false; _cardOverridesWatcher.Changed -= OnCardOverridesFileChanged; _cardOverridesWatcher.Created -= OnCardOverridesFileChanged; _cardOverridesWatcher.Deleted -= OnCardOverridesFileChanged; _cardOverridesWatcher.Renamed -= OnCardOverridesFileRenamed; _cardOverridesWatcher.Dispose(); } catch { } _cardOverridesWatcher = null; } if (_meshOverridesWatcher != null) { try { _meshOverridesWatcher.EnableRaisingEvents = false; _meshOverridesWatcher.Changed -= OnMeshOverridesFileChanged; _meshOverridesWatcher.Created -= OnMeshOverridesFileChanged; _meshOverridesWatcher.Deleted -= OnMeshOverridesFileChanged; _meshOverridesWatcher.Renamed -= OnMeshOverridesFileRenamed; _meshOverridesWatcher.Dispose(); } catch { } _meshOverridesWatcher = null; } RevertAllMeshOverrides(); DisposeMeshOverrideResources(); if (_harmony != null) { try { _harmony.UnpatchSelf(); } catch { } _harmony = null; } foreach (Texture2D value in _customTextures.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } _customTextures.Clear(); _customTextureIds.Clear(); _knownTextureIds.Clear(); _textureBuffer.Clear(); _spriteBuffer.Clear(); _collectionScratch.Clear(); _spriteScratch.Clear(); _cardOverrides.Clear(); foreach (Texture2D generatedTexture in _generatedTextures) { if ((Object)(object)generatedTexture != (Object)null) { Object.Destroy((Object)(object)generatedTexture); } } _generatedTextures.Clear(); foreach (Sprite generatedSprite in _generatedSprites) { if ((Object)(object)generatedSprite != (Object)null) { Object.Destroy((Object)(object)generatedSprite); } } _generatedSprites.Clear(); _textureOverrides.Clear(); _textureOverridesByName.Clear(); _spriteOverrides.Clear(); _spriteOverridesByName.Clear(); _spriteOverrideTextures.Clear(); _imageOverrideInstances.Clear(); _rawImageOverrideInstances.Clear(); _meshTextureFolders.Clear(); _meshCardOverrides.Clear(); Instance = null; _pendingSpriteAtlasReapply.Clear(); } private void Update() { if (!_loggedHeartbeat) { _loggedHeartbeat = true; _logger.LogInfo((object)"[CustomTextureReplacer] Update loop active. Use F8 or create 'CustomTextures.dump.now' to trigger dumps."); SafeAppendDebug("Update heartbeat."); } if (CheckAndConsumeFileTrigger(_dumpTriggerFile)) { _logger.LogInfo((object)("[CustomTextureReplacer] Manual dump triggered via '" + Path.GetFileName(_dumpTriggerFile) + "'.")); SafeAppendDebug("Dump trigger consumed."); DumpAllTextures(); DumpAllSprites(); DumpAllMeshes(); } if (CheckAndConsumeFileTrigger(_refreshTriggerFile)) { _logger.LogInfo((object)("[CustomTextureReplacer] Manual refresh triggered via '" + Path.GetFileName(_refreshTriggerFile) + "'.")); SafeAppendDebug("Refresh trigger consumed."); ReloadCustomTextures(); } if (CheckAndConsumeFileTrigger(_spriteDumpTriggerFile)) { _logger.LogInfo((object)("[CustomTextureReplacer] Manual sprite dump triggered via '" + Path.GetFileName(_spriteDumpTriggerFile) + "'.")); SafeAppendDebug("Sprite dump trigger consumed."); DumpAllSprites(); } if (CheckAndConsumeFileTrigger(_meshDumpTriggerFile)) { _logger.LogInfo((object)("[CustomTextureReplacer] Manual mesh dump triggered via '" + Path.GetFileName(_meshDumpTriggerFile) + "'.")); SafeAppendDebug("Mesh dump trigger consumed."); DumpAllMeshes(); } if (_reloadRequested) { _reloadRequested = false; SafeAppendDebug("Reload flag consumed."); ReloadCustomTextures(); } if (_cardOverridesReloadRequested) { _cardOverridesReloadRequested = false; SafeAppendDebug("Card override reload flag consumed."); LoadCardOverrides(logDetails: true); ReapplyCardOverrides(); } if (_meshOverridesReloadRequested) { _meshOverridesReloadRequested = false; SafeAppendDebug("Mesh override reload flag consumed."); LoadMeshOverrides(logDetails: true); } if (Input.GetKeyDown((KeyCode)289)) { _logger.LogInfo((object)"[CustomTextureReplacer] Manual dump triggered (F8)."); SafeAppendDebug("F8 pressed"); DumpAllTextures(); DumpAllSprites(); DumpAllMeshes(); } if (Input.GetKeyDown((KeyCode)290)) { _logger.LogInfo((object)"[CustomTextureReplacer] Original card data dump requested (F9)."); SafeAppendDebug("F9 pressed - dumping original card data."); DumpOriginalCardData(); } if (Input.GetKeyDown((KeyCode)291)) { _logger.LogInfo((object)"[CustomTextureReplacer] Manual mesh dump triggered (F10)."); SafeAppendDebug("F10 pressed"); DumpAllMeshes(); } if (Time.realtimeSinceStartup >= _nextScanTime) { _nextScanTime = Time.realtimeSinceStartup + 2f; if (DetectNewTextures()) { SafeAppendDebug("Periodic scan detected new textures."); RequestReapply("PeriodicScan"); } } if (_reapplyRequested) { _reapplyRequested = false; SafeAppendDebug("Reapply flag consumed."); ReplaceAllTextures(); } if (_pendingDump) { _pendingDump = false; SafeAppendDebug("Pending dump executed."); DumpAllTextures(); DumpAllSprites(); DumpAllMeshes(); } ProcessExtractionRequests(); if (_overridesDirty) { _overridesDirty = false; ApplyTextureOverridesToMaterials(); ApplyTextureOverridesToRenderers(); ApplySpriteOverridesToComponents(); ScheduleMeshOverrideReapply(0f); } ApplyMeshOverrides(); } private void RefreshWatchers() { foreach (FileSystemWatcher watcher in _watchers) { try { watcher.EnableRaisingEvents = false; watcher.Created -= OnTextureFileChanged; watcher.Changed -= OnTextureFileChanged; watcher.Deleted -= OnTextureFileChanged; watcher.Renamed -= OnTextureFileRenamed; watcher.Dispose(); } catch { } } _watchers.Clear(); foreach (string textureFolder in _textureFolders) { try { Directory.CreateDirectory(textureFolder); FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(textureFolder, "*.png") { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.LastWrite), IncludeSubdirectories = false, EnableRaisingEvents = true }; fileSystemWatcher.Created += OnTextureFileChanged; fileSystemWatcher.Changed += OnTextureFileChanged; fileSystemWatcher.Deleted += OnTextureFileChanged; fileSystemWatcher.Renamed += OnTextureFileRenamed; _watchers.Add(fileSystemWatcher); } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("[CustomTextureReplacer] Could not watch '" + textureFolder + "': " + ex.Message)); } } } SafeAppendDebug($"FileSystemWatcher initialised for {_textureFolders.Count} folder(s)."); } private bool DiscoverTextureFolders(bool logDetails) { List<string> list = new List<string>(); try { string text = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; if (!string.IsNullOrEmpty(text)) { TryAddTextureFolder(list, Path.Combine(text, "CustomTextures"), logDetails); string text2 = Directory.GetParent(text)?.FullName; if (!string.IsNullOrEmpty(text2)) { string[] directories = Directory.GetDirectories(text2); foreach (string path in directories) { TryAddTextureFolder(list, Path.Combine(path, "CustomTextures"), logDetails); } TryAddTextureFolder(list, Path.Combine(text2, "CustomTextures"), logDetails); } } } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("[CustomTextureReplacer] Unable to discover texture folders: " + ex.Message)); } } if (list.Count == 0) { string path2 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? Paths.PluginPath; string path3 = Path.Combine(path2, "CustomTextures"); Directory.CreateDirectory(path3); list.Add(Path.GetFullPath(path3)); if (logDetails) { ManualLogSource logger2 = _logger; if (logger2 != null) { logger2.LogInfo((object)("[CustomTextureReplacer] No texture folders found; using default: " + list[0])); } } } if (_meshTextureFolders.Count > 0) { foreach (string meshTextureFolder in _meshTextureFolders) { TryAddTextureFolder(list, meshTextureFolder, logDetails); } } bool flag = !_textureFolders.SequenceEqual<string>(list, StringComparer.OrdinalIgnoreCase); if (flag) { _textureFolders.Clear(); _textureFolders.AddRange(list); if (!logDetails) { foreach (string textureFolder in _textureFolders) { ManualLogSource logger3 = _logger; if (logger3 != null) { logger3.LogInfo((object)("[CustomTextureReplacer] Texture folder: " + textureFolder)); } } } } return flag; } private void UpdatePreferredFolderTokens() { string text = _preferredFolderHint?.Value ?? string.Empty; if (string.IsNullOrWhiteSpace(text)) { _preferredFolderTokens = Array.Empty<string>(); return; } _preferredFolderTokens = (from token in text.Split(PreferredFolderSeparators, StringSplitOptions.RemoveEmptyEntries) select token.Trim() into token where token.Length > 0 select token.Replace('\\', '/').ToLowerInvariant()).ToArray(); } private bool TryAddTextureFolder(List<string> list, string candidate, bool logDetails) { if (string.IsNullOrEmpty(candidate)) { return false; } string fullPath; try { fullPath = Path.GetFullPath(candidate); } catch { return false; } if (!Directory.Exists(fullPath)) { return false; } if (list.Any((string entry) => string.Equals(entry, fullPath, StringComparison.OrdinalIgnoreCase))) { return false; } list.Add(fullPath); if (logDetails) { ManualLogSource logger = _logger; if (logger != null) { logger.LogInfo((object)("[CustomTextureReplacer] Found texture folder: " + fullPath)); } } return true; } private int GetPreferredScore(string path) { if (_preferredFolderTokens.Length == 0) { return int.MaxValue; } string text = path.Replace('\\', '/').ToLowerInvariant(); for (int i = 0; i < _preferredFolderTokens.Length; i++) { if (text.Contains(_preferredFolderTokens[i])) { return i; } } return int.MaxValue; } private static string NormalizePath(string path) { if (string.IsNullOrEmpty(path)) { return string.Empty; } try { return Path.GetFullPath(path); } catch { return path; } } private void RecordFileEvent(string path) { string text = NormalizePath(path); if (string.IsNullOrEmpty(text)) { return; } _fileEventCounter++; _fileEventOrders[text] = _fileEventCounter; try { if (File.Exists(text)) { _fileEventTimes[text] = DateTime.UtcNow; } else { _fileEventTimes.Remove(text); } } catch { _fileEventTimes[text] = DateTime.UtcNow; } } private void RemoveFileEvent(string path) { string text = NormalizePath(path); if (!string.IsNullOrEmpty(text)) { _fileEventTimes.Remove(text); _fileEventOrders.Remove(text); } } private bool IsBetterCandidate(in TextureCandidate candidate, in TextureCandidate existing) { switch (_folderPriorityMode.Value) { case FolderPriorityMode.LastModified: { if (candidate.EventOrder != existing.EventOrder) { return candidate.EventOrder > existing.EventOrder; } DateTime timestampUtc = candidate.TimestampUtc; int num2 = timestampUtc.CompareTo(existing.TimestampUtc); if (num2 != 0) { return num2 > 0; } return candidate.FolderIndex > existing.FolderIndex; } case FolderPriorityMode.FolderOrder: if (candidate.EventOrder != existing.EventOrder) { return candidate.EventOrder > existing.EventOrder; } if (candidate.FolderIndex != existing.FolderIndex) { return candidate.FolderIndex < existing.FolderIndex; } return candidate.TimestampUtc > existing.TimestampUtc; case FolderPriorityMode.PreferredFolder: { if (candidate.EventOrder != existing.EventOrder) { return candidate.EventOrder > existing.EventOrder; } int preferredScore = GetPreferredScore(candidate.Path); int preferredScore2 = GetPreferredScore(existing.Path); if (preferredScore != preferredScore2) { return preferredScore < preferredScore2; } DateTime timestampUtc = candidate.TimestampUtc; int num = timestampUtc.CompareTo(existing.TimestampUtc); if (num != 0) { return num > 0; } return candidate.FolderIndex > existing.FolderIndex; } default: return false; } } private void LoadCustomTextures() { Dictionary<string, TextureCandidate> dictionary = new Dictionary<string, TextureCandidate>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < _textureFolders.Count; i++) { string text = _textureFolders[i]; try { if (!Directory.Exists(text)) { continue; } string[] files = Directory.GetFiles(text, "*.png", SearchOption.TopDirectoryOnly); foreach (string path in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); if (string.IsNullOrEmpty(fileNameWithoutExtension)) { continue; } string text2 = NormalizePath(path); FileInfo fileInfo = new FileInfo(text2); if (!fileInfo.Exists) { continue; } long ticks = Math.Max(fileInfo.LastWriteTimeUtc.Ticks, fileInfo.CreationTimeUtc.Ticks); DateTime dateTime = new DateTime(ticks, DateTimeKind.Utc); if (_fileEventTimes.TryGetValue(text2, out var value) && value > dateTime) { dateTime = value; } _fileEventOrders.TryGetValue(text2, out var value2); TextureCandidate textureCandidate = default(TextureCandidate); textureCandidate.Path = text2; textureCandidate.TimestampUtc = dateTime; textureCandidate.FolderIndex = i; textureCandidate.EventOrder = value2; TextureCandidate candidate = textureCandidate; if (dictionary.TryGetValue(fileNameWithoutExtension, out var value3)) { if (IsBetterCandidate(in candidate, in value3)) { dictionary[fileNameWithoutExtension] = candidate; } } else { dictionary[fileNameWithoutExtension] = candidate; } } } catch (Exception ex) { ManualLogSource logger = _logger; if (logger != null) { logger.LogWarning((object)("[CustomTextureReplacer] Failed to enumerate '" + text + "': " + ex.Message)); } } } foreach (KeyValuePair<string, TextureCandidate> item in dictionary) { TextureCandidate value4 = item.Value; string path2 = value4.Path; if (TryLoadTexture(path2, out var texture) && (Object)(object)texture != (Object)null) { _customTextures[item.Key] = texture; _customTextureIds.Add(((Object)texture).GetInstanceID()); } _fileEventTimes[path2] = value4.TimestampUtc; if (value4.EventOrder > 0) { _fileEventOrders[path2] = value4.EventOrder; } } } private void OnTextureFileChanged(object sender, FileSystemEventArgs e) { if (IsPng(e.FullPath)) { if (e.ChangeType == WatcherChangeTypes.Deleted) { RemoveFileEvent(e.FullPath); } else { RecordFileEvent(e.FullPath); } _logger.LogInfo((object)("[CustomTextureReplacer] Detected change for '" + e.Name + "'. Reloading custom textures.")); SafeAppendDebug("File change detected: " + e.Name); _reloadRequested = true; } } private void OnTextureFileRenamed(object sender, RenamedEventArgs e) { if (IsPng(e.FullPath) || IsPng(e.OldFullPath)) { RemoveFileEvent(e.OldFullPath); RecordFileEvent(e.FullPath); _logger.LogInfo((object)("[CustomTextureReplacer] Detected rename from '" + e.OldName + "' to '" + e.Name + "'. Reloading custom textures.")); SafeAppendDebug("File rename detected: " + e.OldName + " -> " + e.Name); _reloadRequested = true; } } private static bool IsPng(string path) { return string.Equals(Path.GetExtension(path), ".png", StringComparison.OrdinalIgnoreCase); } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { SafeAppendDebug("Scene loaded: " + ((Scene)(ref scene)).name); _hasDumpedAfterSceneLoad = false; ((MonoBehaviour)this).StartCoroutine(ApplyReplacementsNextFrame(((Scene)(ref scene)).name)); } [IteratorStateMachine(typeof(<InitialDump>d__149))] private IEnumerator InitialDump() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitialDump>d__149(0) { <>4__this = this }; } [IteratorStateMachine(typeof(<ApplyReplacementsNextFrame>d__150))] private IEnumerator ApplyReplacementsNextFrame(string sceneName) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ApplyReplacementsNextFrame>d__150(0) { <>4__this = this, sceneName = sceneName }; } private void ReloadCustomTextures() { foreach (Texture2D value in _customTextures.Values) { if ((Object)(object)value != (Object)null) { _customTextureIds.Remove(((Object)value).GetInstanceID()); Object.Destroy((Object)(object)value); } } _customTextures.Clear(); _customTextureIds.Clear(); _spritePersistence.Clear(); _spritePersistenceByTexture.Clear(); if (DiscoverTextureFolders(logDetails: false) || _watchers.Count == 0) { RefreshWatchers(); } if (ResolveCardOverridesPath()) { LoadCardOverrides(logDetails: true); SetupCardOverridesWatcher(); } bool flag = ResolveMeshOverridesPath(); LoadCustomTextures(); LoadMeshOverrides(flag || _meshOverridesByTarget.Count == 0); if ((_meshOverridesWatcher == null && !string.IsNullOrEmpty(_meshOverridesPath)) || flag) { SetupMeshOverridesWatcher(); } _logger.LogInfo((object)$"[CustomTextureReplacer] Loaded {_customTextures.Count} custom textures from disk."); SafeAppendDebug($"ReloadCustomTextures complete: {_customTextures.Count} textures"); RequestReapply("CustomTexturesReloaded"); _overridesDirty = true; ReapplyCardOverrides(); } private bool TryLoadTexture(string path, out Texture2D texture) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown texture = null; try { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); using MemoryStream memoryStream = new MemoryStream(); fileStream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, array, false)) { Object.Destroy((Object)(object)val); _logger.LogWarning((object)("[CustomTextureReplacer] Failed to decode '" + path + "'.")); SafeAppendDebug("Failed to decode " + Path.GetFileName(path)); return false; } ((Object)val).name = Path.GetFileNameWithoutExtension(path); ((Texture)val).wrapMode = (TextureWrapMode)1; texture = val; return true; } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Could not load '" + path + "': " + ex.Message)); SafeAppendDebug("Exception loading " + Path.GetFileName(path) + ": " + ex.Message); return false; } } private bool DetectNewTextures() { CollectCandidateTextures(_textureBuffer); bool flag = false; _newTextureNames.Clear(); foreach (Texture2D item in _textureBuffer) { if (!((Object)(object)item == (Object)null) && _knownTextureIds.Add(((Object)item).GetInstanceID())) { flag = true; if (_logNewTextureNames.Value) { _newTextureNames.Add($"{((Object)item).name} ({((Texture)item).width}x{((Texture)item).height})"); } } } if (flag && _logNewTextureNames.Value && _newTextureNames.Count > 0) { string text = string.Join(", ", _newTextureNames.Take(10)); if (_newTextureNames.Count > 10) { text += ", ..."; } _logger.LogInfo((object)$"[CustomTextureReplacer] Detected {_newTextureNames.Count} new textures: {text}"); SafeAppendDebug($"Detected {_newTextureNames.Count} new textures"); } if (flag) { _overridesDirty = true; } return flag; } private void ReplaceAllTextures() { //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Unknown result type (might be due to invalid IL or missing references) if (_customTextures.Count == 0) { SafeAppendDebug("ReplaceAllTextures aborted (no custom textures)"); return; } CollectCandidateTextures(_textureBuffer); int num = 0; int num2 = 0; foreach (Texture2D item in _textureBuffer) { if ((Object)(object)item == (Object)null || !_customTextures.TryGetValue(((Object)item).name, out var value) || value == item) { continue; } bool flag = ((Texture)value).width != ((Texture)item).width || ((Texture)value).height != ((Texture)item).height; string extraMessage; bool usedFallback; if (!TryGetSliceDimensions((Texture)(object)item, 0, 0, ((Texture)item).width, ((Texture)item).height, out var sliceWidth, out var sliceHeight, out var clipped, out var failureReason)) { if (!string.IsNullOrEmpty(failureReason)) { _logger.LogWarning((object)failureReason); SafeAppendDebug(failureReason); } if (TryCreateTextureOverride((Texture)(object)item, value, ((Texture)item).width, ((Texture)item).height)) { num++; } else { num2++; } } else if (TryBlitToTexture((Texture)(object)value, (Texture)(object)item, sliceWidth, sliceHeight, out extraMessage, out usedFallback)) { num++; if (flag) { _logger.LogInfo((object)$"[CustomTextureReplacer] Resized + replaced '{((Object)item).name}' (custom {((Texture)value).width}x{((Texture)value).height} -> game {((Texture)item).width}x{((Texture)item).height})."); } else { _logger.LogInfo((object)$"[CustomTextureReplacer] Replaced '{((Object)item).name}' (custom {((Texture)value).width}x{((Texture)value).height})."); } if (clipped) { _logger.LogWarning((object)$"[CustomTextureReplacer] Replacement for '{((Object)item).name}' was clipped to {sliceWidth}x{sliceHeight}."); SafeAppendDebug($"Clipped texture copy for {((Object)item).name} to {sliceWidth}x{sliceHeight}"); } if (!string.IsNullOrEmpty(extraMessage)) { _logger.LogWarning((object)extraMessage); SafeAppendDebug(extraMessage); } if (usedFallback) { TryCreateTextureOverride((Texture)(object)item, value, ((Texture)item).width, ((Texture)item).height); } } else if (TryCreateTextureOverride((Texture)(object)item, value, ((Texture)item).width, ((Texture)item).height)) { num++; } else { num2++; } } CollectCandidateSprites(_spriteBuffer); foreach (Sprite item2 in _spriteBuffer) { if ((Object)(object)item2 == (Object)null || !_customTextures.TryGetValue(((Object)item2).name, out var value2)) { continue; } Texture2D texture = item2.texture; if ((Object)(object)texture == (Object)null || value2 == texture || _customTextureIds.Contains(((Object)texture).GetInstanceID())) { continue; } Rect textureRect = item2.textureRect; int num3 = Mathf.RoundToInt(((Rect)(ref textureRect)).width); int num4 = Mathf.RoundToInt(((Rect)(ref textureRect)).height); int num5 = Mathf.RoundToInt(((Rect)(ref textureRect)).x); int num6 = Mathf.RoundToInt(((Rect)(ref textureRect)).y); if (num3 <= 0 || num4 <= 0 || !TryGetTextureSize((Texture)(object)texture, out var _, out var _)) { continue; } string extraMessage2; bool usedFallback2; if (!TryGetSliceDimensions((Texture)(object)texture, num5, num6, num3, num4, out var sliceWidth2, out var sliceHeight2, out var clipped2, out var failureReason2)) { if (!string.IsNullOrEmpty(failureReason2)) { _logger.LogWarning((object)failureReason2); SafeAppendDebug(failureReason2); } if (TryCreateSpriteOverride(item2, value2, num3, num4)) { num++; } else { num2++; } } else if (TryBlitToTexture((Texture)(object)value2, (Texture)(object)texture, sliceWidth2, sliceHeight2, out extraMessage2, out usedFallback2, num5, num6)) { num++; if (sliceWidth2 != num3 || sliceHeight2 != num4) { _logger.LogInfo((object)$"[CustomTextureReplacer] Resized + clipped sprite '{((Object)item2).name}' in atlas '{((Object)texture).name}' ({((Texture)value2).width}x{((Texture)value2).height} -> region {sliceWidth2}x{sliceHeight2} at {num5},{num6})."); } else if (num3 != ((Texture)value2).width || num4 != ((Texture)value2).height) { _logger.LogInfo((object)$"[CustomTextureReplacer] Resized + replaced sprite '{((Object)item2).name}' in atlas '{((Object)texture).name}' ({((Texture)value2).width}x{((Texture)value2).height} -> region {sliceWidth2}x{sliceHeight2} at {num5},{num6})."); } else { _logger.LogInfo((object)$"[CustomTextureReplacer] Replaced sprite '{((Object)item2).name}' in atlas '{((Object)texture).name}' (region {sliceWidth2}x{sliceHeight2} at {num5},{num6})."); } if (clipped2) { _logger.LogWarning((object)$"[CustomTextureReplacer] Sprite '{((Object)item2).name}' in atlas '{((Object)texture).name}' was clipped to {sliceWidth2}x{sliceHeight2}."); SafeAppendDebug($"Clipped sprite copy for {((Object)item2).name} to {sliceWidth2}x{sliceHeight2}"); } if (!string.IsNullOrEmpty(extraMessage2)) { _logger.LogWarning((object)extraMessage2); SafeAppendDebug(extraMessage2); } RegisterPersistentSpriteOverride(item2, value2, num3, num4); if (usedFallback2) { TryCreateSpriteOverride(item2, value2, num3, num4); } } else if (TryCreateSpriteOverride(item2, value2, num3, num4)) { num++; } else { num2++; } } ApplyTextureOverridesToMaterials(); ApplySpriteOverridesToComponents(); if (num > 0 || num2 > 0) { _logger.LogInfo((object)$"[CustomTextureReplacer] Applied replacements. Success: {num}, skipped: {num2}."); SafeAppendDebug($"ReplaceAllTextures finished. Success={num} Skipped={num2}"); } ScheduleMeshOverrideReapply(0f); ApplyMeshOverrides(); } private bool TryBlitToTexture(Texture source, Texture destination, int width, int height, out string extraMessage, out bool usedFallback, int dstX = 0, int dstY = 0) { //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_0035: Invalid comparison between Unknown and I4 //IL_0082: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00bb: Unknown result type (might be due to invalid IL or missing references) extraMessage = string.Empty; usedFallback = false; RenderTexture val = null; Texture2D val2 = null; RenderTexture val3 = null; bool flag = false; try { val = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0); Graphics.Blit(source, val); TextureFormat val4 = (TextureFormat)4; bool flag2 = false; bool flag3 = (int)QualitySettings.activeColorSpace == 1; Texture2D val5 = (Texture2D)(object)((destination is Texture2D) ? destination : null); if (val5 != null) { val4 = val5.format; if (!ValidateTextureFormat(val4, width, height)) { val4 = (TextureFormat)4; flag = true; } } val3 = RenderTexture.active; RenderTexture.active = val; val2 = new Texture2D(width, height, val4, flag2, flag3) { name = ((destination != null) ? ((Object)destination).name : null) + "_TempCopy" }; val2.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0); val2.Apply(false, false); RenderTexture.active = val3; val3 = null; bool flag4 = false; try { Graphics.CopyTexture((Texture)(object)val2, 0, 0, 0, 0, width, height, destination, 0, 0, dstX, dstY); flag4 = true; } catch (Exception ex) { if (dstX == 0 && dstY == 0 && destination.width == width && destination.height == height) { try { Graphics.ConvertTexture((Texture)(object)val2, destination); extraMessage = "[CustomTextureReplacer] ConvertTexture used for '" + ((destination != null) ? ((Object)destination).name : null) + "' after CopyTexture fallback: " + ex.Message; usedFallback = true; flag4 = true; } catch (Exception ex2) { extraMessage = "[CustomTextureReplacer] ConvertTexture failed for '" + ((destination != null) ? ((Object)destination).name : null) + "': " + ex2.Message; flag4 = false; } } else { extraMessage = "[CustomTextureReplacer] CopyTexture failed for '" + ((destination != null) ? ((Object)destination).name : null) + "': " + ex.Message; flag4 = false; } } if (!flag4 && flag && string.IsNullOrEmpty(extraMessage)) { extraMessage = "[CustomTextureReplacer] Unable to blit into '" + ((destination != null) ? ((Object)destination).name : null) + "' due to format mismatch."; usedFallback = true; } return flag4; } finally { if (flag && string.IsNullOrEmpty(extraMessage)) { extraMessage = "[CustomTextureReplacer] Fallback format copy used for '" + ((destination != null) ? ((Object)destination).name : null) + "', possible truncation."; usedFallback = true; } if ((Object)(object)val3 != (Object)null) { RenderTexture.active = val3; } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } } } private bool TryCopyIntoTexture(Texture source, Texture2D destination, int width, int height, string label) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null || (Object)(object)destination == (Object)null) { return false; } RenderTexture val = null; RenderTexture active = RenderTexture.active; try { val = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0); Graphics.Blit(source, val); RenderTexture.active = val; destination.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0); destination.Apply(false, false); RenderTexture.active = active; SafeAppendDebug("Override texture refreshed for " + label); return true; } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Failed to refresh override for '" + label + "': " + ex.Message)); SafeAppendDebug("Override refresh failed for " + label + ": " + ex.Message); return false; } finally { RenderTexture.active = active; if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } } } private bool TryCreateTextureOverride(Texture target, Texture2D replacement, int width, int height) { //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) Texture2D val = (Texture2D)(object)((target is Texture2D) ? target : null); if (val == null || (Object)(object)replacement == (Object)null) { return false; } try { if (_textureOverrides.TryGetValue((Texture)(object)val, out var value)) { Texture2D val2 = (Texture2D)(object)((value is Texture2D) ? value : null); if (val2 != null && ((Texture)val2).width == width && ((Texture)val2).height == height) { if (TryCopyIntoTexture((Texture)(object)replacement, val2, width, height, ((Object)val).name)) { _textureOverrides[(Texture)(object)val] = (Texture)(object)val2; _textureOverridesByName[((Object)val).name] = (Texture)(object)val2; _overridesDirty = true; return true; } RemoveTextureOverride((Texture)(object)val); } } if (_textureOverridesByName.TryGetValue(((Object)val).name, out var value2)) { Texture2D val3 = (Texture2D)(object)((value2 is Texture2D) ? value2 : null); if (val3 != null && ((Texture)val3).width == width && ((Texture)val3).height == height) { if (TryCopyIntoTexture((Texture)(object)replacement, val3, width, height, ((Object)val).name)) { _textureOverrides[(Texture)(object)val] = (Texture)(object)val3; _textureOverridesByName[((Object)val).name] = (Texture)(object)val3; _overridesDirty = true; return true; } RemoveTextureOverride((Texture)(object)val); } } RemoveTextureOverride((Texture)(object)val); Texture2D val4 = new Texture2D(width, height, (TextureFormat)4, false) { name = ((Object)val).name + "_Custom", wrapMode = ((Texture)val).wrapMode, filterMode = ((Texture)val).filterMode, anisoLevel = ((Texture)val).anisoLevel }; RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)replacement, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; val4.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0); val4.Apply(); RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); _generatedTextures.Add(val4); _textureOverrides[(Texture)(object)val] = (Texture)(object)val4; _textureOverridesByName[((Object)val).name] = (Texture)(object)val4; Texture2D[] array = Resources.FindObjectsOfTypeAll<Texture2D>(); foreach (Texture2D val5 in array) { if (!((Object)(object)val5 == (Object)null) && val5 != val && string.Equals(((Object)val5).name, ((Object)val).name, StringComparison.OrdinalIgnoreCase)) { _textureOverrides[(Texture)(object)val5] = (Texture)(object)val4; } } _logger.LogInfo((object)("[CustomTextureReplacer] Using runtime texture override for '" + ((Object)val).name + "'.")); _logger.LogDebug((object)$"[CustomTextureReplacer] Override '{((Object)val).name}' uses custom '{((Object)replacement).name}' ({((Texture)replacement).width}x{((Texture)replacement).height})."); SafeAppendDebug("Texture override created for " + ((Object)val).name); _overridesDirty = true; ApplyTextureOverridesToMaterials((Texture)(object)val); return true; } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Failed to create texture override for '" + ((target != null) ? ((Object)target).name : null) + "': " + ex.Message)); SafeAppendDebug("Texture override failed for " + ((target != null) ? ((Object)target).name : null) + ": " + ex.Message); return false; } } private bool TryCreateSpriteOverride(Sprite originalSprite, Texture2D replacement, int width, int height) { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)originalSprite == (Object)null || (Object)(object)replacement == (Object)null) { return false; } try { if (_spriteOverridesByName.TryGetValue(((Object)originalSprite).name, out var value) && (Object)(object)value != (Object)null) { Texture2D texture = value.texture; if ((Object)(object)texture != (Object)null && ((Texture)texture).width == width && ((Texture)texture).height == height) { if (texture != null) { Texture2D val = texture; if (TryCopyIntoTexture((Texture)(object)replacement, val, width, height, ((Object)originalSprite).name)) { _spriteOverrides[originalSprite] = value; _spriteOverridesByName[((Object)originalSprite).name] = value; _spriteOverrideTextures[value] = val; RegisterPersistentSpriteOverride(originalSprite, replacement, width, height); _overridesDirty = true; return true; } } RemoveSpriteOverride(((Object)originalSprite).name); } } RemoveSpriteOverride(((Object)originalSprite).name); RenderTexture temporary = RenderTexture.GetTemporary(width, height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)replacement, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; Texture2D val2 = new Texture2D(width, height, (TextureFormat)4, false) { name = ((Object)originalSprite).name + "_Custom" }; val2.ReadPixels(new Rect(0f, 0f, (float)width, (float)height), 0, 0); val2.Apply(); RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); _generatedTextures.Add(val2); Rect rect = originalSprite.rect; Vector2 size = ((Rect)(ref rect)).size; Vector2 val3 = ((((Vector2)(ref size)).sqrMagnitude > 0f) ? new Vector2(originalSprite.pivot.x / ((Rect)(ref rect)).width, originalSprite.pivot.y / ((Rect)(ref rect)).height) : new Vector2(0.5f, 0.5f)); Sprite val4 = Sprite.Create(val2, new Rect(0f, 0f, (float)width, (float)height), val3, originalSprite.pixelsPerUnit, 0u, (SpriteMeshType)0, originalSprite.border, false); ((Object)val4).name = ((Object)originalSprite).name; _generatedSprites.Add(val4); _spriteOverrideTextures[val4] = val2; _spriteOverrides[originalSprite] = val4; _spriteOverridesByName[((Object)originalSprite).name] = val4; _spriteOverrides[val4] = val4; Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>(); foreach (Sprite val5 in array) { if (!((Object)(object)val5 == (Object)null) && val5 != originalSprite && string.Equals(((Object)val5).name, ((Object)originalSprite).name, StringComparison.OrdinalIgnoreCase)) { _spriteOverrides[val5] = val4; } } _logger.LogInfo((object)("[CustomTextureReplacer] Created runtime sprite override for '" + ((Object)originalSprite).name + "'.")); _logger.LogDebug((object)$"[CustomTextureReplacer] Sprite override '{((Object)originalSprite).name}' uses custom '{((Object)replacement).name}' ({((Texture)replacement).width}x{((Texture)replacement).height})."); SafeAppendDebug("Sprite override created for " + ((Object)originalSprite).name); RegisterPersistentSpriteOverride(originalSprite, replacement, width, height); _overridesDirty = true; ApplySpriteOverridesToComponents(); return true; } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Failed to create sprite override for '" + ((originalSprite != null) ? ((Object)originalSprite).name : null) + "': " + ex.Message)); SafeAppendDebug("Sprite override failed for " + ((originalSprite != null) ? ((Object)originalSprite).name : null) + ": " + ex.Message); return false; } } private void RemoveTextureOverride(Texture target) { if ((Object)(object)target == (Object)null) { return; } Texture value3; if (_textureOverrides.TryGetValue(target, out var value)) { _textureOverrides.Remove(target); Texture2D val = (Texture2D)(object)((value is Texture2D) ? value : null); if (val != null && _generatedTextures.Remove(val)) { Object.Destroy((Object)(object)val); } if (_textureOverridesByName.TryGetValue(((Object)target).name, out var value2) && value2 == value) { _textureOverridesByName.Remove(((Object)target).name); } _overridesDirty = true; } else if (_textureOverridesByName.TryGetValue(((Object)target).name, out value3)) { Texture2D val2 = (Texture2D)(object)((value3 is Texture2D) ? value3 : null); if (val2 != null && _generatedTextures.Remove(val2)) { _textureOverridesByName.Remove(((Object)target).name); Object.Destroy((Object)(object)val2); _overridesDirty = true; } } } private void RemoveSpriteOverride(string spriteName) { if (string.IsNullOrEmpty(spriteName)) { return; } if (_spriteOverridesByName.TryGetValue(spriteName, out var existing)) { List<Sprite> list = (from kvp in _spriteOverrides.Where(delegate(KeyValuePair<Sprite, Sprite> kvp) { int result; if (kvp.Value != existing) { Sprite key = kvp.Key; result = (string.Equals((key != null) ? ((Object)key).name : null, spriteName, StringComparison.OrdinalIgnoreCase) ? 1 : 0); } else { result = 1; } return (byte)result != 0; }) select kvp.Key).ToList(); foreach (Sprite item in list) { _spriteOverrides.Remove(item); } _spriteOverridesByName.Remove(spriteName); if (_spriteOverrideTextures.TryGetValue(existing, out var value)) { _spriteOverrideTextures.Remove(existing); if ((Object)(object)value != (Object)null && _generatedTextures.Remove(value)) { Object.Destroy((Object)(object)value); } } if (_generatedSprites.Remove(existing)) { Object.Destroy((Object)(object)existing); } _overridesDirty = true; } if (!_spritePersistence.TryGetValue(spriteName, out var value2)) { return; } _spritePersistence.Remove(spriteName); if (!string.IsNullOrEmpty(value2.TargetTextureName) && _spritePersistenceByTexture.TryGetValue(value2.TargetTextureName, out var value3)) { value3.RemoveAll((PersistentSpriteOverride entry) => string.Equals(entry.SpriteName, spriteName, StringComparison.OrdinalIgnoreCase)); if (value3.Count == 0) { _spritePersistenceByTexture.Remove(value2.TargetTextureName); } } } private Texture GetReplacementTexture(Texture original) { if ((Object)(object)original == (Object)null) { return null; } if (_textureOverrides.TryGetValue(original, out var value)) { return value; } if (_textureOverridesByName.TryGetValue(((Object)original).name, out var value2)) { return value2; } return null; } internal Sprite GetReplacementSprite(Sprite original) { if ((Object)(object)original == (Object)null) { return null; } if (_spriteOverrides.TryGetValue(original, out var value)) { return value; } if (_spriteOverridesByName.TryGetValue(((Object)original).name, out var value2)) { return value2; } return null; } private void ApplyTextureOverridesToMaterials(Texture targetHint = null) { if (_textureOverrides.Count == 0 && _textureOverridesByName.Count == 0) { return; } Material[] array = Resources.FindObjectsOfTypeAll<Material>(); foreach (Material val in array) { if ((Object)(object)val == (Object)null) { continue; } string[] texturePropertyNames = val.GetTexturePropertyNames(); foreach (string text in texturePropertyNames) { Texture texture = val.GetTexture(text); if (!((Object)(object)texture == (Object)null) && (!((Object)(object)targetHint != (Object)null) || texture == targetHint || string.Equals(((Object)texture).name, ((Object)targetHint).name, StringComparison.OrdinalIgnoreCase))) { Texture replacementTexture = GetReplacementTexture(texture); if ((Object)(object)replacementTexture != (Object)null && texture != replacementTexture) { val.SetTexture(text, replacementTexture); } } } } } private void ApplyTextureOverridesToRenderers() { if (_textureOverrides.Count == 0 && _textureOverridesByName.Count == 0) { return; } Renderer[] array = Resources.FindObjectsOfTypeAll<Renderer>(); foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } bool flag = false; val.GetPropertyBlock(_propertyBlock); string[] rendererTextureProperties = RendererTextureProperties; foreach (string text in rendererTextureProperties) { Texture val2 = null; try { val2 = _propertyBlock.GetTexture(text); } catch { val2 = null; } if (!((Object)(object)val2 == (Object)null)) { Texture replacementTexture = GetReplacementTexture(val2); if ((Object)(object)replacementTexture != (Object)null && val2 != replacementTexture) { _propertyBlock.SetTexture(text, replacementTexture); flag = true; } } } if (flag) { val.SetPropertyBlock(_propertyBlock); } } } private void ApplySpriteOverridesToComponents() { if (_spriteOverridesByName.Count == 0 && _spriteOverrides.Count == 0) { return; } SpriteRenderer[] array = Resources.FindObjectsOfTypeAll<SpriteRenderer>(); foreach (SpriteRenderer val in array) { if (!((Object)(object)val == (Object)null)) { Sprite replacementSprite = GetReplacementSprite(val.sprite); if ((Object)(object)replacementSprite != (Object)null && val.sprite != replacementSprite) { string[] obj = new string[7] { "SpriteRenderer override applied on '", ((Object)val).name, "' using sprite '", ((Object)replacementSprite).name, "' (was '", null, null }; Sprite sprite = val.sprite; obj[5] = ((sprite != null) ? ((Object)sprite).name : null) ?? "<null>"; obj[6] = "')."; SafeAppendDebug(string.Concat(obj)); val.sprite = replacementSprite; } } } if (UIImageType != null && UIImageSpriteProperty != null) { Object[] array2 = Resources.FindObjectsOfTypeAll(UIImageType); foreach (Object val2 in array2) { Image val3 = (Image)(object)((val2 is Image) ? val2 : null); if (val3 != null && (Object)(object)val3 != (Object)null) { Sprite assigned = val3.overrideSprite ?? val3.sprite; ApplySpriteOverrideToImage(val3, assigned); } } } if (!(UIRawImageType != null) || !(UIRawImageTextureProperty != null)) { return; } Object[] array3 = Resources.FindObjectsOfTypeAll(UIRawImageType); foreach (Object val4 in array3) { RawImage val5 = (RawImage)(object)((val4 is RawImage) ? val4 : null); if (val5 != null && (Object)(object)val5 != (Object)null) { ApplyTextureOverrideToRawImage(val5, val5.texture); } } } internal void ApplySpriteOverrideToImage(Image image, Sprite assigned) { if ((Object)(object)image == (Object)null || _applyingImageOverride) { return; } Sprite val = assigned ?? image.sprite; Sprite replacementSprite = GetReplacementSprite(val); int instanceID = ((Object)image).GetInstanceID(); if ((Object)(object)replacementSprite != (Object)null && val != replacementSprite) { _imageOverrideInstances.Add(instanceID); if (image.overrideSprite != replacementSprite) { _applyingImageOverride = true; image.overrideSprite = replacementSprite; _applyingImageOverride = false; ((Graphic)image).SetAllDirty(); SafeAppendDebug("UI Image override applied: " + (((val != null) ? ((Object)val).name : null) ?? "<null>") + " -> " + ((Object)replacementSprite).name); } } else if (_imageOverrideInstances.Remove(instanceID)) { _applyingImageOverride = true; if ((Object)(object)assigned != (Object)null && image.sprite != assigned) { image.overrideSprite = assigned; } else { image.overrideSprite = null; } _applyingImageOverride = false; ((Graphic)image).SetAllDirty(); SafeAppendDebug("UI Image override cleared for '" + (((val != null) ? ((Object)val).name : null) ?? "<null>") + "'."); } } internal void ApplyTextureOverrideToRawImage(RawImage rawImage, Texture assigned) { if ((Object)(object)rawImage == (Object)null || _applyingRawImageOverride) { return; } Texture val = assigned ?? rawImage.texture; Texture replacementTexture = GetReplacementTexture(val); int instanceID = ((Object)rawImage).GetInstanceID(); if ((Object)(object)replacementTexture != (Object)null && val != replacementTexture) { _rawImageOverrideInstances.Add(instanceID); if (rawImage.texture != replacementTexture) { _applyingRawImageOverride = true; rawImage.texture = replacementTexture; _applyingRawImageOverride = false; ((Graphic)rawImage).SetAllDirty(); SafeAppendDebug("UI RawImage override applied: " + (((val != null) ? ((Object)val).name : null) ?? "<null>") + " -> " + ((Object)replacementTexture).name); } } else if (_rawImageOverrideInstances.Remove(instanceID)) { if (rawImage.texture != val) { _applyingRawImageOverride = true; rawImage.texture = val; _applyingRawImageOverride = false; } ((Graphic)rawImage).SetAllDirty(); SafeAppendDebug("UI RawImage override cleared for '" + (((val != null) ? ((Object)val).name : null) ?? "<null>") + "'."); } } private bool ValidateTextureFormat(TextureFormat format, int width, int height) { //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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_001f: 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_0024: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 if ((int)format <= 12) { if ((int)format == 10 || (int)format == 12) { goto IL_0028; } } else if ((int)format == 25 || format - 28 <= 1) { goto IL_0028; } return true; IL_0028: return false; } private bool TryGetTextureSize(Texture texture, out int width, out int height) { width = 0; height = 0; Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (val == null) { RenderTexture val2 = (RenderTexture)(object)((texture is RenderTexture) ? texture : null); if (val2 != null) { width = ((Texture)val2).width; height = ((Texture)val2).height; return true; } if ((Object)(object)texture != (Object)null) { width = texture.width; height = texture.height; return width > 0 && height > 0; } return false; } width = ((Texture)val).width; height = ((Texture)val).height; return true; } private bool TryGetGraphicsFormat(Texture texture, out GraphicsFormat format) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected I4, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected I4, but got Unknown format = (GraphicsFormat)0; Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (val == null) { RenderTexture val2 = (RenderTexture)(object)((texture is RenderTexture) ? texture : null); if (val2 != null) { format = (GraphicsFormat)(int)val2.graphicsFormat; return (int)format != 0; } return false; } format = (GraphicsFormat)(int)((Texture)val).graphicsFormat; return (int)format != 0; } private bool TryGetSliceDimensions(Texture texture, int dstX, int dstY, int width, int height, out int sliceWidth, out int sliceHeight, out bool clipped, out string failureReason) { //IL_0093: 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) sliceWidth = 0; sliceHeight = 0; clipped = false; failureReason = string.Empty; if (!TryGetTextureSize(texture, out var width2, out var height2)) { return false; } int num = width2 - dstX; int num2 = height2 - dstY; if (num <= 0 || num2 <= 0) { return false; } sliceWidth = Mathf.Min(width, num); sliceHeight = Mathf.Min(height, num2); if (sliceWidth <= 0 || sliceHeight <= 0) { return false; } if (TryGetGraphicsFormat(texture, out var format) && GraphicsFormatUtility.IsCompressedFormat(format)) { failureReason = string.Format("[CustomTextureReplacer] Texture '{0}' uses compressed format ({1}), switching to runtime override.", ((texture != null) ? ((Object)texture).name : null) ?? "<unnamed>", format); return false; } clipped = sliceWidth != width || sliceHeight != height; return true; } private void DumpAllTextures() { try { CollectCandidateTextures(_textureBuffer); _textureBuffer.Sort((Texture2D a, Texture2D b) => string.Compare((a != null) ? ((Object)a).name : null, (b != null) ? ((Object)b).name : null, StringComparison.OrdinalIgnoreCase)); _knownTextureIds.Clear(); using StreamWriter streamWriter = new StreamWriter(_dumpFile, append: false); foreach (Texture2D item in _textureBuffer) { if (!((Object)(object)item == (Object)null)) { _knownTextureIds.Add(((Object)item).GetInstanceID()); string value = $"{((Object)item).name} ({((Texture)item).width}x{((Texture)item).height})"; streamWriter.WriteLine(value); } } _logger.LogInfo((object)$"[CustomTextureReplacer] Dumped texture list to '{_dumpFile}' ({_knownTextureIds.Count} entries)."); SafeAppendDebug($"DumpAllTextures wrote {_knownTextureIds.Count} entries"); } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Unable to dump textures: " + ex.Message)); SafeAppendDebug("DumpAllTextures exception: " + ex.Message); } } private void DumpAllSprites() { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) try { CollectCandidateSprites(_spriteBuffer); _spriteBuffer.Sort((Sprite a, Sprite b) => string.Compare((a != null) ? ((Object)a).name : null, (b != null) ? ((Object)b).name : null, StringComparison.OrdinalIgnoreCase)); using StreamWriter streamWriter = new StreamWriter(_spriteDumpFile, append: false); foreach (Sprite item in _spriteBuffer) { if (!((Object)(object)item == (Object)null)) { Texture2D texture = item.texture; string text = (((Object)(object)texture != (Object)null) ? ((Object)texture).name : "<null>"); Rect textureRect = item.textureRect; string value = $"{((Object)item).name} -> {text} ({Mathf.RoundToInt(((Rect)(ref textureRect)).width)}x{Mathf.RoundToInt(((Rect)(ref textureRect)).height)} at {Mathf.RoundToInt(((Rect)(ref textureRect)).x)},{Mathf.RoundToInt(((Rect)(ref textureRect)).y)})"; streamWriter.WriteLine(value); } } _logger.LogInfo((object)$"[CustomTextureReplacer] Dumped sprite list to '{_spriteDumpFile}' ({_spriteBuffer.Count} entries)."); SafeAppendDebug($"DumpAllSprites wrote {_spriteBuffer.Count} entries"); } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Unable to dump sprites: " + ex.Message)); SafeAppendDebug("DumpAllSprites exception: " + ex.Message); } } private void DumpAllMeshes() { if (string.IsNullOrEmpty(_meshDumpFile)) { return; } try { Mesh[] source = Resources.FindObjectsOfTypeAll<Mesh>(); List<Mesh> list = source.Where((Mesh mesh) => (Object)(object)mesh != (Object)null).OrderBy<Mesh, string>((Mesh mesh) => ((Object)mesh).name, StringComparer.OrdinalIgnoreCase).ToList(); using StreamWriter streamWriter = new StreamWriter(_meshDumpFile, append: false); foreach (Mesh item in list) { int num = (((Object)(object)item != (Object)null) ? item.vertexCount : 0); int num2 = (((Object)(object)item != (Object)null) ? item.subMeshCount : 0); streamWriter.WriteLine($"{((Object)item).name} (vertices: {num}, subMeshes: {num2})"); } _logger.LogInfo((object)$"[CustomTextureReplacer] Dumped mesh list to '{_meshDumpFile}' ({list.Count} entries)."); SafeAppendDebug($"DumpAllMeshes wrote {list.Count} entries"); } catch (Exception ex) { _logger.LogWarning((object)("[CustomTextureReplacer] Unable to dump meshes: " + ex.Message)); SafeAppendDebug("DumpAllMeshes exception: " + ex.Message); } } private bool ResolveMeshOverridesPath() { string a = _meshOverridesPath ?? string.Empty; List<string> candidates = new List<string>(); HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (_textureFolders != null) { foreach (string textureFolder in _textureFolders) { if (!string.IsNullOrWhiteSpace(textureFolder)) { AddCandidate(Path.Combine(textureFolder, "MeshOverrides.json")); } } } AddCandidate(Path.Combine(Paths.PluginPath, "CustomTextures", "MeshOverrides.json")); AddCandidate(Path.Combine(Paths.PluginPath, "MeshOverrides.json")); string text = string.Empty; foreach (string item in candidates) { try { if (File.Exists(item)) { text = item; break; } } catch { } if (string.IsNullOrEmpty(text)) { text = item; } } if (string.IsNullOrEmpty(text)) { text = Path.Combine(Paths.PluginPath, "MeshOverrides.json"); } bool flag = !string.Equals(a, text, StringComparison.OrdinalIgnoreCase); _meshOverridesPath = text; if (flag) { SafeAppendDebug("MeshOverrides path resolved: " + _meshOverridesPath); } return flag; void AddCandidate(string candidate) { if (string.IsNullOrWhiteSpace(candidate)) { return; } try { string fullPath = Path.GetFullPath(candidate); if (seen.Add(fullPath)) { candidates.Add(fullPath); } } catch { if (seen.Add(candidate)) { candidates.Add(candidate); } } } } private void LoadMeshOverrides(bool logDetails) { SafeAppendDebug("LoadMeshOverrides invoked."); RevertAllMeshOverrides(); DisposeMeshOverrideResources(); _meshCardOverrides.Clear(); if (string.IsNullOrEmpty(_meshOverridesPath)) { SafeAppendDebug("MeshOverrides path empty; nothing to load."); ScheduleMeshOverrideReapply(0f); RequestReapply("MeshOverridesReloaded"); return; } string text = _meshOverridesPath; try { text = NormalizePath(text); } catch { } if (!File.Exists(text)) { if (logDetails) { _logger.LogInfo((object)("[CustomTextureReplacer] Mesh override file not found at '" + text + "'.")); } SafeAppendDebug("MeshOverrides file not found: " + text); ScheduleMeshOverrideReapply(0f); RequestReapply("MeshOverridesReloaded"); return; } bool flag = false; try { string json = File.ReadAllText(text); object obj2 = MiniJson.Deserialize(json); IList list = null; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); if (obj2 is IList list2) { list = list2; } else if (obj2 is Dictionary<string, object> dictionary) { CollectMeshTextureFolders(dictionary, "textureFolders", Path.GetDirectoryName(text) ?? Paths.PluginPath, hashSet); CollectMeshTextureFolders(dictionary, "textureFolder", Path.GetDirectoryName(text) ?? Paths.PluginPath, hashSet); object value2; if (dictionary.TryGetValue("overrides", out var value) && value is IList list3) { list = list3; } else if (dictionary.TryGetValue("entries", out value2) && value2 is IList list4) { list = list4; } } if (list == null) { list = Array.Empty<object>(); } string text2 = Path.GetDirectoryName(text) ?? Paths.PluginPath; int num = 0; HashSet<string> hashSet2 = new HashSet<string>(hashSet, StringComparer.OrdinalIgnoreCase); foreach (object item in list) { if (!(item is Dictionary<string, object> dictionary2)) { continue; } string @string = GetString(dictionary2, "target"); if (string.IsNullOrWhiteSpace(@string)) { SafeAppendDebug("MeshOverride entry skipped (missing target)."); continue; } string text3 = GetString(dictionary2, "mesh"); string string2 = GetString(dictionary2, "meshHint"); string[] stringArray = GetStringArray(dictionary2, "materialHints"); bool flag2 = dictionary2.ContainsKey("materials"); bool autoSelectMesh = string.IsNullOrWhiteSpace(text3); bool autoSelectMaterials = !flag2; string string3 = GetString(dictionary2, "bundle"); string resolvedPath = string.Empty; AssetBundle val = null; if (!string.IsNullOrWhiteSpace(string3)) { if (!TryResolveMeshBundlePath(string3, text2, out resolvedPath)) { _logger.LogWarning((object)("[CustomTextureReplacer] Could not loc