using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GlobalEnums; using HarmonyLib; using Microsoft.CodeAnalysis; using Stitchwork; using Stitchwork.GUI; using Stitchwork.GUI.Pillars; using Stitchwork.Handlers; using Stitchwork.Packs; using Stitchwork.Util; using Stitchwork.Watchers; using TMProOld; using TeamCherry.Cinematics; using TeamCherry.Localization; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.Scripting; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using UnityEngine.UI; using UnityEngine.Video; using UnityStandardAssets.ImageEffects; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Stitchwork")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.8.6.0")] [assembly: AssemblyInformationalVersion("0.8.6+c28c073e0f977ca49375077628d1be4bda3c86ac")] [assembly: AssemblyProduct("Stitchwork")] [assembly: AssemblyTitle("Stitchwork")] [assembly: AssemblyMetadata("GameLibsPin", "1.2.0-silksong1.0.30000")] [assembly: AssemblyMetadata("DevTools", "false")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.8.6.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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; } } } [HarmonyPatch] public static class AnimationController { private sealed class AnimEntry { public string Name; public int Seq; public tk2dSpriteAnimator Live; public bool Stale; public float LastSeenRealtime; public tk2dSpriteAnimationClip Clip; public tk2dSpriteCollectionData FrameColl; public int Frame; public int SpriteId; public string ClipName; public string CollName; public string SpriteName; public int FrameCount; public tk2dSpriteAnimation Library; public tk2dSpriteAnimationClip ViewClip; public int ViewFrame; public readonly List RecentClips = new List(); } private sealed class ClipCoverage { public int Frames; public int Shared; public int SharedFromPack; public int PerClip; public readonly List MissingShared = new List(); } private static bool Paused = false; private static bool FrameChangeRequested = false; private static bool Frozen = false; private static Vector3 FrozenPosition; private const string HeroName = "Hero_Hornet(Clone)"; private const int MaxRecentClips = 5; private static readonly Dictionary _entries = new Dictionary(); private static int _seqCounter = 0; private const int MaxEntries = 64; private static readonly Color ColStale = new Color(0.5f, 0.5f, 0.5f); private static bool _browseClips; private static Vector2 _objectScroll; private static Vector2 _clipScroll; private static string _clipSearch = ""; private static tk2dSpriteCollectionData _lensColl; private static int _lensSpriteId = -1; private static string _lensSpriteName; private static Action _pendingUiAction; private static bool _previewPlaying; private static tk2dSpriteAnimationClip _previewClip; private static int _previewFrame; private static float _previewClock; private static float _previewFps = 12f; private const float PreviewFallbackFps = 12f; private static float _wsCountAt = -999f; private static int _wsListSize = -1; private static List _wsClips; private const float SuffixColW = 132f; private static GUIStyle _suffixStyle; private static readonly Dictionary<(int libId, string clip, string coll), ClipCoverage> _coverage = new Dictionary<(int, string, string), ClipCoverage>(); private static int _coverageIndexVersion = -1; private static readonly FieldInfo _windyLibField = AccessTools.Field(typeof(HeroAnimationController), "windyAnimLib"); private static readonly Type _heroConfigType = AccessTools.TypeByName("HeroControllerConfig"); private static readonly FieldInfo _heroConfigOverrideLib = ((_heroConfigType != null) ? AccessTools.Field(_heroConfigType, "heroAnimOverrideLib") : null); private static readonly List _configOverrideLibs = new List(); private static float _configLibsNextScan; private static readonly Dictionary _libDominantColl = new Dictionary(); private static int _dominantCollVersion = -1; private const string KeybindHex = "#B78CFF"; private static GUIStyle _transportStyle; private static GUIStyle _hintStyle; private static Texture2D _spritePreviewTex; private static int _spvCollId = 0; private static int _spvSpriteId = -1; private static int _spvFileVer = -1; private static readonly Dictionary<(int coll, int sprite), Texture2D> _thumbCache = new Dictionary<(int, int), Texture2D>(); private static int _thumbFileVer = -1; private const int ThumbCacheCap = 96; private const int ThumbRenderSize = 96; private static Texture2D _previewWhite; private static string _reuseCacheKey = null; private static int _reuseFrameCount = 0; private static int _reuseClipCount = 0; private static readonly List<(tk2dSpriteAnimationClip Clip, List Frames)> _reuseSources = new List<(tk2dSpriteAnimationClip, List)>(); private static string _anchorCacheKey = null; private static bool _anchorReplExists = false; private static string _anchorCurrent = null; private static string _anchorFilePath = null; private static string _anchorPackSource = null; public static string SelectedAnimator { get; private set; } = null; private static GUIStyle SuffixStyle { get { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown object obj = _suffixStyle; if (obj == null) { GUIStyle val = new GUIStyle(GUIHelper.LabelStyle) { wordWrap = false, clipping = (TextClipping)1 }; _suffixStyle = val; obj = (object)val; } return (GUIStyle)obj; } } private static GUIStyle TransportStyle { get { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown int num = GUIHelper.FontSize(12); if (_transportStyle == null || _transportStyle.fontSize != num) { _transportStyle = new GUIStyle(GUI.skin.button) { fontSize = num, richText = true, alignment = (TextAnchor)4 }; } return _transportStyle; } } private static GUIStyle HintStyle { get { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown int num = GUIHelper.FontSize(12); if (_hintStyle == null || _hintStyle.fontSize != num) { _hintStyle = new GUIStyle(GUI.skin.label) { fontSize = num, wordWrap = true }; } return _hintStyle; } } internal static void SelectFromWorldPick(string name) { SelectedAnimator = name; _browseClips = true; } public static void RegisterAnimator(tk2dSpriteAnimator animator) { if (!((Object)(object)animator == (Object)null) && !((Object)(object)((Component)animator).gameObject == (Object)null)) { string name = ((Object)((Component)animator).gameObject).name; if (!_entries.TryGetValue(name, out var value)) { value = new AnimEntry { Name = name, Seq = _seqCounter++ }; _entries[name] = value; } value.Live = animator; value.Stale = false; } } public static void ClearAnimators() { _entries.Clear(); SelectedAnimator = null; Paused = false; Frozen = false; _seqCounter = 0; _browseClips = false; _clipSearch = ""; ClearLens(); StopPreview(); _coverage.Clear(); ClearThumbCache(); } public static void ApplyPatches(Harmony harmony) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Expected O, but got Unknown //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Expected O, but got Unknown //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Expected O, but got Unknown //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Expected O, but got Unknown //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "Play", (Type[])null, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "Play", new Type[1] { typeof(string) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayWithNamePatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "Play", new Type[1] { typeof(tk2dSpriteAnimationClip) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayWithClipPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFromFrame", new Type[1] { typeof(int) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromFramePatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFromFrame", new Type[2] { typeof(string), typeof(int) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromFrameWithNamePatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFromFrame", new Type[2] { typeof(tk2dSpriteAnimationClip), typeof(int) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromFrameWithClipPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFrom", new Type[1] { typeof(float) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFrom", new Type[2] { typeof(string), typeof(float) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromWithNamePatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "PlayFrom", new Type[2] { typeof(tk2dSpriteAnimationClip), typeof(float) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayFromWithClipPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteAnimator), "Play", new Type[3] { typeof(tk2dSpriteAnimationClip), typeof(float), typeof(float) }, (Type[])null), new HarmonyMethod(typeof(AnimationController), "PlayOverrideFpsPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool PlayPatchInternal(tk2dSpriteAnimator __instance) { long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)__instance != (Object)null) { RegisterAnimator(__instance); if (SelectedAnimator == ((Object)((Component)__instance).gameObject).name && Paused && !FrameChangeRequested) { return false; } } return true; } finally { DevProfiler.AddOpPositiveNetHeapDelta("Prefix.tk2dAnim.Play", heapBefore); } } public static bool PlayPatch(tk2dSpriteAnimator __instance) { return PlayPatchInternal(__instance); } public static bool PlayWithNamePatch(tk2dSpriteAnimator __instance, string name) { return PlayPatchInternal(__instance); } public static bool PlayWithClipPatch(tk2dSpriteAnimator __instance, tk2dSpriteAnimationClip clip) { return PlayPatchInternal(__instance); } public static bool PlayFromFramePatch(tk2dSpriteAnimator __instance, int frame) { return PlayPatchInternal(__instance); } public static bool PlayFromFrameWithNamePatch(tk2dSpriteAnimator __instance, string name, int frame) { return PlayPatchInternal(__instance); } public static bool PlayFromFrameWithClipPatch(tk2dSpriteAnimator __instance, tk2dSpriteAnimationClip clip, int frame) { return PlayPatchInternal(__instance); } public static bool PlayFromPatch(tk2dSpriteAnimator __instance, float clipStartTime) { return PlayPatchInternal(__instance); } public static bool PlayFromWithNamePatch(tk2dSpriteAnimator __instance, string name, float clipStartTime) { return PlayPatchInternal(__instance); } public static bool PlayFromWithClipPatch(tk2dSpriteAnimator __instance, tk2dSpriteAnimationClip clip, float clipStartTime) { return PlayPatchInternal(__instance); } public static bool PlayOverrideFpsPatch(tk2dSpriteAnimator __instance, tk2dSpriteAnimationClip clip, float clipStartTime, float overrideFps) { return PlayPatchInternal(__instance); } private static bool TryGetLiveSelected(out tk2dSpriteAnimator animator) { animator = null; if (SelectedAnimator != null && _entries.TryGetValue(SelectedAnimator, out var value) && (Object)(object)value.Live != (Object)null && (Object)(object)((Component)value.Live).gameObject != (Object)null) { animator = value.Live; } return (Object)(object)animator != (Object)null; } public static void Update() { //IL_000c: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) if (!GUIHelper.IsTextFieldFocused) { if (Input.GetKeyDown(Plugin.Config.AnimationControllerPauseKey)) { TogglePause(); } if (Input.GetKeyDown(Plugin.Config.AnimationControllerFreezeKey)) { ToggleFreeze(); } if (Input.GetKeyDown(Plugin.Config.AnimationControllerNextFrameKey)) { StepFrame(1); } if (Input.GetKeyDown(Plugin.Config.AnimationControllerPrevFrameKey)) { StepFrame(-1); } } if (Frozen && TryGetLiveSelected(out var animator)) { ((Component)animator).gameObject.transform.position = FrozenPosition; } if (_previewPlaying) { tk2dSpriteAnimationClip previewClip = _previewClip; if (previewClip == null || previewClip.frames == null || previewClip.frames.Length == 0) { StopPreview(); } else { int num = previewClip.frames.Length; float num2 = 1f / _previewFps; _previewClock += Time.unscaledDeltaTime; while (_previewClock >= num2) { _previewClock -= num2; _previewFrame = (_previewFrame + 1) % num; } } } foreach (AnimEntry value in _entries.Values) { tk2dSpriteAnimator live = value.Live; if ((Object)(object)live == (Object)null || (Object)(object)((Component)live).gameObject == (Object)null) { value.Live = null; MarkStale(value); continue; } if (!((Component)live).gameObject.activeSelf || live.CurrentClip == null || live.CurrentFrame < 0 || live.CurrentFrame >= live.CurrentClip.frames.Length) { MarkStale(value); continue; } RefreshSnapshot(value, live); value.Stale = false; value.ViewClip = null; value.LastSeenRealtime = Time.realtimeSinceStartup; } if (_entries.Count > 64) { EvictOldestStale(); } } private static void RefreshSnapshot(AnimEntry e, tk2dSpriteAnimator a) { tk2dSpriteAnimationClip currentClip = a.CurrentClip; int currentFrame = a.CurrentFrame; tk2dSpriteAnimationFrame val = currentClip.frames[currentFrame]; if (e.ClipName != currentClip.name) { e.RecentClips.Remove(currentClip.name); e.RecentClips.Insert(0, currentClip.name); if (e.RecentClips.Count > 5) { e.RecentClips.RemoveAt(5); } } e.Clip = currentClip; e.Frame = currentFrame; e.FrameColl = val.spriteCollection; e.SpriteId = val.spriteId; e.ClipName = currentClip.name; e.FrameCount = currentClip.frames.Length; e.Library = a.Library; if ((Object)(object)e.FrameColl != (Object)null && val.spriteId >= 0 && val.spriteId < e.FrameColl.spriteDefinitions.Length) { e.CollName = ((Object)e.FrameColl).name; e.SpriteName = e.FrameColl.spriteDefinitions[val.spriteId].name; } } private static void MarkStale(AnimEntry e) { e.Stale = true; if (e.ViewClip == null && e.Clip != null) { e.ViewClip = e.Clip; e.ViewFrame = e.Frame; } } private static void EvictOldestStale() { List list = (from e in _entries.Values where e.Stale && e.Name != SelectedAnimator && e.Name != "Hero_Hornet(Clone)" orderby e.LastSeenRealtime select e).ToList(); int num = _entries.Count - 64; int num2 = 0; while (num2 < list.Count && num > 0) { _entries.Remove(list[num2].Name); num2++; num--; } } private static void SelectAnimator(AnimEntry e) { //IL_003d: 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) Frozen = false; if (Paused && TryGetLiveSelected(out var animator)) { animator.Resume(); } Paused = false; SelectedAnimator = e.Name; _browseClips = true; _clipSearch = ""; _clipScroll = Vector2.zero; ClearLens(); StopPreview(); } private static void TogglePause() { StopPreview(); if (TryGetLiveSelected(out var animator)) { Paused = !Paused; if (Paused) { animator.Pause(); } else { animator.Resume(); } } } private static void ToggleFreeze() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) if (TryGetLiveSelected(out var animator)) { Frozen = !Frozen; FrozenPosition = ((Component)animator).gameObject.transform.position; } } private static void StepFrame(int delta) { StopPreview(); tk2dSpriteAnimator animator; if (SelectedAnimator != null && _entries.TryGetValue(SelectedAnimator, out var value) && (value.Stale || (Object)(object)value.Live == (Object)null)) { if (value.ViewClip != null && value.ViewClip.frames != null && value.ViewClip.frames.Length != 0) { int num = value.ViewClip.frames.Length; value.ViewFrame = ((value.ViewFrame + delta) % num + num) % num; } } else if (TryGetLiveSelected(out animator) && Paused && animator.CurrentClip != null) { int num2 = animator.CurrentClip.frames.Length; if (num2 > 0) { int num3 = ((animator.CurrentFrame + delta) % num2 + num2) % num2; FrameChangeRequested = true; animator.PlayFromFrame(num3); animator.UpdateAnimation(Time.deltaTime); FrameChangeRequested = false; } } } private static float ResolvePreviewFps(AnimEntry e, tk2dSpriteAnimationClip clip) { return 12f; } private static void StopPreview() { _previewPlaying = false; _previewClip = null; } private static void ClearLens() { _lensColl = null; _lensSpriteId = -1; _lensSpriteName = null; } private static void SetLens(tk2dSpriteCollectionData coll, int spriteId, string spriteName) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) _lensColl = coll; _lensSpriteId = spriteId; _lensSpriteName = spriteName; _browseClips = true; _clipScroll = Vector2.zero; _clipSearch = ""; } private static AnimEntry SelectedEntry() { if (SelectedAnimator == null || !_entries.TryGetValue(SelectedAnimator, out var value)) { return null; } return value; } public static void DrawPillarContent() { AnimEntry animEntry = SelectedEntry(); GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (_browseClips && animEntry != null && animEntry.ClipName != null) { DrawClipStage(animEntry); } else { DrawObjectStage(); } GUILayout.EndVertical(); float num = GUIHelper.Scaled(190f); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(num) }); DrawPreviewColumn(animEntry, num); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUIHelper.Space(4f); DrawConsole(animEntry); if (_pendingUiAction != null) { Action pendingUiAction = _pendingUiAction; _pendingUiAction = null; pendingUiAction(); } } private static void DrawObjectStage() { //IL_0000: 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_0006: 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) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = GUIHelper.ColConfirm; if (GUILayout.Button(Loc.T("Pick object in world"), GUIHelper.ButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Height(40f) })) { _pendingUiAction = delegate { WorldPickMode.Enter(WorldPickMode.Preset.Tk2d); }; } GUI.backgroundColor = backgroundColor; if (_entries.Count == 0) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("No animators registered."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } _objectScroll = GUILayout.BeginScrollView(_objectScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); foreach (AnimEntry item in from x in _entries.Values orderby (!(x.Name == "Hero_Hornet(Clone)")) ? 1 : 0, x.Seq select x) { if (item.ClipName != null) { DrawObjectRow(item); } } GUILayout.EndScrollView(); } private static void DrawObjectRow(AnimEntry e) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) bool flag = SelectedAnimator == e.Name; bool flag2 = e.Stale || (Object)(object)e.Live == (Object)null; GUILayout.BeginHorizontal(Array.Empty()); float num = GUIHelper.Scaled(40f); DrawSpriteThumb(GUILayoutUtility.GetRect(num, num, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num), GUILayout.Height(num) }), e.FrameColl, e.SpriteId, flag2); GUILayout.BeginVertical(Array.Empty()); GUI.contentColor = (flag ? GUIHelper.ColConfirm : (flag2 ? ColStale : Color.white)); if (GUILayout.Button(e.Name, GUIHelper.ButtonStyle, Array.Empty())) { _pendingUiAction = delegate { SelectAnimator(e); }; } GUI.contentColor = Color.white; GUI.contentColor = (flag2 ? ColStale : GUIHelper.ColMuted); GUILayout.Label($"{e.ClipName} · {e.Frame + 1}/{e.FrameCount}" + (flag2 ? (" " + Loc.T("(stopped)")) : ""), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUIHelper.Space(2f); } private static void DrawSpriteThumb(Rect r, tk2dSpriteCollectionData coll, int spriteId, bool dim) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) EnsurePreviewWhite(); GUI.color = GUIHelper.ColSurface2; GUI.DrawTexture(r, (Texture)(object)_previewWhite); GUI.color = Color.white; if ((Object)(object)coll == (Object)null || coll.spriteDefinitions == null || spriteId < 0 || spriteId >= coll.spriteDefinitions.Length) { return; } tk2dSpriteDefinition val = coll.spriteDefinitions[spriteId]; Material val2 = (((Object)(object)val.materialInst != (Object)null) ? val.materialInst : val.material); Texture val3 = (((Object)(object)val2 != (Object)null) ? val2.mainTexture : null); Vector2[] uvs = val.uvs; if ((Object)(object)val3 == (Object)null || uvs == null || uvs.Length < 4) { return; } Rect container = default(Rect); ((Rect)(ref container))..ctor(((Rect)(ref r)).x + 2f, ((Rect)(ref r)).y + 2f, ((Rect)(ref r)).width - 4f, ((Rect)(ref r)).height - 4f); GUI.color = (Color)(dim ? new Color(1f, 1f, 1f, 0.45f) : Color.white); Texture2D uprightThumb = GetUprightThumb(coll, spriteId, val, val3); if ((Object)(object)uprightThumb != (Object)null) { GUI.DrawTexture(ScaleToFitRect(container, ((Texture)uprightThumb).width, ((Texture)uprightThumb).height), (Texture)(object)uprightThumb); } else { float num = uvs.Min((Vector2 v) => v.x); float num2 = uvs.Max((Vector2 v) => v.x); float num3 = uvs.Min((Vector2 v) => v.y); float num4 = uvs.Max((Vector2 v) => v.y); int texW = Mathf.Max(1, Mathf.RoundToInt((num2 - num) * (float)val3.width)); int texH = Mathf.Max(1, Mathf.RoundToInt((num4 - num3) * (float)val3.height)); GUI.DrawTextureWithTexCoords(ScaleToFitRect(container, texW, texH), val3, new Rect(num, num3, num2 - num, num4 - num3), true); } GUI.color = Color.white; } private static void DrawClipStage(AnimEntry e) { //IL_009c: 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_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_0440: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_0515: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05d0: Unknown result type (might be due to invalid IL or missing references) bool flag = e.Stale || (Object)(object)e.Live == (Object)null; bool live = !flag && (Object)(object)e.Live != (Object)null && (Object)(object)((Component)e.Live).gameObject != (Object)null; if (GUILayout.Button("◂ " + e.Name + " · " + Loc.T("click to change object"), GUIHelper.ButtonStyle, Array.Empty())) { _pendingUiAction = delegate { _browseClips = false; }; } if (flag) { GUI.contentColor = ColStale; GUILayout.Label(Loc.T("(stopped — inspecting the snapshot)"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } tk2dSpriteAnimation library = e.Library; if ((Object)(object)library == (Object)null || library.clips == null || library.clips.Length == 0) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("No clip library on this object."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(Loc.T("Search:"), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(60f) }); _clipSearch = GUIHelper.TextField(_clipSearch, GUILayout.ExpandWidth(true), GUIHelper.Height(32f)); GUILayout.EndHorizontal(); if (_lensSpriteName != null) { Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = GUIHelper.ColWarn; if (GUILayout.Button(Loc.T("Reusing this sprite") + ": " + _lensSpriteName + " ✕", GUIHelper.ButtonStyle, Array.Empty())) { _pendingUiAction = ClearLens; } GUI.backgroundColor = backgroundColor; } List list = library.clips.Where((tk2dSpriteAnimationClip c) => c != null && !string.IsNullOrEmpty(c.name)).ToList(); IEnumerable enumerable = SecondaryLibClips(e, library); if (enumerable != null) { list.AddRange(enumerable); } HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); foreach (tk2dSpriteAnimationClip item in list) { if (!hashSet2.Add(item.name)) { hashSet.Add(item.name); } } IEnumerable source = list; if (!string.IsNullOrEmpty(_clipSearch)) { string q = _clipSearch; source = source.Where((tk2dSpriteAnimationClip c) => c.name.IndexOf(q, StringComparison.OrdinalIgnoreCase) >= 0); } if (_lensSpriteId >= 0) { source = source.Where((tk2dSpriteAnimationClip c) => ClipUsesSprite(c, _lensColl, _lensSpriteId)); } List list2 = source.OrderBy((tk2dSpriteAnimationClip c) => c.name, StringComparer.OrdinalIgnoreCase).ToList(); HashSet mainSet = new HashSet(library.clips ?? Array.Empty()); int num = list2.Count((tk2dSpriteAnimationClip c) => !mainSet.Contains(c)); int num2 = list2.Count - num; GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label((num > 0) ? string.Format(Loc.T("{0} + {1} variants"), num2, num) : $"{num2} / {library.clips.Length}", GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; bool reserveSuffixCol = false; string dominantCollection = GetDominantCollection(library); foreach (tk2dSpriteAnimationClip item2 in list2) { string text = PrimaryCollectionName(item2); if (text != null && (hashSet.Contains(item2.name) || !string.Equals(text, dominantCollection, StringComparison.Ordinal))) { reserveSuffixCol = true; break; } } string dominantCollection2 = GetDominantCollection(library); if (dominantCollection2 != null) { GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("Collection") + ": " + dominantCollection2, GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } DrawBatchRow(list2); tk2dSpriteAnimationClip val = ((!flag) ? e.Clip : ((e.ViewClip != null) ? e.ViewClip : e.Clip)); string currentClip = ((!flag) ? e.ClipName : ((e.ViewClip != null) ? e.ViewClip.name : e.ClipName)); _clipScroll = GUILayout.BeginScrollView(_clipScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); if (string.IsNullOrEmpty(_clipSearch) && _lensSpriteId < 0 && e.RecentClips.Count > 0) { GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("Recently played"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; foreach (string recentClip in e.RecentClips) { tk2dSpriteAnimationClip val2 = ((val != null && val.name == recentClip) ? val : FindClip(library, recentClip)); if (val2 != null) { DrawClipRow(e, library, val2, val, currentClip, live, hashSet, reserveSuffixCol); } } GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("All clips"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } foreach (tk2dSpriteAnimationClip item3 in list2) { DrawClipRow(e, library, item3, val, currentClip, live, hashSet, reserveSuffixCol); } GUILayout.EndScrollView(); } private static void DrawBatchRow(List list) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) if (AnimCanvasBatch.IsRunning) { GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(string.Format(Loc.T("Batch: {0}/{1}"), AnimCanvasBatch.Processed, AnimCanvasBatch.Total) + " · " + AnimCanvasBatch.CurrentClip, GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUI.contentColor = Color.white; if (GUILayout.Button(Loc.T("Cancel"), GUIHelper.ButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(80f) })) { AnimCanvasBatch.Cancel(); } GUILayout.EndHorizontal(); return; } if (Time.unscaledTime - _wsCountAt > 2f || _wsClips == null || list.Count != _wsListSize) { _wsClips = AnimCanvasBatch.WithWorkspace(list); _wsListSize = list.Count; _wsCountAt = Time.unscaledTime; } GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("Canvas:"), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(58f) }); GUI.contentColor = Color.white; if (GUILayout.Button(string.Format(Loc.T("Extract {0} listed"), list.Count), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasBatch.ExtractAll(list, fromCurrentRender: false); } if (GUILayout.Button(Loc.T("Extract As Rendered"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasBatch.ExtractAll(list, fromCurrentRender: true); } GUI.enabled = _wsClips.Count > 0; if (GUILayout.Button(string.Format(Loc.T("Compile {0} workspaces"), _wsClips.Count), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasBatch.CompileAll(_wsClips); } GUI.enabled = true; GUILayout.EndHorizontal(); } private static void DrawClipRow(AnimEntry e, tk2dSpriteAnimation lib, tk2dSpriteAnimationClip clip, tk2dSpriteAnimationClip currentObj, string currentClip, bool live, HashSet dupNames, bool reserveSuffixCol) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) ClipCoverage coverage = GetCoverage(lib, clip); GUILayout.BeginHorizontal(Array.Empty()); Color color = ((coverage.Shared == 0) ? GUIHelper.ColBorderHi : ((coverage.SharedFromPack > 0) ? GUIHelper.ColPackBlue : GUIHelper.ColConfirm)); string text = string.Format("{0}: {1}/{2}", Loc.T("shared replacements"), coverage.Shared, coverage.Frames); if (coverage.SharedFromPack > 0) { text = text + " · " + string.Format(Loc.T("{0} from pack"), coverage.SharedFromPack); } if (coverage.Shared > 0 && coverage.Shared < coverage.Frames && coverage.MissingShared.Count > 0) { text = text + "\n" + Loc.T("not found") + ": " + string.Join(", ", coverage.MissingShared) + ((coverage.Frames - coverage.Shared > coverage.MissingShared.Count) ? ", …" : ""); } GUIHelper.StatusDotSlot(DotStateFor(coverage.Shared, coverage.Frames), color, text); GUIHelper.StatusDotSlot(DotStateFor(coverage.PerClip, coverage.Frames), (coverage.PerClip > 0) ? GUIHelper.ColAccent : GUIHelper.ColBorderHi, string.Format("{0}: {1}/{2}", Loc.T("per-clip (Appliqué)"), coverage.PerClip, coverage.Frames)); bool flag = ((currentObj != null) ? (clip == currentObj) : (clip.name == currentClip)); GUI.contentColor = (flag ? GUIHelper.ColConfirm : Color.white); if (GUILayout.Button(clip.name, GUIHelper.ButtonStyle, Array.Empty())) { PlayClip(e, clip, live); } GUI.contentColor = Color.white; GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label($"×{((clip.frames != null) ? clip.frames.Length : 0)}", GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(40f) }); GUI.contentColor = Color.white; if (reserveSuffixCol) { string text2 = PrimaryCollectionName(clip); bool flag2 = dupNames?.Contains(clip.name) ?? false; bool num = text2 != null && (flag2 || !string.Equals(text2, GetDominantCollection(lib), StringComparison.Ordinal)); GUI.contentColor = (flag ? GUIHelper.ColConfirm : GUIHelper.ColFaint); GUILayout.Label((GUIContent)((!num) ? ((object)GUIContent.none) : ((object)new GUIContent("· " + text2, text2))), SuffixStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(132f) }); GUI.contentColor = Color.white; } GUILayout.EndHorizontal(); } private static void PlayClip(AnimEntry e, tk2dSpriteAnimationClip clip, bool live) { StopPreview(); if (live && (Object)(object)e.Live != (Object)null) { tk2dSpriteAnimator live2 = e.Live; Paused = true; live2.Pause(); FrameChangeRequested = true; live2.Play(clip); live2.UpdateAnimation(Time.deltaTime); FrameChangeRequested = false; } else { e.ViewClip = clip; e.ViewFrame = 0; } } private static tk2dSpriteAnimationClip FindClip(tk2dSpriteAnimation lib, string name) { tk2dSpriteAnimationClip[] clips = lib.clips; foreach (tk2dSpriteAnimationClip val in clips) { if (val != null && val.name == name) { return val; } } return null; } private static bool ClipUsesSprite(tk2dSpriteAnimationClip clip, tk2dSpriteCollectionData coll, int spriteId) { if (clip == null || clip.frames == null || (Object)(object)coll == (Object)null) { return false; } tk2dSpriteAnimationFrame[] frames = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames) { if ((Object)(object)val.spriteCollection == (Object)(object)coll && val.spriteId == spriteId) { return true; } } return false; } private static void RefreshConfigOverrideLibs() { if (_heroConfigOverrideLib == null || Time.realtimeSinceStartup < _configLibsNextScan) { return; } _configLibsNextScan = Time.realtimeSinceStartup + 5f; _configOverrideLibs.Clear(); Object[] array = Resources.FindObjectsOfTypeAll(_heroConfigType); foreach (Object val in array) { if (!(val == (Object)null)) { object? value = _heroConfigOverrideLib.GetValue(val); tk2dSpriteAnimation val2 = (tk2dSpriteAnimation)((value is tk2dSpriteAnimation) ? value : null); if (val2 != null && (Object)(object)val2 != (Object)null && val2.clips != null && !_configOverrideLibs.Contains(val2)) { _configOverrideLibs.Add(val2); } } } } private static IEnumerable SecondaryLibClips(AnimEntry e, tk2dSpriteAnimation mainLib) { if (e == null) { return null; } tk2dSpriteAnimator live = e.Live; if ((Object)(object)live == (Object)null) { return null; } HeroAnimationController component = ((Component)live).GetComponent(); if ((Object)(object)component == (Object)null) { return null; } List list = new List(); if (_windyLibField != null) { object? value = _windyLibField.GetValue(component); tk2dSpriteAnimation val = (tk2dSpriteAnimation)((value is tk2dSpriteAnimation) ? value : null); if (val != null && (Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)mainLib && val.clips != null) { list.Add(val); } } RefreshConfigOverrideLibs(); foreach (tk2dSpriteAnimation configOverrideLib in _configOverrideLibs) { if ((Object)(object)configOverrideLib != (Object)null && (Object)(object)configOverrideLib != (Object)(object)mainLib && !list.Contains(configOverrideLib)) { list.Add(configOverrideLib); } } if (list.Count == 0) { return null; } HashSet mainSet = new HashSet(mainLib.clips ?? Array.Empty()); HashSet seen = new HashSet(); return from c in list.SelectMany((tk2dSpriteAnimation l) => l.clips) where c != null && !string.IsNullOrEmpty(c.name) && !mainSet.Contains(c) && seen.Add(c) select c; } private static string GetDominantCollection(tk2dSpriteAnimation lib) { if (_dominantCollVersion != SpriteLoader.FileIndexVersion) { _libDominantColl.Clear(); _dominantCollVersion = SpriteLoader.FileIndexVersion; } int instanceID = ((Object)lib).GetInstanceID(); if (_libDominantColl.TryGetValue(instanceID, out var value)) { return value; } Dictionary dictionary = new Dictionary(); if (lib.clips != null) { tk2dSpriteAnimationClip[] clips = lib.clips; foreach (tk2dSpriteAnimationClip val in clips) { if (val == null || val.frames == null) { continue; } tk2dSpriteAnimationFrame[] frames = val.frames; for (int j = 0; j < frames.Length; j++) { tk2dSpriteCollectionData spriteCollection = frames[j].spriteCollection; if (!((Object)(object)spriteCollection == (Object)null)) { dictionary[((Object)spriteCollection).name] = ((!dictionary.TryGetValue(((Object)spriteCollection).name, out var value2)) ? 1 : (value2 + 1)); } } } } string text = null; int num = 0; foreach (KeyValuePair item in dictionary) { if (item.Value > num) { num = item.Value; text = item.Key; } } _libDominantColl[instanceID] = text; return text; } private static string PrimaryCollectionName(tk2dSpriteAnimationClip clip) { if (clip.frames == null) { return null; } tk2dSpriteAnimationFrame[] frames = clip.frames; for (int i = 0; i < frames.Length; i++) { tk2dSpriteCollectionData spriteCollection = frames[i].spriteCollection; if ((Object)(object)spriteCollection != (Object)null) { return ((Object)spriteCollection).name; } } return null; } private static GUIHelper.DotState DotStateFor(int n, int total) { if (n > 0) { if (n < total) { return GUIHelper.DotState.Partial; } return GUIHelper.DotState.Full; } return GUIHelper.DotState.None; } private static ClipCoverage GetCoverage(tk2dSpriteAnimation lib, tk2dSpriteAnimationClip clip) { if (_coverageIndexVersion != SpriteLoader.FileIndexVersion) { _coverage.Clear(); _coverageIndexVersion = SpriteLoader.FileIndexVersion; } (int, string, string) key = (((Object)lib).GetInstanceID(), clip.name, PrimaryCollectionName(clip)); if (_coverage.TryGetValue(key, out var value)) { return value; } value = new ClipCoverage(); if (clip.frames != null) { tk2dSpriteAnimationFrame[] frames = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames) { tk2dSpriteCollectionData spriteCollection = val.spriteCollection; if ((Object)(object)spriteCollection == (Object)null || spriteCollection.spriteDefinitions == null || val.spriteId < 0 || val.spriteId >= spriteCollection.spriteDefinitions.Length) { continue; } tk2dSpriteDefinition val2 = spriteCollection.spriteDefinitions[val.spriteId]; if (!string.IsNullOrEmpty(val2.name)) { value.Frames++; SpriteLoader.ReplacementSource spriteProvenance = SpriteLoader.GetSpriteProvenance(spriteCollection, val2); if (spriteProvenance != SpriteLoader.ReplacementSource.None) { value.Shared++; } else if (value.MissingShared.Count < 4) { string text = (((Object)(object)val2.material != (Object)null) ? ((Object)val2.material).name.Split(' ')[0] : "?"); value.MissingShared.Add(val2.name + " (" + ((Object)spriteCollection).name + "/" + text + ")"); } if (spriteProvenance == SpriteLoader.ReplacementSource.Pack) { value.SharedFromPack++; } if (AppliqueLoader.HasClipOverride(((Object)spriteCollection).name, clip.name, val2.name)) { value.PerClip++; } } } } _coverage[key] = value; return value; } private static bool TryResolveDisplay(AnimEntry e, out tk2dSpriteAnimationClip clip, out tk2dSpriteCollectionData coll, out int spriteId, out int frame, out int frameCount) { clip = null; coll = null; spriteId = -1; frame = 0; frameCount = 0; if (e == null || e.ClipName == null) { return false; } if (_previewPlaying && _previewClip != null && _previewClip.frames != null && _previewClip.frames.Length != 0) { int num = _previewClip.frames.Length; int num2 = (_previewFrame % num + num) % num; tk2dSpriteAnimationFrame val = _previewClip.frames[num2]; clip = _previewClip; coll = val.spriteCollection; spriteId = val.spriteId; frame = num2; frameCount = num; return true; } if ((e.Stale || (Object)(object)e.Live == (Object)null) && e.ViewClip != null && e.ViewClip.frames != null && e.ViewClip.frames.Length != 0) { int num3 = Mathf.Clamp(e.ViewFrame, 0, e.ViewClip.frames.Length - 1); tk2dSpriteAnimationFrame val2 = e.ViewClip.frames[num3]; clip = e.ViewClip; coll = val2.spriteCollection; spriteId = val2.spriteId; frame = num3; frameCount = e.ViewClip.frames.Length; } else { clip = e.Clip; coll = e.FrameColl; spriteId = e.SpriteId; frame = e.Frame; frameCount = e.FrameCount; } return true; } private static void DrawPreviewColumn(AnimEntry e, float colW) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Invalid comparison between Unknown and I4 //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_0579: Unknown result type (might be due to invalid IL or missing references) //IL_059d: Unknown result type (might be due to invalid IL or missing references) //IL_05e2: Unknown result type (might be due to invalid IL or missing references) //IL_05db: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Unknown result type (might be due to invalid IL or missing references) //IL_03c1: Unknown result type (might be due to invalid IL or missing references) //IL_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_067a: Unknown result type (might be due to invalid IL or missing references) //IL_06af: Unknown result type (might be due to invalid IL or missing references) //IL_06e9: Unknown result type (might be due to invalid IL or missing references) //IL_06e2: Unknown result type (might be due to invalid IL or missing references) //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_0539: Unknown result type (might be due to invalid IL or missing references) //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_056f: Unknown result type (might be due to invalid IL or missing references) //IL_0724: Unknown result type (might be due to invalid IL or missing references) //IL_074f: Unknown result type (might be due to invalid IL or missing references) //IL_076e: Unknown result type (might be due to invalid IL or missing references) //IL_07a8: Unknown result type (might be due to invalid IL or missing references) //IL_07a1: Unknown result type (might be due to invalid IL or missing references) //IL_07c7: Unknown result type (might be due to invalid IL or missing references) //IL_07f2: Unknown result type (might be due to invalid IL or missing references) //IL_0818: Unknown result type (might be due to invalid IL or missing references) //IL_086c: Unknown result type (might be due to invalid IL or missing references) if (e == null || !TryResolveDisplay(e, out var clip, out var coll, out var spriteId, out var frame, out var _)) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Select an object to preview."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } if ((Object)(object)coll == (Object)null || coll.spriteDefinitions == null || spriteId < 0 || spriteId >= coll.spriteDefinitions.Length) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Preview unavailable — the animator's atlas was unloaded."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } tk2dSpriteDefinition val = coll.spriteDefinitions[spriteId]; Material val2 = (((Object)(object)val.materialInst != (Object)null) ? val.materialInst : val.material); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.mainTexture == (Object)null) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Preview unavailable — material/texture released."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } Texture mainTexture = val2.mainTexture; Vector2[] uvs = val.uvs; bool flag = uvs != null && uvs.Length >= 4; float num = 0f; float num2 = 1f; float num3 = 0f; float num4 = 1f; if (flag) { num = uvs.Min((Vector2 v) => v.x); num2 = uvs.Max((Vector2 v) => v.x); num3 = uvs.Min((Vector2 v) => v.y); num4 = uvs.Max((Vector2 v) => v.y); } float num5 = colW - GUIHelper.Scaled(8f); Rect rect = GUILayoutUtility.GetRect(num5, num5, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num5), GUILayout.Height(num5) }); GUIHelper.DrawPreviewBackdrop(rect); GUI.DrawTexture(rect, mainTexture, (ScaleMode)2, true); if (flag) { Rect val3 = ScaleToFitRect(rect, mainTexture.width, mainTexture.height); float num6 = ((Rect)(ref val3)).x + num * ((Rect)(ref val3)).width; float num7 = ((Rect)(ref val3)).y + (1f - num4) * ((Rect)(ref val3)).height; float num8 = (num2 - num) * ((Rect)(ref val3)).width; float num9 = (num4 - num3) * ((Rect)(ref val3)).height; EnsurePreviewWhite(); GUI.color = new Color(1f, 1f, 0f, 0.4f); GUI.DrawTexture(new Rect(num6, num7, num8, num9), (Texture)(object)_previewWhite); GUI.color = Color.white; } GUI.contentColor = ColStale; GUILayout.Label(Loc.T("Atlas"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; Rect rect2 = GUILayoutUtility.GetRect(num5, num5, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(num5), GUILayout.Height(num5) }); GUIHelper.DrawPreviewBackdrop(rect2); if ((int)Event.current.type == 7 && flag) { Texture2D uprightSpriteTex = GetUprightSpriteTex(coll, spriteId, val, mainTexture); Rect val4; if ((Object)(object)uprightSpriteTex != (Object)null) { val4 = ScaleToFitRect(rect2, ((Texture)uprightSpriteTex).width, ((Texture)uprightSpriteTex).height); GUI.DrawTexture(val4, (Texture)(object)uprightSpriteTex); } else { int texW = Mathf.Max(1, Mathf.RoundToInt((num2 - num) * (float)mainTexture.width)); int texH = Mathf.Max(1, Mathf.RoundToInt((num4 - num3) * (float)mainTexture.height)); val4 = ScaleToFitRect(rect2, texW, texH); GUI.DrawTextureWithTexCoords(val4, mainTexture, new Rect(num, num3, num2 - num, num4 - num3), true); } Vector3[] positions = val.positions; if (positions != null && positions.Length >= 4) { float num10 = positions.Min((Vector3 p) => p.x); float num11 = positions.Max((Vector3 p) => p.x); float num12 = positions.Min((Vector3 p) => p.y); float num13 = positions.Max((Vector3 p) => p.y); float num14 = num11 - num10; float num15 = num13 - num12; if (num14 > 1E-06f && num15 > 1E-06f) { float num16 = (0f - num10) / num14; float num17 = (0f - num12) / num15; float num18 = ((Rect)(ref val4)).x + num16 * ((Rect)(ref val4)).width; float num19 = ((Rect)(ref val4)).y + (1f - num17) * ((Rect)(ref val4)).height; EnsurePreviewWhite(); GUI.color = new Color(1f, 0.15f, 0.8f, 0.95f); GUI.DrawTexture(new Rect(num18 - 6f, num19 - 0.5f, 13f, 1f), (Texture)(object)_previewWhite); GUI.DrawTexture(new Rect(num18 - 0.5f, num19 - 6f, 1f, 13f), (Texture)(object)_previewWhite); GUI.color = Color.white; } } } GUI.contentColor = ColStale; GUILayout.Label(Loc.T("Sprite ⊹ = pivot"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; GUIHelper.DrawBackdropSliderRow(); GUIHelper.Space(4f); GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUI.contentColor = (_previewPlaying ? GUIHelper.ColConfirm : Color.white); if (GUILayout.Button(_previewPlaying ? ("▮▮ " + Loc.T("Preview")) : ("▶ " + Loc.T("Preview")), GUIHelper.ButtonStyle, Array.Empty())) { if (_previewPlaying) { StopPreview(); } else if (clip != null && clip.frames != null && clip.frames.Length != 0) { _previewClip = clip; _previewFrame = frame; _previewClock = 0f; _previewFps = ResolvePreviewFps(e, clip); _previewPlaying = true; } } GUI.contentColor = Color.white; float num20 = GUIHelper.Scaled(38f); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(TransportLabel("|◀", Loc.T("Prev"), Plugin.Config.AnimationControllerPrevFrameKey), TransportStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num20) })) { StepFrame(-1); } GUI.contentColor = (Paused ? GUIHelper.ColWarn : Color.white); if (GUILayout.Button(TransportLabel(Paused ? "▶" : "▮▮", Loc.T(Paused ? "Play" : "Pause"), Plugin.Config.AnimationControllerPauseKey), TransportStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num20) })) { TogglePause(); } GUI.contentColor = Color.white; if (GUILayout.Button(TransportLabel("▶|", Loc.T("Next"), Plugin.Config.AnimationControllerNextFrameKey), TransportStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num20) })) { StepFrame(1); } GUI.contentColor = (Frozen ? Color.cyan : Color.white); if (GUILayout.Button(TransportLabel("❄", Loc.T("Freeze"), Plugin.Config.AnimationControllerFreezeKey), TransportStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(num20) })) { ToggleFreeze(); } GUI.contentColor = Color.white; GUILayout.EndHorizontal(); bool num21 = e.Stale || (Object)(object)e.Live == (Object)null; GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(num21 ? Loc.T("Stopped — stepping browses the snapshot.") : ((!Paused) ? Loc.T("Pause to step frames.") : " "), HintStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(GUIHelper.Scaled(30f)) }); GUI.contentColor = Color.white; GUILayout.EndVertical(); } private static string TransportLabel(string symbol, string word, KeyCode key) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) return symbol + " " + word + "\n[" + KeyName(key) + "]"; } internal static void OpenSpriteForEdit(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def) { if ((Object)(object)coll == (Object)null || def == null || string.IsNullOrEmpty(def.name)) { return; } Material val = (((Object)(object)def.materialInst != (Object)null) ? def.materialInst : def.material); if ((Object)(object)val == (Object)null) { return; } string text = ((Object)val).name.Split(' ')[0]; string text2 = SpriteLoader.FindWorkspaceSpriteFile(coll, def) ?? Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text, def.name + ".png"); if (!File.Exists(text2)) { SpriteDumper.DumpSingleSprite(def, coll); string text3 = Path.Combine(SpriteDumper.DumpPath, ((Object)coll).name, text, def.name + ".png"); if (!File.Exists(text3)) { Plugin.Logger.LogError((object)("Failed to dump sprite for editing: " + ((Object)coll).name + "/" + text + "/" + def.name)); return; } IOUtil.EnsureDirectoryExists(Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text)); File.Copy(text3, text2, overwrite: true); } IOUtil.OpenPath(text2); _anchorCacheKey = null; } internal static Texture2D GetUprightSpriteTexShared(tk2dSpriteCollectionData coll, int spriteId) { if ((Object)(object)coll == (Object)null || coll.spriteDefinitions == null || spriteId < 0 || spriteId >= coll.spriteDefinitions.Length) { return null; } tk2dSpriteDefinition val = coll.spriteDefinitions[spriteId]; Material val2 = (((Object)(object)val.materialInst != (Object)null) ? val.materialInst : val.material); Texture val3 = (((Object)(object)val2 != (Object)null) ? val2.mainTexture : null); if ((Object)(object)val3 == (Object)null) { return null; } return GetUprightSpriteTex(coll, spriteId, val, val3); } private static Texture2D GetUprightSpriteTex(tk2dSpriteCollectionData coll, int spriteId, tk2dSpriteDefinition def, Texture atlas) { int instanceID = ((Object)coll).GetInstanceID(); int fileIndexVersion = SpriteLoader.FileIndexVersion; if ((Object)(object)_spritePreviewTex != (Object)null && _spvCollId == instanceID && _spvSpriteId == spriteId && _spvFileVer == fileIndexVersion) { return _spritePreviewTex; } Texture2D val = RenderUprightNew(def, atlas, 2048); if ((Object)(object)val == (Object)null) { return null; } if ((Object)(object)_spritePreviewTex != (Object)null) { Object.Destroy((Object)(object)_spritePreviewTex); } _spritePreviewTex = val; _spvCollId = instanceID; _spvSpriteId = spriteId; _spvFileVer = fileIndexVersion; return _spritePreviewTex; } private static Texture2D RenderUprightNew(tk2dSpriteDefinition def, Texture atlas, int maxSize) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Invalid comparison between Unknown and I4 //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Expected O, but got Unknown //IL_031f: Unknown result type (might be due to invalid IL or missing references) if (!TexUtil.RotateDrawAvailable) { return null; } if ((Object)(object)TexUtil.RotateMaterial == (Object)null || (Object)(object)atlas == (Object)null) { return null; } Vector2[] uvs = def.uvs; Vector3[] positions = def.positions; if (uvs == null || uvs.Length < 4 || positions == null || positions.Length < 4) { return null; } float num = uvs[0].x; float num2 = num; float num3 = uvs[0].y; float num4 = num3; for (int i = 1; i < 4; i++) { num = Mathf.Min(num, uvs[i].x); num2 = Mathf.Max(num2, uvs[i].x); num3 = Mathf.Min(num3, uvs[i].y); num4 = Mathf.Max(num4, uvs[i].y); } int num5 = Mathf.RoundToInt((num2 - num) * (float)atlas.width); int num6 = Mathf.RoundToInt((num4 - num3) * (float)atlas.height); bool num7 = (int)def.flipped > 0; int num8 = Mathf.Clamp(num7 ? num6 : num5, 1, 2048); int num9 = Mathf.Clamp(num7 ? num5 : num6, 1, 2048); if (Mathf.Max(num8, num9) > maxSize) { float num10 = (float)maxSize / (float)Mathf.Max(num8, num9); num8 = Mathf.Max(1, Mathf.RoundToInt((float)num8 * num10)); num9 = Mathf.Max(1, Mathf.RoundToInt((float)num9 * num10)); } float num11 = positions[0].x; float num12 = num11; float num13 = positions[0].y; float num14 = num13; for (int j = 1; j < 4; j++) { num11 = Mathf.Min(num11, positions[j].x); num12 = Mathf.Max(num12, positions[j].x); num13 = Mathf.Min(num13, positions[j].y); num14 = Mathf.Max(num14, positions[j].y); } float num15 = num12 - num11; float num16 = num14 - num13; if (num15 < 1E-06f || num16 < 1E-06f) { return null; } float num17 = (float)num8 / num15; float num18 = (float)num9 / num16; RenderTexture active = RenderTexture.active; RenderTexture temporary = RenderTexture.GetTemporary(num8, num9, 0, (RenderTextureFormat)0, TexUtil.ReadWriteForSource(atlas)); Texture2D val; try { RenderTexture.active = temporary; GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)num8, 0f, (float)num9); TexUtil.SetPassTextured(atlas); GL.Begin(7); GL.Color(Color.white); int[] array = new int[4] { 0, 1, 3, 2 }; foreach (int num19 in array) { GL.TexCoord(Vector2.op_Implicit(uvs[num19])); GL.Vertex3((positions[num19].x - num11) * num17, (positions[num19].y - num13) * num18, 0f); } GL.End(); GL.PopMatrix(); val = new Texture2D(num8, num9, (TextureFormat)4, false) { hideFlags = (HideFlags)32 }; val.ReadPixels(new Rect(0f, 0f, (float)num8, (float)num9), 0, 0); val.Apply(); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } FlipPreviewHorizontal(val, num8, num9); return val; } internal static void ClearThumbCache() { foreach (Texture2D value in _thumbCache.Values) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } } _thumbCache.Clear(); } private static Texture2D GetUprightThumb(tk2dSpriteCollectionData coll, int spriteId, tk2dSpriteDefinition def, Texture atlas) { int fileIndexVersion = SpriteLoader.FileIndexVersion; if (fileIndexVersion != _thumbFileVer) { ClearThumbCache(); _thumbFileVer = fileIndexVersion; } (int, int) key = (((Object)coll).GetInstanceID(), spriteId); if (_thumbCache.TryGetValue(key, out var value) && (Object)(object)value != (Object)null) { return value; } if (_thumbCache.Count >= 96) { ClearThumbCache(); } Texture2D val = RenderUprightNew(def, atlas, 96); if ((Object)(object)val != (Object)null) { _thumbCache[key] = val; } return val; } private static void FlipPreviewHorizontal(Texture2D tex, int w, int h) { //IL_0028: 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) Color32[] pixels = tex.GetPixels32(); Color32[] array = (Color32[])(object)new Color32[pixels.Length]; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { array[i * w + j] = pixels[i * w + (w - 1 - j)]; } } tex.SetPixels32(array); tex.Apply(); } private static void DrawConsole(AnimEntry e) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Invalid comparison between Unknown and I4 //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_03d5: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_04b5: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Unknown result type (might be due to invalid IL or missing references) //IL_0559: Unknown result type (might be due to invalid IL or missing references) //IL_055f: Invalid comparison between Unknown and I4 //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_0774: Unknown result type (might be due to invalid IL or missing references) //IL_0779: Unknown result type (might be due to invalid IL or missing references) //IL_077b: Unknown result type (might be due to invalid IL or missing references) //IL_079f: Unknown result type (might be due to invalid IL or missing references) //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_097c: Unknown result type (might be due to invalid IL or missing references) //IL_09af: Unknown result type (might be due to invalid IL or missing references) //IL_0a8a: Unknown result type (might be due to invalid IL or missing references) //IL_0aae: Unknown result type (might be due to invalid IL or missing references) //IL_09d2: Unknown result type (might be due to invalid IL or missing references) //IL_0a04: Unknown result type (might be due to invalid IL or missing references) //IL_0a5a: Unknown result type (might be due to invalid IL or missing references) //IL_0a7e: Unknown result type (might be due to invalid IL or missing references) //IL_0aff: Unknown result type (might be due to invalid IL or missing references) //IL_0b23: Unknown result type (might be due to invalid IL or missing references) //IL_0e4d: Unknown result type (might be due to invalid IL or missing references) //IL_0e9e: Unknown result type (might be due to invalid IL or missing references) //IL_0fa7: Unknown result type (might be due to invalid IL or missing references) //IL_0fac: Unknown result type (might be due to invalid IL or missing references) if (e == null || !TryResolveDisplay(e, out var clip, out var coll, out var spriteId, out var frame, out var frameCount) || (Object)(object)coll == (Object)null || coll.spriteDefinitions == null || spriteId < 0 || spriteId >= coll.spriteDefinitions.Length) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Select an object to see its controls."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } tk2dSpriteDefinition val = coll.spriteDefinitions[spriteId]; Material val2 = (((Object)(object)val.materialInst != (Object)null) ? val.materialInst : val.material); if ((Object)(object)val2 == (Object)null) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Preview unavailable — material/texture released."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; return; } string text = ((Object)val2).name.Split(' ')[0]; bool flag = e.Stale || (Object)(object)e.Live == (Object)null; bool flag2 = !flag && (Object)(object)e.Live != (Object)null && (Object)(object)((Component)e.Live).gameObject != (Object)null; string text2 = ((Object)coll).GetInstanceID() + "/" + spriteId; if (_reuseCacheKey != text2 && (int)Event.current.type == 8) { _reuseCacheKey = text2; RecomputeReuse(e, coll, spriteId); } GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinHeight(GUIHelper.Scaled(28f)) }); string text3 = ((clip != null) ? clip.name : (e.ClipName ?? "?")); GUILayout.Label(string.Format("{0} · {1} {2}/{3}", text3, Loc.T("Frame"), frame + 1, frameCount) + (flag ? (" " + Loc.T("(stopped)")) : ""), GUIHelper.LabelStyle, Array.Empty()); if (Paused && flag2) { GUI.contentColor = GUIHelper.ColWarn; GUILayout.Label(Loc.T("[PAUSED]"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } if (Frozen && flag2) { GUI.contentColor = Color.cyan; GUILayout.Label(Loc.T("[FROZEN]"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } if (_previewPlaying) { GUI.contentColor = GUIHelper.ColConfirm; GUILayout.Label(Loc.T("[PREVIEW]"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } GUILayout.FlexibleSpace(); if (_reuseFrameCount > 1) { int num = 0; foreach (var reuseSource in _reuseSources) { if (reuseSource.Clip == clip) { num = reuseSource.Frames.Count; break; } } GUI.contentColor = GUIHelper.ColWarn; if (GUILayout.Button(string.Format("{0} ×{1} · ", Loc.T("Reused frames"), _reuseFrameCount) + string.Format(Loc.T("{0} clips@anim", "{0} clips"), _reuseClipCount) + ((num > 1) ? (" " + string.Format(Loc.T("({0} in this clip)"), num)) : ""), GUIHelper.ButtonStyle, Array.Empty())) { tk2dSpriteCollectionData lensColl = coll; int lensId = spriteId; string lensName = val.name; _pendingUiAction = delegate { SetLens(lensColl, lensId, lensName); }; } GUI.contentColor = Color.white; } else { GUI.contentColor = GUIHelper.ColConfirm; GUILayout.Label(Loc.T("Only used here — safe to edit in isolation."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } GUILayout.EndHorizontal(); string text4 = ""; if (clip != null && clip.fps > 0f) { text4 = " · " + string.Format(Loc.T("clip {0:F0} fps"), clip.fps); tk2dSpriteAnimator live = e.Live; if ((Object)(object)live != (Object)null && live.CurrentClip == clip && live.ClipFps > 0f && Mathf.Abs(live.ClipFps - clip.fps) > 0.05f) { text4 += string.Format(Loc.T(" → {0:F0} live"), live.ClipFps); } } GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label($"{((Object)coll).name} · {text} · {val.name} · id {spriteId}{text4}", GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; string text5 = ((Object)coll).name + "/" + text + "/" + val.name; if (_anchorCacheKey != text5 && (int)Event.current.type == 8) { _anchorCacheKey = text5; _anchorReplExists = false; _anchorCurrent = null; _anchorFilePath = null; _anchorPackSource = null; foreach (string pluginPackPath in Plugin.PluginPackPaths) { if (FindTaggedOrPlain(Path.Combine(pluginPackPath, "Sprites", ((Object)coll).name, text), val.name) != null) { string path = pluginPackPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string fileName = Path.GetFileName(path); if (string.Equals(fileName, "Stitchwork", StringComparison.OrdinalIgnoreCase) || string.Equals(fileName, "Patchwork", StringComparison.OrdinalIgnoreCase)) { fileName = Path.GetFileName(Path.GetDirectoryName(path)); } _anchorPackSource = fileName; break; } } if (_anchorPackSource == null) { string text6 = FindTaggedOrPlain(Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text), val.name); if (text6 != null) { _anchorReplExists = true; _anchorFilePath = text6; _anchorCurrent = IOUtil.ParseAnchorTag(Path.GetFileNameWithoutExtension(text6)).anchor; } } } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.Label(Loc.T("This Sprite"), GUIHelper.SectionHeaderStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(Loc.T(File.Exists(Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text, val.name + ".png")) ? "Edit (opens your file)" : "Edit (new from vanilla)"), GUIHelper.ButtonStyle, Array.Empty())) { OpenSpriteForEdit(coll, val); } if (GUILayout.Button(Loc.T("Folder"), GUIHelper.ButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(64f) })) { IOUtil.OpenDeepestExisting(Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text)); } GUILayout.EndHorizontal(); GUI.enabled = clip != null; Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = GUIHelper.ColAccent; bool num2 = GUILayout.Button(Loc.T("Detach for This Clip"), GUIHelper.ButtonStyle, Array.Empty()); GUI.backgroundColor = backgroundColor; if (num2) { if (clip.name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { Plugin.Logger.LogWarning((object)("[AnimCtrl] Detach: clip name '" + clip.name + "' isn't filesystem-safe.")); } else { string text7 = Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, "Clips", clip.name); string text8 = Path.Combine(text7, val.name + ".png"); if (!File.Exists(text8)) { string text9 = Path.Combine(SpriteLoader.LoadPath, ((Object)coll).name, text, val.name + ".png"); string text10 = (File.Exists(text9) ? text9 : null); if (text10 == null) { SpriteDumper.DumpSingleSprite(val, coll); string text11 = Path.Combine(SpriteDumper.DumpPath, ((Object)coll).name, text, val.name + ".png"); if (File.Exists(text11)) { text10 = text11; } } if (text10 == null) { Plugin.Logger.LogError((object)("[AnimCtrl] Detach: no source for " + ((Object)coll).name + "/" + text + "/" + val.name)); } else { IOUtil.EnsureDirectoryExists(text7); File.Copy(text10, text8, overwrite: true); Plugin.Logger.LogInfo((object)("[AnimCtrl] Detached '" + val.name + "' for clip '" + clip.name + "' → " + text8)); } } if (File.Exists(text8)) { IOUtil.OpenPath(text8); } } } GUI.enabled = true; GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Height(GUIHelper.Scaled(48f)), GUILayout.ExpandWidth(true) }); if (_anchorPackSource != null) { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Supplied by pack:") + " " + _anchorPackSource, GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } else if (_anchorReplExists) { GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("Anchor:"), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(52f) }); GUI.contentColor = Color.white; DrawAnchorButton("↑", "top-center"); DrawAnchorButton("↓", null); DrawAnchorButton("←", "left-center"); DrawAnchorButton("→", "right-center"); DrawAnchorButton("◆", "center"); GUILayout.EndHorizontal(); GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Anchor affects enlarged replacements only."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } else { GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("No replacement file yet — Edit creates one."), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } GUILayout.FlexibleSpace(); GUILayout.EndVertical(); GUILayout.EndVertical(); GUILayout.BeginVertical(GUI.skin.box, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.Label(Loc.T("Whole Clip"), GUIHelper.SectionHeaderStyle, Array.Empty()); GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label(Loc.T("Animation canvas — whole-clip workflow:"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; GUI.enabled = clip != null && !AnimCanvasBatch.IsRunning; if (GUILayout.Button(Loc.T("Ready All for Edit"), GUIHelper.ButtonStyle, Array.Empty())) { int num3 = 0; for (int num4 = 0; num4 < clip.frames.Length; num4++) { tk2dSpriteAnimationFrame val3 = clip.frames[num4]; tk2dSpriteCollectionData spriteCollection = val3.spriteCollection; if ((Object)(object)spriteCollection == (Object)null || val3.spriteId < 0 || val3.spriteId >= spriteCollection.spriteDefinitions.Length) { continue; } tk2dSpriteDefinition val4 = spriteCollection.spriteDefinitions[val3.spriteId]; if (string.IsNullOrEmpty(val4.name)) { continue; } string text12 = ((Object)(((Object)(object)val4.materialInst != (Object)null) ? val4.materialInst : val4.material)).name.Split(' ')[0]; string text13 = Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection).name, text12, val4.name + ".png"); if (File.Exists(text13)) { num3++; continue; } SpriteDumper.DumpSingleSprite(val4, spriteCollection); string text14 = Path.Combine(SpriteDumper.DumpPath, ((Object)spriteCollection).name, text12, val4.name + ".png"); if (!File.Exists(text14)) { Plugin.Logger.LogError((object)("Failed to dump: " + ((Object)spriteCollection).name + "/" + text12 + "/" + val4.name)); } else { IOUtil.EnsureDirectoryExists(Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection).name, text12)); File.Copy(text14, text13, overwrite: true); num3++; } } Plugin.Logger.LogInfo((object)$"[AnimCtrl] Copied {num3} sprites from '{clip.name}' ({clip.frames.Length} frames) to {SpriteLoader.LoadPath}"); for (int num5 = 0; num5 < clip.frames.Length; num5++) { tk2dSpriteAnimationFrame val5 = clip.frames[num5]; tk2dSpriteCollectionData spriteCollection2 = val5.spriteCollection; if ((Object)(object)spriteCollection2 == (Object)null || val5.spriteId < 0 || val5.spriteId >= spriteCollection2.spriteDefinitions.Length) { continue; } tk2dSpriteDefinition val6 = spriteCollection2.spriteDefinitions[val5.spriteId]; if (!string.IsNullOrEmpty(val6.name)) { string path2 = ((Object)(((Object)(object)val6.materialInst != (Object)null) ? val6.materialInst : val6.material)).name.Split(' ')[0]; string path3 = Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection2).name, path2, val6.name + ".png"); if (File.Exists(path3)) { IOUtil.OpenPath(path3); } } } _anchorCacheKey = null; } string text15 = AnimCanvasAuthoring.WipDirFor(clip); if (text15 != null) { string fileName2 = Path.GetFileName(Path.GetDirectoryName(text15)); GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("Workspace") + ": " + fileName2 + "/" + Path.GetFileName(text15), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; if (GUILayout.Button(Loc.T("Open"), GUIHelper.ButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(52f) })) { IOUtil.OpenDeepestExisting(text15); } GUILayout.EndHorizontal(); } GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(Loc.T("Extract Vanilla"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasAuthoring.ExtractClip(clip); string text16 = AnimCanvasAuthoring.WipDirFor(clip); if (text16 != null && Directory.Exists(text16)) { IOUtil.OpenPath(text16); } } if (GUILayout.Button(Loc.T("Extract As Rendered"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasAuthoring.ExtractClip(clip, 0.5f, 0, fromCurrentRender: true); string text17 = AnimCanvasAuthoring.WipDirFor(clip); if (text17 != null && Directory.Exists(text17)) { IOUtil.OpenPath(text17); } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(Loc.T("Compile Animation from Workspace"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasCompile.CompileClip(clip); } Color backgroundColor2 = GUI.backgroundColor; GUI.backgroundColor = GUIHelper.ColAccent; if (GUILayout.Button(Loc.T("Compile for This Clip"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasCompile.CompileClip(clip, forClipOnly: true); } GUI.backgroundColor = backgroundColor2; GUILayout.EndHorizontal(); if (GUILayout.Button(Loc.T("Export GIF"), GUIHelper.ButtonStyle, Array.Empty())) { int width; int height; List list = AnimCanvasAuthoring.RenderClipSequence(clip, fromCurrentRender: true, 0.1f, out width, out height); if (list == null) { Plugin.Logger.LogWarning((object)("[Showcase] '" + clip.name + "': no renderable frames — GIF not written.")); } else { string text18 = Path.Combine(Plugin.BasePath, "Showcase"); IOUtil.EnsureDirectoryExists(text18); string text19 = Path.Combine(text18, T2DUtil.SanitizeForFilesystem(clip.name) + ".gif"); int num6 = Mathf.Max(2, Mathf.RoundToInt(100f / Mathf.Max(1f, _previewFps))); try { GifEncoder.Write(text19, width, height, list, num6); Plugin.Logger.LogInfo((object)$"[Showcase] GIF: {list.Count} frame(s) {width}×{height} @ {num6} cs/frame → {text19}"); IOUtil.OpenPath(text19); } catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException) { Plugin.Logger.LogError((object)("[Showcase] GIF write failed: " + ex.Message)); } } } GUI.enabled = true; GUILayout.EndVertical(); GUILayout.EndHorizontal(); } private unsafe static string KeyName(KeyCode k) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected I4, but got Unknown if ((int)k != 127) { return (k - 277) switch { 3 => "PgUp", 4 => "PgDn", 0 => "Ins", _ => ((object)(*(KeyCode*)(&k))/*cast due to .constrained prefix*/).ToString(), }; } return "Del"; } private static void EnsurePreviewWhite() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_previewWhite != (Object)null)) { _previewWhite = new Texture2D(1, 1) { hideFlags = (HideFlags)32 }; _previewWhite.SetPixel(0, 0, Color.white); _previewWhite.Apply(); } } private static Rect ScaleToFitRect(Rect container, int texW, int texH) { //IL_0008: 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) if (texW <= 0 || texH <= 0) { return container; } float num = Mathf.Min(((Rect)(ref container)).width / (float)texW, ((Rect)(ref container)).height / (float)texH); float num2 = (float)texW * num; float num3 = (float)texH * num; return new Rect(((Rect)(ref container)).x + (((Rect)(ref container)).width - num2) * 0.5f, ((Rect)(ref container)).y + (((Rect)(ref container)).height - num3) * 0.5f, num2, num3); } private static void RecomputeReuse(AnimEntry e, tk2dSpriteCollectionData coll, int spriteId) { _reuseFrameCount = 0; _reuseClipCount = 0; _reuseSources.Clear(); tk2dSpriteAnimation val = e?.Library; if ((Object)(object)val == (Object)null || val.clips == null || (Object)(object)coll == (Object)null) { return; } tk2dSpriteAnimationClip[] clips = val.clips; foreach (tk2dSpriteAnimationClip val2 in clips) { if (val2 == null || val2.frames == null) { continue; } List list = null; for (int j = 0; j < val2.frames.Length; j++) { tk2dSpriteAnimationFrame val3 = val2.frames[j]; if ((Object)(object)val3.spriteCollection == (Object)(object)coll && val3.spriteId == spriteId) { (list ?? (list = new List())).Add(j); } } if (list != null) { _reuseFrameCount += list.Count; _reuseClipCount++; _reuseSources.Add((val2, list)); } } } private static void DrawAnchorButton(string label, string anchor) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) GUI.contentColor = ((_anchorCurrent == anchor) ? GUIHelper.ColConfirm : Color.white); if (GUILayout.Button(label, GUIHelper.ButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(30f) })) { SetAnchorByRename(anchor); } GUI.contentColor = Color.white; } private static string FindTaggedOrPlain(string dir, string spriteName) { if (!Directory.Exists(dir)) { return null; } string text = Path.Combine(dir, spriteName + ".png"); if (File.Exists(text)) { return text; } string[] array = new string[5] { "@t", "@b", "@l", "@r", "@c" }; foreach (string text2 in array) { string text3 = Path.Combine(dir, spriteName + text2 + ".png"); if (File.Exists(text3)) { return text3; } } return null; } private static void SetAnchorByRename(string anchor) { if (_anchorFilePath == null) { return; } string directoryName = Path.GetDirectoryName(_anchorFilePath); string item = IOUtil.ParseAnchorTag(Path.GetFileNameWithoutExtension(_anchorFilePath)).name; string text = Path.Combine(directoryName, item + anchor switch { "top-center" => "@t", "left-center" => "@l", "right-center" => "@r", "center" => "@c", _ => "", } + ".png"); if (string.Equals(text, _anchorFilePath, StringComparison.OrdinalIgnoreCase)) { return; } if (File.Exists(text)) { Plugin.Logger.LogWarning((object)("[Anchor] Can't set anchor: '" + Path.GetFileName(text) + "' already exists - remove the duplicate first (multiple anchor-tagged files for one sprite).")); return; } try { File.Move(_anchorFilePath, text); _anchorFilePath = text; _anchorCurrent = anchor; _anchorCacheKey = null; Plugin.Logger.LogInfo((object)("[Anchor] " + Path.GetFileName(text) + " anchor set to " + (anchor ?? "bottom (default)"))); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Anchor] rename failed: " + ex.Message)); } } } public class DialogueHandler { private sealed class StoredCopyBridge { public Type Type; public MethodInfo Refresh; public FieldInfo KeyField; } private static string _cachedLangString; private static bool _gameLanguageAlive; public static Dictionary>> TextCache = new Dictionary>>(); private static readonly HashSet RequestedOverrideKeys = new HashSet(); private static readonly HashSet AllOverrideKeys = new HashSet(); private static bool _staleKeysReported; private static bool _dumpInProgress; private static FieldInfo _entrySheetsField; private static bool _entrySheetsResolved; private static object _lastDictInstance; private static readonly Dictionary<(string sheet, string key), (bool existed, string value)> _dictOriginals = new Dictionary<(string, string), (bool, string)>(); private static List _storedCopyBridges; public static string TextDumpPath => Path.Combine(Plugin.BasePath, "TextDumps"); public static string TextLoadPath => Path.Combine(Plugin.BasePath, "Text"); public static string CurrentLangCode => CurrentLangStringCached(); public static int StaleKeyCount { get; private set; } public static int CachedSheetCount { get { int num = 0; foreach (Dictionary> value in TextCache.Values) { num += value.Count; } return num; } } public static int CachedKeyCount { get { int num = 0; foreach (Dictionary> value in TextCache.Values) { foreach (Dictionary value2 in value.Values) { num += value2.Count; } } return num; } } public static event Action OnTextAccessed; private static string CurrentLangStringCached() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (_cachedLangString != null) { return _cachedLangString; } if (!_gameLanguageAlive) { return "EN"; } try { return _cachedLangString = ((object)Language.CurrentLanguage()/*cast due to .constrained prefix*/).ToString(); } catch (TypeInitializationException) { return "EN"; } } public static void DumpText() { string text = CurrentLangStringCached(); _dumpInProgress = true; try { foreach (object value in Enum.GetValues(typeof(SupportedLanguages))) { string text2 = value.ToString(); Plugin.Logger.LogDebug((object)("Dumping text for language code " + text2 + "...")); Language.SwitchLanguage(text2); int num = 0; int num2 = 0; foreach (string sheet in Language.GetSheets()) { IOUtil.EnsureDirectoryExists(Path.Combine(TextDumpPath, sheet)); using StreamWriter streamWriter = new StreamWriter(Path.Combine(TextDumpPath, sheet, text2 + ".yml")); foreach (string key in Language.GetKeys(sheet)) { string text3 = Language.Get(key, sheet); streamWriter.WriteLine(key + ": \"" + text3.Replace("\"", "\\\"") + "\""); num++; } streamWriter.Flush(); streamWriter.Close(); num2++; } Plugin.Logger.LogDebug((object)$"Finished dumping text for language code {text2}. Dumped {num} keys in {num2} sheets."); } Plugin.Logger.LogInfo((object)("[Stitchwork] Text dump complete (vanilla source) → " + TextDumpPath)); } finally { _dumpInProgress = false; Language.SwitchLanguage(text); } } public static void InvalidateCache(string sheet, string lang) { if (TextCache.TryGetValue(lang, out var value) && value.Remove(sheet)) { Plugin.Logger.LogDebug((object)("Invalidating text cache for sheet: " + sheet + ", lang: " + lang)); } } public static IEnumerable<(string Sheet, string Key, string Value)> SearchCache(string filter, HashSet skipKeys = null) { if (string.IsNullOrEmpty(filter) || !TextCache.TryGetValue(CurrentLangCode, out var value)) { yield break; } foreach (KeyValuePair> item in value) { string sheet = item.Key; bool sheetMatch = sheet.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; foreach (KeyValuePair item2 in item.Value) { if ((skipKeys == null || !skipKeys.Contains(sheet + "|" + item2.Key)) && (sheetMatch || item2.Key.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 || item2.Value.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)) { yield return (Sheet: sheet, Key: item2.Key, Value: item2.Value); } } } } public static void Reload() { ConflictTracker.ClearTypes("text"); TextCache.Clear(); RequestedOverrideKeys.Clear(); AllOverrideKeys.Clear(); StaleKeyCount = 0; _staleKeysReported = false; Language.SwitchLanguage(CurrentLangStringCached()); Plugin.Logger.LogDebug((object)"[Stitchwork] Text hot-reload: cleared cache and re-switched language to force refresh."); } public static void ApplyPatches(Harmony harmony) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(Language), "Get", new Type[2] { typeof(string), typeof(string) }, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(DialogueHandler), "GetTextPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); try { int num = 0; MethodInfo[] methods = typeof(Language).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo.Name != "SwitchLanguage") && !methodInfo.IsGenericMethod) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(DialogueHandler), "SwitchLanguagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[DialogueHandler] Failed to patch Language.SwitchLanguage overload " + $"({methodInfo}): {ex.Message}")); } } } if (num == 0) { Plugin.Logger.LogWarning((object)"[DialogueHandler] No Language.SwitchLanguage overloads were patched. Language-change cache invalidation will be skipped; stale CurrentLangCode is possible until next scene/reload."); } } catch (Exception ex2) { Plugin.Logger.LogError((object)("[DialogueHandler] SwitchLanguage enumeration threw: " + ex2.Message + ". Cache invalidation patches skipped; downstream patches continue.")); } } private static void SwitchLanguagePostfix() { _gameLanguageAlive = true; _cachedLangString = null; Loc.OnGameLanguageChanged(); SyncGameLanguageDict(); } private static void GetTextPostfix(string key, string sheetTitle, ref string __result) { if (!_gameLanguageAlive) { _gameLanguageAlive = true; Loc.OnGameLanguageChanged(); } if (_dumpInProgress) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { string text = CurrentLangStringCached(); if (GetOrLoadSheet(text, sheetTitle).TryGetValue(key, out var value)) { __result = value; RequestedOverrideKeys.Add(text + "|" + sheetTitle + "|" + key); } FontHandler.NoteRenderedText(__result); DialogueHandler.OnTextAccessed?.Invoke(sheetTitle, key, __result); } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Language.Get", heapBefore); } } private static Dictionary GetOrLoadSheet(string lang, string sheetTitle) { if (!TextCache.TryGetValue(lang, out var value)) { value = new Dictionary>(); TextCache[lang] = value; } if (!value.TryGetValue(sheetTitle, out var value2)) { value2 = (value[sheetTitle] = LoadTextSheet(sheetTitle, lang)); if (value2.Count > 0) { FontFileWatcher.ReloadFonts = true; } } return value2; } private static Dictionary LoadTextSheet(string sheetTitle, string lang) { Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { foreach (KeyValuePair item in LoadTextSheet(sheetTitle, lang, Path.Combine(pluginPackPath, "Text"))) { if (!dictionary.ContainsKey(item.Key)) { dictionary[item.Key] = item.Value; dictionary2[item.Key] = pluginPackPath; } else if (!string.Equals(dictionary[item.Key], item.Value, StringComparison.Ordinal)) { ConflictTracker.Record("text", sheetTitle + "/" + lang + "/" + item.Key, dictionary2.TryGetValue(item.Key, out var value) ? value : null, pluginPackPath); } } } foreach (KeyValuePair item2 in LoadTextSheet(sheetTitle, lang, TextLoadPath)) { if (dictionary.ContainsKey(item2.Key) && dictionary2.TryGetValue(item2.Key, out var value2) && !string.Equals(dictionary[item2.Key], item2.Value, StringComparison.Ordinal)) { ConflictTracker.Record("text", sheetTitle + "/" + lang + "/" + item2.Key, null, value2); } dictionary[item2.Key] = item2.Value; } foreach (string key in dictionary.Keys) { AllOverrideKeys.Add(lang + "|" + sheetTitle + "|" + key); } return dictionary; } public static void CheckForStaleKeys() { if (_staleKeysReported) { return; } int num = 0; string text = CurrentLangStringCached(); string value = text + "|"; foreach (string allOverrideKey in AllOverrideKeys) { if (!RequestedOverrideKeys.Contains(allOverrideKey) && allOverrideKey.StartsWith(value, StringComparison.Ordinal)) { string[] array = allOverrideKey.Split('|', 3); string text2 = array[1]; string text3 = array[2]; num++; Plugin.Logger.LogWarning((object)("[Stitchwork] Text override key \"" + text3 + "\" in sheet \"" + text2 + "\" (" + text + ") was never requested by the game — key may have been renamed by a game update. Consider re-dumping text with DumpText enabled.")); } } StaleKeyCount = num; _staleKeysReported = true; if (num > 0) { Plugin.Logger.LogWarning((object)$"[Stitchwork] {num} stale text override key(s) detected. These overrides are not being applied."); } } private static Dictionary LoadTextSheet(string sheetTitle, string lang, string basePath) { return ParseSheetFile(Path.Combine(basePath, sheetTitle, lang + ".yml")); } internal static Dictionary ParseSheetFile(string filePath) { Dictionary dictionary = new Dictionary(); if (!File.Exists(filePath)) { return dictionary; } string[] array = File.ReadAllLines(filePath); foreach (string text in array) { if (!text.StartsWith("#") && !string.IsNullOrWhiteSpace(text)) { int num = text.IndexOf(':'); if (num != -1) { string key = text.Substring(0, num).Trim(); string value = text.Substring(num + 1).Trim().Trim('"') .Replace("\\\"", "\""); dictionary[key] = value; } } } return dictionary; } private static void SyncGameLanguageDict() { if (!GameBridge.IsHK1) { return; } if (!_entrySheetsResolved) { _entrySheetsResolved = true; _entrySheetsField = AccessTools.Field(typeof(Language), "_currentEntrySheets"); if (_entrySheetsField == null) { Plugin.Logger.LogWarning((object)"[DialogueHandler] HK1 dict sync: Language._currentEntrySheets not found — push-model text disabled; stored-string consumers may show vanilla."); } } if (_entrySheetsField == null || !(_entrySheetsField.GetValue(null) is Dictionary> dictionary)) { return; } if (dictionary != _lastDictInstance) { _dictOriginals.Clear(); _lastDictInstance = dictionary; } string text = CurrentLangStringCached(); HashSet<(string sheet, string key)> desired = new HashSet<(string, string)>(); int num = 0; foreach (string item in dictionary.Keys.ToList()) { Dictionary orLoadSheet = GetOrLoadSheet(text, item); if (orLoadSheet.Count == 0) { continue; } Dictionary dictionary2 = dictionary[item]; foreach (KeyValuePair item2 in orLoadSheet) { (string, string) tuple = (item, item2.Key); desired.Add(tuple); if (!_dictOriginals.ContainsKey(tuple)) { _dictOriginals[tuple] = (dictionary2.TryGetValue(item2.Key, out var value), value); } dictionary2[item2.Key] = item2.Value; num++; } } foreach (var item3 in _dictOriginals.Keys.Where(((string sheet, string key) a) => !desired.Contains(a)).ToList()) { if (dictionary.TryGetValue(item3.sheet, out var value2) && _dictOriginals.TryGetValue(item3, out (bool, string) value3)) { if (value3.Item1) { value2[item3.key] = value3.Item2; } else { value2.Remove(item3.key); } } _dictOriginals.Remove(item3); } if (num > 0) { Plugin.Logger.LogDebug((object)$"[DialogueHandler] HK1 dict sync: {num} override(s) written into the game's language dict ({text})."); } RefreshStoredTextCopies(); } private static void RefreshStoredTextCopies() { if (_storedCopyBridges == null) { _storedCopyBridges = new List(); Resolve("SetTextMeshProGameText", "UpdateText", null); Resolve("TeamCherry.Localization.LocalizedTextMesh", "LocalizeTextMesh", "keyValue"); } foreach (StoredCopyBridge storedCopyBridge in _storedCopyBridges) { int num = 0; Object[] array = Resources.FindObjectsOfTypeAll(storedCopyBridge.Type); foreach (Object val in array) { if (val == (Object)null) { continue; } if (storedCopyBridge.KeyField != null) { if (!(storedCopyBridge.KeyField.GetValue(val) is string { Length: not 0 } text)) { continue; } storedCopyBridge.Refresh.Invoke(val, new object[1] { text }); } else { storedCopyBridge.Refresh.Invoke(val, null); } num++; } if (num > 0) { Plugin.Logger.LogDebug((object)$"[DialogueHandler] HK1 dict sync: refreshed {num} {storedCopyBridge.Type.Name} instance(s)."); } } static void Resolve(string typeName, string methodName, string keyFieldName) { Type type = AccessTools.TypeByName(typeName); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : null); FieldInfo fieldInfo = ((keyFieldName != null && type != null) ? AccessTools.Field(type, keyFieldName) : null); if (methodInfo == null || (keyFieldName != null && fieldInfo == null)) { Plugin.Logger.LogWarning((object)("[DialogueHandler] HK1 dict sync: " + typeName + " bridge unresolved — its stored-copy refresh is disabled.")); } else { _storedCopyBridges.Add(new StoredCopyBridge { Type = type, Refresh = methodInfo, KeyField = fieldInfo }); } } } } namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } } namespace Stitchwork { [BepInPlugin("Stitchwork", "Stitchwork", "0.8.6")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; internal static StitchworkConfig Config; internal static SpriteFileWatcher SpriteFileWatcher; internal static AudioFileWatcher AudioFileWatcher; internal static TextFileWatcher TextFileWatcher; internal static FontFileWatcher FontFileWatcher; private bool _isInert; private static bool _inertToastDismissed; public static bool ShowPackManager = false; public static bool ShowDevHub = false; private const float MenuSceneDeferDelay = 1f; private static readonly string[] MenuScenePatterns = new string[3] { "menu", "title", "mode_select" }; private const float DeferredReapplyDelay = 0.1f; private static int _conditionPollFrames = 0; private const int ConditionPollInterval = 120; private int _focusRegainReloadFrame = -1; private const int FocusRegainReloadDelayFrames = 3; private static GUIStyle _inertTitleStyle; private static GUIStyle _inertBodyStyle; private static GUIStyle _inertBoxStyle; private static string _noticeTitle; private static string _noticeBody; internal static Harmony HarmonyInstance { get; private set; } public static Plugin Instance { get; private set; } public static string BasePath => Path.GetDirectoryName(typeof(Plugin).Assembly.Location); public static bool CustomizerPluginPresent { get; private set; } public static IEnumerable PluginPackPaths => PackManager.ActivePackPaths; public static bool ShowStatusOverlay { get { return Config?.ShowStatusOverlay ?? true; } set { if (Config != null) { Config.ShowStatusOverlay = value; } } } public static int DevHubTab { get { return Config?.DevHubTab ?? 0; } set { if (Config != null) { Config.DevHubTab = value; } } } private void Awake() { //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Expected O, but got Unknown Instance = this; Logger = ((BaseUnityPlugin)this).Logger; if (Chainloader.PluginInfos.ContainsKey("Patchwork")) { Logger.LogWarning((object)"════════════════════════════════════════════════════════"); Logger.LogWarning((object)"[Stitchwork] Upstream Patchwork detected."); Logger.LogWarning((object)""); Logger.LogWarning((object)" Stitchwork is a fork that supersedes Patchwork. Running"); Logger.LogWarning((object)" both at the same time will produce duplicate Harmony"); Logger.LogWarning((object)" patches on sprite/texture setters and unpredictable"); Logger.LogWarning((object)" asset-swap behaviour."); Logger.LogWarning((object)""); Logger.LogWarning((object)" Stitchwork is INERT this session."); Logger.LogWarning((object)" Uninstall Patchwork.dll from BepInEx/plugins/ to enable"); Logger.LogWarning((object)" Stitchwork."); Logger.LogWarning((object)"════════════════════════════════════════════════════════"); _isInert = true; return; } Config = new StitchworkConfig(((BaseUnityPlugin)this).Config); if (Config.DiagnosticMode) { Logger.LogWarning((object)"════════════════════════════════════════════════════════"); Logger.LogWarning((object)"[Stitchwork] DiagnosticMode ACTIVE"); Logger.LogWarning((object)" - No Harmony patches applied"); Logger.LogWarning((object)" - No packs loaded"); Logger.LogWarning((object)" - No replacements will work"); Logger.LogWarning((object)" - DevProfiler [Mem] logging continues"); Logger.LogWarning((object)" - Use this for vanilla allocation-rate measurement only"); Logger.LogWarning((object)" - Toggle DiagnosticMode=false in BepInEx config to restore"); Logger.LogWarning((object)"════════════════════════════════════════════════════════"); DevProfiler.Initialize(); GCUtil.InitBridge(); return; } Logger.LogInfo((object)"Stitchwork is loaded! Version: 0.8.6"); LogInstallRoot(); Loc.SelfExtractEmbeddedTables(); Loc.Init(); int num = ((Config.HeapReserveMB < 0) ? GCUtil.SuggestReserveMB() : Config.HeapReserveMB); if (num > 0) { Logger.LogInfo((object)$"[GC] Pre-warming Mono heap: {num} MB (system RAM: {SystemInfo.systemMemorySize} MB)"); GCUtil.PrewarmHeap((long)num * 1024L * 1024); } switch (BootConfigGC.Sync(Config.AutoEnableIncrementalGC)) { case BootConfigGC.Result.KeyWritten: QueueNoticeToast(Loc.T("#toast.gc-enable.title", "Stitchwork — one restart to smoother performance"), Loc.T("#toast.gc-enable.body", "Incremental garbage collection has been enabled for Silksong (one line in boot.config; the original is backed up). This removes the game's periodic GC stutter.\n\nIt takes effect on the NEXT game launch — restart when convenient.\nOpt out anytime: AutoEnableIncrementalGC = false in Stitchwork's config.")); break; case BootConfigGC.Result.KeyRemoved: QueueNoticeToast(Loc.T("#toast.gc-revert.title", "Stitchwork — restart to finish reverting"), Loc.T("#toast.gc-revert.body", "AutoEnableIncrementalGC is off — the incremental-GC line was removed from boot.config. The game's default GC behaviour returns on the next launch.")); break; } foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string key = pluginInfo.Key; PluginInfo value = pluginInfo.Value; object obj; if (value == null) { obj = null; } else { BepInPlugin metadata = value.Metadata; obj = ((metadata != null) ? metadata.Name : null); } if (obj == null) { obj = key; } string text = (string)obj; if (key.IndexOf("Customizer", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Customizer", StringComparison.OrdinalIgnoreCase) >= 0) { CustomizerPluginPresent = true; Logger.LogError((object)("[Stitchwork] Conflicting plugin detected: '" + text + "' (GUID: " + key + "). Both mods patch sprite hooks and will conflict, causing broken sprite loading and enemy AI issues. Remove this plugin — Stitchwork replaces Customizer's functionality.")); } } PackManager.Initialize(); TexUtil.Initialize(); InitializeFolders(); AudioFileWatcher = new AudioFileWatcher(); SpriteFileWatcher = new SpriteFileWatcher(); TextFileWatcher = new TextFileWatcher(); FontFileWatcher = new FontFileWatcher(); FontFileWatcher.ReloadFonts = true; DevProfiler.Initialize(); T2DLoader.PreloadAllTextures(); FileCache.BeginCycle(); try { VideoHandler.Reload(); } finally { FileCache.EndCycle(); } FileCache.BeginCycle(); try { AudioHandler.Reload(); } finally { FileCache.EndCycle(); } SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; Harmony val = (HarmonyInstance = new Harmony("Stitchwork")); val.PatchAll(); AudioHandler.ApplyPatches(val); AnimationController.ApplyPatches(val); SpriteLoader.ApplyPatches(val); VideoHandler.ApplyPatches(val); SlashColourHandler.ApplyPatches(val); GUIHelper.InitInputBlocking(); DialogueHandler.OnTextAccessed += delegate(string sheet, string key2, string text2) { TextLog.LogText(sheet, key2, text2); }; DialogueHandler.OnTextAccessed += delegate(string sheet, string key2, string text2) { if (ShowDevHub) { DialogueEditor.TrackText(sheet, key2, text2); } }; AudioHandler.OnAudioPlayed += delegate(AudioClip clip, AudioSource src) { AudioLog.LogAudio(clip, src); }; T2DLoader.OnT2DTrigger += delegate(string raw, string clean, string sprite) { T2DLog.LogTrigger(raw, clean, sprite); }; VideoHandler.OnCinematicTriggered += delegate(string name, bool replaced) { VideoPillar.LogCinematic(name, replaced); }; GCUtil.OnTCGCStutter += delegate { DevProfiler.RecordSpike("TC GCManager stutter — threshold bumped 10%", 0f); }; RawKeyboardLeakBlocker.ApplyPatches(val); ((MonoBehaviour)this).StartCoroutine((IEnumerator)AwakeDelayed(val)); } private IEnumerator AwakeDelayed(Harmony harmony) { yield return null; DialogueHandler.ApplyPatches(harmony); DialogueFlagTracker.ApplyPatches(harmony); GUIHelper.InitCursor(); if (Config.DumpText) { DialogueHandler.DumpText(); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { GCUtil.InitBridge(); GameBridge.InitBridge(); SpriteAtlasBridge.Init(); HealthCheck.LogBootSummaryOnce(); GUIHelper.LogEventSystemInventory(((Scene)(ref scene)).name); if (IsMainMenuScene(((Scene)(ref scene)).name)) { GUIUtility.keyboardControl = 0; GUIHelper.ForceRestoreGameActions(); } if (SceneTraverser.IsTraversing) { SceneTraverser.NotifySceneLoaded(); } else if (Config.DumpSprites) { Logger.LogInfo((object)("Dumping sprites for scene " + ((Scene)(ref scene)).name)); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { SpriteDumper.DumpCollection(array[i]); } T2DDumper.DumpAllT2DSprites(); SceneTraverser.OnDumpCompleted(); Logger.LogInfo((object)("Finished dumping sprites for scene " + ((Scene)(ref scene)).name)); } T2DLoader.ApplyReplacementsInScene(); if (IsMenuScene(((Scene)(ref scene)).name)) { ((MonoBehaviour)this).StartCoroutine(DeferredMenuApply()); } ((MonoBehaviour)this).StartCoroutine(DelayedReapply()); FileCache.BeginCycle(); try { AudioHandler.PrimeForScene(); } finally { FileCache.EndCycle(); } RecolourHandler.ApplyToScene(); SlashColourHandler.ApplyToConfigs(); AnimationController.ClearAnimators(); DialogueHandler.CheckForStaleKeys(); PackManager.OnSceneLoaded(((Scene)(ref scene)).name); T2DLoader.OnSceneLoaded(); T2DLoader.CheckForUninitializedSprites(); } private void OnSceneUnloaded(Scene scene) { LogGate.Restore("scene unload"); Logger.LogInfo((object)("[Scene] '" + ((Scene)(ref scene)).name + "' unloading")); T2DLoader.PruneStaleOriginals(); T2DLoader.PruneSceneState(); T2DLoader.ResetSceneSeed(); RecolourHandler.OnSceneUnloaded(); CameraTracker.OnSceneUnloaded(); } private static bool IsMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return false; } string[] menuScenePatterns = MenuScenePatterns; foreach (string value in menuScenePatterns) { if (sceneName.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private IEnumerator DeferredMenuApply() { yield return (object)new WaitForSeconds(1f); Logger.LogDebug((object)"[MenuDefer] Running deferred ApplyReplacementsInScene for menu scene."); T2DLoader.ApplyReplacementsInScene(); AppliqueLoader.Apply(); } private IEnumerator DelayedReapply() { yield return (object)new WaitForSeconds(0.1f); Logger.LogDebug((object)"[SceneLoad] Running deferred ApplyReplacementsInScene (100 ms post-load)."); T2DLoader.ApplyReplacementsInScene(); RecolourHandler.ApplyToScene(); SlashColourHandler.ApplyToConfigs(); AppliqueLoader.Apply(); } public static void ScheduleDeferredApplyPasses() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Instance == (Object)null)) { ((MonoBehaviour)Instance).StartCoroutine(Instance.DelayedReapply()); Scene activeScene = SceneManager.GetActiveScene(); if (IsMenuScene(((Scene)(ref activeScene)).name)) { ((MonoBehaviour)Instance).StartCoroutine(Instance.DeferredMenuApply()); } } } private void LogInstallRoot() { Logger.LogInfo((object)("[Stitchwork] Install root: " + BasePath)); } private void Update() { if (!_isInert) { DevProfiler.RecordFrame(); DevProfiler.BeginUpdateTiming(); long heapBefore = DevProfiler.SnapshotHeapBytes(); T2DLoader.AdvanceSetterTracking(); DevProfiler.StartOp(); T2DLoader.CheckForUninitializedSprites(); DevProfiler.RecordUninitMs(DevProfiler.StopOp("UninitCheck")); T2DLoader.ScanForLogTriggers(); if (PackManager.HasHotReloadPacks && ++_conditionPollFrames >= 120) { _conditionPollFrames = 0; PackManager.PollHotReloadConditions(); } GUIHelper.UpdateInputBlocking(); HotkeyController.Poll(); ReloadCoordinator.Poll(); SpriteLoader.TickBindingWatchdog(); LogGate.Tick(); RecolourHandler.TickGlobal(); if (_focusRegainReloadFrame > 0 && Time.frameCount >= _focusRegainReloadFrame) { _focusRegainReloadFrame = -1; Logger.LogDebug((object)"[Plugin] Focus regained; re-running SpriteLoader.Reload + T2D reload to refresh atlas RT contents."); SpriteLoader.Reload(); T2DLoader.ReloadSpritesInScene(); } AnimationController.Update(); DevProfiler.AddOpPositiveNetHeapDelta("Plugin.Update.Total", heapBefore); DevProfiler.EndUpdateTiming(); } } private void LateUpdate() { long heapBefore = DevProfiler.SnapshotHeapBytes(); if (ShowDevHub || ShowPackManager || CanvasPackManager.IsVisible || WorldPickMode.Active) { GUIHelper.ShowCursorForWindow(); } CameraTracker.LateUpdate(); RecolourHandler.EnforceAmbient(); RecolourHandler.EnforceGlobal(); DevProfiler.StartOp(); T2DLoader.EnforceT2DReplacements(); DevProfiler.RecordEnforceMs(DevProfiler.StopOp("EnforceT2D")); string text = Config?.T2DDiagWatchName ?? ""; if (text.Length > 0) { T2DLoader.RunWatchedSpriteDiagnostic(text); } DevProfiler.AddOpPositiveNetHeapDelta("Plugin.LateUpdate.Total", heapBefore); } private void OnDestroy() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) if (Config != null) { Rect windowRect = DevHub.WindowRect; if (((Rect)(ref windowRect)).width > 0f) { Config.DevHubX = ((Rect)(ref windowRect)).x; Config.DevHubY = ((Rect)(ref windowRect)).y; } Rect windowRect2 = PackManagerWindow.WindowRect; if (((Rect)(ref windowRect2)).width > 0f) { Config.PackManagerX = ((Rect)(ref windowRect2)).x; Config.PackManagerY = ((Rect)(ref windowRect2)).y; } } GUIHelper.ForceRestoreGameActions(); if (Config != null && Config.DevMode) { Loc.DumpKeysReport(); } SpriteFileWatcher?.Dispose(); AudioFileWatcher?.Dispose(); TextFileWatcher?.Dispose(); FontFileWatcher?.Dispose(); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; } private void OnApplicationFocus(bool focus) { if (!_isInert && focus && Config != null && Config.EagerFocusReload) { _focusRegainReloadFrame = Time.frameCount + 3; } } private void OnGUI() { if (_isInert) { DrawInertToast(); return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); GUIHelper.BeginOnGUI(); CameraTracker.DrawIndicator(); T2DLoader.IsT2DLogActive = false; AudioHandler.IsAudioBrowserActive = false; try { if (ShowStatusOverlay) { StatusOverlay.Draw(); } DumpProgressOverlay.Draw(); ReloadToast.Draw(); if (ShowDevHub) { DevHub.Draw(); } if (ShowPackManager) { PackManagerWindow.Draw(); } WorldPickMode.OnGUI(); if ((ShowDevHub || ShowPackManager || CanvasPackManager.IsVisible) && !Cursor.visible) { GUIHelper.DrawSoftwareCursor(); } DrawNoticeToast(); } catch (Exception arg) { Logger.LogError((object)$"[Stitchwork] OnGUI exception (will recover next frame): {arg}"); GUIHelper.ReleaseFocus(); } GUIHelper.EndOnGUI(); DevProfiler.AddOpPositiveNetHeapDelta("Plugin.OnGUI.Total", heapBefore); } private static void QueueNoticeToast(string title, string body) { _noticeTitle = title; _noticeBody = body; } private void DrawNoticeToast() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (_noticeTitle == null) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (IsMainMenuScene(((Scene)(ref activeScene)).name)) { EnsureInertStyles(); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width - 540f - 16f, (float)Screen.height - 210f - 16f, 540f, 210f); Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.08f, 0.08f, 0.1f, 0.94f); GUI.Box(val, "", _inertBoxStyle); GUI.backgroundColor = backgroundColor; GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 16f, ((Rect)(ref val)).y + 12f, ((Rect)(ref val)).width - 32f, ((Rect)(ref val)).height - 24f)); GUILayout.Label(_noticeTitle, _inertTitleStyle, Array.Empty()); GUILayout.Space(6f); GUILayout.Label(_noticeBody, _inertBodyStyle, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(Loc.T("Got it"), (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(96f), GUILayout.Height(30f) })) { _noticeTitle = null; _noticeBody = null; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndArea(); } } private void DrawInertToast() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (_inertToastDismissed) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (IsMainMenuScene(((Scene)(ref activeScene)).name)) { EnsureInertStyles(); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width - 540f - 16f, (float)Screen.height - 180f - 16f, 540f, 180f); Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.08f, 0.08f, 0.1f, 0.94f); GUI.Box(val, "", _inertBoxStyle); GUI.backgroundColor = backgroundColor; GUILayout.BeginArea(new Rect(((Rect)(ref val)).x + 16f, ((Rect)(ref val)).y + 12f, ((Rect)(ref val)).width - 32f, ((Rect)(ref val)).height - 24f)); GUILayout.Label("Stitchwork is INACTIVE", _inertTitleStyle, Array.Empty()); GUILayout.Space(6f); GUILayout.Label("Upstream Patchwork was detected as also installed. Stitchwork has disabled itself this session to avoid Harmony patch conflicts.\n\nTo enable Stitchwork: remove Patchwork.dll from BepInEx/plugins/.", _inertBodyStyle, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("Got it", (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(96f), GUILayout.Height(30f) })) { _inertToastDismissed = true; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndArea(); } } private static bool IsMainMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return false; } if (!(sceneName == "Menu_Title")) { return sceneName.StartsWith("Menu_", StringComparison.Ordinal); } return true; } private static void EnsureInertStyles() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown if (_inertTitleStyle == null) { _inertTitleStyle = new GUIStyle(GUI.skin.label) { fontSize = 20, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; _inertTitleStyle.normal.textColor = new Color(0.95f, 0.5f, 0.25f); _inertBodyStyle = new GUIStyle(GUI.skin.label) { fontSize = 13, wordWrap = true, alignment = (TextAnchor)4 }; _inertBodyStyle.normal.textColor = new Color(0.92f, 0.92f, 0.92f); _inertBoxStyle = new GUIStyle(GUI.skin.box); } } private void InitializeFolders() { IOUtil.EnsureDirectoryExists(SpriteDumper.DumpPath); IOUtil.EnsureDirectoryExists(SpriteLoader.LoadPath); IOUtil.EnsureDirectoryExists(SpriteLoader.AtlasLoadPath); IOUtil.EnsureDirectoryExists(T2DDumper.DumpPath); IOUtil.EnsureDirectoryExists(Path.Combine(SpriteLoader.LoadPath, "T2D")); IOUtil.EnsureDirectoryExists(T2DLoader.AtlasLoadPath); IOUtil.EnsureDirectoryExists(AudioHandler.SoundFolder); IOUtil.EnsureDirectoryExists(VideoHandler.VideoLoadPath); IOUtil.EnsureDirectoryExists(DialogueHandler.TextDumpPath); IOUtil.EnsureDirectoryExists(DialogueHandler.TextLoadPath); } } public static class StitchworkApi { public const int ApiVersion = 1; private static bool _warnedNotReady; public static bool InstanceSkinsReady => false; public static int ApplyInstanceSkin(GameObject root, string skinId, Texture2D sheet) { if ((Object)(object)root == (Object)null || string.IsNullOrEmpty(skinId) || (Object)(object)sheet == (Object)null) { return 0; } WarnNotReadyOnce(); return 0; } public static int RemoveInstanceSkin(GameObject root) { _ = (Object)(object)root == (Object)null; return 0; } public static void ClearInstanceSkins() { } private static void WarnNotReadyOnce() { if (!_warnedNotReady) { _warnedNotReady = true; Plugin.Logger.LogWarning((object)"[StitchworkApi] ApplyInstanceSkin called, but the instance-skin mechanism is not implemented in this build (InstanceSkinsReady == false). The call is a safe no-op. This warning prints once per session."); } } } public sealed class StitchworkInstanceSkin : MonoBehaviour { public string SkinId; public Texture2D Sheet; public string SheetPath; } public class StitchworkConfig { private readonly ConfigEntry _DumpSprites; private readonly ConfigEntry _ConvertSpritesheets; private readonly ConfigEntry _ConvertOnlyChanged; private readonly ConfigEntry _FullDumpKey; private readonly ConfigEntry _DumpText; private readonly ConfigEntry _LogAudioDuration; private readonly ConfigEntry _HideModdedAudioInLog; private readonly ConfigEntry _AnimationControllerPauseKey; private readonly ConfigEntry _AnimationControllerNextFrameKey; private readonly ConfigEntry _AnimationControllerPrevFrameKey; private readonly ConfigEntry _AnimationControllerFreezeKey; private readonly ConfigEntry _ShowPackManager; private readonly ConfigEntry _DevHubDashboardKey; private readonly ConfigEntry _DevHubGraphicsKey; private readonly ConfigEntry _DevHubTextKey; private readonly ConfigEntry _DevHubVideoKey; private readonly ConfigEntry _DevHubAudioKey; private readonly ConfigEntry _DevHubPerformanceKey; private readonly ConfigEntry _TextLogDuration; private readonly ConfigEntry _TextLogMaxVisible; private readonly ConfigEntry _AudioLogMaxVisible; private readonly ConfigEntry _GCEveryNScenes; private readonly ConfigEntry _AnimCompileAlphaThreshold; private readonly ConfigEntry _HeapReserveMB; private readonly ConfigEntry _DisableHeapThresholdBump; private readonly ConfigEntry _DropFileCacheBytesAfterReload; private readonly ConfigEntry _HeapCaptureWindowSeconds; private readonly ConfigEntry _GcStallCaptureThresholdMs; private readonly ConfigEntry _DisableUninitCheck; private readonly ConfigEntry _UninitCheckCacheRefreshSeconds; private readonly ConfigEntry _ThoroughT2DEnrollment; private readonly ConfigEntry _DiagnosticMode; private readonly ConfigEntry _MatchOriginalColorSpace; private readonly ConfigEntry _LogTextureSwaps; private readonly ConfigEntry _AudioCompressedInMemoryThresholdKB; private readonly ConfigEntry _SuppressLogDuringCinematics; private readonly ConfigEntry _SlicedSpriteReload; private readonly ConfigEntry _FontFallbackEnabled; private readonly ConfigEntry _VideoSkipFrameOnDrop; private readonly ConfigEntry _VideoSkipColorGrading; private readonly ConfigEntry _VideoAudioDirect; private readonly ConfigEntry _GlobalPacksPath; private readonly ConfigEntry _FontFallbackScale; private readonly ConfigEntry _FontFallbackPointSize; private readonly ConfigEntry _FontFallbackBitmapMode; private readonly ConfigEntry _AutoEnableIncrementalGC; private readonly ConfigEntry _IncrementalGCTimeSliceMs; private readonly ConfigEntry _EagerFocusReload; private readonly ConfigEntry _DevMode; private readonly ConfigEntry _T2DDiagWatchName; private readonly ConfigEntry _EnableApplique; private readonly ConfigEntry _LogT2DLookupMisses; private readonly ConfigEntry _VerboseLogging; private readonly ConfigEntry _PreviewBackdropOpacity; private readonly ConfigEntry _PreviewBackdropWhite; private readonly ConfigEntry _ShowStatusOverlay; private readonly ConfigEntry _UILanguage; private readonly ConfigEntry _DisableCopyTextureFastPath; private readonly ConfigEntry _TargetPackPath; private readonly ConfigEntry _UseCanvasPackManager; private readonly ConfigEntry _DevHubX; private readonly ConfigEntry _DevHubY; private readonly ConfigEntry _DevHubTab; private readonly ConfigEntry _PackManagerX; private readonly ConfigEntry _PackManagerY; private readonly ConfigEntry _CanvasPMX; private readonly ConfigEntry _CanvasPMY; public bool DumpSprites { get { return _DumpSprites.Value; } set { _DumpSprites.Value = value; } } public bool ConvertSpritesheets => _ConvertSpritesheets.Value; public bool ConvertOnlyChanged { get { return _ConvertOnlyChanged.Value; } set { _ConvertOnlyChanged.Value = value; } } public KeyCode FullDumpKey => _FullDumpKey.Value; public bool DumpText { get { return _DumpText.Value; } set { _DumpText.Value = value; } } public double LogAudioDuration => _LogAudioDuration.Value; public bool HideModdedAudioInLog => _HideModdedAudioInLog.Value; public KeyCode AnimationControllerPauseKey => _AnimationControllerPauseKey.Value; public KeyCode AnimationControllerNextFrameKey => _AnimationControllerNextFrameKey.Value; public KeyCode AnimationControllerPrevFrameKey => _AnimationControllerPrevFrameKey.Value; public KeyCode AnimationControllerFreezeKey => _AnimationControllerFreezeKey.Value; public KeyCode ShowPackManagerKey => _ShowPackManager.Value; public KeyCode DevHubDashboardKey => _DevHubDashboardKey.Value; public KeyCode DevHubGraphicsKey => _DevHubGraphicsKey.Value; public KeyCode DevHubTextKey => _DevHubTextKey.Value; public KeyCode DevHubVideoKey => _DevHubVideoKey.Value; public KeyCode DevHubAudioKey => _DevHubAudioKey.Value; public KeyCode DevHubPerformanceKey => _DevHubPerformanceKey.Value; public double TextLogDuration => _TextLogDuration.Value; public int TextLogMaxVisible => _TextLogMaxVisible.Value; public int AudioLogMaxVisible => _AudioLogMaxVisible.Value; public int GCEveryNScenes => _GCEveryNScenes.Value; public int AnimCompileAlphaThreshold => _AnimCompileAlphaThreshold.Value; public int HeapReserveMB => _HeapReserveMB.Value; public bool DisableHeapThresholdBump => _DisableHeapThresholdBump.Value; public bool DropFileCacheBytesAfterReload => _DropFileCacheBytesAfterReload.Value; public int HeapCaptureWindowSeconds => _HeapCaptureWindowSeconds.Value; public int GcStallCaptureThresholdMs => _GcStallCaptureThresholdMs.Value; public bool DisableUninitCheck { get { return _DisableUninitCheck.Value; } set { _DisableUninitCheck.Value = value; } } public float UninitCheckCacheRefreshSeconds { get { return _UninitCheckCacheRefreshSeconds.Value; } set { _UninitCheckCacheRefreshSeconds.Value = value; } } public bool ThoroughT2DEnrollment { get { return _ThoroughT2DEnrollment.Value; } set { _ThoroughT2DEnrollment.Value = value; } } public bool DiagnosticMode => _DiagnosticMode.Value; public bool MatchOriginalColorSpace => _MatchOriginalColorSpace.Value; public bool LogTextureSwaps => _LogTextureSwaps.Value; public int AudioCompressedInMemoryThresholdKB => _AudioCompressedInMemoryThresholdKB.Value; public bool SuppressLogDuringCinematics => _SuppressLogDuringCinematics.Value; public bool SlicedSpriteReload => _SlicedSpriteReload.Value; public bool FontFallbackEnabled => _FontFallbackEnabled.Value; public bool VideoSkipFrameOnDrop => _VideoSkipFrameOnDrop.Value; public bool VideoSkipColorGrading => _VideoSkipColorGrading.Value; public bool VideoAudioDirect => _VideoAudioDirect.Value; public string GlobalPacksPath => _GlobalPacksPath.Value; public float FontFallbackScale => Mathf.Clamp(_FontFallbackScale.Value, 0.5f, 1.5f); public int FontFallbackPointSize => _FontFallbackPointSize.Value; public bool FontFallbackBitmapMode => _FontFallbackBitmapMode.Value; public bool AutoEnableIncrementalGC => _AutoEnableIncrementalGC.Value; public int IncrementalGCTimeSliceMs => _IncrementalGCTimeSliceMs.Value; public bool EagerFocusReload => _EagerFocusReload.Value; public bool DevMode => _DevMode.Value; public bool EnableApplique => _EnableApplique.Value; public string T2DDiagWatchName => _T2DDiagWatchName.Value; public bool LogT2DLookupMisses => _LogT2DLookupMisses.Value; public bool VerboseLogging => _VerboseLogging.Value; public float PreviewBackdropOpacity { get { return _PreviewBackdropOpacity.Value; } set { _PreviewBackdropOpacity.Value = value; } } public bool PreviewBackdropWhite { get { return _PreviewBackdropWhite.Value; } set { _PreviewBackdropWhite.Value = value; } } public bool ShowStatusOverlay { get { return _ShowStatusOverlay.Value; } set { _ShowStatusOverlay.Value = value; } } public string UILanguage { get { return _UILanguage.Value; } set { _UILanguage.Value = value; } } internal ConfigEntry UILanguageEntry => _UILanguage; public bool DisableCopyTextureFastPath => _DisableCopyTextureFastPath.Value; public string TargetPackPath => _TargetPackPath.Value; internal ConfigEntry TargetPackPathEntry => _TargetPackPath; public bool UseCanvasPackManager => _UseCanvasPackManager.Value; public float DevHubX { get { return _DevHubX.Value; } set { _DevHubX.Value = value; } } public float DevHubY { get { return _DevHubY.Value; } set { _DevHubY.Value = value; } } public int DevHubTab { get { return _DevHubTab.Value; } set { _DevHubTab.Value = value; } } public float PackManagerX { get { return _PackManagerX.Value; } set { _PackManagerX.Value = value; } } public float PackManagerY { get { return _PackManagerY.Value; } set { _PackManagerY.Value = value; } } public float CanvasPMX { get { return _CanvasPMX.Value; } set { _CanvasPMX.Value = value; } } public float CanvasPMY { get { return _CanvasPMY.Value; } set { _CanvasPMY.Value = value; } } public StitchworkConfig(ConfigFile config) { //IL_06bb: Unknown result type (might be due to invalid IL or missing references) //IL_06c5: Expected O, but got Unknown _DisableCopyTextureFastPath = config.Bind("Diagnostics", "DisableCopyTextureFastPath", false, "Diagnostic. Forces atlas composites to use Graphics.DrawTexture instead of Graphics.CopyTexture, even when the GPU supports the fast path. Try this if sprite replacements are not visible (reported on macOS/Metal). Costs a little speed on large reloads; changes nothing visually when both paths work."); _TargetPackPath = config.Bind("GUI", "TargetPackPath", "", "Push/Pull target pack folder path (set from the Dev Hub Dashboard; empty = none selected)."); _LogAudioDuration = config.Bind("GUI", "LogAudioDuration", 5.0, "Duration (in seconds) to keep audio log entries visible."); _HideModdedAudioInLog = config.Bind("GUI", "HideModdedAudioInLog", true, "Hide modded audio clips from the audio log."); _TextLogDuration = config.Bind("GUI", "TextLogDuration", 10.0, "Duration (in seconds) for bumped log entries to fade out after leaving the visible slots."); _TextLogMaxVisible = config.Bind("GUI", "TextLogMaxVisible", 15, "Number of latest text log entries always visible. Entries beyond this fade out over TextLogDuration seconds. (Range: 5-50)"); _AudioLogMaxVisible = config.Bind("GUI", "AudioLogMaxVisible", 15, "Number of latest audio log entries always visible. Entries beyond this fade out over LogAudioDuration seconds. (Range: 5-50)"); _DumpSprites = config.Bind("Dumping", "DumpSprites", false, "Enable dumping of sprites"); _AnimCompileAlphaThreshold = config.Bind("Dumping", "AnimCompileAlphaThreshold", 16, "Alpha value (1-255) a canvas pixel must reach for Animation Canvas Compile to count it as art (classification AND crop bounds). The default 16 ignores stray editor residue. Lower it toward 1 when your art carries soft glow / bloom / haze, so the faint fringe survives the cut — and keep the canvas free of accidental faint pixels at low values."); _DumpText = config.Bind("Dumping", "DumpText", false, "Enable dumping of text when the game starts."); _ConvertSpritesheets = config.Bind("Dumping", "ConvertSpritesheets", false, "LEGACY auto mode: convert modded spritesheets to individual PNGs on every load. Prefer the Dev Hub Debug tab's 'Convert Sheets' button — explicit, overwrites stale output, and can skip frames identical to vanilla."); _ConvertOnlyChanged = config.Bind("Dumping", "ConvertOnlyChanged", true, "Sheet conversion (Debug tab button): only write sprites whose pixels differ from vanilla. Turns a full-sheet port into a minimal individual-sprite pack. Off = convert every sprite on the sheet."); _FullDumpKey = config.Bind("Keybinds", "FullDumpKey", (KeyCode)0, "Optional key to start the full dump (loads every scene in the game). Default None — the Dev Hub Debug tab has the button, with a confirm step this key skips. Only fires when DumpSprites is enabled."); _ShowPackManager = config.Bind("Keybinds", "ShowPackManager", (KeyCode)49, "Key to toggle the Pack Manager."); _DevHubDashboardKey = config.Bind("Keybinds", "DevHubDashboard", (KeyCode)50, "Open Dev Hub at Dashboard tab (press again to close)."); _DevHubGraphicsKey = config.Bind("Keybinds", "DevHubGraphics", (KeyCode)51, "Open Dev Hub at Graphics tab (press again to close)."); _DevHubTextKey = config.Bind("Keybinds", "DevHubText", (KeyCode)52, "Open Dev Hub at Text tab (press again to close)."); _DevHubVideoKey = config.Bind("Keybinds", "DevHubVideo", (KeyCode)53, "Open Dev Hub at Video tab (press again to close)."); _DevHubAudioKey = config.Bind("Keybinds", "DevHubAudio", (KeyCode)54, "Open Dev Hub at Audio tab (press again to close)."); _DevHubPerformanceKey = config.Bind("Keybinds", "DevHubPerformance", (KeyCode)55, "Open Dev Hub at Performance tab (press again to close)."); _AnimationControllerPauseKey = config.Bind("Keybinds", "AnimationControllerPauseKey", (KeyCode)278, "Key to pause/unpause the selected animator in the animation controller."); _AnimationControllerNextFrameKey = config.Bind("Keybinds", "AnimationControllerNextFrameKey", (KeyCode)280, "Key to advance one frame in the selected animator in the animation controller when paused."); _AnimationControllerPrevFrameKey = config.Bind("Keybinds", "AnimationControllerPrevFrameKey", (KeyCode)277, "Key to go back one frame in the selected animator in the animation controller when paused."); _AnimationControllerFreezeKey = config.Bind("Keybinds", "AnimationControllerFreezeKey", (KeyCode)279, "Key to freeze/unfreeze the object of the selected animator in the animation controller."); _GCEveryNScenes = config.Bind("Dumping", "GCEveryNScenes", 10, "Run garbage collection every N scenes during full dump. Lower = more stable but slower."); _HeapReserveMB = config.Bind("Performance", "HeapReserveMB", -1, "Mono heap reserve in MB. Pre-allocates this much memory at startup to raise the GC's high-water mark, reducing mid-gameplay stutter from heap-growth collections when custom asset packs are loaded. -1 = auto (40% of TC's heap threshold, capped at 512 MB). 0 = disabled."); _DisableHeapThresholdBump = config.Bind("Performance", "DisableHeapThresholdBump", false, "EXPERIMENTAL. When true, Stitchwork stops updating the game's GC heap threshold to account for our own retained caches. Collects fire more often but each is smaller and faster. Trade longer infrequent stutters for shorter frequent ones. Default false preserves the current threshold-bumping behaviour. Toggle live via Configuration Manager to compare."); _DropFileCacheBytesAfterReload = config.Bind("Performance", "DropFileCacheBytesAfterReload", false, "DIAGNOSTIC. Retained for heap-floor probe reports. Current main keeps FileCache bytes after reload unless a later build wires this flag into FileCache.EndCycle."); _HeapCaptureWindowSeconds = config.Bind("Performance", "HeapCaptureWindowSeconds", 10, "Duration in seconds for the DevProfiler 'Capture Heap Window' diagnostic. The report samples Mono heap, Stitchwork cache buckets, GC events, and instrumented operation aggregates into BepInEx/plugins/Stitchwork/diagnostics."); _GcStallCaptureThresholdMs = config.Bind("Performance", "GcStallCaptureThresholdMs", 50, "Frame-time threshold in milliseconds for the DevProfiler 'Arm GC Stall Capture' diagnostic. When armed, the profiler keeps a rolling pre-buffer and writes a gc-stall report after the next gen-max collect frame at or above this threshold."); _DisableUninitCheck = config.Bind("Performance", "DisableUninitCheck", false, "DIAGNOSTIC ABLATION. When true, skips the per-frame T2D uninitialized sprite sweep entirely. This can make late-spawned or pooled T2D sprites miss their replacement until another path catches them. Use only for short GC attribution tests."); _UninitCheckCacheRefreshSeconds = config.Bind("Performance", "UninitCheckCacheRefreshSeconds", 0.3f, "DIAGNOSTIC. Real-time seconds between FindObjectsByType refreshes inside the T2D uninitialized sprite sweep. Uses unscaled time so uncapped rendering does not multiply discovery work. Lower values reduce discovery delay but increase allocation. Default 0.30 matches the successful diagnostic cadence."); _ThoroughT2DEnrollment = config.Bind("Performance", "ThoroughT2DEnrollment", false, "OPT-IN FIX for transient T2D sprites that lose their replacement (death cocoon, Warrior/Beast charge-slash, brief VFX). When true, the uninit sweep also scans INACTIVE renderers, so a pooled sprite is enrolled while still in the pool and kept correct from the moment it activates, instead of relying on the sweep catching its short active window. COST: the inactive scan is the heaviest allocation path and the enforced renderer set grows — leave OFF unless you actually hit the transient-revert issue. Pair with a lower UninitCheckCacheRefreshSeconds."); _DiagnosticMode = config.Bind("Experimental", "DiagnosticMode", false, "DIAGNOSTIC ONLY. When true, Stitchwork loads but applies NO Harmony patches and does NOT load any packs. The DevProfiler [Mem] log lines still fire so you can measure vanilla Silksong's baseline allocation rate without our hooks contributing. Compare against a normal session over the same gameplay to quantify Stitchwork's share. NOT for normal use — packs won't apply, replacements won't work, Dev Hub won't appear. Restart the game after toggling."); _MatchOriginalColorSpace = config.Bind("Experimental", "MatchOriginalColorSpace", false, "EXPERIMENTAL. Allocate atlas RenderTextures with sRGB / Linear read-write matching the original Texture2D's graphics format, rather than hard-coded Linear. Fixes washed-out / oversaturated replacements on configurations where the project color space is gamma and the vanilla texture is sRGB-flagged. Off by default until validated; restart after toggling."); _LogTextureSwaps = config.Bind("Experimental", "LogTextureSwaps", false, "DIAGNOSTIC. When true, every successful T2D texture swap is logged at LogInfo (default-visibility) instead of LogDebug, and every name-pattern denylist refusal emits a one-shot trace. Use to debug creator-pack issues. Default false; turn on temporarily when investigating a specific report."); _DevMode = config.Bind("Experimental", "DevMode", false, "Gates research / development tooling surfaces not intended for creators. Currently affects: the Graphics pillar's \"Materials\" sub-tab (material tint diagnostic + neutralizer PoC + ambient probe — research-grade tooling, see docs/material-tint-investigation.md). Default false; flip to true when investigating material tint or other diagnostic surfaces without finished UX."); _T2DDiagWatchName = config.Bind("Experimental", "T2DDiagWatchName", "", "DIAGNOSTIC. Substring (case-insensitive) of a sprite/texture name to watch. When non-empty, Stitchwork periodically logs the full replacement state of matching renderers ([T2D-Diag] lines): enrolled-for-enforcement, sprite/texture fake-null, cached-replacement fake-null, and render path (sprite vs material/standalone). Built to pin the transparent death-cocoon failure — set to e.g. 'death_pieces', reproduce, read the log. Empty = off (zero cost)."); _EnableApplique = config.Bind("Experimental", "EnableApplique", true, "Appliqué: per-clip sprite overrides via Sprites//Clips//.png (art-on-rails — same frames/timing, per-clip pixels; docs/frame-decoupling-design.md). Zero cost when no such files exist. Disable to force every clip back to shared art."); _SlicedSpriteReload = config.Bind("Performance", "SlicedSpriteReload", true, "Spread sprite reloads (pack apply, hot reload) across frames with a progress bar, instead of freezing the game for the whole reload. The mixed state is brief: some collections show the new art a few frames before the rest. Turn OFF to restore the old single-frame synchronous reload (freezes, but atomic)."); _SuppressLogDuringCinematics = config.Bind("Performance", "SuppressLogDuringCinematics", true, "Stop writing the log file while a cinematic video plays, and resume when it ends. Every log line is a synchronous disk write on the main thread; during video playback that competes with the video stream and starves the audio buffer, which is heard as choppy cinematic audio. Measured on an install whose other mods emit a per-frame shader error: 68% of all log lines in the playback window. Log lines from every mod are paused, not just Stitchwork's — the log records the gap and how many lines it covered. Turn OFF if you need a complete log across a cinematic (e.g. debugging a crash that happens during one)."); _AudioCompressedInMemoryThresholdKB = config.Bind("Performance", "AudioCompressedInMemoryThresholdKB", 1024, "Replacement audio files at or above this size (in KB) keep their compressed bytes in memory and decode during playback, instead of being fully decompressed to PCM when loaded. Prevents multi-gigabyte memory use (and crashes) with music-heavy sound packs — a 1 GB MP3 pack would otherwise decode to 5-10 GB. Small sound effects below the threshold stay fully decoded for zero-latency playback. 0 = always fully decompress (the pre-0709 behaviour)."); _FontFallbackEnabled = config.Bind("Fonts", "FontFallbackEnabled", true, "Inject fallback fonts (from packs' or the root Fonts/_fallback/ folder, .ttf/.otf) into TextMeshPro's global fallback chain. Fixes 'tofu' box characters when replacement text uses characters the game's fonts don't cover (rare Chinese/Japanese characters, etc.). Additive only — the game's own fonts are never replaced, and glyphs they already cover keep their vanilla look."); _FontFallbackPointSize = config.Bind("Fonts", "FontFallbackPointSize", 64, "Rasterization point size for baked fallback glyphs (24-128). Larger = crisper at big text sizes but a larger atlas texture."); _VideoSkipFrameOnDrop = config.Bind("Video", "VideoSkipFrameOnDrop", true, "Let cinematics drop video frames to stay in sync when the frame rate dips, instead of slowing playback (the vanilla setting) — slowed playback is heard as chopped audio. Applies to every cinematic, replaced or not. Turn OFF to restore vanilla never-drop behaviour (perfect frame delivery, audio pays for any hitch)."); _VideoSkipColorGrading = config.Bind("Video", "VideoSkipColorGrading", true, "Replaced cinematics only: skip the game's colour-grade animation over the video and hold neutral curves, so your video shows as you authored it. Vanilla cinematics keep their grading. Turn OFF to let the game grade replacement videos too."); _VideoAudioDirect = config.Bind("Video", "VideoAudioDirect", false, "Replacement videos only: send the video's audio to Direct output instead of an AudioSource. Try ON if replaced cinematics still chop; some platforms produce no audio in Direct mode, hence default off."); _GlobalPacksPath = config.Bind("Packs", "GlobalPacksPath", "", "Folder scanned for packs in addition to BepInEx/plugins. Empty = /Packs, which every mod-manager profile shares (it hangs off the game install, not the profile). Drop packs there in their distributed shape (PackName/Stitchwork/...). Enable/disable state stays per profile. Set an absolute path to redirect (e.g. the save folder on macOS/Linux)."); _FontFallbackScale = config.Bind("Fonts", "FontFallbackScale", 1f, "Size multiplier for fallback-font glyphs relative to the game's own text (0.5-1.5). CJK fonts fill their em square edge-to-edge while the game's fonts leave headroom, so fallback glyphs can read slightly LARGE at 1.0 — try 0.9. Per-font override: a trailing @s tag on the font file name, e.g. 'myfont@s0.85.ttf'. Changes apply on the next font reload."); _FontFallbackScale.SettingChanged += delegate { FontFileWatcher.ReloadFonts = true; }; _FontFallbackBitmapMode = config.Bind("Fonts", "FontFallbackBitmapMode", false, "Bake fallback glyphs as antialiased bitmap instead of SDF (signed distance field). Try this if fallback characters render with artifacts — bitmap is the guaranteed-correct mode, at the cost of slight softness at large text sizes."); _AutoEnableIncrementalGC = config.Bind("Performance", "AutoEnableIncrementalGC", true, "Default ON (FLAG-06). Stitchwork keeps the gc-max-time-slice line in the game's boot.config so Silksong boots with Unity's incremental garbage collector — the game's own GC manager then stands down and the periodic ~150 ms GC stutter disappears. Changes take effect on the NEXT game launch (a menu notice prompts the one-time restart). Survives Steam 'verify integrity' automatically (re-applied on the following launch). Set false to opt out; Stitchwork then removes the line and the game's default GC behaviour returns after a restart."); _IncrementalGCTimeSliceMs = config.Bind("Experimental", "IncrementalGCTimeSliceMs", 0, "EXPERIMENTAL (FLAG-06). Override for the incremental GC time slice, in milliseconds, applied at startup when the player is running with incremental GC enabled (via the boot.config gc-max-time-slice key). 0 = leave the boot.config/engine value as-is. Larger slice = more GC progress per frame at higher per-frame cost; smaller = smoother frames but slower collection. If set while incremental GC is NOT active, Stitchwork logs a warning — usual cause: Steam 'verify integrity' restored boot.config and removed the gc-max-time-slice line."); _EagerFocusReload = config.Bind("Experimental", "EagerFocusReload", false, "Opt-in: re-stamp atlas RenderTextures on focus regain (alt-tab back into the game). Guards against GPU-side RT content being zeroed across focus changes on Steam Deck suspend/resume, exclusive-fullscreen alt-tab, fullscreen/resolution mode changes, or driver device-lost recovery. Default false because on Windows borderless fullscreen and modern macOS (the common case) RTs survive focus changes and this reload is a deterministic ~130-170 ms stall for no benefit. Turn on if you actually observe atlas-transparent rendering after a focus event."); _LogT2DLookupMisses = config.Bind("Experimental", "LogT2DLookupMisses", false, "DIAGNOSTIC. When true, dumps all _loadedSprites keys after preload, and logs every T2D sprite lookup that fails to find a replacement. Use to pinpoint discovery-vs-runtime key mismatches when a creator pack's individual sprite replacement isn't applying despite the file being on disk. Significant log noise — keep off in normal use. Restart the game after toggling."); _VerboseLogging = config.Bind("Logging", "VerboseLogging", false, "When true, Stitchwork's periodic-diagnostic chatter (DevProfiler memory/uninit heartbeat, per-GC [Mem-GC] lines under incremental GC, GC threshold bumps, sprite-expansion detail, per-cycle reload notices) is logged at info level. When false (default) it drops to debug level — hidden at BepInEx's default verbosity. One-shot/actionable lines and all warnings/errors are unaffected either way. Honored live (no restart)."); _ShowStatusOverlay = config.Bind("GUI", "ShowStatusOverlay", true, "Show the HUD status badge in the bottom-left corner. Can also be toggled from the Pack Manager."); _UILanguage = config.Bind("GUI", "UILanguage", "auto", new ConfigDescription("Language for Stitchwork's own UI (Pack Manager, status overlay, notices). 'auto' follows the game's language setting. Explicit codes load Lang/.yml next to Stitchwork.dll — drop a new translation file in and it appears here after restart. Asset names, file paths, and log lines always stay English.", (AcceptableValueBase)(object)new AcceptableValueList(Loc.DiscoverLanguages()), Array.Empty())); _UseCanvasPackManager = config.Bind("GUI", "UseCanvasPackManager", true, "Use the Canvas-based Pack Manager. Default true — the Canvas manager is the primary UI surface as of 0509. Set false to fall back to the legacy IMGUI Pack Manager (kept for diagnostics and compatibility)."); _PreviewBackdropOpacity = config.Bind("GUI", "PreviewBackdropOpacity", 0.6f, "Opacity of the solid backdrop behind sprite/atlas previews in the Dev Hub (0 = transparent window surface, 1 = fully opaque). Adjustable live via the BG slider under the preview panes."); _PreviewBackdropWhite = config.Bind("GUI", "PreviewBackdropWhite", false, "Preview backdrop surface: false = black (bright art), true = white (dark or glow-heavy art). Toggled live by clicking the BG button next to the slider."); _DevHubX = config.Bind("GUI", "DevHubX", 0f, "Dev Hub window X position (saved on exit)."); _DevHubY = config.Bind("GUI", "DevHubY", 0f, "Dev Hub window Y position (saved on exit)."); _DevHubTab = config.Bind("GUI", "DevHubTab", 0, "Last active Dev Hub tab index (0=Dashboard … 5=Performance)."); _PackManagerX = config.Bind("GUI", "PackManagerX", 0f, "Pack Manager window X position (saved on exit)."); _PackManagerY = config.Bind("GUI", "PackManagerY", 0f, "Pack Manager window Y position (saved on exit)."); _CanvasPMX = config.Bind("GUI", "CanvasPMX", 0f, "Canvas Pack Manager X position (saved on drag)."); _CanvasPMY = config.Bind("GUI", "CanvasPMY", 0f, "Canvas Pack Manager Y position (saved on drag)."); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "Stitchwork"; public const string PLUGIN_NAME = "Stitchwork"; public const string PLUGIN_VERSION = "0.8.6"; } } namespace Stitchwork.Watchers { public class AudioFileWatcher : IDisposable { public FileSystemWatcher AudioWatcher; public List PackWatchers = new List(); public static volatile bool ReloadAudio; public AudioFileWatcher() { AudioWatcher = CreateWatcher(AudioHandler.SoundFolder); RebuildPackWatchers(); } private FileSystemWatcher CreateWatcher(string path) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.Filter = "*.*"; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.InternalBufferSize = 65536; fileSystemWatcher.Changed += OnAudioChanged; fileSystemWatcher.Created += OnAudioChanged; fileSystemWatcher.Deleted += OnAudioChanged; fileSystemWatcher.Renamed += OnAudioChanged; fileSystemWatcher.Error += delegate(object s, ErrorEventArgs e) { Plugin.Logger.LogWarning((object)("[AudioFileWatcher] Watcher buffer overflow (" + e.GetException()?.Message + ") — events lost; raising full audio reload.")); ReloadAudio = true; }; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } public void RebuildPackWatchers() { foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = Path.Combine(pluginPackPath, "Sounds"); if (Directory.Exists(path)) { PackWatchers.Add(CreateWatcher(path)); } } Plugin.Logger.LogDebug((object)$"[AudioFileWatcher] Rebuilt pack watchers: {PackWatchers.Count} dir(s)"); } public void Dispose() { AudioWatcher.EnableRaisingEvents = false; AudioWatcher.Dispose(); foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); } private void OnAudioChanged(object sender, FileSystemEventArgs e) { Plugin.Logger.LogDebug((object)$"[FileWatcher] Audio file event: {e.ChangeType} — {e.FullPath}"); ReloadToast.Record(ReloadToast.Kind.Audio, e.FullPath); ReloadAudio = true; } } public class FontFileWatcher : IDisposable { public FileSystemWatcher RootWatcher; public List PackWatchers = new List(); public static volatile bool ReloadFonts; public FontFileWatcher() { string path = Path.Combine(Plugin.BasePath, "Fonts"); Directory.CreateDirectory(path); RootWatcher = CreateWatcher(path); RebuildPackWatchers(); } private FileSystemWatcher CreateWatcher(string path) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.Filter = "*.*"; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.InternalBufferSize = 65536; fileSystemWatcher.Changed += OnFontChanged; fileSystemWatcher.Created += OnFontChanged; fileSystemWatcher.Deleted += OnFontChanged; fileSystemWatcher.Renamed += OnFontChanged; fileSystemWatcher.Error += delegate(object s, ErrorEventArgs e) { Plugin.Logger.LogWarning((object)("[FontFileWatcher] Watcher buffer overflow (" + e.GetException()?.Message + ") — raising font reload.")); ReloadFonts = true; }; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } public void RebuildPackWatchers() { foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = Path.Combine(pluginPackPath, "Fonts"); if (Directory.Exists(path)) { PackWatchers.Add(CreateWatcher(path)); } } Plugin.Logger.LogDebug((object)$"[FontFileWatcher] Rebuilt pack watchers: {PackWatchers.Count} dir(s)"); } public void Dispose() { RootWatcher.EnableRaisingEvents = false; RootWatcher.Dispose(); foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); } private void OnFontChanged(object sender, FileSystemEventArgs e) { if (e.FullPath.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase) || e.FullPath.EndsWith(".otf", StringComparison.OrdinalIgnoreCase)) { Plugin.Logger.LogDebug((object)$"[FileWatcher] Font file event: {e.ChangeType} — {e.FullPath}"); ReloadToast.Record(ReloadToast.Kind.Font, e.FullPath); ReloadFonts = true; } } } public class TextFileWatcher : IDisposable { public FileSystemWatcher TextWatcher; public List PackWatchers = new List(); public static volatile bool ReloadText; public TextFileWatcher() { TextWatcher = CreateWatcher(DialogueHandler.TextLoadPath); RebuildPackWatchers(); } private FileSystemWatcher CreateWatcher(string path) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.Filter = "*.yml"; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.InternalBufferSize = 65536; fileSystemWatcher.Changed += OnTextChanged; fileSystemWatcher.Created += OnTextChanged; fileSystemWatcher.Deleted += OnTextChanged; fileSystemWatcher.Renamed += OnTextChanged; fileSystemWatcher.Error += delegate(object s, ErrorEventArgs e) { Plugin.Logger.LogWarning((object)("[TextFileWatcher] Watcher buffer overflow (" + e.GetException()?.Message + ") — events lost; raising full text reload.")); ReloadText = true; }; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } public void RebuildPackWatchers() { foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = Path.Combine(pluginPackPath, "Text"); if (Directory.Exists(path)) { PackWatchers.Add(CreateWatcher(path)); } } Plugin.Logger.LogDebug((object)$"[TextFileWatcher] Rebuilt pack watchers: {PackWatchers.Count} dir(s)"); } public void Dispose() { TextWatcher.EnableRaisingEvents = false; TextWatcher.Dispose(); foreach (FileSystemWatcher packWatcher in PackWatchers) { packWatcher.EnableRaisingEvents = false; packWatcher.Dispose(); } PackWatchers.Clear(); } private void OnTextChanged(object sender, FileSystemEventArgs e) { Plugin.Logger.LogDebug((object)$"[FileWatcher] Text file event: {e.ChangeType} — {e.FullPath}"); ReloadToast.Record(ReloadToast.Kind.Text, e.FullPath); ReloadText = true; } } } namespace Stitchwork.Util { public static class ArchitectBridge { private static bool _init; private static FieldInfo _fiHasPreloaded; private static FieldInfo _fiIsLoading; private static bool _inEpisode; private static float _episodeStart; private static int _suppressedThisEpisode; private static bool _stuckReleased; private static bool _editProbed; private static Func _isEditing; public static bool BridgeAvailable { get { if (_fiHasPreloaded != null) { return _fiIsLoading != null; } return false; } } public static bool IsEditing { get { if (!_editProbed) { _editProbed = true; ResolveEditBridge(); } if (_isEditing != null) { return _isEditing(); } return false; } } private static void InitBridge() { if (_init) { return; } _init = true; Type type = AccessTools.TypeByName("Architect.Content.Preloads.PreloadManager"); if (!(type == null)) { _fiHasPreloaded = type.GetField("HasPreloaded", BindingFlags.Static | BindingFlags.Public); _fiIsLoading = type.GetField("IsLoading", BindingFlags.Static | BindingFlags.Public); if (BridgeAvailable) { Plugin.Logger.LogInfo((object)"[ArchitectBridge] Architect detected — LoadCollection will hold during its preload (mirror of Architect's own PatchworkFix, applied on our side)."); } else { Plugin.Logger.LogWarning((object)"[ArchitectBridge] Architect detected but PreloadManager.HasPreloaded/IsLoading did not resolve (their internals moved?) — preload guard inactive. Vanilla captures during Architect preload are unguarded this session; see docs/architect-reference.md."); } } } public static bool ShouldSuppressLoadCollection() { if (!_init) { InitBridge(); } if (!BridgeAvailable || _stuckReleased) { return false; } bool flag; try { flag = !(bool)_fiHasPreloaded.GetValue(null) || (bool)_fiIsLoading.GetValue(null); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[ArchitectBridge] PreloadManager read failed (" + ex.Message + ") — guard disabled for session.")); _stuckReleased = true; return false; } if (flag) { if (!_inEpisode) { _inEpisode = true; _episodeStart = Time.realtimeSinceStartup; _suppressedThisEpisode = 0; Plugin.Logger.LogDebug((object)"[ArchitectBridge] Architect preloading — holding LoadCollection."); } else if (Time.realtimeSinceStartup - _episodeStart > 90f) { _stuckReleased = true; Plugin.Logger.LogWarning((object)"[ArchitectBridge] Architect has reported 'preloading' for >90 s — releasing the guard for this session so skins aren't suppressed indefinitely. If Architect's preload genuinely runs this long on this machine, report it to us."); return false; } _suppressedThisEpisode++; return true; } if (_inEpisode) { _inEpisode = false; if (_suppressedThisEpisode > 0) { Plugin.Logger.LogInfo((object)($"[ArchitectBridge] Architect preload finished — {_suppressedThisEpisode} suppressed " + "LoadCollection call(s); raising full tk2d reload to apply skins to them.")); SpriteFileWatcher.ReloadSprites = true; } } return false; } private static void ResolveEditBridge() { Type type = AccessTools.TypeByName("Architect.Editor.EditManager"); if (type == null) { return; } FieldInfo f = type.GetField("IsEditing", BindingFlags.Static | BindingFlags.Public); if (f == null || f.FieldType != typeof(bool)) { Plugin.Logger.LogWarning((object)"[ArchitectBridge] Architect present but EditManager.IsEditing did not resolve — hotkey yield disabled."); return; } try { _isEditing = Expression.Lambda>(Expression.Field(null, f), Array.Empty()).Compile(); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[ArchitectBridge] EditManager accessor compile failed (" + ex.Message + "); using reflection fallback.")); _isEditing = () => (bool)f.GetValue(null); } Plugin.Logger.LogInfo((object)"[ArchitectBridge] Architect detected — Stitchwork hotkeys yield while its editor is in edit mode."); } } public static class BootConfigGC { public enum Result { NoAction, KeyWritten, KeyRemoved, KeyIgnoredByPlayer, Unavailable } private const string Key = "gc-max-time-slice"; private const string Line = "gc-max-time-slice=3"; private static readonly string[] _bootConfigCandidates = new string[3] { "boot.config", Path.Combine("Resources", "Data", "boot.config"), Path.Combine("Data", "boot.config") }; public static Result LastResult { get; private set; } = Result.NoAction; private static string BootConfigPath { get { string[] bootConfigCandidates = _bootConfigCandidates; foreach (string path in bootConfigCandidates) { string text = Path.Combine(Application.dataPath, path); if (File.Exists(text)) { return text; } } return Path.Combine(Application.dataPath, _bootConfigCandidates[0]); } } private static string BootConfigCandidatesJoined => string.Join(", ", Array.ConvertAll(_bootConfigCandidates, (string rel) => Path.Combine(Application.dataPath, rel))); public static Result Sync(bool autoEnable) { LastResult = SyncInternal(autoEnable); return LastResult; } private static Result SyncInternal(bool autoEnable) { string bootConfigPath = BootConfigPath; bool isIncremental = GarbageCollector.isIncremental; try { if (!File.Exists(bootConfigPath)) { if (autoEnable && !isIncremental) { Plugin.Logger.LogWarning((object)("[BootConfigGC] boot.config not found — cannot auto-enable incremental GC. Probed: " + BootConfigCandidatesJoined + ". See docs/architecture-review-2026-06.md FLAG-06.")); } return Result.Unavailable; } List list = new List(File.ReadAllLines(bootConfigPath)); bool flag = list.RemoveAll((string l) => l.TrimStart().StartsWith("gc-max-time-slice", StringComparison.Ordinal)) > 0; if (autoEnable) { if (isIncremental) { return Result.NoAction; } if (flag) { Plugin.Logger.LogWarning((object)"[BootConfigGC] gc-max-time-slice is present in boot.config but the player booted WITHOUT incremental GC — this build appears to ignore the key. No restart prompt will be shown; running under the TC-managed GC regime."); return Result.KeyIgnoredByPlayer; } string text = bootConfigPath + ".stitchwork-bak"; if (!File.Exists(text)) { File.Copy(bootConfigPath, text); } list.Add("gc-max-time-slice=3"); File.WriteAllLines(bootConfigPath, list); Plugin.Logger.LogInfo((object)("[BootConfigGC] Appended 'gc-max-time-slice=3' to boot.config (backup: " + Path.GetFileName(text) + "). Incremental GC engages on the next game launch.")); return Result.KeyWritten; } if (!flag) { return Result.NoAction; } File.WriteAllLines(bootConfigPath, list); Plugin.Logger.LogInfo((object)"[BootConfigGC] AutoEnableIncrementalGC=false — removed gc-max-time-slice from boot.config. Default GC behaviour returns on the next game launch."); return Result.KeyRemoved; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[BootConfigGC] Could not reconcile boot.config: " + ex.Message + ". Manual setup: add the line 'gc-max-time-slice=3' to boot.config next to the game data.")); return Result.Unavailable; } } } public static class ConflictTracker { public struct Entry { public string Type; public string Key; public string WinnerPack; public string LoserPack; public string WinnerFile; public string LoserFile; public Entry(string type, string key, string winnerPack, string loserPack, string winnerFile = null, string loserFile = null) { Type = type; Key = key; WinnerPack = winnerPack; LoserPack = loserPack; WinnerFile = winnerFile; LoserFile = loserFile; } } private static readonly List _entries = new List(); public static IReadOnlyList All => _entries; public static int Total => _entries.Count; public static void Clear() { _entries.Clear(); } public static void ClearTypes(params string[] types) { _entries.RemoveAll((Entry e) => Array.IndexOf(types, e.Type) >= 0); } public static void Record(string type, string key, string winnerPack, string loserPack, string sameSourceDetail = null, string winnerFile = null, string loserFile = null) { if (sameSourceDetail != null && string.Equals(winnerPack, loserPack, StringComparison.OrdinalIgnoreCase)) { key = key + " (" + sameSourceDetail + ")"; } _entries.Add(new Entry(type, key, winnerPack, loserPack, winnerFile, loserFile)); } public static int ShadowedCount(string packPath) { return _entries.Count((Entry e) => string.Equals(e.LoserPack, packPath, StringComparison.OrdinalIgnoreCase)); } } public static class FileCache { private static readonly Dictionary _cache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _inCycle; private static readonly HashSet _readThisCycle = new HashSet(StringComparer.OrdinalIgnoreCase); public static int Count => _cache.Count; public static long TotalBytes { get { long num = 0L; foreach (KeyValuePair item in _cache) { if (item.Value.Item2 != null) { num += item.Value.Item2.Length; } } return num; } } public static void BeginCycle() { _readThisCycle.Clear(); _inCycle = true; } public static void EndCycle() { _inCycle = false; } public static byte[] ReadBytes(string path) { try { if (_inCycle && _readThisCycle.Contains(path) && _cache.TryGetValue(path, out (DateTime, byte[]) value)) { return value.Item2; } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(path); if (_cache.TryGetValue(path, out (DateTime, byte[]) value2) && value2.Item1 == lastWriteTimeUtc) { if (_inCycle) { _readThisCycle.Add(path); } return value2.Item2; } byte[] array = File.ReadAllBytes(IOUtil.LongSafe(path)); _cache[path] = (lastWriteTimeUtc, array); if (_inCycle) { _readThisCycle.Add(path); } return array; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[FileCache] Could not read '" + path + "': " + ex.Message)); return null; } } public static void Clear() { _cache.Clear(); _readThisCycle.Clear(); } } public static class GameBridge { public static readonly bool IsHK1 = string.Equals(Paths.ProcessName, "hollow_knight", StringComparison.OrdinalIgnoreCase); private static bool _bridgeInit; private static Type _gmType; private static PropertyInfo _gmInstanceProp; private static MethodInfo _gmBeginSceneTransition; private static MethodInfo _gmGetSceneNameString; private static FieldInfo _gmEntryGateNameField; private static MethodInfo _gmIsGamePaused; private static MethodInfo _gmPauseGameToggle; private static MethodInfo _gmReturnToMenuNoSave; private static MethodInfo _gmDoEmergencyQuit; private static MethodInfo _gmReturnToMenuCoroutine; private static Type _gmRtmSaveModesType; private static Type _sliType; private static FieldInfo _sliSceneName; private static FieldInfo _sliEntryGateName; private static FieldInfo _sliIsFirstLevel; private static FieldInfo _sliAlwaysUnloadUnusedAssets; private static Type _hcType; private static PropertyInfo _hcInstanceProp; private static MethodInfo _hcHazardRespawn; private static FieldInfo _hcIsInvincible; private static Type _coreLoopType; private static MethodInfo _clInvokeNext; private static MethodInfo _clInvokeOnGameThread; private static readonly string[] MenuSceneTokens = new string[3] { "menu", "title", "mode_select" }; public static bool CanReloadScene { get { if (_gmInstanceProp != null && _gmBeginSceneTransition != null) { return _sliType != null; } return false; } } public static bool IsGameManagerLive { get { if (_gmInstanceProp == null) { return false; } try { return _gmInstanceProp.GetValue(null) != null; } catch { return false; } } } public static bool IsInMenuScene { get { //IL_0000: 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) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (string.IsNullOrEmpty(name)) { return false; } string[] menuSceneTokens = MenuSceneTokens; foreach (string value in menuSceneTokens) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } } public static bool IsInGameScene { get { if (IsGameManagerLive) { return !IsInMenuScene; } return false; } } public static bool CanHazardRespawn { get { if (_hcInstanceProp != null) { return _hcHazardRespawn != null; } return false; } } public static bool CanInvokeNext { get { if (_clInvokeNext != null) { return _clInvokeOnGameThread != null; } return false; } } public static void InitBridge() { if (_bridgeInit) { return; } _bridgeInit = true; _gmType = Type.GetType("GameManager, Assembly-CSharp"); if (_gmType != null) { _gmInstanceProp = _gmType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); _gmBeginSceneTransition = _gmType.GetMethod("BeginSceneTransition", BindingFlags.Instance | BindingFlags.Public); _gmGetSceneNameString = _gmType.GetMethod("GetSceneNameString", BindingFlags.Instance | BindingFlags.Public); _gmEntryGateNameField = _gmType.GetField("entryGateName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _gmIsGamePaused = _gmType.GetMethod("IsGamePaused", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); _gmPauseGameToggle = _gmType.GetMethod("PauseGameToggle", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(bool) }, null); _gmReturnToMenuNoSave = _gmType.GetMethod("ReturnToMainMenuNoSave", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); _gmDoEmergencyQuit = _gmType.GetMethod("DoEmergencyQuit", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); _gmRtmSaveModesType = _gmType.GetNestedType("ReturnToMainMenuSaveModes", BindingFlags.Public); if (_gmRtmSaveModesType != null) { _gmReturnToMenuCoroutine = _gmType.GetMethod("ReturnToMainMenu", BindingFlags.Instance | BindingFlags.Public, null, new Type[2] { _gmRtmSaveModesType, typeof(Action) }, null); } } _sliType = _gmType?.GetNestedType("SceneLoadInfo", BindingFlags.Public); if (_sliType != null) { _sliSceneName = _sliType.GetField("SceneName"); _sliEntryGateName = _sliType.GetField("EntryGateName"); _sliIsFirstLevel = _sliType.GetField("IsFirstLevelForPlayer"); _sliAlwaysUnloadUnusedAssets = _sliType.GetField("AlwaysUnloadUnusedAssets"); } _hcType = Type.GetType("HeroController, Assembly-CSharp"); if (_hcType != null) { _hcInstanceProp = _hcType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public); _hcHazardRespawn = _hcType.GetMethod("HazardRespawn", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); _hcIsInvincible = _hcType.GetField("isInvincible", BindingFlags.Instance | BindingFlags.Public); } _coreLoopType = Type.GetType("CoreLoop, Assembly-CSharp"); if (_coreLoopType != null) { _clInvokeNext = _coreLoopType.GetMethod("InvokeNext", BindingFlags.Static | BindingFlags.Public); _clInvokeOnGameThread = _coreLoopType.GetMethod("InvokeOnGameThread", BindingFlags.Static | BindingFlags.Public); } if (!CanReloadScene) { Plugin.Logger.LogWarning((object)("[GameBridge] Scene-reload handles did not fully resolve. " + $"gmType={_gmType != null}, instance={_gmInstanceProp != null}, " + $"begin={_gmBeginSceneTransition != null}, sli={_sliType != null}")); } if (!CanHazardRespawn) { Plugin.Logger.LogWarning((object)("[GameBridge] HazardRespawn handles did not fully resolve. " + $"hcType={_hcType != null}, instance={_hcInstanceProp != null}, " + $"hazard={_hcHazardRespawn != null}")); } if (!CanInvokeNext) { Plugin.Logger.LogWarning((object)("[GameBridge] CoreLoop handles did not fully resolve. " + $"clType={_coreLoopType != null}, invokeNext={_clInvokeNext != null}, " + $"invokeOnGameThread={_clInvokeOnGameThread != null}. " + "InvokeNext / InvokeOnGameThread will fall back to inline execution.")); } } public static bool ReturnToMainMenuNoSave() { object obj = _gmInstanceProp?.GetValue(null); if (obj == null) { Plugin.Logger.LogWarning((object)"[GameBridge] ReturnToMainMenuNoSave: GameManager.instance is null."); return false; } if (_gmReturnToMenuNoSave != null) { _gmReturnToMenuNoSave.Invoke(obj, null); Plugin.Logger.LogInfo((object)"[GameBridge] ReturnToMainMenuNoSave dispatched."); return true; } if (_gmDoEmergencyQuit != null) { _gmDoEmergencyQuit.Invoke(obj, null); Plugin.Logger.LogWarning((object)"[GameBridge] ReturnToMainMenuNoSave handle missing — dispatched DoEmergencyQuit instead."); return true; } if (_gmReturnToMenuCoroutine != null && _gmRtmSaveModesType != null) { MonoBehaviour val = (MonoBehaviour)((obj is MonoBehaviour) ? obj : null); if (val != null && Array.IndexOf(Enum.GetNames(_gmRtmSaveModesType), "DontSave") >= 0) { object obj2 = Enum.Parse(_gmRtmSaveModesType, "DontSave"); if (_gmReturnToMenuCoroutine.Invoke(obj, new object[2] { obj2, null }) is IEnumerator enumerator) { val.StartCoroutine(enumerator); Plugin.Logger.LogInfo((object)"[GameBridge] ReturnToMainMenu(DontSave) coroutine dispatched (HK1 shape)."); return true; } } } Plugin.Logger.LogWarning((object)"[GameBridge] No main-menu return handle resolved."); return false; } public static bool SetHeroInvincible(bool on) { if (_hcIsInvincible == null || _hcInstanceProp == null) { return false; } object value = _hcInstanceProp.GetValue(null); if (value == null) { return false; } _hcIsInvincible.SetValue(value, on); return true; } public static bool TriggerSceneReloadCurrent() { if (!CanReloadScene) { Plugin.Logger.LogWarning((object)"[GameBridge] TriggerSceneReloadCurrent: bridge unavailable, skipping."); return false; } object value = _gmInstanceProp.GetValue(null); if (value == null) { Plugin.Logger.LogWarning((object)"[GameBridge] TriggerSceneReloadCurrent: GameManager.instance is null."); return false; } if (_gmIsGamePaused != null) { bool flag = false; try { flag = (bool)_gmIsGamePaused.Invoke(value, null); } catch (Exception) { } if (flag) { Time.timeScale = 1f; Plugin.Logger.LogInfo((object)"[GameBridge] Game was paused — forced Time.timeScale = 1 so the scene-transition coroutine can tick. Dispatching PauseGameToggle(false) for UI/hero/audio cleanup."); if (_gmPauseGameToggle != null) { MonoBehaviour val = (MonoBehaviour)((value is MonoBehaviour) ? value : null); if (val != null) { try { IEnumerator enumerator = (IEnumerator)_gmPauseGameToggle.Invoke(value, new object[1] { false }); if (enumerator != null) { val.StartCoroutine(enumerator); } } catch (Exception ex2) { Plugin.Logger.LogWarning((object)($"[GameBridge] PauseGameToggle(false) threw: {(ex2 as TargetInvocationException)?.InnerException ?? ex2}. " + "Proceeding with timeScale override only — scene reload will clean up residual pause UI.")); } } } } } string text = ((_gmGetSceneNameString != null) ? ((string)_gmGetSceneNameString.Invoke(value, null)) : null); string text2 = ((_gmEntryGateNameField != null) ? ((string)_gmEntryGateNameField.GetValue(value)) : null); if (string.IsNullOrEmpty(text)) { Plugin.Logger.LogWarning((object)"[GameBridge] TriggerSceneReloadCurrent: scene name is empty, aborting."); return false; } object obj; try { obj = Activator.CreateInstance(_sliType); } catch (Exception ex3) { Plugin.Logger.LogWarning((object)$"[GameBridge] Could not construct SceneLoadInfo: {(ex3 as TargetInvocationException)?.InnerException ?? ex3}"); return false; } _sliSceneName?.SetValue(obj, text); _sliEntryGateName?.SetValue(obj, text2 ?? ""); _sliIsFirstLevel?.SetValue(obj, false); _sliAlwaysUnloadUnusedAssets?.SetValue(obj, true); try { _gmBeginSceneTransition.Invoke(value, new object[1] { obj }); Plugin.Logger.LogInfo((object)("[GameBridge] Scene reload dispatched: '" + text + "' via gate '" + (text2 ?? string.Empty) + "'")); return true; } catch (Exception ex4) { Plugin.Logger.LogWarning((object)$"[GameBridge] BeginSceneTransition threw: {(ex4 as TargetInvocationException)?.InnerException ?? ex4}"); return false; } } public static void InvokeNext(Action action) { if (action == null) { return; } if (_clInvokeNext == null) { action(); return; } try { _clInvokeNext.Invoke(null, new object[1] { action }); } catch (Exception ex) { Plugin.Logger.LogWarning((object)$"[GameBridge] CoreLoop.InvokeNext threw: {(ex as TargetInvocationException)?.InnerException ?? ex}. Running inline."); action(); } } public static void InvokeOnGameThread(Action action) { if (action == null) { return; } if (_clInvokeOnGameThread == null) { action(); return; } try { _clInvokeOnGameThread.Invoke(null, new object[1] { action }); } catch (Exception ex) { Plugin.Logger.LogWarning((object)$"[GameBridge] CoreLoop.InvokeOnGameThread threw: {(ex as TargetInvocationException)?.InnerException ?? ex}. Running inline."); action(); } } public static bool TryTriggerHazardRespawn() { if (!CanHazardRespawn) { Plugin.Logger.LogWarning((object)"[GameBridge] TryTriggerHazardRespawn: bridge unavailable."); return false; } object value = _hcInstanceProp.GetValue(null); MonoBehaviour val = (MonoBehaviour)((value is MonoBehaviour) ? value : null); if (val == null) { Plugin.Logger.LogWarning((object)"[GameBridge] HeroController.instance is null or not a MonoBehaviour."); return false; } IEnumerator enumerator; try { enumerator = (IEnumerator)_hcHazardRespawn.Invoke(value, null); } catch (Exception ex) { Plugin.Logger.LogWarning((object)$"[GameBridge] HazardRespawn threw: {(ex as TargetInvocationException)?.InnerException ?? ex}"); return false; } if (enumerator == null) { return false; } val.StartCoroutine(enumerator); return true; } } public static class GCUtil { private static bool _bridgeInit; private static MethodInfo _miMonoUsed; private static MethodInfo _miMonoTotal; private static MethodInfo _miMemUsed; private static MethodInfo _miMemTotal; private static PropertyInfo _piThreshold; private static MethodInfo _piThresholdSetter; private static MethodInfo _miGmCollect; private static MethodInfo _miGmForceCollect; private static MethodInfo _miAmpIncrease; private static MethodInfo _miAmpDecrease; private static MethodInfo _miAmpClear; private static PropertyInfo _piAmpAllocated; private static PropertyInfo _piAmpFreeBytes; private static double _lastBumpMB; public static long TCMonoHeapUsed { get { if (!(_miMonoUsed != null)) { return GC.GetTotalMemory(forceFullCollection: false); } return InvokeLong(_miMonoUsed); } } public static long TCMonoHeapTotal => InvokeLong(_miMonoTotal); public static long TCMemUsed => InvokeLong(_miMemUsed); public static long TCMemTotal => InvokeLong(_miMemTotal); public static double TCHeapThresholdMB { get { if (!(_piThreshold != null)) { return 0.0; } return (double)_piThreshold.GetValue(null); } } public static bool BridgeAvailable => _miMonoUsed != null; public static bool IsIncrementalMode => GarbageCollector.isIncremental; public static double HeapPressure { get { double tCHeapThresholdMB = TCHeapThresholdMB; if (tCHeapThresholdMB <= 0.0) { return 0.0; } return (double)TCMonoHeapUsed / 1048576.0 / tCHeapThresholdMB; } } public static bool AmpBridgeAvailable => _miAmpIncrease != null; public static long AmpAllocatedBytes { get { if (!(_piAmpAllocated != null)) { return 0L; } return (long)_piAmpAllocated.GetValue(null); } } public static long AmpFreeBytes { get { if (!(_piAmpFreeBytes != null)) { return 0L; } return (long)_piAmpFreeBytes.GetValue(null); } } public static event Action OnTCGCStutter; public static void InitBridge() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (_bridgeInit) { return; } _bridgeInit = true; Type type = Type.GetType("GCManager, Assembly-CSharp"); Plugin.Logger.LogInfo((object)("[GCUtil] GC fingerprint: " + $"isIncremental={GarbageCollector.isIncremental}, " + $"GCMode={GarbageCollector.GCMode}" + (GarbageCollector.isIncremental ? $", timeSliceNs={GarbageCollector.incrementalTimeSliceNanoseconds}" : "") + $", gcManagerInstances={((type != null) ? Resources.FindObjectsOfTypeAll(type).Length : (-1))}")); int num = Plugin.Config?.IncrementalGCTimeSliceMs ?? 0; if (num > 0) { if (GarbageCollector.isIncremental) { GarbageCollector.incrementalTimeSliceNanoseconds = (ulong)num * 1000000uL; Plugin.Logger.LogInfo((object)$"[GCUtil] Incremental GC time slice set to {num} ms (config override)"); } else if (BootConfigGC.LastResult != BootConfigGC.Result.KeyWritten) { Plugin.Logger.LogWarning((object)"[GCUtil] IncrementalGCTimeSliceMs is set but incremental GC is NOT active. boot.config has likely lost its gc-max-time-slice line (Steam verify-files restores boot.config). Re-add the line — or leave AutoEnableIncrementalGC on and it self-heals next launch."); } } if (type == null) { return; } _miMonoUsed = type.GetMethod("GetMonoHeapUsage", BindingFlags.Static | BindingFlags.Public); _miMonoTotal = type.GetMethod("GetMonoHeapTotal", BindingFlags.Static | BindingFlags.Public); _miMemUsed = type.GetMethod("GetMemoryUsage", BindingFlags.Static | BindingFlags.Public); _miMemTotal = type.GetMethod("GetMemoryTotal", BindingFlags.Static | BindingFlags.Public); _piThreshold = type.GetProperty("HeapUsageThreshold", BindingFlags.Static | BindingFlags.Public); _miGmCollect = type.GetMethod("Collect", BindingFlags.Static | BindingFlags.Public, null, Type.EmptyTypes, null); _miGmForceCollect = type.GetMethod("ForceCollect", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(bool), typeof(bool) }, null); FieldInfo field = type.GetField("OnGCStutter", BindingFlags.Static | BindingFlags.NonPublic); if (field != null) { Action b = delegate { GCUtil.OnTCGCStutter?.Invoke(); }; Action a = (Action)field.GetValue(null); field.SetValue(null, Delegate.Combine(a, b)); } _piThresholdSetter = _piThreshold?.GetSetMethod(nonPublic: true); Type type2 = Type.GetType("GCArtificialMemoryPressure, Assembly-CSharp"); if (type2 != null) { _miAmpIncrease = type2.GetMethod("IncreaseGCPressure", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(float) }, null); _miAmpDecrease = type2.GetMethod("DecreaseGCPressure", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(float) }, null); _miAmpClear = type2.GetMethod("ClearAllocatedMemory", BindingFlags.Static | BindingFlags.Public); _piAmpAllocated = type2.GetProperty("AllocatedBytes", BindingFlags.Static | BindingFlags.Public); _piAmpFreeBytes = type2.GetProperty("FreeBytesCount", BindingFlags.Static | BindingFlags.Public); } BumpThresholdForStitchwork(); } public static void BumpThresholdForStitchwork() { if (_piThresholdSetter == null || IsIncrementalMode) { return; } StitchworkConfig config = Plugin.Config; if (config != null && config.DisableHeapThresholdBump) { if (_lastBumpMB != 0.0) { double tCHeapThresholdMB = TCHeapThresholdMB; double num = tCHeapThresholdMB - _lastBumpMB; _piThresholdSetter.Invoke(null, new object[1] { num }); Log.Verbose("[GCUtil] DisableHeapThresholdBump=true — reverting our bump: " + $"{tCHeapThresholdMB:F0}MB → {num:F0}MB"); _lastBumpMB = 0.0; } return; } double num2 = (double)(T2DLoader.OriginalTextureDataBytes + FileCache.TotalBytes) / 1048576.0; double num3 = num2 - _lastBumpMB; if (!(Math.Abs(num3) < 1.0)) { double tCHeapThresholdMB2 = TCHeapThresholdMB; double num4 = tCHeapThresholdMB2 + num3; _piThresholdSetter.Invoke(null, new object[1] { num4 }); Log.Verbose("[GCUtil] TC heap threshold " + ((num3 > 0.0) ? "raised" : "lowered") + " " + $"{tCHeapThresholdMB2:F0}MB → {num4:F0}MB (Stitchwork caches: {num2:F0}MB)"); _lastBumpMB = num2; } } private static long InvokeLong(MethodInfo mi) { if (!(mi != null)) { return -1L; } return (long)mi.Invoke(null, null); } public static void PrewarmHeap(long bytes) { if (bytes > 0) { long num = bytes; while (num > 0) { int num2 = (int)Math.Min(num, 536870912L); GC.KeepAlive(new byte[num2]); num -= num2; } ForceCollect(); } } public static int SuggestReserveMB() { int systemMemorySize = SystemInfo.systemMemorySize; return (int)Math.Clamp(Math.Clamp(0.12102111566341002 * (double)systemMemorySize, 384.0, 1024.0) * 0.4, 64.0, 512.0); } public static void ForceCollect() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (_miGmCollect != null) { _miGmCollect.Invoke(null, null); return; } Mode gCMode = GarbageCollector.GCMode; GarbageCollector.GCMode = (Mode)1; GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true); GC.WaitForPendingFinalizers(); GarbageCollector.GCMode = gCMode; } public static void IncreasePressure(float t) { if (!(_miAmpIncrease == null)) { _miAmpIncrease.Invoke(null, new object[1] { Mathf.Clamp01(t) }); } } public static void DecreasePressure(float t) { if (!(_miAmpDecrease == null)) { _miAmpDecrease.Invoke(null, new object[1] { Mathf.Clamp01(t) }); } } public static void ClearPressure() { if (!(_miAmpClear == null)) { _miAmpClear.Invoke(null, null); } } } internal static class GifEncoder { private const int TransparentIndex = 255; private const int PaletteSize = 255; public static void Write(string path, int width, int height, List frames, int delayCs) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) if (frames == null || frames.Count == 0) { throw new ArgumentException("no frames"); } List list = BuildPalette(frames); Dictionary cache = new Dictionary(); using FileStream output = new FileStream(IOUtil.LongSafe(path), FileMode.Create, FileAccess.Write); using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(Encoding.ASCII.GetBytes("GIF89a")); binaryWriter.Write((ushort)width); binaryWriter.Write((ushort)height); binaryWriter.Write((byte)247); binaryWriter.Write(byte.MaxValue); binaryWriter.Write((byte)0); for (int i = 0; i < 256; i++) { Color32 val = (Color32)((i < list.Count) ? list[i] : new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue)); binaryWriter.Write(val.r); binaryWriter.Write(val.g); binaryWriter.Write(val.b); } binaryWriter.Write(new byte[3] { 33, 255, 11 }); binaryWriter.Write(Encoding.ASCII.GetBytes("NETSCAPE2.0")); binaryWriter.Write(new byte[5] { 3, 1, 0, 0, 0 }); byte[] array = new byte[width * height]; foreach (Color32[] frame in frames) { binaryWriter.Write(new byte[4] { 33, 249, 4, 9 }); binaryWriter.Write((ushort)Mathf.Max(2, delayCs)); binaryWriter.Write(byte.MaxValue); binaryWriter.Write((byte)0); binaryWriter.Write((byte)44); binaryWriter.Write((ushort)0); binaryWriter.Write((ushort)0); binaryWriter.Write((ushort)width); binaryWriter.Write((ushort)height); binaryWriter.Write((byte)0); int num = 0; for (int num2 = height - 1; num2 >= 0; num2--) { int num3 = num2 * width; for (int j = 0; j < width; j++) { Color32 val2 = frame[num3 + j]; array[num++] = ((val2.a < 128) ? byte.MaxValue : Nearest(val2, list, cache)); } } binaryWriter.Write((byte)8); LzwEncode(array, binaryWriter); binaryWriter.Write((byte)0); } binaryWriter.Write((byte)59); } private static List BuildPalette(List frames) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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_0122: Unknown result type (might be due to invalid IL or missing references) //IL_045f: Unknown result type (might be due to invalid IL or missing references) Dictionary dictionary = new Dictionary(); foreach (Color32[] frame in frames) { foreach (Color32 val in frame) { if (val.a >= 128) { int key = (val.r >> 2 << 12) | (val.g >> 2 << 6) | (val.b >> 2); dictionary.TryGetValue(key, out var value); dictionary[key] = value + 1; } } } List<(int, int, int, int)> list = new List<(int, int, int, int)>(dictionary.Count); foreach (KeyValuePair item in dictionary) { list.Add((((item.Key >> 12) & 0x3F) << 2, ((item.Key >> 6) & 0x3F) << 2, (item.Key & 0x3F) << 2, item.Value)); } if (list.Count == 0) { return new List { new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue) }; } List> list2 = new List> { list }; while (list2.Count < 255) { int num = -1; int num2 = 0; int bestCh = 0; for (int j = 0; j < list2.Count; j++) { List<(int, int, int, int)> list3 = list2[j]; if (list3.Count < 2) { continue; } int num3 = 255; int num4 = 0; int num5 = 255; int num6 = 0; int num7 = 255; int num8 = 0; foreach (var item2 in list3) { if (item2.Item1 < num3) { (num3, _, _, _) = item2; } if (item2.Item1 > num4) { (num4, _, _, _) = item2; } if (item2.Item2 < num5) { num5 = item2.Item2; } if (item2.Item2 > num6) { num6 = item2.Item2; } if (item2.Item3 < num7) { num7 = item2.Item3; } if (item2.Item3 > num8) { num8 = item2.Item3; } } int num9 = num4 - num3; int num10 = num6 - num5; int val2 = num8 - num7; int num11 = Math.Max(num9, Math.Max(num10, val2)); if (num11 > num2) { num2 = num11; num = j; bestCh = ((num11 != num9) ? ((num11 == num10) ? 1 : 2) : 0); } } if (num < 0) { break; } List<(int, int, int, int)> list4 = list2[num]; list4.Sort(((int r, int g, int b, int n) a, (int r, int g, int b, int n) c) => (bestCh != 0) ? ((bestCh != 1) ? a.b.CompareTo(c.b) : a.g.CompareTo(c.g)) : a.r.CompareTo(c.r)); long num12 = 0L; foreach (var item3 in list4) { num12 += item3.Item4; } long num13 = 0L; int num14; for (num14 = 0; num14 < list4.Count - 1; num14++) { num13 += list4[num14].Item4; if (num13 * 2 >= num12) { num14++; break; } } if (num14 <= 0 || num14 >= list4.Count) { num14 = list4.Count / 2; } list2[num] = list4.GetRange(0, num14); list2.Add(list4.GetRange(num14, list4.Count - num14)); } List list5 = new List(list2.Count); foreach (List<(int, int, int, int)> item4 in list2) { long num15 = 0L; long num16 = 0L; long num17 = 0L; long num18 = 0L; foreach (var item5 in item4) { num15 += (long)item5.Item1 * (long)item5.Item4; num16 += (long)item5.Item2 * (long)item5.Item4; num17 += (long)item5.Item3 * (long)item5.Item4; num18 += item5.Item4; } if (num18 != 0L) { list5.Add(new Color32((byte)(num15 / num18), (byte)(num16 / num18), (byte)(num17 / num18), byte.MaxValue)); } } return list5; } private static byte Nearest(Color32 c, List palette, Dictionary cache) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: 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_0071: Unknown result type (might be due to invalid IL or missing references) int key = (c.r >> 2 << 12) | (c.g >> 2 << 6) | (c.b >> 2); if (cache.TryGetValue(key, out var value)) { return value; } int num = 0; int num2 = int.MaxValue; for (int i = 0; i < palette.Count; i++) { int num3 = palette[i].r - c.r; int num4 = palette[i].g - c.g; int num5 = palette[i].b - c.b; int num6 = num3 * num3 + num4 * num4 + num5 * num5; if (num6 < num2) { num2 = num6; num = i; } } cache[key] = (byte)num; return (byte)num; } private static void LzwEncode(byte[] data, BinaryWriter w) { List block = new List(4096); int bitBuf = 0; int bitCnt = 0; Dictionary dictionary = new Dictionary(); int num = 258; int num2 = 9; Emit(256, num2); int num3 = ((data.Length != 0) ? data[0] : 0); for (int i = 1; i < data.Length; i++) { int num4 = data[i]; int key = (num3 << 8) | num4; if (dictionary.TryGetValue(key, out var value)) { num3 = value; continue; } Emit(num3, num2); if (num < 4096) { dictionary[key] = num++; if (num - 1 == 1 << num2 && num2 < 12) { num2++; } } else { Emit(256, num2); dictionary.Clear(); num = 258; num2 = 9; } num3 = num4; } Emit(num3, num2); Emit(257, num2); if (bitCnt > 0) { block.Add((byte)(bitBuf & 0xFF)); } if (block.Count > 0) { w.Write((byte)block.Count); w.Write(block.ToArray()); } void Emit(int code, int size) { bitBuf |= code << bitCnt; bitCnt += size; while (bitCnt >= 8) { block.Add((byte)(bitBuf & 0xFF)); bitBuf >>= 8; bitCnt -= 8; if (block.Count == 255) { w.Write(byte.MaxValue); w.Write(block.ToArray()); block.Clear(); } } } } } public static class HealthCheck { public enum Status { Ok, Warn, Info } public readonly struct Item { public readonly string Name; public readonly Status Status; public readonly string Detail; public Item(string name, Status status, string detail) { Name = name; Status = status; Detail = detail; } } private static bool _bootLogged; public static string CompiledAgainstGameVersion { get; } = ReadPinSuffix(); private static string ReadPinSuffix() { foreach (AssemblyMetadataAttribute customAttribute in typeof(HealthCheck).Assembly.GetCustomAttributes()) { if (!(customAttribute.Key != "GameLibsPin") && !string.IsNullOrEmpty(customAttribute.Value)) { int num = customAttribute.Value.IndexOf("silksong", StringComparison.OrdinalIgnoreCase); return (num >= 0) ? customAttribute.Value.Substring(num + "silksong".Length) : customAttribute.Value; } } return null; } public static List Run() { //IL_02c2: Unknown result type (might be due to invalid IL or missing references) List list = new List(); string compiledAgainstGameVersion = CompiledAgainstGameVersion; string version = Application.version; if (string.IsNullOrEmpty(compiledAgainstGameVersion)) { list.Add(new Item("GameLibs pin", Status.Info, "pin metadata absent from this build")); } else if (string.IsNullOrEmpty(version)) { list.Add(new Item("GameLibs pin", Status.Info, "built vs " + compiledAgainstGameVersion + "; game version unreadable")); } else { version = version.Trim(); compiledAgainstGameVersion = compiledAgainstGameVersion.Trim(); if (string.Equals(version, compiledAgainstGameVersion, StringComparison.OrdinalIgnoreCase)) { list.Add(new Item("GameLibs pin", Status.Ok, "built vs " + compiledAgainstGameVersion + " = game " + version)); } else { int[] array = SplitNumeric(version); int[] array2 = SplitNumeric(compiledAgainstGameVersion); if (array == null || array2 == null) { list.Add(new Item("GameLibs pin", Status.Info, "built vs " + compiledAgainstGameVersion + "; game reports '" + version + "' — formats differ, compare manually")); } else { int num = Math.Min(array.Length, array2.Length); bool flag = true; for (int i = 0; i < num; i++) { if (array[i] != array2[i]) { flag = false; break; } } list.Add(flag ? new Item("GameLibs pin", Status.Ok, $"built vs {compiledAgainstGameVersion}, game {version} (matches to {num} segment(s))") : new Item("GameLibs pin", Status.Warn, "built vs " + compiledAgainstGameVersion + ", game is " + version + " — API drift possible; a newer Stitchwork build may exist")); } } } Harmony harmonyInstance = Plugin.HarmonyInstance; if (harmonyInstance == null) { list.Add(new Item("Harmony patches", Status.Warn, "no Harmony instance recorded — Awake did not complete")); } else { int num2 = harmonyInstance.GetPatchedMethods().Count(); list.Add((num2 >= 10) ? new Item("Harmony patches", Status.Ok, $"{num2} methods patched") : new Item("Harmony patches", Status.Warn, $"only {num2} methods patched — boot likely aborted early (check the log for exceptions)")); } list.Add(GCUtil.BridgeAvailable ? new Item("TC GCManager bridge", Status.Ok, "resolved") : new Item("TC GCManager bridge", Status.Warn, "unresolved — heap-threshold bump inactive; heavy packs may trigger TC collect stutter")); list.Add(GameBridge.CanReloadScene ? new Item("GameBridge (scene reload)", Status.Ok, "resolved") : new Item("GameBridge (scene reload)", Status.Warn, "unresolved — pack Apply falls back to in-place reload (no scene refresh)")); list.Add(GUIHelper.InputBlockingAvailable ? new Item("InputSystem blocking", Status.Ok, "resolved") : new Item("InputSystem blocking", Status.Warn, "unresolved — typing in Stitchwork text fields may leak game hotkeys")); if (!TexUtil.RotateDrawAvailable) { list.Add(new Item("Rotate draw", Status.Warn, "no opaque blit shader resolvable (Hidden/BlitCopy AND Sprites/Default missing) — rotated composites inactive. This should not be possible; please report.")); } else if (TexUtil.RotateShaderUnsupported) { list.Add(new Item("Rotate draw", Status.Ok, $"GL path active. (Legacy bundle shader unsupported on {SystemInfo.graphicsDeviceType} — " + "no longer needed; Probe C runs without its comparator here.)")); } else { list.Add(new Item("Rotate draw", Status.Ok, TexUtil.RotateShaderAvailable ? "GL path active; legacy shader loaded as the self-test comparator." : "GL path active; legacy bundle shader absent (comparator-only role).")); } int num3 = 0; try { if (Directory.Exists(Loc.LangDir)) { num3 = Directory.GetFiles(Loc.LangDir, "*.yml").Length; } } catch { } list.Add((num3 > 0) ? new Item("Lang tables", Status.Ok, $"{num3} file(s)") : new Item("Lang tables", Status.Info, "Lang/ missing or empty — UI localization unavailable (English only)")); try { string path = Plugin.BasePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string directoryName = Path.GetDirectoryName(path); string text = (string.IsNullOrEmpty(directoryName) ? null : Path.Combine(directoryName, "Patchwork")); if (text != null && string.Equals(Path.GetFullPath(text), Path.GetFullPath(path), StringComparison.OrdinalIgnoreCase)) { text = null; } if (text != null && Directory.Exists(text) && !File.Exists(Path.Combine(text, "Patchwork.dll"))) { int num4 = 0; string[] contentRoots = PackLayout.ContentRoots; foreach (string path2 in contentRoots) { if (Directory.Exists(Path.Combine(text, path2))) { num4++; } } if (num4 > 0) { list.Add(new Item("Patchwork leftovers", Status.Warn, $"{num4} content folder(s) in plugins/Patchwork/ (upstream's old working " + "folder) — Stitchwork does NOT read that location. Move them into the Stitchwork/ folder next to Stitchwork.dll.")); } } } catch { } int length = Plugin.BasePath.Length; if (Path.DirectorySeparatorChar != '\\') { list.Add(new Item("Install path depth", Status.Ok, $"{length} chars — no MAX_PATH limit on this platform")); } else if (length + 110 > 260) { list.Add(new Item("Install path depth", Status.Warn, $"install root is {length} chars deep — the longest asset paths will exceed " + "Windows' 260-char limit. Stitchwork shims its own file access (long-path prefix), but image editors and Explorer may still fail on workspace files. Remedy: remove extra folder layers inside the plugin folder, or enable Windows long paths (LongPathsEnabled).")); } else { list.Add(new Item("Install path depth", Status.Ok, $"{length} chars — headroom OK")); } return list; } private static int[] SplitNumeric(string v) { string[] array = v.Split('.'); int[] array2 = new int[array.Length]; for (int i = 0; i < array.Length; i++) { if (!int.TryParse(array[i], out array2[i])) { return null; } } return array2; } public static void LogBootSummaryOnce() { if (_bootLogged) { return; } _bootLogged = true; List source = Run(); int num = source.Count((Item i) => i.Status == Status.Ok); int num2 = source.Count((Item i) => i.Status == Status.Info); List list = source.Where((Item i) => i.Status == Status.Warn).ToList(); if (list.Count == 0) { Plugin.Logger.LogInfo((object)string.Format("[Health] {0} OK{1} — all checks green.", num, (num2 > 0) ? $", {num2} info" : "")); return; } Plugin.Logger.LogWarning((object)$"[Health] {num} OK, {list.Count} WARN:"); foreach (Item item in list) { Plugin.Logger.LogWarning((object)("[Health] " + item.Name + ": " + item.Detail)); } } } public static class IOUtil { private static bool _longPathLogged; public static string LongSafe(string path) { if (string.IsNullOrEmpty(path) || path.Length < 240) { return path; } if (Path.DirectorySeparatorChar != '\\') { return path; } if (path.StartsWith("\\\\?\\", StringComparison.Ordinal)) { return path; } string fullPath = Path.GetFullPath(path); if (fullPath.Length < 248) { return fullPath; } if (!_longPathLogged) { _longPathLogged = true; Plugin.Logger.LogInfo((object)($"[IO] Long-path shim engaged ({fullPath.Length} chars, Windows MAX_PATH is 260) — using \\\\?\\ form. " + "If file operations still fail, the install is nested too deep (mod-manager profile paths): see the Health strip / wiki troubleshooting.")); } return "\\\\?\\" + fullPath; } public static void EnsureDirectoryExists(string path) { path = LongSafe(path); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } public static void OpenPath(string path) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (string.IsNullOrEmpty(path)) { return; } try { RuntimePlatform platform = Application.platform; if ((int)platform > 1) { if ((int)platform == 13 || (int)platform == 16) { Process.Start("xdg-open", "\"" + path + "\""); } else { Process.Start(path); } } else { Process.Start("open", "\"" + path + "\""); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[IO] Could not open '" + path + "': " + ex.Message)); } } public static void RevealPath(string filePath) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (string.IsNullOrEmpty(filePath)) { return; } try { RuntimePlatform platform = Application.platform; if ((int)platform > 1) { if ((int)platform == 13 || (int)platform == 16) { OpenPath(Path.GetDirectoryName(filePath)); } else { Process.Start("explorer.exe", "/select,\"" + filePath + "\""); } } else { Process.Start("open", "-R \"" + filePath + "\""); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[IO] Could not reveal '" + filePath + "': " + ex.Message)); } } public static void OpenDeepestExisting(string path) { string text = path; while (!string.IsNullOrEmpty(text) && !Directory.Exists(text)) { text = Path.GetDirectoryName(text); } if (!string.IsNullOrEmpty(text)) { OpenPath(text); } } public static (int width, int height) ReadPngDimensions(string path) { try { using FileStream fileStream = File.OpenRead(path); if (fileStream.Length < 24) { return (width: 0, height: 0); } fileStream.Seek(16L, SeekOrigin.Begin); byte[] array = new byte[8]; if (fileStream.Read(array, 0, 8) < 8) { return (width: 0, height: 0); } int item = (array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]; int item2 = (array[4] << 24) | (array[5] << 16) | (array[6] << 8) | array[7]; return (width: item, height: item2); } catch (Exception) { return (width: 0, height: 0); } } public static bool TryReadPngDimensions(byte[] data, out int width, out int height) { width = (height = 0); if (data == null || data.Length < 24) { return false; } if (data[0] != 137 || data[1] != 80 || data[2] != 78 || data[3] != 71 || data[4] != 13 || data[5] != 10 || data[6] != 26 || data[7] != 10) { return false; } width = (data[16] << 24) | (data[17] << 16) | (data[18] << 8) | data[19]; height = (data[20] << 24) | (data[21] << 16) | (data[22] << 8) | data[23]; if (width > 0) { return height > 0; } return false; } public static (string name, string anchor) ParseAnchorTag(string baseName) { if (string.IsNullOrEmpty(baseName)) { return (name: baseName, anchor: null); } int num = baseName.LastIndexOf('@'); if (num < 0 || num == baseName.Length - 1) { return (name: baseName, anchor: null); } string text = baseName.Substring(num + 1); if (text.Length == 1) { string text2 = char.ToLowerInvariant(text[0]) switch { 't' => "top-center", 'b' => "bottom-center", 'l' => "left-center", 'r' => "right-center", 'c' => "center", _ => null, }; if (text2 == null) { return (name: baseName, anchor: null); } return (name: baseName.Substring(0, num), anchor: text2); } if (text[0] == 'p' || text[0] == 'P') { int num2 = text.IndexOf('_'); if (num2 > 1 && num2 < text.Length - 1 && int.TryParse(text.Substring(1, num2 - 1), out var result) && int.TryParse(text.Substring(num2 + 1), out var result2)) { return (name: baseName.Substring(0, num), anchor: $"pivot:{result}:{result2}"); } } return (name: baseName, anchor: null); } public static bool TryParsePivotAnchor(string anchor, out int px, out int py) { px = (py = 0); if (string.IsNullOrEmpty(anchor) || !anchor.StartsWith("pivot:", StringComparison.Ordinal)) { return false; } string[] array = anchor.Split(':'); if (array.Length == 3 && int.TryParse(array[1], out px)) { return int.TryParse(array[2], out py); } return false; } public static bool BenignDuplicate(string a, string b) { if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) { return false; } if (!string.Equals(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)) { return false; } try { FileInfo fileInfo = new FileInfo(LongSafe(a)); FileInfo fileInfo2 = new FileInfo(LongSafe(b)); if (!fileInfo.Exists || !fileInfo2.Exists || fileInfo.Length != fileInfo2.Length) { return false; } } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } catch (ArgumentException) { return false; } catch (NotSupportedException) { return false; } byte[] array = FileCache.ReadBytes(a); byte[] array2 = FileCache.ReadBytes(b); if (array != null && array2 != null) { return array.AsSpan().SequenceEqual(array2); } return false; } public static bool FileMatchesBytes(string path, byte[] bytes) { if (string.IsNullOrEmpty(path) || bytes == null) { return false; } try { FileInfo fileInfo = new FileInfo(LongSafe(path)); if (!fileInfo.Exists || fileInfo.Length != bytes.Length) { return false; } } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } catch (ArgumentException) { return false; } catch (NotSupportedException) { return false; } return FileCache.ReadBytes(path)?.AsSpan().SequenceEqual(bytes) ?? false; } } public static class Loc { private static Dictionary _table; private static string _activeCode = "en"; private static bool _init; private static readonly HashSet _seenKeys = new HashSet(StringComparer.Ordinal); private static readonly List> _glossary = new List>(); public static int Generation { get; private set; } public static string ActiveCode => _activeCode; public static string LangDir => Path.Combine(Plugin.BasePath, "Lang"); public static IReadOnlyList> Glossary => _glossary; public static void SelfExtractEmbeddedTables() { Assembly assembly = typeof(Loc).Assembly; string[] manifestResourceNames = assembly.GetManifestResourceNames(); foreach (string text in manifestResourceNames) { if (!text.StartsWith("Stitchwork.Lang.", StringComparison.Ordinal)) { continue; } string text2 = text.Substring("Stitchwork.Lang.".Length); string path = Path.Combine(LangDir, text2); if (File.Exists(path)) { continue; } try { IOUtil.EnsureDirectoryExists(LangDir); using Stream stream = assembly.GetManifestResourceStream(text); using FileStream destination = File.Create(path); stream.CopyTo(destination); Plugin.Logger.LogInfo((object)("[Loc] Extracted embedded " + text2 + " → Lang/ (was missing).")); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Loc] Could not extract embedded " + text2 + ": " + ex.Message)); } } } public static string[] DiscoverLanguages() { List list = new List { "auto", "en" }; try { if (Directory.Exists(LangDir)) { string[] files = Directory.GetFiles(LangDir, "*.yml"); for (int i = 0; i < files.Length; i++) { string text = Path.GetFileNameWithoutExtension(files[i]).ToLowerInvariant(); if (!text.EndsWith("-glossary", StringComparison.Ordinal) && !list.Contains(text)) { list.Add(text); } } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Loc] Language discovery failed: " + ex.Message)); } return list.ToArray(); } public static string NativeName(string code) { return code switch { "auto" => T("Auto"), "en" => "English", "zh" => "中文", _ => code, }; } public static void Init() { if (_init) { return; } _init = true; Reload(); ConfigEntry val = Plugin.Config?.UILanguageEntry; if (val != null) { val.SettingChanged += delegate { Reload(); }; } } private static string ResolveAutoCode() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 string currentLangCode = DialogueHandler.CurrentLangCode; if (!string.IsNullOrEmpty(currentLangCode) && currentLangCode != "EN") { return currentLangCode.ToLowerInvariant(); } SystemLanguage systemLanguage = Application.systemLanguage; if ((int)systemLanguage <= 15) { if ((int)systemLanguage == 6) { return "zh"; } if ((int)systemLanguage == 15) { return "de"; } } else { if ((int)systemLanguage == 40) { return "zh"; } if ((int)systemLanguage == 41) { return "zh"; } } return (currentLangCode ?? "EN").ToLowerInvariant(); } public static void OnGameLanguageChanged() { if (_init && string.Equals(Plugin.Config?.UILanguage, "auto", StringComparison.OrdinalIgnoreCase)) { Reload(); } } public static void CycleLanguage() { string[] array = DiscoverLanguages(); string cur = Plugin.Config?.UILanguage ?? "auto"; int num = Array.FindIndex(array, (string l) => string.Equals(l, cur, StringComparison.OrdinalIgnoreCase)); string uILanguage = array[(num + 1 + array.Length) % array.Length]; if (Plugin.Config != null) { Plugin.Config.UILanguage = uILanguage; } } private static void Reload() { string text = Plugin.Config?.UILanguage ?? "auto"; string text2 = (string.Equals(text, "auto", StringComparison.OrdinalIgnoreCase) ? ResolveAutoCode() : text.ToLowerInvariant()); Dictionary dictionary = null; if (text2 != "en") { string path = Path.Combine(LangDir, text2 + ".yml"); if (File.Exists(path)) { dictionary = ParseFile(path); } } _table = dictionary; _activeCode = ((dictionary != null) ? text2 : "en"); _glossary.Clear(); if (_activeCode != "en") { string path2 = Path.Combine(LangDir, _activeCode + "-glossary.yml"); if (File.Exists(path2)) { foreach (KeyValuePair item in ParseFileEntries(path2)) { _glossary.Add(item); } } } Generation++; Plugin.Logger.LogInfo((object)("[Loc] UI language: " + _activeCode + ((dictionary != null) ? $" ({dictionary.Count} strings)" : " (built-in English)") + ((_glossary.Count > 0) ? $", glossary: {_glossary.Count} terms" : "") + " — setting='" + text + "'")); } public static string T(string key, string englishDefault = null) { if (Plugin.Config != null && Plugin.Config.DevMode) { _seenKeys.Add(key); } Dictionary table = _table; if (table != null && table.TryGetValue(key, out var value)) { return value; } return englishDefault ?? key; } public static void DumpKeysReport() { if (_seenKeys.Count == 0) { return; } try { IOUtil.EnsureDirectoryExists(LangDir); string text = Path.Combine(LangDir, "_keys-seen.txt"); List list = new List(_seenKeys); list.Sort(StringComparer.Ordinal); File.WriteAllLines(text, list); Plugin.Logger.LogInfo((object)$"[Loc] Key report: {list.Count} key(s) → {text}"); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Loc] Key report failed: " + ex.Message)); } } private static Dictionary ParseFile(string path) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair item in ParseFileEntries(path)) { dictionary[item.Key] = item.Value; } return dictionary; } private static List> ParseFileEntries(string path) { List> list = new List>(); int num = 0; string[] array = File.ReadAllLines(path); foreach (string obj in array) { num++; string text = obj.Trim(); if (text.Length == 0 || text[0] == '#') { continue; } if (!TryReadQuoted(text, 0, out var result, out var end)) { Warn(path, num, "expected quoted key"); continue; } int j; for (j = end; j < text.Length && text[j] == ' '; j++) { } if (j >= text.Length || text[j] != '=') { Warn(path, num, "expected '=' after key"); continue; } for (j++; j < text.Length && text[j] == ' '; j++) { } if (!TryReadQuoted(text, j, out var result2, out var _)) { Warn(path, num, "expected quoted value"); } else { list.Add(new KeyValuePair(result, result2)); } } return list; } private static void Warn(string path, int line, string what) { Plugin.Logger.LogWarning((object)$"[Loc] {Path.GetFileName(path)}:{line} — {what}; line skipped"); } private static bool TryReadQuoted(string line, int start, out string result, out int end) { result = null; end = start; if (start >= line.Length || line[start] != '"') { return false; } StringBuilder stringBuilder = new StringBuilder(); for (int i = start + 1; i < line.Length; i++) { char c = line[i]; if (c == '\\' && i + 1 < line.Length) { char c2 = line[++i]; StringBuilder stringBuilder2 = stringBuilder; stringBuilder2.Append(c2 switch { 'n' => '\n', 't' => '\t', '"' => '"', '\\' => '\\', _ => c2, }); } else { if (c == '"') { result = stringBuilder.ToString(); end = i + 1; return true; } stringBuilder.Append(c); } } return false; } } public static class Log { public static void Verbose(string message) { StitchworkConfig config = Plugin.Config; if (config != null && config.VerboseLogging) { Plugin.Logger.LogInfo((object)message); } else { Plugin.Logger.LogDebug((object)message); } } public static void Info(string message) { Plugin.Logger.LogInfo((object)message); } public static void Warn(string message) { Plugin.Logger.LogWarning((object)message); } public static void Error(string message) { Plugin.Logger.LogError((object)message); } } internal static class LogGate { private sealed class CountingListener : ILogListener, IDisposable { internal int Count; public void LogEvent(object sender, LogEventArgs eventArgs) { Count++; } public void Dispose() { } } private const float HardDeadlineSec = 300f; private const float PrepareGraceSec = 0.5f; private const float NeverStartedSec = 5f; private static readonly List _detached = new List(); private static CountingListener _counter; private static VideoPlayer _watching; private static string _what; private static bool _sawPlaying; private static float _openedAt; private static float _deadline; internal static bool IsSuppressed => _detached.Count > 0; internal static void SuppressForCinematic(VideoPlayer player, string what) { if (Plugin.Config == null || !Plugin.Config.SuppressLogDuringCinematics) { return; } _watching = player; _what = (string.IsNullOrEmpty(what) ? "(unnamed)" : what); _sawPlaying = false; _deadline = Time.realtimeSinceStartup + 300f; if (IsSuppressed) { return; } Plugin.Logger.LogInfo((object)("[LogGate] Cinematic '" + _what + "' starting — pausing log file writes (SuppressLogDuringCinematics). Log resumes when playback ends.")); foreach (ILogListener item in new List(Logger.Listeners)) { if (item != null && ((object)item).GetType().Name.Contains("Disk")) { Logger.Listeners.Remove(item); _detached.Add(item); } } if (_detached.Count == 0) { Plugin.Logger.LogInfo((object)"[LogGate] No disk log listener found — nothing to pause."); return; } _counter = new CountingListener(); Logger.Listeners.Add((ILogListener)(object)_counter); _openedAt = Time.realtimeSinceStartup; } internal static void Tick() { if (!IsSuppressed) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup >= _deadline) { Restore("deadline elapsed"); } else if (!(realtimeSinceStartup - _openedAt < 0.5f)) { if ((Object)(object)_watching == (Object)null) { Restore("video player gone"); } else if (_watching.isPlaying) { _sawPlaying = true; } else if (_sawPlaying) { Restore("playback ended"); } else if (realtimeSinceStartup - _openedAt > 5f) { Restore("playback never started"); } } } internal static void Restore(string reason) { if (!IsSuppressed) { return; } int num = ((_counter != null) ? _counter.Count : 0); float num2 = Time.realtimeSinceStartup - _openedAt; if (_counter != null) { Logger.Listeners.Remove((ILogListener)(object)_counter); _counter = null; } foreach (ILogListener item in _detached) { Logger.Listeners.Add(item); } _detached.Clear(); _watching = null; _sawPlaying = false; string arg = ((num2 > 0.01f) ? $", ≈{(float)num / num2:F0}/s" : ""); Plugin.Logger.LogInfo((object)($"[LogGate] Cinematic '{_what}' done ({reason}) — log resumed after {num2:F1} s; " + $"{num} line(s) not written{arg}.")); _what = null; } } internal static class PipelineSelfTest { public static string LastSummary { get; private set; } = ""; public static void Run() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) ManualLogSource logger = Plugin.Logger; StringBuilder stringBuilder = new StringBuilder(); logger.LogInfo((object)($"[SelfTest] project colorspace={QualitySettings.activeColorSpace}, " + $"MatchOriginalColorSpace={Plugin.Config.MatchOriginalColorSpace}")); Texture2D val = FindProbeTexture(); if ((Object)(object)val == (Object)null) { logger.LogWarning((object)"[SelfTest] A: no probe texture found (no tk2d material / Sprite loaded?)"); stringBuilder.Append("A: no probe · "); } else { int num = Mathf.Min(1024, Mathf.Max(((Texture)val).width, ((Texture)val).height)); Color32[] a = ReadVia((Texture)(object)val, num, null); Color32[] b = ReadVia((Texture)(object)val, num, (RenderTextureReadWrite)1); Color32[] b2 = ReadVia((Texture)(object)val, num, (RenderTextureReadWrite)2); (int, double, int) tuple = Diff(a, b); (int, double, int) tuple2 = Diff(a, b2); logger.LogInfo((object)($"[SelfTest] A: probe '{((Object)val).name}' {((Texture)val).width}x{((Texture)val).height} " + $"(format {((Texture)val).graphicsFormat}), sampled at {num}²")); logger.LogInfo((object)$"[SelfTest] A: Linear intermediate: maxΔ={tuple.Item1} meanΔ={tuple.Item2:F3} diffPx={tuple.Item3}"); logger.LogInfo((object)$"[SelfTest] A: sRGB intermediate: maxΔ={tuple2.Item1} meanΔ={tuple2.Item2:F3} diffPx={tuple2.Item3}"); string text = ((tuple.Item1 <= 1 && tuple2.Item1 <= 1) ? "both RT modes appearance-preserving — RT class exonerated for the hue report" : ((tuple.Item1 <= 1) ? "Linear (current default) preserves; sRGB alters — current default CORRECT" : ((tuple2.Item1 <= 1) ? "Linear (current default) ALTERS appearance; sRGB preserves — hue-shift root cause; MatchOriginalColorSpace=true is the mitigation" : "NEITHER mode lossless — deeper format issue, report these numbers"))); logger.LogInfo((object)("[SelfTest] A verdict: " + text)); stringBuilder.Append($"A: lin Δ{tuple.Item1} srgb Δ{tuple2.Item1} · "); } List<(string, string, RenderTexture, RenderTexture)> compositePairsForDiagnostics = SpriteLoader.GetCompositePairsForDiagnostics(); if (compositePairsForDiagnostics.Count == 0) { logger.LogInfo((object)"[SelfTest] B: no composited atlases live (nothing to compare)."); stringBuilder.Append("B: no composites"); } else { int num2 = 0; int num3 = 0; int num4 = 0; for (int i = 0; i < compositePairsForDiagnostics.Count && i < 16; i++) { (string, string, RenderTexture, RenderTexture) tuple3 = compositePairsForDiagnostics[i]; string item = tuple3.Item1; string item2 = tuple3.Item2; RenderTexture item3 = tuple3.Item3; RenderTexture item4 = tuple3.Item4; int num5 = ((i == 0) ? Mathf.Min(1024, Mathf.Max(((Texture)item3).width, ((Texture)item3).height)) : 256); Color32[] array = ReadVia((Texture)(object)item3, num5, null); Color32[] b3 = ReadVia((Texture)(object)item4, num5, null); (int, double, int) tuple4 = Diff(array, b3); num4++; if (tuple4.Item1 <= 1) { num2++; continue; } num3++; logger.LogInfo((object)("[SelfTest] B: '" + item + "/" + item2 + "' differs from vanilla backup: " + $"maxΔ={tuple4.Item1} meanΔ={tuple4.Item2:F3} diffPx={tuple4.Item3}/{array.Length} @{num5}² " + "(expected IF replacements are active on it; damage if none are)")); } logger.LogInfo((object)($"[SelfTest] B: {compositePairsForDiagnostics.Count} composited atlas(es), {num4} compared " + $"({num2} identical to vanilla, {num3} differing" + ((compositePairsForDiagnostics.Count > 16) ? $", {compositePairsForDiagnostics.Count - 16} skipped for memory" : "") + ").")); logger.LogInfo((object)"[SelfTest] B: to make this an assertion, run with all packs/workspace files disabled (any differing atlas = pipeline damage) or with one pristine-dump null replacement (differences must be confined to it)."); stringBuilder.Append($"B: {num2}/{num4} identical, {num3} differ"); } stringBuilder.Append(" · "); stringBuilder.Append(RotateParityProbe(logger)); stringBuilder.Append(" · "); stringBuilder.Append(RoundTripProbe(logger)); LastSummary = stringBuilder.ToString(); logger.LogInfo((object)"[SelfTest] done."); } private static int RoundTrip(Color32[] regionPx, int w, int h, FlipMode flip) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(w, h, (TextureFormat)4, false) { filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; val.SetPixels32(regionPx); val.Apply(false, false); Texture2D val2 = null; Texture2D val3 = null; RenderTexture temporary = RenderTexture.GetTemporary(w, h, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); RenderTexture active = RenderTexture.active; bool flag = false; try { val2 = SpriteDumper.RotateToScreen(val, flip); RenderTexture.active = temporary; GL.Clear(true, true, Color.clear); GL.PushMatrix(); flag = true; GL.LoadPixelMatrix(0f, (float)w, (float)h, 0f); var (uB, vB) = SpriteUtil.CompositeBasisFor(flip); TexUtil.DrawRotated(new Rect(0f, 0f, (float)w, (float)h), (Texture)(object)val2, uB, vB); GL.PopMatrix(); flag = false; val3 = new Texture2D(w, h, (TextureFormat)4, false); val3.ReadPixels(new Rect(0f, 0f, (float)w, (float)h), 0, 0); val3.Apply(false, false); Color32[] pixels = val3.GetPixels32(); int num = 0; for (int i = 0; i < pixels.Length; i++) { if (pixels[i].r != regionPx[i].r || pixels[i].g != regionPx[i].g || pixels[i].b != regionPx[i].b || pixels[i].a != regionPx[i].a) { num++; } } return num; } finally { if (flag) { GL.PopMatrix(); } RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)val); if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)val3); } } } private static string RoundTripProbe(ManualLogSource log) { //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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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_021b: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Unknown result type (might be due to invalid IL or missing references) //IL_039a: Expected O, but got Unknown //IL_03c3: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_0424: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Unknown result type (might be due to invalid IL or missing references) if (!TexUtil.RotateDrawAvailable) { return "D: NO BLIT MAT"; } FlipMode[] array = (FlipMode[])(object)new FlipMode[3] { default(FlipMode), (FlipMode)1, (FlipMode)2 }; Color32[] array2 = (Color32[])(object)new Color32[24]; for (int i = 0; i < array2.Length; i++) { array2[i] = new Color32((byte)(i * 5 + 7), (byte)(251 - i * 9), (byte)(i * 11 + 2), byte.MaxValue); } StringBuilder stringBuilder = new StringBuilder("D:"); FlipMode[] array3 = array; foreach (FlipMode val in array3) { int num = RoundTrip(array2, 6, 4, val); bool flag = num == 0; log.LogInfo((object)($"[SelfTest] D {ModeTag(val)} synthetic {6}x{4}: " + (flag ? "PASS" : "FAIL") + (flag ? "" : $" ({num}px differ)"))); stringBuilder.Append(" " + ModeTag(val) + " " + (flag ? "✓" : "✗")); } Dictionary dictionary = new Dictionary(); tk2dSpriteCollectionData[] array4 = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val2 in array4) { if (dictionary.Count == array.Length) { break; } if ((Object)(object)val2 == (Object)null || val2.spriteDefinitions == null || (((Object)val2).name != null && ((Object)val2).name.StartsWith("Stitchwork_Shadow"))) { continue; } tk2dSpriteDefinition[] spriteDefinitions = val2.spriteDefinitions; foreach (tk2dSpriteDefinition val3 in spriteDefinitions) { if (!string.IsNullOrEmpty(val3.name) && !dictionary.ContainsKey(val3.flipped) && val3.uvs != null && val3.uvs.Length >= 4) { dictionary[val3.flipped] = (val2, val3); if (dictionary.Count == array.Length) { break; } } } } array3 = array; foreach (FlipMode val4 in array3) { if (!dictionary.TryGetValue(val4, out var value)) { log.LogInfo((object)("[SelfTest] D " + ModeTag(val4) + " live: no def loaded (run in a scene that has one)")); continue; } if (!SpriteLoader.TryGetVanilla(value.Item1, value.Item2, out var atlas, out var uvs, out var _)) { Material[] materials = value.Item1.materials; Material val5 = ((materials != null && value.Item2.materialId >= 0 && value.Item2.materialId < materials.Length) ? materials[value.Item2.materialId] : null); atlas = (((Object)(object)val5 != (Object)null) ? val5.mainTexture : null); } if ((Object)(object)atlas == (Object)null) { log.LogInfo((object)("[SelfTest] D " + ModeTag(val4) + " live: def '" + value.Item2.name + "' has no atlas — skipped")); continue; } Rect spriteRect = SpriteUtil.GetSpriteRect(uvs, atlas); int num2 = (int)((Rect)(ref spriteRect)).width; int num3 = (int)((Rect)(ref spriteRect)).height; if (num2 <= 0 || num3 <= 0) { log.LogInfo((object)("[SelfTest] D " + ModeTag(val4) + " live: def '" + value.Item2.name + "' zero rect — skipped")); continue; } RenderTexture active = RenderTexture.active; RenderTexture val6 = null; Texture2D val7 = new Texture2D(num2, num3, (TextureFormat)4, false); Color32[] pixels; try { RenderTexture val8 = (RenderTexture)(object)((atlas is RenderTexture) ? atlas : null); if ((Object)(object)val8 == (Object)null) { val6 = TexUtil.GetReadable(atlas); val8 = val6; } RenderTexture.active = val8; val7.ReadPixels(spriteRect, 0, 0); val7.Apply(false, false); pixels = val7.GetPixels32(); } finally { RenderTexture.active = active; Object.Destroy((Object)(object)val7); if ((Object)(object)val6 != (Object)null) { RenderTexture.ReleaseTemporary(val6); } } int num4 = RoundTrip(pixels, num2, num3, val4); bool flag2 = num4 == 0; log.LogInfo((object)($"[SelfTest] D {ModeTag(val4)} live '{((Object)value.Item1).name}/{value.Item2.name}' {num2}x{num3}: " + (flag2 ? "PASS" : "FAIL") + (flag2 ? "" : $" ({num4}px differ)"))); stringBuilder.Append(" " + ModeTag(val4) + "(live) " + (flag2 ? "✓" : "✗")); } return stringBuilder.ToString(); } private static string ModeTag(FlipMode m) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)m != 1) { if ((int)m == 2) { return "tpk"; } return "id"; } return "tk2d"; } private static string RotateParityProbe(ManualLogSource log) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) if (!TexUtil.RotateDrawAvailable) { return "C: NO BLIT MAT"; } Texture2D src = new Texture2D(4, 4, (TextureFormat)4, false) { filterMode = (FilterMode)0, wrapMode = (TextureWrapMode)1 }; Color32[] array = (Color32[])(object)new Color32[16]; for (int i = 0; i < array.Length; i++) { array[i] = new Color32((byte)(i * 16 + 8), (byte)(255 - i * 16), (byte)(i * 7 + 3), byte.MaxValue); } src.SetPixels32(array); src.Apply(false, false); (string, Vector2, Vector2, bool)[] obj = new(string, Vector2, Vector2, bool)[3] { ("id", Vector2.right, Vector2.up, false), ("tk2d", Vector2.up, Vector2.left, true), ("tpk", Vector2.down, Vector2.right, true) }; StringBuilder stringBuilder = new StringBuilder("C:"); (string, Vector2, Vector2, bool)[] array2 = obj; for (int j = 0; j < array2.Length; j++) { (string, Vector2, Vector2, bool) tuple = array2[j]; string item = tuple.Item1; Vector2 uB = tuple.Item2; Vector2 vB = tuple.Item3; int outW = 4; int outH = 4; Color32[] array3 = Draw(viaShader: false); bool flag; string text; if (TexUtil.RotateShaderAvailable) { Color32[] array4 = Draw(viaShader: true); int num = 0; for (int k = 0; k < array3.Length; k++) { if (array3[k].r != array4[k].r || array3[k].g != array4[k].g || array3[k].b != array4[k].b || array3[k].a != array4[k].a) { num++; } } flag = num == 0; text = (flag ? "==shader" : $"{num}px≠shader"); } else if (item == "id") { int num2 = 0; for (int l = 0; l < array3.Length; l++) { if (array3[l].r != array[l].r || array3[l].g != array[l].g || array3[l].b != array[l].b) { num2++; } } flag = num2 == 0; text = (flag ? "==src" : $"{num2}px≠src"); } else { flag = true; text = "no comparator"; } log.LogInfo((object)("[SelfTest] C " + item + ": " + (flag ? "PASS" : "FAIL") + " (" + text + ")")); stringBuilder.Append(" " + item + " " + (flag ? "✓" : "✗")); Color32[] Draw(bool viaShader) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_00bd: 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) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) RenderTexture temporary = RenderTexture.GetTemporary(outW, outH, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); RenderTexture active = RenderTexture.active; Texture2D val = new Texture2D(outW, outH, (TextureFormat)4, false); try { RenderTexture.active = temporary; GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)((Texture)temporary).height, (float)((Texture)temporary).width, 0f); if (viaShader) { TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(uB.x, uB.y, vB.x, vB.y)); Graphics.DrawTexture(new Rect(0f, 0f, 4f, 4f), (Texture)(object)src, new Rect(0f, 0f, 1f, 1f), 0, 0, 0, 0, Color.white, TexUtil.RotateMaterial); } else { TexUtil.DrawRotated(new Rect(0f, 0f, 4f, 4f), (Texture)(object)src, uB, vB); } GL.PopMatrix(); val.ReadPixels(new Rect(0f, 0f, (float)outW, (float)outH), 0, 0); val.Apply(false, false); return val.GetPixels32(); } finally { RenderTexture.active = active; Object.Destroy((Object)(object)val); RenderTexture.ReleaseTemporary(temporary); } } } Object.Destroy((Object)(object)src); return stringBuilder.ToString(); } private static Color32[] ReadVia(Texture src, int outSize, RenderTextureReadWrite? intermediate) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Expected O, but got Unknown //IL_001d: Unknown result type (might be due to invalid IL or missing references) RenderTexture val = null; Texture val2 = src; if (intermediate.HasValue) { val = RenderTexture.GetTemporary(src.width, src.height, 0, (RenderTextureFormat)0, intermediate.Value); Graphics.Blit(src, val); val2 = (Texture)(object)val; } RenderTexture temporary = RenderTexture.GetTemporary(outSize, outSize, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Graphics.Blit(val2, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; Texture2D val3 = new Texture2D(outSize, outSize, (TextureFormat)4, false); val3.ReadPixels(new Rect(0f, 0f, (float)outSize, (float)outSize), 0, 0); val3.Apply(false); Color32[] pixels = val3.GetPixels32(); RenderTexture.active = active; Object.Destroy((Object)val3); RenderTexture.ReleaseTemporary(temporary); if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } return pixels; } private static (int Max, double Mean, int DiffPx) Diff(Color32[] a, Color32[] b) { int num = Mathf.Min(a.Length, b.Length); int num2 = 0; int num3 = 0; long num4 = 0L; for (int i = 0; i < num; i++) { int num5 = Mathf.Max(Mathf.Max(Mathf.Abs(a[i].r - b[i].r), Mathf.Abs(a[i].g - b[i].g)), Mathf.Max(Mathf.Abs(a[i].b - b[i].b), Mathf.Abs(a[i].a - b[i].a))); if (num5 > 0) { num3++; num4 += num5; if (num5 > num2) { num2 = num5; } } } return (Max: num2, Mean: (num > 0) ? ((double)num4 / (double)num) : 0.0, DiffPx: num3); } private static Texture2D FindProbeTexture() { tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if ((Object)(object)val == (Object)null || val.materials == null) { continue; } Material[] materials = val.materials; foreach (Material val2 in materials) { if ((Object)(object)val2 != (Object)null) { Texture mainTexture = val2.mainTexture; Texture2D val3 = (Texture2D)(object)((mainTexture is Texture2D) ? mainTexture : null); if (val3 != null && ((Texture)val3).width >= 64) { return val3; } } } } Sprite[] array2 = Resources.FindObjectsOfTypeAll(); foreach (Sprite val4 in array2) { if ((Object)(object)val4 != (Object)null && (Object)(object)val4.texture != (Object)null && ((Texture)val4.texture).width >= 64) { return val4.texture; } } return null; } } public static class PlayerDataCatalog { public readonly struct Entry { public readonly string Name; public readonly Type FieldType; public readonly string TypeLabel; public Entry(string name, Type type) { Name = name; FieldType = type; TypeLabel = ((type == typeof(bool)) ? "bool" : ((type == typeof(int)) ? "int" : ((type == typeof(float)) ? "float" : ((type == typeof(string)) ? "str" : type.Name)))); } } private static List _catalog; private static readonly Dictionary> _accessorCache = new Dictionary>(StringComparer.Ordinal); public static IReadOnlyList All { get { EnsureBuilt(); return _catalog; } } public static int Count { get { EnsureBuilt(); return _catalog.Count; } } public static IEnumerable Search(string query) { EnsureBuilt(); if (string.IsNullOrEmpty(query)) { return _catalog; } return SearchFiltered(query); } private static IEnumerable SearchFiltered(string query) { foreach (Entry item in _catalog) { if (item.Name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) { yield return item; } } } public static object GetValue(PlayerData pd, string name) { if (pd == null || string.IsNullOrEmpty(name)) { return null; } if (!_accessorCache.TryGetValue(name, out var value)) { value = BuildAccessor(name); _accessorCache[name] = value; } return value?.Invoke(pd); } private static Func BuildAccessor(string name) { Type typeFromHandle = typeof(PlayerData); MemberInfo member = (MemberInfo)(((object)typeFromHandle.GetField(name, BindingFlags.Instance | BindingFlags.Public)) ?? ((object)typeFromHandle.GetProperty(name, BindingFlags.Instance | BindingFlags.Public))); if (member == null) { return null; } try { ParameterExpression parameterExpression = Expression.Parameter(typeof(PlayerData), "pd"); return Expression.Lambda>(Expression.Convert((member is FieldInfo field) ? Expression.Field(parameterExpression, field) : Expression.Property(parameterExpression, (PropertyInfo)member), typeof(object)), new ParameterExpression[1] { parameterExpression }).Compile(); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[PlayerDataCatalog] Expression compile failed for '" + name + "': " + ex.Message + "; falling back to reflection.")); FieldInfo fi2 = member as FieldInfo; return ((object)fi2 != null) ? ((Func)((PlayerData pd) => fi2.GetValue(pd))) : ((Func)((PlayerData pd) => ((PropertyInfo)member).GetValue(pd))); } } private static void EnsureBuilt() { if (_catalog == null) { _catalog = Build(); } } private static List Build() { List list = new List(512); HashSet hashSet = new HashSet(StringComparer.Ordinal); Type typeFromHandle = typeof(PlayerData); HashSet hashSet2 = new HashSet { typeof(bool), typeof(int), typeof(float), typeof(string) }; FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (hashSet2.Contains(fieldInfo.FieldType) && hashSet.Add(fieldInfo.Name)) { list.Add(new Entry(fieldInfo.Name, fieldInfo.FieldType)); } } PropertyInfo[] properties = typeFromHandle.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.CanRead && hashSet2.Contains(propertyInfo.PropertyType) && hashSet.Add(propertyInfo.Name)) { list.Add(new Entry(propertyInfo.Name, propertyInfo.PropertyType)); } } list.Sort((Entry a, Entry b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); return list; } } internal static class RawKeyboardLeakBlocker { private static bool _patched; public static void ApplyPatches(Harmony harmony) { if (_patched) { return; } try { Type typeFromHandle = typeof(ButtonControl); PatchPropertyGetter(harmony, typeFromHandle, "isPressed", "BlockBoolPrefix"); PatchPropertyGetter(harmony, typeFromHandle, "wasPressedThisFrame", "BlockBoolPrefix"); PatchPropertyGetter(harmony, typeFromHandle, "wasReleasedThisFrame", "BlockBoolPrefix"); PatchMethod(harmony, typeFromHandle, "ReadValue", Type.EmptyTypes, "BlockFloatPrefix"); _patched = true; Plugin.Logger.LogInfo((object)"[RawKeyboardLeakBlocker] ButtonControl polling patches applied."); } catch (Exception arg) { Plugin.Logger.LogWarning((object)$"[RawKeyboardLeakBlocker] Failed to apply patches: {arg}"); } } private static void PatchPropertyGetter(Harmony harmony, Type targetType, string propertyName, string prefixName) { MethodInfo target = AccessTools.PropertyGetter(targetType, propertyName); PatchWithPrefix(harmony, target, prefixName, targetType.Name + "." + propertyName); } private static void PatchMethod(Harmony harmony, Type targetType, string methodName, Type[] args, string prefixName) { MethodInfo target = AccessTools.Method(targetType, methodName, args, (Type[])null); PatchWithPrefix(harmony, target, prefixName, targetType.Name + "." + methodName + "()"); } private static void PatchWithPrefix(Harmony harmony, MethodInfo target, string prefixName, string label) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown if (target == null) { Plugin.Logger.LogWarning((object)("[RawKeyboardLeakBlocker] Target not found: " + label)); return; } MethodInfo methodInfo = AccessTools.Method(typeof(RawKeyboardLeakBlocker), prefixName, (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Logger.LogWarning((object)("[RawKeyboardLeakBlocker] Prefix not found: " + prefixName)); return; } harmony.Patch((MethodBase)target, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Logger.LogInfo((object)("[RawKeyboardLeakBlocker] Patched " + label)); } private static bool ShouldBlock(ButtonControl __instance) { if (!GUIHelper.IsTextFieldFocused) { return false; } if (__instance == null) { return false; } return ((InputControl)__instance).device is Keyboard; } private static bool BlockBoolPrefix(ButtonControl __instance, ref bool __result) { if (!ShouldBlock(__instance)) { return true; } __result = false; return false; } private static bool BlockFloatPrefix(ButtonControl __instance, ref float __result) { if (!ShouldBlock(__instance)) { return true; } __result = 0f; return false; } } public static class SpriteUtil { public static Rect GetSpriteRect(tk2dSpriteDefinition def, Texture tex) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return GetSpriteRect(def.uvs, tex); } public static Rect GetSpriteRect(Vector2[] uvs, Texture tex) { //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_0043: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) float x = uvs[0].x; float x2 = uvs[0].x; float y = uvs[0].y; float y2 = uvs[0].y; for (int i = 1; i < uvs.Length; i++) { Vector2 val = uvs[i]; if (val.x < x) { x = val.x; } if (val.x > x2) { x2 = val.x; } if (val.y < y) { y = val.y; } if (val.y > y2) { y2 = val.y; } } int num = Mathf.FloorToInt(x * (float)tex.width + 0.25f); int num2 = Mathf.CeilToInt(x2 * (float)tex.width - 0.25f); int num3 = Mathf.FloorToInt(y * (float)tex.height + 0.25f); int num4 = Mathf.CeilToInt(y2 * (float)tex.height - 0.25f); int num5 = Mathf.Min(num2 - num, tex.width); int num6 = Mathf.Min(num4 - num3, tex.height); return new Rect((float)num, (float)num3, (float)num5, (float)num6); } public static (Vector2 uBasis, Vector2 vBasis) DumpBasisFor(FlipMode flip) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0006: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if ((int)flip != 1) { if ((int)flip == 2) { return (uBasis: Vector2.down, vBasis: Vector2.right); } return (uBasis: Vector2.right, vBasis: Vector2.up); } return (uBasis: Vector2.up, vBasis: Vector2.left); } public static (Vector2 uBasis, Vector2 vBasis) CompositeBasisFor(FlipMode flip) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0006: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if ((int)flip != 1) { if ((int)flip == 2) { return (uBasis: Vector2.up, vBasis: Vector2.left); } return (uBasis: Vector2.right, vBasis: Vector2.up); } return (uBasis: Vector2.down, vBasis: Vector2.right); } } public sealed class T2DSceneEntry { public readonly string CleanName; public readonly string RawName; public readonly Texture NativeTexture; public readonly int Width; public readonly int Height; public readonly bool IsT2D; public readonly List SpriteNames; public readonly bool HasSpritesheetOverride; public readonly int LoadedIndividualCount; public readonly int AtlasIndex; public readonly string DisplayName; public T2DSceneEntry(string cleanName, string rawName, Texture tex, bool isT2D, List spriteNames, bool hasOverride, int loadedCount, int atlasIndex, string displayName) { CleanName = cleanName; RawName = rawName; NativeTexture = tex; Width = (((Object)(object)tex != (Object)null) ? tex.width : 0); Height = (((Object)(object)tex != (Object)null) ? tex.height : 0); IsT2D = isT2D; SpriteNames = spriteNames ?? new List(); HasSpritesheetOverride = hasOverride; LoadedIndividualCount = loadedCount; AtlasIndex = atlasIndex; DisplayName = displayName ?? cleanName; } } public static class T2DUtil { private static readonly Dictionary _cleanNameCache = new Dictionary(); public static bool IsT2DTexture(string textureName) { if (!textureName.Contains("-BC7-")) { return textureName.Contains("DXT5|BC3-"); } return true; } public static string SpriteKey(string cleanTexName, string spriteName) { return cleanTexName + "/" + spriteName; } public static string SanitizeForFilesystem(string textureName) { return textureName.Replace("|", "_").Replace("/", "_").Replace("\\", "_") .Replace(":", "_"); } public static string CleanTextureName(string textureName) { if (_cleanNameCache.TryGetValue(textureName, out var value)) { return value; } string text = CleanTextureNameUncached(textureName); _cleanNameCache[textureName] = text; return text; } internal static void ClearCleanNameCache() { _cleanNameCache.Clear(); } public static int AtlasIndexFromRaw(string rawName) { if (string.IsNullOrEmpty(rawName)) { return -1; } if (!rawName.StartsWith("sactx-", StringComparison.Ordinal)) { return -1; } int num = rawName.IndexOf('-'); if (num < 0 || num == rawName.Length - 1) { return -1; } int num2 = rawName.IndexOf('-', num + 1); if (num2 < 0) { return -1; } if (!int.TryParse(rawName.Substring(num + 1, num2 - num - 1), out var result)) { return -1; } return result; } private static string CleanTextureNameUncached(string textureName) { string text = null; if (textureName.Contains("-BC7-")) { text = "-BC7-"; } else if (textureName.Contains("DXT5|BC3-")) { text = "DXT5|BC3-"; } else if (textureName.Contains("DXT5_BC3-")) { text = "DXT5_BC3-"; } if (text == null) { return textureName; } string text2 = textureName.Split(new string[1] { text }, StringSplitOptions.None)[1]; int num = text2.LastIndexOf('-'); if (num <= 0) { return text2; } return text2.Substring(0, num); } } public static class TexUtil { public static Material RotateMaterial; private static Material _rotateDrawMat; private static bool _rotateDrawResolved; public static bool RotateShaderAvailable => (Object)(object)RotateMaterial != (Object)null; public static bool RotateShaderUnsupported { get; private set; } public static bool RotateDrawAvailable { get { EnsureRotateDrawMat(); return (Object)(object)_rotateDrawMat != (Object)null; } } public static void Initialize() { //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Expected O, but got Unknown //IL_0143: Unknown result type (might be due to invalid IL or missing references) byte[] array = null; try { using Stream stream = typeof(TexUtil).Assembly.GetManifestResourceStream("Stitchwork.stitchwork.assetbundle"); if (stream != null) { array = new byte[stream.Length]; int i; int num; for (i = 0; i < array.Length; i += num) { num = stream.Read(array, i, array.Length - i); if (num <= 0) { break; } } if (i != array.Length) { Plugin.Logger.LogWarning((object)$"[TexUtil] Embedded assetbundle short read: {i}/{array.Length} bytes."); array = null; } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[TexUtil] Embedded assetbundle read failed: " + ex.Message)); } string text = Path.Combine(Plugin.BasePath, "stitchwork.assetbundle"); AssetBundle val = null; if (File.Exists(text)) { val = AssetBundle.LoadFromFile(text); } if ((Object)(object)val == (Object)null && array != null) { val = AssetBundle.LoadFromMemory(array); } if ((Object)(object)val == (Object)null) { Plugin.Logger.LogError((object)"[TexUtil] stitchwork.assetbundle could not be loaded (missing/corrupt file and no embedded copy). Stitchwork stays functional; rotated-sprite features are degraded (upright previews fall back to raw crops, animation-canvas extract unavailable)."); return; } Shader val2 = val.LoadAsset("Assets/Patchwork/Rotate.shader"); if ((Object)(object)val2 == (Object)null) { Plugin.Logger.LogError((object)"[TexUtil] Rotate.shader missing from the assetbundle — rotated-sprite features degraded."); } else if (!val2.isSupported) { RotateShaderUnsupported = true; Plugin.Logger.LogError((object)("[TexUtil] Rotate.shader loaded but is NOT SUPPORTED on this GPU / graphics API " + $"(device: {SystemInfo.graphicsDeviceType}). The assetbundle is platform-specific and was " + "built for a different target. Sprite replacement falls back to a plain textured draw: unrotated sprites are pixel-identical, ROTATED atlas sprites are skipped (they need the shader's basis transform). Rotated-sprite dev tools are unavailable this session.")); } else { RotateMaterial = new Material(val2); } } private static void EnsureRotateDrawMat() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown if (_rotateDrawResolved) { return; } _rotateDrawResolved = true; Shader val = Shader.Find("Hidden/BlitCopy"); if ((Object)(object)val == (Object)null) { val = Shader.Find("Sprites/Default"); if ((Object)(object)val != (Object)null) { Plugin.Logger.LogWarning((object)"[TexUtil] Hidden/BlitCopy not found — rotate draws fall back to Sprites/Default (alpha-blended, not opaque copy). Report this configuration."); } } if ((Object)(object)val == (Object)null) { Plugin.Logger.LogError((object)"[TexUtil] No blit shader resolvable — rotate draws unavailable."); return; } _rotateDrawMat = new Material(val) { hideFlags = (HideFlags)32 }; } public static void DrawRotated(Rect dest, Texture tex, Vector2 uB, Vector2 vB) { //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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) EnsureRotateDrawMat(); float uOff; float vOff; if (!((Object)(object)_rotateDrawMat == (Object)null)) { _rotateDrawMat.mainTexture = tex; _rotateDrawMat.SetPass(0); uOff = ((uB.x + uB.y < 0f) ? 1f : 0f); vOff = ((vB.x + vB.y < 0f) ? 1f : 0f); GL.Begin(7); Corner(((Rect)(ref dest)).xMin, ((Rect)(ref dest)).yMin, 0f, 1f); Corner(((Rect)(ref dest)).xMax, ((Rect)(ref dest)).yMin, 1f, 1f); Corner(((Rect)(ref dest)).xMax, ((Rect)(ref dest)).yMax, 1f, 0f); Corner(((Rect)(ref dest)).xMin, ((Rect)(ref dest)).yMax, 0f, 0f); GL.End(); } void Corner(float dx, float dy, float u, float v) { GL.TexCoord2(uB.x * u + uB.y * v + uOff, vB.x * u + vB.y * v + vOff); GL.Vertex3(dx, dy, 0f); } } public static bool SetPassTextured(Texture tex) { EnsureRotateDrawMat(); if ((Object)(object)_rotateDrawMat == (Object)null) { return false; } _rotateDrawMat.mainTexture = tex; _rotateDrawMat.SetPass(0); return true; } public static RenderTexture GetReadable(Texture tex) { RenderTexture temporary = RenderTexture.GetTemporary(tex.width, tex.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Graphics.Blit(tex, temporary); return temporary; } public static RenderTextureReadWrite ReadWriteForSource(Texture source) { if (Plugin.Config.MatchOriginalColorSpace) { if (IsSRGBSource(source)) { return (RenderTextureReadWrite)2; } return (RenderTextureReadWrite)1; } return (RenderTextureReadWrite)1; } public static bool IsSRGBSource(Texture source) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)source == (Object)null) { return false; } RenderTexture val = (RenderTexture)(object)((source is RenderTexture) ? source : null); if (val != null) { return val.sRGB; } Texture2D val2 = (Texture2D)(object)((source is Texture2D) ? source : null); if (val2 != null) { return ((object)((Texture)val2).graphicsFormat/*cast due to .constrained prefix*/).ToString().EndsWith("_SRGB", StringComparison.Ordinal); } return false; } public static Texture2D LoadFromPNG(string path) { byte[] array = FileCache.ReadBytes(path); if (array == null) { return null; } Texture2D obj = CreateTextureFromBytes(array); if ((Object)(object)obj == (Object)null) { Plugin.Logger.LogWarning((object)("LoadFromPNG: Failed to decode image data from '" + path + "'")); } return obj; } public static Texture2D CreateTextureFromBytes(byte[] pngData, bool markNonReadable = true) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown Texture2D val = new Texture2D(2, 2); if (!ImageConversion.LoadImage(val, pngData)) { Plugin.Logger.LogWarning((object)"CreateTextureFromBytes: Failed to load image data"); Object.Destroy((Object)(object)val); return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; val.Apply(false, markNonReadable); ((Object)val).hideFlags = (HideFlags)32; return val; } } } namespace Stitchwork.Packs { public enum ConditionType { Scene, SceneContains, PackActive, CrestEquipped, NailUpgrade, PlayerData } public enum LogicJoin { Or, And } public enum ReloadTrigger { OnSceneTransition, HotReload } public class PackCondition { public static readonly (string Id, string DisplayName)[] KnownCrests = new(string, string)[9] { ("Hunter", "Hunter"), ("Reaper", "Reaper"), ("Wanderer", "Wanderer"), ("Spell", "Shaman"), ("Toolmaster", "Architect"), ("Warrior", "Beast"), ("Witch", "Witch"), ("Cursed", "Cursed"), ("Cloakless", "Cloakless") }; public static readonly (string Value, string DisplayName)[] KnownNailLevels = new(string, string)[9] { ("pale", "Pale Steel Needle"), ("hivesteel", "Hivesteel Needle"), ("shining", "Shining Needle"), ("sharpened", "Sharpened Needle"), ("needle", "Needle"), (">=pale", "≥ Pale Steel Needle"), (">=hivesteel", "≥ Hivesteel Needle"), (">=shining", "≥ Shining Needle"), (">=sharpened", "≥ Sharpened Needle") }; public ConditionType Type { get; set; } public string Value { get; set; } = ""; public bool Negate { get; set; } public LogicJoin JoinNext { get; set; } public string TypeLabel => LabelFor(Type); public bool Evaluate(string sceneName, IReadOnlyList allPacks) { HeroController instance = HeroController.instance; PlayerData val = (((Object)(object)instance == (Object)null) ? null : instance.playerData); bool flag = Type switch { ConditionType.Scene => string.Equals(sceneName, Value, StringComparison.OrdinalIgnoreCase), ConditionType.SceneContains => sceneName.IndexOf(Value, StringComparison.OrdinalIgnoreCase) >= 0, ConditionType.PackActive => allPacks.Any((PackInfo p) => p.IsEnabled && string.Equals(p.Name, Value, StringComparison.OrdinalIgnoreCase)), ConditionType.CrestEquipped => CrestMatches(val?.CurrentCrestID, Value), ConditionType.NailUpgrade => NailUpgradeMatches(val?.nailUpgrades ?? 0, Value), ConditionType.PlayerData => EvaluatePlayerData(val, Value), _ => true, }; if (!Negate) { return flag; } return !flag; } private static bool NailUpgradeMatches(int actual, string value) { if (string.IsNullOrEmpty(value)) { return false; } string text = value.Trim(); string text2 = "=="; if (text.StartsWith(">=")) { text2 = ">="; text = text.Substring(2); } else if (text.StartsWith("<=")) { text2 = "<="; text = text.Substring(2); } else if (text.StartsWith(">")) { text2 = ">"; text = text.Substring(1); } else if (text.StartsWith("<")) { text2 = "<"; text = text.Substring(1); } else if (text.StartsWith("==")) { text2 = "=="; text = text.Substring(2); } int num = ParseNailLevel(text.Trim()); if (num < 0) { return false; } return text2 switch { ">=" => actual >= num, "<=" => actual <= num, ">" => actual > num, "<" => actual < num, _ => actual == num, }; } private static bool EvaluatePlayerData(PlayerData pd, string expr) { if (pd == null || string.IsNullOrEmpty(expr)) { return false; } if (!TryParseFieldExpr(expr, out var fieldName, out var op, out var target)) { return false; } object value = PlayerDataCatalog.GetValue(pd, fieldName); if (value == null) { return false; } if (op == null) { if (value is bool) { return (bool)value; } if (value is int num) { return num != 0; } if (value is float num2) { return num2 != 0f; } if (value is string value2) { return !string.IsNullOrEmpty(value2); } return false; } return CompareValue(value, op, target); } private static bool TryParseFieldExpr(string expr, out string fieldName, out string op, out string target) { fieldName = expr.Trim(); op = null; target = null; string[] array = new string[6] { ">=", "<=", "!=", ">", "<", "==" }; foreach (string text in array) { int num = expr.IndexOf(text, StringComparison.Ordinal); if (num >= 0) { fieldName = expr.Substring(0, num).Trim(); op = text; target = expr.Substring(num + text.Length).Trim(); return true; } } return false; } private static bool CompareValue(object val, string op, string target) { if (val is bool flag) { bool flag2 = target.Equals("true", StringComparison.OrdinalIgnoreCase) || target == "1"; if (!(op == "==")) { if (op == "!=") { return flag != flag2; } return flag == flag2; } return flag == flag2; } if (val is int num) { if (!int.TryParse(target, out var result)) { return false; } return op switch { ">=" => num >= result, "<=" => num <= result, "!=" => num != result, ">" => num > result, "<" => num < result, _ => num == result, }; } if (val is float num2) { if (!float.TryParse(target, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2)) { return false; } return op switch { ">=" => num2 >= result2, "<=" => num2 <= result2, "!=" => num2 != result2, ">" => num2 > result2, "<" => num2 < result2, _ => num2 == result2, }; } if (val is string strA) { int num3 = string.Compare(strA, target, StringComparison.OrdinalIgnoreCase); return op switch { ">=" => num3 >= 0, "<=" => num3 <= 0, "!=" => num3 != 0, ">" => num3 > 0, "<" => num3 < 0, _ => num3 == 0, }; } return false; } private static int ParseNailLevel(string s) { switch (s.ToLowerInvariant()) { case "0": case "needle": return 0; case "1": case "sharpened": return 1; case "2": case "shining": return 2; case "3": case "hivesteel": return 3; case "4": case "pale": return 4; default: { int result; return (int.TryParse(s, out result) && result >= 0) ? result : (-1); } } } private static bool CrestMatches(string currentID, string value) { if (currentID == null || string.IsNullOrEmpty(value)) { return false; } if (string.Equals(currentID, value, StringComparison.OrdinalIgnoreCase)) { return true; } return currentID.StartsWith(value + "_", StringComparison.OrdinalIgnoreCase); } public PackCondition Clone() { return new PackCondition { Type = Type, Value = Value, Negate = Negate, JoinNext = JoinNext }; } public static string CrestDisplayName(string id) { if (string.IsNullOrEmpty(id)) { return "—"; } (string, string)[] knownCrests = KnownCrests; for (int i = 0; i < knownCrests.Length; i++) { var (a, result) = knownCrests[i]; if (string.Equals(a, id, StringComparison.OrdinalIgnoreCase)) { return result; } } return id; } public static string NailDisplayValue(string value) { if (string.IsNullOrEmpty(value)) { return "—"; } (string, string)[] knownNailLevels = KnownNailLevels; for (int i = 0; i < knownNailLevels.Length; i++) { var (a, result) = knownNailLevels[i]; if (string.Equals(a, value, StringComparison.OrdinalIgnoreCase)) { return result; } } return value; } public static string LabelFor(ConditionType type) { return type switch { ConditionType.Scene => "scene", ConditionType.SceneContains => "scene~", ConditionType.PackActive => "pack", ConditionType.CrestEquipped => "crest", ConditionType.NailUpgrade => "nail", ConditionType.PlayerData => "pd", _ => type.ToString(), }; } public static bool IsMidSceneType(ConditionType type) { if (type != ConditionType.CrestEquipped && type != ConditionType.NailUpgrade) { return type == ConditionType.PlayerData; } return true; } public string Serialize() { string text = ((JoinNext == LogicJoin.And) ? "and" : "or"); return $"{TypeLabel}|{(Negate ? 1 : 0)}|{text}|{Value}"; } public static PackCondition TryDeserialize(string token) { string[] array = token.Split('|'); if (array.Length < 3) { return null; } ConditionType type = array[0] switch { "scene~" => ConditionType.SceneContains, "pack" => ConditionType.PackActive, "crest" => ConditionType.CrestEquipped, "nail" => ConditionType.NailUpgrade, "pd" => ConditionType.PlayerData, _ => ConditionType.Scene, }; bool negate = array[1] == "1"; LogicJoin joinNext = LogicJoin.Or; string value; if (array.Length >= 4 && (array[2] == "or" || array[2] == "and")) { joinNext = ((array[2] == "and") ? LogicJoin.And : LogicJoin.Or); value = string.Join("|", array, 3, array.Length - 3); } else { value = string.Join("|", array, 2, array.Length - 2); } return new PackCondition { Type = type, Negate = negate, JoinNext = joinNext, Value = value }; } } public static class PackDoctor { public enum Severity { Error, Warn, Info } public readonly struct Finding { public readonly Severity Severity; public readonly string Rule; public readonly string RelPath; public readonly string Message; public Finding(Severity sev, string rule, string relPath, string message) { Severity = sev; Rule = rule; RelPath = relPath; Message = message; } } private const int MaxPerRule = 12; private static string[] ContentRoots => PackLayout.ContentRoots; public static string ScanAndReport(PackInfo pack) { if (pack == null || !Directory.Exists(pack.Path)) { Plugin.Logger.LogWarning((object)("[PackDoctor] Pack folder missing: " + pack?.Path)); return null; } List list = Scan(pack.Path); string text = WriteReport(pack, list); Plugin.Logger.LogInfo((object)($"[PackDoctor] '{pack.Name}': {Count(list, Severity.Error)} error(s), " + $"{Count(list, Severity.Warn)} warning(s), {Count(list, Severity.Info)} note(s) → {text}")); return text; } private static int Count(List f, Severity s) { return f.Count((Finding x) => x.Severity == s); } public static List Scan(string packPath) { List list = new List(); packPath = packPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); if (PackLayout.IsCustomizerPack(packPath)) { list.Add(new Finding(Severity.Info, "customizer-format", "", "Customizer-format skin (auto-adopted; read in place, never modified). Sheet-based art breaks when a game update repacks atlases — and the Dev Hub's Convert Sheets can only cut a sheet correctly WHILE it still matches the current game's layout. Convert EARLY, not later: the conversion window closes at the next atlas repack, after which the art needs manual re-authoring.")); return list; } CheckRootsAndNesting(packPath, list); CheckManifest(packPath, list); CheckDebris(packPath, list); string text = FindRoot(packPath, "Sprites"); if (text != null) { CheckSpriteTiers(packPath, text, list); CheckAnchorTags(packPath, text, list); CheckDumpsCrossReference(packPath, text, list); } string text2 = FindRoot(packPath, "Spritesheets"); if (text2 != null) { CheckSheetTiers(packPath, text2, list); } string text3 = FindRoot(packPath, "Sounds"); if (text3 != null) { CheckExtensions(packPath, text3, AudioHandler.SupportedExtensions, "audio", list); } string text4 = FindRoot(packPath, "Videos"); if (text4 != null) { CheckExtensions(packPath, text4, VideoHandler.SupportedExtensions, "video", list); } string text5 = FindRoot(packPath, "Text"); if (text5 != null) { CheckTextTree(packPath, text5, list); } string text6 = FindRoot(packPath, "Fonts"); if (text6 != null) { CheckFontsTree(packPath, text6, list); } return list; } private static void CheckFontsTree(string packPath, string fontsRoot, List f) { string[] files = Directory.GetFiles(fontsRoot, "*", SearchOption.AllDirectories); foreach (string text in files) { bool flag = text.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase) || text.EndsWith(".otf", StringComparison.OrdinalIgnoreCase); bool flag2 = string.Equals(Path.GetFileName(Path.GetDirectoryName(text)), "_fallback", StringComparison.OrdinalIgnoreCase); if (flag && !flag2) { f.Add(new Finding(Severity.Warn, "font-location", Rel(packPath, text), "Font file outside Fonts/_fallback/ — it will NOT be loaded. Move it to Fonts/_fallback/.")); } else if (!flag && flag2 && !text.EndsWith(".txt", StringComparison.OrdinalIgnoreCase) && !text.EndsWith(".md", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Warn, "font-ext", Rel(packPath, text), "Not a .ttf/.otf font — the fallback loader will skip this file.")); } } } private static void CheckRootsAndNesting(string packPath, List f) { bool flag = false; string[] directories = Directory.GetDirectories(packPath); for (int i = 0; i < directories.Length; i++) { string fileName = Path.GetFileName(directories[i]); string[] contentRoots = ContentRoots; foreach (string text in contentRoots) { if (string.Equals(fileName, text, StringComparison.OrdinalIgnoreCase)) { flag = true; if (!string.Equals(fileName, text, StringComparison.Ordinal)) { f.Add(new Finding(Severity.Warn, "root-case", fileName, "folder is '" + fileName + "' but the canonical name is '" + text + "' — works on Windows, silently ignored on case-sensitive hosts (Linux/Steam Deck). Rename it.")); } } } } if (flag) { return; } directories = Directory.GetDirectories(packPath); foreach (string path in directories) { string[] contentRoots = Directory.GetDirectories(path); foreach (string path2 in contentRoots) { string subName = Path.GetFileName(path2); if (ContentRoots.Any((string r) => string.Equals(subName, r, StringComparison.OrdinalIgnoreCase))) { f.Add(new Finding(Severity.Error, "nesting", Path.GetFileName(path), "no content roots at the pack root, but '" + Path.GetFileName(path) + "/" + subName + "/' exists — content is nested one level too deep (typical zip-in-zip). Move it up.")); return; } } } f.Add(new Finding(Severity.Error, "nesting", "", "no Stitchwork content found (no Sprites/, Spritesheets/, Sounds/, Videos/ or Text/ folder). Nothing in this pack will load.")); } private static void CheckManifest(string packPath, List f) { string path = Path.Combine(packPath, "pack.json"); if (!File.Exists(path)) { f.Add(new Finding(Severity.Info, "manifest", "pack.json", "no pack.json — the pack works, but without one users LOSE its enable-state/order/conditions when the folder is renamed or a new version ships under a new folder. Add {\"name\": \"…\", \"author\": \"…\"} to give it a durable identity.")); } else if (File.ReadAllText(path).IndexOf("\"name\"", StringComparison.OrdinalIgnoreCase) < 0) { f.Add(new Finding(Severity.Warn, "manifest", "pack.json", "pack.json has no \"name\" field — display name falls back to the folder name and stable identity degrades with it.")); } } private static void CheckDebris(string packPath, List f) { string[] directories = Directory.GetDirectories(packPath, "*_WIP", SearchOption.AllDirectories); foreach (string full in directories) { f.Add(new Finding(Severity.Warn, "debris", Rel(packPath, full), "animation-canvas _WIP folder in a shipping pack — the loader skips it, but it bloats the download and confuses users. Delete before shipping (keep your local copy).")); } directories = Directory.GetFiles(packPath, "anchors.txt", SearchOption.AllDirectories); foreach (string full2 in directories) { f.Add(new Finding(Severity.Warn, "debris", Rel(packPath, full2), "legacy anchors.txt — no longer read (filename tags @t/@b/@l/@r/@c replaced it). Delete.")); } directories = Directory.GetDirectories(packPath, "*", SearchOption.AllDirectories); foreach (string text in directories) { string fileName = Path.GetFileName(text); if (string.Equals(fileName, "Dumps", StringComparison.OrdinalIgnoreCase) || string.Equals(fileName, "TextDumps", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Warn, "debris", Rel(packPath, text), "a dumps folder inside the pack — dumps are working material, not pack content. Don't ship them.")); } } } private static void CheckSpriteTiers(string packPath, string spritesRoot, List f) { foreach (string item in EnumerateNonWip(spritesRoot)) { string text = Rel(spritesRoot, item); string[] array = text.Split('/'); string text2 = Path.GetExtension(item).ToLowerInvariant(); if (text2 != ".png") { if (!text.EndsWith("_sidecar.yml", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Warn, "sprite-tier", Rel(packPath, item), "non-PNG file under Sprites/ — ignored by the loader (" + text2 + ").")); } } else if (Path.GetFileNameWithoutExtension(item).EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Error, "sprite-double-ext", Rel(packPath, item), "doubled extension — the sprite name includes '.png' and will never match a game sprite. Rename to a single .png.")); } else if (string.Equals(array[0], "T2D", StringComparison.OrdinalIgnoreCase)) { if (array.Length > 3) { f.Add(new Finding(Severity.Error, "t2d-tier", Rel(packPath, item), "T2D sprite nested too deep — the T2D loader scans one atlas folder deep: Sprites/T2D//.png, Sprites/T2D/_standalone/.png, or flat Sprites/T2D/.png. This file will not load.")); } else if (array.Length == 2) { f.Add(new Finding(Severity.Info, "t2d-flat", Rel(packPath, item), "flat T2D sprite — loads fine, but matches by name across ALL atlases; if two atlases share this sprite name, qualify it: Sprites/T2D//.png.")); } } else if (array.Any((string s) => string.Equals(s, "Clips", StringComparison.OrdinalIgnoreCase))) { if (array.Length != 4 || !string.Equals(array[1], "Clips", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Error, "clips-shape", Rel(packPath, item), "a 'Clips' folder outside the per-clip shape — expected exactly Sprites//Clips//.png. This file will not load.")); } } else if (array.Length != 3) { f.Add(new Finding(Severity.Error, "sprite-tier", Rel(packPath, item), (array.Length < 3) ? "sprite PNG above the required tier — expected Sprites///.png. Silently ignored." : "sprite PNG nested too deep — expected Sprites///.png. Silently ignored.")); } } } private static void CheckSheetTiers(string packPath, string sheetsRoot, List f) { foreach (string item in EnumerateNonWip(sheetsRoot)) { string text = Rel(sheetsRoot, item); if (Path.GetExtension(item).ToLowerInvariant() != ".png") { f.Add(new Finding(Severity.Warn, "sheet-tier", Rel(packPath, item), "non-PNG file under Spritesheets/ — ignored by the loader.")); } else if (Path.GetFileNameWithoutExtension(item).EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Error, "sheet-double-ext", Rel(packPath, item), "doubled extension — the sheet name includes '.png' and will never match a material. Rename to a single .png.")); } else if (text.Split('/').Length != 2) { f.Add(new Finding(Severity.Error, "sheet-tier", Rel(packPath, item), "spritesheet at the wrong tier — expected Spritesheets//.png. Silently ignored.")); } } } private static void CheckAnchorTags(string packPath, string spritesRoot, List f) { Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (string item in EnumerateNonWip(spritesRoot)) { if (Path.GetExtension(item).ToLowerInvariant() != ".png") { continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); (string name, string anchor) tuple = IOUtil.ParseAnchorTag(fileNameWithoutExtension); var (text, _) = tuple; if (tuple.anchor == null && fileNameWithoutExtension.Contains('@')) { string text2 = fileNameWithoutExtension.Substring(fileNameWithoutExtension.LastIndexOf('@') + 1); if (text2.Length > 0 && (text2[0] == 'p' || text2[0] == 'P') && text2.Skip(1).All((char c) => char.IsDigit(c) || c == '_' || c == '-')) { f.Add(new Finding(Severity.Error, "tag", Rel(packPath, item), "'@" + text2 + "' looks like a precise-pivot tag but doesn't parse — the format is @p_ (e.g. @p112_108). As-is the '@' stays in the sprite name and it never matches.")); } else if (text2.Length == 1) { f.Add(new Finding(Severity.Warn, "tag", Rel(packPath, item), "'@" + text2 + "' is not a valid anchor letter (valid: @t @b @l @r @c) — the '@' stays in the sprite name.")); } } string key = Path.GetDirectoryName(item) + "/" + text; List list3; if (!dictionary.TryGetValue(key, out var value)) { List list = (dictionary[key] = new List()); list3 = list; } else { list3 = value; } list3.Add(fileNameWithoutExtension); } foreach (KeyValuePair> item2 in dictionary.Where((KeyValuePair> k) => k.Value.Count > 1)) { f.Add(new Finding(Severity.Warn, "tag-conflict", Rel(packPath, Path.GetDirectoryName(item2.Key + ".x")), "multiple tag variants of one sprite (" + string.Join(", ", item2.Value) + ") — first-wins alphabetically; keep exactly one.")); } } private static void CheckExtensions(string packPath, string root, string[] supported, string kind, List f) { string[] files = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories); foreach (string text in files) { string text2 = Path.GetExtension(text).ToLowerInvariant(); if (Array.IndexOf(supported, text2) < 0) { f.Add(new Finding(Severity.Error, kind + "-format", Rel(packPath, text), "unsupported " + kind + " format '" + text2 + "' — will not load. Supported: " + string.Join(" ", supported) + ".")); continue; } string text3 = Path.GetExtension(Path.GetFileNameWithoutExtension(text)).ToLowerInvariant(); if (text3.Length > 0 && Array.IndexOf(supported, text3) >= 0) { f.Add(new Finding(Severity.Error, kind + "-double-ext", Rel(packPath, text), "doubled extension — the " + kind + " name includes '" + text3 + "' and will never match a game clip. Rename to strip the inner extension (Silently ignored otherwise).")); } } } private static void CheckTextTree(string packPath, string textRoot, List f) { string[] files = Directory.GetFiles(textRoot, "*.*", SearchOption.AllDirectories); foreach (string text in files) { string text2 = Rel(textRoot, text); if (!text2.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)) { f.Add(new Finding(Severity.Warn, "text-tier", Rel(packPath, text), "non-.yml file under Text/ — ignored by the loader.")); } else if (text2.Split('/').Length != 2) { f.Add(new Finding(Severity.Error, "text-tier", Rel(packPath, text), "text file at the wrong tier — expected Text//.yml. Silently ignored.")); } else if (!File.ReadLines(text).Any(delegate(string l) { string text3 = l.TrimStart(); if (text3.Length == 0 || text3[0] == '#') { return false; } int num = text3.IndexOf(':'); return num > 0 && text3.Substring(num + 1).TrimStart().StartsWith("\""); })) { f.Add(new Finding(Severity.Warn, "text-empty", Rel(packPath, text), "no KEY: \"value\" entries found — the file contributes nothing.")); } } } private static void CheckDumpsCrossReference(string packPath, string spritesRoot, List f) { string dumpPath = SpriteDumper.DumpPath; if (!Directory.Exists(dumpPath)) { return; } string[] directories = Directory.GetDirectories(spritesRoot); foreach (string text in directories) { string fileName = Path.GetFileName(text); string text2 = Path.Combine(dumpPath, fileName); if (!Directory.Exists(text2)) { f.Add(new Finding(Severity.Info, "dumps-xref", Rel(packPath, text), "collection '" + fileName + "' isn't in your Dumps/ — possible typo, or your dumps are partial.")); continue; } string[] directories2 = Directory.GetDirectories(text); foreach (string text3 in directories2) { string fileName2 = Path.GetFileName(text3); if (string.Equals(fileName2, "Clips", StringComparison.OrdinalIgnoreCase)) { continue; } string text4 = Path.Combine(text2, fileName2); if (!Directory.Exists(text4)) { f.Add(new Finding(Severity.Warn, "dumps-xref", Rel(packPath, text3), "material '" + fileName2 + "' isn't under Dumps/" + fileName + "/ — possible typo (or a deliberate instance-specific vanilla-texture path, which dumps don't key by).")); continue; } string[] files = Directory.GetFiles(text3, "*.png"); foreach (string text5 in files) { string item = IOUtil.ParseAnchorTag(Path.GetFileNameWithoutExtension(text5)).name; if (!File.Exists(Path.Combine(text4, item + ".png"))) { f.Add(new Finding(Severity.Warn, "dumps-xref", Rel(packPath, text5), "sprite '" + item + "' isn't in Dumps/" + fileName + "/" + fileName2 + "/ — possible typo in the sprite name.")); } } } } } private static string WriteReport(PackInfo pack, List findings) { string text = Path.Combine(Plugin.BasePath, "doctor-reports"); IOUtil.EnsureDirectoryExists(text); string text2 = Path.Combine(text, T2DUtil.SanitizeForFilesystem(pack.Name) + ".txt"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Stitchwork Pack Doctor — " + pack.Name); stringBuilder.AppendLine(pack.Path); stringBuilder.AppendLine(string.Format("{0:yyyy-MM-dd HH:mm} Stitchwork {1}", DateTime.Now, "0.8.6")); stringBuilder.AppendLine(); stringBuilder.AppendLine($"Result: {Count(findings, Severity.Error)} error(s), " + $"{Count(findings, Severity.Warn)} warning(s), {Count(findings, Severity.Info)} note(s)"); if (findings.Count == 0) { stringBuilder.AppendLine().AppendLine("Clean bill of health — nothing to report."); } Severity[] array = new Severity[3] { Severity.Error, Severity.Warn, Severity.Info }; for (int i = 0; i < array.Length; i++) { Severity sev = array[i]; List list = findings.Where((Finding x) => x.Severity == sev).ToList(); if (list.Count == 0) { continue; } stringBuilder.AppendLine(); stringBuilder.AppendLine($"═══ {sev.ToString().ToUpperInvariant()} ({list.Count}) ═══"); foreach (IGrouping item in from x in list group x by x.Rule) { foreach (Finding item2 in item.Take(12)) { stringBuilder.AppendLine(" [" + item2.Rule + "] " + item2.RelPath + "\n " + item2.Message); } if (item.Count() > 12) { stringBuilder.AppendLine($" [{item.Key}] … and {item.Count() - 12} more like this."); } } } File.WriteAllText(text2, stringBuilder.ToString()); return text2; } private static string FindRoot(string packPath, string root) { string[] directories = Directory.GetDirectories(packPath); foreach (string text in directories) { if (string.Equals(Path.GetFileName(text), root, StringComparison.OrdinalIgnoreCase)) { return text; } } return null; } private static IEnumerable EnumerateNonWip(string root) { string[] files = Directory.GetFiles(root, "*.*", SearchOption.AllDirectories); foreach (string text in files) { if (text.IndexOf("_WIP" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("_WIP" + Path.AltDirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) < 0) { yield return text; } } } private static string Rel(string root, string full) { return full.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Replace('\\', '/'); } } public enum PackSource { Plugin, Customizer, Global } public class PackInfo { private string _effAuthor; private bool _effAuthorComputed; private string _stableId; public string Path { get; } public string Name { get; } public string Author { get; } public string EffectiveAuthor { get { if (!_effAuthorComputed) { _effAuthor = ComputeEffectiveAuthor(); _effAuthorComputed = true; } return _effAuthor; } } public string Version { get; } public string Description { get; } public bool IsEnabled { get; set; } public PackSource Source { get; } public string StableId => _stableId ?? (_stableId = SanitizeId(string.IsNullOrEmpty(Author) ? Name : (Author + "/" + Name))); public PackStats Stats { get; set; } public List Conditions { get; set; } = new List(); public ReloadTrigger ReloadTrigger { get; set; } public bool HasConditions => Conditions.Count > 0; private string ComputeEffectiveAuthor() { if (!string.IsNullOrEmpty(Author)) { return Author; } if (Source == PackSource.Customizer) { return null; } string fileName = System.IO.Path.GetFileName(System.IO.Path.GetDirectoryName(Path)); if (string.IsNullOrEmpty(fileName)) { return null; } int num = fileName.IndexOf('-'); if (num <= 0 || num >= fileName.Length - 1) { return null; } if (char.IsDigit(fileName.Substring(num + 1)[0])) { return null; } return fileName.Substring(0, num); } private static string SanitizeId(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return s.Replace('|', '_').Replace('\t', ' ').Replace('\n', ' ') .Replace('\r', ' '); } public bool EvaluateConditions(string sceneName, IReadOnlyList allPacks) { if (!HasConditions) { return true; } bool flag = true; bool result = false; for (int i = 0; i < Conditions.Count; i++) { flag &= Conditions[i].Evaluate(sceneName, allPacks); if (i == Conditions.Count - 1 || Conditions[i].JoinNext == LogicJoin.Or) { if (flag) { result = true; } flag = true; } } return result; } public PackInfo(string path, string name, PackSource source, string author = null, string version = null, string description = null) { Path = path; Name = name; Source = source; Author = author; Version = version; Description = description; } public PackInfo Clone() { return new PackInfo(Path, Name, Source, Author, Version, Description) { IsEnabled = IsEnabled, Stats = Stats, Conditions = Conditions.Select((PackCondition c) => c.Clone()).ToList(), ReloadTrigger = ReloadTrigger }; } } public static class PackLayout { public static readonly string[] ContentRoots = new string[7] { "Sprites", "Spritesheets", "Sounds", "Videos", "Text", "Fonts", "Recolour" }; private static string _customizerRoot; private static string _globalPacksRoot; private static string _customizerRootFull; private static readonly HashSet _markerRoots = new HashSet(StringComparer.OrdinalIgnoreCase); public static string CustomizerRoot => _customizerRoot ?? (_customizerRoot = Path.Combine(Application.dataPath, "Mods", "Customizer")); public static string GlobalPacksRoot { get { string globalPacksPath = Plugin.Config.GlobalPacksPath; if (!string.IsNullOrWhiteSpace(globalPacksPath)) { return globalPacksPath.Trim(); } return _globalPacksRoot ?? (_globalPacksRoot = Path.Combine(Path.GetDirectoryName(Application.dataPath) ?? Application.dataPath, "Packs")); } } public static void ClearMarkerRoots() { _markerRoots.Clear(); } public static void RegisterMarkerRoot(string packPath) { _markerRoots.Add(Path.GetFullPath(packPath)); } public static bool IsMarkerRoot(string packPath) { if (!string.IsNullOrEmpty(packPath)) { return _markerRoots.Contains(Path.GetFullPath(packPath)); } return false; } public static bool IsCustomizerPack(string packPath) { if (string.IsNullOrEmpty(packPath)) { return false; } string fullPath = Path.GetFullPath(packPath); if (_markerRoots.Contains(fullPath)) { return true; } if (_customizerRootFull == null) { _customizerRootFull = Path.GetFullPath(CustomizerRoot); } return fullPath.StartsWith(_customizerRootFull, StringComparison.OrdinalIgnoreCase); } public static bool LooksLikeCustomizerSkin(string dir) { if (!File.Exists(Path.Combine(dir, "active.txt"))) { return false; } if (Directory.EnumerateFiles(dir, "*.dll").Any()) { return false; } if (!Directory.EnumerateDirectories(dir).Any((string sub) => (!string.Equals(Path.GetFileName(sub), "Texture2D", StringComparison.OrdinalIgnoreCase)) ? Directory.EnumerateFiles(sub, "atlas*.png").Any() : Directory.EnumerateFiles(sub, "*.png").Any()) && !Directory.EnumerateFiles(dir, "sactx-*.png").Any()) { return Directory.EnumerateFiles(dir, "atlas*.png").Any(); } return true; } public static bool IsPackagingAsset(string filePath) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath); if (!fileNameWithoutExtension.Equals("icon", StringComparison.OrdinalIgnoreCase) && !fileNameWithoutExtension.Equals("logo", StringComparison.OrdinalIgnoreCase)) { return fileNameWithoutExtension.StartsWith("library_", StringComparison.OrdinalIgnoreCase); } return true; } public static string SheetsRoot(string packPath) { if (!IsCustomizerPack(packPath)) { return Path.Combine(packPath, "Spritesheets"); } return packPath; } public static string T2DSheetsRoot(string packPath) { if (!IsCustomizerPack(packPath)) { return Path.Combine(packPath, "Spritesheets", "T2D"); } return Path.Combine(packPath, "Texture2D"); } public static string VideosRoot(string packPath) { if (!IsCustomizerPack(packPath)) { return Path.Combine(packPath, "Videos"); } return packPath; } } public static class PackManager { private static readonly List _packs = new List(); private static string _currentScene = ""; private static readonly Dictionary _prevConditionalStates = new Dictionary(); private static List _activePackPathsCache; private static List _hotReloadPacksCache; private static readonly string[] PackSubfolderNames = new string[2] { "Stitchwork", "Patchwork" }; private static bool _legacyStateSeen; private static string ConfigPath => Path.Combine(Plugin.BasePath, "packs.txt"); private static string StatsCachePath => Path.Combine(Plugin.BasePath, "packs-stats.txt"); private static string ProfilesDir => Path.Combine(Plugin.BasePath, "Profiles"); private static string ConditionsPath => Path.Combine(Plugin.BasePath, "packs-conditions.txt"); public static IReadOnlyList AllPacks => _packs; public static IReadOnlyList ActivePackPaths { get { if (_activePackPathsCache == null) { _activePackPathsCache = new List(); foreach (PackInfo pack in _packs) { if (pack.IsEnabled && (!pack.HasConditions || pack.EvaluateConditions(_currentScene, _packs))) { _activePackPathsCache.Add(pack.Path); } } } return _activePackPathsCache; } } private static List HotReloadPacks { get { if (_hotReloadPacksCache == null) { _hotReloadPacksCache = new List(); foreach (PackInfo pack in _packs) { if (pack.HasConditions && pack.ReloadTrigger == ReloadTrigger.HotReload) { _hotReloadPacksCache.Add(pack); } } } return _hotReloadPacksCache; } } public static bool HasHotReloadPacks => HotReloadPacks.Count > 0; private static void InvalidateActivePathsCache() { _activePackPathsCache = null; _hotReloadPacksCache = null; } public static void Initialize() { MergeWithSavedConfig(DiscoverAll()); LoadStatsCache(); ScanMissingStats(); LoadConditions(); if (_legacyStateSeen) { SaveConfig(); SaveConditions(); _legacyStateSeen = false; Plugin.Logger.LogInfo((object)"[PackManager] packs.txt / packs-conditions.txt upgraded to stable-id format."); } } private static List DiscoverAll() { List list = new List(); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); string[] packSubfolderNames = PackSubfolderNames; foreach (string searchPattern in packSubfolderNames) { string[] directories = Directory.GetDirectories(Paths.PluginPath, searchPattern, SearchOption.AllDirectories); foreach (string text in directories) { if (!SamePath(text, Plugin.BasePath) && hashSet.Add(text)) { list.Add(ReadManifest(text, PackSource.Plugin)); } } } string globalPacksRoot = PackLayout.GlobalPacksRoot; try { Directory.CreateDirectory(globalPacksRoot); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { Plugin.Logger.LogWarning((object)("[PackManager] Global packs root not creatable: " + globalPacksRoot + " — " + ex.Message)); } int num = 0; if (Directory.Exists(globalPacksRoot)) { packSubfolderNames = PackSubfolderNames; foreach (string searchPattern2 in packSubfolderNames) { string[] directories = Directory.GetDirectories(globalPacksRoot, searchPattern2, SearchOption.AllDirectories); foreach (string text2 in directories) { if (hashSet.Add(text2)) { list.Add(ReadManifest(text2, PackSource.Global)); num++; } } } } Plugin.Logger.LogInfo((object)$"[PackManager] Global packs root: {globalPacksRoot} — {num} pack(s)"); if (Plugin.CustomizerPluginPresent) { if (Directory.Exists(PackLayout.CustomizerRoot)) { Plugin.Logger.LogWarning((object)"[PackManager] Customizer plugin is loaded — NOT auto-adopting its skins. Remove the Customizer plugin (Stitchwork replaces it) and its skins will appear here automatically."); } } else if (Directory.Exists(PackLayout.CustomizerRoot)) { packSubfolderNames = Directory.GetDirectories(PackLayout.CustomizerRoot); foreach (string path in packSubfolderNames) { list.Add(ReadManifest(path, PackSource.Customizer)); } } PackLayout.ClearMarkerRoots(); foreach (string item in CustomizerMarkerCandidates()) { if (hashSet.Add(item)) { PackLayout.RegisterMarkerRoot(item); Plugin.Logger.LogInfo((object)("[PackManager] Adopted Customizer-format skin (active.txt marker): " + item)); list.Add(ReadManifest(item, PackSource.Customizer)); } } return list; } private static IEnumerable CustomizerMarkerCandidates() { string[] directories = Directory.GetDirectories(Paths.PluginPath); foreach (string top in directories) { if (SamePath(top, Plugin.BasePath) || PackSubfolderNames.Any((string n) => Directory.Exists(Path.Combine(top, n)))) { continue; } if (PackLayout.LooksLikeCustomizerSkin(top)) { yield return top; continue; } string[] directories2 = Directory.GetDirectories(top); foreach (string nested in directories2) { if (!PackSubfolderNames.Any((string n) => Directory.Exists(Path.Combine(nested, n))) && PackLayout.LooksLikeCustomizerSkin(nested)) { yield return nested; } } } } private static bool SamePath(string a, string b) { if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b)) { return false; } return string.Equals(Path.GetFullPath(a).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(b).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } private static PackInfo ReadManifest(string path, PackSource source) { string text = Path.Combine(path, "pack.json"); if (!File.Exists(text)) { string fileName = Path.GetFileName(path); bool flag = false; string[] packSubfolderNames = PackSubfolderNames; foreach (string b in packSubfolderNames) { if (string.Equals(fileName, b, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } string name; if (flag) { string fileName2 = Path.GetFileName(Path.GetDirectoryName(path)); name = (string.IsNullOrEmpty(fileName2) ? fileName : fileName2); } else { name = fileName; } return new PackInfo(path, name, source); } try { string json = File.ReadAllText(text); string text2 = ParseJsonString(json, "name"); string author = ParseJsonString(json, "author"); string version = ParseJsonString(json, "version"); string description = ParseJsonString(json, "description"); if (text2 == null) { Plugin.Logger.LogWarning((object)("[PackManager] pack.json at '" + text + "' has no 'name' field — using folder name")); } return new PackInfo(path, text2 ?? Path.GetFileName(path), source, author, version, description); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[PackManager] Failed to read '" + text + "': " + ex.Message + " — using folder name")); return new PackInfo(path, Path.GetFileName(path), source); } } private static string ParseJsonString(string json, string key) { string text = "\"" + key + "\""; int num = json.IndexOf(text, StringComparison.OrdinalIgnoreCase); if (num < 0) { return null; } int num2 = json.IndexOf(':', num + text.Length); if (num2 < 0) { return null; } int num3 = -1; for (int i = num2 + 1; i < json.Length; i++) { switch (json[i]) { case '"': break; default: return null; case '\t': case '\n': case '\r': case ' ': continue; } num3 = i; break; } if (num3 < 0) { return null; } StringBuilder stringBuilder = new StringBuilder(); for (int j = num3 + 1; j < json.Length; j++) { char c = json[j]; if (c == '\\' && j + 1 < json.Length) { char c2 = json[++j]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; default: stringBuilder.Append('\\'); stringBuilder.Append(c2); break; } } else { if (c == '"') { return stringBuilder.ToString(); } stringBuilder.Append(c); } } return null; } private static PackInfo FindByIdentity(IReadOnlyList candidates, string id, string path) { if (!string.IsNullOrEmpty(id)) { PackInfo packInfo = null; bool flag = false; foreach (PackInfo candidate in candidates) { if (string.Equals(candidate.StableId, id, StringComparison.OrdinalIgnoreCase)) { if (packInfo != null) { flag = true; break; } packInfo = candidate; } } if (packInfo != null && !flag) { return packInfo; } } if (!string.IsNullOrEmpty(path)) { foreach (PackInfo candidate2 in candidates) { if (string.Equals(candidate2.Path, path, StringComparison.OrdinalIgnoreCase)) { return candidate2; } } } return null; } private static (string Id, string Path) ParseIdentityField(string field) { int num = field.IndexOf('|'); if (num >= 0) { return (Id: field.Substring(0, num), Path: field.Substring(num + 1)); } _legacyStateSeen = true; return (Id: null, Path: field); } private static void MergeWithSavedConfig(List discovered) { _packs.Clear(); if (!File.Exists(ConfigPath)) { _packs.AddRange(discovered); return; } try { string[] array = File.ReadAllLines(ConfigPath); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#") && text.Length >= 3 && text[1] == '|') { bool isEnabled = text[0] == '+'; (string Id, string Path) tuple = ParseIdentityField(text.Substring(2)); string item = tuple.Id; string item2 = tuple.Path; PackInfo packInfo = FindByIdentity(discovered, item, item2); if (packInfo != null) { packInfo.IsEnabled = isEnabled; _packs.Add(packInfo); discovered.Remove(packInfo); } } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[PackManager] Failed to read packs.txt: " + ex.Message)); } _packs.AddRange(discovered); } public static void SaveConfig() { List list = new List { "# Stitchwork Pack Order — top of list = highest priority", "# Format: +|id|path (enabled) or -|id|path (disabled)", "# id = stable pack identity (pack.json author/name); path = fallback for duplicates" }; foreach (PackInfo pack in _packs) { list.Add($"{(pack.IsEnabled ? '+' : '-')}|{pack.StableId}|{pack.Path}"); } File.WriteAllLines(ConfigPath, list); } public static void Apply(List staged) { _packs.Clear(); _packs.AddRange(staged); InvalidateActivePathsCache(); SaveConfig(); SaveConditions(); ConflictTracker.Clear(); TriggerFullReload(); if (GameBridge.IsInGameScene) { ReloadCoordinator.RequestSceneReloadAfterDrain(); Plugin.Logger.LogInfo((object)"[PackManager] Apply: watcher flags raised; scene reload armed (dispatches after reloads drain)."); } else { Plugin.Logger.LogInfo((object)"[PackManager] Apply: watcher flags raised; scene-transition dispatch skipped (menu context — BeginSceneTransition would break menu input routing)."); } ScanAllStats(); Plugin.Logger.LogDebug((object)($"[PackManager] Applied pack order: {_packs.Count} packs, " + $"{_packs.Count((PackInfo p) => p.IsEnabled)} enabled")); } public static void Rescan() { InvalidateActivePathsCache(); ConflictTracker.Clear(); List discovered = DiscoverAll(); _packs.RemoveAll((PackInfo p) => !discovered.Any((PackInfo packInfo) => string.Equals(p.Path, packInfo.Path, StringComparison.OrdinalIgnoreCase))); foreach (PackInfo d in discovered) { if (!_packs.Any((PackInfo p) => string.Equals(p.Path, d.Path, StringComparison.OrdinalIgnoreCase))) { _packs.Add(d); } } Plugin.Logger.LogDebug((object)$"[PackManager] Rescan complete: {_packs.Count} packs found"); } public static string GetPackName(string path) { if (path == null) { return "(base)"; } return _packs.Find((PackInfo p) => string.Equals(p.Path, path, StringComparison.OrdinalIgnoreCase))?.Name ?? Path.GetFileName(path); } public static string[] GetProfileNames() { IOUtil.EnsureDirectoryExists(ProfilesDir); return (from n in Directory.GetFiles(ProfilesDir, "*.txt").Select(Path.GetFileNameWithoutExtension) orderby n select n).ToArray(); } public static void SaveProfile(string name, List packs = null) { IOUtil.EnsureDirectoryExists(ProfilesDir); List list = new List { "# Stitchwork Profile: " + name, "# Format: +|-|id|path[\\ttrigger\\tcond1\\tcond2...] (conditions optional, tab-separated)" }; foreach (PackInfo item in packs ?? _packs) { string text = $"{(item.IsEnabled ? '+' : '-')}|{item.StableId}|{item.Path}"; if (item.HasConditions) { string text2 = ((item.ReloadTrigger == ReloadTrigger.HotReload) ? "hot" : "scene"); string text3 = string.Join("\t", item.Conditions.Select((PackCondition c) => c.Serialize())); text = text + "\t" + text2 + "\t" + text3; } list.Add(text); } File.WriteAllLines(Path.Combine(ProfilesDir, name + ".txt"), list); Plugin.Logger.LogDebug((object)("[PackManager] Saved profile '" + name + "'")); } public static List StageProfile(string name) { string path = Path.Combine(ProfilesDir, name + ".txt"); if (!File.Exists(path)) { return null; } List<(string, string, bool, string[])> list = new List<(string, string, bool, string[])>(); string[] array = File.ReadAllLines(path); foreach (string text in array) { if (!string.IsNullOrWhiteSpace(text) && !text.StartsWith("#") && text.Length >= 3) { bool item = text[0] == '+'; string[] array2 = text.Substring(2).Split('\t'); string item2 = null; string item3 = array2[0]; int num = array2[0].IndexOf('|'); if (num >= 0) { item2 = array2[0].Substring(0, num); item3 = array2[0].Substring(num + 1); } list.Add((item2, item3, item, array2)); } } List list2 = new List(); foreach (var item8 in list) { string item4 = item8.Item1; string item5 = item8.Item2; bool item6 = item8.Item3; string[] item7 = item8.Item4; PackInfo packInfo = FindByIdentity(_packs, item4, item5); if (packInfo == null) { continue; } PackInfo packInfo2 = packInfo.Clone(); packInfo2.IsEnabled = item6; if (item7.Length > 2) { packInfo2.ReloadTrigger = ((item7[1] == "hot") ? ReloadTrigger.HotReload : ReloadTrigger.OnSceneTransition); packInfo2.Conditions.Clear(); for (int j = 2; j < item7.Length; j++) { PackCondition packCondition = PackCondition.TryDeserialize(item7[j]); if (packCondition != null) { packInfo2.Conditions.Add(packCondition); } } } list2.Add(packInfo2); } foreach (PackInfo p in _packs) { if (!list2.Any((PackInfo r) => string.Equals(r.Path, p.Path, StringComparison.OrdinalIgnoreCase))) { list2.Add(p.Clone()); } } Plugin.Logger.LogDebug((object)("[PackManager] Staged profile '" + name + "'")); return list2; } public static void DeleteProfile(string name) { string path = Path.Combine(ProfilesDir, name + ".txt"); if (File.Exists(path)) { File.Delete(path); } Plugin.Logger.LogDebug((object)("[PackManager] Deleted profile '" + name + "'")); } private static void LoadStatsCache() { if (!File.Exists(StatsCachePath)) { return; } string[] array = File.ReadAllLines(StatsCachePath); foreach (string line in array) { if (PackStats.TryDeserialize(line, out var path, out var stats)) { PackInfo packInfo = _packs.Find((PackInfo p) => string.Equals(p.Path, path, StringComparison.OrdinalIgnoreCase)); if (packInfo != null) { packInfo.Stats = stats; } } } } private static void SaveStatsCache() { List list = new List { "# Stitchwork Pack Stats — auto-generated, do not edit manually" }; foreach (PackInfo pack in _packs) { if (pack.Stats.IsScanned) { list.Add(pack.Stats.Serialize(pack.Path)); } } File.WriteAllLines(StatsCachePath, list); } private static void ScanAllStats() { foreach (PackInfo pack in _packs) { pack.Stats = PackStats.Scan(pack.Path); } SaveStatsCache(); Plugin.Logger.LogDebug((object)$"[PackManager] Pack stats refreshed for {_packs.Count} pack(s)"); } private static void ScanMissingStats() { bool flag = false; foreach (PackInfo pack in _packs) { if (!pack.Stats.IsScanned) { pack.Stats = PackStats.Scan(pack.Path); flag = true; } } if (flag) { SaveStatsCache(); } } public static void OnSceneLoaded(string sceneName) { _currentScene = sceneName; InvalidateActivePathsCache(); EvaluateConditionalPacks(onlyHotReload: false); } public static void PollHotReloadConditions() { EvaluateConditionalPacks(onlyHotReload: true); } private static void EvaluateConditionalPacks(bool onlyHotReload) { IReadOnlyList readOnlyList; if (onlyHotReload) { readOnlyList = HotReloadPacks; } else { List list = new List(); foreach (PackInfo pack in _packs) { if (pack.HasConditions) { list.Add(pack); } } readOnlyList = list; } if (readOnlyList.Count == 0) { return; } bool flag = false; foreach (PackInfo item in readOnlyList) { bool flag2 = item.IsEnabled && item.EvaluateConditions(_currentScene, _packs); _prevConditionalStates.TryGetValue(item.Path, out var value); if (flag2 != value) { _prevConditionalStates[item.Path] = flag2; flag = true; Plugin.Logger.LogInfo((object)("[PackManager] '" + item.Name + "' condition → " + (flag2 ? "active" : "inactive") + " (scene='" + _currentScene + "')")); } } if (flag) { InvalidateActivePathsCache(); ConflictTracker.Clear(); TriggerFullReload(); } } private static void LoadConditions() { if (!File.Exists(ConditionsPath)) { return; } string[] array = File.ReadAllLines(ConditionsPath); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text) || text.StartsWith("#")) { continue; } string[] array2 = text.Split('\t'); if (array2.Length < 3) { continue; } string id; string path; int num; if (array2[1] == "hot" || array2[1] == "scene") { id = null; path = array2[0]; num = 1; _legacyStateSeen = true; } else { if (array2.Length < 4) { continue; } id = array2[0]; path = array2[1]; num = 2; } PackInfo packInfo = FindByIdentity(_packs, id, path); if (packInfo == null) { continue; } packInfo.ReloadTrigger = ((array2[num] == "hot") ? ReloadTrigger.HotReload : ReloadTrigger.OnSceneTransition); int num2 = num + 1; if (num2 < array2.Length && (array2[num2] == "and" || array2[num2] == "or")) { num2++; } packInfo.Conditions.Clear(); for (int j = num2; j < array2.Length; j++) { PackCondition packCondition = PackCondition.TryDeserialize(array2[j]); if (packCondition != null) { packInfo.Conditions.Add(packCondition); } } } Plugin.Logger.LogDebug((object)("[PackManager] Loaded conditions for " + $"{_packs.Count((PackInfo p) => p.HasConditions)} pack(s)")); } public static void SaveConditions() { List list = new List { "# Stitchwork Pack Conditions — auto-generated, do not edit manually", "# Format: id \\t path \\t trigger \\t cond1 [\\t cond2 ...]" }; foreach (PackInfo item in _packs.Where((PackInfo q) => q.HasConditions)) { string text = ((item.ReloadTrigger == ReloadTrigger.HotReload) ? "hot" : "scene"); string text2 = string.Join("\t", item.Conditions.Select((PackCondition c) => c.Serialize())); list.Add(item.StableId + "\t" + item.Path + "\t" + text + "\t" + text2); } File.WriteAllLines(ConditionsPath, list); } public static void TriggerFullReload() { Plugin.TextFileWatcher?.RebuildPackWatchers(); Plugin.FontFileWatcher?.RebuildPackWatchers(); Plugin.SpriteFileWatcher?.RebuildPackPngWatchers(); Plugin.AudioFileWatcher?.RebuildPackWatchers(); RecolourHandler.ReloadRequested = true; FileCache.Clear(); SpriteFileWatcher.ReloadSprites = true; SpriteFileWatcher.ReloadT2DSprites = true; AudioFileWatcher.ReloadAudio = true; TextFileWatcher.ReloadText = true; FontFileWatcher.ReloadFonts = true; VideoHandler.ReloadVideos = true; } } public readonly struct PackStats { public bool IsScanned { get; init; } public int Sprites { get; init; } public int Sheets { get; init; } public int Audio { get; init; } public int Video { get; init; } public int Text { get; init; } public int Total => Sprites + Sheets + Audio + Video + Text; public string Badge { get { if (!IsScanned) { return null; } List list = new List(); if (Sprites > 0) { list.Add(string.Format("{0} sprite{1}", Sprites, (Sprites == 1) ? "" : "s")); } if (Sheets > 0) { list.Add(string.Format("{0} sheet{1}", Sheets, (Sheets == 1) ? "" : "s")); } if (Audio > 0) { list.Add($"{Audio} sfx"); } if (Video > 0) { list.Add(string.Format("{0} video{1}", Video, (Video == 1) ? "" : "s")); } if (Text > 0) { list.Add($"{Text} text"); } if (list.Count <= 0) { return null; } return string.Join(" ", list); } } public static PackStats Scan(string packPath) { try { return new PackStats { IsScanned = true, Sprites = CountFiles(packPath, "Sprites", "*.png"), Sheets = CountFiles(PackLayout.SheetsRoot(packPath), "", "*.png"), Audio = CountFiles(packPath, "Sounds", "*.*", new string[3] { ".wav", ".ogg", ".mp3" }), Video = CountFiles(packPath, "Videos", "*.*"), Text = CountFiles(packPath, "Text", "*.*", new string[2] { ".yml", ".yaml" }) }; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[PackStats] Scan failed for '" + packPath + "': " + ex.Message)); return new PackStats { IsScanned = true }; } } private static int CountFiles(string packPath, string subdir, string pattern, string[] extensions = null) { string path = Path.Combine(packPath, subdir); if (!Directory.Exists(path)) { return 0; } string[] files = Directory.GetFiles(path, pattern, SearchOption.AllDirectories); if (extensions == null) { return files.Length; } int num = 0; string[] array = files; foreach (string path2 in array) { if (Array.IndexOf(extensions, Path.GetExtension(path2).ToLowerInvariant()) >= 0) { num++; } } return num; } public string Serialize(string packPath) { return $"{packPath}\t{Sprites}\t{Sheets}\t{Audio}\t{Video}\t{Text}"; } public static bool TryDeserialize(string line, out string path, out PackStats stats) { stats = default(PackStats); path = null; if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) { return false; } string[] array = line.Split('\t'); if (array.Length < 6) { return false; } if (!int.TryParse(array[1], out var result) || !int.TryParse(array[2], out var result2) || !int.TryParse(array[3], out var result3) || !int.TryParse(array[4], out var result4) || !int.TryParse(array[5], out var result5)) { return false; } path = array[0]; stats = new PackStats { IsScanned = true, Sprites = result, Sheets = result2, Audio = result3, Video = result4, Text = result5 }; return true; } } public static class PushPull { private static readonly string[] ContentRoots = PackLayout.ContentRoots; private static bool _targetResolved; public static PackInfo Target { get; private set; } public static string LastSummary { get; private set; } public static bool TargetIsReadOnly { get { if (Target != null) { return PackLayout.IsCustomizerPack(Target.Path); } return false; } } public static void SetTarget(PackInfo pack) { Target = pack; _targetResolved = true; if (Plugin.Config?.TargetPackPathEntry != null) { Plugin.Config.TargetPackPathEntry.Value = pack?.Path ?? ""; } } public static PackInfo ResolveTarget() { if (Target != null && !PackManager.AllPacks.Contains(Target)) { _targetResolved = false; } if (_targetResolved) { return Target; } _targetResolved = true; string path = Plugin.Config?.TargetPackPath; Target = (string.IsNullOrEmpty(path) ? null : PackManager.AllPacks.FirstOrDefault((PackInfo p) => string.Equals(p.Path, path, StringComparison.OrdinalIgnoreCase))); return Target; } public static void Push() { if (Target == null || TargetIsReadOnly) { return; } int num = 0; int num2 = 0; int num3 = 0; string[] contentRoots = ContentRoots; foreach (string path in contentRoots) { string text = Path.Combine(Plugin.BasePath, path); if (!Directory.Exists(text)) { continue; } foreach (string item in Directory.EnumerateFiles(text, "*", SearchOption.AllDirectories)) { string text2 = item.Substring(text.Length + 1); if (IsWipPath(text2)) { num3++; } else if (!string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(item))) { string path2 = Path.Combine(Target.Path, path, text2); bool num4 = File.Exists(path2); IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(path2)); File.Copy(IOUtil.LongSafe(item), IOUtil.LongSafe(path2), overwrite: true); if (num4) { num2++; } else { num++; } } } } LastSummary = string.Format(Loc.T("Pushed to {0}: {1} new · {2} overwritten · {3} WIP skipped"), Target.Name, num, num2, num3); Plugin.Logger.LogInfo((object)("[PushPull] " + LastSummary)); } public static void Pull(bool replace) { if (Target == null) { return; } int num = (replace ? ClearWorkspaceContentRoots() : 0); int copied = 0; int overwritten = 0; if (PackLayout.IsCustomizerPack(Target.Path)) { CopyTree(PackLayout.T2DSheetsRoot(Target.Path), Path.Combine(Plugin.BasePath, "Spritesheets", "T2D"), "*.png", ref copied, ref overwritten); CopyTree(Target.Path, Path.Combine(Plugin.BasePath, "Spritesheets"), "*.png", ref copied, ref overwritten, "Texture2D"); } else { string[] contentRoots = ContentRoots; foreach (string path in contentRoots) { CopyTree(Path.Combine(Target.Path, path), Path.Combine(Plugin.BasePath, path), "*", ref copied, ref overwritten); } } LastSummary = (replace ? string.Format(Loc.T("Pulled (replace) from {0}: {1} removed · {2} copied"), Target.Name, num, copied + overwritten) : string.Format(Loc.T("Pulled (merge) from {0}: {1} new · {2} overwritten"), Target.Name, copied, overwritten)); Plugin.Logger.LogInfo((object)("[PushPull] " + LastSummary)); } private static void CopyTree(string srcRoot, string dstRoot, string pattern, ref int copied, ref int overwritten, string skipTopDir = null) { if (!Directory.Exists(srcRoot)) { return; } foreach (string item in Directory.EnumerateFiles(srcRoot, pattern, SearchOption.AllDirectories)) { string text = item.Substring(srcRoot.Length + 1); if (!IsWipPath(text) && (skipTopDir == null || !text.StartsWith(skipTopDir + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) && !string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(item))) { string path = Path.Combine(dstRoot, text); bool num = File.Exists(path); IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(path)); File.Copy(IOUtil.LongSafe(item), IOUtil.LongSafe(path), overwrite: true); if (num) { overwritten++; } else { copied++; } } } } private static int ClearWorkspaceContentRoots() { int num = 0; string[] contentRoots = ContentRoots; foreach (string path in contentRoots) { string text = Path.Combine(Plugin.BasePath, path); if (!Directory.Exists(text)) { continue; } foreach (string item in Directory.EnumerateFiles(text, "*", SearchOption.AllDirectories).ToList()) { if (!IsWipPath(item.Substring(text.Length + 1))) { File.Delete(IOUtil.LongSafe(item)); num++; } } } return num; } private static bool IsWipPath(string rel) { string[] array = rel.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); for (int i = 0; i < array.Length; i++) { if (array[i].EndsWith("_WIP", StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } } } namespace Stitchwork.Handlers { public static class AnimCanvasAuthoring { private struct FrameGeom { public string SpriteName; public string CollectionName; public string MaterialName; public Texture Atlas; public Vector2[] Uvs; public Vector3[] Positions; public Vector2 TexelSize; public float PxMin; public float PxMax; public float PyMin; public float PyMax; public FlipMode Flipped; } private struct CanvasGeom { public int W; public int H; public int PivotX; public int PivotY; public float PpuX; public float PpuY; } public static string WipDirFor(tk2dSpriteAnimationClip clip) { if (clip?.frames == null) { return null; } tk2dSpriteAnimationFrame[] frames = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames) { tk2dSpriteCollectionData spriteCollection = val.spriteCollection; if (!((Object)(object)spriteCollection == (Object)null) && val.spriteId >= 0 && val.spriteId < spriteCollection.spriteDefinitions.Length) { return Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection).name, T2DUtil.SanitizeForFilesystem(clip.name) + "_WIP"); } } return null; } public static string ExtractClip(tk2dSpriteAnimationClip clip, float marginFraction = 0.5f, int marginFloorPx = 0, bool fromCurrentRender = false) { //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_0409: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Expected O, but got Unknown //IL_05be: Unknown result type (might be due to invalid IL or missing references) if (clip?.frames == null || clip.frames.Length == 0) { return null; } if (!TexUtil.RotateDrawAvailable) { Plugin.Logger.LogError((object)"[AnimWIP] Canvas extract needs the Rotate shader (stitchwork.assetbundle) — unavailable this session."); return null; } string text = WipDirFor(clip); if (text == null) { Plugin.Logger.LogWarning((object)("[AnimWIP] '" + clip?.name + "': can't resolve a sprite load path for this clip.")); return null; } List frames = new List(); HashSet hashSet = new HashSet(); tk2dSpriteAnimationFrame[] frames2 = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames2) { tk2dSpriteCollectionData spriteCollection = val.spriteCollection; if (!((Object)(object)spriteCollection == (Object)null) && val.spriteId >= 0 && val.spriteId < spriteCollection.spriteDefinitions.Length) { tk2dSpriteDefinition val2 = spriteCollection.spriteDefinitions[val.spriteId]; if (!string.IsNullOrEmpty(val2.name) && hashSet.Add(val2.name) && TryBuildFrameGeom(spriteCollection, val2, fromCurrentRender, out var g)) { frames.Add(g); } } } if (frames.Count == 0) { Plugin.Logger.LogWarning((object)("[AnimWIP] '" + clip.name + "': no usable frames to extract.")); return null; } if (frames.Select((FrameGeom f) => f.Flipped).Distinct().Count() > 1) { Plugin.Logger.LogWarning((object)("[AnimWIP] '" + clip.name + "': MIXED flip modes across frames — " + string.Join(", ", frames.Select((FrameGeom f) => $"{f.SpriteName}={f.Flipped}")))); } else { Plugin.Logger.LogInfo((object)$"[AnimWIP] '{clip.name}': flip mode {frames[0].Flipped} (uniform across {frames.Count} frames)."); } Vector2 texel = frames[0].TexelSize; if (frames.Any((FrameGeom f) => f.CollectionName != frames[0].CollectionName)) { Plugin.Logger.LogWarning((object)("[AnimWIP] '" + clip.name + "': frames span multiple collections — canvas assumes one; folder named for '" + frames[0].CollectionName + "'.")); } if (frames.Any((FrameGeom f) => Mathf.Abs(f.TexelSize.x - texel.x) > 1E-06f || Mathf.Abs(f.TexelSize.y - texel.y) > 1E-06f)) { Plugin.Logger.LogWarning((object)("[AnimWIP] '" + clip.name + "': frames have mismatched texelSize — cross-atlas scale differs; registration may drift.")); } CanvasGeom cg = ComputeCanvas(frames, marginFraction, marginFloorPx); int w = cg.W; int h = cg.H; int pivotX = cg.PivotX; int pivotY = cg.PivotY; IOUtil.EnsureDirectoryExists(text); StringBuilder stringBuilder = new StringBuilder(); CultureInfo invariantCulture = CultureInfo.InvariantCulture; stringBuilder.AppendLine("# Stitchwork animation-canvas WIP sidecar — do not hand-edit pivot/canvas."); stringBuilder.AppendLine("animationClip: \"" + clip.name + "\""); stringBuilder.AppendLine("schemaVersion: 6"); stringBuilder.AppendLine($"issuedCanvas: {{ w: {w}, h: {h} }}"); stringBuilder.AppendLine($"pivotInCanvas: {{ x: {w - 1 - pivotX}, y: {pivotY} }}"); stringBuilder.AppendLine("pixelsPerUnit: " + (1f / texel.x).ToString("0.####", invariantCulture)); stringBuilder.AppendLine("marginFraction: " + marginFraction.ToString(invariantCulture)); stringBuilder.AppendLine("frames:"); float ppuX = cg.PpuX; float ppuY = cg.PpuY; RenderTexture active = RenderTexture.active; RenderTexture temporary = RenderTexture.GetTemporary(w, h, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Texture2D val3 = new Texture2D(w, h, (TextureFormat)4, false); Dictionary dictionary = new Dictionary(); try { int num = 0; foreach (FrameGeom item in frames) { RenderFrameInto(temporary, val3, item, cg, dictionary); string text2 = $"{num:D3}_{T2DUtil.SanitizeForFilesystem(item.SpriteName)}.png"; File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(text, text2)), ImageConversion.EncodeToPNG(val3)); int num2 = Mathf.RoundToInt((float)pivotX + item.PxMin * ppuX); int num3 = Mathf.RoundToInt((float)pivotX + item.PxMax * ppuX) - num2; int num4 = w - num2 - num3; int num5 = Mathf.RoundToInt((float)pivotY + item.PyMin * ppuY); int num6 = Mathf.RoundToInt((float)pivotY + item.PyMax * ppuY) - num5; stringBuilder.AppendLine(" - spriteName: \"" + item.SpriteName + "\""); stringBuilder.AppendLine(" collection: \"" + item.CollectionName + "\""); stringBuilder.AppendLine(" material: \"" + item.MaterialName + "\""); stringBuilder.AppendLine(" dumpedFile: \"" + text2 + "\""); stringBuilder.AppendLine($" baselineHash: \"{AnimCanvasCompile.HashPixels(val3.GetPixels32()):x16}\""); stringBuilder.AppendLine($" canvasRect: {{ x: {num4}, y: {num5}, w: {num3}, h: {num6} }}"); stringBuilder.AppendLine($" flipped: {item.Flipped}"); num++; } } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)val3); foreach (RenderTexture value in dictionary.Values) { if ((Object)(object)value != (Object)null) { RenderTexture.ReleaseTemporary(value); } } } string path = T2DUtil.SanitizeForFilesystem(clip.name) + "_sidecar.yml"; File.WriteAllText(IOUtil.LongSafe(Path.Combine(text, path)), stringBuilder.ToString()); Plugin.Logger.LogInfo((object)($"[AnimWIP] '{clip.name}': {frames.Count} frame(s) → {w}×{h}, pivot ({pivotX},{pivotY}) " + "[source: " + (fromCurrentRender ? "current render" : "vanilla") + "] → " + text)); return text; } private static bool TryBuildFrameGeom(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def, bool fromCurrentRender, out FrameGeom g) { //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) g = default(FrameGeom); if (string.IsNullOrEmpty(def.name) || def.positions == null || def.positions.Length < 4) { return false; } if (def.uvs == null || def.uvs.Length < 4) { return false; } if (def.texelSize.x <= 0f || def.texelSize.y <= 0f) { return false; } Texture atlas = null; Vector2[] uvs = null; Vector3[] positions = null; if (fromCurrentRender || !SpriteLoader.TryGetVanilla(coll, def, out atlas, out uvs, out positions)) { Material val = (((Object)(object)def.materialInst != (Object)null) ? def.materialInst : def.material); atlas = (((Object)(object)val != (Object)null) ? val.mainTexture : null); uvs = def.uvs; positions = def.positions; } if ((Object)(object)atlas == (Object)null || uvs == null || uvs.Length < 4 || positions == null || positions.Length < 4) { return false; } Material val2 = ((def.materialId >= 0 && coll.materials != null && def.materialId < coll.materials.Length) ? coll.materials[def.materialId] : null); string materialName = (((Object)(object)val2 != (Object)null) ? ((Object)val2).name.Split(' ')[0] : ""); g = new FrameGeom { SpriteName = def.name, CollectionName = ((Object)coll).name, MaterialName = materialName, Atlas = atlas, Uvs = uvs, Positions = positions, TexelSize = def.texelSize, PxMin = positions.Min((Vector3 p) => p.x), PxMax = positions.Max((Vector3 p) => p.x), PyMin = positions.Min((Vector3 p) => p.y), PyMax = positions.Max((Vector3 p) => p.y), Flipped = def.flipped }; return true; } private static CanvasGeom ComputeCanvas(List frames, float marginFraction, int marginFloorPx) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) float num = frames.Min((FrameGeom f) => f.PxMin); float num2 = frames.Max((FrameGeom f) => f.PxMax); float num3 = frames.Min((FrameGeom f) => f.PyMin); float num4 = frames.Max((FrameGeom f) => f.PyMax); Vector2 texelSize = frames[0].TexelSize; int num5 = (int)((num2 - num) / texelSize.x) + 1; int num6 = (int)((num4 - num3) / texelSize.y) + 1; int num7 = Mathf.Max(Mathf.CeilToInt(marginFraction * (float)num5), marginFloorPx); int num8 = Mathf.Max(Mathf.CeilToInt(marginFraction * (float)num6), marginFloorPx); return new CanvasGeom { W = num5 + 2 * num7, H = num6 + 2 * num8, PivotX = (int)((0f - num) / texelSize.x) + num7, PivotY = (int)((0f - num3) / texelSize.y) + num8, PpuX = 1f / texelSize.x, PpuY = 1f / texelSize.y }; } private static void RenderFrameInto(RenderTexture rt, Texture2D readback, FrameGeom f, CanvasGeom cg, Dictionary pointAtlas) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_004d: 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_00fe: Unknown result type (might be due to invalid IL or missing references) RenderTexture pointAtlasRT = GetPointAtlasRT(f.Atlas, pointAtlas); RenderTexture.active = rt; GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)cg.W, 0f, (float)cg.H); TexUtil.SetPassTextured((Texture)(object)pointAtlasRT); GL.Begin(7); GL.Color(Color.white); int[] array = new int[4] { 0, 1, 3, 2 }; foreach (int num in array) { GL.TexCoord(Vector2.op_Implicit(f.Uvs[num])); GL.Vertex3((float)cg.PivotX + f.Positions[num].x * cg.PpuX, (float)cg.PivotY + f.Positions[num].y * cg.PpuY, 0f); } GL.End(); GL.PopMatrix(); readback.ReadPixels(new Rect(0f, 0f, (float)cg.W, (float)cg.H), 0, 0); readback.Apply(); FlipHorizontal(readback, cg.W, cg.H); } public static List RenderClipSequence(tk2dSpriteAnimationClip clip, bool fromCurrentRender, float marginFraction, out int width, out int height) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown width = (height = 0); if (clip?.frames == null || clip.frames.Length == 0) { return null; } if (!TexUtil.RotateDrawAvailable) { Plugin.Logger.LogError((object)"[AnimWIP] Clip render needs the Rotate shader (stitchwork.assetbundle) — unavailable this session."); return null; } Dictionary dictionary = new Dictionary(); List list = new List(); tk2dSpriteAnimationFrame[] frames = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames) { tk2dSpriteCollectionData spriteCollection = val.spriteCollection; if ((Object)(object)spriteCollection == (Object)null || val.spriteId < 0 || val.spriteId >= spriteCollection.spriteDefinitions.Length) { continue; } tk2dSpriteDefinition val2 = spriteCollection.spriteDefinitions[val.spriteId]; if (string.IsNullOrEmpty(val2.name)) { continue; } if (!dictionary.ContainsKey(val2.name)) { if (!TryBuildFrameGeom(spriteCollection, val2, fromCurrentRender, out var g)) { continue; } dictionary[val2.name] = g; } list.Add(val2.name); } if (list.Count == 0) { return null; } CanvasGeom cg = ComputeCanvas(dictionary.Values.ToList(), marginFraction, 0); width = cg.W; height = cg.H; List list2 = new List(list.Count); RenderTexture active = RenderTexture.active; RenderTexture temporary = RenderTexture.GetTemporary(cg.W, cg.H, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Texture2D val3 = new Texture2D(cg.W, cg.H, (TextureFormat)4, false); Dictionary dictionary2 = new Dictionary(); try { foreach (string item in list) { RenderFrameInto(temporary, val3, dictionary[item], cg, dictionary2); list2.Add(val3.GetPixels32()); } return list2; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)val3); foreach (RenderTexture value in dictionary2.Values) { if ((Object)(object)value != (Object)null) { RenderTexture.ReleaseTemporary(value); } } } } private static RenderTexture GetPointAtlasRT(Texture atlas, Dictionary cache) { int instanceID = ((Object)atlas).GetInstanceID(); if (cache.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null) { return value; } RenderTexture temporary = RenderTexture.GetTemporary(atlas.width, atlas.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); ((Texture)temporary).filterMode = (FilterMode)0; Graphics.Blit(atlas, temporary); cache[instanceID] = temporary; return temporary; } private static void FlipHorizontal(Texture2D tex, int w, int h) { //IL_0028: 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) Color32[] pixels = tex.GetPixels32(); Color32[] array = (Color32[])(object)new Color32[pixels.Length]; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { array[i * w + j] = pixels[i * w + (w - 1 - j)]; } } tex.SetPixels32(array); tex.Apply(); } } public static class AnimCanvasBatch { public enum BatchMode { Extract, Compile } private static bool _cancel; private static int _sceneAtStart; public static bool IsRunning { get; private set; } public static BatchMode Mode { get; private set; } public static int Total { get; private set; } public static int Processed { get; private set; } public static string CurrentClip { get; private set; } = ""; public static float StartedAt { get; private set; } public static int Ok { get; private set; } public static int Skipped { get; private set; } public static int Failed { get; private set; } public static void ExtractAll(List clips, bool fromCurrentRender) { if (CanStart(clips)) { if (!TexUtil.RotateDrawAvailable) { Plugin.Logger.LogError((object)"[AnimBatch] Canvas extract needs the Rotate shader (stitchwork.assetbundle) — unavailable this session."); return; } Begin(BatchMode.Extract, clips.Count); ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(clips, fromCurrentRender)); } } public static void CompileAll(List clips) { if (CanStart(clips)) { Begin(BatchMode.Compile, clips.Count); ((MonoBehaviour)Plugin.Instance).StartCoroutine(Run(clips, fromCurrentRender: false)); } } public static void Cancel() { _cancel = true; } public static List WithWorkspace(List clips) { List list = new List(); if (clips == null) { return list; } foreach (tk2dSpriteAnimationClip clip in clips) { string text = AnimCanvasAuthoring.WipDirFor(clip); if (text != null && Directory.Exists(IOUtil.LongSafe(text)) && File.Exists(IOUtil.LongSafe(Path.Combine(text, T2DUtil.SanitizeForFilesystem(clip.name) + "_sidecar.yml")))) { list.Add(clip); } } return list; } private static bool CanStart(List clips) { if (!IsRunning && !SceneTraverser.IsTraversing && clips != null) { return clips.Count > 0; } return false; } private static void Begin(BatchMode mode, int total) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Mode = mode; Total = total; Processed = 0; Ok = 0; Skipped = 0; Failed = 0; CurrentClip = ""; _cancel = false; Scene activeScene = SceneManager.GetActiveScene(); _sceneAtStart = ((Scene)(ref activeScene)).handle; StartedAt = Time.realtimeSinceStartup; IsRunning = true; Plugin.Logger.LogInfo((object)string.Format("[AnimBatch] {0} started: {1} clips.", (mode == BatchMode.Extract) ? "Extract" : "Compile", total)); } private static IEnumerator Run(List clips, bool fromCurrentRender) { foreach (tk2dSpriteAnimationClip clip in clips) { if (_cancel) { Plugin.Logger.LogInfo((object)$"[AnimBatch] Cancelled at {Processed}/{Total}."); break; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).handle != _sceneAtStart) { Plugin.Logger.LogWarning((object)$"[AnimBatch] Scene changed — stopped at {Processed}/{Total}. Restart the batch in the new scene."); break; } CurrentClip = clip?.name ?? "?"; try { if (Mode == BatchMode.Extract) { if (AnimCanvasAuthoring.ExtractClip(clip, 0.5f, 0, fromCurrentRender) != null) { Ok++; } else { Skipped++; } } else if (AnimCanvasCompile.CompileClip(clip).Count > 0) { Ok++; } else { Skipped++; } } catch (Exception arg) { Failed++; Plugin.Logger.LogError((object)$"[AnimBatch] '{CurrentClip}' failed: {arg}"); } Processed++; yield return null; } float num = (Time.realtimeSinceStartup - StartedAt) / 60f; Plugin.Logger.LogInfo((object)("[AnimBatch] " + ((Mode == BatchMode.Extract) ? "Extract" : "Compile") + " done: " + $"{Ok} ok, {Skipped} skipped, {Failed} failed of {Total} ({num:F1} min).")); if (Mode == BatchMode.Extract && Ok > 0) { IOUtil.OpenPath(SpriteLoader.LoadPath); } IsRunning = false; } } public static class AnimCanvasCompile { public struct FrameOutcome { public string Sprite; public string Kind; public string Detail; } private class Sidecar { public int CanvasW; public int CanvasH; public int PivotX; public int PivotY; public readonly List Frames = new List(); } private class FrameEntry { public string Sprite; public string Collection; public string Material; public string DumpedFile; public int RectX; public int RectY; public int RectW; public int RectH; public string Flipped = "Tk2d"; public string BaselineHash; } private const int StrayTolerance = 2; private static int _faintThisClip; private static string _displacedDir; private static int _displacedCount; private static byte AlphaThreshold => (byte)Mathf.Clamp(Plugin.Config?.AnimCompileAlphaThreshold ?? 16, 1, 255); public static List CompileClip(tk2dSpriteAnimationClip clip, bool forClipOnly = false) { List list = new List(); string text = AnimCanvasAuthoring.WipDirFor(clip); if (string.IsNullOrEmpty(text) || !Directory.Exists(text)) { string text2 = (string.IsNullOrEmpty(text) ? null : SameNameWipsElsewhere(text)); Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip?.name + "': no WIP folder — extract first." + ((text2 != null) ? (" NOTE: a same-named workspace exists at " + text2 + " — that is a same-named clip on ANOTHER rig (e.g. regular vs cloakless Hornet both have 'idle'). Check the clip list's Collection line and select that rig's clip to compile its workspace.") : ""))); return list; } string clipDest = null; if (forClipOnly) { if (clip.name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip.name + "': clip name isn't filesystem-safe — per-clip compile unavailable for it.")); return list; } clipDest = clip.name; } string path = Path.Combine(text, T2DUtil.SanitizeForFilesystem(clip.name) + "_sidecar.yml"); if (!File.Exists(path)) { string text3 = SameNameWipsElsewhere(text); Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip.name + "': sidecar missing — re-extract." + ((text3 != null) ? (" NOTE: a same-named workspace exists at " + text3 + " — likely the same-named clip on another rig; select that rig's clip (see the clip list's Collection line) to compile it.") : ""))); return list; } Sidecar sc = ParseSidecar(File.ReadAllLines(path)); return CompileParsed(clip, text, sc, clipDest, list); } private static string SameNameWipsElsewhere(string expectedDir) { try { if (!Directory.Exists(SpriteLoader.LoadPath)) { return null; } string fileName = Path.GetFileName(expectedDir); List list = null; string[] directories = Directory.GetDirectories(SpriteLoader.LoadPath); foreach (string text in directories) { string text2 = Path.Combine(text, fileName); if (!string.Equals(text2, expectedDir, StringComparison.OrdinalIgnoreCase) && Directory.Exists(text2)) { (list ?? (list = new List())).Add(Path.GetFileName(text) + "/" + fileName); } } return (list == null) ? null : string.Join(", ", list); } catch (IOException) { return null; } } private static List CompileParsed(tk2dSpriteAnimationClip clip, string dir, Sidecar sc, string clipDest, List outcomes) { if (sc == null || sc.CanvasW <= 0 || sc.CanvasH <= 0) { Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip.name + "': sidecar unparseable or missing canvas dims.")); return outcomes; } Plugin.Logger.LogInfo((object)($"[AnimCompile] '{clip.name}': compiling {sc.Frames.Count} frame(s) from {dir}" + ((clipDest != null) ? (" → per-clip (Clips/" + clipDest + "/)") : ""))); _faintThisClip = 0; _displacedCount = 0; _displacedDir = Path.Combine(dir, "_displaced-" + DateTime.Now.ToString("yyyyMMdd-HHmmss")); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (clip != null && clip.frames != null) { tk2dSpriteAnimationFrame[] frames = clip.frames; foreach (tk2dSpriteAnimationFrame val in frames) { tk2dSpriteCollectionData spriteCollection = val.spriteCollection; if (!((Object)(object)spriteCollection == (Object)null) && spriteCollection.spriteDefinitions != null && val.spriteId >= 0 && val.spriteId < spriteCollection.spriteDefinitions.Length) { tk2dSpriteDefinition val2 = spriteCollection.spriteDefinitions[val.spriteId]; if (val2 != null && !string.IsNullOrEmpty(val2.name)) { dictionary[val2.name] = ((!dictionary.TryGetValue(val2.name, out var value)) ? 1 : (value + 1)); } } } } bool flag = false; foreach (FrameEntry frame in sc.Frames) { if (string.IsNullOrEmpty(frame.BaselineHash)) { flag = true; } FrameOutcome item = CompileFrame(dir, sc, frame, clipDest); outcomes.Add(item); Plugin.Logger.LogInfo((object)("[AnimCompile] " + item.Sprite + ": " + item.Kind + " — " + item.Detail)); if ((item.Kind == "inbounds" || item.Kind == "spill") && clipDest == null && dictionary.TryGetValue(item.Sprite, out var value2) && value2 > 1) { Plugin.Logger.LogWarning((object)($"[AnimCompile] '{item.Sprite}' is used ×{value2} in this clip (and possibly by other clips) — " + "shared art changes everywhere it appears. For per-clip divergence use the per-clip compile (Appliqué Clips/ output), which is collision-free by construction.")); } } if (flag) { Plugin.Logger.LogInfo((object)("[AnimCompile] '" + clip.name + "': sidecar predates dirty-frame detection (schema 5) — every readable frame was written. Re-extract to enable unchanged-frame skipping.")); } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; foreach (FrameOutcome outcome in outcomes) { if (outcome.Kind == "inbounds") { num++; } else if (outcome.Kind == "spill") { num2++; } else if (outcome.Kind == "unchanged") { num3++; } else { num4++; } } Plugin.Logger.LogInfo((object)($"[AnimCompile] '{clip.name}': done — {num} in-bounds, {num2} spill, {num3} unchanged (not written), " + string.Format("{0} skipped{1}.", num4, (_displacedCount > 0) ? $"; {_displacedCount} prior file(s) backed up to {Path.GetFileName(_displacedDir)}/" : ""))); if (_faintThisClip > 0 && AlphaThreshold > 1) { Plugin.Logger.LogInfo((object)($"[AnimCompile] '{clip.name}': {_faintThisClip} faint pixel(s) below alpha {AlphaThreshold} were " + "treated as empty. Painting soft glow/bloom/haze? Lower AnimCompileAlphaThreshold in the config so the fringe survives the cut (keep the canvas free of stray faint pixels at low values).")); } return outcomes; } private static FrameOutcome CompileFrame(string dir, Sidecar sc, FrameEntry fe, string clipDest = null) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown string path = Path.Combine(dir, fe.DumpedFile); if (!File.Exists(IOUtil.LongSafe(path))) { return Skip(fe.Sprite, "edited PNG missing: " + fe.DumpedFile); } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, File.ReadAllBytes(IOUtil.LongSafe(path)))) { Object.Destroy((Object)(object)val); return Skip(fe.Sprite, "PNG decode failed"); } try { int width = ((Texture)val).width; int height = ((Texture)val).height; if (width != sc.CanvasW || height != sc.CanvasH) { return Skip(fe.Sprite, $"canvas resized in editor ({width}x{height} vs issued {sc.CanvasW}x{sc.CanvasH}) — re-dump, don't guess"); } int rectX = fe.RectX; int rectY = fe.RectY; int num = fe.RectX + fe.RectW; int num2 = fe.RectY + fe.RectH; Color32[] pixels = val.GetPixels32(); if (!string.IsNullOrEmpty(fe.BaselineHash) && string.Equals($"{HashPixels(pixels):x16}", fe.BaselineHash, StringComparison.OrdinalIgnoreCase)) { return new FrameOutcome { Sprite = fe.Sprite, Kind = "unchanged", Detail = "canvas matches the extract baseline — not written" }; } int num3 = 0; byte alphaThreshold = AlphaThreshold; int num4 = int.MaxValue; int num5 = int.MaxValue; int num6 = int.MinValue; int num7 = int.MinValue; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { byte a = pixels[i * width + j].a; if (a < alphaThreshold) { if (a > 0) { _faintThisClip++; } continue; } if (j < num4) { num4 = j; } if (j > num6) { num6 = j; } if (i < num5) { num5 = i; } if (i > num7) { num7 = i; } if (j < rectX || j >= num || i < rectY || i >= num2) { num3++; } } } string text = ((clipDest == null) ? Path.Combine(SpriteLoader.LoadPath, fe.Collection, fe.Material) : Path.Combine(SpriteLoader.LoadPath, fe.Collection, "Clips", clipDest)); IOUtil.EnsureDirectoryExists(text); bool flag = clipDest != null || string.Equals(fe.Flipped, "None", StringComparison.OrdinalIgnoreCase); if (num3 <= 2) { byte[] bytes = CropPng(pixels, width, height, rectX, rectY, fe.RectW, fe.RectH, flag); DisplaceSpriteFiles(text, fe.Sprite); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(text, fe.Sprite + ".png")), bytes); string text2 = ((num3 > 0) ? $"; {num3} stray px outside ignored" : ""); return new FrameOutcome { Sprite = fe.Sprite, Kind = "inbounds", Detail = $"{fe.RectW}x{fe.RectH} → {fe.Sprite}.png{text2} [{fe.Flipped}]" }; } int num8 = Mathf.Min(num4, rectX); int num9 = Mathf.Min(num5, rectY); int num10 = Mathf.Max(num6, num - 1); int num11 = Mathf.Max(num7, num2 - 1); int num12 = num10 - num8 + 1; int num13 = num11 - num9 + 1; bool num14 = num8 <= 0 || num9 <= 0 || num10 >= width - 1 || num11 >= height - 1; int num15 = sc.PivotX - num8; int num16 = sc.PivotY - num9; if (flag) { num15 = num12 - 1 - num15; } byte[] bytes2 = CropPng(pixels, width, height, num8, num9, num12, num13, flag); string text3 = $"{fe.Sprite}@p{num15}_{num16}.png"; DisplaceSpriteFiles(text, fe.Sprite); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(text, text3)), bytes2); string text4 = (num14 ? "; WARNING content at canvas edge — may be clipped, use More Room" : ""); return new FrameOutcome { Sprite = fe.Sprite, Kind = "spill", Detail = $"{num12}x{num13} @p{num15}_{num16} ({num3} px outside) → {text3}{text4}" }; } finally { Object.Destroy((Object)(object)val); } } private static FrameOutcome Skip(string sprite, string why) { return new FrameOutcome { Sprite = sprite, Kind = "skip-error", Detail = why }; } internal static ulong HashPixels(Color32[] px) { ulong num = 14695981039346656037uL; for (int i = 0; i < px.Length; i++) { num = (num ^ px[i].r) * 1099511628211L; num = (num ^ px[i].g) * 1099511628211L; num = (num ^ px[i].b) * 1099511628211L; num = (num ^ px[i].a) * 1099511628211L; } return num; } private static void DisplaceSpriteFiles(string outDir, string sprite) { if (!Directory.Exists(outDir)) { return; } string[] array = new string[2] { sprite + ".png", sprite + "@*.png" }; foreach (string searchPattern in array) { string[] files = Directory.GetFiles(outDir, searchPattern, SearchOption.TopDirectoryOnly); foreach (string path in files) { IOUtil.EnsureDirectoryExists(_displacedDir); File.Copy(IOUtil.LongSafe(path), IOUtil.LongSafe(Path.Combine(_displacedDir, Path.GetFileName(path))), overwrite: true); File.Delete(IOUtil.LongSafe(path)); _displacedCount++; } } } private static byte[] CropPng(Color32[] src, int srcW, int srcH, int x0, int y0, int w, int h, bool flipX = false) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_001f: Expected O, but got Unknown //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00b5: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) if (w <= 0 || h <= 0) { Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); byte[] result = ImageConversion.EncodeToPNG(val); Object.Destroy((Object)val); return result; } Color32[] array = (Color32[])(object)new Color32[w * h]; for (int i = 0; i < h; i++) { int num = y0 + i; for (int j = 0; j < w; j++) { int num2 = x0 + (flipX ? (w - 1 - j) : j); if (num2 >= 0 && num2 < srcW && num >= 0 && num < srcH) { array[i * w + j] = src[num * srcW + num2]; } } } Texture2D val2 = new Texture2D(w, h, (TextureFormat)4, false); val2.SetPixels32(array); val2.Apply(); byte[] result2 = ImageConversion.EncodeToPNG(val2); Object.Destroy((Object)val2); return result2; } private static Sidecar ParseSidecar(string[] lines) { Sidecar sidecar = new Sidecar(); FrameEntry frameEntry = null; for (int i = 0; i < lines.Length; i++) { string text = lines[i].Trim(); if (text.Length != 0 && text[0] != '#') { if (text.StartsWith("issuedCanvas:")) { sidecar.CanvasW = IntAfter(text, "w:"); sidecar.CanvasH = IntAfter(text, "h:"); } else if (text.StartsWith("pivotInCanvas:")) { sidecar.PivotX = IntAfter(text, "x:"); sidecar.PivotY = IntAfter(text, "y:"); } else if (text.StartsWith("- spriteName:")) { frameEntry = new FrameEntry { Sprite = ParseStr(text) }; sidecar.Frames.Add(frameEntry); } else if (frameEntry != null && text.StartsWith("collection:")) { frameEntry.Collection = ParseStr(text); } else if (frameEntry != null && text.StartsWith("material:")) { frameEntry.Material = ParseStr(text); } else if (frameEntry != null && text.StartsWith("dumpedFile:")) { frameEntry.DumpedFile = ParseStr(text); } else if (frameEntry != null && text.StartsWith("canvasRect:")) { frameEntry.RectX = IntAfter(text, "x:"); frameEntry.RectY = IntAfter(text, "y:"); frameEntry.RectW = IntAfter(text, "w:"); frameEntry.RectH = IntAfter(text, "h:"); } else if (frameEntry != null && text.StartsWith("flipped:")) { frameEntry.Flipped = text.Substring(text.IndexOf(':') + 1).Trim(); } else if (frameEntry != null && text.StartsWith("baselineHash:")) { frameEntry.BaselineHash = ParseStr(text); } } } if (sidecar.Frames.Count <= 0) { return null; } return sidecar; } private static string ParseStr(string line) { int num = line.IndexOf('"'); if (num < 0) { return ""; } int num2 = line.IndexOf('"', num + 1); if (num2 < 0) { return ""; } return line.Substring(num + 1, num2 - num - 1); } private static int IntAfter(string line, string key) { int num = line.IndexOf(key, StringComparison.Ordinal); if (num < 0) { return 0; } int i; for (i = num + key.Length; i < line.Length && line[i] == ' '; i++) { } int num2 = i; if (i < line.Length && (line[i] == '-' || line[i] == '+')) { i++; } for (; i < line.Length && char.IsDigit(line[i]); i++) { } if (!int.TryParse(line.Substring(num2, i - num2), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return 0; } return result; } } internal static class AppliqueLoader { private sealed class Entry { public string CollName; public string ClipName; public string SpriteName; public string Anchor; public string FullPath; public string SourcePack; } private sealed class ShadowSprite { public Texture2D Tex; public Material Mat; public string LoadedPath; } private const string LogPrefix = "[Applique]"; internal const string ShadowNamePrefix = "Stitchwork_Shadow"; private static readonly Dictionary _index = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _shadows = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _shadowSprites = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _shadowCollIds = new HashSet(); private static readonly Dictionary> _backups = new Dictionary>(); private static bool _rotatedNoteShown; internal static int IndexCount => _index.Count; internal static int IndexPackCount { get { int num = 0; foreach (Entry value in _index.Values) { if (value.SourcePack != null) { num++; } } return num; } } internal static void ClearIndex() { _index.Clear(); } internal static bool HasClipOverride(string collName, string clipName, string spriteName) { if (collName != null && clipName != null && spriteName != null) { return _index.ContainsKey(collName + "/" + clipName + "/" + spriteName); } return false; } internal static bool HasAnyClipOverridesFor(string collName) { if (string.IsNullOrEmpty(collName)) { return false; } foreach (Entry value in _index.Values) { if (string.Equals(value.CollName, collName, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static void IndexFile(string rel, string fullPath, string sourcePack) { string[] array = rel.Split('/'); if (array.Length != 4 || array[3].Length <= 4 || !array[3].EndsWith(".png", StringComparison.OrdinalIgnoreCase)) { Plugin.Logger.LogWarning((object)("[Applique] Ignoring '" + rel + "' — expected Sprites//Clips//.png.")); return; } (string name, string anchor) tuple = IOUtil.ParseAnchorTag(array[3].Substring(0, array[3].Length - 4)); string item = tuple.name; string item2 = tuple.anchor; string key = array[0] + "/" + array[2] + "/" + item; if (_index.TryGetValue(key, out var value)) { if (!IOUtil.BenignDuplicate(value.FullPath, fullPath)) { ConflictTracker.Record("clip", key, value.SourcePack, sourcePack, Path.GetFileName(value.FullPath) + " › " + Path.GetFileName(fullPath), value.FullPath, fullPath); } return; } _index[key] = new Entry { CollName = array[0], ClipName = array[2], SpriteName = item, Anchor = item2, FullPath = fullPath, SourcePack = sourcePack }; } internal static void Apply() { int num = RestoreAll(); if (!Plugin.Config.EnableApplique || _index.Count == 0) { if (num > 0) { Plugin.Logger.LogInfo((object)string.Format("{0} Reverted {1} frame entr(ies); no active clip overrides.", "[Applique]", num)); } return; } Dictionary> dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); tk2dSpriteAnimation[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteAnimation val in array) { if ((Object)(object)val == (Object)null || val.clips == null) { continue; } tk2dSpriteAnimationClip[] clips = val.clips; foreach (tk2dSpriteAnimationClip val2 in clips) { if (val2 != null && !string.IsNullOrEmpty(val2.name) && val2.frames != null) { if (!dictionary.TryGetValue(val2.name, out var value)) { value = (dictionary[val2.name] = new List()); } value.Add(val2); } } } int num2 = 0; int num3 = 0; List list2 = new List(); foreach (Entry value4 in _index.Values) { if (!dictionary.TryGetValue(value4.ClipName, out var value2)) { list2.Add(value4.ClipName); continue; } int num4 = -1; bool flag = false; bool flag2 = false; tk2dSpriteCollectionData spriteCollection = null; foreach (tk2dSpriteAnimationClip item in value2) { for (int k = 0; k < item.frames.Length; k++) { tk2dSpriteAnimationFrame val3 = item.frames[k]; tk2dSpriteCollectionData spriteCollection2 = val3.spriteCollection; if ((Object)(object)spriteCollection2 == (Object)null || !string.Equals(((Object)spriteCollection2).name, value4.CollName, StringComparison.OrdinalIgnoreCase) || spriteCollection2.spriteDefinitions == null || val3.spriteId < 0 || val3.spriteId >= spriteCollection2.spriteDefinitions.Length) { continue; } tk2dSpriteDefinition val4 = spriteCollection2.spriteDefinitions[val3.spriteId]; if (val4 == null || !string.Equals(val4.name, value4.SpriteName, StringComparison.OrdinalIgnoreCase)) { continue; } if (num4 < 0) { if (flag) { flag2 = true; break; } flag = true; (spriteCollection, num4) = BuildShadowDef(value4, spriteCollection2, val4); if (num4 < 0) { flag2 = true; break; } num3++; } if (!_backups.TryGetValue(item, out List<(int, tk2dSpriteCollectionData, int)> value3)) { value3 = (_backups[item] = new List<(int, tk2dSpriteCollectionData, int)>()); } value3.Add((k, spriteCollection2, val3.spriteId)); item.frames[k].spriteCollection = spriteCollection; item.frames[k].spriteId = num4; num2++; } if (flag2) { break; } } } if (num2 > 0 || num > 0 || list2.Count > 0) { List list4 = list2.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); string arg = ((list4.Count > 0) ? string.Format("; {0} clip name(s) not loaded yet ({1}{2})", list4.Count, string.Join(", ", list4.Take(4)), (list4.Count > 4) ? ", …" : "") : ""); Plugin.Logger.LogInfo((object)(string.Format("{0} {1} frame entr(ies) diverted across {2} clip-sprite(s) ", "[Applique]", num2, num3) + $"({_index.Count} indexed; {num} restored first){arg}")); } } private static int RestoreAll() { int num = 0; foreach (KeyValuePair> backup in _backups) { tk2dSpriteAnimationClip key = backup.Key; if (key == null || key.frames == null) { continue; } foreach (var (num2, spriteCollection, spriteId) in backup.Value) { if (num2 >= 0 && num2 < key.frames.Length) { tk2dSpriteCollectionData spriteCollection2 = key.frames[num2].spriteCollection; if (!((Object)(object)spriteCollection2 == (Object)null) && _shadowCollIds.Contains(((Object)spriteCollection2).GetInstanceID())) { key.frames[num2].spriteCollection = spriteCollection; key.frames[num2].spriteId = spriteId; num++; } } } } _backups.Clear(); return num; } private static (tk2dSpriteCollectionData shadow, int id) BuildShadowDef(Entry entry, tk2dSpriteCollectionData vanillaColl, tk2dSpriteDefinition vanillaDef) { //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: 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_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Invalid comparison between Unknown and I4 //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0590: Unknown result type (might be due to invalid IL or missing references) //IL_0636: Unknown result type (might be due to invalid IL or missing references) //IL_063b: Unknown result type (might be due to invalid IL or missing references) //IL_06ce: Unknown result type (might be due to invalid IL or missing references) //IL_06d5: Expected O, but got Unknown //IL_066e: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_078f: Unknown result type (might be due to invalid IL or missing references) //IL_07a8: Unknown result type (might be due to invalid IL or missing references) //IL_07ad: Unknown result type (might be due to invalid IL or missing references) //IL_07bc: Unknown result type (might be due to invalid IL or missing references) //IL_07be: Unknown result type (might be due to invalid IL or missing references) //IL_07c5: Unknown result type (might be due to invalid IL or missing references) //IL_07c7: Unknown result type (might be due to invalid IL or missing references) //IL_07db: Unknown result type (might be due to invalid IL or missing references) //IL_07dd: Unknown result type (might be due to invalid IL or missing references) //IL_07e4: Unknown result type (might be due to invalid IL or missing references) //IL_07e6: Unknown result type (might be due to invalid IL or missing references) byte[] array; try { array = File.ReadAllBytes(IOUtil.LongSafe(entry.FullPath)); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Applique] Read failed for '" + entry.FullPath + "': " + ex.Message)); return (shadow: null, id: -1); } if (vanillaDef.complexGeometry) { Plugin.Logger.LogWarning((object)("[Applique] '" + entry.SpriteName + "' has complex geometry — per-clip override skipped (unsupported in v1).")); return (shadow: null, id: -1); } string key = entry.CollName + "/" + entry.ClipName + "/" + entry.SpriteName; if (!_shadowSprites.TryGetValue(key, out var value)) { value = (_shadowSprites[key] = new ShadowSprite()); } if ((Object)(object)value.Tex == (Object)null) { value.Tex = new Texture2D(2, 2, (TextureFormat)4, false) { hideFlags = (HideFlags)32, wrapMode = (TextureWrapMode)1 }; } if (!ImageConversion.LoadImage(value.Tex, array)) { Plugin.Logger.LogWarning((object)("[Applique] PNG decode failed for '" + entry.FullPath + "'")); return (shadow: null, id: -1); } value.LoadedPath = entry.FullPath; Material val = (((Object)(object)vanillaDef.materialInst != (Object)null) ? vanillaDef.materialInst : vanillaDef.material); if ((Object)(object)value.Mat == (Object)null) { value.Mat = new Material(val) { hideFlags = (HideFlags)32 }; } value.Mat.mainTexture = (Texture)(object)value.Tex; Vector3[] positions = vanillaDef.positions; float num = float.MaxValue; float num2 = float.MinValue; float num3 = float.MaxValue; float num4 = float.MinValue; Vector3[] array2 = positions; foreach (Vector3 val2 in array2) { if (val2.x < num) { num = val2.x; } if (val2.x > num2) { num2 = val2.x; } if (val2.y < num3) { num3 = val2.y; } if (val2.y > num4) { num4 = val2.y; } } float num5 = num2 - num; float num6 = num4 - num3; if (num5 < 1E-06f || num6 < 1E-06f) { Plugin.Logger.LogWarning((object)("[Applique] Degenerate vanilla quad for '" + entry.SpriteName + "' — skipped.")); return (shadow: null, id: -1); } Texture val3 = (((Object)(object)val != (Object)null) ? val.mainTexture : null); bool flag = (int)vanillaDef.flipped > 0; float num7 = float.MaxValue; float num8 = float.MinValue; float num9 = float.MaxValue; float num10 = float.MinValue; Vector2[] uvs = vanillaDef.uvs; foreach (Vector2 val4 in uvs) { if (val4.x < num7) { num7 = val4.x; } if (val4.x > num8) { num8 = val4.x; } if (val4.y < num9) { num9 = val4.y; } if (val4.y > num10) { num10 = val4.y; } } float num11 = (num8 - num7) * (float)((!((Object)(object)val3 != (Object)null)) ? 1 : val3.width); float num12 = (num10 - num9) * (float)((!((Object)(object)val3 != (Object)null)) ? 1 : val3.height); int num13 = Mathf.Max(1, Mathf.RoundToInt(flag ? num12 : num11)); int num14 = Mathf.Max(1, Mathf.RoundToInt(flag ? num11 : num12)); float num15 = num5 / (float)num13; float num16 = num6 / (float)num14; int width = ((Texture)value.Tex).width; int height = ((Texture)value.Tex).height; int num17 = width - num13; int num18 = height - num14; string text = entry.Anchor ?? "bottom-center"; float num19; float num20; float num21; float num22; if (IOUtil.TryParsePivotAnchor(text, out var px, out var py)) { num19 = (float)(-px) * num15; num20 = (float)(width - px) * num15; num21 = (float)(-py) * num16; num22 = (float)(height - py) * num16; } else { switch (text) { case "top-center": num19 = num - (float)num17 * 0.5f * num15; num20 = num2 + (float)num17 * 0.5f * num15; num22 = num4; num21 = num3 - (float)num18 * num16; break; case "left-center": num19 = num; num20 = num2 + (float)num17 * num15; num21 = num3 - (float)num18 * 0.5f * num16; num22 = num4 + (float)num18 * 0.5f * num16; break; case "right-center": num20 = num2; num19 = num - (float)num17 * num15; num21 = num3 - (float)num18 * 0.5f * num16; num22 = num4 + (float)num18 * 0.5f * num16; break; case "center": num19 = num - (float)num17 * 0.5f * num15; num20 = num2 + (float)num17 * 0.5f * num15; num21 = num3 - (float)num18 * 0.5f * num16; num22 = num4 + (float)num18 * 0.5f * num16; break; default: num19 = num - (float)num17 * 0.5f * num15; num20 = num2 + (float)num17 * 0.5f * num15; num21 = num3; num22 = num4 + (float)num18 * num16; break; } } if (flag && !_rotatedNoteShown) { _rotatedNoteShown = true; Plugin.Logger.LogInfo((object)(string.Format("{0} NOTE: '{1}' has a rotated vanilla def ({2}). ", "[Applique]", entry.SpriteName, vanillaDef.flipped) + "Appliqué renders replacements upright by design; if this clip's art appears mirrored, report it — rotated-target parity is the one unverified orientation case.")); } Vector3[] array3 = (Vector3[])(object)new Vector3[positions.Length]; Vector2[] array4 = (Vector2[])(object)new Vector2[positions.Length]; float num23 = 0.5f / (float)width; float num24 = 0.5f / (float)height; for (int j = 0; j < positions.Length; j++) { float num25 = (positions[j].x - num) / num5; float num26 = (positions[j].y - num3) / num6; array3[j] = new Vector3(num19 + num25 * (num20 - num19), num21 + num26 * (num22 - num21), positions[j].z); array4[j] = new Vector2((num25 > 0.5f) ? (1f - num23) : num23, (num26 > 0.5f) ? (1f - num24) : num24); } Vector3 val5 = default(Vector3); ((Vector3)(ref val5))..ctor((num19 + num20) * 0.5f, (num21 + num22) * 0.5f, 0f); Vector3 val6 = default(Vector3); ((Vector3)(ref val6))..ctor((num20 - num19) * 0.5f, (num22 - num21) * 0.5f, 0.1f); tk2dSpriteDefinition val7 = new tk2dSpriteDefinition(); val7.name = entry.SpriteName + "#" + entry.ClipName; val7.material = value.Mat; val7.materialInst = value.Mat; val7.positions = array3; val7.uvs = array4; val7.normals = ((vanillaDef.normals != null) ? ((Vector3[])vanillaDef.normals.Clone()) : null); val7.tangents = ((vanillaDef.tangents != null) ? ((Vector4[])vanillaDef.tangents.Clone()) : null); val7.indices = ((vanillaDef.indices != null) ? ((int[])vanillaDef.indices.Clone()) : null); val7.complexGeometry = false; val7.flipped = (FlipMode)0; val7.texelSize = new Vector2((num20 - num19) / (float)width, (num22 - num21) / (float)height); val7.boundsData = (Vector3[])(object)new Vector3[2] { val5, val6 }; val7.untrimmedBoundsData = (Vector3[])(object)new Vector3[2] { val5, val6 }; tk2dSpriteDefinition def = val7; tk2dSpriteCollectionData obj = EnsureShadow(entry.CollName); obj.premultipliedAlpha = vanillaColl.premultipliedAlpha; tk2dSpriteDefinition[] array5 = (tk2dSpriteDefinition[])(((object)obj.spriteDefinitions) ?? ((object)new tk2dSpriteDefinition[0])); int num27 = Array.FindIndex(array5, (tk2dSpriteDefinition d) => d != null && d.name == def.name); if (num27 < 0) { tk2dSpriteDefinition[] array6 = (tk2dSpriteDefinition[])(object)new tk2dSpriteDefinition[array5.Length + 1]; array5.CopyTo(array6, 0); num27 = array5.Length; array5 = array6; } array5[num27] = def; def.materialId = num27; obj.spriteDefinitions = array5; Material[] array7 = (Material[])(((object)obj.materials) ?? ((object)new Material[0])); if (array7.Length < array5.Length) { Material[] array8 = (Material[])(object)new Material[array5.Length]; array7.CopyTo(array8, 0); array7 = array8; } array7[num27] = value.Mat; obj.materials = array7; return (shadow: obj, id: num27); } private static tk2dSpriteCollectionData EnsureShadow(string collName) { //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_0030: Expected O, but got Unknown if (_shadows.TryGetValue(collName, out var value) && (Object)(object)value != (Object)null) { return value; } GameObject val = new GameObject("Stitchwork_Shadow_" + collName); Object.DontDestroyOnLoad((Object)val); tk2dSpriteCollectionData val2 = val.AddComponent(); val2.spriteDefinitions = (tk2dSpriteDefinition[])(object)new tk2dSpriteDefinition[0]; val2.materials = (Material[])(object)new Material[0]; _shadows[collName] = val2; _shadowCollIds.Add(((Object)val2).GetInstanceID()); return val2; } } public static class AudioCatalog { private static readonly HashSet _names = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool _loaded; private static bool _dirty; private static string CatalogPath => Path.Combine(Plugin.BasePath, "audio-catalog.txt"); public static IReadOnlyCollection Names { get { EnsureLoaded(); return _names; } } public static int Count { get { EnsureLoaded(); return _names.Count; } } public static void Observe(string clipName) { if (!string.IsNullOrEmpty(clipName)) { EnsureLoaded(); if (_names.Add(Clean(clipName))) { _dirty = true; } } } public static void ObserveMany(IEnumerable clipNames) { if (clipNames == null) { return; } EnsureLoaded(); foreach (string clipName in clipNames) { if (!string.IsNullOrEmpty(clipName) && _names.Add(Clean(clipName))) { _dirty = true; } } } public static bool Contains(string clipName) { if (string.IsNullOrEmpty(clipName)) { return false; } EnsureLoaded(); return _names.Contains(Clean(clipName)); } private static string Clean(string name) { return name.Replace("PATCHWORK_", ""); } private static void EnsureLoaded() { if (_loaded) { return; } _loaded = true; try { if (!File.Exists(CatalogPath)) { return; } string[] array = File.ReadAllLines(CatalogPath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0 && text[0] != '#') { _names.Add(text); } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[AudioCatalog] load failed: " + ex.Message)); } } public static void Flush() { if (!_dirty) { return; } EnsureLoaded(); try { List list = new List(_names); list.Sort(StringComparer.OrdinalIgnoreCase); StringBuilder stringBuilder = new StringBuilder(list.Count * 24 + 128); stringBuilder.AppendLine("# Stitchwork audio catalog — every clip name observed (loaded or played)."); stringBuilder.AppendLine("# Auto-maintained, persisted across sessions. One clip name per line."); stringBuilder.AppendLine("# A name here that your pack doesn't cover is a replacement opportunity."); foreach (string item in list) { stringBuilder.AppendLine(item); } File.WriteAllText(CatalogPath, stringBuilder.ToString()); _dirty = false; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[AudioCatalog] save failed: " + ex.Message)); } } } public static class AudioDumper { private static GameObject _previewGO; public static string DumpPath => Path.Combine(SpriteDumper.DumpPath, "Audio"); public static void Preview(AudioClip clip) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if (!((Object)(object)clip == (Object)null)) { if ((Object)(object)_previewGO == (Object)null) { _previewGO = new GameObject("StitchworkAudioPreview") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)_previewGO); AudioSource obj = _previewGO.AddComponent(); obj.spatialBlend = 0f; obj.playOnAwake = false; } AudioSource component = _previewGO.GetComponent(); component.Stop(); component.clip = clip; component.volume = 1f; component.Play(); } } public static void StopPreview() { GameObject previewGO = _previewGO; if (previewGO != null) { AudioSource component = previewGO.GetComponent(); if (component != null) { component.Stop(); } } } public static IEnumerator CaptureClipAsync(AudioClip clip, Action onDone) { if ((Object)(object)clip == (Object)null) { onDone(arg1: false, "no vanilla clip in memory for that name."); yield break; } GameObject go = new GameObject("StitchworkAudioCapture") { hideFlags = (HideFlags)61 }; AudioSource src = go.AddComponent(); AudioCaptureFilter cap = go.AddComponent(); src.spatialBlend = 0f; src.volume = 1f; src.playOnAwake = false; src.clip = clip; cap.Capturing = true; src.Play(); float deadline = Time.realtimeSinceStartup + clip.length + 2f; yield return null; while (src.isPlaying && Time.realtimeSinceStartup < deadline) { yield return null; } cap.Capturing = false; yield return null; yield return null; src.Stop(); int num = ((cap.Channels > 0) ? cap.Channels : Mathf.Max(1, clip.channels)); int outputSampleRate = AudioSettings.outputSampleRate; float[] array = cap.Samples.ToArray(); Object.Destroy((Object)(object)go); if (array.Length == 0) { onDone(arg1: false, "'" + ((Object)clip).name + "' capture produced no samples (clip didn't play — muted or zero-length?)."); yield break; } IOUtil.EnsureDirectoryExists(DumpPath); string text = T2DUtil.SanitizeForFilesystem(((Object)clip).name.Replace("PATCHWORK_", "")); string text2 = Path.Combine(DumpPath, text + ".wav"); WriteWav16(text2, array, num, outputSampleRate); onDone(arg1: true, $"{text2} ({array.Length / Mathf.Max(1, num)} frames @ {outputSampleRate} Hz, {num}ch)"); } private static void WriteWav16(string path, float[] samples, int channels, int frequency) { int value = frequency * channels * 2; int num = channels * 2; int num2 = samples.Length * 2; using FileStream output = new FileStream(path, FileMode.Create, FileAccess.Write); using BinaryWriter binaryWriter = new BinaryWriter(output); binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF")); binaryWriter.Write(36 + num2); binaryWriter.Write(Encoding.ASCII.GetBytes("WAVE")); binaryWriter.Write(Encoding.ASCII.GetBytes("fmt ")); binaryWriter.Write(16); binaryWriter.Write((short)1); binaryWriter.Write((short)channels); binaryWriter.Write(frequency); binaryWriter.Write(value); binaryWriter.Write((short)num); binaryWriter.Write((short)16); binaryWriter.Write(Encoding.ASCII.GetBytes("data")); binaryWriter.Write(num2); foreach (float num3 in samples) { binaryWriter.Write((short)Mathf.Clamp(Mathf.RoundToInt(num3 * 32767f), -32768, 32767)); } } } public class AudioCaptureFilter : MonoBehaviour { public readonly List Samples = new List(); public int Channels; public volatile bool Capturing; private void OnAudioFilterRead(float[] data, int channels) { if (Capturing) { Channels = channels; Samples.AddRange(data); Array.Clear(data, 0, data.Length); } } } public sealed class AudioClipEntry { public readonly string ClipName; public readonly float LengthSeconds; public readonly int Channels; public readonly int Frequency; public readonly bool HasReplacement; public readonly bool CurrentlyLoaded; public readonly List SourcePaths = new List(); public AudioClipEntry(string name, float length, int channels, int frequency, bool hasReplacement) { ClipName = name; LengthSeconds = length; Channels = channels; Frequency = frequency; HasReplacement = hasReplacement; CurrentlyLoaded = true; } public AudioClipEntry(string name, bool hasReplacement) { ClipName = name; HasReplacement = hasReplacement; CurrentlyLoaded = false; } } [HarmonyPatch] public static class AudioHandler { public static readonly string SoundFolder = Path.Combine(Plugin.BasePath, "Sounds"); internal static readonly string[] SupportedExtensions = new string[5] { ".ogg", ".wav", ".mp3", ".aiff", ".aif" }; private static readonly Dictionary LoadedClips = new Dictionary(); private static readonly Dictionary _originalClips = new Dictionary(); private static readonly Dictionary _soundIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _indexBuilt; private static bool _reverting; private static bool _inAudioPlayChain; private static readonly HashSet _loadingInProgress = new HashSet(StringComparer.OrdinalIgnoreCase); private static int _reloadGeneration; public static bool IsAudioBrowserActive; private static FieldInfo _pmAudioClipField; private static PropertyInfo _pmFsmValueProp; private static readonly HashSet _mutedClips = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _rootMutedClips = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _completedAwaitingApply = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool _applySweepScheduled; private static readonly HashSet _formatWarned = new HashSet(StringComparer.OrdinalIgnoreCase); public static bool HasAudioReplacements { get { if (_indexBuilt) { return _soundIndex.Count > 0; } return false; } } public static string MuteFilePath => Path.Combine(SoundFolder, "mute.txt"); public static int CachedClipCount => LoadedClips.Count; public static event Action OnAudioPlayed; public static void ApplyPatches(Harmony harmony) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_011d: Expected O, but got Unknown //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_01b1: Expected O, but got Unknown //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Expected O, but got Unknown //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Expected O, but got Unknown //IL_03bd: Unknown result type (might be due to invalid IL or missing references) //IL_03cb: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(AudioSource), "PlayHelper", new Type[2] { typeof(AudioSource), typeof(ulong) }, (Type[])null), new HarmonyMethod(typeof(AudioHandler), "PlayHelperPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(AudioSource), "PlayOneShotHelper", new Type[3] { typeof(AudioSource), typeof(AudioClip), typeof(float) }, (Type[])null), new HarmonyMethod(typeof(AudioHandler), "PlayOneShotHelperPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[1] { typeof(AudioClip) }, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(AudioHandler), "PlayOneShotPublicPrefix1Arg", (Type[])null), new HarmonyMethod(typeof(AudioHandler), "PlayOneShotPublicPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Logger.LogInfo((object)"[Audio] hooked AudioSource.PlayOneShot(AudioClip)"); } else { Plugin.Logger.LogWarning((object)"[Audio] AudioSource.PlayOneShot(AudioClip) not found — single-arg public hook skipped"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2] { typeof(AudioClip), typeof(float) }, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(AudioHandler), "PlayOneShotPublicPrefix2Arg", (Type[])null), new HarmonyMethod(typeof(AudioHandler), "PlayOneShotPublicPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Logger.LogInfo((object)"[Audio] hooked AudioSource.PlayOneShot(AudioClip, float)"); } else { Plugin.Logger.LogWarning((object)"[Audio] AudioSource.PlayOneShot(AudioClip, float) not found — two-arg public hook skipped"); } TryPatchSourcePlay(harmony, "Play", Type.EmptyTypes, "PlayInstancePrefix", "SourcePlayPostfix"); TryPatchSourcePlay(harmony, "Play", new Type[1] { typeof(double) }, "PlayInstancePrefix", "SourcePlayPostfix"); TryPatchSourcePlay(harmony, "PlayDelayed", new Type[1] { typeof(float) }, "PlayInstancePrefix", "SourcePlayPostfix"); TryPatchSourcePlay(harmony, "PlayScheduled", new Type[1] { typeof(double) }, "PlayInstancePrefix", "SourcePlayPostfix"); harmony.Patch((MethodBase)AccessTools.PropertySetter(typeof(AudioSource), "clip"), (HarmonyMethod)null, new HarmonyMethod(typeof(AudioHandler), "ClipSetterPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Type type = AccessTools.TypeByName("OverrideNeedolinLoop"); if (type != null) { MethodInfo methodInfo3 = AccessTools.Method(type, "StartSyncedAudio", new Type[2] { typeof(AudioSource), typeof(AudioClip) }, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(AudioHandler), "StartSyncedAudioPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Logger.LogWarning((object)"[Audio] OverrideNeedolinLoop.StartSyncedAudio(AudioSource, AudioClip) not found — Needolin loop replacement disabled"); } } else { Plugin.Logger.LogWarning((object)"[Audio] OverrideNeedolinLoop type not found — Needolin loop replacement disabled"); } Type type2 = AccessTools.TypeByName("HutongGames.PlayMaker.Actions.SetAudioClip"); if (type2 != null) { MethodInfo methodInfo4 = AccessTools.Method(type2, "OnEnter", (Type[])null, (Type[])null); _pmAudioClipField = type2.GetField("audioClip", BindingFlags.Instance | BindingFlags.Public); if (_pmAudioClipField != null) { _pmFsmValueProp = _pmAudioClipField.FieldType.GetProperty("Value"); } if (methodInfo4 != null && _pmAudioClipField != null && _pmFsmValueProp != null) { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(AudioHandler), "SetAudioClipOnEnterPatch", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } private static void LogAudioCall(string route, AudioClip clip, AudioSource source) { if (!((Object)(object)clip == (Object)null)) { string text = ((Object)clip).name ?? ""; string text2 = text.Replace("PATCHWORK_", ""); AudioCatalog.Observe(text2); string arg = (((Object)(object)source != (Object)null && (Object)(object)((Component)source).gameObject != (Object)null) ? ((Object)((Component)source).gameObject).name : ""); Log.Verbose("[Audio] route=" + route + " clip=" + text + " clean=" + text2 + " " + $"hasRepl={_soundIndex.ContainsKey(text2)} cached={LoadedClips.ContainsKey(text2)} src={arg}"); } } private static bool IsDiagnosticSource(AudioSource source) { if ((Object)(object)source != (Object)null) { return ((Object)((Component)source).gameObject).name.StartsWith("Stitchwork", StringComparison.Ordinal); } return false; } public static bool PlayHelperPatch(AudioSource source, ulong delay) { if (_inAudioPlayChain) { return true; } if (IsDiagnosticSource(source)) { return true; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)source.clip != (Object)null) { LogAudioCall("PlayHelper", source.clip, source); AudioHandler.OnAudioPlayed?.Invoke(source.clip, source); if (_mutedClips.Count > 0 && IsMuted(((Object)source.clip).name)) { return false; } LoadAudio(source); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.PlayHelper", heapBefore); } return true; } private static void TryPatchSourcePlay(Harmony harmony, string methodName, Type[] paramTypes, string prefix, string postfix) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown //IL_00be: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(AudioSource), methodName, paramTypes, (Type[])null); string text = "AudioSource." + methodName + "(" + string.Join(", ", paramTypes.Select((Type t) => t.Name)) + ")"; if (methodInfo == null) { Plugin.Logger.LogWarning((object)("[Audio] " + text + " not found — hook skipped")); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(AudioHandler), prefix, (Type[])null), new HarmonyMethod(typeof(AudioHandler), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Logger.LogInfo((object)("[Audio] hooked " + text)); } public static bool PlayInstancePrefix(AudioSource __instance) { if (IsDiagnosticSource(__instance)) { return true; } if (_inAudioPlayChain) { return true; } _inAudioPlayChain = true; if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.clip == (Object)null) { return true; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { LogAudioCall("Play", __instance.clip, __instance); AudioHandler.OnAudioPlayed?.Invoke(__instance.clip, __instance); if (_mutedClips.Count > 0 && IsMuted(((Object)__instance.clip).name)) { return false; } LoadAudio(__instance); } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.Play", heapBefore); } return true; } public static void SourcePlayPostfix() { _inAudioPlayChain = false; } public static bool PlayOneShotHelperPatch(AudioSource source, ref AudioClip clip, float volumeScale) { if (_inAudioPlayChain) { return true; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)clip != (Object)null) { LogAudioCall("PlayOneShot", clip, source); AudioHandler.OnAudioPlayed?.Invoke(clip, source); if (_mutedClips.Count > 0 && IsMuted(((Object)clip).name)) { return false; } LoadAudio(ref clip); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.PlayOneShot", heapBefore); } return true; } public static bool PlayOneShotPublicPrefix1Arg(AudioSource __instance, AudioClip clip) { return DispatchPublicPlayOneShot(__instance, clip, 1f); } public static bool PlayOneShotPublicPrefix2Arg(AudioSource __instance, AudioClip clip, float volumeScale) { return DispatchPublicPlayOneShot(__instance, clip, volumeScale); } private static bool DispatchPublicPlayOneShot(AudioSource source, AudioClip clip, float volumeScale) { if (_inAudioPlayChain) { return true; } if ((Object)(object)source == (Object)null || (Object)(object)clip == (Object)null) { return true; } _inAudioPlayChain = true; long heapBefore = DevProfiler.SnapshotHeapBytes(); try { LogAudioCall("PlayOneShot.Public", clip, source); AudioHandler.OnAudioPlayed?.Invoke(clip, source); string text = ((Object)clip).name?.Replace("PATCHWORK_", "") ?? string.Empty; if (IsMuted(text)) { return false; } if (LoadedClips.TryGetValue(text, out var value) && (Object)(object)value != (Object)null && (Object)(object)value != (Object)(object)clip) { try { source.PlayOneShot(value, volumeScale); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[Audio] PlayOneShot.Public substitute failed for '" + text + "': " + ex.Message)); return true; } return false; } if (_soundIndex.ContainsKey(text)) { EnqueueLoad(text); } return true; } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.PlayOneShot.Public", heapBefore); } } public static void PlayOneShotPublicPostfix() { _inAudioPlayChain = false; } public static void ClipSetterPatch(AudioSource __instance, AudioClip value) { if (IsDiagnosticSource(__instance)) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)value != (Object)null) { LogAudioCall("ClipSetter", value, __instance); AudioHandler.OnAudioPlayed?.Invoke(value, __instance); if (!_reverting && (!((Object)value).name.StartsWith("PATCHWORK_") || !LoadedClips.ContainsKey(((Object)value).name.Replace("PATCHWORK_", "")))) { LoadAudio(__instance); } } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.ClipSetter", heapBefore); } } public static void StartSyncedAudioPatch(AudioSource targetSource, ref AudioClip defaultClip) { if ((Object)(object)defaultClip == (Object)null || string.IsNullOrEmpty(((Object)defaultClip).name)) { return; } LogAudioCall("NeedolinSync", defaultClip, targetSource); string text = ((Object)defaultClip).name.Replace("PATCHWORK_", ""); if (!_soundIndex.ContainsKey(text)) { return; } int num = (((Object)(object)targetSource != (Object)null) ? ((Object)targetSource).GetInstanceID() : (-1)); if (num >= 0 && !((Object)defaultClip).name.StartsWith("PATCHWORK_") && !_originalClips.ContainsKey(num)) { _originalClips[num] = defaultClip; } if (LoadedClips.TryGetValue(text, out var value)) { if (!((Object)defaultClip).name.StartsWith("PATCHWORK_")) { WarnIfFormatMismatch(defaultClip, value); } defaultClip = value; } else { EnqueueLoad(text); } } public static void SetAudioClipOnEnterPatch(object __instance) { try { object value = _pmAudioClipField.GetValue(__instance); if (value == null) { return; } object? value2 = _pmFsmValueProp.GetValue(value); AudioClip val = (AudioClip)((value2 is AudioClip) ? value2 : null); if ((Object)(object)val == (Object)null || string.IsNullOrEmpty(((Object)val).name)) { return; } LogAudioCall("PMSetAudioClip", val, null); string name = ((Object)val).name; if (_soundIndex.ContainsKey(name)) { if (LoadedClips.TryGetValue(name, out var value3)) { _pmFsmValueProp.SetValue(value, value3); } else { EnqueueLoad(name); } } } catch (Exception ex) { Plugin.Logger.LogWarning((object)$"[Audio] SetAudioClipOnEnterPatch: {(ex as TargetInvocationException)?.InnerException ?? ex}"); } } public static void Reload() { ReloadInternal(clearCache: true); } public static void PrimeForScene() { ReloadInternal(clearCache: false); } private static void ReloadInternal(bool clearCache) { AudioCatalog.Flush(); RebuildSoundIndex(); Plugin.Logger.LogDebug((object)("[Audio] " + (clearCache ? "Reload" : "PrimeForScene") + ": " + $"soundIndex={_soundIndex.Count} LoadedClips={LoadedClips.Count} _originalClips={_originalClips.Count}")); if (!HasAudioReplacements && LoadedClips.Count == 0) { Plugin.Logger.LogDebug((object)"[Audio] early exit — no replacements and no cached clips to restore"); return; } if (clearCache) { LoadedClips.Clear(); _loadingInProgress.Clear(); _reloadGeneration++; _formatWarned.Clear(); } AudioSource[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null || IsDiagnosticSource(val) || !((Object)(object)val.clip != (Object)null)) { continue; } string text = ((Object)val.clip).name.Replace("PATCHWORK_", ""); if (_soundIndex.ContainsKey(text)) { int instanceID = ((Object)val).GetInstanceID(); if (!((Object)val.clip).name.StartsWith("PATCHWORK_") && !_originalClips.ContainsKey(instanceID)) { _originalClips[instanceID] = val.clip; } if (LoadedClips.TryGetValue(text, out var value) && (Object)(object)value != (Object)null) { if ((Object)(object)val.clip != (Object)(object)value && !val.isPlaying) { val.clip = value; } } else { EnqueueLoad(text); } } else { if (!((Object)val.clip).name.StartsWith("PATCHWORK_")) { continue; } int instanceID2 = ((Object)val).GetInstanceID(); if (!_originalClips.TryGetValue(instanceID2, out var value2) || (Object)(object)value2 == (Object)null) { foreach (AudioClip value3 in _originalClips.Values) { if ((Object)(object)value3 != (Object)null && string.Equals(((Object)value3).name, text, StringComparison.OrdinalIgnoreCase)) { value2 = value3; break; } } if ((Object)(object)value2 == (Object)null) { AudioClip[] array2 = Resources.FindObjectsOfTypeAll(); foreach (AudioClip val2 in array2) { if ((Object)(object)val2 != (Object)null && !((Object)val2).name.StartsWith("PATCHWORK_") && string.Equals(((Object)val2).name, text, StringComparison.OrdinalIgnoreCase)) { value2 = val2; break; } } } if ((Object)(object)value2 != (Object)null) { _originalClips[instanceID2] = value2; } } if ((Object)(object)value2 != (Object)null) { _reverting = true; val.clip = value2; _reverting = false; continue; } Plugin.Logger.LogWarning((object)("[Audio] Reload: cannot revert '" + text + "' on '" + ((Object)((Component)val).gameObject).name + "' — vanilla clip not found in memory")); } } foreach (string key in _soundIndex.Keys) { EnqueueLoad(key); } } private static void RebuildSoundIndex() { _soundIndex.Clear(); _indexBuilt = true; foreach (string pluginPackPath in Plugin.PluginPackPaths) { ScanDir(Path.Combine(pluginPackPath, "Sounds")); } ScanDir(SoundFolder); RebuildMuteList(); static void ScanDir(string dir) { if (Directory.Exists(dir)) { string[] files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories); foreach (string text in files) { string value = Path.GetExtension(text).ToLowerInvariant(); if (Array.IndexOf(SupportedExtensions, value) >= 0) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); _soundIndex.TryAdd(fileNameWithoutExtension, text); } } } } } public static bool IsMuted(string rawName) { if (_mutedClips.Count == 0 || string.IsNullOrEmpty(rawName)) { return false; } return _mutedClips.Contains(rawName.Replace("PATCHWORK_", "")); } public static bool IsRootMuted(string cleanName) { return _rootMutedClips.Contains(cleanName); } private static void RebuildMuteList() { _mutedClips.Clear(); _rootMutedClips.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { ReadMuteFile(Path.Combine(pluginPackPath, "Sounds", "mute.txt"), null); } ReadMuteFile(MuteFilePath, _rootMutedClips); static void ReadMuteFile(string path, HashSet alsoInto) { if (File.Exists(path)) { string[] array = File.ReadAllLines(IOUtil.LongSafe(path)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && !text.StartsWith("#")) { _mutedClips.Add(text); alsoInto?.Add(text); } } } } } public static void SetRootMute(string cleanName, bool mute) { if (string.IsNullOrEmpty(cleanName)) { return; } List list = (File.Exists(MuteFilePath) ? File.ReadAllLines(IOUtil.LongSafe(MuteFilePath)).ToList() : new List { "# One clip name per line. Lines starting with # are comments." }); if (mute) { if (!list.Any((string l) => string.Equals(l.Trim(), cleanName, StringComparison.OrdinalIgnoreCase))) { list.Add(cleanName); } } else { list.RemoveAll((string l) => string.Equals(l.Trim(), cleanName, StringComparison.OrdinalIgnoreCase)); } IOUtil.EnsureDirectoryExists(SoundFolder); File.WriteAllLines(IOUtil.LongSafe(MuteFilePath), list); RebuildMuteList(); Plugin.Logger.LogInfo((object)string.Format("[Audio] {0} '{1}' via root mute.txt ({2} total)", mute ? "Muted" : "Unmuted", cleanName, _mutedClips.Count)); } public static void LoadAudio(AudioSource source) { if (!((Object)(object)source == (Object)null) && !((Object)(object)source.clip == (Object)null) && !string.IsNullOrEmpty(((Object)source.clip).name) && !IsDiagnosticSource(source)) { string text = ((Object)source.clip).name.Replace("PATCHWORK_", ""); int instanceID = ((Object)source).GetInstanceID(); if (!((Object)source.clip).name.StartsWith("PATCHWORK_") && !_originalClips.ContainsKey(instanceID)) { _originalClips[instanceID] = source.clip; } if (LoadedClips.TryGetValue(text, out var value)) { source.clip = value; } else { EnqueueLoad(text); } } } public static void LoadAudio(ref AudioClip clip) { if (!((Object)(object)clip == (Object)null) && !string.IsNullOrEmpty(((Object)clip).name)) { string text = ((Object)clip).name.Replace("PATCHWORK_", ""); if (LoadedClips.TryGetValue(text, out var value)) { clip = value; } else { EnqueueLoad(text); } } } private static void EnqueueLoad(string soundName) { if (!LoadedClips.ContainsKey(soundName) && !_loadingInProgress.Contains(soundName)) { string soundPath = GetSoundPath(soundName); if (soundPath != null) { _loadingInProgress.Add(soundName); ((MonoBehaviour)Plugin.Instance).StartCoroutine(LoadAudioClipAsync(soundName, soundPath, _reloadGeneration)); } } } private static IEnumerator LoadAudioClipAsync(string soundName, string path, int gen) { string text = "file:///" + Uri.EscapeUriString(path.Replace("\\", "/")); UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(text, (AudioType)0); int num = Plugin.Config?.AudioCompressedInMemoryThresholdKB ?? 1024; if (num > 0) { DownloadHandler downloadHandler = request.downloadHandler; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null); if (val != null) { long num2 = 0L; try { num2 = new FileInfo(path).Length; } catch (IOException) { } catch (UnauthorizedAccessException) { } if (num2 >= (long)num * 1024L) { val.compressed = true; } } } yield return request.SendWebRequest(); _loadingInProgress.Remove(soundName); if (_reloadGeneration != gen) { yield break; } if ((int)request.result != 1) { Plugin.Logger.LogError((object)("[Audio] Failed to load '" + path + "': " + request.error)); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(request); ((Object)content).name = "PATCHWORK_" + soundName; LoadedClips[soundName] = content; _completedAwaitingApply.Add(soundName); if (!_applySweepScheduled) { _applySweepScheduled = true; GameBridge.InvokeNext(ApplyCompletedLoads); } } private static void WarnIfFormatMismatch(AudioClip vanilla, AudioClip replacement) { if (!((Object)(object)vanilla == (Object)null) && !((Object)(object)replacement == (Object)null) && vanilla.frequency != replacement.frequency) { string text = ((Object)replacement).name.Replace("PATCHWORK_", ""); if (_formatWarned.Add(text)) { Plugin.Logger.LogWarning((object)($"[Audio] '{text}': replacement is {replacement.frequency} Hz but the vanilla clip " + $"is {vanilla.frequency} Hz. Synced tracks (Needolin melodies, layered music) will " + "periodically snap or rewind under the game's sample-based sync. " + $"Re-export the file at {vanilla.frequency} Hz.")); } } } private static void ApplyCompletedLoads() { _applySweepScheduled = false; if (_completedAwaitingApply.Count == 0) { return; } AudioSource[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.clip == (Object)null || IsDiagnosticSource(val)) { continue; } string text = ((Object)val.clip).name.Replace("PATCHWORK_", ""); if (!_completedAwaitingApply.Contains(text) || !LoadedClips.TryGetValue(text, out var value) || (Object)(object)value == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (!((Object)val.clip).name.StartsWith("PATCHWORK_") && !_originalClips.ContainsKey(instanceID)) { _originalClips[instanceID] = val.clip; } bool flag = val.isPlaying && val.loop; if (!((Object)(object)val.clip == (Object)(object)value)) { if (_originalClips.TryGetValue(instanceID, out var value2)) { WarnIfFormatMismatch(value2, value); } val.clip = value; if (flag) { val.Stop(); val.Play(); } } } _completedAwaitingApply.Clear(); } private static string GetSoundPath(string soundName) { if (!_indexBuilt) { RebuildSoundIndex(); } if (!_soundIndex.TryGetValue(soundName, out var value)) { return null; } return value; } public static List GetClipInventory() { if (!_indexBuilt) { RebuildSoundIndex(); } Dictionary dictionary = new Dictionary(); AudioClip[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioClip val in array) { if ((Object)(object)val == (Object)null) { continue; } string text = ((Object)val).name.Replace("PATCHWORK_", ""); if (!string.IsNullOrWhiteSpace(text)) { int instanceID = ((Object)val).GetInstanceID(); if (!dictionary.ContainsKey(instanceID)) { dictionary[instanceID] = new AudioClipEntry(text, val.length, val.channels, val.frequency, _soundIndex.ContainsKey(text)); } } } AudioSource[] array2 = Resources.FindObjectsOfTypeAll(); foreach (AudioSource val2 in array2) { if ((Object)(object)val2 == (Object)null || (Object)(object)val2.clip == (Object)null) { continue; } int instanceID2 = ((Object)val2.clip).GetInstanceID(); if (dictionary.TryGetValue(instanceID2, out var value)) { string gameObjectPath = GetGameObjectPath(val2); if (!value.SourcePaths.Contains(gameObjectPath)) { value.SourcePaths.Add(gameObjectPath); } } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (AudioClipEntry value3 in dictionary.Values) { if (dictionary2.TryGetValue(value3.ClipName, out var value2)) { foreach (string sourcePath in value3.SourcePaths) { if (!value2.SourcePaths.Contains(sourcePath)) { value2.SourcePaths.Add(sourcePath); } } } else { dictionary2[value3.ClipName] = value3; } } List list = new List(dictionary2.Values); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (AudioClipEntry item in list) { hashSet.Add(item.ClipName); } AudioCatalog.ObserveMany(hashSet); foreach (string name in AudioCatalog.Names) { if (!string.IsNullOrWhiteSpace(name) && !hashSet.Contains(name)) { list.Add(new AudioClipEntry(name, _soundIndex.ContainsKey(name))); } } AudioCatalog.Flush(); list.Sort((AudioClipEntry a, AudioClipEntry b) => string.Compare(a.ClipName, b.ClipName, StringComparison.OrdinalIgnoreCase)); return list; } internal static string GetGameObjectPath(AudioSource src) { if ((Object)(object)src == (Object)null) { return ""; } Transform transform = ((Component)src).transform; string text = ((Object)transform).name; if ((Object)(object)transform.parent != (Object)null) { text = ((Object)transform.parent).name + "/" + text; } return text; } public static AudioClip FindVanillaClipForDump(string clipName) { if (string.IsNullOrWhiteSpace(clipName)) { return null; } foreach (AudioClip value in _originalClips.Values) { if ((Object)(object)value != (Object)null && !((Object)value).name.StartsWith("PATCHWORK_") && string.Equals(((Object)value).name, clipName, StringComparison.OrdinalIgnoreCase)) { return value; } } HashSet hashSet = new HashSet(); foreach (AudioClip value2 in LoadedClips.Values) { if ((Object)(object)value2 != (Object)null) { hashSet.Add(((Object)value2).GetInstanceID()); } } AudioClip[] array = Resources.FindObjectsOfTypeAll(); foreach (AudioClip val in array) { if (!((Object)(object)val == (Object)null) && !((Object)val).name.StartsWith("PATCHWORK_") && !hashSet.Contains(((Object)val).GetInstanceID()) && string.Equals(((Object)val).name, clipName, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } public static AudioClip GetReplacementClipForPreview(string clipName) { if (LoadedClips.TryGetValue(clipName, out var value) && (Object)(object)value != (Object)null) { return value; } if (_soundIndex.ContainsKey(clipName)) { EnqueueLoad(clipName); } return null; } } public enum FlagKind { Bool, Int, String, Enum } public struct FlagChange { public string Name; public FlagKind Kind; public object Original; public object Current; public float LastChange; public bool NearDialogue; public bool Story; } public static class DialogueFlagTracker { public const float DialogueWindow = 6f; private static readonly Dictionary _changes = new Dictionary(256); private static readonly Dictionary _fieldCache = new Dictionary(256); private static float _lastDialogueActivity = -999f; private static readonly string[] NameStems = new string[16] { "convo", "met", "encounter", "quest", "talk", "dlg", "location", "greet", "spoke", "heard", "npc", "intro", "offered", "giv", "asked", "defeated" }; private static readonly string[] NpcTokens = new string[15] { "sherma", "mapper", "garmond", "nuu", "pilby", "lace", "seth", "caravan", "flea", "druid", "gilly", "ballow", "grindle", "farmer", "architect" }; private static readonly string[] TypeTokens = new string[3] { "location", "encounter", "npc" }; public static IReadOnlyDictionary Changes => _changes; public static int Count => _changes.Count; public static bool IsLikelyDialogueFlag(string name, Type fieldType) { if (!string.IsNullOrEmpty(name)) { string[] nameStems = NameStems; foreach (string value in nameStems) { if (name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } nameStems = NpcTokens; foreach (string value2 in nameStems) { if (name.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } if (fieldType != null) { string name2 = fieldType.Name; string[] nameStems = TypeTokens; foreach (string value3 in nameStems) { if (name2.IndexOf(value3, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } return false; } public static void ApplyPatches(Harmony harmony) { DialogueHandler.OnTextAccessed += OnTextAccessed; Patch(harmony, "SetBool", new Type[2] { typeof(string), typeof(bool) }, "SetBoolPrefix", "SetBoolPostfix"); Patch(harmony, "SetInt", new Type[2] { typeof(string), typeof(int) }, "SetIntPrefix", "SetIntPostfix"); Patch(harmony, "SetString", new Type[2] { typeof(string), typeof(string) }, "SetStringPrefix", "SetStringPostfix"); Patch(harmony, "IncrementInt", new Type[1] { typeof(string) }, "IntNamePrefix", "IntNamePostfix"); Patch(harmony, "DecrementInt", new Type[1] { typeof(string) }, "IntNamePrefix", "IntNamePostfix"); Patch(harmony, "OnUpdatedVariable", new Type[1] { typeof(string) }, null, "OnUpdatedVariablePostfix"); } private static void Patch(Harmony h, string method, Type[] args, string pre, string post) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = AccessTools.Method(typeof(PlayerData), method, args, (Type[])null); if (methodInfo == null) { Log.Warn("[DialogueFlagTracker] PlayerData." + method + " not found; skipped."); } else { h.Patch((MethodBase)methodInfo, (pre == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(DialogueFlagTracker), pre, (Type[])null), (post == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(DialogueFlagTracker), post, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void OnTextAccessed(string sheet, string key, string text) { _lastDialogueActivity = Time.unscaledTime; } private static void SetBoolPrefix(PlayerData __instance, string boolName, out bool __state) { __state = __instance.GetBool(boolName); } private static void SetBoolPostfix(string boolName, bool value, bool __state) { if (__state != value) { Record(boolName, FlagKind.Bool, __state, value); } } private static void SetIntPrefix(PlayerData __instance, string intName, out int __state) { __state = __instance.GetInt(intName); } private static void SetIntPostfix(string intName, int value, int __state) { if (__state != value) { Record(intName, FlagKind.Int, __state, value); } } private static void SetStringPrefix(PlayerData __instance, string stringName, out string __state) { __state = __instance.GetString(stringName); } private static void SetStringPostfix(string stringName, string value, string __state) { if (!string.Equals(__state, value)) { Record(stringName, FlagKind.String, __state, value); } } private static void IntNamePrefix(PlayerData __instance, string intName, out int __state) { __state = __instance.GetInt(intName); } private static void IntNamePostfix(PlayerData __instance, string intName, int __state) { int num = __instance.GetInt(intName); if (num != __state) { Record(intName, FlagKind.Int, __state, num); } } private static void OnUpdatedVariablePostfix(PlayerData __instance, string variableName) { FieldInfo fieldInfo = Field(variableName); if (!(fieldInfo == null)) { FlagKind? flagKind = KindOf(fieldInfo.FieldType); if (flagKind.HasValue) { object value = fieldInfo.GetValue(__instance); Record(variableName, flagKind.Value, value, value); } } } private static FieldInfo Field(string name) { if (!_fieldCache.TryGetValue(name, out var value)) { value = typeof(PlayerData).GetField(name, BindingFlags.Instance | BindingFlags.Public); _fieldCache[name] = value; } return value; } private static FlagKind? KindOf(Type t) { if (!(t == typeof(bool))) { if (!(t == typeof(int))) { if (!(t == typeof(string))) { if (!t.IsEnum) { return null; } return FlagKind.Enum; } return FlagKind.String; } return FlagKind.Int; } return FlagKind.Bool; } private static void Record(string name, FlagKind kind, object oldVal, object newVal) { Type fieldType = Field(name)?.FieldType; bool story = IsLikelyDialogueFlag(name, fieldType); bool flag = Time.unscaledTime - _lastDialogueActivity <= 6f; if (_changes.TryGetValue(name, out var value)) { value.Current = newVal; value.LastChange = Time.unscaledTime; value.NearDialogue |= flag; value.Story = story; _changes[name] = value; } else { _changes[name] = new FlagChange { Name = name, Kind = kind, Original = oldVal, Current = newVal, LastChange = Time.unscaledTime, NearDialogue = flag, Story = story }; } } public static bool TrySet(string name, FlagKind kind, object value) { PlayerData instance = PlayerData.instance; if (instance == null) { return false; } switch (kind) { case FlagKind.Bool: instance.SetBool(name, (bool)value); break; case FlagKind.Int: instance.SetInt(name, (int)value); break; case FlagKind.String: instance.SetString(name, (string)value); break; case FlagKind.Enum: { FieldInfo fieldInfo = Field(name); if (fieldInfo == null) { return false; } fieldInfo.SetValue(instance, value); break; } default: return false; } if (_changes.TryGetValue(name, out var value2)) { value2.Current = value; value2.LastChange = Time.unscaledTime; _changes[name] = value2; } return true; } public static void RevertToOriginal(string name) { if (_changes.TryGetValue(name, out var value)) { TrySet(value.Name, value.Kind, value.Original); } } public static void RevertAll() { foreach (FlagChange item in new List(_changes.Values)) { TrySet(item.Name, item.Kind, item.Original); } } public static void ClearLog() { _changes.Clear(); } } public static class FontHandler { private const string AssetPrefix = "Stitchwork_Fallback_"; private const int Padding = 5; private static readonly List _ourAssets = new List(); private static readonly List _graveyard = new List(); private static MethodInfo _tryAddGlyphToTexture; private static bool _bridgeInit; private static bool _warnedNoTemplate; private static HashSet _bakedCharset; private static List _bakedFontFiles; private static bool _bakedBitmapMode; private static float _bakedScale = -1f; private static readonly HashSet _seenChars = new HashSet(); private static bool _seenDirty; private static float _seenDirtyAt; private const float SeenDebounceSec = 1.5f; private const int SeenCharsCap = 20000; public static int LoadedFallbackFonts => _ourAssets.Count; public static void NoteRenderedText(string text) { if (string.IsNullOrEmpty(text) || !Plugin.Config.FontFallbackEnabled || _seenChars.Count >= 20000) { return; } bool flag = false; for (int i = 0; i < text.Length; i++) { char c = text[i]; if (c >= '\u0080') { uint item = c; if (char.IsHighSurrogate(c) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) { item = (uint)char.ConvertToUtf32(c, text[++i]); } if (_seenChars.Add(item)) { flag = true; } } } if (flag) { _seenDirty = true; _seenDirtyAt = Time.realtimeSinceStartup; } } public static void Tick() { if (_seenDirty && Time.realtimeSinceStartup - _seenDirtyAt > 1.5f) { _seenDirty = false; FontFileWatcher.ReloadFonts = true; } } public static void Reload() { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) if (!InitBridge()) { return; } foreach (Object item in _graveyard) { if (item != (Object)null) { Object.Destroy(item); } } _graveyard.Clear(); if (!Plugin.Config.FontFallbackEnabled) { _bakedCharset = null; _bakedFontFiles = null; if (RemoveInjected() > 0) { RefreshAllText(); } return; } List list = DiscoverFontFiles(); HashSet hashSet = ComputeCharset(); bool fontFallbackBitmapMode = Plugin.Config.FontFallbackBitmapMode; bool flag = _ourAssets.Count > 0 && _bakedCharset != null && _bakedFontFiles != null && fontFallbackBitmapMode == _bakedBitmapMode && Mathf.Approximately(Plugin.Config.FontFallbackScale, _bakedScale) && list.SequenceEqual(_bakedFontFiles, StringComparer.OrdinalIgnoreCase) && StillInjected(); if (flag && _bakedCharset.IsSupersetOf(hashSet)) { return; } if (flag && _ourAssets.Count < 32) { HashSet hashSet2 = new HashSet(hashSet); hashSet2.ExceptWith(_bakedCharset); Material val = (fontFallbackBitmapMode ? null : FindLiveSdfMaterialTemplate()); if (!fontFallbackBitmapMode && (Object)(object)val == (Object)null) { return; } int count = _ourAssets.Count; foreach (string item2 in list) { TMP_FontAsset val2 = BakeFontAsset(item2, hashSet2, val, fontFallbackBitmapMode); if ((Object)(object)val2 != (Object)null) { _ourAssets.Add(val2); } } FontEngine.UnloadAllFontFaces(); if (_ourAssets.Count == count) { _bakedCharset.UnionWith(hashSet2); Plugin.Logger.LogInfo((object)$"[Font] {hashSet2.Count} rendered codepoint(s) not coverable by the fallback font(s) — recorded, no future rebake for them."); } else if (InjectIntoGlobalFallbacks()) { _bakedCharset.UnionWith(hashSet2); Plugin.Logger.LogInfo((object)($"[Font] Delta-baked {hashSet2.Count} new codepoint(s) into {_ourAssets.Count - count} supplementary asset(s) — " + $"chain now {_ourAssets.Count} asset(s), {_bakedCharset.Count} codepoint(s) attempted.")); RefreshAllText(); } return; } _bakedCharset = null; _bakedFontFiles = null; int num = RemoveInjected(); if (list.Count == 0) { if (num > 0) { RefreshAllText(); } return; } if (hashSet.Count == 0) { Plugin.Logger.LogInfo((object)"[Font] Fallback fonts present but no non-ASCII replacement text loaded — nothing to bake yet."); if (num > 0) { RefreshAllText(); } return; } Material val3 = (fontFallbackBitmapMode ? null : FindLiveSdfMaterialTemplate()); if (!fontFallbackBitmapMode && (Object)(object)val3 == (Object)null) { if (!_warnedNoTemplate) { _warnedNoTemplate = true; Plugin.Logger.LogWarning((object)"[Font] No live SDF font material found to clone yet — will retry on next reload."); } return; } foreach (string item3 in list) { TMP_FontAsset val4 = BakeFontAsset(item3, hashSet, val3, fontFallbackBitmapMode); if ((Object)(object)val4 != (Object)null) { _ourAssets.Add(val4); } } FontEngine.UnloadAllFontFaces(); if (_ourAssets.Count > 0 && InjectIntoGlobalFallbacks()) { _bakedCharset = hashSet; _bakedFontFiles = list; _bakedBitmapMode = fontFallbackBitmapMode; _bakedScale = Plugin.Config.FontFallbackScale; Plugin.Logger.LogInfo((object)$"[Font] Injected {_ourAssets.Count} fallback font(s) covering {hashSet.Count} codepoint(s) into TMP_Settings.fallbackFontAssets."); RefreshAllText(); } } private static bool StillInjected() { List list = (((Object)(object)TMP_Settings.instance != (Object)null) ? TMP_Settings.fallbackFontAssets : null); if (list == null) { return false; } foreach (TMP_FontAsset ourAsset in _ourAssets) { if ((Object)(object)ourAsset == (Object)null || !list.Contains(ourAsset)) { return false; } } return true; } private static bool InitBridge() { if (_bridgeInit) { return _tryAddGlyphToTexture != null; } _bridgeInit = true; _tryAddGlyphToTexture = AccessTools.Method(typeof(FontEngine), "TryAddGlyphToTexture", new Type[8] { typeof(uint), typeof(int), typeof(GlyphPackingMode), typeof(List), typeof(List), typeof(GlyphRenderMode), typeof(Texture2D), typeof(Glyph).MakeByRefType() }, (Type[])null); if (_tryAddGlyphToTexture == null) { Plugin.Logger.LogWarning((object)"[Font] FontEngine.TryAddGlyphToTexture not resolvable — fallback font baking unavailable on this game build."); } return _tryAddGlyphToTexture != null; } private static List DiscoverFontFiles() { List result = new List(); foreach (string activePackPath in PackManager.ActivePackPaths) { AddFontsFrom(Path.Combine(activePackPath, "Fonts", "_fallback"), result); } AddFontsFrom(Path.Combine(Plugin.BasePath, "Fonts", "_fallback"), result); return result; } private static void AddFontsFrom(string dir, List result) { if (!Directory.Exists(dir)) { return; } foreach (string item in (from f in Directory.GetFiles(dir) where f.EndsWith(".ttf", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".otf", StringComparison.OrdinalIgnoreCase) select f).OrderBy((string f) => f, StringComparer.OrdinalIgnoreCase)) { if (!result.Contains(item)) { result.Add(item); } } } private static HashSet ComputeCharset() { HashSet hashSet = new HashSet(); foreach (Dictionary> value in DialogueHandler.TextCache.Values) { foreach (Dictionary value2 in value.Values) { foreach (string value3 in value2.Values) { if (string.IsNullOrEmpty(value3)) { continue; } for (int i = 0; i < value3.Length; i++) { char c = value3[i]; if (char.IsHighSurrogate(c) && i + 1 < value3.Length && char.IsLowSurrogate(value3[i + 1])) { hashSet.Add((uint)char.ConvertToUtf32(c, value3[++i])); } else if (c >= '\u0080') { hashSet.Add(c); } } } } } if (_seenChars.Count > 0) { HashSet hashSet2 = BuildLiveCoverage(); foreach (uint seenChar in _seenChars) { if (!hashSet2.Contains((int)seenChar)) { hashSet.Add(seenChar); } } } return hashSet; } private static HashSet BuildLiveCoverage() { HashSet hashSet = new HashSet(); TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_FontAsset val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((TMP_Asset)val).material == (Object)null || ((Object)val).name.StartsWith("Stitchwork_Fallback_", StringComparison.Ordinal)) { continue; } Dictionary characterDictionary = val.characterDictionary; if (characterDictionary == null) { continue; } foreach (int key in characterDictionary.Keys) { hashSet.Add(key); } } return hashSet; } private static TMP_FontAsset BakeFontAsset(string path, HashSet charset, Material sdfTemplate, bool bitmap) { //IL_0017: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_038b: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_040e: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Expected O, but got Unknown //IL_0484: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown //IL_047a: Unknown result type (might be due to invalid IL or missing references) //IL_0481: Expected O, but got Unknown //IL_051a: Unknown result type (might be due to invalid IL or missing references) //IL_0549: Unknown result type (might be due to invalid IL or missing references) //IL_0553: Expected O, but got Unknown int num = Mathf.Clamp(Plugin.Config.FontFallbackPointSize, 24, 128); if ((int)FontEngine.InitializeFontEngine() != 0) { Plugin.Logger.LogWarning((object)"[Font] FontEngine failed to initialize."); return null; } if ((int)FontEngine.LoadFontFace(path, num) != 0) { Plugin.Logger.LogWarning((object)("[Font] Could not load font face: " + Path.GetFileName(path))); return null; } int num2 = num + 10; int num3; for (num3 = 512; num3 < 8192; num3 *= 2) { int num4 = num3 / num2; if ((float)(num4 * num4) >= (float)charset.Count * 1.15f) { break; } } Texture2D val = new Texture2D(num3, num3, (TextureFormat)1, false) { name = "Stitchwork_Fallback_" + Path.GetFileNameWithoutExtension(path) + "_Atlas", hideFlags = (HideFlags)32, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; val.LoadRawTextureData(new byte[num3 * num3]); val.Apply(false, false); GlyphRenderMode val2 = (GlyphRenderMode)(bitmap ? 4117 : 4165); List list = new List { new GlyphRect(1, 1, num3 - 2, num3 - 2) }; List list2 = new List(); List list3 = new List(charset.Count); int num5 = 0; int num6 = 0; uint num7 = default(uint); foreach (uint item in charset.OrderBy((uint u) => u)) { if (!FontEngine.TryGetGlyphIndex(item, ref num7) || num7 == 0) { num5++; continue; } object[] array = new object[8] { num7, 5, (object)(GlyphPackingMode)0, list, list2, val2, val, null }; bool flag; try { flag = (bool)_tryAddGlyphToTexture.Invoke(null, array); } catch (TargetInvocationException ex) { Plugin.Logger.LogWarning((object)$"[Font] Glyph render failed for U+{item:X4}: {ex.InnerException?.Message}"); num6++; continue; } if (!flag) { num6++; continue; } Glyph val3 = (Glyph)array[7]; GlyphRect glyphRect = val3.glyphRect; GlyphMetrics metrics = val3.metrics; list3.Add(new TMP_Glyph { id = (int)item, x = ((GlyphRect)(ref glyphRect)).x, y = num3 - ((GlyphRect)(ref glyphRect)).y - ((GlyphRect)(ref glyphRect)).height, width = ((GlyphRect)(ref glyphRect)).width, height = ((GlyphRect)(ref glyphRect)).height, xOffset = ((GlyphMetrics)(ref metrics)).horizontalBearingX, yOffset = ((GlyphMetrics)(ref metrics)).horizontalBearingY, xAdvance = ((GlyphMetrics)(ref metrics)).horizontalAdvance, scale = 1f }); } if (list3.Count == 0) { Plugin.Logger.LogWarning((object)$"[Font] '{Path.GetFileName(path)}' covered none of the {charset.Count} needed codepoint(s) — skipped."); _graveyard.Add((Object)(object)val); return null; } FaceInfo faceInfo = FontEngine.GetFaceInfo(); float scale = Plugin.Config.FontFallbackScale; string text = Path.GetFileNameWithoutExtension(path); int num8 = text.LastIndexOf("@s", StringComparison.OrdinalIgnoreCase); if (num8 > 0 && float.TryParse(text.Substring(num8 + 2), NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { scale = Mathf.Clamp(result, 0.5f, 1.5f); text = text.Substring(0, num8); } FaceInfo val4 = new FaceInfo { Name = text, PointSize = ((FaceInfo)(ref faceInfo)).pointSize, Scale = scale, LineHeight = ((FaceInfo)(ref faceInfo)).lineHeight, Baseline = ((FaceInfo)(ref faceInfo)).baseline, Ascender = ((FaceInfo)(ref faceInfo)).ascentLine, Descender = ((FaceInfo)(ref faceInfo)).descentLine, CapHeight = ((FaceInfo)(ref faceInfo)).capLine, Padding = 5f, AtlasWidth = num3, AtlasHeight = num3, Underline = ((FaceInfo)(ref faceInfo)).underlineOffset, UnderlineThickness = ((FaceInfo)(ref faceInfo)).underlineThickness, TabWidth = ((FaceInfo)(ref faceInfo)).tabWidth, SubSize = 0.5f, SuperscriptOffset = ((FaceInfo)(ref faceInfo)).ascentLine, SubscriptOffset = ((FaceInfo)(ref faceInfo)).underlineOffset }; Material val6; if (bitmap) { Shader val5 = Shader.Find("TextMeshPro/Bitmap") ?? Shader.Find("TextMeshPro/Mobile/Bitmap"); if ((Object)(object)val5 == (Object)null) { Plugin.Logger.LogWarning((object)"[Font] No TMP bitmap shader found — cannot build bitmap-mode fallback."); _graveyard.Add((Object)(object)val); return null; } val6 = new Material(val5); } else { val6 = new Material(sdfTemplate); val6.SetFloat("_GradientScale", 6f); val6.SetFloat("_TextureWidth", (float)num3); val6.SetFloat("_TextureHeight", (float)num3); } ((Object)val6).name = "Stitchwork_Fallback_" + Path.GetFileNameWithoutExtension(path) + "_Mat"; ((Object)val6).hideFlags = (HideFlags)32; val6.SetTexture("_MainTex", (Texture)(object)val); TMP_FontAsset val7 = ScriptableObject.CreateInstance(); ((Object)val7).name = "Stitchwork_Fallback_" + Path.GetFileNameWithoutExtension(path); ((Object)val7).hideFlags = (HideFlags)32; val7.fontAssetType = (FontAssetTypes)((!bitmap) ? 1 : 2); val7.atlas = val; ((TMP_Asset)val7).material = val6; val7.AddFaceInfo(val4); val7.AddGlyphInfo(list3.ToArray()); val7.AddKerningInfo(new KerningTable()); val7.ReadFontDefinition(); string text2 = ((num5 > 0 || num6 > 0) ? $" ({num5} not in font, {num6} pack/render failures)" : ""); Plugin.Logger.LogInfo((object)string.Format("[Font] Baked '{0}': {1} glyph(s) into {2}x{3} {4} atlas{5}.", Path.GetFileName(path), list3.Count, num3, num3, bitmap ? "bitmap" : "SDF", text2)); return val7; } private static Material FindLiveSdfMaterialTemplate() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Invalid comparison between Unknown and I4 TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_FontAsset val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)((TMP_Asset)val).material == (Object)null) && !((Object)val).name.StartsWith("Stitchwork_Fallback_", StringComparison.Ordinal) && (int)val.fontAssetType == 1) { return ((TMP_Asset)val).material; } } return null; } private static bool InjectIntoGlobalFallbacks() { TMP_Settings instance = TMP_Settings.instance; if ((Object)(object)instance == (Object)null) { Plugin.Logger.LogWarning((object)"[Font] TMP_Settings.instance is null — cannot inject (will retry on next reload)."); return false; } List list = TMP_Settings.fallbackFontAssets; if (list == null) { list = new List(); AccessTools.Field(typeof(TMP_Settings), "m_fallbackFontAssets")?.SetValue(instance, list); if (TMP_Settings.fallbackFontAssets == null) { Plugin.Logger.LogWarning((object)"[Font] Could not initialize TMP_Settings.m_fallbackFontAssets."); return false; } } list.RemoveAll((TMP_FontAsset f) => (Object)(object)f != (Object)null && ((Object)f).name.StartsWith("Stitchwork_Fallback_", StringComparison.Ordinal)); list.AddRange(_ourAssets); return true; } private static int RemoveInjected() { int result = 0; List list = (((Object)(object)TMP_Settings.instance != (Object)null) ? TMP_Settings.fallbackFontAssets : null); if (list != null) { result = list.RemoveAll((TMP_FontAsset f) => (Object)(object)f != (Object)null && ((Object)f).name.StartsWith("Stitchwork_Fallback_", StringComparison.Ordinal)); } foreach (TMP_FontAsset ourAsset in _ourAssets) { if (!((Object)(object)ourAsset == (Object)null)) { if ((Object)(object)ourAsset.atlas != (Object)null) { _graveyard.Add((Object)(object)ourAsset.atlas); } if ((Object)(object)((TMP_Asset)ourAsset).material != (Object)null) { _graveyard.Add((Object)(object)((TMP_Asset)ourAsset).material); } _graveyard.Add((Object)(object)ourAsset); } } _ourAssets.Clear(); return result; } private static void RefreshAllText() { TMP_Text[] array = Resources.FindObjectsOfTypeAll(); foreach (TMP_Text val in array) { if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy) { val.ForceMeshUpdate(); } } } } public static class GlobalAmbientProbe { private static bool _isOverridden; private static Color _savedAmbientLight; private static Color _savedAmbientSky; private static Color _savedAmbientEquator; private static Color _savedAmbientGround; private static float _savedAmbientIntensity; private static Vector4 _savedSHAr; private static Vector4 _savedSHAg; private static Vector4 _savedSHAb; private static Vector4 _savedSHBr; private static Vector4 _savedSHBg; private static Vector4 _savedSHBb; private static Vector4 _savedSHC; public static string LastStatus = ""; public static bool IsOverridden => _isOverridden; public static void ZeroAll() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: 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_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00a0: Unknown result type (might be due to invalid IL or missing references) if (!_isOverridden) { _savedAmbientLight = RenderSettings.ambientLight; _savedAmbientSky = RenderSettings.ambientSkyColor; _savedAmbientEquator = RenderSettings.ambientEquatorColor; _savedAmbientGround = RenderSettings.ambientGroundColor; _savedAmbientIntensity = RenderSettings.ambientIntensity; _savedSHAr = Shader.GetGlobalVector("unity_SHAr"); _savedSHAg = Shader.GetGlobalVector("unity_SHAg"); _savedSHAb = Shader.GetGlobalVector("unity_SHAb"); _savedSHBr = Shader.GetGlobalVector("unity_SHBr"); _savedSHBg = Shader.GetGlobalVector("unity_SHBg"); _savedSHBb = Shader.GetGlobalVector("unity_SHBb"); _savedSHC = Shader.GetGlobalVector("unity_SHC"); _isOverridden = true; } RenderSettings.ambientLight = Color.white; RenderSettings.ambientSkyColor = Color.white; RenderSettings.ambientEquatorColor = Color.white; RenderSettings.ambientGroundColor = Color.white; Shader.SetGlobalColor("unity_AmbientSky", Color.white); Shader.SetGlobalColor("unity_AmbientEquator", Color.white); Shader.SetGlobalColor("unity_AmbientGround", Color.white); Shader.SetGlobalVector("unity_SHAr", Vector4.zero); Shader.SetGlobalVector("unity_SHAg", Vector4.zero); Shader.SetGlobalVector("unity_SHAb", Vector4.zero); Shader.SetGlobalVector("unity_SHBr", Vector4.zero); Shader.SetGlobalVector("unity_SHBg", Vector4.zero); Shader.SetGlobalVector("unity_SHBb", Vector4.zero); Shader.SetGlobalVector("unity_SHC", Vector4.zero); LastStatus = "Ambient + SH zeroed. Saved: sky=" + ColorStr(_savedAmbientSky) + " eq=" + ColorStr(_savedAmbientEquator) + " gr=" + ColorStr(_savedAmbientGround) + ", plus 7 SH coefficients."; Plugin.Logger.LogInfo((object)("[GlobalAmbientProbe] " + LastStatus)); } public static void Restore() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (!_isOverridden) { LastStatus = "Nothing to restore."; return; } RenderSettings.ambientLight = _savedAmbientLight; RenderSettings.ambientSkyColor = _savedAmbientSky; RenderSettings.ambientEquatorColor = _savedAmbientEquator; RenderSettings.ambientGroundColor = _savedAmbientGround; RenderSettings.ambientIntensity = _savedAmbientIntensity; Shader.SetGlobalColor("unity_AmbientSky", _savedAmbientSky); Shader.SetGlobalColor("unity_AmbientEquator", _savedAmbientEquator); Shader.SetGlobalColor("unity_AmbientGround", _savedAmbientGround); Shader.SetGlobalVector("unity_SHAr", _savedSHAr); Shader.SetGlobalVector("unity_SHAg", _savedSHAg); Shader.SetGlobalVector("unity_SHAb", _savedSHAb); Shader.SetGlobalVector("unity_SHBr", _savedSHBr); Shader.SetGlobalVector("unity_SHBg", _savedSHBg); Shader.SetGlobalVector("unity_SHBb", _savedSHBb); Shader.SetGlobalVector("unity_SHC", _savedSHC); _isOverridden = false; LastStatus = "Ambient + SH restored."; Plugin.Logger.LogInfo((object)("[GlobalAmbientProbe] " + LastStatus)); } private static string ColorStr(Color c) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return $"({c.r:0.00},{c.g:0.00},{c.b:0.00})"; } } public static class MaterialDumper { private enum MpbPropType { Color, Float, Vector } private static readonly (string Name, MpbPropType Type)[] KnownTintProps = new(string, MpbPropType)[15] { ("_Color", MpbPropType.Color), ("_CharacterTintColor", MpbPropType.Color), ("_FlashColor", MpbPropType.Color), ("_FlashAmount", MpbPropType.Float), ("_IsCharacter", MpbPropType.Float), ("_IsHero", MpbPropType.Float), ("_CanDesaturate", MpbPropType.Float), ("_Desaturation", MpbPropType.Float), ("_CanLerpAmbient", MpbPropType.Float), ("_AmbientLerp", MpbPropType.Float), ("_BlackThreadAmount", MpbPropType.Float), ("_IsBlackThreaded", MpbPropType.Float), ("_CanHueShift", MpbPropType.Float), ("_HueShift", MpbPropType.Float), ("_IsMaskingSprite", MpbPropType.Float) }; public static string LastStatus = ""; private static readonly string[] CandidateGlobalNames = new string[30] { "unity_AmbientSky", "unity_AmbientEquator", "unity_AmbientGround", "unity_FogColor", "unity_LightColor0", "unity_SHAr", "unity_SHAg", "unity_SHAb", "unity_SHBr", "unity_SHBg", "unity_SHBb", "unity_SHC", "_SceneTint", "_SceneColor", "_AmbientColor", "_GlobalColor", "_GlobalTint", "_LightColor", "_LightTint", "_FogColor", "_TimeOfDayColor", "_DayNightTint", "_SaturationGlobal", "_BrightnessGlobal", "_ContrastGlobal", "_TintColor", "_Tint", "_Brightness", "_Saturation", "_Contrast" }; public static string DefaultOutputPath { get { //IL_0000: 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) Scene activeScene = SceneManager.GetActiveScene(); string obj = ((Scene)(ref activeScene)).name ?? "unknown"; StringBuilder stringBuilder = new StringBuilder(obj.Length); string text = obj; foreach (char c in text) { if (char.IsLetterOrDigit(c) || c == '_' || c == '-') { stringBuilder.Append(c); } else { stringBuilder.Append('_'); } } return Path.Combine(Plugin.BasePath, $"material-tint-dump__{stringBuilder}.txt"); } } public static string DumpAll(string outputPath = null) { if (outputPath == null) { outputPath = DefaultOutputPath; } StringBuilder stringBuilder = new StringBuilder(16384); try { WriteHeader(stringBuilder); } catch (Exception arg) { stringBuilder.AppendLine($"[error] header: {arg}"); } try { WriteSceneGlobals(stringBuilder); } catch (Exception arg2) { stringBuilder.AppendLine($"[error] scene globals: {arg2}"); } try { WriteCollections(stringBuilder); } catch (Exception arg3) { stringBuilder.AppendLine($"[error] collections: {arg3}"); } try { WriteRenderers(stringBuilder); } catch (Exception arg4) { stringBuilder.AppendLine($"[error] renderers: {arg4}"); } try { IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(outputPath)); File.WriteAllText(IOUtil.LongSafe(outputPath), stringBuilder.ToString()); LastStatus = $"Wrote {stringBuilder.Length:N0} chars to {Path.GetFileName(outputPath)}"; Plugin.Logger.LogInfo((object)("[MaterialDumper] " + LastStatus + " (" + outputPath + ")")); return outputPath; } catch (Exception ex) { LastStatus = "FAILED to write report: " + ex.Message; Plugin.Logger.LogError((object)("[MaterialDumper] " + LastStatus)); return null; } } private static void WriteHeader(StringBuilder sb) { //IL_0000: 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) Scene activeScene = SceneManager.GetActiveScene(); sb.AppendLine("# Stitchwork — Material Tint Diagnostic Dump"); sb.AppendLine($"# Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); sb.AppendLine($"# Active scene: {((Scene)(ref activeScene)).name} (build index {((Scene)(ref activeScene)).buildIndex})"); sb.AppendLine("# Stitchwork version: 0.8.6"); sb.AppendLine(); sb.AppendLine("Scope:"); sb.AppendLine(" 1. Scene-level state (RenderSettings, Camera, Lights, shader globals)"); sb.AppendLine(" 2. Every live tk2dSpriteCollectionData's per-material shader properties"); sb.AppendLine(" 3. A sample of tk2d sprite + standard renderer per-instance state"); sb.AppendLine(); sb.AppendLine("Cross-reference: docs/material-tint-investigation.md."); sb.AppendLine(); sb.AppendLine(); } private static void WriteSceneGlobals(StringBuilder sb) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: 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_0115: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_0585: Unknown result type (might be due to invalid IL or missing references) //IL_058a: Unknown result type (might be due to invalid IL or missing references) //IL_058c: Unknown result type (might be due to invalid IL or missing references) //IL_05d1: Unknown result type (might be due to invalid IL or missing references) //IL_059a: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_05a8: Unknown result type (might be due to invalid IL or missing references) //IL_061a: Unknown result type (might be due to invalid IL or missing references) //IL_061f: Unknown result type (might be due to invalid IL or missing references) //IL_05b6: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("================================================================"); sb.AppendLine(" SCENE GLOBALS"); sb.AppendLine("================================================================"); sb.AppendLine(); sb.AppendLine("[RenderSettings]"); sb.AppendLine($" ambientMode = {RenderSettings.ambientMode}"); sb.AppendLine(" ambientLight = " + C(RenderSettings.ambientLight)); sb.AppendLine(" ambientSkyColor = " + C(RenderSettings.ambientSkyColor)); sb.AppendLine(" ambientEquatorColor = " + C(RenderSettings.ambientEquatorColor)); sb.AppendLine(" ambientGroundColor = " + C(RenderSettings.ambientGroundColor)); sb.AppendLine($" ambientIntensity = {RenderSettings.ambientIntensity:0.000}"); sb.AppendLine($" fog = {RenderSettings.fog}"); sb.AppendLine($" fogMode = {RenderSettings.fogMode}"); sb.AppendLine(" fogColor = " + C(RenderSettings.fogColor)); sb.AppendLine($" fogDensity = {RenderSettings.fogDensity:0.000}"); sb.AppendLine($" fogStartDistance = {RenderSettings.fogStartDistance:0.000}"); sb.AppendLine($" fogEndDistance = {RenderSettings.fogEndDistance:0.000}"); sb.AppendLine(); Camera main = Camera.main; if ((Object)(object)main != (Object)null) { sb.AppendLine("[Camera.main: '" + ((Object)main).name + "']"); sb.AppendLine(" backgroundColor = " + C(main.backgroundColor)); sb.AppendLine($" clearFlags = {main.clearFlags}"); sb.AppendLine($" allowHDR = {main.allowHDR}"); sb.AppendLine($" allowMSAA = {main.allowMSAA}"); sb.AppendLine($" cullingMask = 0x{main.cullingMask:X8}"); sb.AppendLine($" orthographic = {main.orthographic}"); sb.AppendLine($" orthographicSize = {main.orthographicSize:0.000}"); } else { sb.AppendLine("[Camera.main] (none)"); } sb.AppendLine(); Camera[] array = Object.FindObjectsByType((FindObjectsSortMode)0); sb.AppendLine($"[All Cameras] ({array.Length} total)"); Camera[] array2 = array; foreach (Camera val in array2) { if (!((Object)(object)val == (Object)null)) { sb.AppendLine(" '" + TPath(((Component)val).transform) + "' " + $"depth={val.depth:0.0} clear={val.clearFlags} bg={C(val.backgroundColor)}"); } } sb.AppendLine(); Light[] array3 = Object.FindObjectsByType((FindObjectsSortMode)0); sb.AppendLine($"[Lights] ({array3.Length} total)"); Light[] array4 = array3; foreach (Light val2 in array4) { if (!((Object)(object)val2 == (Object)null)) { sb.AppendLine($" '{TPath(((Component)val2).transform)}' enabled={((Behaviour)val2).enabled} " + $"type={val2.type} color={C(val2.color)} " + $"intensity={val2.intensity:0.00} bounceIntensity={val2.bounceIntensity:0.00}"); } } sb.AppendLine(); sb.AppendLine("[Post-process / Volume scan — reflection probe]"); string[] array5; try { string[] obj = new string[3] { "UnityEngine.Rendering.PostProcessing.PostProcessVolume", "UnityEngine.Rendering.Volume", "UnityEngine.Rendering.PostProcessing.PostProcessLayer" }; int num = 0; array5 = obj; foreach (string text in array5) { Type type = AccessTools.TypeByName(text); if (type == null) { continue; } Object[] array6 = Object.FindObjectsByType(type, (FindObjectsSortMode)0); if (array6.Length == 0) { continue; } sb.AppendLine($" type {text}: {array6.Length} instance(s)"); Object[] array7 = array6; foreach (Object obj2 in array7) { Component val3 = (Component)(object)((obj2 is Component) ? obj2 : null); if (val3 == null) { continue; } string text2 = "?"; try { PropertyInfo property = type.GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { text2 = ((bool)property.GetValue(val3)).ToString(); } } catch (TargetInvocationException) { } catch (InvalidCastException) { } sb.AppendLine(" '" + TPath(val3.transform) + "' enabled=" + text2); } num += array6.Length; } if (num == 0) { sb.AppendLine(" (no PostProcessVolume / Volume / PostProcessLayer found)"); } } catch (Exception ex3) { sb.AppendLine(" [error: " + ex3.Message + "]"); } sb.AppendLine(); sb.AppendLine("[Shader Globals — speculative probe]"); sb.AppendLine(" (probed by candidate names; non-default values are signal)"); bool flag = false; array5 = CandidateGlobalNames; foreach (string text3 in array5) { Color globalColor = Shader.GetGlobalColor(text3); if (globalColor.r != 0f || globalColor.g != 0f || globalColor.b != 0f || globalColor.a != 0f) { sb.AppendLine(" GlobalColor '" + text3 + "' = " + C(globalColor)); flag = true; } float globalFloat = Shader.GetGlobalFloat(text3); if (Math.Abs(globalFloat) > 0.0001f) { sb.AppendLine($" GlobalFloat '{text3}' = {globalFloat:0.000}"); flag = true; } Vector4 globalVector = Shader.GetGlobalVector(text3); if (((Vector4)(ref globalVector)).sqrMagnitude > 0.0001f) { sb.AppendLine(" GlobalVector '" + text3 + "' = " + ((Vector4)(ref globalVector)).ToString("F3")); flag = true; } } if (!flag) { sb.AppendLine(" (none of the probed names had non-default values)"); } sb.AppendLine(); sb.AppendLine(); } private static void WriteCollections(StringBuilder sb) { sb.AppendLine("================================================================"); sb.AppendLine(" tk2dSpriteCollectionData materials"); sb.AppendLine("================================================================"); sb.AppendLine(); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); sb.AppendLine($"({array.Length} collections)"); sb.AppendLine(); tk2dSpriteCollectionData[] array2 = array; foreach (tk2dSpriteCollectionData val in array2) { if ((Object)(object)val == (Object)null) { continue; } try { sb.AppendLine("--- " + ((Object)val).name + " ---"); if (val.materials == null || val.materials.Length == 0) { sb.AppendLine(" (no materials)"); sb.AppendLine(); continue; } for (int j = 0; j < val.materials.Length; j++) { Material val2 = val.materials[j]; if ((Object)(object)val2 == (Object)null) { sb.AppendLine($" [Material {j}] (null)"); continue; } string name = ((Object)val2).name; string arg = ((name != null) ? name.Split(' ')[0] : null) ?? ""; string arg2 = (((Object)(object)val2.shader != (Object)null) ? ((Object)val2.shader).name : ""); sb.AppendLine($" [Material {j}: {arg}] shader: {arg2}"); string[] array3 = null; try { array3 = val2.shaderKeywords; } catch { } if (array3 != null && array3.Length != 0) { sb.AppendLine(" keywords: " + string.Join(", ", array3)); } DumpShaderProperties(sb, val2, " "); sb.AppendLine(); } } catch (Exception ex) { sb.AppendLine(" [error: " + ex.Message + "]"); } sb.AppendLine(); } } private static void DumpShaderProperties(StringBuilder sb, Material mat, string indent) { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected I4, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_022e: 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) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) Shader shader = mat.shader; if ((Object)(object)shader == (Object)null) { sb.AppendLine(indent + "(null shader; no properties)"); return; } int num = 0; try { num = shader.GetPropertyCount(); } catch (Exception ex) { sb.AppendLine(indent + "(GetPropertyCount failed: " + ex.Message + ")"); return; } if (num == 0) { sb.AppendLine(indent + "(shader exposes 0 properties)"); return; } for (int i = 0; i < num; i++) { try { string propertyName = shader.GetPropertyName(i); ShaderPropertyType propertyType = shader.GetPropertyType(i); string text; switch ((int)propertyType) { case 0: text = C(mat.GetColor(propertyName)); break; case 1: { Vector4 vector = mat.GetVector(propertyName); text = ((Vector4)(ref vector)).ToString("F3"); break; } case 2: case 3: text = mat.GetFloat(propertyName).ToString("0.000"); break; case 4: { Texture texture = mat.GetTexture(propertyName); if ((Object)(object)texture == (Object)null) { text = "(null)"; break; } Texture2D val = (Texture2D)(object)((texture is Texture2D) ? texture : null); if (val != null) { text = $"{((object)val).GetType().Name} '{((Object)val).name}' {((Texture)val).width}x{((Texture)val).height} fmt={val.format}"; } else { RenderTexture val2 = (RenderTexture)(object)((texture is RenderTexture) ? texture : null); text = ((val2 == null) ? (((object)texture).GetType().Name + " '" + ((Object)texture).name + "'") : $"RenderTexture '{((Object)val2).name}' {((Texture)val2).width}x{((Texture)val2).height} fmt={val2.format}"); } break; } case 5: text = mat.GetInt(propertyName).ToString(); break; default: text = "(unknown property type)"; break; } sb.AppendLine($"{indent}{propertyName,-22} ({propertyType,-8}) = {text}"); } catch (Exception ex2) { sb.AppendLine($"{indent} [error on property {i}: {ex2.Message}]"); } } } private static void WriteRenderers(StringBuilder sb) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Expected O, but got Unknown //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) sb.AppendLine("================================================================"); sb.AppendLine(" RENDERERS — sample of per-instance state"); sb.AppendLine("================================================================"); sb.AppendLine(); sb.AppendLine("Per-renderer .color and MaterialPropertyBlock samples. Capped at"); sb.AppendLine("first 100 entries each to keep the report scannable."); sb.AppendLine(); try { tk2dBaseSprite[] array = Resources.FindObjectsOfTypeAll(); sb.AppendLine($"[tk2dBaseSprite] ({array.Length} total)"); int num = 0; int num2 = 0; tk2dBaseSprite[] array2 = array; foreach (tk2dBaseSprite val in array2) { if (!((Object)(object)val == (Object)null)) { if (num >= 100) { break; } Color val2 = Color.white; try { val2 = val.color; } catch { } bool flag = val2.r == 1f && val2.g == 1f && val2.b == 1f && val2.a == 1f; if (!flag) { num2++; } sb.AppendLine(" " + (flag ? " " : "*") + " '" + TPath(((Component)val).transform) + "' color=" + C(val2)); num++; } } if (array.Length > 100) { sb.AppendLine($" ... +{array.Length - 100} more (sample shown above)"); } sb.AppendLine($" ({num2} of first {num} have non-white .color — flagged with '*')"); sb.AppendLine(); } catch (Exception ex) { sb.AppendLine("[tk2dBaseSprite] error: " + ex.Message); sb.AppendLine(); } try { SpriteRenderer[] array3 = Resources.FindObjectsOfTypeAll(); sb.AppendLine($"[SpriteRenderer] ({array3.Length} total)"); int num3 = 0; int num4 = 0; SpriteRenderer[] array4 = array3; foreach (SpriteRenderer val3 in array4) { if (!((Object)(object)val3 == (Object)null)) { if (num3 >= 100) { break; } bool flag2 = val3.color.r == 1f && val3.color.g == 1f && val3.color.b == 1f && val3.color.a == 1f; if (!flag2) { num4++; } sb.AppendLine(" " + (flag2 ? " " : "*") + " '" + TPath(((Component)val3).transform) + "' color=" + C(val3.color)); num3++; } } if (array3.Length > 100) { sb.AppendLine($" ... +{array3.Length - 100} more (sample shown above)"); } sb.AppendLine($" ({num4} of first {num3} have non-white .color — flagged with '*')"); sb.AppendLine(); } catch (Exception ex2) { sb.AppendLine("[SpriteRenderer] error: " + ex2.Message); sb.AppendLine(); } try { Renderer[] array5 = Resources.FindObjectsOfTypeAll(); sb.AppendLine("[MaterialPropertyBlock contents — probed by known names]"); sb.AppendLine(" Known names: " + string.Join(", ", KnownTintProps.Select(((string Name, MpbPropType Type) p) => p.Name))); sb.AppendLine(" Showing renderers with at least one non-default known tint property."); sb.AppendLine(" Capped at first 100 to keep the report scannable."); sb.AppendLine(); MaterialPropertyBlock val4 = new MaterialPropertyBlock(); int num5 = 0; int num6 = 0; int num7 = 0; Renderer[] array6 = array5; foreach (Renderer val5 in array6) { if ((Object)(object)val5 == (Object)null) { continue; } val5.GetPropertyBlock(val4); if (val4.isEmpty) { continue; } num5++; List list = new List(); (string, MpbPropType)[] knownTintProps = KnownTintProps; for (int num8 = 0; num8 < knownTintProps.Length; num8++) { (string, MpbPropType) tuple = knownTintProps[num8]; string text = ProbeMpbProperty(val4, tuple.Item1, tuple.Item2); if (text != null) { list.Add(text); } } if (list.Count == 0) { continue; } num6++; if (num7 >= 100) { continue; } sb.AppendLine(" '" + TPath(((Component)val5).transform) + "' (" + ((object)val5).GetType().Name + ")"); foreach (string item in list) { sb.AppendLine(" " + item); } num7++; } if (num5 == 0) { sb.AppendLine(" (no renderers carry an MPB override)"); } else { if (num6 > 100) { sb.AppendLine($" ... +{num6 - 100} more renderers with " + "non-default known tint props"); } sb.AppendLine($" total renderers scanned: {array5.Length}"); sb.AppendLine($" renderers with non-empty MPB: {num5}"); sb.AppendLine($" renderers with non-default known tint props: {num6}"); sb.AppendLine(" ratio (interesting/all-MPB): " + $"{((num5 == 0) ? 0.0 : ((double)num6 * 100.0 / (double)num5)):0.0}%"); } sb.AppendLine(); } catch (Exception ex3) { sb.AppendLine("[MaterialPropertyBlock] error: " + ex3.Message); sb.AppendLine(); } } private static string ProbeMpbProperty(MaterialPropertyBlock mpb, string name, MpbPropType type) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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) switch (type) { case MpbPropType.Color: { Color color = mpb.GetColor(name); bool num2 = color.r == 1f && color.g == 1f && color.b == 1f && color.a == 1f; bool flag = color.r == 0f && color.g == 0f && color.b == 0f && color.a == 0f; if (num2 || flag) { return null; } return $"{name,-22} (Color ) = {C(color)}"; } case MpbPropType.Float: { float num = mpb.GetFloat(name); if (Math.Abs(num) < 0.0001f) { return null; } return $"{name,-22} (Float ) = {num:0.000}"; } case MpbPropType.Vector: { Vector4 vector = mpb.GetVector(name); if (((Vector4)(ref vector)).sqrMagnitude < 0.0001f) { return null; } return string.Format("{0,-22} (Vector ) = {1}", name, ((Vector4)(ref vector)).ToString("F3")); } default: return null; } } private static string C(Color c) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) return $"({c.r:0.000}, {c.g:0.000}, {c.b:0.000}, {c.a:0.000})"; } private static string TPath(Transform t) { if ((Object)(object)t == (Object)null) { return ""; } StringBuilder stringBuilder = new StringBuilder(64); stringBuilder.Append(((Object)t).name); Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Insert(0, '/'); stringBuilder.Insert(0, ((Object)parent).name); parent = parent.parent; } return stringBuilder.ToString(); } } public static class RecolourHandler { private sealed class Rule { public Color Old; public Color New; public float Tol = 0.05f; public string Source; } private sealed class SceneRules { public readonly List Renderer = new List(); public readonly List Particle = new List(); public readonly List Groups = new List(); public Color? Ambient; } private sealed class TrackedTint { public Color From; public Color Written; public Rule Rule; public Sprite SpriteRef; public int TkSpriteId; } public const float DefaultTolerance = 0.05f; public static bool ReloadRequested; private static SceneRules _rules; private static string _rulesScene; private static readonly Dictionary _applied = new Dictionary(); private static readonly Dictionary _appliedPs = new Dictionary(); private static Rule _preview; private static Color? _previewAmbient; private static bool _ambientApplied; private static Color _ambVanLight; private static Color _ambVanSky; private static Color _ambVanEquator; private static Color _ambVanGround; private static List _globalRules; private static Rule _globalPreview; private static readonly List _effGlobal = new List(); private static bool _effGlobalDirty = true; private static readonly Dictionary _trackedSr = new Dictionary(); private static readonly Dictionary _trackedTk = new Dictionary(); private static float _nextGlobalSweep; public static int AppliedCount => _applied.Count + _appliedPs.Count; public static Color CurrentAmbientVanilla { get { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) if (!_ambientApplied) { return RenderSettings.ambientLight; } return _ambVanLight; } } public static string GlobalFile => Path.Combine(Plugin.BasePath, "Recolour", "_global.txt"); public static int GlobalTrackedCount => _trackedSr.Count + _trackedTk.Count; public static string RootFileFor(string scene) { return Path.Combine(Plugin.BasePath, "Recolour", scene + ".txt"); } public static void OnSceneUnloaded() { RestoreAll(); _rules = null; _rulesScene = null; _preview = null; } internal static bool SceneOwned(GameObject go) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Scene scene = go.scene; return !string.Equals(((Scene)(ref scene)).name, "DontDestroyOnLoad", StringComparison.Ordinal); } public static void Reload() { RestoreAll(); _rules = null; _rulesScene = null; ApplyToScene(); ReloadGlobal(); } public static void ApplyToScene() { //IL_0000: 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_029a: Unknown result type (might be due to invalid IL or missing references) //IL_009a: 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_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: 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_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_01f2: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (string.IsNullOrEmpty(name)) { return; } if (!string.Equals(_rulesScene, name, StringComparison.Ordinal)) { _rules = LoadRules(name); _rulesScene = name; } List list = EffectiveRendererRules(); if (list.Count > 0) { SpriteRenderer[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (SpriteRenderer val in array) { if ((Object)(object)val == (Object)null || !SceneOwned(((Component)val).gameObject)) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (_applied.ContainsKey(instanceID)) { continue; } Color color = val.color; foreach (Rule item in list) { if (Match(color, item.Old, item.Tol)) { if (_rules == null || _rules.Groups.Count <= 0 || InGroups(((Component)val).transform)) { _applied[instanceID] = (val, color); val.color = item.New; } break; } } } } if (_rules != null && _rules.Particle.Count > 0) { ParticleSystem[] array2 = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (ParticleSystem val2 in array2) { if ((Object)(object)val2 == (Object)null || !SceneOwned(((Component)val2).gameObject)) { continue; } int instanceID2 = ((Object)val2).GetInstanceID(); if (_appliedPs.ContainsKey(instanceID2)) { continue; } MainModule main = val2.main; MinMaxGradient startColor = ((MainModule)(ref main)).startColor; if ((int)((MinMaxGradient)(ref startColor)).mode != 0) { continue; } Color color2 = ((MinMaxGradient)(ref startColor)).color; foreach (Rule item2 in _rules.Particle) { if (Match(color2, item2.Old, item2.Tol)) { _appliedPs[instanceID2] = (val2, color2); ((MainModule)(ref main)).startColor = new MinMaxGradient(item2.New); break; } } } } Color? val3 = _previewAmbient ?? ((_rules != null) ? _rules.Ambient : ((Color?)null)); if (val3.HasValue) { ApplyAmbient(val3.Value); } } private static void ApplyAmbient(Color c) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) if (!_ambientApplied) { SnapshotAmbientVanilla(); _ambientApplied = true; } ApplyAmbientValues(c); } private static void SnapshotAmbientVanilla() { //IL_0000: 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_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) _ambVanLight = RenderSettings.ambientLight; _ambVanSky = RenderSettings.ambientSkyColor; _ambVanEquator = RenderSettings.ambientEquatorColor; _ambVanGround = RenderSettings.ambientGroundColor; } private static void ApplyAmbientValues(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) RenderSettings.ambientLight = c; RenderSettings.ambientSkyColor = c; RenderSettings.ambientEquatorColor = c; RenderSettings.ambientGroundColor = c; Shader.SetGlobalColor("unity_AmbientSky", c); Shader.SetGlobalColor("unity_AmbientEquator", c); Shader.SetGlobalColor("unity_AmbientGround", c); } public static void EnforceAmbient() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: 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_005c: Unknown result type (might be due to invalid IL or missing references) if (!_ambientApplied) { return; } Color? val = _previewAmbient ?? ((_rules != null) ? _rules.Ambient : ((Color?)null)); if (val.HasValue) { Color value = val.Value; if (!(RenderSettings.ambientLight == value)) { SnapshotAmbientVanilla(); ApplyAmbientValues(value); } } } private static void RestoreAll() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in _applied) { var (val, color) = item.Value; if (!((Object)(object)val == (Object)null)) { val.color = color; } } _applied.Clear(); foreach (KeyValuePair appliedP in _appliedPs) { var (val2, val3) = appliedP.Value; if (!((Object)(object)val2 == (Object)null)) { MainModule main = val2.main; ((MainModule)(ref main)).startColor = new MinMaxGradient(val3); } } _appliedPs.Clear(); if (_ambientApplied) { RenderSettings.ambientLight = _ambVanLight; RenderSettings.ambientSkyColor = _ambVanSky; RenderSettings.ambientEquatorColor = _ambVanEquator; RenderSettings.ambientGroundColor = _ambVanGround; Shader.SetGlobalColor("unity_AmbientSky", _ambVanSky); Shader.SetGlobalColor("unity_AmbientEquator", _ambVanEquator); Shader.SetGlobalColor("unity_AmbientGround", _ambVanGround); _ambientApplied = false; } } public static void SetPreview(Color oldC, Color newC, float tol = 0.05f) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) _preview = new Rule { Old = oldC, New = newC, Tol = tol, Source = null }; RestoreAll(); ApplyToScene(); } public static void ClearPreview() { ClearGlobalPreview(); if (_preview != null || _previewAmbient.HasValue) { _preview = null; _previewAmbient = null; RestoreAll(); ApplyToScene(); } } public static void SetAmbientPreview(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) _previewAmbient = c; RestoreAll(); ApplyToScene(); } public static void WriteRule(string scene, Color oldC, Color newC) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) UpsertRuleLine(RootFileFor(scene), oldC, newC); _preview = null; Reload(); } public static void RemoveRule(string scene, Color oldC) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (RemoveRuleLine(RootFileFor(scene), oldC)) { _preview = null; Reload(); } } private static void UpsertRuleLine(string file, Color oldC, Color newC) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(file)); List list = (File.Exists(file) ? new List(File.ReadAllLines(IOUtil.LongSafe(file))) : new List()); string text = ToHex(oldC) + " -> " + ToHex(newC); int num = FindRuleLine(list, oldC); if (num >= 0) { list[num] = text; } else { list.Add(text); } File.WriteAllLines(IOUtil.LongSafe(file), list); } private static bool RemoveRuleLine(string file, Color oldC) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (!File.Exists(file)) { return false; } List list = new List(File.ReadAllLines(IOUtil.LongSafe(file))); int num = FindRuleLine(list, oldC); if (num < 0) { return false; } list.RemoveAt(num); File.WriteAllLines(IOUtil.LongSafe(file), list); return true; } public static bool TryGetVanilla(int rendererId, out Color vanilla) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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) if (_applied.TryGetValue(rendererId, out (SpriteRenderer, Color) value)) { vanilla = value.Item2; return true; } vanilla = default(Color); return false; } public static List<(Color oldC, Color newC)> GetRootRules(string scene) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) List<(Color, Color)> list = new List<(Color, Color)>(); string path = RootFileFor(scene); if (!File.Exists(path)) { return list; } string[] array = File.ReadAllLines(IOUtil.LongSafe(path)); for (int i = 0; i < array.Length; i++) { if (TryParseRuleLine(array[i], out var particle, out var rule) && !particle) { list.Add((rule.Old, rule.New)); } } return list; } private static int FindRuleLine(List lines, Color oldC) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < lines.Count; i++) { if (TryParseRuleLine(lines[i], out var particle, out var rule) && !particle && Match(rule.Old, oldC, Mathf.Max(rule.Tol, 0.05f))) { return i; } } return -1; } public static void WriteAmbientRule(string scene, Color c) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) string path = RootFileFor(scene); IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(path)); List list = (File.Exists(path) ? new List(File.ReadAllLines(IOUtil.LongSafe(path))) : new List()); int num = list.FindIndex((string l) => l.TrimStart().StartsWith("ambient:", StringComparison.OrdinalIgnoreCase)); string text = "ambient: " + ToHex(c); if (num >= 0) { list[num] = text; } else { list.Insert(0, text); } File.WriteAllLines(IOUtil.LongSafe(path), list); _previewAmbient = null; Reload(); } public static void RemoveAmbientRule(string scene) { string path = RootFileFor(scene); if (File.Exists(path)) { List list = new List(File.ReadAllLines(IOUtil.LongSafe(path))); if (list.RemoveAll((string l) => l.TrimStart().StartsWith("ambient:", StringComparison.OrdinalIgnoreCase)) != 0) { File.WriteAllLines(IOUtil.LongSafe(path), list); _previewAmbient = null; Reload(); } } } public static bool TryGetRootAmbient(string scene, out Color c) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) c = default(Color); string path = RootFileFor(scene); if (!File.Exists(path)) { return false; } string[] array = File.ReadAllLines(IOUtil.LongSafe(path)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.StartsWith("ambient:", StringComparison.OrdinalIgnoreCase) && TryParseHex(text.Substring(8).Trim(), out c)) { return true; } } return false; } private static List EffectiveRendererRules() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) if (_preview == null) { if (_rules == null) { return new List(); } return _rules.Renderer; } List list = new List { _preview }; if (_rules != null) { foreach (Rule item in _rules.Renderer) { if (!Match(item.Old, _preview.Old, Mathf.Max(item.Tol, _preview.Tol))) { list.Add(item); } } } return list; } private static SceneRules LoadRules(string scene) { //IL_0193: Unknown result type (might be due to invalid IL or missing references) ConflictTracker.ClearTypes("recolour"); SceneRules rules = null; List renderer = new List(); List particle = new List(); foreach (string activePackPath in PackManager.ActivePackPaths) { LoadFile(Path.Combine(activePackPath, "Recolour", scene + ".txt"), activePackPath, rootWins: false); } LoadFile(RootFileFor(scene), null, rootWins: true); if (rules == null) { return null; } rules.Renderer.AddRange(renderer); rules.Particle.AddRange(particle); if (rules.Renderer.Count > 0 || rules.Particle.Count > 0 || rules.Ambient.HasValue) { Plugin.Logger.LogInfo((object)($"[Recolour] Scene '{scene}': {rules.Renderer.Count} renderer rule(s), " + $"{rules.Particle.Count} particle rule(s), {rules.Groups.Count} group scope(s)" + (rules.Ambient.HasValue ? (", ambient " + ToHex(rules.Ambient.Value)) : "") + ".")); } return rules; static Rule FindNear(List list, Rule r) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) foreach (Rule item in list) { if (Match(item.Old, r.Old, Mathf.Max(item.Tol, r.Tol))) { return item; } } return null; } void LoadFile(string file, string source, bool rootWins) { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) if (File.Exists(file)) { if (rules == null) { rules = new SceneRules(); } string[] array = File.ReadAllLines(IOUtil.LongSafe(file)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { bool particle2; Rule rule; if (text.StartsWith("group:", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring(6).Trim(); if (text2.Length > 0 && !rules.Groups.Contains(text2)) { rules.Groups.Add(text2); } } else if (text.StartsWith("ambient:", StringComparison.OrdinalIgnoreCase)) { if (TryParseHex(text.Substring(8).Trim(), out var c)) { rules.Ambient = c; } } else if (TryParseRuleLine(text, out particle2, out rule)) { rule.Source = source; List list = (particle2 ? particle : renderer); Rule rule2 = FindNear(list, rule); if (rule2 == null) { list.Add(rule); } else if (rootWins) { if (!SameColour(rule2.New, rule.New)) { ConflictTracker.Record("recolour", scene + "/" + ToHex(rule.Old), null, rule2.Source); } list[list.IndexOf(rule2)] = rule; } else if (!SameColour(rule2.New, rule.New)) { ConflictTracker.Record("recolour", scene + "/" + ToHex(rule.Old), rule2.Source, source); } } } } } } } private static bool TryParseRuleLine(string line, out bool particle, out Rule rule) { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) particle = false; rule = null; line = line.Trim(); if (line.StartsWith("particle:", StringComparison.OrdinalIgnoreCase)) { particle = true; line = line.Substring(9).Trim(); } int num = line.IndexOf("->", StringComparison.Ordinal); if (num < 0) { return false; } string s = line.Substring(0, num).Trim(); string text = line.Substring(num + 2).Trim(); float tol = 0.05f; int num2 = text.IndexOf(' '); if (num2 > 0) { string s2 = text.Substring(num2 + 1).Trim(); text = text.Substring(0, num2).Trim(); if (float.TryParse(s2, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { tol = Mathf.Clamp(result, 0.001f, 0.5f); } } if (!TryParseHex(s, out var c) || !TryParseHex(text, out var c2)) { return false; } rule = new Rule { Old = c, New = c2, Tol = tol }; return true; } private static List EffectiveGlobalRules() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) if (_globalRules == null) { LoadGlobalRules(); _effGlobalDirty = true; } if (_effGlobalDirty) { _effGlobalDirty = false; _effGlobal.Clear(); if (_globalPreview != null) { _effGlobal.Add(_globalPreview); } foreach (Rule globalRule in _globalRules) { if (_globalPreview == null || !Match(globalRule.Old, _globalPreview.Old, Mathf.Max(globalRule.Tol, _globalPreview.Tol))) { _effGlobal.Add(globalRule); } } } return _effGlobal; } private static void LoadGlobalRules() { ConflictTracker.ClearTypes("recolour-global"); _globalRules = new List(); foreach (string activePackPath in PackManager.ActivePackPaths) { LoadFile(Path.Combine(activePackPath, "Recolour", "_global.txt"), activePackPath, rootWins: false); } LoadFile(GlobalFile, null, rootWins: true); if (_globalRules.Count > 0) { Plugin.Logger.LogInfo((object)$"[Recolour] Global: {_globalRules.Count} volatile rule(s) loaded."); } static void LoadFile(string file, string source, bool rootWins) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: 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_0128: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (File.Exists(file)) { string[] array = File.ReadAllLines(IOUtil.LongSafe(file)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && !(!TryParseRuleLine(text, out var particle, out var rule) || particle)) { rule.Source = source; Rule rule2 = null; foreach (Rule globalRule in _globalRules) { if (Match(globalRule.Old, rule.Old, Mathf.Max(globalRule.Tol, rule.Tol))) { rule2 = globalRule; break; } } if (rule2 == null) { _globalRules.Add(rule); } else if (rootWins) { if (!SameColour(rule2.New, rule.New)) { ConflictTracker.Record("recolour-global", "_global/" + ToHex(rule.Old), null, rule2.Source); } _globalRules[_globalRules.IndexOf(rule2)] = rule; } else if (!SameColour(rule2.New, rule.New)) { ConflictTracker.Record("recolour-global", "_global/" + ToHex(rule.Old), rule2.Source, source); } } } } } } private static Rule MatchGlobalRule(List rules, Color c) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) foreach (Rule rule in rules) { if (Match(c, rule.Old, rule.Tol)) { return rule; } } return null; } public static void TickGlobal() { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) List list = EffectiveGlobalRules(); if (list.Count == 0) { if (GlobalTrackedCount > 0) { ReleaseAllGlobal(); } return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextGlobalSweep) { return; } _nextGlobalSweep = realtimeSinceStartup + 0.25f; SpriteRenderer[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (SpriteRenderer val in array) { if ((Object)(object)val == (Object)null) { continue; } int instanceID = ((Object)val).GetInstanceID(); if (!_trackedSr.ContainsKey(instanceID) && !_applied.ContainsKey(instanceID)) { Rule rule = MatchGlobalRule(list, val.color); if (rule != null) { _trackedSr[instanceID] = (val, new TrackedTint { From = val.color, Written = rule.New, Rule = rule, SpriteRef = val.sprite }); val.color = rule.New; } } } tk2dBaseSprite[] array2 = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (tk2dBaseSprite val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } int instanceID2 = ((Object)val2).GetInstanceID(); if (!_trackedTk.ContainsKey(instanceID2)) { Rule rule2 = MatchGlobalRule(list, val2.color); if (rule2 != null) { _trackedTk[instanceID2] = (val2, new TrackedTint { From = val2.color, Written = rule2.New, Rule = rule2, TkSpriteId = val2.spriteId }); val2.color = rule2.New; } } } } private static bool SameRGB(Color a, Color b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) if (Mathf.Approximately(a.r, b.r) && Mathf.Approximately(a.g, b.g)) { return Mathf.Approximately(a.b, b.b); } return false; } private static Color WithAlphaOf(Color rgb, Color alphaSrc) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) return new Color(rgb.r, rgb.g, rgb.b, alphaSrc.a); } public static void EnforceGlobal() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0092: 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_00a9: 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_00d7: 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) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: 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_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0281: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) if (GlobalTrackedCount == 0) { return; } List list = EffectiveGlobalRules(); List list2 = null; foreach (KeyValuePair item in _trackedSr) { var (val, trackedTint) = item.Value; if ((Object)(object)val == (Object)null) { (list2 ?? (list2 = new List())).Add(item.Key); continue; } if (!list.Contains(trackedTint.Rule) || (Object)(object)val.sprite != (Object)(object)trackedTint.SpriteRef) { if (SameRGB(val.color, trackedTint.Written)) { val.color = WithAlphaOf(trackedTint.From, val.color); } (list2 ?? (list2 = new List())).Add(item.Key); continue; } Color color = val.color; if (SameRGB(color, trackedTint.Written)) { if (color != trackedTint.Written) { trackedTint.Written = color; } continue; } Rule rule = MatchGlobalRule(list, color); if (rule != null) { trackedTint.From = color; trackedTint.Written = rule.New; trackedTint.Rule = rule; val.color = rule.New; } else { (list2 ?? (list2 = new List())).Add(item.Key); } } if (list2 != null) { foreach (int item2 in list2) { _trackedSr.Remove(item2); } list2.Clear(); } foreach (KeyValuePair item3 in _trackedTk) { var (val2, trackedTint2) = item3.Value; if ((Object)(object)val2 == (Object)null) { (list2 ?? (list2 = new List())).Add(item3.Key); continue; } if (!list.Contains(trackedTint2.Rule) || val2.spriteId != trackedTint2.TkSpriteId) { if (SameRGB(val2.color, trackedTint2.Written)) { val2.color = WithAlphaOf(trackedTint2.From, val2.color); } (list2 ?? (list2 = new List())).Add(item3.Key); continue; } Color color2 = val2.color; if (SameRGB(color2, trackedTint2.Written)) { if (color2 != trackedTint2.Written) { trackedTint2.Written = color2; } continue; } Rule rule2 = MatchGlobalRule(list, color2); if (rule2 != null) { trackedTint2.From = color2; trackedTint2.Written = rule2.New; trackedTint2.Rule = rule2; val2.color = rule2.New; } else { (list2 ?? (list2 = new List())).Add(item3.Key); } } if (list2 == null) { return; } foreach (int item4 in list2) { _trackedTk.Remove(item4); } } private static void ReleaseAllGlobal() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair item in _trackedSr) { var (val, trackedTint) = item.Value; if ((Object)(object)val != (Object)null && SameRGB(val.color, trackedTint.Written)) { val.color = WithAlphaOf(trackedTint.From, val.color); } } _trackedSr.Clear(); foreach (KeyValuePair item2 in _trackedTk) { var (val2, trackedTint2) = item2.Value; if ((Object)(object)val2 != (Object)null && SameRGB(val2.color, trackedTint2.Written)) { val2.color = WithAlphaOf(trackedTint2.From, val2.color); } } _trackedTk.Clear(); } public static void SetGlobalPreview(Color oldC, Color newC, float tol = 0.05f) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) _globalPreview = new Rule { Old = oldC, New = newC, Tol = tol }; _effGlobalDirty = true; _nextGlobalSweep = 0f; } public static void ClearGlobalPreview() { if (_globalPreview != null) { _globalPreview = null; _effGlobalDirty = true; } } public static void WriteGlobalRule(Color oldC, Color newC) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) UpsertRuleLine(GlobalFile, oldC, newC); _globalPreview = null; ReloadGlobal(); } public static void RemoveGlobalRule(Color oldC) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) if (RemoveRuleLine(GlobalFile, oldC)) { _globalPreview = null; ReloadGlobal(); } } public static bool TryGetGlobalRule(Color oldC, out Color newC) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) newC = default(Color); if (_globalRules == null) { LoadGlobalRules(); } foreach (Rule globalRule in _globalRules) { if (Match(globalRule.Old, oldC, Mathf.Max(globalRule.Tol, 0.05f))) { newC = globalRule.New; return true; } } return false; } public static bool TryGetGlobalFrom(int rendererId, out Color from) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (_trackedSr.TryGetValue(rendererId, out (SpriteRenderer, TrackedTint) value)) { from = value.Item2.From; return true; } if (_trackedTk.TryGetValue(rendererId, out (tk2dBaseSprite, TrackedTint) value2)) { from = value2.Item2.From; return true; } from = default(Color); return false; } public static void ReloadGlobal() { _globalRules = null; _effGlobalDirty = true; _nextGlobalSweep = 0f; } public static bool TryParseHex(string s, out Color c) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) c = default(Color); if (string.IsNullOrEmpty(s)) { return false; } if (s[0] == '#') { s = s.Substring(1); } if (s.Length != 6 && s.Length != 8) { return false; } if (!uint.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { return false; } if (s.Length == 6) { result = (result << 8) | 0xFF; } c = new Color((float)((result >> 24) & 0xFF) / 255f, (float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f); return true; } public static string ToHex(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) var (b, b2, b3, b4) = Key(c); return $"#{b:X2}{b2:X2}{b3:X2}{b4:X2}"; } public static bool Match(Color a, Color b, float tol) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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) if (Mathf.Abs(a.r - b.r) <= tol && Mathf.Abs(a.g - b.g) <= tol && Mathf.Abs(a.b - b.b) <= tol) { return Mathf.Abs(a.a - b.a) <= tol; } return false; } private static (byte, byte, byte, byte) Key(Color c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) return ((byte)Mathf.RoundToInt(Mathf.Clamp01(c.r) * 255f), (byte)Mathf.RoundToInt(Mathf.Clamp01(c.g) * 255f), (byte)Mathf.RoundToInt(Mathf.Clamp01(c.b) * 255f), (byte)Mathf.RoundToInt(Mathf.Clamp01(c.a) * 255f)); } private static bool SameColour(Color a, Color b) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) var (b2, b3, b4, b5) = Key(a); var (b6, b7, b8, b9) = Key(b); if (b2 == b6 && b3 == b7 && b4 == b8) { return b5 == b9; } return false; } private static bool InGroups(Transform t) { string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } foreach (string group in _rules.Groups) { if (text.IndexOf(group, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } } internal static class ReloadCoordinator { private static bool _pendingSceneDispatch; private static bool AnyFlagSet { get { if (!SpriteFileWatcher.ReloadSprites && !SpriteFileWatcher.ReloadT2DSprites && !AudioFileWatcher.ReloadAudio && !TextFileWatcher.ReloadText && !FontFileWatcher.ReloadFonts && !VideoHandler.ReloadVideos && !RecolourHandler.ReloadRequested) { return SpriteLoader.IsReloadingSliced; } return true; } } internal static void RequestSceneReloadAfterDrain() { _pendingSceneDispatch = true; } internal static void Poll() { SpriteFileWatcher.Tick(); FontHandler.Tick(); ReloadToast.TickSpriteFlush(); if (SpriteFileWatcher.ReloadSprites) { Plugin.Logger.LogDebug((object)"[Update] ReloadSprites flag set — triggering SpriteLoader.Reload()"); SpriteFileWatcher.ReloadSprites = false; ReloadToast.ArmSpriteFlush(); if (Plugin.Config.SlicedSpriteReload) { SpriteLoader.RequestSlicedReload(); } else { DevProfiler.StartOp(); FileCache.BeginCycle(); try { SpriteLoader.Reload(); } finally { FileCache.EndCycle(); } float num = DevProfiler.StopOp("SpriteLoader.Reload"); if (num > 250f) { Plugin.Logger.LogInfo((object)$"[ReloadTiming] SpriteLoader.Reload: {num:F0} ms (synchronous — one frozen frame)"); } Plugin.Logger.LogDebug((object)$"[Reload] SpriteLoader.Reload completed in {num:F1}ms"); } } if (SpriteFileWatcher.ReloadT2DSprites) { Plugin.Logger.LogDebug((object)"[Update] ReloadT2DSprites flag set — triggering T2DLoader.ReloadSpritesInScene()"); SpriteFileWatcher.ReloadT2DSprites = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { T2DLoader.ReloadSpritesInScene(); } finally { FileCache.EndCycle(); } float num2 = DevProfiler.StopOp("T2DLoader.ReloadSpritesInScene"); if (num2 > 250f) { Plugin.Logger.LogInfo((object)$"[ReloadTiming] T2DLoader.ReloadSpritesInScene: {num2:F0} ms"); } Plugin.Logger.LogDebug((object)$"[Reload] T2DLoader.ReloadSpritesInScene completed in {num2:F1}ms"); ReloadToast.Flush(ReloadToast.Kind.T2D, "T2D"); T2DLoader.OnSceneLoaded(); T2DTextureController.RequestRefresh(); T2DLoader.RefreshSubstitutedCursor(); Plugin.ScheduleDeferredApplyPasses(); if (!GCUtil.IsIncrementalMode && GCUtil.HeapPressure > 0.7) { DevProfiler.MarkExpectedCollect("t2d-reload"); GCUtil.ForceCollect(); } } if (AudioFileWatcher.ReloadAudio) { AudioFileWatcher.ReloadAudio = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { AudioHandler.Reload(); } finally { FileCache.EndCycle(); } DevProfiler.StopOp("AudioHandler.Reload"); ReloadToast.Flush(ReloadToast.Kind.Audio, Loc.T("Audio")); if (!GCUtil.IsIncrementalMode && GCUtil.HeapPressure > 0.7) { DevProfiler.MarkExpectedCollect("audio-reload"); GCUtil.ForceCollect(); } } if (TextFileWatcher.ReloadText) { TextFileWatcher.ReloadText = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { DialogueHandler.Reload(); } finally { FileCache.EndCycle(); } DevProfiler.StopOp("DialogueHandler.Reload"); ReloadToast.Flush(ReloadToast.Kind.Text, Loc.T("Text")); FontFileWatcher.ReloadFonts = true; } if (FontFileWatcher.ReloadFonts) { FontFileWatcher.ReloadFonts = false; DevProfiler.StartOp(); try { FontHandler.Reload(); } finally { DevProfiler.StopOp("FontHandler.Reload"); } ReloadToast.Flush(ReloadToast.Kind.Font, Loc.T("Fonts")); } if (VideoHandler.ReloadVideos) { VideoHandler.ReloadVideos = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { VideoHandler.Reload(); } finally { FileCache.EndCycle(); } DevProfiler.StopOp("VideoHandler.Reload"); } if (RecolourHandler.ReloadRequested) { RecolourHandler.ReloadRequested = false; DevProfiler.StartOp(); try { RecolourHandler.Reload(); } finally { DevProfiler.StopOp("RecolourHandler.Reload"); } DevProfiler.StartOp(); try { SlashColourHandler.Reload(); } finally { DevProfiler.StopOp("SlashColourHandler.Reload"); } } GCUtil.BumpThresholdForStitchwork(); if (_pendingSceneDispatch && !AnyFlagSet) { _pendingSceneDispatch = false; if (!GameBridge.IsInGameScene) { Plugin.Logger.LogInfo((object)"[Reload] Deferred scene reload dropped — no longer in a game scene."); } else if (GameBridge.TriggerSceneReloadCurrent()) { Plugin.Logger.LogInfo((object)"[Reload] Reloads drained — pack-apply scene reload dispatched."); } else { Plugin.Logger.LogInfo((object)"[Reload] Reloads drained but GameBridge scene reload unavailable — in-place only."); } } } } public static class SceneTraverser { private static readonly Queue sceneQueue = new Queue(); private static int scenesProcessed = 0; private static bool _isTraversing = false; private static int _scenesSinceLastGC = 0; private static bool _sceneArrived; private static int _consecutiveArrivalFailures; private const float FrameBudgetSec = 0.04f; private const float SceneArriveTimeoutSec = 90f; private const float UnloadTimeoutSec = 60f; private const int MaxConsecutiveArrivalFailures = 3; private static int GCEveryNScenes => Plugin.Config.GCEveryNScenes; public static bool IsTraversing => _isTraversing; public static int Processed => scenesProcessed; public static int Total => scenesProcessed + sceneQueue.Count; public static string CurrentScene { get; private set; } = ""; public static string SubProgress { get; private set; } = ""; public static float StartedAt { get; private set; } public static void TraverseAllScenes() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (_isTraversing) { return; } Plugin.Logger.LogInfo((object)"Starting scene traversal for full sprite dump..."); _isTraversing = true; _scenesSinceLastGC = 0; _consecutiveArrivalFailures = 0; sceneQueue.Clear(); scenesProcessed = 0; StartedAt = Time.realtimeSinceStartup; foreach (KeyValuePair item in SceneTeleportMap.GetTeleportMap()) { if ((int)item.Value.MapZone != 0) { Plugin.Logger.LogDebug((object)$"Enqueued scene: {item.Key} : {item.Value.MapZone}"); sceneQueue.Enqueue(item.Key); } } Plugin.Logger.LogInfo((object)$"Full dump: {sceneQueue.Count} scenes queued."); ((MonoBehaviour)Plugin.Instance).StartCoroutine(RunTraversal()); } public static void NotifySceneLoaded() { _sceneArrived = true; } private static IEnumerator RunTraversal() { try { while (sceneQueue.Count > 0) { string next = sceneQueue.Dequeue(); scenesProcessed++; CurrentScene = next; SubProgress = ""; _sceneArrived = false; GameManager.instance.LoadScene(next); float deadline = Time.unscaledTime + 90f; while (!_sceneArrived && Time.unscaledTime < deadline) { yield return null; } if (!_sceneArrived) { Plugin.Logger.LogWarning((object)$"[FullDump] '{next}' did not fire sceneLoaded within {90f:F0} s — skipped."); _consecutiveArrivalFailures++; if (_consecutiveArrivalFailures >= 3) { Plugin.Logger.LogError((object)($"[FullDump] {3} consecutive scenes failed to arrive — the " + "engine's scene-load pipeline is wedged (usually a game-side scene-load exception on the LAST SUCCESSFUL scene; check the log above it). Aborting the run at " + $"{scenesProcessed}/{Total}; dumps up to this point are on disk.")); break; } continue; } _consecutiveArrivalFailures = 0; GameBridge.SetHeroInvincible(on: true); tk2dSpriteCollectionData[] collections = Resources.FindObjectsOfTypeAll(); float num = Time.realtimeSinceStartup + 0.04f; for (int i = 0; i < collections.Length; i++) { tk2dSpriteCollectionData val = collections[i]; if (!((Object)(object)val == (Object)null)) { SubProgress = $"{i + 1}/{collections.Length}"; SpriteDumper.DumpCollection(val); if (Time.realtimeSinceStartup >= num) { yield return null; num = Time.realtimeSinceStartup + 0.04f; } } } SubProgress = ""; T2DDumper.DumpAllT2DSprites(); yield return null; AsyncOperation unload = Resources.UnloadUnusedAssets(); float unloadDeadline = Time.unscaledTime + 60f; while (!unload.isDone && Time.unscaledTime < unloadDeadline) { yield return null; } if (!unload.isDone) { Plugin.Logger.LogWarning((object)($"[FullDump] UnloadUnusedAssets did not complete within {60f:F0} s after " + "'" + next + "' — continuing without it (a wedged game scene-load routine blocks the unload op; if the next scenes fail to arrive, the run will abort with a summary).")); } _scenesSinceLastGC++; bool flag = GCUtil.HeapPressure > 0.75; if (_scenesSinceLastGC >= GCEveryNScenes || flag) { string text = (flag ? $"heap pressure {GCUtil.HeapPressure:P0}" : $"{_scenesSinceLastGC} scenes"); Plugin.Logger.LogDebug((object)("[GC] Running cleanup after " + text + "...")); GCUtil.ForceCollect(); _scenesSinceLastGC = 0; } Log.Verbose($"[FullDump] Progress: {scenesProcessed}/{Total} scenes"); } } finally { _isTraversing = false; CurrentScene = ""; SubProgress = ""; float num2 = (Time.realtimeSinceStartup - StartedAt) / 60f; Plugin.Logger.LogInfo((object)($"Full dump complete! Processed {scenesProcessed} scenes in {num2:F1} min. " + "Output: " + SpriteDumper.DumpPath)); Resources.UnloadUnusedAssets(); GCUtil.ForceCollect(); GameBridge.SetHeroInvincible(on: false); GameBridge.ReturnToMainMenuNoSave(); } } public static void OnDumpCompleted() { _scenesSinceLastGC++; bool flag = GCUtil.HeapPressure > 0.75; if (_scenesSinceLastGC >= GCEveryNScenes || flag) { string text = (flag ? $"heap pressure {GCUtil.HeapPressure:P0}" : $"{_scenesSinceLastGC} scenes"); Plugin.Logger.LogDebug((object)("[GC] Running cleanup after " + text + "...")); Resources.UnloadUnusedAssets(); GCUtil.ForceCollect(); _scenesSinceLastGC = 0; } } } public static class SlashColourHandler { private static Color? _fileBase; private static Color? _fileFire; private static Color? _filePoison; private static Color? _prevBase; private static Color? _prevFire; private static Color? _prevPoison; private static bool _loaded; private static readonly Dictionary _vanillaTint = new Dictionary(); private static readonly FieldRef _slashSpriteRef = AccessTools.FieldRefAccess("slashSprite"); private static readonly FieldRef _hcRef = AccessTools.FieldRefAccess("hc"); private static readonly FieldRef _tintSpritesRef = AccessTools.FieldRefAccess("tintSprites"); private static readonly FieldRef _tintTkRef = AccessTools.FieldRefAccess("tintTk2dSprites"); private static readonly FieldRef _nailConfigsRef = AccessTools.FieldRefAccess("nailConfigs"); public static string RootFile => Path.Combine(Plugin.BasePath, "Recolour", "_slash.txt"); public static Color? EffectiveBase { get { EnsureLoaded(); return _prevBase ?? _fileBase; } } public static Color? EffectiveFire { get { EnsureLoaded(); return _prevFire ?? _fileFire; } } public static Color? EffectivePoison { get { EnsureLoaded(); return _prevPoison ?? _filePoison; } } public static void ApplyPatches(Harmony harmony) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(NailAttackBase), "OnSlashStarting", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(SlashColourHandler), "OnSlashStartingPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(HeroExtraNailSlash), "OnEnable", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(SlashColourHandler), "ExtraSlashOnEnablePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void OnSlashStartingPostfix(NailAttackBase __instance) { //IL_0011: 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_006a: Unknown result type (might be due to invalid IL or missing references) Color? effectiveBase = EffectiveBase; if (!effectiveBase.HasValue) { return; } Color valueOrDefault = effectiveBase.GetValueOrDefault(); HeroController val = _hcRef.Invoke(__instance); if (!((Object)(object)val == (Object)null) && !((Object)(object)val.NailImbuement == (Object)null) && !((Object)(object)val.NailImbuement.CurrentImbuement != (Object)null)) { tk2dSprite val2 = _slashSpriteRef.Invoke(__instance); if ((Object)(object)val2 != (Object)null) { ((tk2dBaseSprite)val2).color = valueOrDefault; } } } private static void ExtraSlashOnEnablePostfix(HeroExtraNailSlash __instance) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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_00b7: Unknown result type (might be due to invalid IL or missing references) Color? effectiveBase = EffectiveBase; if (!effectiveBase.HasValue) { return; } Color valueOrDefault = effectiveBase.GetValueOrDefault(); HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.NailImbuement == (Object)null || (Object)(object)instance.NailImbuement.CurrentImbuement != (Object)null) { return; } SpriteRenderer[] array = _tintSpritesRef.Invoke(__instance); if (array != null) { SpriteRenderer[] array2 = array; foreach (SpriteRenderer val in array2) { if ((Object)(object)val != (Object)null) { val.color = valueOrDefault; } } } tk2dSprite[] array3 = _tintTkRef.Invoke(__instance); if (array3 == null) { return; } tk2dSprite[] array4 = array3; foreach (tk2dSprite val2 in array4) { if ((Object)(object)val2 != (Object)null) { ((tk2dBaseSprite)val2).color = valueOrDefault; } } } public static void Invalidate() { _loaded = false; } private static void EnsureLoaded() { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) if (_loaded) { return; } _loaded = true; ConflictTracker.ClearTypes("recolour-slash"); Dictionary merged = new Dictionary(); foreach (string activePackPath in PackManager.ActivePackPaths) { LoadFile(Path.Combine(activePackPath, "Recolour", "_slash.txt"), activePackPath, rootWins: false); } LoadFile(RootFile, null, rootWins: true); _fileBase = (merged.TryGetValue("base", out (Color, string) value) ? new Color?(value.Item1) : ((Color?)null)); _fileFire = (merged.TryGetValue("fire", out (Color, string) value2) ? new Color?(value2.Item1) : ((Color?)null)); _filePoison = (merged.TryGetValue("poison", out (Color, string) value3) ? new Color?(value3.Item1) : ((Color?)null)); if (merged.Count > 0) { Plugin.Logger.LogInfo((object)$"[Recolour] Slash: {merged.Count} colour rule(s) loaded."); } void LoadFile(string file, string source, bool rootWins) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: 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) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (File.Exists(file)) { string[] array = File.ReadAllLines(IOUtil.LongSafe(file)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); int num = text.IndexOf(':'); if (num > 0) { string text2 = text.Substring(0, num).Trim().ToLowerInvariant(); if ((!(text2 != "base") || !(text2 != "fire") || !(text2 != "poison")) && RecolourHandler.TryParseHex(text.Substring(num + 1).Trim(), out var c)) { if (!merged.TryGetValue(text2, out (Color, string) value4)) { merged[text2] = (c, source); } else if (rootWins) { if (value4.Item1 != c) { ConflictTracker.Record("recolour-slash", "_slash/" + text2, null, value4.Item2); } merged[text2] = (c, source); } else if (value4.Item1 != c) { ConflictTracker.Record("recolour-slash", "_slash/" + text2, value4.Item2, source); } } } } } } } public static void ApplyToConfigs() { HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.NailImbuement == (Object)null)) { NailImbuementConfig[] array = _nailConfigsRef.Invoke(instance.NailImbuement); if (array != null) { ApplyOne(array, (NailElements)1, EffectiveFire); ApplyOne(array, (NailElements)2, EffectivePoison); } } } private static void ApplyOne(NailImbuementConfig[] configs, NailElements el, Color? c) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Expected I4, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) int num = (int)el; if (num < 0 || num >= configs.Length) { return; } NailImbuementConfig val = configs[num]; if ((Object)(object)val == (Object)null) { return; } Color value; if (c.HasValue) { Color valueOrDefault = c.GetValueOrDefault(); if (!_vanillaTint.ContainsKey(el)) { _vanillaTint[el] = val.NailTintColor; } val.NailTintColor = valueOrDefault; } else if (_vanillaTint.TryGetValue(el, out value)) { val.NailTintColor = value; _vanillaTint.Remove(el); } } public static void Reload() { Invalidate(); ApplyToConfigs(); } public static Color VanillaFor(string key) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_0034: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected I4, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (key == "base") { return Color.white; } NailElements val = (NailElements)((key == "fire") ? 1 : 2); if (_vanillaTint.TryGetValue(val, out var value)) { return value; } HeroController instance = HeroController.instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.NailImbuement != (Object)null) { NailImbuementConfig[] array = _nailConfigsRef.Invoke(instance.NailImbuement); int num = (int)val; if (array != null && num < array.Length && (Object)(object)array[num] != (Object)null) { return array[num].NailTintColor; } } return Color.white; } public static void SetPreview(string key, Color c) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) switch (key) { case "base": _prevBase = c; break; case "fire": _prevFire = c; break; case "poison": _prevPoison = c; break; } ApplyToConfigs(); } public static void ClearPreview() { _prevBase = (_prevFire = (_prevPoison = null)); ApplyToConfigs(); } public static bool TryGetRootRule(string key, out Color c) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) c = default(Color); if (!File.Exists(RootFile)) { return false; } string[] array = File.ReadAllLines(IOUtil.LongSafe(RootFile)); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); int num = text.IndexOf(':'); if (num > 0 && string.Equals(text.Substring(0, num).Trim(), key, StringComparison.OrdinalIgnoreCase)) { return RecolourHandler.TryParseHex(text.Substring(num + 1).Trim(), out c); } } return false; } public static void WriteRule(string key, Color c) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) List list = (File.Exists(RootFile) ? new List(File.ReadAllLines(IOUtil.LongSafe(RootFile))) : new List()); string text = key + ": " + RecolourHandler.ToHex(c); int num = list.FindIndex((string l) => l.TrimStart().StartsWith(key + ":", StringComparison.OrdinalIgnoreCase)); if (num >= 0) { list[num] = text; } else { list.Add(text); } IOUtil.EnsureDirectoryExists(Path.GetDirectoryName(RootFile)); File.WriteAllLines(IOUtil.LongSafe(RootFile), list); Reload(); } public static void RemoveRule(string key) { if (File.Exists(RootFile)) { List list = new List(File.ReadAllLines(IOUtil.LongSafe(RootFile))); if (list.RemoveAll((string l) => l.TrimStart().StartsWith(key + ":", StringComparison.OrdinalIgnoreCase)) != 0) { File.WriteAllLines(IOUtil.LongSafe(RootFile), list); Reload(); } } } } public static class SpriteDumper { private static bool _rotateFallbackWarned; public static string DumpPath => Path.Combine(Plugin.BasePath, "Dumps"); public static string ConvertPath => Path.Combine(Plugin.BasePath, "Converted"); public static void DumpCollection(tk2dSpriteCollectionData collection, bool convert = false) { //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Expected O, but got Unknown //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) if (((Object)collection).name != null && ((Object)collection).name.StartsWith("Stitchwork_Shadow", StringComparison.Ordinal)) { return; } string text = (convert ? ConvertPath : DumpPath); Material[] materials = collection.materials; foreach (Material val in materials) { if ((Object)(object)val == (Object)null || (Object)(object)val.mainTexture == (Object)null || val.mainTexture.width == 0 || val.mainTexture.height == 0) { continue; } string text2 = ((Object)val).name.Split(' ')[0]; int matIndex = Array.IndexOf(collection.materials, val); tk2dSpriteDefinition[] array = ((matIndex < 0) ? Array.Empty() : collection.spriteDefinitions.Where((tk2dSpriteDefinition def) => def.materialId == matIndex).ToArray()); if (array.Length == 0) { continue; } Texture atlas; Vector2[] uvs; Vector3[] positions; bool flag = SpriteLoader.TryGetVanilla(collection, array[0], out atlas, out uvs, out positions); Texture val2 = (flag ? atlas : val.mainTexture); RenderTexture val3 = null; if (!(val2 is RenderTexture)) { val3 = TexUtil.GetReadable(val2); val2 = (Texture)(object)val3; } RenderTexture active = RenderTexture.active; bool flag2 = false; try { RenderTexture.active = (RenderTexture)(object)((val2 is RenderTexture) ? val2 : null); GL.PushMatrix(); flag2 = true; GL.LoadPixelMatrix(0f, (float)val2.width, (float)val2.height, 0f); tk2dSpriteDefinition[] array2 = array; foreach (tk2dSpriteDefinition val4 in array2) { if (string.IsNullOrEmpty(val4.name) || File.Exists(IOUtil.LongSafe(Path.Combine(text, ((Object)collection).name, text2, val4.name + ".png")))) { continue; } Vector2[] uvs2 = val4.uvs; if (flag) { SpriteLoader.TryGetVanilla(collection, val4, out var _, out uvs2, out positions); } Rect spriteRect = SpriteUtil.GetSpriteRect(uvs2, val2); if (((Rect)(ref spriteRect)).width == 0f || ((Rect)(ref spriteRect)).height == 0f) { continue; } Texture2D val5 = new Texture2D((int)((Rect)(ref spriteRect)).width, (int)((Rect)(ref spriteRect)).height, (TextureFormat)4, false); try { val5.ReadPixels(spriteRect, 0, 0); val5.Apply(); if ((int)val4.flipped == 0) { byte[] bytes = ImageConversion.EncodeToPNG(val5); IOUtil.EnsureDirectoryExists(Path.Combine(text, ((Object)collection).name, text2)); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(text, ((Object)collection).name, text2, val4.name + ".png")), bytes); } else { DumpRotatedSprite(val4, val5, spriteRect, text, ((Object)collection).name, text2); } } finally { Object.Destroy((Object)(object)val5); } } } finally { if (flag2) { GL.PopMatrix(); } RenderTexture.active = active; if ((Object)(object)val3 != (Object)null) { RenderTexture.ReleaseTemporary(val3); } } } } public static (int sheets, int written, int identical) ConvertCollectionSheets(tk2dSpriteCollectionData collection, bool onlyChanged) { //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02d6: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Expected O, but got Unknown //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Expected O, but got Unknown //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; int num3 = 0; if ((Object)(object)collection == (Object)null || collection.materials == null || collection.spriteDefinitions == null) { return (sheets: 0, written: 0, identical: 0); } Material[] materials = collection.materials; foreach (Material val in materials) { if ((Object)(object)val == (Object)null || (Object)(object)val.mainTexture == (Object)null) { continue; } string text = ((Object)val).name.Split(' ')[0]; int matIndex = Array.IndexOf(collection.materials, val); if (matIndex < 0) { continue; } tk2dSpriteDefinition[] array = collection.spriteDefinitions.Where((tk2dSpriteDefinition d) => d.materialId == matIndex).ToArray(); if (array.Length == 0) { continue; } Texture2D val2 = SpriteLoader.FindSpritesheetTex(collection, ((Object)val).GetInstanceID(), text); if ((Object)(object)val2 == (Object)null) { continue; } num++; Texture atlas; Vector2[] uvs; Vector3[] positions; bool flag = SpriteLoader.TryGetVanilla(collection, array[0], out atlas, out uvs, out positions); RenderTexture val3 = (RenderTexture)(object)((atlas is RenderTexture) ? atlas : null); RenderTexture temporary = RenderTexture.GetTemporary(((Texture)val2).width, ((Texture)val2).height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)val2, temporary); RenderTexture active = RenderTexture.active; bool flag2 = false; try { Dictionary dictionary = null; Texture atlas2; tk2dSpriteDefinition[] array2; if (onlyChanged && flag && (Object)(object)val3 != (Object)null && ((Texture)val3).width == ((Texture)val2).width && ((Texture)val3).height == ((Texture)val2).height) { dictionary = new Dictionary(); RenderTexture.active = val3; GL.PushMatrix(); flag2 = true; GL.LoadPixelMatrix(0f, (float)((Texture)val3).width, (float)((Texture)val3).height, 0f); array2 = array; foreach (tk2dSpriteDefinition val4 in array2) { if (string.IsNullOrEmpty(val4.name)) { continue; } Vector2[] uvs2 = val4.uvs; SpriteLoader.TryGetVanilla(collection, val4, out atlas2, out uvs2, out positions); Rect spriteRect = SpriteUtil.GetSpriteRect(uvs2, (Texture)(object)val3); if (((Rect)(ref spriteRect)).width != 0f && ((Rect)(ref spriteRect)).height != 0f) { Texture2D val5 = new Texture2D((int)((Rect)(ref spriteRect)).width, (int)((Rect)(ref spriteRect)).height, (TextureFormat)4, false); try { val5.ReadPixels(spriteRect, 0, 0); val5.Apply(); dictionary[val4.name] = val5.GetRawTextureData(); } finally { Object.Destroy((Object)(object)val5); } } } GL.PopMatrix(); flag2 = false; } RenderTexture.active = temporary; GL.PushMatrix(); flag2 = true; GL.LoadPixelMatrix(0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height, 0f); array2 = array; foreach (tk2dSpriteDefinition val6 in array2) { if (string.IsNullOrEmpty(val6.name)) { continue; } Vector2[] uvs3 = val6.uvs; if (flag) { SpriteLoader.TryGetVanilla(collection, val6, out atlas2, out uvs3, out positions); } Rect spriteRect2 = SpriteUtil.GetSpriteRect(uvs3, (Texture)(object)temporary); if (((Rect)(ref spriteRect2)).width == 0f || ((Rect)(ref spriteRect2)).height == 0f) { continue; } Texture2D val7 = new Texture2D((int)((Rect)(ref spriteRect2)).width, (int)((Rect)(ref spriteRect2)).height, (TextureFormat)4, false); try { val7.ReadPixels(spriteRect2, 0, 0); val7.Apply(); if (dictionary != null && dictionary.TryGetValue(val6.name, out var value) && BytesEqual(value, val7.GetRawTextureData())) { num3++; continue; } string text2 = Path.Combine(ConvertPath, ((Object)collection).name, text); IOUtil.EnsureDirectoryExists(text2); if ((int)val6.flipped == 0) { File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(text2, val6.name + ".png")), ImageConversion.EncodeToPNG(val7)); } else { DumpRotatedSprite(val6, val7, spriteRect2, ConvertPath, ((Object)collection).name, text); } num2++; } finally { Object.Destroy((Object)(object)val7); } } } finally { if (flag2) { GL.PopMatrix(); } RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); Object.Destroy((Object)(object)val2); } } return (sheets: num, written: num2, identical: num3); } private static bool BytesEqual(byte[] a, byte[] b) { if (a != null && b != null) { return a.AsSpan().SequenceEqual(b); } return false; } public static Texture2D RotateToScreen(Texture2D src, FlipMode flip) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) (Vector2 uBasis, Vector2 vBasis) tuple = SpriteUtil.DumpBasisFor(flip); Vector2 item = tuple.uBasis; Vector2 item2 = tuple.vBasis; bool num = (int)flip > 0; int num2 = (num ? ((Texture)src).height : ((Texture)src).width); int num3 = (num ? ((Texture)src).width : ((Texture)src).height); RenderTexture temporary = RenderTexture.GetTemporary(num2, num3, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); RenderTexture active = RenderTexture.active; Texture2D val = new Texture2D(num2, num3, (TextureFormat)4, false); bool flag = false; try { RenderTexture.active = temporary; GL.PushMatrix(); flag = true; GL.LoadPixelMatrix(0f, (float)((Texture)temporary).height, (float)((Texture)temporary).width, 0f); TexUtil.DrawRotated(new Rect(0f, 0f, (float)((Texture)src).width, (float)((Texture)src).height), (Texture)(object)src, item, item2); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height), 0, 0); val.Apply(); return val; } finally { if (flag) { GL.PopMatrix(); } RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static void DumpRotatedSprite(tk2dSpriteDefinition def, Texture2D spriteTex, Rect spriteRect, string baseDir, string collectionName, string matname) { //IL_00ab: 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_00bb: 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) //IL_00c2: 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) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (!TexUtil.RotateDrawAvailable) { if (!_rotateFallbackWarned) { _rotateFallbackWarned = true; Plugin.Logger.LogWarning((object)"[SpriteDumper] Rotate shader unavailable — rotated frames dump in atlas orientation (sideways)."); } IOUtil.EnsureDirectoryExists(Path.Combine(baseDir, collectionName, matname)); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(baseDir, collectionName, matname, def.name + ".png")), ImageConversion.EncodeToPNG(spriteTex)); return; } RenderTexture temporary = RenderTexture.GetTemporary((int)((Rect)(ref spriteRect)).height, (int)((Rect)(ref spriteRect)).width, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Texture2D val = null; RenderTexture active = RenderTexture.active; bool flag = false; try { RenderTexture.active = temporary; GL.PushMatrix(); flag = true; GL.LoadPixelMatrix(0f, (float)((Texture)temporary).height, (float)((Texture)temporary).width, 0f); var (uB, vB) = SpriteUtil.DumpBasisFor(def.flipped); TexUtil.DrawRotated(new Rect(0f, 0f, (float)((Texture)spriteTex).width, (float)((Texture)spriteTex).height), (Texture)(object)spriteTex, uB, vB); val = new Texture2D((int)((Rect)(ref spriteRect)).height, (int)((Rect)(ref spriteRect)).width, (TextureFormat)4, false); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height), 0, 0); val.Apply(); byte[] bytes = ImageConversion.EncodeToPNG(val); IOUtil.EnsureDirectoryExists(Path.Combine(baseDir, collectionName, matname)); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(baseDir, collectionName, matname, def.name + ".png")), bytes); } finally { if (flag) { GL.PopMatrix(); } RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } public static void DumpSingleSprite(tk2dSpriteDefinition def, tk2dSpriteCollectionData collection) { //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Expected O, but got Unknown //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) if (def == null || string.IsNullOrEmpty(def.name) || (Object)(object)collection == (Object)null || collection.materials == null || def.materialId < 0 || def.materialId >= collection.materials.Length) { return; } Material val = collection.materials[def.materialId]; if ((Object)(object)val == (Object)null || (Object)(object)val.mainTexture == (Object)null || val.mainTexture.width == 0 || val.mainTexture.height == 0) { return; } string text = ((Object)val).name.Split(' ')[0]; if (File.Exists(IOUtil.LongSafe(Path.Combine(DumpPath, ((Object)collection).name, text, def.name + ".png")))) { return; } Texture atlas; Vector2[] uvs; Vector3[] positions; Texture val2 = (SpriteLoader.TryGetVanilla(collection, def, out atlas, out uvs, out positions) ? atlas : val.mainTexture); RenderTexture val3 = null; if (!(val2 is RenderTexture)) { val3 = TexUtil.GetReadable(val2); val2 = (Texture)(object)val3; } RenderTexture active = RenderTexture.active; bool flag = false; try { RenderTexture.active = (RenderTexture)(object)((val2 is RenderTexture) ? val2 : null); GL.PushMatrix(); flag = true; GL.LoadPixelMatrix(0f, (float)val2.width, (float)val2.height, 0f); Rect spriteRect = SpriteUtil.GetSpriteRect(uvs, val2); if (((Rect)(ref spriteRect)).width == 0f || ((Rect)(ref spriteRect)).height == 0f) { return; } Texture2D val4 = new Texture2D((int)((Rect)(ref spriteRect)).width, (int)((Rect)(ref spriteRect)).height, (TextureFormat)4, false); try { val4.ReadPixels(spriteRect, 0, 0); val4.Apply(); if ((int)def.flipped == 0) { byte[] bytes = ImageConversion.EncodeToPNG(val4); IOUtil.EnsureDirectoryExists(Path.Combine(DumpPath, ((Object)collection).name, text)); File.WriteAllBytes(IOUtil.LongSafe(Path.Combine(DumpPath, ((Object)collection).name, text, def.name + ".png")), bytes); } else { DumpRotatedSprite(def, val4, spriteRect, DumpPath, ((Object)collection).name, text); } } finally { Object.Destroy((Object)(object)val4); } } finally { if (flag) { GL.PopMatrix(); } RenderTexture.active = active; if ((Object)(object)val3 != (Object)null) { RenderTexture.ReleaseTemporary(val3); } } } } [HarmonyPatch] public static class SpriteLoader { private sealed class AtlasExpansionPlan { public int VanillaWidth; public int VanillaHeight; public int Width; public int Height; public readonly Dictionary AllocatedRects = new Dictionary(); public readonly Dictionary VanillaRects = new Dictionary(); public readonly Dictionary Anchors = new Dictionary(); public readonly Dictionary RepDimensions = new Dictionary(); } internal enum ReplacementSource { None, WorkingFolder, Pack } private static readonly Dictionary> LoadedAtlases = new Dictionary>(); private static readonly Dictionary> LoadedAtlasesTextures = new Dictionary>(); private static readonly Dictionary _boundMaterials = new Dictionary(); private static readonly HashSet _rebindWarned = new HashSet(); private static float _nextBindingCheck; private static int _rebindHeals; private static readonly Dictionary>> LoadedSprites = new Dictionary>>(); private static readonly Dictionary _originalTextures = new Dictionary(); private static readonly Dictionary _instanceKeyToName = new Dictionary(); private static readonly Dictionary _nameInstanceCount = new Dictionary(StringComparer.Ordinal); private static readonly HashSet _duplicateNameWarned = new HashSet(); private static readonly Dictionary _vanillaTexNames = new Dictionary(); private static readonly Dictionary _vanillaCaptureSourceId = new Dictionary(); private static readonly Dictionary<(string collName, int matIndex), RenderTexture> _originalTexturesByColIndex = new Dictionary<(string, int), RenderTexture>(); private static readonly Dictionary<(string collName, int matIndex), string> _vanillaTexNamesByColIndex = new Dictionary<(string, int), string>(); private static readonly Dictionary<(int matId, string name), Vector2[]> _originalUVs = new Dictionary<(int, string), Vector2[]>(); private static readonly Dictionary<(int matId, string name), Vector3[]> _originalPositions = new Dictionary<(int, string), Vector3[]>(); private static readonly HashSet _collectionsExpandedThisReload = new HashSet(); private static bool? _copyTextureToRTSupportedCache; private static readonly ConditionalWeakTable _instanceKeyCache = new ConditionalWeakTable(); private static readonly Dictionary _spriteFileIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _sheetFileIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _spriteAnchors = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _anchorsTxtWarned; private const int NativeDrawGraceTexels = 5; private static readonly HashSet _collectionsWithFiles = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool _fileIndexBuilt; private static int _loadedSpriteCount; private static int _spriteDefsWalked; private static int _rotatedReplacements; private static readonly HashSet _collectionsRevertedThisReload = new HashSet(); private static readonly Dictionary _perCollectionReplacements = new Dictionary(); private static int _reloadGen; private static bool _rerunRequested; private const long SliceBudgetMs = 12L; public static string LoadPath => Path.Combine(Plugin.BasePath, "Sprites"); public static string AtlasLoadPath => Path.Combine(Plugin.BasePath, "Spritesheets"); private static bool _copyTextureToRTSupported { get { //IL_002c: 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_0034: Invalid comparison between Unknown and I4 if (Plugin.Config != null && Plugin.Config.DisableCopyTextureFastPath) { return false; } if (_copyTextureToRTSupportedCache.HasValue) { return _copyTextureToRTSupportedCache.Value; } bool flag = (SystemInfo.copyTextureSupport & 8) > 0; _copyTextureToRTSupportedCache = flag; Plugin.Logger.LogInfo((object)($"[SpriteLoader] CopyTextureSupport.TextureToRT = {flag}" + (flag ? "" : " — falling back to Graphics.DrawTexture for atlas blits"))); return flag; } } public static int LoadedCollectionCount => LoadedAtlases.Count; public static int LoadedSpriteCount => _loadedSpriteCount; public static long AtlasVramBytes { get { long num = 0L; foreach (Dictionary value in LoadedAtlasesTextures.Values) { foreach (RenderTexture value2 in value.Values) { if ((Object)(object)value2 != (Object)null) { num += (long)((Texture)value2).width * (long)((Texture)value2).height * 4; } } } foreach (RenderTexture value3 in _originalTextures.Values) { if ((Object)(object)value3 != (Object)null) { num += (long)((Texture)value3).width * (long)((Texture)value3).height * 4; } } return num; } } public static int AtlasRTCount { get { int num = _originalTextures.Count; foreach (Dictionary value in LoadedAtlasesTextures.Values) { num += value.Count; } return num; } } internal static int FileIndexVersion { get; private set; } public static bool IsReloadingSliced { get; private set; } public static int SlicedTotal { get; private set; } public static int SlicedProcessed { get; private set; } public static string SlicedCurrent { get; private set; } = ""; public static float SlicedStartedAt { get; private set; } private static void RegisterBinding(Material mat, RenderTexture rt) { _boundMaterials[((Object)mat).GetInstanceID()] = (mat, rt); } private static void UnregisterBinding(Material mat) { _boundMaterials.Remove(((Object)mat).GetInstanceID()); } internal static void TickBindingWatchdog() { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextBindingCheck) { return; } _nextBindingCheck = realtimeSinceStartup + 1f; if (SceneTraverser.IsTraversing) { return; } List list = null; foreach (KeyValuePair boundMaterial in _boundMaterials) { var (val, val2) = boundMaterial.Value; if ((Object)(object)val == (Object)null) { (list ?? (list = new List())).Add(boundMaterial.Key); continue; } Texture mainTexture = val.mainTexture; if ((object)mainTexture != val2) { string text = (((Object)(object)mainTexture == (Object)null) ? "(null)" : $"'{((Object)mainTexture).name}' ({((object)mainTexture).GetType().Name} {mainTexture.width}x{mainTexture.height})"); _rebindHeals++; string text2 = "[SpriteLoader] Atlas binding hijacked on material '" + ((Object)val).name + "': now " + text + " " + $"— re-pointing to the composite RT (heal #{_rebindHeals}). An external mod rebound this material."; if (_rebindWarned.Add(boundMaterial.Key)) { Plugin.Logger.LogWarning((object)text2); } else { Plugin.Logger.LogDebug((object)text2); } val.mainTexture = (Texture)(object)val2; } } if (list == null) { return; } foreach (int item in list) { _boundMaterials.Remove(item); } } private static string InstanceKey(tk2dSpriteCollectionData coll) { if (_instanceKeyCache.TryGetValue(coll, out var value)) { return value; } string text = ((Object)coll).name + "\0" + ((Object)coll).GetInstanceID(); _instanceKeyCache.Add(coll, text); return text; } public static bool TryGetVanilla(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def, out Texture atlas, out Vector2[] uvs, out Vector3[] positions) { atlas = null; uvs = def.uvs; positions = def.positions; if ((Object)(object)coll == (Object)null || coll.materials == null || def.materialId < 0 || def.materialId >= coll.materials.Length) { return false; } Material val = coll.materials[def.materialId]; if ((Object)(object)val == (Object)null) { return false; } int instanceID = ((Object)val).GetInstanceID(); if (!_originalTextures.TryGetValue(instanceID, out var value) || !((Object)(object)value != (Object)null) || !value.IsCreated()) { _originalTexturesByColIndex.TryGetValue((((Object)coll).name, def.materialId), out value); } if ((Object)(object)value == (Object)null || !value.IsCreated()) { return false; } atlas = (Texture)(object)value; (int, string) key = (instanceID, def.name); if (_originalUVs.TryGetValue(key, out var value2) && value2 != null) { uvs = value2; } if (_originalPositions.TryGetValue(key, out var value3) && value3 != null) { positions = value3; } return true; } public static void ApplyPatches(Harmony harmony) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(tk2dSpriteCollectionData), "Init", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(SpriteLoader), "InitPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void InitPostfix(tk2dSpriteCollectionData __instance) { LoadCollection(__instance); } public static void LoadCollection(tk2dSpriteCollectionData collection) { //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Expected O, but got Unknown //IL_08fa: Unknown result type (might be due to invalid IL or missing references) //IL_0916: Unknown result type (might be due to invalid IL or missing references) //IL_091b: Unknown result type (might be due to invalid IL or missing references) //IL_0943: Unknown result type (might be due to invalid IL or missing references) //IL_094e: Unknown result type (might be due to invalid IL or missing references) //IL_0953: Unknown result type (might be due to invalid IL or missing references) //IL_0955: Unknown result type (might be due to invalid IL or missing references) //IL_095a: Unknown result type (might be due to invalid IL or missing references) //IL_095c: Unknown result type (might be due to invalid IL or missing references) //IL_095e: Unknown result type (might be due to invalid IL or missing references) //IL_0962: Unknown result type (might be due to invalid IL or missing references) //IL_09d4: Unknown result type (might be due to invalid IL or missing references) //IL_09d8: Unknown result type (might be due to invalid IL or missing references) //IL_09da: Unknown result type (might be due to invalid IL or missing references) //IL_061e: Unknown result type (might be due to invalid IL or missing references) //IL_0623: Unknown result type (might be due to invalid IL or missing references) //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_0630: Unknown result type (might be due to invalid IL or missing references) //IL_063a: Expected O, but got Unknown //IL_0693: Unknown result type (might be due to invalid IL or missing references) //IL_071f: Unknown result type (might be due to invalid IL or missing references) if (ArchitectBridge.ShouldSuppressLoadCollection()) { return; } if (!_fileIndexBuilt) { RebuildFileIndex(); } string key = InstanceKey(collection); bool num = !_instanceKeyToName.ContainsKey(key); _instanceKeyToName[key] = ((Object)collection).name; if (num) { _nameInstanceCount[((Object)collection).name] = _nameInstanceCount.GetValueOrDefault(((Object)collection).name) + 1; } bool flag = _nameInstanceCount.GetValueOrDefault(((Object)collection).name) > 1 && _collectionsWithFiles.Contains(((Object)collection).name) && _duplicateNameWarned.Add(((Object)collection).name); if (flag) { Plugin.Logger.LogWarning((object)("[SpriteLoader] Duplicate collection name '" + ((Object)collection).name + "' " + $"(instanceId {((Object)collection).GetInstanceID()}). " + "Use the vanilla texture name as the folder/file name to target this instance specifically: Sprites/" + ((Object)collection).name + "/{texName}/... or Spritesheets/" + ((Object)collection).name + "/{texName}.png (texture names logged per material below when vanilla is first captured).")); } bool flag2 = false; Material[] materials = collection.materials; foreach (Material val in materials) { if ((Object)(object)val == (Object)null) { continue; } string name = ((Object)val).name; string text = ((Object)val).name.Split(' ')[0]; int instanceID = ((Object)val).GetInstanceID(); bool num2 = !_collectionsWithFiles.Contains(((Object)collection).name); int num3 = Array.IndexOf(collection.materials, val); if (!num2 && !(val.mainTexture is RenderTexture) && (Object)(object)val.mainTexture != (Object)null) { int instanceID2 = ((Object)val.mainTexture).GetInstanceID(); if (!_vanillaCaptureSourceId.TryGetValue(instanceID, out var value) || value != instanceID2 || !_originalTextures.ContainsKey(instanceID)) { if (_originalTextures.TryGetValue(instanceID, out var value2) && (Object)(object)value2 != (Object)null && ((Texture)value2).width == val.mainTexture.width && ((Texture)value2).height == val.mainTexture.height) { Graphics.Blit(val.mainTexture, value2); } else { if ((Object)(object)value2 != (Object)null) { value2.Release(); Object.Destroy((Object)(object)value2); } RenderTexture val2 = new RenderTexture(val.mainTexture.width, val.mainTexture.height, 0, (RenderTextureFormat)0, TexUtil.ReadWriteForSource(val.mainTexture)) { hideFlags = (HideFlags)32, name = name + "_vanilla" }; Graphics.Blit(val.mainTexture, val2); _originalTextures[instanceID] = val2; } string name2 = ((Object)val.mainTexture).name; _vanillaTexNames[instanceID] = name2; if (num3 >= 0) { _originalTexturesByColIndex[(((Object)collection).name, num3)] = _originalTextures[instanceID]; _vanillaTexNamesByColIndex[(((Object)collection).name, num3)] = name2; } if (flag) { Plugin.Logger.LogWarning((object)("[SpriteLoader] mat '" + text + "' → vanilla texture '" + name2 + "'")); } _vanillaCaptureSourceId[instanceID] = instanceID2; } } if (!_originalTextures.TryGetValue(instanceID, out var value3) || (Object)(object)value3 == (Object)null) { (string, int) key2 = (((Object)collection).name, num3); if (num3 < 0 || !_originalTexturesByColIndex.TryGetValue(key2, out value3) || !((Object)(object)value3 != (Object)null) || !value3.IsCreated()) { continue; } _originalTextures[instanceID] = value3; if (_vanillaTexNamesByColIndex.TryGetValue(key2, out var value4)) { _vanillaTexNames[instanceID] = value4; } } else { (string, int) key3 = (((Object)collection).name, num3); if (num3 >= 0 && _originalTexturesByColIndex.TryGetValue(key3, out var value5) && (Object)(object)value5 != (Object)null && (Object)(object)value5 != (Object)(object)value3 && value5.IsCreated()) { _originalTextures[instanceID] = value5; value3 = value5; if (_vanillaTexNamesByColIndex.TryGetValue(key3, out var value6)) { _vanillaTexNames[instanceID] = value6; } } } if (!_collectionsWithFiles.Contains(((Object)collection).name)) { if (RestoreVanillaDefsForMat(collection, instanceID, num3)) { _collectionsRevertedThisReload.Add(collection); } if (LoadedAtlasesTextures.TryGetValue(key, out var value7) && value7.TryGetValue(name, out var value8) && (Object)(object)value8 != (Object)null) { if (((Texture)value8).width != ((Texture)value3).width || ((Texture)value8).height != ((Texture)value3).height) { value8.Release(); ((Texture)value8).width = ((Texture)value3).width; ((Texture)value8).height = ((Texture)value3).height; value8.Create(); } Graphics.Blit((Texture)(object)value3, value8); val.mainTexture = (Texture)(object)value8; RegisterBinding(val, value8); } else { val.mainTexture = (Texture)(object)value3; UnregisterBinding(val); } continue; } if (!LoadedAtlases.TryGetValue(key, out var value9)) { value9 = (LoadedAtlases[key] = new HashSet()); } Dictionary value12; RenderTexture value13; if (value9.Add(name)) { if (RestoreVanillaDefsForMat(collection, instanceID, num3)) { _collectionsRevertedThisReload.Add(collection); } AtlasExpansionPlan atlasExpansionPlan = ((num3 >= 0) ? PlanExpansion(collection, num3, instanceID, text, value3) : null); Texture2D val3 = FindSpritesheetTex(collection, instanceID, text); Texture val4 = (Texture)(((Object)(object)val3 != (Object)null) ? ((object)val3) : ((object)value3)); if ((Object)(object)val3 != (Object)null) { flag2 = true; } int num4 = atlasExpansionPlan?.Width ?? val4.width; int num5 = atlasExpansionPlan?.Height ?? val4.height; if (!LoadedAtlasesTextures.TryGetValue(key, out var value10)) { value10 = (LoadedAtlasesTextures[key] = new Dictionary()); } if (!value10.TryGetValue(name, out var value11) || (Object)(object)value11 == (Object)null) { RenderTexture val5 = new RenderTexture(num4, num5, 0, (RenderTextureFormat)0, TexUtil.ReadWriteForSource(val4)) { hideFlags = (HideFlags)32, name = name }; value11 = (value10[name] = val5); } else if (((Texture)value11).width != num4 || ((Texture)value11).height != num5) { value11.Release(); ((Texture)value11).width = num4; ((Texture)value11).height = num5; value11.Create(); } if (atlasExpansionPlan != null) { RenderTexture active = RenderTexture.active; RenderTexture.active = value11; GL.Clear(false, true, Color.clear); RenderTexture.active = active; if (_copyTextureToRTSupported) { Graphics.CopyTexture(val4, 0, 0, 0, 0, val4.width, val4.height, (Texture)(object)value11, 0, 0, 0, 0); } else { RenderTexture active2 = RenderTexture.active; RenderTexture.active = value11; GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)((Texture)value11).width, (float)((Texture)value11).height, 0f); Graphics.DrawTexture(new Rect(0f, (float)(((Texture)value11).height - val4.height), (float)val4.width, (float)val4.height), val4); GL.PopMatrix(); RenderTexture.active = active2; } } else { Graphics.Blit(val4, value11); } if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)val3); } if (atlasExpansionPlan != null) { ApplyExpansionPlan(collection, atlasExpansionPlan, instanceID, num3, ((Texture)value11).width, ((Texture)value11).height); _collectionsExpandedThisReload.Add(collection); } val.mainTexture = (Texture)(object)value11; RegisterBinding(val, value11); } else if (LoadedAtlasesTextures.TryGetValue(key, out value12) && value12.TryGetValue(name, out value13)) { val.mainTexture = (Texture)(object)value13; RegisterBinding(val, value13); } RenderTexture active3 = RenderTexture.active; Texture mainTexture = val.mainTexture; RenderTexture.active = (RenderTexture)(object)((mainTexture is RenderTexture) ? mainTexture : null); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)val.mainTexture.width, (float)val.mainTexture.height, 0f); if (num3 >= 0) { tk2dSpriteDefinition[] spriteDefinitions = collection.spriteDefinitions; foreach (tk2dSpriteDefinition val7 in spriteDefinitions) { if (val7.materialId != num3 || string.IsNullOrEmpty(val7.name)) { continue; } if (!LoadedSprites.TryGetValue(key, out var value14)) { value14 = (LoadedSprites[key] = new Dictionary>()); } if (!value14.TryGetValue(name, out var value15)) { value15 = (value14[name] = new HashSet()); } if (!value15.Add(val7.name)) { continue; } _spriteDefsWalked++; Texture2D val8 = FindSprite(((Object)collection).name, instanceID, text, val7.name); if ((Object)(object)val8 == (Object)null) { continue; } _loadedSpriteCount++; _perCollectionReplacements.TryGetValue(((Object)collection).name, out var value16); _perCollectionReplacements[((Object)collection).name] = value16 + 1; if ((int)val7.flipped != 0) { _rotatedReplacements++; } Rect spriteRect = SpriteUtil.GetSpriteRect(val7, val.mainTexture); ((Rect)(ref spriteRect)).y = (float)val.mainTexture.height - ((Rect)(ref spriteRect)).y - ((Rect)(ref spriteRect)).height; (Vector2 uBasis, Vector2 vBasis) tuple = SpriteUtil.CompositeBasisFor(val7.flipped); Vector2 item = tuple.uBasis; Vector2 item2 = tuple.vBasis; Rect dest = spriteRect; if ((int)val7.flipped == 0) { int num6 = (int)((Rect)(ref spriteRect)).width - ((Texture)val8).width; int num7 = (int)((Rect)(ref spriteRect)).height - ((Texture)val8).height; if ((num6 > 0 || num7 > 0) && num6 >= 0 && num7 >= 0 && num6 <= 5 && num7 <= 5) { ((Rect)(ref dest))..ctor(((Rect)(ref spriteRect)).x, ((Rect)(ref spriteRect)).y + (float)num7, (float)((Texture)val8).width, (float)((Texture)val8).height); } } TexUtil.DrawRotated(dest, (Texture)(object)val8, item, item2); Object.Destroy((Object)(object)val8); } } val.mainTexture.IncrementUpdateCount(); GL.PopMatrix(); RenderTexture.active = active3; } if (flag2 && Plugin.Config.ConvertSpritesheets) { SpriteDumper.DumpCollection(collection, convert: true); } } internal static List<(string Coll, string Mat, RenderTexture Live, RenderTexture Vanilla)> GetCompositePairsForDiagnostics() { List<(string, string, RenderTexture, RenderTexture)> list = new List<(string, string, RenderTexture, RenderTexture)>(); HashSet hashSet = new HashSet(); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if ((Object)(object)val == (Object)null || val.materials == null || (((Object)val).name != null && ((Object)val).name.StartsWith("Stitchwork_Shadow"))) { continue; } Material[] materials = val.materials; foreach (Material val2 in materials) { if (!((Object)(object)val2 == (Object)null)) { Texture mainTexture = val2.mainTexture; RenderTexture val3 = (RenderTexture)(object)((mainTexture is RenderTexture) ? mainTexture : null); if (val3 != null && hashSet.Add(((Object)val3).GetInstanceID()) && _originalTextures.TryGetValue(((Object)val2).GetInstanceID(), out var value) && (Object)(object)value != (Object)null && value.IsCreated()) { list.Add((((Object)val).name, ((Object)val2).name.Split(' ')[0], val3, value)); } } } } return list; } internal static List<(tk2dSpriteCollectionData Coll, Material Mat, int MatIndex, RenderTexture Live, RenderTexture Vanilla, string VanillaTexName)> GetAtlasExportTargets() { List<(tk2dSpriteCollectionData, Material, int, RenderTexture, RenderTexture, string)> list = new List<(tk2dSpriteCollectionData, Material, int, RenderTexture, RenderTexture, string)>(); HashSet hashSet = new HashSet(); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if ((Object)(object)val == (Object)null || val.materials == null || (((Object)val).name != null && ((Object)val).name.StartsWith("Stitchwork_Shadow"))) { continue; } for (int j = 0; j < val.materials.Length; j++) { Material val2 = val.materials[j]; if ((Object)(object)val2 == (Object)null) { continue; } Texture mainTexture = val2.mainTexture; RenderTexture val3 = (RenderTexture)(object)((mainTexture is RenderTexture) ? mainTexture : null); if (val3 != null && hashSet.Add(((Object)val3).GetInstanceID())) { int instanceID = ((Object)val2).GetInstanceID(); if (_originalTextures.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null && value.IsCreated()) { _vanillaTexNames.TryGetValue(instanceID, out var value2); list.Add((val, val2, j, val3, value, string.IsNullOrEmpty(value2) ? ((Object)val2).name.Split(' ')[0] : value2)); } } } } return list; } internal static string FindWorkspaceSpriteFile(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def) { if ((Object)(object)coll == (Object)null || def == null || string.IsNullOrEmpty(def.name)) { return null; } Material val = (((Object)(object)def.materialInst != (Object)null) ? def.materialInst : def.material); if ((Object)(object)val == (Object)null) { return null; } if (_vanillaTexNames.TryGetValue(((Object)val).GetInstanceID(), out var value) && !string.IsNullOrEmpty(value)) { string text = Path.Combine(LoadPath, ((Object)coll).name, value, def.name + ".png"); if (File.Exists(text)) { return text; } } string text2 = Path.Combine(LoadPath, ((Object)coll).name, ((Object)val).name.Split(' ')[0], def.name + ".png"); if (!File.Exists(text2)) { return null; } return text2; } internal static bool HasSheetReplacement(string collectionName, int matId, string matnameAbbr) { if (!_fileIndexBuilt) { RebuildFileIndex(); } if (_vanillaTexNames.TryGetValue(matId, out var value) && !string.IsNullOrEmpty(value) && _sheetFileIndex.ContainsKey(collectionName + "/" + value + ".png")) { return true; } return _sheetFileIndex.ContainsKey(collectionName + "/" + matnameAbbr + ".png"); } private static void RebuildFileIndex() { ConflictTracker.ClearTypes("sprite", "sheet", "clip"); _spriteFileIndex.Clear(); _sheetFileIndex.Clear(); _spriteAnchors.Clear(); AppliqueLoader.ClearIndex(); _fileIndexBuilt = true; FileIndexVersion++; foreach (string pluginPackPath in Plugin.PluginPackPaths) { IndexDir(Path.Combine(pluginPackPath, "Sprites"), pluginPackPath, _spriteFileIndex, "sprite"); } IndexDir(LoadPath, null, _spriteFileIndex, "sprite"); foreach (string pluginPackPath2 in Plugin.PluginPackPaths) { IndexDir(PackLayout.SheetsRoot(pluginPackPath2), pluginPackPath2, _sheetFileIndex, "sheet"); } IndexDir(AtlasLoadPath, null, _sheetFileIndex, "sheet"); if (!_anchorsTxtWarned) { WarnIfLegacyAnchorsPresent(); } _collectionsWithFiles.Clear(); foreach (string key in _spriteFileIndex.Keys) { int num = key.IndexOf('/'); if (num > 0) { _collectionsWithFiles.Add(key.Substring(0, num)); } } foreach (string key2 in _sheetFileIndex.Keys) { int num2 = key2.IndexOf('/'); if (num2 > 0) { _collectionsWithFiles.Add(key2.Substring(0, num2)); } } int indexCount = AppliqueLoader.IndexCount; int indexPackCount = AppliqueLoader.IndexPackCount; Plugin.Logger.LogInfo((object)($"[SpriteLoader] Index rebuilt: {_spriteFileIndex.Count} sprite file(s), " + $"{_sheetFileIndex.Count} sheet file(s), {_collectionsWithFiles.Count} collection(s) with files, " + $"{indexCount} clip override(s) ({indexPackCount} from pack(s), {indexCount - indexPackCount} from root).")); static void IndexDir(string root, string sourcePack, Dictionary index, string conflictType) { if (Directory.Exists(root)) { string[] files = Directory.GetFiles(root, "*.png", SearchOption.AllDirectories); Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); string[] array = files; foreach (string text in array) { string text2 = text.Substring(root.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Replace('\\', '/'); if (string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(text))) { Plugin.Logger.LogWarning((object)("[SpriteLoader] Ignoring '" + text2 + "' — file has no name.")); } else if ((!(conflictType == "sheet") || sourcePack == null || !text2.StartsWith("Texture2D/", StringComparison.OrdinalIgnoreCase) || !PackLayout.IsCustomizerPack(sourcePack)) && text2.IndexOf("_WIP/", StringComparison.OrdinalIgnoreCase) < 0 && !text2.StartsWith("T2D/", StringComparison.OrdinalIgnoreCase) && text2.IndexOf('/') >= 0) { if (conflictType == "sprite") { int num3 = text2.IndexOf('/'); if (num3 > 0) { int num4 = text2.IndexOf('/', num3 + 1); if (num4 > num3 && string.Compare(text2, num3 + 1, "Clips", 0, num4 - num3 - 1, StringComparison.OrdinalIgnoreCase) == 0 && num4 - num3 - 1 == 5) { AppliqueLoader.IndexFile(text2, text, sourcePack); continue; } } } string text3 = null; if (conflictType == "sprite") { int num5 = text2.LastIndexOf('/'); string obj = ((num5 >= 0) ? text2.Substring(0, num5 + 1) : ""); string text4 = ((num5 >= 0) ? text2.Substring(num5 + 1) : text2); (string name, string anchor) tuple = IOUtil.ParseAnchorTag(text4.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ? text4.Substring(0, text4.Length - 4) : text4); string item = tuple.name; text3 = tuple.anchor; text2 = obj + item + ".png"; } if (index.TryGetValue(text2, out var value)) { if (!IOUtil.BenignDuplicate(value.Item1, text)) { ConflictTracker.Record(conflictType, ConflictKey(conflictType, text2), value.Item2, sourcePack, Path.GetFileName(value.Item1) + " › " + Path.GetFileName(text), value.Item1, text); } } else { index[text2] = (text, sourcePack); if (text3 != null) { _spriteAnchors[text2] = text3; } } } } } } } private static string ConflictKey(string type, string rel) { string text = (rel.EndsWith(".png") ? rel.Substring(0, rel.Length - 4) : rel); string[] array = text.Split('/'); if (!(type == "sprite") || array.Length < 3) { return type + ":" + text; } return "sprite:" + array[0] + "/" + array[2]; } private static string FindSpritePath(string collectionName, int matId, string matnameAbbr, string spriteName) { if (!_fileIndexBuilt) { RebuildFileIndex(); } if (_vanillaTexNames.TryGetValue(matId, out var value) && !string.IsNullOrEmpty(value)) { string key = collectionName + "/" + value + "/" + spriteName + ".png"; if (_spriteFileIndex.TryGetValue(key, out (string, string) value2)) { return value2.Item1; } } string key2 = collectionName + "/" + matnameAbbr + "/" + spriteName + ".png"; if (!_spriteFileIndex.TryGetValue(key2, out (string, string) value3)) { return null; } return value3.Item1; } internal static bool HasSpriteReplacement(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def) { if ((Object)(object)coll == (Object)null || def == null || string.IsNullOrEmpty(def.name)) { return false; } Material val = null; if (coll.materials != null && def.materialId >= 0 && def.materialId < coll.materials.Length) { val = coll.materials[def.materialId]; } if ((Object)(object)val == (Object)null) { val = def.material; } if ((Object)(object)val == (Object)null) { return false; } return FindSpritePath(((Object)coll).name, ((Object)val).GetInstanceID(), ((Object)val).name.Split(' ')[0], def.name) != null; } internal static ReplacementSource GetSpriteProvenance(tk2dSpriteCollectionData coll, tk2dSpriteDefinition def) { if ((Object)(object)coll == (Object)null || def == null || string.IsNullOrEmpty(def.name)) { return ReplacementSource.None; } Material val = null; if (coll.materials != null && def.materialId >= 0 && def.materialId < coll.materials.Length) { val = coll.materials[def.materialId]; } if ((Object)(object)val == (Object)null) { val = def.material; } if ((Object)(object)val == (Object)null) { return ReplacementSource.None; } if (!_fileIndexBuilt) { RebuildFileIndex(); } int instanceID = ((Object)val).GetInstanceID(); if (_vanillaTexNames.TryGetValue(instanceID, out var value) && !string.IsNullOrEmpty(value)) { string key = ((Object)coll).name + "/" + value + "/" + def.name + ".png"; if (_spriteFileIndex.TryGetValue(key, out (string, string) value2)) { if (value2.Item2 == null) { return ReplacementSource.WorkingFolder; } return ReplacementSource.Pack; } } string key2 = ((Object)coll).name + "/" + ((Object)val).name.Split(' ')[0] + "/" + def.name + ".png"; if (_spriteFileIndex.TryGetValue(key2, out (string, string) value3)) { if (value3.Item2 == null) { return ReplacementSource.WorkingFolder; } return ReplacementSource.Pack; } return ReplacementSource.None; } private static string FindSpriteAnchor(string collectionName, int matId, string matnameAbbr, string spriteName) { if (!_fileIndexBuilt) { RebuildFileIndex(); } if (_vanillaTexNames.TryGetValue(matId, out var value) && !string.IsNullOrEmpty(value)) { string key = collectionName + "/" + value + "/" + spriteName + ".png"; if (_spriteFileIndex.ContainsKey(key)) { if (!_spriteAnchors.TryGetValue(key, out var value2)) { return null; } return value2; } } string key2 = collectionName + "/" + matnameAbbr + "/" + spriteName + ".png"; if (_spriteFileIndex.ContainsKey(key2)) { if (!_spriteAnchors.TryGetValue(key2, out var value3)) { return null; } return value3; } return null; } private static void WarnIfLegacyAnchorsPresent() { bool found = false; foreach (string pluginPackPath in Plugin.PluginPackPaths) { Scan(Path.Combine(pluginPackPath, "Sprites")); } Scan(LoadPath); if (found) { _anchorsTxtWarned = true; Plugin.Logger.LogWarning((object)"[SpriteLoader] A legacy anchors.txt was found but is NO LONGER read. Anchors are now filename tags: name@t / @l / @r / @c (omit for bottom-center). Corners are no longer supported — pick an edge. Re-tag the affected PNGs."); } void Scan(string root) { if (!found && !string.IsNullOrEmpty(root) && Directory.Exists(root) && Directory.GetFiles(root, "anchors.txt", SearchOption.AllDirectories).Length != 0) { found = true; } } } private static Texture2D FindSprite(string collectionName, int matId, string matnameAbbr, string spriteName) { string text = FindSpritePath(collectionName, matId, matnameAbbr, spriteName); if (text == null) { return null; } return TexUtil.LoadFromPNG(text); } internal static Texture2D FindSpritesheetTex(tk2dSpriteCollectionData collection, int matId, string matnameAbbr) { if (!_fileIndexBuilt) { RebuildFileIndex(); } string text = null; if (_vanillaTexNames.TryGetValue(matId, out var value) && !string.IsNullOrEmpty(value)) { text = ((Object)collection).name + "/" + value + ".png"; if (_sheetFileIndex.TryGetValue(text, out (string, string) value2)) { Log.Verbose("[SpriteLoader] Sheet hit (instance-specific): " + text + " ← " + (value2.Item2 ?? "")); return TexUtil.LoadFromPNG(value2.Item1); } } string text2 = ((Object)collection).name + "/" + matnameAbbr + ".png"; if (_sheetFileIndex.TryGetValue(text2, out (string, string) value3)) { Log.Verbose("[SpriteLoader] Sheet hit: " + text2 + " ← " + (value3.Item2 ?? "")); return TexUtil.LoadFromPNG(value3.Item1); } Log.Verbose("[SpriteLoader] Sheet miss: tried '" + (text ?? "(no vanilla-tex key)") + "' " + $"and '{text2}' — {_sheetFileIndex.Count} sheet file(s) indexed"); return null; } private static AtlasExpansionPlan PlanExpansion(tk2dSpriteCollectionData collection, int matIndex, int matId, string matnameAbbr, RenderTexture vanillaTex) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: 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_00a8: Invalid comparison between Unknown and I4 //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) int width = ((Texture)vanillaTex).width; int height = ((Texture)vanillaTex).height; int num = width; int num2 = 0; int num3 = 0; int num4 = 0; AtlasExpansionPlan atlasExpansionPlan = null; tk2dSpriteDefinition[] spriteDefinitions = collection.spriteDefinitions; Rect value = default(Rect); foreach (tk2dSpriteDefinition val in spriteDefinitions) { if (val.materialId != matIndex || string.IsNullOrEmpty(val.name)) { continue; } string text = FindSpritePath(((Object)collection).name, matId, matnameAbbr, val.name); if (text == null) { continue; } var (num5, num6) = IOUtil.ReadPngDimensions(text); if (num5 <= 0 || num6 <= 0) { continue; } Rect spriteRect = SpriteUtil.GetSpriteRect(val, (Texture)(object)vanillaTex); bool flag = (int)val.flipped > 0; int num7 = (flag ? ((int)((Rect)(ref spriteRect)).height) : ((int)((Rect)(ref spriteRect)).width)); int num8 = (flag ? ((int)((Rect)(ref spriteRect)).width) : ((int)((Rect)(ref spriteRect)).height)); if (num5 > num7 || num6 > num8) { if (atlasExpansionPlan == null) { atlasExpansionPlan = new AtlasExpansionPlan(); } string text2 = FindSpriteAnchor(((Object)collection).name, matId, matnameAbbr, val.name) ?? "bottom-center"; int num9 = (flag ? num6 : num5); int num10 = (flag ? num5 : num6); if (num9 > num) { num = num9; } if (num2 + num9 > num) { num4 += num3; num2 = 0; num3 = 0; } ((Rect)(ref value))..ctor((float)num2, (float)(height + num4), (float)num9, (float)num10); num2 += num9; num3 = Math.Max(num3, num10); atlasExpansionPlan.AllocatedRects[val.name] = value; atlasExpansionPlan.VanillaRects[val.name] = spriteRect; atlasExpansionPlan.Anchors[val.name] = text2; atlasExpansionPlan.RepDimensions[val.name] = (num5, num6); Log.Verbose("[SpriteLoader] Expand: " + ((Object)collection).name + "/" + matnameAbbr + "/" + val.name + " " + $"{(int)((Rect)(ref spriteRect)).width}×{(int)((Rect)(ref spriteRect)).height} → {num5}×{num6} " + $"anchor={text2} slot=({(int)((Rect)(ref value)).x},{(int)((Rect)(ref value)).y})"); } } if (atlasExpansionPlan != null) { num4 += num3; atlasExpansionPlan.VanillaWidth = width; atlasExpansionPlan.VanillaHeight = height; atlasExpansionPlan.Width = num; atlasExpansionPlan.Height = height + num4; } return atlasExpansionPlan; } private static void ApplyExpansionPlan(tk2dSpriteCollectionData collection, AtlasExpansionPlan plan, int matId, int matIndex, int newW, int newH) { //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: 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_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0424: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_043f: Unknown result type (might be due to invalid IL or missing references) //IL_0436: Unknown result type (might be due to invalid IL or missing references) //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04b7: Invalid comparison between Unknown and I4 //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_0745: Unknown result type (might be due to invalid IL or missing references) //IL_074a: Unknown result type (might be due to invalid IL or missing references) float num = (float)plan.VanillaHeight / (float)newH; float num2 = (float)plan.VanillaWidth / (float)newW; tk2dSpriteDefinition[] spriteDefinitions = collection.spriteDefinitions; foreach (tk2dSpriteDefinition val in spriteDefinitions) { if (val.materialId != matIndex || string.IsNullOrEmpty(val.name)) { continue; } (int, string) key = (matId, val.name); bool flag = plan.AllocatedRects.ContainsKey(val.name); if (val.uvs != null && !_originalUVs.ContainsKey(key)) { tk2dSpriteDefinition[] spriteDefinitions2 = collection.spriteDefinitions; foreach (tk2dSpriteDefinition val2 in spriteDefinitions2) { if (val2 != val && val2.uvs == val.uvs) { val.uvs = (Vector2[])val.uvs.Clone(); break; } } _originalUVs[key] = (Vector2[])val.uvs.Clone(); } if (val.uvs != null && _originalUVs.TryGetValue(key, out var value)) { Rect value2; if (!flag) { for (int k = 0; k < val.uvs.Length && k < value.Length; k++) { val.uvs[k] = new Vector2(value[k].x * num2, value[k].y * num); } } else if (plan.AllocatedRects.TryGetValue(val.name, out value2)) { float num3 = float.MaxValue; float num4 = float.MinValue; float num5 = float.MaxValue; float num6 = float.MinValue; Vector2[] array = value; foreach (Vector2 val3 in array) { if (val3.x < num3) { num3 = val3.x; } if (val3.x > num4) { num4 = val3.x; } if (val3.y < num5) { num5 = val3.y; } if (val3.y > num6) { num6 = val3.y; } } float num7 = ((Rect)(ref value2)).x / (float)newW; float num8 = (((Rect)(ref value2)).x + ((Rect)(ref value2)).width) / (float)newW; float num9 = ((Rect)(ref value2)).y / (float)newH; float num10 = (((Rect)(ref value2)).y + ((Rect)(ref value2)).height) / (float)newH; float num11 = num4 - num3; float num12 = num6 - num5; for (int l = 0; l < val.uvs.Length && l < value.Length; l++) { float num13 = ((num11 > 1E-06f) ? ((value[l].x - num3) / num11) : 0.5f); float num14 = ((num12 > 1E-06f) ? ((value[l].y - num5) / num12) : 0.5f); val.uvs[l] = new Vector2(num7 + num13 * (num8 - num7), num9 + num14 * (num10 - num9)); } } } if (!flag || !plan.VanillaRects.TryGetValue(val.name, out var value3) || !plan.RepDimensions.TryGetValue(val.name, out (int, int) value4)) { continue; } if (val.positions != null && !_originalPositions.ContainsKey(key)) { tk2dSpriteDefinition[] spriteDefinitions2 = collection.spriteDefinitions; foreach (tk2dSpriteDefinition val4 in spriteDefinitions2) { if (val4 != val && val4.positions == val.positions) { val.positions = (Vector3[])val.positions.Clone(); break; } } _originalPositions[key] = (Vector3[])val.positions.Clone(); } if (val.positions == null || !_originalPositions.TryGetValue(key, out var value5) || val.positions.Length != value5.Length || value5.Length < 4) { continue; } float num15 = float.MaxValue; float num16 = float.MinValue; float num17 = float.MaxValue; float num18 = float.MinValue; Vector3[] array2 = value5; foreach (Vector3 val5 in array2) { if (val5.x < num15) { num15 = val5.x; } if (val5.x > num16) { num16 = val5.x; } if (val5.y < num17) { num17 = val5.y; } if (val5.y > num18) { num18 = val5.y; } } float num19 = num16 - num15; float num20 = num18 - num17; if (num19 < 1E-06f || num20 < 1E-06f) { continue; } bool flag2 = (int)val.flipped > 0; int num21 = (flag2 ? ((int)((Rect)(ref value3)).height) : ((int)((Rect)(ref value3)).width)); int num22 = (flag2 ? ((int)((Rect)(ref value3)).width) : ((int)((Rect)(ref value3)).height)); float num23 = num19 / (float)num21; float num24 = num20 / (float)num22; int num25 = value4.Item1 - num21; int num26 = value4.Item2 - num22; string valueOrDefault = plan.Anchors.GetValueOrDefault(val.name, "bottom-center"); float num28; float num29; float num30; float num31; if (IOUtil.TryParsePivotAnchor(valueOrDefault, out var px, out var py)) { int num27 = (flag2 ? (value4.Item1 - 1 - px) : px); num28 = (float)(-num27) * num23; num29 = (float)(value4.Item1 - num27) * num23; num30 = (float)(-py) * num24; num31 = (float)(value4.Item2 - py) * num24; } else { switch (valueOrDefault) { case "top-center": num28 = num15 - (float)num25 * 0.5f * num23; num29 = num16 + (float)num25 * 0.5f * num23; num31 = num18; num30 = num17 - (float)num26 * num24; break; case "left-center": num28 = num15; num29 = num16 + (float)num25 * num23; num30 = num17 - (float)num26 * 0.5f * num24; num31 = num18 + (float)num26 * 0.5f * num24; break; case "right-center": num29 = num16; num28 = num15 - (float)num25 * num23; num30 = num17 - (float)num26 * 0.5f * num24; num31 = num18 + (float)num26 * 0.5f * num24; break; case "center": num28 = num15 - (float)num25 * 0.5f * num23; num29 = num16 + (float)num25 * 0.5f * num23; num30 = num17 - (float)num26 * 0.5f * num24; num31 = num18 + (float)num26 * 0.5f * num24; break; default: num28 = num15 - (float)num25 * 0.5f * num23; num29 = num16 + (float)num25 * 0.5f * num23; num30 = num17; num31 = num18 + (float)num26 * num24; break; } } float num32 = num19; float num33 = num20; for (int m = 0; m < val.positions.Length && m < value5.Length; m++) { float num34 = (value5[m].x - num15) / num32; float num35 = (value5[m].y - num17) / num33; val.positions[m] = new Vector3(num28 + num34 * (num29 - num28), num30 + num35 * (num31 - num30), value5[m].z); } } } private static bool RestoreVanillaDefsForMat(tk2dSpriteCollectionData collection, int matId, int matIndex) { if (matIndex < 0) { return false; } bool result = false; tk2dSpriteDefinition[] spriteDefinitions = collection.spriteDefinitions; foreach (tk2dSpriteDefinition val in spriteDefinitions) { if (val.materialId == matIndex && !string.IsNullOrEmpty(val.name)) { (int, string) key = (matId, val.name); if (_originalUVs.TryGetValue(key, out var value) && val.uvs != null && val.uvs.Length == value.Length) { Array.Copy(value, val.uvs, value.Length); result = true; } if (_originalPositions.TryGetValue(key, out var value2) && val.positions != null && val.positions.Length == value2.Length) { Array.Copy(value2, val.positions, value2.Length); result = true; } } } return result; } internal static bool WasSpriteDrawnThisCycle(string spriteName) { lock (LoadedSprites) { foreach (Dictionary> value in LoadedSprites.Values) { foreach (HashSet value2 in value.Values) { if (value2.Contains(spriteName)) { return true; } } } } return false; } public static void MarkReloadSprite(string collectionName, string atlasName, string spriteName) { lock (LoadedSprites) { foreach (string item in InstanceKeysForName(collectionName)) { if (!LoadedSprites.TryGetValue(item, out var value)) { continue; } foreach (KeyValuePair> item2 in value) { if (item2.Key.StartsWith(atlasName)) { item2.Value.Remove(spriteName); } } } } } public static void MarkReloadAtlas(string collectionName, string atlasName) { lock (LoadedAtlases) { foreach (string item in InstanceKeysForName(collectionName)) { if (LoadedAtlases.TryGetValue(item, out var value)) { value.RemoveWhere((string a) => a.StartsWith(atlasName)); } if (!LoadedSprites.TryGetValue(item, out var value2)) { continue; } foreach (KeyValuePair> item2 in value2) { if (item2.Key.StartsWith(atlasName)) { item2.Value.Clear(); } } } } } private static IEnumerable InstanceKeysForName(string collectionName) { foreach (KeyValuePair item in _instanceKeyToName) { if (string.Equals(item.Value, collectionName, StringComparison.Ordinal)) { yield return item.Key; } } } public static void Reload() { _reloadGen++; IsReloadingSliced = false; _rerunRequested = false; ReloadBegin(); foreach (tk2dSpriteCollectionData item in SnapshotCollections()) { LoadCollection(item); } ReloadFinish(); } public static void RequestSlicedReload() { if (IsReloadingSliced) { _rerunRequested = true; } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(ReloadSlicedRoutine()); } } private static IEnumerator ReloadSlicedRoutine() { int myGen = ++_reloadGen; IsReloadingSliced = true; SlicedStartedAt = Time.realtimeSinceStartup; SlicedCurrent = ""; Scene activeScene = SceneManager.GetActiveScene(); int sceneAtStart = ((Scene)(ref activeScene)).handle; Stopwatch work = new Stopwatch(); Stopwatch slice = new Stopwatch(); int frames = 1; FileCache.BeginCycle(); try { work.Start(); slice.Start(); ReloadBegin(); List list = SnapshotCollections(); SlicedTotal = list.Count; SlicedProcessed = 0; foreach (tk2dSpriteCollectionData collection in list) { if (slice.ElapsedMilliseconds >= 12) { work.Stop(); yield return null; frames++; if (_reloadGen != myGen) { yield break; } activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).handle != sceneAtStart) { IsReloadingSliced = false; SpriteFileWatcher.ReloadSprites = true; Plugin.Logger.LogInfo((object)$"[SpriteLoader] Sliced reload restarted by scene change at {SlicedProcessed}/{SlicedTotal}."); yield break; } work.Start(); slice.Restart(); } if ((Object)(object)collection == (Object)null) { SlicedProcessed++; continue; } SlicedCurrent = ((Object)collection).name; LoadCollection(collection); SlicedProcessed++; } ReloadFinish(); work.Stop(); Plugin.Logger.LogInfo((object)($"[ReloadTiming] Sliced sprite reload: {work.ElapsedMilliseconds} ms work across {frames} frame(s), " + $"span {Time.realtimeSinceStartup - SlicedStartedAt:F1} s.")); IsReloadingSliced = false; if (_rerunRequested) { _rerunRequested = false; SpriteFileWatcher.ReloadSprites = true; } } finally { FileCache.EndCycle(); } } private static List SnapshotCollections() { tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); List list = new List(array.Length); tk2dSpriteCollectionData[] array2 = array; foreach (tk2dSpriteCollectionData val in array2) { if (!((Object)val).name.StartsWith("Stitchwork_Shadow", StringComparison.Ordinal)) { list.Add(val); } } return list; } private static void ReloadBegin() { RebuildFileIndex(); LoadedAtlases.Clear(); LoadedSprites.Clear(); _loadedSpriteCount = 0; _spriteDefsWalked = 0; _rotatedReplacements = 0; _instanceKeyToName.Clear(); _nameInstanceCount.Clear(); _collectionsExpandedThisReload.Clear(); _collectionsRevertedThisReload.Clear(); _perCollectionReplacements.Clear(); } private static void ReloadFinish() { if (_collectionsWithFiles.Count == 0 && _originalTextures.Count > 0) { int num = 0; Material[] array = Resources.FindObjectsOfTypeAll(); foreach (Material val in array) { if (!((Object)(object)val == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); if (_originalTextures.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null && value.IsCreated() && (Object)(object)val.mainTexture != (Object)(object)value) { val.mainTexture = (Texture)(object)value; UnregisterBinding(val); num++; } } } if (num > 0) { Plugin.Logger.LogInfo((object)($"[SpriteLoader] Reverted {num} orphaned material(s) " + "missed by collection sweep")); } } HashSet hashSet = new HashSet(_collectionsExpandedThisReload); hashSet.UnionWith(_collectionsRevertedThisReload); if (hashSet.Count > 0) { int num2 = 0; tk2dBaseSprite[] array2 = Resources.FindObjectsOfTypeAll(); foreach (tk2dBaseSprite val2 in array2) { if (!((Object)(object)val2 == (Object)null) && hashSet.Contains(val2.Collection)) { val2.ForceBuild(); num2++; } } if (num2 > 0) { Plugin.Logger.LogDebug((object)$"[SpriteLoader] ForceBuild on {num2} sprite instance(s) after def changes"); } } string arg = ""; if (_perCollectionReplacements.Count > 0) { List> list = new List>(_perCollectionReplacements); list.Sort((KeyValuePair a, KeyValuePair b) => b.Value.CompareTo(a.Value)); StringBuilder stringBuilder = new StringBuilder(" — "); int num3 = 0; for (int num4 = 0; num4 < list.Count; num4++) { if (num3 >= 8) { break; } if (num3 > 0) { stringBuilder.Append(", "); } stringBuilder.Append(list[num4].Key).Append('=').Append(list[num4].Value); num3++; } if (list.Count > num3) { stringBuilder.Append(", +").Append(list.Count - num3).Append(" more"); } arg = stringBuilder.ToString(); } Plugin.Logger.LogInfo((object)($"[SpriteLoader] Reload complete: {LoadedSpriteCount} replacement(s) drawn across " + $"{LoadedAtlases.Count} atlas(es) ({_spriteDefsWalked} sprite def(s) walked)" + $"{arg} — {_rotatedReplacements} rotated — " + $"atlas VRAM {AtlasRTCount} RT/{AtlasVramBytes >> 20} MB")); AppliqueLoader.Apply(); } } public static class T2DDumper { public static string DumpPath => Path.Combine(SpriteDumper.DumpPath, "T2D"); public static void DumpAllT2DSprites() { HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); HashSet hashSet3 = new HashSet(); HashSet hashSet4 = new HashSet(StringComparer.OrdinalIgnoreCase); Sprite[] array = Resources.FindObjectsOfTypeAll(); foreach (Sprite val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.texture == (Object)null) { continue; } hashSet.Add(((Object)val.texture).GetInstanceID()); if (string.IsNullOrEmpty(((Object)val).name) || string.IsNullOrEmpty(((Object)val.texture).name)) { continue; } if (T2DUtil.IsT2DTexture(((Object)val.texture).name)) { string item = T2DUtil.SpriteKey(T2DUtil.CleanTextureName(((Object)val.texture).name), ((Object)val).name); if (hashSet3.Add(item)) { hashSet4.Add(((Object)val).name); HandleDump(val); } } else if (hashSet2.Add(((Object)val.texture).GetInstanceID())) { HandleDump(val); } } DumpStandaloneTextures(hashSet, hashSet4); } private static void DumpStandaloneTextures(HashSet spriteTextureIds, HashSet atlasDumpedNames) { //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Expected O, but got Unknown //IL_0208: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(DumpPath, "_standalone"); bool flag = false; int num = 0; HashSet hashSet = new HashSet(); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if ((Object)(object)val == (Object)null || val.materials == null) { continue; } Material[] materials = val.materials; foreach (Material val2 in materials) { if ((Object)(object)val2 != (Object)null && (Object)(object)val2.mainTexture != (Object)null) { hashSet.Add(((Object)val2.mainTexture).GetInstanceID()); } } } Texture2D[] array2 = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val3 in array2) { if ((Object)(object)val3 == (Object)null || string.IsNullOrEmpty(((Object)val3).name) || T2DUtil.IsT2DTexture(((Object)val3).name) || hashSet.Contains(((Object)val3).GetInstanceID()) || spriteTextureIds.Contains(((Object)val3).GetInstanceID()) || ((Object)val3).name.StartsWith("unity_") || ((Object)val3).name.StartsWith("UI") || atlasDumpedNames.Contains(((Object)val3).name)) { continue; } string text2 = T2DUtil.SanitizeForFilesystem(((Object)val3).name); string path = Path.Combine(text, text2 + ".png"); if (File.Exists(path)) { continue; } if (T2DLoader.TryGetOriginalPngBytes(((Object)val3).name, out var png)) { if (!flag) { IOUtil.EnsureDirectoryExists(text); flag = true; } File.WriteAllBytes(IOUtil.LongSafe(path), png); num++; continue; } RenderTexture temporary = RenderTexture.GetTemporary(((Texture)val3).width, ((Texture)val3).height, 0, (RenderTextureFormat)0); RenderTexture active = RenderTexture.active; Texture2D val4 = null; try { Graphics.Blit((Texture)(object)val3, temporary); RenderTexture.active = temporary; val4 = new Texture2D(((Texture)val3).width, ((Texture)val3).height, (TextureFormat)4, false); val4.ReadPixels(new Rect(0f, 0f, (float)((Texture)val3).width, (float)((Texture)val3).height), 0, 0); val4.Apply(); if (!flag) { IOUtil.EnsureDirectoryExists(text); flag = true; } File.WriteAllBytes(IOUtil.LongSafe(path), ImageConversion.EncodeToPNG(val4)); num++; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); if ((Object)(object)val4 != (Object)null) { Object.Destroy((Object)(object)val4); } } } if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Dumped {num} standalone textures to {text}"); } } public static void DumpAtlasTexture(Texture2D tex) { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) string text = T2DUtil.CleanTextureName(((Object)tex).name); string text2 = Path.Combine(DumpPath, text); string path = Path.Combine(text2, $"_atlas_{((Texture)tex).width}x{((Texture)tex).height}.png"); if (File.Exists(path)) { return; } if (T2DLoader.TryGetOriginalPngBytes(((Object)tex).name, out var png)) { IOUtil.EnsureDirectoryExists(text2); File.WriteAllBytes(IOUtil.LongSafe(path), png); } else { RenderTexture temporary = RenderTexture.GetTemporary(((Texture)tex).width, ((Texture)tex).height, 0, (RenderTextureFormat)0); RenderTexture active = RenderTexture.active; Texture2D val = null; try { Graphics.Blit((Texture)(object)tex, temporary); RenderTexture.active = temporary; val = new Texture2D(((Texture)tex).width, ((Texture)tex).height, (TextureFormat)4, false); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height), 0, 0); val.Apply(); IOUtil.EnsureDirectoryExists(text2); File.WriteAllBytes(IOUtil.LongSafe(path), ImageConversion.EncodeToPNG(val)); Plugin.Logger.LogDebug((object)$"[T2D] Dumped particle atlas: '{text}' ({((Texture)tex).width}x{((Texture)tex).height})"); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } } Sprite[] array = Resources.FindObjectsOfTypeAll(); foreach (Sprite val2 in array) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.texture == (Object)null) && ((Object)val2.texture).GetInstanceID() == ((Object)tex).GetInstanceID() && !string.IsNullOrEmpty(((Object)val2).name)) { HandleDump(val2); } } } public static void HandleDump(Sprite sprite) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_04d8: Unknown result type (might be due to invalid IL or missing references) //IL_04df: Expected O, but got Unknown //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: 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_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Invalid comparison between Unknown and I4 //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Expected O, but got Unknown //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Expected O, but got Unknown //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Expected O, but got Unknown //IL_036c: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_013e: 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) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) if (T2DUtil.IsT2DTexture(((Object)sprite.texture).name)) { if (string.IsNullOrEmpty(((Object)sprite).name)) { return; } string path = T2DUtil.CleanTextureName(((Object)sprite.texture).name); string text = Path.Combine(DumpPath, path); IOUtil.EnsureDirectoryExists(text); string path2 = Path.Combine(text, ((Object)sprite).name + ".png"); if (File.Exists(path2)) { return; } Rect rect = sprite.rect; int num = (int)((Rect)(ref rect)).width; rect = sprite.rect; int num2 = (int)((Rect)(ref rect)).height; int num3 = 31; GameObject val = new GameObject("TempSpriteRenderer"); SpriteRenderer val2 = null; GameObject val3 = null; Camera val4 = null; RenderTexture val5 = null; Texture2D val6 = null; Texture2D val7 = null; Sprite val8 = null; try { Sprite sprite2 = sprite; if (T2DLoader.TryGetOriginalPngBytes(((Object)sprite.texture).name, out var png)) { Rect val9 = sprite.rect; bool flag = true; if (sprite.packed) { if ((int)sprite.packingMode == 1) { val9 = sprite.textureRect; } else { flag = false; } } if (flag) { val7 = new Texture2D(2, 2, (TextureFormat)4, false); if (ImageConversion.LoadImage(val7, png) && ((Texture)val7).width == ((Texture)sprite.texture).width && ((Texture)val7).height == ((Texture)sprite.texture).height) { Texture2D obj = val7; Rect val10 = val9; float x = sprite.pivot.x; rect = sprite.rect; float num4 = x / ((Rect)(ref rect)).width; float y = sprite.pivot.y; rect = sprite.rect; val8 = Sprite.Create(obj, val10, new Vector2(num4, y / ((Rect)(ref rect)).height), sprite.pixelsPerUnit); sprite2 = val8; } } else { Plugin.Logger.LogWarning((object)("[T2D] DumpSingle '" + ((Object)sprite).name + "': atlas is tight-packed — no axis-aligned vanilla rect exists; dumping the LIVE sprite (may contain replaced sheet art).")); } } val2 = val.AddComponent(); T2DLoader.SetInternalAssignmentGuard(on: true); try { val2.sprite = sprite2; } finally { T2DLoader.SetInternalAssignmentGuard(on: false); } val.layer = num3; Transform transform = val.transform; float x2 = sprite.pivot.x; rect = sprite.rect; float num5 = 10000f + (x2 - ((Rect)(ref rect)).width / 2f) / sprite.pixelsPerUnit; float y2 = sprite.pivot.y; rect = sprite.rect; transform.position = new Vector3(num5, 10000f + (y2 - ((Rect)(ref rect)).height / 2f) / sprite.pixelsPerUnit, 0f); val3 = new GameObject("TempCamera"); val4 = val3.AddComponent(); val4.clearFlags = (CameraClearFlags)2; val4.backgroundColor = new Color(0f, 0f, 0f, 0f); val4.orthographic = true; val4.cullingMask = 1 << num3; val4.orthographicSize = (float)num2 / sprite.pixelsPerUnit / 2f; ((Component)val4).transform.position = new Vector3(10000f, 10000f, -10f); Plugin.Logger.LogDebug((object)$"[T2D] DumpSingle '{((Object)sprite).name}' rect={num}x{num2} tex='{((Object)sprite.texture).name}' vanillaClone={(Object)(object)val8 != (Object)null}"); val5 = new RenderTexture(num, num2, 0, (RenderTextureFormat)0); ((Texture)val5).filterMode = (FilterMode)0; val4.targetTexture = val5; val4.Render(); RenderTexture active = RenderTexture.active; RenderTexture.active = val5; val6 = new Texture2D(num, num2, (TextureFormat)5, false); val6.ReadPixels(new Rect(0f, 0f, (float)num, (float)num2), 0, 0); val6.Apply(); RenderTexture.active = active; File.WriteAllBytes(IOUtil.LongSafe(path2), ImageConversion.EncodeToPNG(val6)); return; } finally { if ((Object)(object)val4 != (Object)null) { val4.targetTexture = null; } if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } if ((Object)(object)val3 != (Object)null) { Object.DestroyImmediate((Object)(object)val3); } if ((Object)(object)val5 != (Object)null) { Object.DestroyImmediate((Object)(object)val5); } if ((Object)(object)val6 != (Object)null) { Object.DestroyImmediate((Object)(object)val6); } if ((Object)(object)val8 != (Object)null) { Object.DestroyImmediate((Object)(object)val8); } if ((Object)(object)val7 != (Object)null) { Object.DestroyImmediate((Object)(object)val7); } } } string text2 = T2DUtil.SanitizeForFilesystem(((Object)sprite.texture).name); if (string.IsNullOrEmpty(text2)) { return; } string path3 = Path.Combine(DumpPath, text2 + ".png"); if (File.Exists(path3)) { return; } if (T2DLoader.TryGetOriginalPngBytes(((Object)sprite.texture).name, out var png2)) { File.WriteAllBytes(IOUtil.LongSafe(path3), png2); return; } Plugin.Logger.LogDebug((object)("[T2D] Dumping non-atlas texture: '" + ((Object)sprite.texture).name + "' -> " + text2 + ".png")); RenderTexture val11 = null; Texture2D val12 = null; try { val11 = TexUtil.GetReadable((Texture)(object)sprite.texture); val12 = new Texture2D(((Texture)val11).width, ((Texture)val11).height, (TextureFormat)5, false); RenderTexture active2 = RenderTexture.active; RenderTexture.active = val11; val12.ReadPixels(new Rect(0f, 0f, (float)((Texture)val11).width, (float)((Texture)val11).height), 0, 0); val12.Apply(); RenderTexture.active = active2; File.WriteAllBytes(IOUtil.LongSafe(path3), ImageConversion.EncodeToPNG(val12)); } finally { if ((Object)(object)val11 != (Object)null) { RenderTexture.ReleaseTemporary(val11); } if ((Object)(object)val12 != (Object)null) { Object.DestroyImmediate((Object)(object)val12); } } } } [HarmonyPatch] public static class T2DHandler { [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void SetSpritePostfix(SpriteRenderer __instance, Sprite value) { if (T2DLoader.IsHandlingOrEnforcing || (Object)(object)__instance == (Object)null || (Object)(object)value == (Object)null || ((Object)((Component)__instance).gameObject).name == "TempSpriteRenderer") { return; } bool flag = Plugin.ShowDevHub || Plugin.Config.DumpSprites || T2DLoader.IsT2DLogActive; if (!T2DLoader.HasT2DReplacements && !flag) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { T2DLoader.NotifySetterCall(); if (Plugin.Config.DumpSprites && !string.IsNullOrEmpty(((Object)value).name) && (Object)(object)value.texture != (Object)null && !string.IsNullOrEmpty(((Object)value.texture).name)) { T2DDumper.HandleDump(value); } if (Plugin.ShowDevHub) { T2DLoader.TrackRenderer(__instance); } if (T2DLoader.IsT2DLogActive && (Object)(object)value.texture != (Object)null) { T2DLoader.RaiseT2DTrigger(((Object)value.texture).name, T2DUtil.CleanTextureName(((Object)value.texture).name), ((Object)value).name); } if (T2DLoader.HasT2DReplacements) { T2DLoader.OnSpriteSet(__instance, value); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.SR.sprite", heapBefore); } } [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void SetImageSpritePostfix(Image __instance, Sprite value) { if (T2DLoader.IsHandlingOrEnforcing || (Object)(object)__instance == (Object)null || (Object)(object)value == (Object)null) { return; } bool flag = Plugin.ShowDevHub || Plugin.Config.DumpSprites || T2DLoader.IsT2DLogActive; if (!T2DLoader.HasT2DReplacements && !flag) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { T2DLoader.NotifySetterCall(); if (Plugin.Config.DumpSprites && !string.IsNullOrEmpty(((Object)value).name) && (Object)(object)value.texture != (Object)null && !string.IsNullOrEmpty(((Object)value.texture).name)) { T2DDumper.HandleDump(value); } if (Plugin.ShowDevHub) { T2DLoader.TrackImage(__instance); } if (T2DLoader.IsT2DLogActive && (Object)(object)value.texture != (Object)null) { T2DLoader.RaiseT2DTrigger(((Object)value.texture).name, T2DUtil.CleanTextureName(((Object)value.texture).name), ((Object)value).name); } if (T2DLoader.HasT2DReplacements) { T2DLoader.OnSpriteSet(__instance, value); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Img.sprite", heapBefore); } } [HarmonyPostfix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static void SetMaterialTexturePostfix(Texture value) { Texture2D val = (Texture2D)(object)((value is Texture2D) ? value : null); if (val == null) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if (T2DLoader.IsT2DLogActive && T2DUtil.IsT2DTexture(((Object)val).name)) { T2DLoader.RaiseT2DTrigger(((Object)val).name, T2DUtil.CleanTextureName(((Object)val).name), ""); } if (T2DLoader.HasT2DReplacements) { T2DLoader.TrySwapTexture(val); if (Plugin.Config.DumpSprites && T2DUtil.IsT2DTexture(((Object)val).name)) { T2DDumper.DumpAtlasTexture(val); } } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Mat.mainTexture", heapBefore); } } [HarmonyPrefix] [HarmonyPatch(typeof(Cursor), "SetCursor", new Type[] { typeof(Texture2D), typeof(Vector2), typeof(CursorMode) })] public static void CursorSetCursorPrefix(ref Texture2D texture, Vector2 hotspot, CursorMode cursorMode) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)texture == (Object)null)) { T2DLoader.RememberCursorCall(texture, hotspot, cursorMode); if (T2DLoader.HasT2DReplacements && T2DLoader.TryGetStandaloneReplacement(((Object)texture).name, out var replacement)) { texture = replacement; T2DLoader.MarkCursorSubstituted(); } } } [HarmonyPostfix] [HarmonyPatch(typeof(Material), "SetTexture", new Type[] { typeof(string), typeof(Texture) })] public static void SetTextureStringPostfix(Texture value) { Texture2D val = (Texture2D)(object)((value is Texture2D) ? value : null); if (val == null) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if (T2DLoader.IsT2DLogActive && T2DUtil.IsT2DTexture(((Object)val).name)) { T2DLoader.RaiseT2DTrigger(((Object)val).name, T2DUtil.CleanTextureName(((Object)val).name), ""); } if (T2DLoader.HasT2DReplacements) { T2DLoader.TrySwapTexture(val); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Mat.SetTexture", heapBefore); } } } internal static class SpriteAtlasBridge { private static bool _init; private static Type _atlasType; private static Type _atlasManagerType; private static EventInfo _evAtlasRegistered; private static MethodInfo _miGetSpritesArray; private static Delegate _subscribedHandler; public static void Init() { if (_init) { return; } _init = true; _atlasType = Type.GetType("UnityEngine.U2D.SpriteAtlas, UnityEngine.U2DModule", throwOnError: false); _atlasManagerType = Type.GetType("UnityEngine.U2D.SpriteAtlasManager, UnityEngine.U2DModule", throwOnError: false); if (!(_atlasType == null) && !(_atlasManagerType == null)) { _evAtlasRegistered = _atlasManagerType.GetEvent("atlasRegistered", BindingFlags.Static | BindingFlags.Public); _miGetSpritesArray = _atlasType.GetMethod("GetSprites", BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(Sprite[]) }, null); if (!(_evAtlasRegistered == null) && !(_miGetSpritesArray == null)) { MethodInfo method = typeof(SpriteAtlasBridge).GetMethod("OnAtlasRegistered", BindingFlags.Static | BindingFlags.NonPublic); _subscribedHandler = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(_atlasType), method); _evAtlasRegistered.GetAddMethod().Invoke(null, new object[1] { _subscribedHandler }); Plugin.Logger.LogDebug((object)"[SpriteAtlasBridge] subscribed to SpriteAtlasManager.atlasRegistered"); } } } private static void OnAtlasRegistered(object atlas) { if (atlas == null || _miGetSpritesArray == null || !T2DLoader.HasT2DReplacements) { return; } Sprite[] array; int num; try { array = (Sprite[])(object)new Sprite[1024]; num = (int)_miGetSpritesArray.Invoke(atlas, new object[1] { array }); } catch (Exception ex) { Plugin.Logger.LogWarning((object)$"[SpriteAtlasBridge] GetSprites threw: {(ex as TargetInvocationException)?.InnerException ?? ex}"); return; } for (int i = 0; i < num && i < array.Length; i++) { Sprite val = array[i]; if ((Object)(object)val != (Object)null && (Object)(object)val.texture != (Object)null) { T2DLoader.TrySwapTexture(val.texture); } } } } public static class T2DLoader { private static readonly Dictionary _loadedSprites = new Dictionary(); private static readonly Dictionary _spriteNameToKey = new Dictionary(); private static readonly Dictionary _t2dProviders = new Dictionary(); private static readonly Dictionary _t2dSourceFiles = new Dictionary(); private static readonly HashSet _coveredCleanNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly HashSet _emittedMissKeys = new HashSet(StringComparer.Ordinal); private static string _lastCursorName; private static Vector2 _lastCursorHotspot; private static CursorMode _lastCursorMode; private static readonly HashSet _knownRenderers = new HashSet(); private static readonly HashSet _knownImages = new HashSet(); private static readonly HashSet _replacedSrs = new HashSet(); private static readonly HashSet _replacedImgs = new HashSet(); private static readonly ConditionalWeakTable _spriteKeyCache = new ConditionalWeakTable(); private static readonly ConditionalWeakTable.CreateValueCallback _spriteKeyCreator = (Sprite s) => T2DUtil.SpriteKey(T2DUtil.CleanTextureName(((Object)s.texture).name), ((Object)s).name); private static readonly HashSet _ourSpriteIds = new HashSet(); private static readonly List _retiredSprites = new List(); private static readonly Predicate _srNull = (SpriteRenderer sr) => (Object)(object)sr == (Object)null; private static readonly Predicate _imgNull = (Image img) => (Object)(object)img == (Object)null; private static bool _enforcing; private static bool _handling; public static bool IsT2DLogActive; private static Sprite[] _seededSprites = Array.Empty(); private static readonly Dictionary _spriteHighlightCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static Texture2D _lastCursorVanillaTex; private static bool _cursorSubstituted; private static float _diagNextTime; private static readonly HashSet _diagSeen = new HashSet(); private static readonly HashSet InitializedSpriteRenderers = new HashSet(); private static SpriteRenderer[] _cachedSrArray = Array.Empty(); private static Image[] _cachedImgArray = Array.Empty(); private static float _nextCacheRefreshTime; private static bool _cacheInvalidated = true; private const float DefaultCacheRefreshSeconds = 0.3f; private static int _setterCallsThisFrame; private static int _prevFrameSetterCalls; private static readonly Dictionary _lastLoggedSpriteIdSr = new Dictionary(); private static readonly Dictionary _lastLoggedSpriteIdImg = new Dictionary(); private static int _enforcePruneCounter; private static readonly Dictionary _lastSeenSpriteIdSr = new Dictionary(); private static readonly Dictionary _lastSeenSpriteIdImg = new Dictionary(); private static readonly Dictionary SpritesheetOverrides = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly HashSet ReplacedTextureIds = new HashSet(); private static readonly HashSet SkippedTextureIds = new HashSet(); private static readonly HashSet SoftSkippedTextureIds = new HashSet(); private static readonly HashSet _staleSheetWarned = new HashSet(); private static Type _tmpFontAssetType; private static MemberInfo _fontAssetAtlasMember; private static MemberInfo _fontAssetAtlasTexturesMember; private static MemberInfo _fontAssetFallbackTableMember; private static bool _fontBridgeInit; private static Type _ppProfileType; private static FieldInfo _ppProfileSettingsField; private static Type _ppParamOverrideType; private static bool _ppBridgeInit; private const int TrackingSetCap = 16384; private static readonly HashSet _restoredNames = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _originalTextureData = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _sheetProviders = new Dictionary(StringComparer.OrdinalIgnoreCase); public static string AtlasLoadPath => Path.Combine(SpriteLoader.AtlasLoadPath, "T2D"); public static int ReplacedRendererCount => _replacedSrs.Count + _replacedImgs.Count; public static bool HasT2DReplacements { get { if (SpritesheetOverrides.Count <= 0) { return _loadedSprites.Count > 0; } return true; } } public static bool IsHandlingOrEnforcing { get { if (!_handling) { return _enforcing; } return true; } } public static int SpritesheetOverrideCount => SpritesheetOverrides.Count; public static int LoadedT2DSpriteCount => _loadedSprites.Count; public static IEnumerable LoadedT2DSpriteNames => _loadedSprites.Keys; public static IEnumerable SpritesheetOverrideNames => SpritesheetOverrides.Keys; private static float UninitRefreshSeconds => Mathf.Clamp(Plugin.Config?.UninitCheckCacheRefreshSeconds ?? 0.3f, 0.01f, 10f); public static int PrevFrameSetterCalls => _prevFrameSetterCalls; internal static bool HasStoredOriginals => _originalTextureData.Count > 0; internal static long SpritesheetOverrideBytes { get { long num = 0L; foreach (var value in SpritesheetOverrides.Values) { if (value.PngData != null) { num += value.PngData.Length; } } return num; } } internal static long OriginalTextureDataBytes { get { long num = 0L; foreach (byte[] value in _originalTextureData.Values) { num += value.Length; } return num; } } public static event Action OnT2DTrigger; public static Dictionary CountActiveRenderersByPack() { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (SpriteRenderer replacedSr in _replacedSrs) { if ((Object)(object)replacedSr != (Object)null) { TallyRendererPack(replacedSr.sprite, dictionary); } } foreach (Image replacedImg in _replacedImgs) { if ((Object)(object)replacedImg != (Object)null) { TallyRendererPack(replacedImg.sprite, dictionary); } } return dictionary; } private static void TallyRendererPack(Sprite sprite, Dictionary counts) { if ((Object)(object)sprite == (Object)null || (Object)(object)sprite.texture == (Object)null) { return; } string name = ((Object)sprite.texture).name; if (string.IsNullOrEmpty(name)) { return; } if (!_sheetProviders.TryGetValue(name, out var value)) { string key = T2DUtil.CleanTextureName(name); if (!_sheetProviders.TryGetValue(key, out value)) { return; } } if (!string.IsNullOrEmpty(value)) { counts[value] = (counts.TryGetValue(value, out var value2) ? value2 : 0) + 1; } } internal static void SetInternalAssignmentGuard(bool on) { _enforcing = on; } public static void RaiseT2DTrigger(string rawTexName, string cleanTexName, string spriteName) { T2DLoader.OnT2DTrigger?.Invoke(rawTexName, cleanTexName, spriteName); } public static void TrackRenderer(SpriteRenderer sr) { if ((Object)(object)sr != (Object)null) { _knownRenderers.Add(sr); } } public static void TrackImage(Image img) { if ((Object)(object)img != (Object)null) { _knownImages.Add(img); } } internal static void InvalidateSpriteLookupCache() { _spriteHighlightCache.Clear(); } public static void ResetSceneSeed() { _seededSprites = Array.Empty(); _spriteHighlightCache.Clear(); _knownRenderers.RemoveWhere(_srNull); _knownImages.RemoveWhere(_imgNull); } public static List GetSceneTextureEntries() { Dictionary> spritesByTexId = new Dictionary>(); Dictionary texById = new Dictionary(); _seededSprites = Resources.FindObjectsOfTypeAll(); Sprite[] seededSprites = _seededSprites; for (int i = 0; i < seededSprites.Length; i++) { Accumulate(seededSprites[i]); } foreach (SpriteRenderer knownRenderer in _knownRenderers) { if (!((Object)(object)knownRenderer == (Object)null)) { Sprite sprite = knownRenderer.sprite; if ((Object)(object)sprite != (Object)null) { Accumulate(sprite); } } } foreach (Image knownImage in _knownImages) { if (!((Object)(object)knownImage == (Object)null)) { Sprite sprite2 = knownImage.sprite; if ((Object)(object)sprite2 != (Object)null) { Accumulate(sprite2); } } } ParticleSystemRenderer[] array = Resources.FindObjectsOfTypeAll(); foreach (ParticleSystemRenderer val in array) { if ((Object)(object)val == (Object)null) { continue; } Material sharedMaterial = ((Renderer)val).sharedMaterial; if ((Object)(object)sharedMaterial == (Object)null) { continue; } Texture mainTexture = sharedMaterial.mainTexture; if (!((Object)(object)mainTexture == (Object)null) && !string.IsNullOrEmpty(((Object)mainTexture).name)) { int instanceID = ((Object)mainTexture).GetInstanceID(); if (!spritesByTexId.ContainsKey(instanceID)) { spritesByTexId[instanceID] = new HashSet(StringComparer.Ordinal); texById[instanceID] = mainTexture; } } } Dictionary, bool, int)> dictionary = new Dictionary, bool, int)>(StringComparer.Ordinal); foreach (KeyValuePair item9 in texById) { Texture value = item9.Value; bool flag = T2DUtil.IsT2DTexture(((Object)value).name); string item = (flag ? T2DUtil.CleanTextureName(((Object)value).name) : ((Object)value).name); int item2 = (flag ? T2DUtil.AtlasIndexFromRaw(((Object)value).name) : (-1)); HashSet hashSet = spritesByTexId[item9.Key]; if (dictionary.TryGetValue(((Object)value).name, out var value2)) { value2.Item4.UnionWith(hashSet); if (value.width * value.height > value2.Item1.width * value2.Item1.height) { dictionary[((Object)value).name] = (value, ((Object)value).name, item, value2.Item4, flag, item2); } } else { dictionary[((Object)value).name] = (value, ((Object)value).name, item, hashSet, flag, item2); } } tk2dSpriteCollectionData[] array2 = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val2 in array2) { if (val2?.spriteDefinitions == null) { continue; } tk2dSpriteDefinition[] spriteDefinitions = val2.spriteDefinitions; foreach (tk2dSpriteDefinition val3 in spriteDefinitions) { if (val3 != null && !((Object)(object)val3.material == (Object)null) && !((Object)(object)val3.material.mainTexture == (Object)null) && !string.IsNullOrEmpty(val3.name)) { string name = ((Object)val3.material.mainTexture).name; if (dictionary.TryGetValue(name, out var value3)) { value3.Item4.Add(val3.name); } } } } Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var value8 in dictionary.Values) { dictionary2.TryGetValue(value8.Item3, out var value4); dictionary2[value8.Item3] = value4 + 1; } List list = new List(dictionary.Count); foreach (KeyValuePair, bool, int)> item10 in dictionary) { (Texture, string, string, HashSet, bool, int) value5 = item10.Value; Texture item3 = value5.Item1; string item4 = value5.Item2; string item5 = value5.Item3; HashSet item6 = value5.Item4; bool item7 = value5.Item5; int item8 = value5.Item6; bool hasOverride = SpritesheetOverrides.ContainsKey(item4) || SpritesheetOverrides.ContainsKey(item5); string value6 = item5 + "/"; int num = 0; foreach (string key in _loadedSprites.Keys) { if (key.StartsWith(value6, StringComparison.OrdinalIgnoreCase)) { num++; } } string displayName = ((dictionary2[item5] > 1 && item8 >= 0) ? $"{item5}-{item8}" : item5); list.Add(new T2DSceneEntry(item5, item4, item3, item7, new List(item6), hasOverride, num, item8, displayName)); } list.Sort((T2DSceneEntry a, T2DSceneEntry b) => (a.IsT2D != b.IsT2D) ? ((!a.IsT2D) ? 1 : (-1)) : string.Compare(a.DisplayName, b.DisplayName, StringComparison.OrdinalIgnoreCase)); return list; void Accumulate(Sprite val4) { if (!((Object)(object)val4 == (Object)null) && !((Object)(object)val4.texture == (Object)null)) { Texture2D texture = val4.texture; int instanceID2 = ((Object)texture).GetInstanceID(); if (!spritesByTexId.TryGetValue(instanceID2, out var value7)) { value7 = new HashSet(StringComparer.Ordinal); spritesByTexId[instanceID2] = value7; texById[instanceID2] = (Texture)(object)texture; } if (!string.IsNullOrEmpty(((Object)val4).name)) { value7.Add(((Object)val4).name); } } } } public static Sprite FindSceneSprite(string rawTexName, string spriteName) { string key = rawTexName + "/" + spriteName; if (_spriteHighlightCache.TryGetValue(key, out var value)) { return value; } foreach (SpriteRenderer knownRenderer in _knownRenderers) { if (!((Object)(object)knownRenderer == (Object)null)) { Sprite sprite = knownRenderer.sprite; if (!((Object)(object)sprite == (Object)null) && !((Object)(object)sprite.texture == (Object)null) && ((Object)sprite).name == spriteName && ((Object)sprite.texture).name == rawTexName) { _spriteHighlightCache[key] = sprite; return sprite; } } } foreach (Image knownImage in _knownImages) { if (!((Object)(object)knownImage == (Object)null)) { Sprite sprite2 = knownImage.sprite; if (!((Object)(object)sprite2 == (Object)null) && !((Object)(object)sprite2.texture == (Object)null) && ((Object)sprite2).name == spriteName && ((Object)sprite2.texture).name == rawTexName) { _spriteHighlightCache[key] = sprite2; return sprite2; } } } Sprite[] seededSprites = _seededSprites; foreach (Sprite val in seededSprites) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.texture == (Object)null) && ((Object)val).name == spriteName && ((Object)val.texture).name == rawTexName) { _spriteHighlightCache[key] = val; return val; } } return null; } public static void OnSpriteSet(SpriteRenderer sr, Sprite value) { if ((Object)(object)value.texture != (Object)null && !HasIndividualReplacement(value)) { TrySwapTexture(value.texture); } HandleLoad(sr, value); } public static void OnSpriteSet(Image img, Sprite value) { if ((Object)(object)value.texture != (Object)null && !HasIndividualReplacement(value)) { TrySwapTexture(value.texture); } HandleLoad(img, value); } private static bool HasIndividualReplacement(Sprite sprite) { if ((Object)(object)sprite.texture == (Object)null || !T2DUtil.IsT2DTexture(((Object)sprite.texture).name)) { return false; } return _loadedSprites.ContainsKey(KeyForSprite(sprite)); } public static void ApplyReplacementsInScene() { InvalidateSpriteLookupCache(); RegisterFontAtlasIds(); RegisterPostProcessTextureIds(); if (SpritesheetOverrides.Count > 0) { Texture2D[] array = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val in array) { if ((Object)(object)val != (Object)null) { TrySwapTexture(val); } } } if (!HasT2DReplacements) { return; } SpriteRenderer[] array2 = Resources.FindObjectsOfTypeAll(); foreach (SpriteRenderer val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.sprite == (Object)null)) { HandleLoad(val2, val2.sprite); } } Image[] array3 = Resources.FindObjectsOfTypeAll(); foreach (Image val3 in array3) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.sprite == (Object)null)) { HandleLoad(val3, val3.sprite); } } PreCorrectAllPPU(); RefreshSubstitutedCursor(); } public static void PreloadAllTextures() { ConflictTracker.ClearTypes("t2d-sprite", "t2d-sheet"); BuildSpritesheetOverrides(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { ScanDirectory(Path.Combine(pluginPackPath, "Sprites", "T2D"), pluginPackPath); } ScanDirectory(Path.Combine(SpriteLoader.LoadPath, "T2D"), null); if (Plugin.Config.LogT2DLookupMisses) { DumpKeysToSideFile(); } static void ScanDirectory(string t2dRoot, string sourcePack) { if (Directory.Exists(t2dRoot)) { string[] directories = Directory.GetDirectories(t2dRoot); foreach (string path in directories) { string fileName = Path.GetFileName(path); bool flag = fileName.Equals("_standalone", StringComparison.OrdinalIgnoreCase); bool flag2 = false; string[] files = Directory.GetFiles(path, "*.png"); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if ((Object)(object)CreateAndStore(flag ? fileNameWithoutExtension : T2DUtil.SpriteKey(fileName, fileNameWithoutExtension), fileNameWithoutExtension, text, flag) != (Object)null) { flag2 = true; } if (flag) { RegisterStandaloneSpritesheet(fileNameWithoutExtension, text, sourcePack); } } if (flag2 && !flag) { _coveredCleanNames.Add(fileName); } } directories = Directory.GetFiles(t2dRoot, "*.png"); foreach (string text2 in directories) { string? fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(text2); CreateAndStore(fileNameWithoutExtension2, fileNameWithoutExtension2, text2, keepReadable: true); } } Sprite CreateAndStore(string key, string spriteName, string file, bool keepReadable) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(spriteName)) { Plugin.Logger.LogWarning((object)("[T2D] Ignoring '" + file + "' — file has no name.")); return null; } if (_loadedSprites.ContainsKey(key)) { if (!IOUtil.BenignDuplicate(_t2dSourceFiles.GetValueOrDefault(key), file)) { ConflictTracker.Record("t2d-sprite", key, _t2dProviders.GetValueOrDefault(key), sourcePack, "Sprites/T2D/_standalone › Sprites/T2D", _t2dSourceFiles.GetValueOrDefault(key), file); } return null; } byte[] array = FileCache.ReadBytes(file); if (array == null) { return null; } Texture2D val = TexUtil.CreateTextureFromBytes(array, !keepReadable); if ((Object)(object)val == (Object)null) { return null; } SkippedTextureIds.Add(((Object)val).GetInstanceID()); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = spriteName; ((Object)val2).hideFlags = (HideFlags)32; _ourSpriteIds.Add(((Object)val2).GetInstanceID()); _loadedSprites[key] = val2; _t2dProviders[key] = sourcePack; _t2dSourceFiles[key] = file; if (!_spriteNameToKey.ContainsKey(spriteName)) { _spriteNameToKey[spriteName] = key; } return val2; } } } private static void DumpKeysToSideFile() { try { string basePath = Plugin.BasePath; IOUtil.EnsureDirectoryExists(basePath); string text = Path.Combine(basePath, "diagnostic-t2d-keys.txt"); using StreamWriter streamWriter = new StreamWriter(text, append: false); streamWriter.WriteLine($"# Stitchwork T2D diagnostic dump — generated {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); streamWriter.WriteLine($"# _loadedSprites: {_loadedSprites.Count} entries"); streamWriter.WriteLine($"# _spriteNameToKey: {_spriteNameToKey.Count} entries (reverse index)"); streamWriter.WriteLine($"# _coveredCleanNames: {_coveredCleanNames.Count} atlases"); streamWriter.WriteLine(); streamWriter.WriteLine("[loadedSprites]"); foreach (string key in _loadedSprites.Keys) { streamWriter.WriteLine(key); } streamWriter.WriteLine(); streamWriter.WriteLine("[coveredCleanNames]"); foreach (string coveredCleanName in _coveredCleanNames) { streamWriter.WriteLine(coveredCleanName); } Plugin.Logger.LogInfo((object)$"[T2D-Keys] Dumped {_loadedSprites.Count} key(s) to {text}"); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[T2D-Keys] Failed to write side file: " + ex.Message)); } } private static Sprite EnsurePPU(string key, Sprite cached, float targetPPU, string vanillaTextureName = null, Sprite vanilla = null) { //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0205: 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) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_0136: 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_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_018f: 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_01a0: Unknown result type (might be due to invalid IL or missing references) Rect rect; Rect val = default(Rect); Vector2 val2 = default(Vector2); float num8; if ((Object)(object)vanilla != (Object)null && (Object)(object)vanilla.texture != (Object)null && ((Texture)vanilla.texture).width > 0 && ((Texture)vanilla.texture).height > 0 && !T2DUtil.IsT2DTexture(((Object)vanilla.texture).name)) { float num = (float)((Texture)cached.texture).width / (float)((Texture)vanilla.texture).width; float num2 = (float)((Texture)cached.texture).height / (float)((Texture)vanilla.texture).height; rect = vanilla.rect; float num3 = ((Rect)(ref rect)).x * num; rect = vanilla.rect; float num4 = ((Rect)(ref rect)).y * num2; rect = vanilla.rect; float num5 = ((Rect)(ref rect)).width * num; rect = vanilla.rect; ((Rect)(ref val))..ctor(num3, num4, num5, ((Rect)(ref rect)).height * num2); ((Rect)(ref val)).width = Mathf.Min(((Rect)(ref val)).width, (float)((Texture)cached.texture).width - ((Rect)(ref val)).x); ((Rect)(ref val)).height = Mathf.Min(((Rect)(ref val)).height, (float)((Texture)cached.texture).height - ((Rect)(ref val)).y); rect = vanilla.rect; float num6; if (!(((Rect)(ref rect)).width > 0f)) { num6 = 0.5f; } else { float x = vanilla.pivot.x; rect = vanilla.rect; num6 = x / ((Rect)(ref rect)).width; } rect = vanilla.rect; float num7; if (!(((Rect)(ref rect)).height > 0f)) { num7 = 0.5f; } else { float y = vanilla.pivot.y; rect = vanilla.rect; num7 = y / ((Rect)(ref rect)).height; } ((Vector2)(ref val2))..ctor(num6, num7); num8 = targetPPU * num; goto IL_026d; } ((Rect)(ref val))..ctor(0f, 0f, (float)((Texture)cached.texture).width, (float)((Texture)cached.texture).height); Vector2 val3; if ((Object)(object)vanilla != (Object)null) { rect = vanilla.rect; if (((Rect)(ref rect)).width > 0f) { rect = vanilla.rect; if (((Rect)(ref rect)).height > 0f) { float x2 = vanilla.pivot.x; rect = vanilla.rect; float num9 = x2 / ((Rect)(ref rect)).width; float y2 = vanilla.pivot.y; rect = vanilla.rect; val3 = new Vector2(num9, y2 / ((Rect)(ref rect)).height); goto IL_026a; } } } val3 = new Vector2(0.5f, 0.5f); goto IL_026a; IL_026d: Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(val2.x * ((Rect)(ref val)).width, val2.y * ((Rect)(ref val)).height); Sprite val5; if (Math.Abs(cached.pixelsPerUnit - num8) <= 0.5f && cached.rect == val && Mathf.Abs(cached.pivot.x - val4.x) <= 0.5f && Mathf.Abs(cached.pivot.y - val4.y) <= 0.5f) { val5 = cached; } else { Sprite val6 = Sprite.Create(cached.texture, val, val2, num8, 0u, (SpriteMeshType)0); ((Object)val6).name = ((Object)cached).name; ((Object)val6).hideFlags = (HideFlags)32; _ourSpriteIds.Add(((Object)val6).GetInstanceID()); _retiredSprites.Add(cached); _loadedSprites[key] = val6; val5 = val6; } if (vanillaTextureName != null && (Object)(object)val5.texture != (Object)null && string.IsNullOrEmpty(((Object)val5.texture).name)) { ((Object)val5.texture).name = vanillaTextureName; } return val5; IL_026a: val2 = val3; num8 = targetPPU; goto IL_026d; } private static void PreCorrectAllPPU() { if (_loadedSprites.Count == 0) { return; } Sprite[] array = Resources.FindObjectsOfTypeAll(); foreach (Sprite val in array) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.texture == (Object)null) && !_ourSpriteIds.Contains(((Object)val).GetInstanceID()) && T2DUtil.IsT2DTexture(((Object)val.texture).name)) { string key = T2DUtil.SpriteKey(T2DUtil.CleanTextureName(((Object)val.texture).name), ((Object)val).name); if (_loadedSprites.TryGetValue(key, out var value) && !((Object)(object)value == (Object)null)) { EnsurePPU(key, value, val.pixelsPerUnit, ((Object)val.texture).name, val); } } } } internal static void HandleLoad(object spriteContainer, Sprite sprite) { //IL_01e4: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Unknown result type (might be due to invalid IL or missing references) //IL_0399: Unknown result type (might be due to invalid IL or missing references) //IL_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_043c: Unknown result type (might be due to invalid IL or missing references) //IL_0440: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Unknown result type (might be due to invalid IL or missing references) //IL_0455: Unknown result type (might be due to invalid IL or missing references) //IL_0459: Unknown result type (might be due to invalid IL or missing references) //IL_0476: Unknown result type (might be due to invalid IL or missing references) //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0290: Unknown result type (might be due to invalid IL or missing references) if (_handling || !HasT2DReplacements || (Object)(object)sprite == (Object)null || (Object)(object)sprite.texture == (Object)null) { return; } _handling = true; try { Sprite val = null; bool flag = false; if (T2DUtil.IsT2DTexture(((Object)sprite.texture).name)) { string text = KeyForSprite(sprite); string text2 = T2DUtil.CleanTextureName(((Object)sprite.texture).name); flag = _coveredCleanNames.Contains(text2); if (_loadedSprites.TryGetValue(text, out var value) && (Object)(object)value != (Object)null && (Object)(object)value.texture != (Object)null) { val = EnsurePPU(text, value, sprite.pixelsPerUnit, ((Object)sprite.texture).name, sprite); } else if (Plugin.Config.LogT2DLookupMisses && _emittedMissKeys.Add(text)) { Plugin.Logger.LogWarning((object)("[T2D-Miss] PRIMARY: spriteName='" + ((Object)sprite).name + "' rawTex='" + ((Object)sprite.texture).name + "' cleanTex='" + text2 + "' " + $"key='{text}' covered={flag} " + $"fallbackHasName={_spriteNameToKey.ContainsKey(((Object)sprite).name)}")); } } if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(((Object)sprite).name) && _spriteNameToKey.TryGetValue(((Object)sprite).name, out var value2) && _loadedSprites.TryGetValue(value2, out var value3) && (Object)(object)value3 != (Object)null && (Object)(object)value3.texture != (Object)null) { val = EnsurePPU(value2, value3, sprite.pixelsPerUnit, ((Object)sprite.texture).name, sprite); } if ((Object)(object)val != (Object)null) { if ((Object)(object)val != (Object)(object)sprite) { ((Object)sprite).hideFlags = (HideFlags)(((Object)sprite).hideFlags | 0x20); if ((Object)(object)sprite.texture != (Object)null) { Texture2D texture = sprite.texture; ((Object)texture).hideFlags = (HideFlags)(((Object)texture).hideFlags | 0x20); } if (Plugin.Config.LogTextureSwaps) { Component val2 = (Component)((spriteContainer is Component) ? spriteContainer : null); string text3 = (((Object)(object)val2 != (Object)null) ? ((Object)val2.gameObject).name : "?"); Vector3 val3 = (((Object)(object)val2 != (Object)null) ? val2.transform.lossyScale : Vector3.one); string arg = ""; Image val4 = (Image)((spriteContainer is Image) ? spriteContainer : null); if (val4 != null) { Rect rect = ((Graphic)val4).rectTransform.rect; arg = $" image(type={val4.type} preserveAspect={val4.preserveAspect} " + $"rt={((Rect)(ref rect)).width:F0}x{((Rect)(ref rect)).height:F0})"; } ManualLogSource logger = Plugin.Logger; string[] obj = new string[13] { "[T2D-Size] '", ((Object)sprite).name, "' on '", text3, "': vanilla ", null, null, null, null, null, null, null, null }; Rect rect2 = sprite.rect; object arg2 = ((Rect)(ref rect2)).width; rect2 = sprite.rect; obj[5] = $"rect={arg2}x{((Rect)(ref rect2)).height} ppu={sprite.pixelsPerUnit} "; obj[6] = $"pivotPx=({sprite.pivot.x:F1},{sprite.pivot.y:F1}) "; Bounds bounds = sprite.bounds; object arg3 = ((Bounds)(ref bounds)).size.x; bounds = sprite.bounds; obj[7] = $"bounds={arg3:F2}x{((Bounds)(ref bounds)).size.y:F2} "; obj[8] = $"tex={((Texture)sprite.texture).width}x{((Texture)sprite.texture).height} → repl "; rect2 = val.rect; object arg4 = ((Rect)(ref rect2)).width; rect2 = val.rect; obj[9] = $"rect={arg4}x{((Rect)(ref rect2)).height} "; obj[10] = $"ppu={val.pixelsPerUnit} "; bounds = val.bounds; object arg5 = ((Bounds)(ref bounds)).size.x; bounds = val.bounds; obj[11] = $"bounds={arg5:F2}x{((Bounds)(ref bounds)).size.y:F2} "; obj[12] = $"scale=({val3.x:F2},{val3.y:F2}){arg}"; logger.LogInfo((object)string.Concat(obj)); } } SetSprite(spriteContainer, val); TrackContainer(spriteContainer); } else if (flag) { TrackContainer(spriteContainer); } } finally { _handling = false; } } private static void SetSprite(object container, Sprite sprite) { SpriteRenderer val = (SpriteRenderer)((container is SpriteRenderer) ? container : null); if (val != null) { val.sprite = sprite; return; } Image val2 = (Image)((container is Image) ? container : null); if (val2 != null) { val2.sprite = sprite; } } private static void TrackContainer(object container) { SpriteRenderer val = (SpriteRenderer)((container is SpriteRenderer) ? container : null); if (val != null) { if (Plugin.ShowDevHub) { _knownRenderers.Add(val); } _replacedSrs.Add(val); return; } Image val2 = (Image)((container is Image) ? container : null); if (val2 != null) { if (Plugin.ShowDevHub) { _knownImages.Add(val2); } _replacedImgs.Add(val2); } } internal static string KeyForSprite(Sprite sprite) { if ((Object)(object)sprite.texture == (Object)null || !T2DUtil.IsT2DTexture(((Object)sprite.texture).name)) { return ((Object)sprite).name; } return _spriteKeyCache.GetValue(sprite, _spriteKeyCreator); } internal static bool TryGetStandaloneReplacement(string textureName, out Texture2D replacement) { replacement = null; if (string.IsNullOrEmpty(textureName)) { return false; } if (_loadedSprites.TryGetValue(textureName, out var value) && (Object)(object)value != (Object)null && (Object)(object)value.texture != (Object)null) { replacement = value.texture; return true; } return false; } internal static void RememberCursorCall(Texture2D texture, Vector2 hotspot, CursorMode mode) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)texture == (Object)null) && !string.IsNullOrWhiteSpace(((Object)texture).name)) { _lastCursorName = ((Object)texture).name; _lastCursorHotspot = hotspot; _lastCursorMode = mode; _lastCursorVanillaTex = texture; ((Object)texture).hideFlags = (HideFlags)(((Object)texture).hideFlags | 0x20); _cursorSubstituted = false; } } internal static void MarkCursorSubstituted() { _cursorSubstituted = true; } public static void RefreshSubstitutedCursor() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) string text = ((!string.IsNullOrEmpty(_lastCursorName)) ? _lastCursorName : "Cursor"); if (TryGetStandaloneReplacement(text, out var replacement)) { Cursor.SetCursor(replacement, _lastCursorHotspot, _lastCursorMode); _cursorSubstituted = true; Plugin.Logger.LogDebug((object)("[T2D-Cursor] Pushed replacement '" + text + "' to OS cursor " + $"({((Texture)replacement).width}x{((Texture)replacement).height}, " + $"hotspot={_lastCursorHotspot}, mode={_lastCursorMode})")); } else { if (!_cursorSubstituted) { return; } Texture2D val = _lastCursorVanillaTex; if ((Object)(object)val == (Object)null && !string.IsNullOrEmpty(_lastCursorName)) { Texture2D[] array = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val2 in array) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name == _lastCursorName) { val = val2; break; } } } Cursor.SetCursor(val, _lastCursorHotspot, _lastCursorMode); _cursorSubstituted = false; Plugin.Logger.LogInfo((object)("[T2D-Cursor] Reverted OS cursor to " + (((Object)(object)val != (Object)null) ? ("vanilla '" + ((Object)val).name + "'") : "system default") + " (no cursor replacement in the active pack set)")); } } public static void RunWatchedSpriteDiagnostic(string watchName) { if (Time.realtimeSinceStartup < _diagNextTime) { return; } _diagNextTime = Time.realtimeSinceStartup + 1f; SpriteRenderer[] array = Resources.FindObjectsOfTypeAll(); foreach (SpriteRenderer val in array) { if (!((Object)(object)val == (Object)null)) { Sprite sprite = val.sprite; if (Match(((Object)val).name) || ((Object)(object)sprite != (Object)null && (Match(((Object)sprite).name) || ((Object)(object)sprite.texture != (Object)null && Match(((Object)sprite.texture).name))))) { EmitSpriteState("SpriteRenderer", ((Object)val).name, ((Component)val).gameObject.activeInHierarchy, sprite, _replacedSrs.Contains(val)); } } } Image[] array2 = Resources.FindObjectsOfTypeAll(); foreach (Image val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { Sprite sprite2 = val2.sprite; if (Match(((Object)val2).name) || ((Object)(object)sprite2 != (Object)null && (Match(((Object)sprite2).name) || ((Object)(object)sprite2.texture != (Object)null && Match(((Object)sprite2.texture).name))))) { EmitSpriteState("Image", ((Object)val2).name, ((Component)val2).gameObject.activeInHierarchy, sprite2, _replacedImgs.Contains(val2)); } } } Renderer[] array3 = Resources.FindObjectsOfTypeAll(); foreach (Renderer val3 in array3) { if ((Object)(object)val3 == (Object)null || val3 is SpriteRenderer) { continue; } Material sharedMaterial = val3.sharedMaterial; Texture val4 = (((Object)(object)sharedMaterial != (Object)null) ? sharedMaterial.mainTexture : null); if (!((Object)(object)val4 == (Object)null) && Match(((Object)val4).name)) { string item = "mat:" + ((Object)val3).name + ":" + ((Object)val4).name; if (_diagSeen.Add(item)) { Plugin.Logger.LogInfo((object)($"[T2D-Diag] path=MATERIAL renderer='{((Object)val3).name}' active={((Component)val3).gameObject.activeInHierarchy} " + "shader='" + (((Object)(object)sharedMaterial.shader != (Object)null) ? ((Object)sharedMaterial.shader).name : "?") + "' tex='" + ((Object)val4).name + "' — material/standalone path; AssetBundle.Unload would drop this and DontUnloadUnusedAsset does NOT protect against it (Memory Rule #3).")); } } } EmitEnrolledFakeNulls(); bool Match(string n) { if (!string.IsNullOrEmpty(n)) { return n.IndexOf(watchName, StringComparison.OrdinalIgnoreCase) >= 0; } return false; } } private static void EmitEnrolledFakeNulls() { foreach (SpriteRenderer replacedSr in _replacedSrs) { if (!((Object)(object)replacedSr == (Object)null) && !((Object)(object)replacedSr.sprite != (Object)null) && ((Component)replacedSr).gameObject.activeInHierarchy && _diagSeen.Add($"enrnull:SR:{((Object)replacedSr).GetInstanceID()}")) { Plugin.Logger.LogInfo((object)("[T2D-Diag] path=ENROLLED-FAKENULL kind=SpriteRenderer obj='" + ((Object)replacedSr).name + "' active=True enrolled=True sprite= — a SpriteRenderer we replaced now holds a null sprite while on-screen (TRANSPARENT). This is the exact failure the name-match sweeps cannot see. Set T2DDiagWatchName to this GO name to also capture the pre-break sprite/texture/cachedRepl state.")); } } foreach (Image replacedImg in _replacedImgs) { if (!((Object)(object)replacedImg == (Object)null) && !((Object)(object)replacedImg.sprite != (Object)null) && ((Component)replacedImg).gameObject.activeInHierarchy && _diagSeen.Add($"enrnull:IMG:{((Object)replacedImg).GetInstanceID()}")) { Plugin.Logger.LogInfo((object)("[T2D-Diag] path=ENROLLED-FAKENULL kind=Image obj='" + ((Object)replacedImg).name + "' active=True enrolled=True sprite= — an Image we replaced now holds a null sprite while on-screen (TRANSPARENT).")); } } } private static void EmitSpriteState(string kind, string objName, bool active, Sprite sprite, bool enrolled) { bool flag = (Object)(object)sprite == (Object)null; string text = (flag ? "" : ((Object)sprite).name); bool flag2 = flag || (Object)(object)sprite.texture == (Object)null; string text2 = (flag2 ? "" : ((Object)sprite.texture).name); string text3 = "n/a"; if (!flag) { string key = KeyForSprite(sprite); text3 = (_loadedSprites.TryGetValue(key, out var value) ? (((Object)(object)value == (Object)null) ? "FAKE-NULL" : "alive") : ((!_spriteNameToKey.TryGetValue(((Object)sprite).name, out var value2) || !_loadedSprites.TryGetValue(value2, out var value3)) ? "no-key" : (((Object)(object)value3 == (Object)null) ? "FAKE-NULL(fallback)" : "alive(fallback)"))); } bool flag3 = !flag2 && T2DUtil.IsT2DTexture(text2); string item = $"{kind}:{objName}:{text}:{flag}:{flag2}:{enrolled}:{text3}"; if (_diagSeen.Add(item)) { Plugin.Logger.LogInfo((object)($"[T2D-Diag] path=SPRITE kind={kind} obj='{objName}' active={active} " + $"enrolled={enrolled} sprite='{text}' spriteFakeNull={flag} " + $"tex='{text2}' texFakeNull={flag2} t2dName={flag3} cachedRepl={text3}")); } } internal static Sprite RebuildLoadedSprite(string key, float ppu) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) string text = ResolveSourceFile(key); if (text == null) { return null; } byte[] array = FileCache.ReadBytes(text); if (array == null) { Plugin.Logger.LogWarning((object)("[T2D-Recovery] '" + key + "' fake-null and source re-read failed (" + text + ").")); return null; } Texture2D val = TexUtil.CreateTextureFromBytes(array, markNonReadable: false); if ((Object)(object)val == (Object)null) { return null; } ((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x20); SkippedTextureIds.Add(((Object)val).GetInstanceID()); int num = key.LastIndexOf('/'); string name = ((num >= 0) ? key.Substring(num + 1) : key); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), (ppu > 0.5f) ? ppu : 100f); ((Object)val2).name = name; ((Object)val2).hideFlags = (HideFlags)32; _ourSpriteIds.Add(((Object)val2).GetInstanceID()); _loadedSprites[key] = val2; Plugin.Logger.LogInfo((object)("[T2D-Recovery] Rebuilt fake-null replacement '" + key + "' from disk.")); return val2; } private static string ResolveSourceFile(string key) { if (!_t2dProviders.TryGetValue(key, out var value)) { return null; } string path = ((value != null) ? Path.Combine(value, "Sprites", "T2D") : Path.Combine(SpriteLoader.LoadPath, "T2D")); int num = key.IndexOf('/'); if (num >= 0) { string path2 = key.Substring(0, num); string text = key.Substring(num + 1); string text2 = Path.Combine(path, path2, text + ".png"); if (!File.Exists(text2)) { return null; } return text2; } string text3 = Path.Combine(path, "_standalone", key + ".png"); if (File.Exists(text3)) { return text3; } string text4 = Path.Combine(path, key + ".png"); if (!File.Exists(text4)) { return null; } return text4; } public static void PruneSceneState() { ReplacedTextureIds.Clear(); SkippedTextureIds.Clear(); ClearSoftSkips(); foreach (Sprite value in _loadedSprites.Values) { if ((Object)(object)value != (Object)null && (Object)(object)value.texture != (Object)null) { SkippedTextureIds.Add(((Object)value.texture).GetInstanceID()); } } InitializedSpriteRenderers.Clear(); InvalidateUninitCache(); _replacedSrs.Clear(); _replacedImgs.Clear(); } public static void ReloadSpritesInScene() { InvalidateSpriteLookupCache(); List list = new List(_loadedSprites.Values); list.AddRange(_retiredSprites); _retiredSprites.Clear(); HashSet hashSet = new HashSet(list); _loadedSprites.Clear(); _spriteNameToKey.Clear(); _t2dProviders.Clear(); _t2dSourceFiles.Clear(); _coveredCleanNames.Clear(); _emittedMissKeys.Clear(); ReplacedTextureIds.Clear(); SkippedTextureIds.Clear(); ClearSoftSkips(); _restoredNames.Clear(); T2DUtil.ClearCleanNameCache(); foreach (Sprite item in list) { if ((Object)(object)item != (Object)null && (Object)(object)item.texture != (Object)null) { SkippedTextureIds.Add(((Object)item.texture).GetInstanceID()); } } _knownRenderers.Clear(); _knownImages.Clear(); InitializedSpriteRenderers.Clear(); InvalidateUninitCache(); _replacedSrs.Clear(); _replacedImgs.Clear(); _lastSeenSpriteIdSr.Clear(); _lastSeenSpriteIdImg.Clear(); _lastLoggedSpriteIdSr.Clear(); _lastLoggedSpriteIdImg.Clear(); _ourSpriteIds.Clear(); PreloadAllTextures(); RegisterFontAtlasIds(); RegisterPostProcessTextureIds(); if (SpritesheetOverrides.Count > 0 || HasStoredOriginals) { Texture2D[] array = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val in array) { if (!((Object)(object)val == (Object)null)) { bool flag = SkippedTextureIds.Contains(((Object)val).GetInstanceID()); if (!TrySwapTexture(val) && !flag) { TryRestoreTexture(val); } } } } SpriteRenderer[] array2 = Resources.FindObjectsOfTypeAll(); Image[] array3 = Resources.FindObjectsOfTypeAll(); SpriteRenderer[] array4 = array2; foreach (SpriteRenderer val2 in array4) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.sprite == (Object)null) && !((Object)(object)val2.sprite.texture == (Object)null)) { HandleLoad(val2, val2.sprite); } } Image[] array5 = array3; foreach (Image val3 in array5) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3.sprite == (Object)null) && !((Object)(object)val3.sprite.texture == (Object)null)) { HandleLoad(val3, val3.sprite); } } PreCorrectAllPPU(); Dictionary vanillaByQual; Dictionary vanillaByName; HashSet ambiguousNames; if (list.Count > 0) { vanillaByQual = new Dictionary(StringComparer.OrdinalIgnoreCase); vanillaByName = new Dictionary(StringComparer.OrdinalIgnoreCase); ambiguousNames = new HashSet(StringComparer.OrdinalIgnoreCase); Sprite[] array6 = Resources.FindObjectsOfTypeAll(); foreach (Sprite val4 in array6) { if ((Object)(object)val4 == (Object)null || hashSet.Contains(val4)) { continue; } if ((Object)(object)val4.texture != (Object)null) { string key = ((Object)val4.texture).name + "/" + ((Object)val4).name; if (!vanillaByQual.ContainsKey(key)) { vanillaByQual[key] = val4; } } if (!vanillaByName.ContainsKey(((Object)val4).name)) { vanillaByName[((Object)val4).name] = val4; } else { ambiguousNames.Add(((Object)val4).name); } } int num = 0; int num2 = 0; _enforcing = true; try { array4 = array2; foreach (SpriteRenderer val5 in array4) { if (!((Object)(object)val5 == (Object)null) && !((Object)(object)val5.sprite == (Object)null) && hashSet.Contains(val5.sprite)) { Sprite value; Sprite val6 = (_loadedSprites.TryGetValue(KeyForSprite(val5.sprite), out value) ? value : FindVanilla(val5.sprite)); if ((Object)(object)val6 != (Object)null) { val5.sprite = val6; num++; continue; } Plugin.Logger.LogWarning((object)("[T2D-Revert] SpriteRenderer '" + ((Object)val5).name + "': old sprite '" + ((Object)val5.sprite).name + "' has no replacement or vanilla match")); num2++; } } array5 = array3; foreach (Image val7 in array5) { if (!((Object)(object)val7 == (Object)null) && !((Object)(object)val7.sprite == (Object)null) && hashSet.Contains(val7.sprite)) { Sprite value2; Sprite val8 = (_loadedSprites.TryGetValue(KeyForSprite(val7.sprite), out value2) ? value2 : FindVanilla(val7.sprite)); if ((Object)(object)val8 != (Object)null) { val7.sprite = val8; num++; continue; } Plugin.Logger.LogWarning((object)("[T2D-Revert] Image '" + ((Object)val7).name + "': old sprite '" + ((Object)val7.sprite).name + "' has no replacement or vanilla match")); num2++; } } } finally { _enforcing = false; } Plugin.Logger.LogDebug((object)$"[T2D-Reload] Revert sweep: {list.Count} old sprites, {vanillaByName.Count} vanilla candidates, {num} reverted, {num2} missed"); if (num2 > 0) { Plugin.Logger.LogWarning((object)$"[T2D-Reload] {num2} sprite(s) could not be reverted — see [T2D-Revert] warnings above for names"); } } HashSet hashSet2 = new HashSet(); array4 = array2; foreach (SpriteRenderer val9 in array4) { if (!((Object)(object)val9 == (Object)null) && !((Object)(object)val9.sprite == (Object)null) && hashSet.Contains(val9.sprite)) { hashSet2.Add(((Object)val9.sprite).GetInstanceID()); } } array5 = array3; foreach (Image val10 in array5) { if (!((Object)(object)val10 == (Object)null) && !((Object)(object)val10.sprite == (Object)null) && hashSet.Contains(val10.sprite)) { hashSet2.Add(((Object)val10.sprite).GetInstanceID()); } } HashSet hashSet3 = new HashSet(); foreach (Sprite item2 in list) { if (!((Object)(object)item2 == (Object)null) && !((Object)(object)item2.texture == (Object)null) && hashSet2.Contains(((Object)item2).GetInstanceID())) { hashSet3.Add(((Object)item2.texture).GetInstanceID()); } } int num3 = 0; int num4 = 0; foreach (Sprite item3 in list) { if ((Object)(object)item3 == (Object)null) { continue; } if (hashSet2.Contains(((Object)item3).GetInstanceID())) { num4++; continue; } if ((Object)(object)item3.texture != (Object)null && !hashSet3.Contains(((Object)item3.texture).GetInstanceID())) { Object.Destroy((Object)(object)item3.texture); } Object.Destroy((Object)(object)item3); num3++; } if (num4 > 0) { Plugin.Logger.LogWarning((object)($"[T2D-Reload] Destroy skipped for {num4} old sprite(s) still held by live renderers; " + $"{num3} destroyed. Skip is intentional (avoids fake-null raycast hazard); " + "a non-zero skip count means the revert sweep couldn't match those sprite names to new/vanilla candidates.")); } Sprite FindVanilla(Sprite old) { if ((Object)(object)old.texture != (Object)null && vanillaByQual.TryGetValue(((Object)old.texture).name + "/" + ((Object)old).name, out var value3)) { return value3; } if (!ambiguousNames.Contains(((Object)old).name) && vanillaByName.TryGetValue(((Object)old).name, out var value4)) { return value4; } return null; } } public static void InvalidateSpritesheet(string texName) { string text = T2DUtil.CleanTextureName(texName); SpritesheetOverrides.Remove(texName); ReplacedTextureIds.Clear(); SkippedTextureIds.Clear(); ClearSoftSkips(); string prefix = text + "/"; foreach (string item in _loadedSprites.Keys.Where((string k) => k.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList()) { _loadedSprites.Remove(item); } } private static bool IsCacheRefreshDue() { if (!_cacheInvalidated) { return Time.realtimeSinceStartup >= _nextCacheRefreshTime; } return true; } private static void MarkCacheRefreshed() { _nextCacheRefreshTime = Time.realtimeSinceStartup + UninitRefreshSeconds; _cacheInvalidated = false; DevProfiler.RecordUninitRefresh(); } public static void InvalidateUninitCache() { _cacheInvalidated = true; } public static void NotifySetterCall() { _setterCallsThisFrame++; } public static void OnSceneLoaded() { InitializedSpriteRenderers.Clear(); _cacheInvalidated = true; _lastSeenSpriteIdSr.Clear(); _lastSeenSpriteIdImg.Clear(); _lastLoggedSpriteIdSr.Clear(); _lastLoggedSpriteIdImg.Clear(); } public static void AdvanceSetterTracking() { _prevFrameSetterCalls = _setterCallsThisFrame; _setterCallsThisFrame = 0; } public static void ScanForLogTriggers() { if (!IsT2DLogActive) { return; } if (!HasT2DReplacements && (_cachedSrArray.Length == 0 || IsCacheRefreshDue())) { _cachedSrArray = Object.FindObjectsByType((FindObjectsSortMode)0); _cachedImgArray = Object.FindObjectsByType((FindObjectsSortMode)0); MarkCacheRefreshed(); } SpriteRenderer[] cachedSrArray = _cachedSrArray; foreach (SpriteRenderer val in cachedSrArray) { if ((Object)(object)val == (Object)null) { continue; } Sprite sprite = val.sprite; if ((Object)(object)sprite == (Object)null) { continue; } Texture2D texture = sprite.texture; if ((Object)(object)texture == (Object)null) { continue; } string name = ((Object)texture).name; if (!string.IsNullOrEmpty(name) && T2DUtil.IsT2DTexture(name)) { int instanceID = ((Object)val).GetInstanceID(); int instanceID2 = ((Object)sprite).GetInstanceID(); if (!_lastLoggedSpriteIdSr.TryGetValue(instanceID, out var value) || value != instanceID2) { _lastLoggedSpriteIdSr[instanceID] = instanceID2; RaiseT2DTrigger(name, T2DUtil.CleanTextureName(name), ((Object)sprite).name); } } } Image[] cachedImgArray = _cachedImgArray; foreach (Image val2 in cachedImgArray) { if ((Object)(object)val2 == (Object)null) { continue; } Sprite sprite2 = val2.sprite; if ((Object)(object)sprite2 == (Object)null) { continue; } Texture2D texture2 = sprite2.texture; if ((Object)(object)texture2 == (Object)null) { continue; } string name2 = ((Object)texture2).name; if (!string.IsNullOrEmpty(name2) && T2DUtil.IsT2DTexture(name2)) { int instanceID3 = ((Object)val2).GetInstanceID(); int instanceID4 = ((Object)sprite2).GetInstanceID(); if (!_lastLoggedSpriteIdImg.TryGetValue(instanceID3, out var value2) || value2 != instanceID4) { _lastLoggedSpriteIdImg[instanceID3] = instanceID4; RaiseT2DTrigger(name2, T2DUtil.CleanTextureName(name2), ((Object)sprite2).name); } } } } public static void ResetLogScanState() { _lastLoggedSpriteIdSr.Clear(); _lastLoggedSpriteIdImg.Clear(); } public static void CheckForUninitializedSprites() { StitchworkConfig config = Plugin.Config; if ((config != null && config.DisableUninitCheck) || !HasT2DReplacements) { return; } if (IsCacheRefreshDue()) { long num = DevProfiler.SnapshotHeapBytes(); StitchworkConfig config2 = Plugin.Config; int num2 = ((config2 != null && config2.ThoroughT2DEnrollment) ? 1 : 0); _cachedSrArray = Object.FindObjectsByType((FindObjectsInactive)num2, (FindObjectsSortMode)0); long num3 = DevProfiler.SnapshotHeapBytes(); DevProfiler.AddOpAlloc("UninitCheck.FindSR", num3 - num); _cachedImgArray = Object.FindObjectsByType((FindObjectsInactive)num2, (FindObjectsSortMode)0); long num4 = DevProfiler.SnapshotHeapBytes(); DevProfiler.AddOpAlloc("UninitCheck.FindImg", num4 - num3); MarkCacheRefreshed(); } long num5 = DevProfiler.SnapshotHeapBytes(); int num6 = 0; SpriteRenderer[] cachedSrArray = _cachedSrArray; foreach (SpriteRenderer val in cachedSrArray) { if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sprite == (Object)null)) { int instanceID = ((Object)val).GetInstanceID(); if (!InitializedSpriteRenderers.Contains(instanceID)) { val.sprite = val.sprite; InitializedSpriteRenderers.Add(instanceID); num6++; } } } long num7 = DevProfiler.SnapshotHeapBytes(); DevProfiler.AddOpAlloc("UninitCheck.SRloop", num7 - num5); int num8 = 0; Image[] cachedImgArray = _cachedImgArray; foreach (Image val2 in cachedImgArray) { if (!((Object)(object)val2 == (Object)null) && !((Object)(object)val2.sprite == (Object)null)) { int instanceID2 = ((Object)val2).GetInstanceID(); if (!InitializedSpriteRenderers.Contains(instanceID2)) { val2.sprite = val2.sprite; InitializedSpriteRenderers.Add(instanceID2); num8++; } } } long num9 = DevProfiler.SnapshotHeapBytes(); DevProfiler.AddOpAlloc("UninitCheck.Imgloop", num9 - num7); DevProfiler.RecordUninitCounts(_cachedSrArray.Length, _cachedImgArray.Length, num6, num8); } public static void EnforceT2DReplacements() { if (!HasT2DReplacements || (_replacedSrs.Count == 0 && _replacedImgs.Count == 0)) { return; } _enforcing = true; try { foreach (SpriteRenderer replacedSr in _replacedSrs) { if ((Object)(object)replacedSr == (Object)null) { continue; } Sprite sprite = replacedSr.sprite; if ((Object)(object)sprite == (Object)null) { continue; } int instanceID = ((Object)replacedSr).GetInstanceID(); int instanceID2 = ((Object)sprite).GetInstanceID(); if (!_lastSeenSpriteIdSr.TryGetValue(instanceID, out var value) || value != instanceID2) { if (TryGetReplacement(sprite, out var replacement) && (Object)(object)sprite != (Object)(object)replacement) { replacedSr.sprite = replacement; _lastSeenSpriteIdSr[instanceID] = ((Object)replacement).GetInstanceID(); } else { _lastSeenSpriteIdSr[instanceID] = instanceID2; } } } foreach (Image replacedImg in _replacedImgs) { if ((Object)(object)replacedImg == (Object)null) { continue; } Sprite sprite2 = replacedImg.sprite; if ((Object)(object)sprite2 == (Object)null) { continue; } int instanceID3 = ((Object)replacedImg).GetInstanceID(); int instanceID4 = ((Object)sprite2).GetInstanceID(); if (!_lastSeenSpriteIdImg.TryGetValue(instanceID3, out var value2) || value2 != instanceID4) { if (TryGetReplacement(sprite2, out var replacement2) && (Object)(object)sprite2 != (Object)(object)replacement2) { replacedImg.sprite = replacement2; _lastSeenSpriteIdImg[instanceID3] = ((Object)replacement2).GetInstanceID(); } else { _lastSeenSpriteIdImg[instanceID3] = instanceID4; } } } } finally { _enforcing = false; } if (++_enforcePruneCounter >= 300) { _enforcePruneCounter = 0; PruneEnforcementCaches(); } } private static void PruneEnforcementCaches() { HashSet liveSrIds = new HashSet(); _replacedSrs.RemoveWhere(delegate(SpriteRenderer sr) { if ((Object)(object)sr == (Object)null) { return true; } liveSrIds.Add(((Object)sr).GetInstanceID()); return false; }); if (_lastSeenSpriteIdSr.Count > liveSrIds.Count) { List list = new List(); foreach (int key in _lastSeenSpriteIdSr.Keys) { if (!liveSrIds.Contains(key)) { list.Add(key); } } foreach (int item in list) { _lastSeenSpriteIdSr.Remove(item); } } HashSet liveImgIds = new HashSet(); _replacedImgs.RemoveWhere(delegate(Image img) { if ((Object)(object)img == (Object)null) { return true; } liveImgIds.Add(((Object)img).GetInstanceID()); return false; }); if (_lastSeenSpriteIdImg.Count <= liveImgIds.Count) { return; } List list2 = new List(); foreach (int key2 in _lastSeenSpriteIdImg.Keys) { if (!liveImgIds.Contains(key2)) { list2.Add(key2); } } foreach (int item2 in list2) { _lastSeenSpriteIdImg.Remove(item2); } } private static bool TryGetReplacement(Sprite sprite, out Sprite replacement) { if (ResolveOrRebuild(KeyForSprite(sprite), sprite, out replacement)) { return true; } if (_spriteNameToKey.TryGetValue(((Object)sprite).name, out var value) && ResolveOrRebuild(value, sprite, out replacement)) { return true; } replacement = null; return false; } private static bool ResolveOrRebuild(string key, Sprite live, out Sprite replacement) { if (!_loadedSprites.TryGetValue(key, out replacement)) { return false; } if ((Object)(object)replacement != (Object)null) { replacement = EnsurePPU(key, replacement, live.pixelsPerUnit, ((Object)(object)live.texture != (Object)null) ? ((Object)live.texture).name : null, live); return (Object)(object)replacement != (Object)null; } replacement = RebuildLoadedSprite(key, live.pixelsPerUnit); return (Object)(object)replacement != (Object)null; } internal static void ClearSoftSkips() { SoftSkippedTextureIds.Clear(); } private static void InitFontBridge() { if (_fontBridgeInit) { return; } _fontBridgeInit = true; _tmpFontAssetType = AccessTools.TypeByName("TMProOld.TMP_FontAsset"); if (_tmpFontAssetType == null) { Plugin.Logger.LogDebug((object)"[T2D] TMProOld.TMP_FontAsset not resolvable; font-atlas hard-ban skipped"); return; } _fontAssetAtlasMember = ResolveAtlasMember(); if (_fontAssetAtlasMember == null) { Plugin.Logger.LogDebug((object)"[T2D] TMProOld.TMP_FontAsset primary atlas member not resolvable; falling back to name-pattern denylist (IsLikelyFontAtlasName)."); } else { Plugin.Logger.LogInfo((object)("[T2D] TMProOld.TMP_FontAsset primary atlas resolved via " + DescribeMember(_fontAssetAtlasMember))); } _fontAssetAtlasTexturesMember = ResolveFontAssetMember("atlasTextures", "m_AtlasTextures"); if (_fontAssetAtlasTexturesMember == null) { Plugin.Logger.LogDebug((object)"[T2D] TMProOld.TMP_FontAsset.atlasTextures not resolvable — Dynamic-mode spillover atlases will not be hard-banned"); } _fontAssetFallbackTableMember = ResolveFontAssetMember("fallbackFontAssetTable", "fallbackFontAssets"); if (_fontAssetFallbackTableMember == null) { Plugin.Logger.LogDebug((object)"[T2D] TMProOld.TMP_FontAsset fallback list not resolvable — fallback fonts will not be recursively hard-banned"); } } private static MemberInfo ResolveAtlasMember() { string[] array = new string[4] { "atlas", "atlasTexture", "m_AtlasTexture", "_atlas" }; string[] array2 = array; foreach (string name in array2) { PropertyInfo property = _tmpFontAssetType.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null && typeof(Texture2D).IsAssignableFrom(property.PropertyType)) { return property; } } array2 = array; foreach (string name2 in array2) { FieldInfo field = _tmpFontAssetType.GetField(name2, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null && typeof(Texture2D).IsAssignableFrom(field.FieldType)) { return field; } } PropertyInfo[] properties = _tmpFontAssetType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.PropertyType == typeof(Texture2D) && propertyInfo.GetIndexParameters().Length == 0) { return propertyInfo; } } FieldInfo[] fields = _tmpFontAssetType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(Texture2D)) { return fieldInfo; } } return null; } private static string DescribeMember(MemberInfo m) { if (!(m is PropertyInfo propertyInfo)) { if (m is FieldInfo fieldInfo) { return "field '" + fieldInfo.Name + "' (type " + fieldInfo.FieldType.Name + ")"; } return m?.Name ?? ""; } return "property '" + propertyInfo.Name + "' (type " + propertyInfo.PropertyType.Name + ")"; } private static MemberInfo ResolveFontAssetMember(string propName, string fieldName) { PropertyInfo property = _tmpFontAssetType.GetProperty(propName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property; } return _tmpFontAssetType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } private static object ReadMember(MemberInfo m, object target) { if (m is PropertyInfo propertyInfo) { return propertyInfo.GetValue(target); } if (m is FieldInfo fieldInfo) { return fieldInfo.GetValue(target); } return null; } internal static bool IsLikelyFontAtlasName(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.Length >= 6 && name.EndsWith(" Atlas", StringComparison.OrdinalIgnoreCase)) { return true; } if (name.IndexOf("font", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (name.IndexOf("SDF", StringComparison.OrdinalIgnoreCase) >= 0) { return true; } string[] array = new string[10] { "Noto", "SourceHan", "Liberation", "Roboto", "OpenSans", "Arial", "Helvetica", "Lato", "Tahoma", "Verdana" }; foreach (string value in array) { if (name.StartsWith(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } internal static bool IsLikelyEngineBuiltin(string name) { if (string.IsNullOrEmpty(name)) { return false; } if (name.Length > 5 && (name[0] == 'U' || name[0] == 'u') && name.StartsWith("Unity", StringComparison.OrdinalIgnoreCase)) { char c = name[5]; if (c >= 'A' && c <= 'Z') { return true; } } if (name.StartsWith("tk2dCamera ", StringComparison.OrdinalIgnoreCase)) { return true; } if (name.StartsWith("Default-", StringComparison.OrdinalIgnoreCase)) { return true; } if (name.Equals("Default Sprite Asset", StringComparison.OrdinalIgnoreCase)) { return true; } return false; } internal static bool IsKnownBuiltinByNameAndDim(string name, int w, int h) { if (string.IsNullOrEmpty(name)) { return false; } switch (name) { case "button": case "button active": case "button hover": case "textfield": case "textfield hover": case "vertical scrollbar thumb": case "horizontal scrollbar thumb": if (w == 12) { return h == 12; } return false; case "vertical scrollbar": if (w == 15) { return h == 18; } return false; case "horizontal scrollbar": if (w == 18) { return h == 15; } return false; case "toggle": if (w == 20) { return h == 20; } return false; case "window": case "box": if (w == 16) { return h == 16; } return false; default: return false; } } internal static int RegisterFontAtlasIds() { InitFontBridge(); if (_tmpFontAssetType == null || _fontAssetAtlasMember == null) { return 0; } int num = 0; HashSet visited = new HashSet(); Object[] array = Resources.FindObjectsOfTypeAll(_tmpFontAssetType); foreach (Object asset in array) { num += BanFontAssetRecursive(asset, visited); } if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Hard-banned {num} font atlas texture(s) from TrySwapTexture"); } return num; } private static int BanFontAssetRecursive(Object asset, HashSet visited) { if (asset == (Object)null) { return 0; } if (!visited.Add(asset.GetInstanceID())) { return 0; } int num = 0; if (_fontAssetAtlasMember != null) { object obj = ReadMember(_fontAssetAtlasMember, asset); Texture2D val = (Texture2D)((obj is Texture2D) ? obj : null); if (val != null && SkippedTextureIds.Add(((Object)val).GetInstanceID())) { num++; } } if (_fontAssetAtlasTexturesMember != null) { object obj2 = ReadMember(_fontAssetAtlasTexturesMember, asset); if (obj2 is Texture2D[] array) { Texture2D[] array2 = array; foreach (Texture2D val2 in array2) { if ((Object)(object)val2 != (Object)null && SkippedTextureIds.Add(((Object)val2).GetInstanceID())) { num++; } } } else if (obj2 is IEnumerable enumerable) { foreach (object item in enumerable) { Texture2D val3 = (Texture2D)((item is Texture2D) ? item : null); if (val3 != null && SkippedTextureIds.Add(((Object)val3).GetInstanceID())) { num++; } } } } if (_fontAssetFallbackTableMember != null && ReadMember(_fontAssetFallbackTableMember, asset) is IEnumerable enumerable2) { foreach (object item2 in enumerable2) { Object val4 = (Object)((item2 is Object) ? item2 : null); if (val4 != null) { num += BanFontAssetRecursive(val4, visited); } } } return num; } private static void InitPostProcessBridge() { if (_ppBridgeInit) { return; } _ppBridgeInit = true; _ppProfileType = AccessTools.TypeByName("UnityEngine.Rendering.PostProcessing.PostProcessProfile"); if (_ppProfileType == null) { Plugin.Logger.LogDebug((object)"[T2D] PostProcessing.PostProcessProfile not resolvable; LUT bridge skipped (name-pattern denylist still protects via IsLikelyEngineBuiltin tk2dCamera prefix)."); return; } _ppProfileSettingsField = _ppProfileType.GetField("settings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (_ppProfileSettingsField == null) { Plugin.Logger.LogDebug((object)"[T2D] PostProcessProfile.settings field not resolvable; LUT bridge skipped."); _ppProfileType = null; } else { _ppParamOverrideType = AccessTools.TypeByName("UnityEngine.Rendering.PostProcessing.ParameterOverride`1"); Plugin.Logger.LogInfo((object)("[T2D] PostProcessing bridge resolved: profile.settings field present" + ((_ppParamOverrideType != null) ? ", ParameterOverride located" : ""))); } } internal static int RegisterPostProcessTextureIds() { InitPostProcessBridge(); if (_ppProfileType == null) { return 0; } int num = 0; Object[] array = Resources.FindObjectsOfTypeAll(_ppProfileType); foreach (Object val in array) { if (val == (Object)null || !(_ppProfileSettingsField.GetValue(val) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { if (item == null) { continue; } FieldInfo[] fields = item.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { object value = fieldInfo.GetValue(item); if (value == null) { continue; } Texture val2 = (Texture)((value is Texture) ? value : null); if (val2 != null && SkippedTextureIds.Add(((Object)val2).GetInstanceID())) { num++; } FieldInfo fieldInfo2 = fieldInfo.FieldType.GetField("value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? fieldInfo.FieldType.GetField("m_Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo2 != null) { object? value2 = fieldInfo2.GetValue(value); Texture val3 = (Texture)((value2 is Texture) ? value2 : null); if (val3 != null && SkippedTextureIds.Add(((Object)val3).GetInstanceID())) { num++; } } } } } if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Hard-banned {num} post-process texture(s) from TrySwapTexture"); } return num; } private static void CheckTrackingCapAndMaybeFlush() { if (ReplacedTextureIds.Count > 16384 || SkippedTextureIds.Count > 16384 || SoftSkippedTextureIds.Count > 16384) { Plugin.Logger.LogInfo((object)($"[T2D] Tracking sets exceeded cap ({16384}) — flushing " + $"(replaced={ReplacedTextureIds.Count}, hardSkip={SkippedTextureIds.Count}, " + $"softSkip={SoftSkippedTextureIds.Count}). TrySwapTexture will re-evaluate " + "textures on next reference.")); ReplacedTextureIds.Clear(); SkippedTextureIds.Clear(); SoftSkippedTextureIds.Clear(); } } internal static bool TryGetOriginalPngBytes(string texName, out byte[] png) { return _originalTextureData.TryGetValue(texName, out png); } internal static bool TrySwapTexture(Texture2D tex) { if ((Object)(object)tex == (Object)null) { return false; } CheckTrackingCapAndMaybeFlush(); int instanceID = ((Object)tex).GetInstanceID(); if (ReplacedTextureIds.Contains(instanceID)) { return true; } if (SkippedTextureIds.Contains(instanceID)) { return false; } if (SoftSkippedTextureIds.Contains(instanceID)) { return false; } if (string.IsNullOrEmpty(((Object)tex).name)) { SkippedTextureIds.Add(instanceID); return false; } if (IsLikelyFontAtlasName(((Object)tex).name)) { SkippedTextureIds.Add(instanceID); if (Plugin.Config.LogTextureSwaps) { Plugin.Logger.LogInfo((object)("[T2D] denylist refused swap on '" + ((Object)tex).name + "' " + $"({((Texture)tex).width}x{((Texture)tex).height}) — name matches font-atlas pattern.")); } return false; } if (IsLikelyEngineBuiltin(((Object)tex).name) || IsKnownBuiltinByNameAndDim(((Object)tex).name, ((Texture)tex).width, ((Texture)tex).height)) { SkippedTextureIds.Add(instanceID); if (Plugin.Config.LogTextureSwaps) { Plugin.Logger.LogInfo((object)("[T2D] denylist refused swap on '" + ((Object)tex).name + "' " + $"({((Texture)tex).width}x{((Texture)tex).height}) — engine built-in.")); } return false; } string text = T2DUtil.CleanTextureName(((Object)tex).name); if (!SpritesheetOverrides.TryGetValue(((Object)tex).name, out (byte[], int, int) value) && !SpritesheetOverrides.TryGetValue(text, out value)) { value = default((byte[], int, int)); string text2 = null; if (T2DUtil.IsT2DTexture(((Object)tex).name)) { int num = T2DUtil.AtlasIndexFromRaw(((Object)tex).name); foreach (KeyValuePair spritesheetOverride in SpritesheetOverrides) { if (!string.Equals(T2DUtil.CleanTextureName(spritesheetOverride.Key), text, StringComparison.OrdinalIgnoreCase)) { continue; } int num2 = T2DUtil.AtlasIndexFromRaw(spritesheetOverride.Key); if (num < 0 || num2 < 0 || num == num2) { if (spritesheetOverride.Value.Item2 == ((Texture)tex).width && spritesheetOverride.Value.Item3 == ((Texture)tex).height) { value = spritesheetOverride.Value; text2 = spritesheetOverride.Key; break; } if (T2DUtil.IsT2DTexture(spritesheetOverride.Key) && _staleSheetWarned.Add(spritesheetOverride.Key)) { Plugin.Logger.LogWarning((object)("[T2D] Sheet '" + spritesheetOverride.Key + "' matches '" + text + "' by name but not by size " + $"({spritesheetOverride.Value.Item2}x{spritesheetOverride.Value.Item3} vs runtime {((Texture)tex).width}x{((Texture)tex).height}) — " + "authored for a different game build; NOT applied. The art needs re-authoring against the current atlas.")); } } } } if (value.Item1 == null) { SoftSkippedTextureIds.Add(instanceID); Plugin.Logger.LogDebug((object)("[T2D] Soft-skip '" + ((Object)tex).name + "' (clean='" + text + "', " + $"{((Texture)tex).width}x{((Texture)tex).height}) — no override matched; will retry after next rebuild")); return false; } bool flag = text2 != null && T2DUtil.IsT2DTexture(text2) && !string.Equals(text2, ((Object)tex).name, StringComparison.OrdinalIgnoreCase); Plugin.Logger.LogInfo((object)("[T2D] Fuzzy match for '" + ((Object)tex).name + "' → clean name '" + text + "' " + $"({((Texture)tex).width}x{((Texture)tex).height})" + (flag ? (" — NOTE: sheet file '" + text2 + "' carries a different build's atlas hash. Applied by name+size, but if this art looks scrambled the update repacked the atlas layout — the sheet needs re-authoring (or convert to per-sprite files while a matching build is available).") : ""))); } if (((Texture)tex).width != value.Item2 || ((Texture)tex).height != value.Item3) { if (((Texture)tex).width == 0 || ((Texture)tex).height == 0) { SoftSkippedTextureIds.Add(instanceID); return false; } Plugin.Logger.LogWarning((object)("[T2D] Spritesheet size mismatch for '" + text + "': " + $"runtime texture '{((Object)tex).name}' is {((Texture)tex).width}x{((Texture)tex).height}, " + $"replacement PNG is {value.Item2}x{value.Item3}. " + $"Skipping. (texture ID: {instanceID})")); SkippedTextureIds.Add(instanceID); return false; } if (!_originalTextureData.ContainsKey(((Object)tex).name)) { byte[] array = CaptureTextureAsPng(tex); if (array != null) { _originalTextureData[((Object)tex).name] = array; } } if (ImageConversion.LoadImage(tex, value.Item1)) { ReplacedTextureIds.Add(instanceID); _restoredNames.Remove(((Object)tex).name); string value2; string value3; string arg = (_sheetProviders.TryGetValue(text, out value2) ? (value2 ?? "") : (_sheetProviders.TryGetValue(((Object)tex).name, out value3) ? (value3 ?? "") : "")); string text3 = "[T2D] swap '" + text + "' applied to '" + ((Object)tex).name + "' " + $"({((Texture)tex).width}x{((Texture)tex).height}) from pack '{arg}'"; if (Plugin.Config.LogTextureSwaps) { Plugin.Logger.LogInfo((object)text3); } else { Plugin.Logger.LogDebug((object)text3); } if (Plugin.Config.ConvertSpritesheets) { ConvertSpritesheet(tex, text); } return true; } Plugin.Logger.LogWarning((object)("[T2D] LoadImage failed for '" + text + "' on texture '" + ((Object)tex).name + "'")); SkippedTextureIds.Add(instanceID); return false; } private static bool IsGenericRuntimeName(string name) { if (string.IsNullOrEmpty(name) || name.Length > 12) { return false; } string text = name.ToLowerInvariant(); string[] array = new string[7] { "atlas", "texture", "sheet", "material", "image", "tex", "img" }; string text4; foreach (string text2 in array) { if (!text.StartsWith(text2)) { continue; } string text3 = text.Substring(text2.Length); if (text3.Length == 0) { return true; } bool flag = true; text4 = text3; foreach (char c in text4) { if (c < '0' || c > '9') { flag = false; break; } } if (flag) { return true; } } bool result = true; text4 = text; foreach (char c2 in text4) { if (c2 < '0' || c2 > '9') { result = false; break; } } return result; } private static void BuildSpritesheetOverrides() { Dictionary previousOverrides = new Dictionary(SpritesheetOverrides, StringComparer.OrdinalIgnoreCase); Dictionary previousProviders = new Dictionary(_sheetProviders, StringComparer.OrdinalIgnoreCase); SpritesheetOverrides.Clear(); _sheetProviders.Clear(); HashSet namesAttempted = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (string pluginPackPath in Plugin.PluginPackPaths) { ScanDirectory(PackLayout.T2DSheetsRoot(pluginPackPath), pluginPackPath); if (PackLayout.IsCustomizerPack(pluginPackPath)) { ScanFlatRoot(pluginPackPath); } } ScanDirectory(AtlasLoadPath, null); void ScanDirectory(string dir, string sourcePack) { if (Directory.Exists(dir)) { string[] files = Directory.GetFiles(dir, "*.png", SearchOption.TopDirectoryOnly); for (int i = 0; i < files.Length; i++) { ScanFile(files[i], sourcePack); } } } void ScanFile(string file, string sourcePack) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); if (string.IsNullOrEmpty(fileNameWithoutExtension)) { Plugin.Logger.LogWarning((object)("[T2D] Ignoring '" + file + "' — file has no name; an empty-keyed override would match every unnamed runtime texture.")); } else { namesAttempted.Add(fileNameWithoutExtension); if (SpritesheetOverrides.ContainsKey(fileNameWithoutExtension)) { if (!IOUtil.FileMatchesBytes(file, SpritesheetOverrides[fileNameWithoutExtension].PngData)) { ConflictTracker.Record("t2d-sheet", fileNameWithoutExtension, _sheetProviders.GetValueOrDefault(fileNameWithoutExtension), sourcePack, null, null, file); } } else { bool flag = false; try { byte[] array = File.ReadAllBytes(IOUtil.LongSafe(file)); if (IOUtil.TryReadPngDimensions(array, out var width, out var height)) { SpritesheetOverrides[fileNameWithoutExtension] = (array, width, height); _sheetProviders[fileNameWithoutExtension] = sourcePack; Plugin.Logger.LogDebug((object)$"[T2D] Loaded spritesheet override: '{fileNameWithoutExtension}' ({width}x{height})"); if (IsGenericRuntimeName(fileNameWithoutExtension)) { Plugin.Logger.LogWarning((object)("[T2D] Generic-name override '" + fileNameWithoutExtension + "' from pack '" + (sourcePack ?? "") + "' — this name collides with many runtime textures and risks corrupting unrelated assets at the same dimensions. Recommend re-keying on the hash-stamped sactx atlas name from the original dump.")); } if (IsLikelyEngineBuiltin(fileNameWithoutExtension)) { Plugin.Logger.LogWarning((object)("[T2D] Engine-builtin override '" + fileNameWithoutExtension + "' from pack '" + (sourcePack ?? "") + "' — Unity's '" + fileNameWithoutExtension + "' is a built-in texture shared across the engine, not a game asset. Replacement will be skipped at swap time to avoid affecting unrelated subsystems (color grading, particle defaults, GUI skin, etc.).")); } flag = true; } else { Plugin.Logger.LogWarning((object)("[T2D] Could not read PNG dimensions for spritesheet '" + fileNameWithoutExtension + "' " + $"(pngData.Length={((array != null) ? array.Length : 0)}). " + "Possibly a partial write; preserving previous override if any.")); } } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[T2D] Failed to load spritesheet '" + file + "': " + ex.Message + ". Preserving previous override if any.")); } if (!flag && previousOverrides.TryGetValue(fileNameWithoutExtension, out (byte[], int, int) value)) { SpritesheetOverrides[fileNameWithoutExtension] = value; if (previousProviders.TryGetValue(fileNameWithoutExtension, out var value2)) { _sheetProviders[fileNameWithoutExtension] = value2; } Plugin.Logger.LogInfo((object)("[T2D] Restored previous spritesheet override for '" + fileNameWithoutExtension + "' " + $"({value.Item2}x{value.Item3}) after fresh read failed.")); } } } } void ScanFlatRoot(string packPath) { string[] files = Directory.GetFiles(packPath, "*.png", SearchOption.TopDirectoryOnly); foreach (string text in files) { if (!PackLayout.IsPackagingAsset(text)) { if (IsGenericRuntimeName(Path.GetFileNameWithoutExtension(text))) { Plugin.Logger.LogInfo((object)("[T2D] Flat Customizer skin '" + Path.GetFileName(packPath) + "': '" + Path.GetFileName(text) + "' is a tk2d atlas with no collection attribution — not applied. The addressable form is Spritesheets/[Collection]/[TextureName].png.")); } else { ScanFile(text, packPath); } } } } } internal static void RegisterStandaloneSpritesheet(string textureName, string file, string sourcePack) { if (string.IsNullOrEmpty(textureName)) { Plugin.Logger.LogWarning((object)("[T2D] Ignoring standalone '" + file + "' — file has no name.")); return; } if (SpritesheetOverrides.ContainsKey(textureName)) { if (!IOUtil.FileMatchesBytes(file, SpritesheetOverrides[textureName].PngData)) { ConflictTracker.Record("t2d-sheet", textureName, _sheetProviders.GetValueOrDefault(textureName), sourcePack, "Spritesheets/T2D › Sprites/T2D/_standalone", null, file); } return; } try { byte[] array = File.ReadAllBytes(IOUtil.LongSafe(file)); if (!IOUtil.TryReadPngDimensions(array, out var width, out var height)) { Plugin.Logger.LogWarning((object)("[T2D] _standalone PNG '" + textureName + "' has unreadable dimensions; in-place swap will be skipped.")); return; } SpritesheetOverrides[textureName] = (array, width, height); _sheetProviders[textureName] = sourcePack; Plugin.Logger.LogDebug((object)$"[T2D] Registered standalone spritesheet override: '{textureName}' ({width}x{height})"); } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[T2D] Failed to load standalone PNG '" + file + "': " + ex.Message)); } } private static (int written, int identical) ConvertSpritesheet(Texture2D atlas, string cleanName, bool onlyChanged = false, bool overwrite = false) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Invalid comparison between Unknown and I4 //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Expected O, but got Unknown //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(SpriteDumper.ConvertPath, "T2D", cleanName); RenderTexture temporary = RenderTexture.GetTemporary(((Texture)atlas).width, ((Texture)atlas).height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)atlas, temporary); RenderTexture val = null; Texture2D val2 = null; if (onlyChanged && TryGetOriginalPngBytes(((Object)atlas).name, out var png)) { val2 = new Texture2D(2, 2, (TextureFormat)4, false); if (ImageConversion.LoadImage(val2, png) && ((Texture)val2).width == ((Texture)atlas).width && ((Texture)val2).height == ((Texture)atlas).height) { val = RenderTexture.GetTemporary(((Texture)atlas).width, ((Texture)atlas).height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)val2, val); } } RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; try { int num = 0; int num2 = 0; int num3 = 0; Sprite[] array = Resources.FindObjectsOfTypeAll(); foreach (Sprite val3 in array) { if ((Object)(object)val3 == (Object)null || (Object)(object)val3.texture == (Object)null || ((Object)val3.texture).GetInstanceID() != ((Object)atlas).GetInstanceID() || string.IsNullOrEmpty(((Object)val3).name)) { continue; } string path = Path.Combine(text, ((Object)val3).name + ".png"); if (!overwrite && File.Exists(path)) { continue; } Rect val4 = val3.rect; if (val3.packed) { if ((int)val3.packingMode != 1) { num3++; continue; } val4 = val3.textureRect; } if (((Rect)(ref val4)).width <= 0f || ((Rect)(ref val4)).height <= 0f) { continue; } if ((Object)(object)val != (Object)null) { Texture2D val5 = new Texture2D((int)((Rect)(ref val4)).width, (int)((Rect)(ref val4)).height, (TextureFormat)4, false); Texture2D val6 = new Texture2D((int)((Rect)(ref val4)).width, (int)((Rect)(ref val4)).height, (TextureFormat)4, false); bool flag; try { RenderTexture.active = val; val5.ReadPixels(new Rect(((Rect)(ref val4)).x, ((Rect)(ref val4)).y, ((Rect)(ref val4)).width, ((Rect)(ref val4)).height), 0, 0); val5.Apply(); RenderTexture.active = temporary; val6.ReadPixels(new Rect(((Rect)(ref val4)).x, ((Rect)(ref val4)).y, ((Rect)(ref val4)).width, ((Rect)(ref val4)).height), 0, 0); val6.Apply(); byte[] rawTextureData = val5.GetRawTextureData(); byte[] rawTextureData2 = val6.GetRawTextureData(); flag = rawTextureData.AsSpan().SequenceEqual(rawTextureData2); } finally { Object.Destroy((Object)(object)val5); Object.Destroy((Object)(object)val6); } if (flag) { num2++; continue; } } Texture2D val7 = new Texture2D((int)((Rect)(ref val4)).width, (int)((Rect)(ref val4)).height, (TextureFormat)4, false); try { val7.ReadPixels(new Rect(((Rect)(ref val4)).x, ((Rect)(ref val4)).y, ((Rect)(ref val4)).width, ((Rect)(ref val4)).height), 0, 0); val7.Apply(); IOUtil.EnsureDirectoryExists(text); File.WriteAllBytes(IOUtil.LongSafe(path), ImageConversion.EncodeToPNG(val7)); num++; } finally { Object.Destroy((Object)(object)val7); } } if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Converted spritesheet '{cleanName}' → {num} individual sprites in {text}"); } if (num3 > 0) { Plugin.Logger.LogWarning((object)($"[T2D] Convert '{cleanName}': {num3} tight-packed sprite(s) skipped — " + "their atlas location is undefined (SpritePackingMode.Tight); a wrong crop would be worse than none.")); } return (written: num, identical: num2); } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } } } internal static (int sheets, int written, int identical) ConvertAllActiveSheets(bool onlyChanged) { int num = 0; int num2 = 0; int num3 = 0; Texture2D[] array = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val in array) { if (!((Object)(object)val == (Object)null) && ReplacedTextureIds.Contains(((Object)val).GetInstanceID())) { var (num4, num5) = ConvertSpritesheet(val, T2DUtil.CleanTextureName(((Object)val).name), onlyChanged, overwrite: true); if (num4 + num5 > 0) { num++; num2 += num4; num3 += num5; } } } return (sheets: num, written: num2, identical: num3); } private static byte[] CaptureTextureAsPng(Texture2D tex) { //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) //IL_0060: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_008a: Expected O, but got Unknown if ((Object)(object)tex == (Object)null) { return null; } try { RenderTexture temporary = RenderTexture.GetTemporary(((Texture)tex).width, ((Texture)tex).height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Graphics.Blit((Texture)(object)tex, temporary); RenderTexture active = RenderTexture.active; RenderTexture.active = temporary; Texture2D val = new Texture2D(((Texture)tex).width, ((Texture)tex).height, (TextureFormat)4, false); val.ReadPixels(new Rect(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height), 0, 0); val.Apply(); RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); byte[] array = ImageConversion.EncodeToPNG(val); Object.Destroy((Object)val); Plugin.Logger.LogDebug((object)$"[T2D] Captured {array.Length / 1024} KB original for '{((Object)tex).name}'"); return array; } catch (Exception ex) { Plugin.Logger.LogWarning((object)("[T2D] Failed to capture original for '" + ((Object)tex).name + "': " + ex.Message)); return null; } } internal static bool TryRestoreTexture(Texture2D tex) { if ((Object)(object)tex == (Object)null) { return false; } if (!_originalTextureData.TryGetValue(((Object)tex).name, out var value)) { Plugin.Logger.LogDebug((object)("[T2D] No stored original for '" + ((Object)tex).name + "' — skipping restore")); return false; } if (ImageConversion.LoadImage(tex, value)) { Plugin.Logger.LogDebug((object)("[T2D] Restored vanilla pixels for '" + ((Object)tex).name + "'")); _restoredNames.Add(((Object)tex).name); return true; } Plugin.Logger.LogWarning((object)("[T2D] LoadImage failed while restoring '" + ((Object)tex).name + "'")); return false; } internal static void PruneStaleOriginals() { if (_originalTextureData.Count == 0) { return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); Texture2D[] array = Resources.FindObjectsOfTypeAll(); foreach (Texture2D val in array) { if ((Object)(object)val != (Object)null) { hashSet.Add(((Object)val).name); } } int count = _originalTextureData.Count; foreach (string item in new List(_originalTextureData.Keys)) { if (!hashSet.Contains(item)) { string key = T2DUtil.CleanTextureName(item); if (!SpritesheetOverrides.ContainsKey(item) && !SpritesheetOverrides.ContainsKey(key) && _restoredNames.Contains(item)) { _originalTextureData.Remove(item); _restoredNames.Remove(item); } } } int num = count - _originalTextureData.Count; if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Pruned {num} stale original(s) after scene unload ({_originalTextureData.Count} remaining)"); } } } public static class TextMerger { public sealed class Conflict { public string Sheet; public string Lang; public string Key; public string RootValue; public string PackValue; public int Choice = -1; public string CustomValue = ""; } public sealed class Plan { public string PackName; public string PackPath; public PackSource PackSource; public readonly List Conflicts = new List(); public readonly Dictionary<(string Sheet, string Lang), Dictionary> Additions = new Dictionary<(string, string), Dictionary>(); public readonly Dictionary<(string Sheet, string Lang), Dictionary> RootOnly = new Dictionary<(string, string), Dictionary>(); public readonly Dictionary<(string Sheet, string Lang), List> IdenticalKeys = new Dictionary<(string, string), List>(); public int AddedCount; public int IdenticalCount; public int RootOnlyCount; public int ResolvedCount => Conflicts.Count((Conflict c) => c.Choice == 0 || c.Choice == 1 || (c.Choice == 2 && !string.IsNullOrWhiteSpace(c.CustomValue))); public bool Resolved => ResolvedCount == Conflicts.Count; public bool IsEmpty { get { if (AddedCount == 0 && IdenticalCount == 0) { return Conflicts.Count == 0; } return false; } } } public static Plan Scan(PackInfo pack) { string path = Path.Combine(pack.Path, "Text"); if (!Directory.Exists(path)) { return null; } Plan plan = new Plan { PackName = pack.Name, PackPath = pack.Path, PackSource = pack.Source }; string[] directories = Directory.GetDirectories(path); foreach (string text in directories) { string fileName = Path.GetFileName(text); string[] files = Directory.GetFiles(text, "*.yml"); foreach (string obj in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(obj); Dictionary dictionary = DialogueHandler.ParseSheetFile(obj); Dictionary dictionary2 = DialogueHandler.ParseSheetFile(Path.Combine(DialogueHandler.TextLoadPath, fileName, fileNameWithoutExtension + ".yml")); foreach (KeyValuePair item in dictionary) { if (!dictionary2.TryGetValue(item.Key, out var value)) { (string, string) key = (fileName, fileNameWithoutExtension); if (!plan.Additions.TryGetValue(key, out var value2)) { value2 = (plan.Additions[key] = new Dictionary()); } value2[item.Key] = item.Value; plan.AddedCount++; } else if (value == item.Value) { plan.IdenticalCount++; (string, string) key2 = (fileName, fileNameWithoutExtension); if (!plan.IdenticalKeys.TryGetValue(key2, out var value3)) { value3 = (plan.IdenticalKeys[key2] = new List()); } value3.Add(item.Key); } else { plan.Conflicts.Add(new Conflict { Sheet = fileName, Lang = fileNameWithoutExtension, Key = item.Key, RootValue = value, PackValue = item.Value }); } } foreach (KeyValuePair item2 in dictionary2) { if (!dictionary.ContainsKey(item2.Key)) { (string, string) key3 = (fileName, fileNameWithoutExtension); if (!plan.RootOnly.TryGetValue(key3, out var value4)) { value4 = (plan.RootOnly[key3] = new Dictionary()); } value4[item2.Key] = item2.Value; plan.RootOnlyCount++; } } } string path2 = Path.Combine(DialogueHandler.TextLoadPath, fileName); if (!Directory.Exists(path2)) { continue; } files = Directory.GetFiles(path2, "*.yml"); foreach (string text2 in files) { string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(text2); if (!File.Exists(Path.Combine(text, fileNameWithoutExtension2 + ".yml"))) { Dictionary dictionary5 = DialogueHandler.ParseSheetFile(text2); if (dictionary5.Count != 0) { plan.RootOnly[(fileName, fileNameWithoutExtension2)] = new Dictionary(dictionary5); plan.RootOnlyCount += dictionary5.Count; } } } } plan.Conflicts.Sort(delegate(Conflict a, Conflict b) { int num = string.Compare(a.Sheet, b.Sheet, StringComparison.OrdinalIgnoreCase); if (num != 0) { return num; } num = string.Compare(a.Lang, b.Lang, StringComparison.OrdinalIgnoreCase); return (num == 0) ? string.Compare(a.Key, b.Key, StringComparison.Ordinal) : num; }); return plan; } public static (int Files, int Keys, string Backup) Apply(Plan plan) { Dictionary<(string, string), Dictionary> dictionary = new Dictionary<(string, string), Dictionary>(); foreach (KeyValuePair<(string, string), Dictionary> addition in plan.Additions) { dictionary[addition.Key] = new Dictionary(addition.Value); } foreach (Conflict conflict in plan.Conflicts) { if (conflict.Choice == 1 || (conflict.Choice == 2 && !string.IsNullOrWhiteSpace(conflict.CustomValue))) { string value = ((conflict.Choice == 1) ? conflict.PackValue : conflict.CustomValue); (string, string) key = (conflict.Sheet, conflict.Lang); if (!dictionary.TryGetValue(key, out var value2)) { value2 = (dictionary[key] = new Dictionary()); } value2[conflict.Key] = value; } } if (dictionary.Count == 0) { return (Files: 0, Keys: 0, Backup: null); } string text = null; string textLoadPath = DialogueHandler.TextLoadPath; if (Directory.Exists(textLoadPath)) { text = $"{textLoadPath}.backup-{DateTime.Now:yyyyMMdd-HHmmss}"; CopyTree(textLoadPath, text); } int num = 0; int num2 = 0; foreach (KeyValuePair<(string, string), Dictionary> item3 in dictionary) { (string, string) key2 = item3.Key; string item = key2.Item1; string item2 = key2.Item2; Dictionary value3 = item3.Value; string text2 = Path.Combine(textLoadPath, item); IOUtil.EnsureDirectoryExists(text2); string path = Path.Combine(text2, item2 + ".yml"); List list = new List(); Dictionary dictionary3 = new Dictionary(value3); if (File.Exists(path)) { string[] array = File.ReadAllLines(path); foreach (string text3 in array) { if (!text3.StartsWith("#") && !string.IsNullOrWhiteSpace(text3)) { int num3 = text3.IndexOf(':'); if (num3 != -1) { string text4 = text3.Substring(0, num3).Trim(); if (dictionary3.TryGetValue(text4, out var value4)) { list.Add(text4 + ": " + EscapeValue(value4)); dictionary3.Remove(text4); continue; } } } list.Add(text3); } } else { list.Add($"# merged from pack '{plan.PackName}' — {DateTime.Now:yyyy-MM-dd}"); } foreach (KeyValuePair item4 in dictionary3.OrderBy, string>((KeyValuePair k) => k.Key, StringComparer.OrdinalIgnoreCase)) { list.Add(item4.Key + ": " + EscapeValue(item4.Value)); } File.WriteAllLines(path, list); num++; num2 += value3.Count; } Plugin.Logger.LogInfo((object)($"[TextMerger] Merged pack '{plan.PackName}' → working folder: {num2} key(s) across " + $"{num} file(s) ({plan.AddedCount} added, {plan.Conflicts.Count} conflict(s) resolved). " + "Backup: " + (text ?? "none needed"))); return (Files: num, Keys: num2, Backup: text); } public static (int Files, int Keys, int Removed, string Backup) ApplyToPack(Plan plan) { if (plan.PackSource == PackSource.Customizer) { Plugin.Logger.LogWarning((object)"[TextMerger] Push refused: Customizer packs are read-only."); return (Files: 0, Keys: 0, Removed: 0, Backup: null); } Dictionary<(string, string), Dictionary> dictionary = new Dictionary<(string, string), Dictionary>(); foreach (KeyValuePair<(string, string), Dictionary> item5 in plan.RootOnly) { dictionary[item5.Key] = new Dictionary(item5.Value); } foreach (Conflict conflict in plan.Conflicts) { if (conflict.Choice == 0 || (conflict.Choice == 2 && !string.IsNullOrWhiteSpace(conflict.CustomValue))) { string value = ((conflict.Choice == 0) ? conflict.RootValue : conflict.CustomValue); (string, string) key = (conflict.Sheet, conflict.Lang); if (!dictionary.TryGetValue(key, out var value2)) { value2 = (dictionary[key] = new Dictionary()); } value2[conflict.Key] = value; } } Dictionary<(string Sheet, string Lang), HashSet> removals = new Dictionary<(string, string), HashSet>(); foreach (Conflict conflict2 in plan.Conflicts) { if (conflict2.Choice == 0 || conflict2.Choice == 1 || (conflict2.Choice == 2 && !string.IsNullOrWhiteSpace(conflict2.CustomValue))) { MarkRemoval(conflict2.Sheet, conflict2.Lang, conflict2.Key); } } foreach (KeyValuePair<(string, string), Dictionary> item6 in plan.RootOnly) { foreach (string key4 in item6.Value.Keys) { MarkRemoval(item6.Key.Item1, item6.Key.Item2, key4); } } foreach (KeyValuePair<(string, string), List> identicalKey in plan.IdenticalKeys) { foreach (string item7 in identicalKey.Value) { MarkRemoval(identicalKey.Key.Item1, identicalKey.Key.Item2, item7); } } if (dictionary.Count == 0 && removals.Count == 0) { return (Files: 0, Keys: 0, Removed: 0, Backup: null); } string text = DateTime.Now.ToString("yyyyMMdd-HHmmss"); string text2 = Path.Combine(plan.PackPath, "Text"); string textLoadPath = DialogueHandler.TextLoadPath; string text3 = null; if (Directory.Exists(text2)) { text3 = text2 + ".backup-" + text; CopyTree(text2, text3); } if (Directory.Exists(textLoadPath)) { CopyTree(textLoadPath, textLoadPath + ".backup-" + text); } int num = 0; int num2 = 0; foreach (KeyValuePair<(string, string), Dictionary> item8 in dictionary) { (string, string) key2 = item8.Key; string item = key2.Item1; string item2 = key2.Item2; Dictionary value3 = item8.Value; string text4 = Path.Combine(text2, item); IOUtil.EnsureDirectoryExists(text4); string path = Path.Combine(text4, item2 + ".yml"); Dictionary dictionary3 = new Dictionary(value3); List list = (File.Exists(path) ? new List(File.ReadAllLines(path)) : new List()); List list2 = new List(); for (int i = 0; i < list.Count; i++) { string text5 = KeyOf(list[i]); if (text5 != null) { if (dictionary3.TryGetValue(text5, out var value4)) { list[i] = text5 + ": " + EscapeValue(value4); dictionary3.Remove(text5); } list2.Add(text5); } } if (dictionary3.Count > 0) { List> list3 = dictionary3.OrderBy, string>((KeyValuePair k) => k.Key, StringComparer.OrdinalIgnoreCase).ToList(); bool flag = list2.Count > 0; for (int num3 = 1; num3 < list2.Count && flag; num3++) { if (string.Compare(list2[num3 - 1], list2[num3], StringComparison.OrdinalIgnoreCase) > 0) { flag = false; } } if (flag) { List list4 = new List(list.Count + list3.Count); int num4 = 0; foreach (string item9 in list) { string text6 = KeyOf(item9); if (text6 != null) { for (; num4 < list3.Count && string.Compare(list3[num4].Key, text6, StringComparison.OrdinalIgnoreCase) < 0; num4++) { list4.Add(list3[num4].Key + ": " + EscapeValue(list3[num4].Value)); } } list4.Add(item9); } for (; num4 < list3.Count; num4++) { list4.Add(list3[num4].Key + ": " + EscapeValue(list3[num4].Value)); } list = list4; } else { list.Add($"# pushed from working folder — {DateTime.Now:yyyy-MM-dd}"); foreach (KeyValuePair item10 in list3) { list.Add(item10.Key + ": " + EscapeValue(item10.Value)); } } } File.WriteAllLines(path, list); num++; num2 += value3.Count; } int num5 = 0; foreach (KeyValuePair<(string, string), HashSet> item11 in removals) { (string, string) key3 = item11.Key; string item3 = key3.Item1; string item4 = key3.Item2; string path2 = Path.Combine(textLoadPath, item3, item4 + ".yml"); if (!File.Exists(path2)) { continue; } List list5 = new List(); string[] array = File.ReadAllLines(path2); foreach (string text7 in array) { string text8 = KeyOf(text7); if (text8 != null && item11.Value.Contains(text8)) { num5++; } else { list5.Add(text7); } } if (list5.Any((string l) => !string.IsNullOrWhiteSpace(l))) { File.WriteAllLines(path2, list5); } else { File.Delete(path2); } } Plugin.Logger.LogInfo((object)($"[TextMerger] Pushed → pack '{plan.PackName}': {num2} key(s) across {num} file(s); " + string.Format("{0} key(s) moved out of the working folder. Backup: {1}", num5, text3 ?? "none needed"))); return (Files: num, Keys: num2, Removed: num5, Backup: text3); void MarkRemoval(string sheet, string lang, string item5) { if (!removals.TryGetValue((sheet, lang), out var value5)) { value5 = (removals[(sheet, lang)] = new HashSet(StringComparer.Ordinal)); } value5.Add(item5); } } private static string KeyOf(string line) { if (line.StartsWith("#") || string.IsNullOrWhiteSpace(line)) { return null; } int num = line.IndexOf(':'); if (num != -1) { return line.Substring(0, num).Trim(); } return null; } private static string EscapeValue(string v) { return "\"" + v.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; } private static void CopyTree(string src, string dst) { Directory.CreateDirectory(dst); string[] directories = Directory.GetDirectories(src, "*", SearchOption.AllDirectories); for (int i = 0; i < directories.Length; i++) { Directory.CreateDirectory(directories[i].Replace(src, dst)); } directories = Directory.GetFiles(src, "*", SearchOption.AllDirectories); foreach (string obj in directories) { File.Copy(obj, obj.Replace(src, dst), overwrite: true); } } } public class VideoHandler { public static Dictionary VideoFileMap = new Dictionary(StringComparer.OrdinalIgnoreCase); public static bool ReloadVideos = false; public static readonly string[] KnownCinematics = new string[26] { "act_03_intro", "bellbeast_travel_children_v3", "bellbeast_travel_children_v3_30_fps", "bellbeast_travel_normal_v3", "bellbeast_travel_normal_v3_30_fps", "bellhart_intro", "city_reveal", "city_reveal_inner", "diving_bell", "doctor_cure", "ending_a", "ending_b", "ending_c", "ending_d", "ending_e", "farsight", "intro_cinematic", "lace_battle", "last_dive", "mr_mushroom", "pinstress_battle", "plinney_sharpen", "plinney_sharpen_oil", "red_memory_ritual", "seamstress_flash", "slab_cinematic" }; internal static readonly string[] SupportedExtensions = new string[9] { ".mp4", ".webm", ".ogv", ".mov", ".avi", ".m4v", ".mpg", ".mpeg", ".wmv" }; private static readonly FieldInfo _ccaCurves = AccessTools.Field(typeof(ColorCorrectionCurvesAnimator), "curves"); private static readonly FieldInfo _ccaPlayer = AccessTools.Field(typeof(ColorCorrectionCurvesAnimator), "getDurationFromCinematic"); public static string VideoLoadPath => Path.Combine(Plugin.BasePath, "Videos"); public static event Action OnCinematicTriggered; public static void Reload() { VideoFileMap.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { ScanDir(PackLayout.VideosRoot(pluginPackPath)); } ScanDir(VideoLoadPath); Plugin.Logger.LogDebug((object)$"[VideoHandler] Reload: {VideoFileMap.Count} video replacement(s) found"); static void ScanDir(string dir) { if (Directory.Exists(dir)) { string[] files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories); foreach (string text in files) { string value = Path.GetExtension(text).ToLowerInvariant(); if (Array.IndexOf(SupportedExtensions, value) >= 0) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (!VideoFileMap.ContainsKey(fileNameWithoutExtension)) { VideoFileMap[fileNameWithoutExtension] = "file:///" + text; } } } } } } public static void ApplyPatches(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Constructor(typeof(EmbeddedCinematicVideoPlayer), new Type[1] { typeof(CinematicVideoPlayerConfig) }, false), (HarmonyMethod)null, new HarmonyMethod(typeof(VideoHandler), "ConstructorPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(EmbeddedCinematicVideoPlayer), "PlayVideo", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(VideoHandler), "PlayVideoPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(EmbeddedCinematicVideoPlayer), "Update", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(VideoHandler), "UpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.PropertyGetter(typeof(CinematicSequence), "VideoLength"), (HarmonyMethod)null, new HarmonyMethod(typeof(VideoHandler), "VideoLengthPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(ColorCorrectionCurvesAnimator), "DoPlay", (Type[])null, (Type[])null), new HarmonyMethod(typeof(VideoHandler), "DoPlayPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(CinematicPlayer), "StartVideo", (Type[])null, (Type[])null), new HarmonyMethod(typeof(VideoHandler), "StartVideoPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static string FindVideoFile(string name) { if (VideoFileMap.TryGetValue(name, out var value)) { return value; } foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = PackLayout.VideosRoot(pluginPackPath); if (Directory.Exists(path)) { string[] array = (from f in Directory.GetFiles(path, name + ".*", SearchOption.AllDirectories) where Array.IndexOf(SupportedExtensions, Path.GetExtension(f).ToLowerInvariant()) >= 0 select f).ToArray(); if (array.Any()) { string text = "file:///" + array[0]; VideoFileMap[name] = text; return text; } } } string[] files = Directory.GetFiles(VideoLoadPath, name + ".*", SearchOption.AllDirectories); if (files.Any()) { string text2 = "file:///" + files[0]; VideoFileMap[name] = text2; return text2; } VideoFileMap[name] = null; return null; } private static void StartVideoPrefix(CinematicPlayer __instance) { if (__instance.videoClip.VideoFileName != null && FindVideoFile(__instance.videoClip.VideoFileName) != null) { __instance.additionalAudio = null; } } private static void UpdatePostfix(EmbeddedCinematicVideoPlayer __instance) { if (FindVideoFile(((CinematicVideoPlayer)__instance).Config.VideoReference.VideoFileName) != null) { Platform.Current.RestoreFrameRate(); } if (Plugin.Config.VideoSkipFrameOnDrop) { VideoPlayer videoPlayer = __instance.videoPlayer; if ((Object)(object)videoPlayer != (Object)null && !videoPlayer.skipOnDrop) { videoPlayer.skipOnDrop = true; } } } private static bool DoPlayPrefix(ColorCorrectionCurvesAnimator __instance) { if (!Plugin.Config.VideoSkipColorGrading) { return true; } object? obj = _ccaPlayer?.GetValue(__instance); CinematicPlayer val = (CinematicPlayer)((obj is CinematicPlayer) ? obj : null); if ((Object)(object)val == (Object)null || (Object)(object)val.videoClip == (Object)null) { return true; } string videoFileName = val.videoClip.VideoFileName; if (FindVideoFile(videoFileName) == null) { return true; } object? obj2 = _ccaCurves?.GetValue(__instance); ColorCorrectionCurves val2 = (ColorCorrectionCurves)((obj2 is ColorCorrectionCurves) ? obj2 : null); if ((Object)(object)val2 != (Object)null) { val2.saturation = 1f; val2.redChannel = AnimationCurve.Linear(0f, 0f, 1f, 1f); val2.greenChannel = AnimationCurve.Linear(0f, 0f, 1f, 1f); val2.blueChannel = AnimationCurve.Linear(0f, 0f, 1f, 1f); val2.UpdateParameters(); val2.UpdateMaterial(); } Plugin.Logger.LogInfo((object)("[Video] '" + videoFileName + "': cinematic colour-grade animation skipped for the replacement (VideoSkipColorGrading).")); return false; } private static void VideoLengthPostfix(CinematicSequence __instance, ref float __result) { CinematicVideoPlayer videoPlayer = __instance.VideoPlayer; EmbeddedCinematicVideoPlayer val = (EmbeddedCinematicVideoPlayer)(object)((videoPlayer is EmbeddedCinematicVideoPlayer) ? videoPlayer : null); if (val != null && (Object)(object)val.videoPlayer != (Object)null && FindVideoFile(((CinematicVideoPlayer)val).Config.VideoReference.VideoFileName) != null && val.videoPlayer.length > 0.0) { __result = (float)val.videoPlayer.length; } } private static void ConstructorPostfix(EmbeddedCinematicVideoPlayer __instance, CinematicVideoPlayerConfig config) { string videoFileName = config.VideoReference.VideoFileName; string text = FindVideoFile(videoFileName); VideoHandler.OnCinematicTriggered?.Invoke(videoFileName, text != null); if (text == null) { return; } __instance.videoPlayer.url = text; __instance.videoPlayer.clip = null; __instance.videoPlayer.playbackSpeed = 1f; if (Object.op_Implicit((Object)(object)config.AudioSource) && !Plugin.Config.VideoAudioDirect) { AudioSource val = ((Component)config.AudioSource).gameObject.AddComponent(); val.playOnAwake = false; __instance.videoPlayer.audioOutputMode = (VideoAudioOutputMode)1; __instance.videoPlayer.SetTargetAudioSource((ushort)0, val); __instance.videoPlayer.controlledAudioTrackCount = 1; val.volume = 1f; } else { if (!Plugin.Config.VideoAudioDirect) { Plugin.Logger.LogWarning((object)("Using custom video file '" + config.VideoReference.VideoFileName + "' with direct audio output. This may result in no audio being played.")); } else { Plugin.Logger.LogInfo((object)("[Video] '" + config.VideoReference.VideoFileName + "': Direct audio output (VideoAudioDirect).")); } __instance.videoPlayer.audioOutputMode = (VideoAudioOutputMode)2; } __instance.videoPlayer.Prepare(); } private static void PlayVideoPostfix(EmbeddedCinematicVideoPlayer __instance) { LogGate.SuppressForCinematic(__instance.videoPlayer, ((CinematicVideoPlayer)__instance).Config.VideoReference.VideoFileName); if (FindVideoFile(((CinematicVideoPlayer)__instance).Config.VideoReference.VideoFileName) != null && !__instance.isSeeking) { ((CinematicVideoPlayer)__instance).StopAudio(); } } } public class SpriteFileWatcher : IDisposable { public FileSystemWatcher SpriteWatcher; public FileSystemWatcher AtlasWatcher; public List PackSpriteWatchers = new List(); public List PackAtlasWatchers = new List(); public static volatile bool ReloadSprites; public static volatile bool ReloadT2DSprites; private const int DebounceMs = 100; private static long _spriteDeadlineTicks; private static long _t2dSpriteDeadlineTicks; private static void DeferReloadSprites() { Interlocked.Exchange(ref _spriteDeadlineTicks, DateTime.UtcNow.AddMilliseconds(100.0).Ticks); } private static void DeferReloadT2DSprites() { Interlocked.Exchange(ref _t2dSpriteDeadlineTicks, DateTime.UtcNow.AddMilliseconds(100.0).Ticks); } public static void Tick() { long ticks = DateTime.UtcNow.Ticks; long num = Interlocked.Read(in _spriteDeadlineTicks); if (num != 0L && ticks >= num) { Interlocked.Exchange(ref _spriteDeadlineTicks, 0L); ReloadSprites = true; } long num2 = Interlocked.Read(in _t2dSpriteDeadlineTicks); if (num2 != 0L && ticks >= num2) { Interlocked.Exchange(ref _t2dSpriteDeadlineTicks, 0L); ReloadT2DSprites = true; } } public SpriteFileWatcher() { SpriteWatcher = CreateSpriteWatcher(SpriteLoader.LoadPath); AtlasWatcher = CreateAtlasWatcher(SpriteLoader.AtlasLoadPath); RebuildPackPngWatchers(); } public void RebuildPackPngWatchers() { foreach (FileSystemWatcher packSpriteWatcher in PackSpriteWatchers) { packSpriteWatcher.EnableRaisingEvents = false; packSpriteWatcher.Dispose(); } PackSpriteWatchers.Clear(); foreach (FileSystemWatcher packAtlasWatcher in PackAtlasWatchers) { packAtlasWatcher.EnableRaisingEvents = false; packAtlasWatcher.Dispose(); } PackAtlasWatchers.Clear(); foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = Path.Combine(pluginPackPath, "Sprites"); if (Directory.Exists(path)) { PackSpriteWatchers.Add(CreateSpriteWatcher(path)); } string path2 = PackLayout.SheetsRoot(pluginPackPath); if (Directory.Exists(path2)) { PackAtlasWatchers.Add(CreateAtlasWatcher(path2)); } } Plugin.Logger.LogDebug((object)("[SpriteFileWatcher] Rebuilt pack PNG watchers: " + $"{PackSpriteWatchers.Count} sprites dir(s), {PackAtlasWatchers.Count} sheets dir(s)")); } private FileSystemWatcher CreateSpriteWatcher(string path) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.Filter = "*.png"; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.InternalBufferSize = 65536; fileSystemWatcher.Changed += OnSpriteChanged; fileSystemWatcher.Created += OnSpriteChanged; fileSystemWatcher.Deleted += OnSpriteChanged; fileSystemWatcher.Renamed += OnSpriteChanged; fileSystemWatcher.Error += OnWatcherOverflow; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } private FileSystemWatcher CreateAtlasWatcher(string path) { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(); fileSystemWatcher.Path = path; fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.Filter = "*.png"; fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite; fileSystemWatcher.InternalBufferSize = 65536; fileSystemWatcher.Changed += OnAtlasChanged; fileSystemWatcher.Created += OnAtlasChanged; fileSystemWatcher.Deleted += OnAtlasChanged; fileSystemWatcher.Renamed += OnAtlasChanged; fileSystemWatcher.Error += OnWatcherOverflow; fileSystemWatcher.EnableRaisingEvents = true; return fileSystemWatcher; } private void OnWatcherOverflow(object sender, ErrorEventArgs e) { Plugin.Logger.LogWarning((object)("[SpriteFileWatcher] Watcher buffer overflow (" + e.GetException()?.Message + ") — events lost; raising full tk2d + T2D reloads.")); DeferReloadSprites(); DeferReloadT2DSprites(); } public void Dispose() { SpriteWatcher.EnableRaisingEvents = false; SpriteWatcher.Dispose(); AtlasWatcher.EnableRaisingEvents = false; AtlasWatcher.Dispose(); foreach (FileSystemWatcher packSpriteWatcher in PackSpriteWatchers) { packSpriteWatcher.EnableRaisingEvents = false; packSpriteWatcher.Dispose(); } PackSpriteWatchers.Clear(); foreach (FileSystemWatcher packAtlasWatcher in PackAtlasWatchers) { packAtlasWatcher.EnableRaisingEvents = false; packAtlasWatcher.Dispose(); } PackAtlasWatchers.Clear(); } private void OnSpriteChanged(object sender, FileSystemEventArgs e) { Plugin.Logger.LogDebug((object)$"[FileWatcher] Sprite file event: {e.ChangeType} — {e.FullPath}"); string relativePath = Path.GetRelativePath((sender as FileSystemWatcher)?.Path ?? SpriteLoader.LoadPath, e.FullPath); if (relativePath.IndexOf("_WIP" + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) >= 0) { return; } string[] array = relativePath.Split(Path.DirectorySeparatorChar); if (array.Length >= 2) { if (array[^2] == "T2D" || (array.Length >= 3 && array[^3] == "T2D")) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(array[^1]); string text = ((array.Length >= 3 && array[^3] == "T2D") ? array[^2] : null); Plugin.Logger.LogDebug((object)("[FileWatcher] → T2D sprite change detected: atlas='" + text + "', sprite='" + fileNameWithoutExtension + "', setting ReloadT2DSprites=true")); ReloadToast.Record(ReloadToast.Kind.T2D, e.FullPath); DeferReloadT2DSprites(); } else if (array.Length < 3) { Plugin.Logger.LogDebug((object)$"[FileWatcher] → Path too short ({array.Length} parts), ignoring: {relativePath}"); } else { string text2 = array[^3]; string text3 = array[^2]; string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(array[^1]); SpriteLoader.MarkReloadSprite(text2, text3, fileNameWithoutExtension2); Plugin.Logger.LogDebug((object)("[FileWatcher] → tk2d sprite change: collection=" + text2 + ", atlas=" + text3 + ", sprite=" + fileNameWithoutExtension2 + ", setting ReloadSprites=true")); ReloadToast.Record(ReloadToast.Kind.Sprite, e.FullPath, (e.ChangeType == WatcherChangeTypes.Deleted) ? null : IOUtil.ParseAnchorTag(fileNameWithoutExtension2).name); DeferReloadSprites(); } } } private void OnAtlasChanged(object sender, FileSystemEventArgs e) { Plugin.Logger.LogDebug((object)$"[FileWatcher] Atlas file event: {e.ChangeType} — {e.FullPath}"); string[] array = Path.GetRelativePath((sender as FileSystemWatcher)?.Path ?? SpriteLoader.AtlasLoadPath, e.FullPath).Split(Path.DirectorySeparatorChar); if (array.Length < 2) { Plugin.Logger.LogDebug((object)$"[FileWatcher] → Atlas path too short ({array.Length} parts), ignoring"); return; } if (array[^2] == "T2D") { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(array[^1]); Plugin.Logger.LogDebug((object)("[FileWatcher] → T2D spritesheet change: '" + fileNameWithoutExtension + "', setting ReloadT2DSprites=true")); ReloadToast.Record(ReloadToast.Kind.T2D, e.FullPath); DeferReloadT2DSprites(); return; } string text = array[^2]; string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(array[^1]); ReloadToast.Record(ReloadToast.Kind.Atlas, e.FullPath); SpriteLoader.MarkReloadAtlas(text, fileNameWithoutExtension2); Plugin.Logger.LogDebug((object)("[FileWatcher] → tk2d atlas change: collection=" + text + ", atlas=" + fileNameWithoutExtension2 + ", setting ReloadSprites=true")); DeferReloadSprites(); } } } namespace Stitchwork.GUI { public static class AudioList { private static List _entries = new List(); private static bool _needsRefresh = true; private static int _version; public static IReadOnlyList Entries => _entries; public static bool NeedsRefresh => _needsRefresh; public static int Version => _version; public static void RequestRefresh() { _needsRefresh = true; } public static void Refresh() { _needsRefresh = false; _entries = AudioHandler.GetClipInventory(); _version++; } public static void ClearList() { _entries.Clear(); _needsRefresh = true; _version++; } } public static class AudioLog { internal class AudioPlayEntry { public string ClipName; public string SourcePath; public DateTime StartTime; public DateTime? BumpedTime; public float GetFadeOpacity(double fadeDuration) { if (!BumpedTime.HasValue) { return 1f; } double totalSeconds = (DateTime.Now - BumpedTime.Value).TotalSeconds; if (totalSeconds >= fadeDuration) { return 0f; } return 1f - (float)(totalSeconds / fadeDuration); } public bool IsFadedOut(double fadeDuration) { if (!BumpedTime.HasValue) { return false; } return (DateTime.Now - BumpedTime.Value).TotalSeconds >= fadeDuration; } } private static readonly List AudioPlayEntries = new List(); private static string _pendingFocusClip = null; public static string ConsumePendingFocus() { string pendingFocusClip = _pendingFocusClip; _pendingFocusClip = null; return pendingFocusClip; } public static void DrawEntries(int maxVisible, double fadeDuration, bool hideModded) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) UpdateEntryLifecycle(maxVisible, fadeDuration); int num = 0; GUILayout.BeginVertical(Array.Empty()); for (int i = 0; i < AudioPlayEntries.Count; i++) { AudioPlayEntry audioPlayEntry = AudioPlayEntries[i]; if (!hideModded || !File.Exists(Path.Combine(AudioHandler.SoundFolder, audioPlayEntry.ClipName + ".wav"))) { float num2 = ((i < maxVisible) ? 1f : audioPlayEntry.GetFadeOpacity(fadeDuration)); GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = new Color(1f, 1f, 1f, num2); if (GUILayout.Button(GUIHelper.TT(audioPlayEntry.ClipName, "Click to copy name"), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })) { GUIUtility.systemCopyBuffer = audioPlayEntry.ClipName; _pendingFocusClip = audioPlayEntry.ClipName; } if (!string.IsNullOrEmpty(audioPlayEntry.SourcePath)) { GUI.contentColor = new Color(0.55f, 0.55f, 0.55f, num2); GUILayout.Label("← " + audioPlayEntry.SourcePath, GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) }); } GUI.contentColor = Color.white; GUILayout.EndHorizontal(); num++; } } if (num == 0) { GUI.contentColor = Color.yellow; GUILayout.Label("No audio played recently.", GUIHelper.LabelStyle, Array.Empty()); } GUI.contentColor = Color.white; GUILayout.EndVertical(); } private static void UpdateEntryLifecycle(int maxVisible, double fadeDuration) { for (int num = AudioPlayEntries.Count - 1; num >= maxVisible; num--) { if (AudioPlayEntries[num].IsFadedOut(fadeDuration)) { AudioPlayEntries.RemoveAt(num); } } for (int i = maxVisible; i < AudioPlayEntries.Count; i++) { if (!AudioPlayEntries[i].BumpedTime.HasValue) { AudioPlayEntries[i].BumpedTime = DateTime.Now; } } for (int j = 0; j < Math.Min(maxVisible, AudioPlayEntries.Count); j++) { AudioPlayEntries[j].BumpedTime = null; } } public static void LogAudio(AudioClip clip, AudioSource source) { if (!Plugin.ShowDevHub) { return; } string cleanName = ((Object)clip).name.Replace("PATCHWORK_", ""); string text = (((Object)(object)source != (Object)null) ? AudioHandler.GetGameObjectPath(source) : ""); AudioPlayEntry audioPlayEntry = AudioPlayEntries.Find((AudioPlayEntry e) => e.ClipName == cleanName); if (audioPlayEntry != null) { AudioPlayEntries.Remove(audioPlayEntry); if (!string.IsNullOrEmpty(text)) { audioPlayEntry.SourcePath = text; } audioPlayEntry.StartTime = DateTime.Now; audioPlayEntry.BumpedTime = null; AudioPlayEntries.Insert(0, audioPlayEntry); } else { AudioPlayEntries.Insert(0, new AudioPlayEntry { ClipName = cleanName, SourcePath = text, StartTime = DateTime.Now, BumpedTime = null }); } } public static void ClearLog() { AudioPlayEntries.Clear(); } } public static class CameraTracker { private static Transform _target; private static CameraTarget _camTarget; private static Vector3 _vel; private static string _label = ""; public static void Track(Component clicked, string label) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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) if (!((Object)(object)clicked == (Object)null)) { GameCameras instance = GameCameras.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.cameraTarget == (Object)null) { Plugin.Logger.LogWarning((object)"[CamTrack] GameCameras/cameraTarget unavailable — cannot track."); return; } _camTarget = instance.cameraTarget; _target = clicked.transform; _label = (string.IsNullOrEmpty(label) ? ((Object)clicked).name : label); _vel = Vector3.zero; _camTarget.mode = (TargetMode)3; Plugin.Logger.LogInfo((object)("[CamTrack] Tracking '" + _label + "'.")); } } public static void Release() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_camTarget != (Object)null) { _camTarget.mode = (TargetMode)0; } _camTarget = null; _target = null; Plugin.Logger.LogInfo((object)"[CamTrack] Released — camera follows Hornet."); } public static void LateUpdate() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: 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_00a1: Invalid comparison between Unknown and I4 //IL_00a9: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_target == (Object)null && (Object)(object)_camTarget == (Object)null) { return; } if ((Object)(object)_target == (Object)null || (Object)(object)_camTarget == (Object)null) { Release(); return; } Vector3 position = ((Component)_camTarget).transform.position; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(_target.position.x, _target.position.y, position.z); ((Component)_camTarget).transform.position = Vector3.SmoothDamp(position, val, ref _vel, 0.12f); if ((int)_camTarget.mode != 3) { _camTarget.mode = (TargetMode)3; } } public static void OnSceneUnloaded() { if ((Object)(object)_target != (Object)null || (Object)(object)_camTarget != (Object)null) { Release(); } } public static void DrawIndicator() { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: 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_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_target == (Object)null)) { GUIContent val = new GUIContent(string.Format(Loc.T("Camera: tracking {0} — click to release"), _label)); Vector2 val2 = GUIHelper.LabelStyle.CalcSize(val); float num = GUIHelper.Scaled(8f); Rect val3 = default(Rect); ((Rect)(ref val3))..ctor((float)Screen.width - val2.x - num * 2f - GUIHelper.Scaled(10f), GUIHelper.Scaled(8f), val2.x + num * 2f, val2.y + GUIHelper.Scaled(6f)); GUI.DrawTexture(val3, (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0, true, 0f, new Color(GUIHelper.ColSurface.r, GUIHelper.ColSurface.g, GUIHelper.ColSurface.b, 0.95f), 0f, ((Rect)(ref val3)).height * 0.5f); GUI.DrawTexture(val3, (Texture)(object)Texture2D.whiteTexture, (ScaleMode)0, true, 0f, new Color(GUIHelper.ColConfirm.r, GUIHelper.ColConfirm.g, GUIHelper.ColConfirm.b, 0.9f), 1f, ((Rect)(ref val3)).height * 0.5f); GUI.contentColor = GUIHelper.ColText; GUI.Label(new Rect(((Rect)(ref val3)).x + num, ((Rect)(ref val3)).y + GUIHelper.Scaled(3f), ((Rect)(ref val3)).width - num, val2.y), val, GUIHelper.LabelStyle); GUI.contentColor = Color.white; if (GUI.Button(val3, GUIContent.none, GUIStyle.none)) { Release(); } } } } public static class CanvasPackManager { private const float WinW = 460f; private const float WinH = 720f; private const float StripW = 15f; private const float Pad = 8f; private const float PadRight = 12f; private static GameObject _root; private static RectTransform _rootRT; private static bool _built; private static List _staged; private static bool _closePrompt; private static string _pendingProfileDelete; private static ScrollRect _packListScroll; private static GameObject _actionBtnRow; private static GameObject _closePromptRow; private static Text _applyHint; private static string _lastStagedProfile; private static Transform _packListContent; private static Text _packsLabel; private static Text _statusText; private static Text _changesText; private static Text _overlayBtnText; private static Text _langBtnText; private static GameObject _discardBtn; private static GameObject _applyBtn; private static Image _applyBg; private static GameObject _profilesContent; private static Transform _profileButtonsContent; private static InputField _profileNameInput; private static Text _profilesFoldText; private static bool _profilesFoldout; private static Transform _conflictsContent; private static GameObject _conflictsScrollGO; private static Text _conflictsFoldText; private static bool _conflictsFoldout; private static Image _applyBorder; private static RawImage _stripImage; private static readonly HashSet _conditionsOpen = new HashSet(); private static int _lastStripH; private static int _builtLocGen = -1; private static string _openPicker; private static bool HasChanges => _staged != null; public static bool IsVisible { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } public static void Show() { if (_built && (Object)(object)_root != (Object)null && _builtLocGen != Loc.Generation) { Object.Destroy((Object)(object)_root); _root = null; _built = false; } if (!_built || (Object)(object)_root == (Object)null) { Build(); } _root.SetActive(true); FabricUI.MakeEventSystemCurrent(); _closePrompt = false; _pendingProfileDelete = null; Rebuild(); } public static void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } FabricUI.RestoreEventSystem(); } public static void Toggle() { if (IsVisible) { RequestClose(); } else { Show(); } } private static void RequestClose() { if (_closePrompt) { DoClose(); return; } NormalizeStaged(); if (_staged != null) { _closePrompt = true; Rebuild(keepPrompts: true); } else { DoClose(); } } private static void DoClose() { _closePrompt = false; Plugin.ShowPackManager = false; Hide(); } private static void Build() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_012b: 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) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) _builtLocGen = Loc.Generation; Canvas rootCanvas = FabricUI.RootCanvas; float num = FabricUI.S(460f); float num2 = FabricUI.S(720f); _root = new GameObject("CanvasPackManager", new Type[1] { typeof(RectTransform) }); _root.transform.SetParent(((Component)rootCanvas).transform, false); _rootRT = _root.GetComponent(); _rootRT.anchorMin = new Vector2(0f, 1f); _rootRT.anchorMax = new Vector2(0f, 1f); _rootRT.pivot = new Vector2(0f, 1f); _rootRT.sizeDelta = new Vector2(num, num2); float num3 = Plugin.Config?.CanvasPMX ?? 0f; float num4 = Plugin.Config?.CanvasPMY ?? 0f; _rootRT.anchoredPosition = ((num3 != 0f || num4 != 0f) ? new Vector2(num3, num4) : new Vector2(FabricUI.S(20f), 0f - FabricUI.S(60f))); Image obj = _root.AddComponent(); obj.sprite = FabricUI.CreateRoundedSprite(FabricUI.SI(6f), FabricUI.SI(32f)); obj.type = (Type)1; ((Graphic)obj).color = GUIHelper.ColSurface; ((Graphic)obj).raycastTarget = true; FabricUI.AddBorder(_root, GUIHelper.ColBorder, 6); _root.AddComponent(); BuildStrip(); float num5 = FabricUI.S(23f); float num6 = FabricUI.S(12f); float num7 = FabricUI.S(8f); GameObject obj2 = FabricUI.CreateVerticalGroup(_root.transform, "Content", FabricUI.S(4f), new RectOffset(Mathf.RoundToInt(num5), Mathf.RoundToInt(num6), Mathf.RoundToInt(num7), Mathf.RoundToInt(num7))); FabricUI.SetStretch(obj2.GetComponent()); BuildHeader(obj2.transform); BuildPackListHeader(obj2.transform); BuildPackListContainer(obj2.transform); BuildSectionsArea(obj2.transform); BuildActionBar(obj2.transform); BuildFooter(obj2.transform); _root.SetActive(false); _built = true; } private static void BuildStrip() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("CassetteStrip", new Type[1] { typeof(RectTransform) }); val.transform.SetParent(_root.transform, false); _stripImage = val.AddComponent(); ((Graphic)_stripImage).raycastTarget = false; FabricUI.SetStretchLeft(val.GetComponent(), FabricUI.S(15f)); RefreshStripTexture(); } private static void RefreshStripTexture() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) int texW = Mathf.Max(FabricUI.SI(15f), 2); int num = Mathf.Max(Mathf.RoundToInt(_rootRT.sizeDelta.y), 2); if (num != _lastStripH || !((Object)(object)_stripImage.texture != (Object)null)) { _stripImage.texture = (Texture)(object)GUIHelper.GetVerticalStripTex(texW, num); _lastStripH = num; } } private static void BuildHeader(Transform parent) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) GameObject obj = FabricUI.CreateHorizontalGroup(parent, "Header", 6f); FabricUI.AddLayout(obj, -1f, FabricUI.S(24f)); GameObject obj2 = FabricUI.CreateText(obj.transform, "Wordmark", "STITCHWORK", 14, GUIHelper.ColBone, (TextAnchor)3); obj2.GetComponent().fontStyle = (FontStyle)1; FabricUI.AddLayout(obj2, FabricUI.S(100f)); FabricUI.AddLayout(FabricUI.CreateText(obj.transform, "Subtitle", Loc.T("Resource packs"), 11, GUIHelper.ColMuted, (TextAnchor)3), -1f, -1f, -1f, -1f, 1f); Transform transform = obj.transform; Action onClick = RequestClose; Color? textColor = Color.Lerp(GUIHelper.ColDanger, Color.white, 0.35f); FabricUI.AddLayout(FabricUI.CreateButton(transform, "Close", "×", onClick, null, textColor), FabricUI.S(26f), FabricUI.S(20f)); } private static void BuildPackListHeader(Transform parent) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) GameObject obj = FabricUI.CreateHorizontalGroup(parent, "PacksHeader", 4f); FabricUI.AddLayout(obj, -1f, FabricUI.S(22f)); GameObject obj2 = FabricUI.CreateText(obj.transform, "PacksLabel", Loc.T("PACKS"), 10, GUIHelper.ColMuted, (TextAnchor)3); FabricUI.AddLayout(obj2, -1f, -1f, -1f, -1f, 1f); _packsLabel = obj2.GetComponent(); FabricUI.CreateButton(obj.transform, "GlobalFolder", Loc.T("Global folder"), delegate { try { Directory.CreateDirectory(PackLayout.GlobalPacksRoot); } catch (Exception ex) when (ex is UnauthorizedAccessException || ex is IOException) { Plugin.Logger.LogWarning((object)("[PackManager] Cannot create global packs root: " + ex.Message)); } IOUtil.OpenDeepestExisting(PackLayout.GlobalPacksRoot); }); GameObject obj3 = FabricUI.CreateButton(obj.transform, "Rescan", Loc.T("Rescan"), delegate { List staged = _staged; PackManager.Rescan(); if (staged != null) { IReadOnlyList live = PackManager.AllPacks; List list = staged.Where((PackInfo s) => live.Any((PackInfo packInfo) => string.Equals(packInfo.Path, s.Path, StringComparison.OrdinalIgnoreCase))).ToList(); foreach (PackInfo p in live) { if (!list.Any((PackInfo m) => string.Equals(m.Path, p.Path, StringComparison.OrdinalIgnoreCase))) { list.Add(p.Clone()); } } _staged = list; } Rebuild(); }); FabricUI.AddLayout(obj3, FabricUI.S(72f), FabricUI.S(20f)); Text componentInChildren = obj3.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fontSize = FabricUI.FontSize(11); } GameObject obj4 = FabricUI.CreateButton(obj.transform, "DisableAll", Loc.T("Disable all"), delegate { EnsureStaged(); foreach (PackInfo item in _staged) { item.IsEnabled = false; } Rebuild(); }, GUIHelper.ColSurface1); FabricUI.AddLayout(obj4, FabricUI.S(88f), FabricUI.S(20f)); Text componentInChildren2 = obj4.GetComponentInChildren(); if ((Object)(object)componentInChildren2 != (Object)null) { componentInChildren2.fontSize = FabricUI.FontSize(11); } } private static void BuildPackListContainer(Transform parent) { GameObject obj = FabricUI.CreateScrollView(parent, "PackList"); FabricUI.AddLayout(obj, -1f, FabricUI.S(240f), -1f, -1f, -1f, 1f); _packListScroll = obj.GetComponent(); _packListContent = (Transform)(object)_packListScroll.content; } private static void BuildSectionsArea(Transform parent) { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) GameObject obj = FabricUI.CreateHorizontalGroup(parent, "Sections", 6f); FabricUI.AddLayout(obj, -1f, FabricUI.S(22f)); GameObject obj2 = FabricUI.CreateButton(obj.transform, "ProfilesFold", "", delegate { _profilesFoldout = !_profilesFoldout; Rebuild(); }); FabricUI.AddLayout(obj2, -1f, FabricUI.S(22f), -1f, -1f, 1f); _profilesFoldText = obj2.GetComponentInChildren(); _profilesFoldText.fontSize = FabricUI.FontSize(10); ((Graphic)_profilesFoldText).color = GUIHelper.ColMuted; GameObject obj3 = FabricUI.CreateButton(obj.transform, "ConflictsFold", "", delegate { _conflictsFoldout = !_conflictsFoldout; Rebuild(); }); FabricUI.AddLayout(obj3, -1f, FabricUI.S(22f), -1f, -1f, 1f); _conflictsFoldText = obj3.GetComponentInChildren(); _conflictsFoldText.fontSize = FabricUI.FontSize(10); _profilesContent = FabricUI.CreateVerticalGroup(parent, "ProfilesContent", 3f); GameObject obj4 = FabricUI.CreateHorizontalGroup(_profilesContent.transform, "ProfileButtons", 4f); FabricUI.AddLayout(obj4, -1f, FabricUI.S(24f)); _profileButtonsContent = obj4.transform; GameObject obj5 = FabricUI.CreateHorizontalGroup(_profilesContent.transform, "SaveRow", 4f); FabricUI.AddLayout(obj5, -1f, FabricUI.S(24f)); FabricUI.AddLayout(FabricUI.CreateText(obj5.transform, "SaveLabel", Loc.T("Save as:"), 12, GUIHelper.ColMuted, (TextAnchor)3), FabricUI.S(55f)); GameObject obj6 = FabricUI.CreateInputField(obj5.transform, "ProfileNameInput", Loc.T("profile name")); FabricUI.AddLayout(obj6, FabricUI.S(140f), FabricUI.S(22f)); _profileNameInput = obj6.GetComponent(); GameObject obj7 = FabricUI.CreateButton(obj5.transform, "SaveBtn", Loc.T("Save"), (Action)delegate { string text = _profileNameInput.text?.Trim(); if (!string.IsNullOrWhiteSpace(text)) { PackManager.SaveProfile(text, _staged); _profileNameInput.text = ""; _lastStagedProfile = text; Rebuild(); } }, (Color?)GUIHelper.ColConfirm, (Color?)new Color(0.031f, 0.145f, 0.11f, 1f), (Color?)null); FabricUI.AddLayout(obj7, FabricUI.S(50f), FabricUI.S(22f)); Text componentInChildren = obj7.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fontStyle = (FontStyle)1; } _profilesContent.SetActive(false); GameObject obj8 = (_conflictsScrollGO = FabricUI.CreateScrollView(parent, "ConflictScroll")); _conflictsContent = (Transform)(object)obj8.GetComponent().content; obj8.SetActive(false); } private static void BuildActionBar(Transform parent) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_04bb: Unknown result type (might be due to invalid IL or missing references) FabricUI.CreateSeparator(parent); GameObject obj = FabricUI.CreateVerticalGroup(parent, "ActionBar", 2f); GameObject obj2 = FabricUI.CreateHorizontalGroup(obj.transform, "StatusRow", 6f); FabricUI.AddLayout(obj2, -1f, FabricUI.S(16f)); GameObject obj3 = FabricUI.CreateText(obj2.transform, "StatusCount", string.Format(Loc.T("{0}/{1} active"), 0, 0), 11, GUIHelper.ColText, (TextAnchor)3); FabricUI.AddLayout(obj3, FabricUI.S(80f)); _statusText = obj3.GetComponent(); GameObject obj4 = FabricUI.CreateText(obj2.transform, "Changes", "", 11, GUIHelper.ColWarn, (TextAnchor)3); FabricUI.AddLayout(obj4, -1f, -1f, -1f, -1f, 1f); _changesText = obj4.GetComponent(); GameObject obj5 = FabricUI.CreateText(obj2.transform, "ApplyHint", "", 10, GUIHelper.ColFaint, (TextAnchor)5); FabricUI.AddLayout(obj5, FabricUI.S(170f)); _applyHint = obj5.GetComponent(); obj5.SetActive(false); GameObject obj6 = FabricUI.CreateHorizontalGroup(obj.transform, "ActionButtons", 4f); FabricUI.AddLayout(obj6, -1f, FabricUI.S(28f)); _actionBtnRow = obj6; _discardBtn = FabricUI.CreateButton(obj6.transform, "Discard", Loc.T("Discard"), delegate { _staged = null; Rebuild(); }); FabricUI.AddLayout(_discardBtn, FabricUI.S(88f), FabricUI.S(26f)); _applyBtn = FabricUI.CreateButton(obj6.transform, "Apply", Loc.T("Apply"), (Action)delegate { if (_staged != null) { PackManager.Apply(_staged); _staged = null; Rebuild(); } }, (Color?)GUIHelper.ColConfirm, (Color?)new Color(0.031f, 0.145f, 0.11f, 1f), (Color?)Color.Lerp(GUIHelper.ColConfirm, Color.white, 0.25f)); FabricUI.AddLayout(_applyBtn, -1f, FabricUI.S(26f), -1f, -1f, 1f); _applyBg = _applyBtn.GetComponent(); Transform val = _applyBtn.transform.Find("Apply_Border"); _applyBorder = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent() : null); Text componentInChildren = _applyBtn.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fontStyle = (FontStyle)1; } GameObject obj7 = FabricUI.CreateHorizontalGroup(obj.transform, "ClosePrompt", 4f); FabricUI.AddLayout(obj7, -1f, FabricUI.S(28f)); _closePromptRow = obj7; GameObject obj8 = FabricUI.CreateButton(obj7.transform, "ApplyClose", Loc.T("Apply & close"), (Action)delegate { if (_staged != null) { PackManager.Apply(_staged); } _staged = null; DoClose(); }, (Color?)GUIHelper.ColConfirm, (Color?)new Color(0.031f, 0.145f, 0.11f, 1f), (Color?)Color.Lerp(GUIHelper.ColConfirm, Color.white, 0.25f)); FabricUI.AddLayout(obj8, -1f, FabricUI.S(26f), -1f, -1f, 1.4f); Text componentInChildren2 = obj8.GetComponentInChildren(); if ((Object)(object)componentInChildren2 != (Object)null) { componentInChildren2.fontStyle = (FontStyle)1; } FabricUI.AddLayout(FabricUI.CreateButton(obj7.transform, "DiscardClose", Loc.T("Discard & close"), (Action)delegate { _staged = null; DoClose(); }, (Color?)new Color(GUIHelper.ColDanger.r * 0.5f, GUIHelper.ColDanger.g * 0.5f, GUIHelper.ColDanger.b * 0.5f), (Color?)null, (Color?)null), -1f, FabricUI.S(26f), -1f, -1f, 1f); FabricUI.AddLayout(FabricUI.CreateButton(obj7.transform, "KeepClose", Loc.T("Keep & close"), delegate { DoClose(); }), -1f, FabricUI.S(26f), -1f, -1f, 1f); obj7.SetActive(false); } private static void BuildFooter(Transform parent) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) FabricUI.CreateSeparator(parent); GameObject obj = FabricUI.CreateHorizontalGroup(parent, "Footer", 4f); FabricUI.AddLayout(obj, -1f, FabricUI.S(26f)); FabricUI.AddLayout(FabricUI.CreateText(obj.transform, "Version", "Stitchwork 0.8.6", 9, GUIHelper.ColFaint, (TextAnchor)3), -1f, -1f, -1f, -1f, 1f); Transform transform = obj.transform; string text = "语言 " + Loc.NativeName(Plugin.Config.UILanguage.ToLowerInvariant()); Action onClick = delegate { Loc.CycleLanguage(); Rebuild(); }; Color? textColor = GUIHelper.ColMuted; GameObject obj2 = FabricUI.CreateButton(transform, "LangCycle", text, onClick, null, textColor); FabricUI.AddLayout(obj2, FabricUI.S(92f), FabricUI.S(20f)); _langBtnText = obj2.GetComponentInChildren(); _langBtnText.fontSize = FabricUI.FontSize(9); Transform transform2 = obj.transform; string text2 = (Plugin.ShowStatusOverlay ? Loc.T("Overlay: on") : Loc.T("Overlay: off")); Action onClick2 = delegate { Plugin.ShowStatusOverlay = !Plugin.ShowStatusOverlay; Rebuild(); }; textColor = GUIHelper.ColMuted; GameObject obj3 = FabricUI.CreateButton(transform2, "OverlayToggle", text2, onClick2, null, textColor); FabricUI.AddLayout(obj3, FabricUI.S(84f), FabricUI.S(20f)); _overlayBtnText = obj3.GetComponentInChildren(); _overlayBtnText.fontSize = FabricUI.FontSize(9); Transform transform3 = obj.transform; string text3 = Loc.T("dev tools"); Action onClick3 = delegate { DevHub.OpenAt(1); }; textColor = GUIHelper.ColMuted; GameObject obj4 = FabricUI.CreateButton(transform3, "DevTools", text3, onClick3, null, textColor); FabricUI.AddLayout(obj4, FabricUI.S(64f), FabricUI.S(20f)); Text componentInChildren = obj4.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.fontSize = FabricUI.FontSize(9); } } private static void Rebuild(bool keepPrompts = false) { if (!((Object)(object)_root == (Object)null)) { if (!keepPrompts) { _closePrompt = false; _pendingProfileDelete = null; } NormalizeStaged(); List list = _staged ?? PackManager.AllPacks.ToList(); float num = (((Object)(object)_packListScroll != (Object)null) ? _packListScroll.verticalNormalizedPosition : 1f); RefreshStripTexture(); RebuildPackList(list); RebuildProfiles(); RefreshConflicts(); RefreshActionBar(list); RefreshFooter(); if ((Object)(object)_packListScroll != (Object)null) { Canvas.ForceUpdateCanvases(); _packListScroll.verticalNormalizedPosition = Mathf.Clamp01(num); } } } private static void NormalizeStaged() { if (_staged != null && !StagedDiffers()) { _staged = null; } } private static bool StagedDiffers() { IReadOnlyList allPacks = PackManager.AllPacks; if (_staged.Count != allPacks.Count) { return true; } for (int i = 0; i < _staged.Count; i++) { PackInfo packInfo = _staged[i]; PackInfo packInfo2 = allPacks[i]; if (!string.Equals(packInfo.Path, packInfo2.Path, StringComparison.OrdinalIgnoreCase)) { return true; } if (packInfo.IsEnabled != packInfo2.IsEnabled) { return true; } if (packInfo.ReloadTrigger != packInfo2.ReloadTrigger) { return true; } if (packInfo.Conditions.Count != packInfo2.Conditions.Count) { return true; } for (int j = 0; j < packInfo.Conditions.Count; j++) { if (packInfo.Conditions[j].Serialize() != packInfo2.Conditions[j].Serialize()) { return true; } } } return false; } private static PackInfo StagedPack(string path) { EnsureStaged(); return _staged.FirstOrDefault((PackInfo p) => string.Equals(p.Path, path, StringComparison.OrdinalIgnoreCase)); } private static void RebuildPackList(List list) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) FabricUI.ClearChildren(_packListContent); if (list.Count == 0) { FabricUI.AddLayout(FabricUI.CreateText(_packListContent, "Empty", Loc.T("No packs found.\nDrop packs into Stitchwork/Packs/ or install via Thunderstore."), 12, GUIHelper.ColMuted, (TextAnchor)3), -1f, FabricUI.S(40f)); return; } for (int i = 0; i < list.Count; i++) { BuildPackRow(list, i); } } private static void BuildPackRow(List list, int idx) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001d: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Expected O, but got Unknown //IL_0268: 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_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Expected O, but got Unknown //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Unknown result type (might be due to invalid IL or missing references) //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_05bc: Unknown result type (might be due to invalid IL or missing references) //IL_0650: Unknown result type (might be due to invalid IL or missing references) //IL_0655: Unknown result type (might be due to invalid IL or missing references) //IL_065f: Unknown result type (might be due to invalid IL or missing references) //IL_0649: Unknown result type (might be due to invalid IL or missing references) //IL_069c: Unknown result type (might be due to invalid IL or missing references) //IL_073d: Unknown result type (might be due to invalid IL or missing references) PackInfo packInfo = list[idx]; bool hasConditions = packInfo.HasConditions; Color color = (packInfo.IsEnabled ? GUIHelper.ColSurface1 : Color.Lerp(GUIHelper.ColSurface, GUIHelper.ColSurface1, 0.5f)); GameObject val = FabricUI.CreatePanel(_packListContent, $"Pack_{idx}", color); Image component = val.GetComponent(); component.sprite = FabricUI.CreateRoundedSprite(FabricUI.SI(5f), FabricUI.SI(20f)); component.type = (Type)1; FabricUI.AddBorder(val, GUIHelper.ColBorder, 5); VerticalLayoutGroup obj = val.AddComponent(); ((HorizontalOrVerticalLayoutGroup)obj).spacing = FabricUI.S(2f); ((LayoutGroup)obj).padding = new RectOffset(FabricUI.SI(9f), FabricUI.SI(8f), FabricUI.SI(6f), FabricUI.SI(6f)); ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; FabricUI.AddFitter(val, (FitMode)0, (FitMode)2); if (packInfo.IsEnabled) { GameObject val2 = new GameObject("StateEdge", new Type[2] { typeof(RectTransform), typeof(Image) }); val2.transform.SetParent(val.transform, false); ((Graphic)val2.GetComponent()).color = GUIHelper.ColConfirm; ((Graphic)val2.GetComponent()).raycastTarget = false; FabricUI.SetStretchLeft(val2.GetComponent(), FabricUI.S(3f)); FabricUI.AddLayout(val2, -1f, -1f, -1f, -1f, -1f, -1f, ignoreLayout: true); } int capturedIdx = idx; GameObject obj2 = FabricUI.CreateHorizontalGroup(val.transform, "MainRow", 4f); FabricUI.AddFitter(obj2, (FitMode)0, (FitMode)2); bool canUp = idx > 0; bool canDown = idx < list.Count - 1; float num = FabricUI.S(18f); float preferredHeight = FabricUI.S(13f); GameObject obj3 = FabricUI.CreateVerticalGroup(obj2.transform, "Arrows", 2f); ((HorizontalOrVerticalLayoutGroup)obj3.GetComponent()).childForceExpandWidth = false; FabricUI.AddLayout(obj3, num, -1f, num); Transform transform = obj3.transform; Action onClick = delegate { if (canUp) { EnsureStaged(); List staged = _staged; int index = capturedIdx - 1; List staged2 = _staged; int index2 = capturedIdx; PackInfo value = _staged[capturedIdx]; PackInfo value2 = _staged[capturedIdx - 1]; staged[index] = value; staged2[index2] = value2; Rebuild(); } }; Color? textColor = GUIHelper.ColMuted; GameObject val3 = FabricUI.CreateButton(transform, "Up", "▲", onClick, null, textColor); float minWidth = num; FabricUI.AddLayout(val3, num, preferredHeight, minWidth); val3.GetComponentInChildren().fontSize = FabricUI.FontSize(8); if (!canUp) { ((Selectable)val3.GetComponent