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 TeamCherry.Cinematics; using TeamCherry.Localization; using Unity.Mathematics; 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.UI; using UnityEngine.Video; [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.0.0")] [assembly: AssemblyInformationalVersion("0.8.0+0475ee1ae4a7529149d6d3ad4b00f66006b5e890")] [assembly: AssemblyProduct("Stitchwork")] [assembly: AssemblyTitle("Stitchwork")] [assembly: AssemblyMetadata("GameLibsPin", "1.2.0-silksong1.0.30000")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.8.0.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 PerClip; } 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 readonly Dictionary<(int libId, string clip), ClipCoverage> _coverage = new Dictionary<(int, string), ClipCoverage>(); private static int _coverageIndexVersion = -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 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) { tk2dSpriteAnimator val = e?.Live; if ((Object)(object)val != (Object)null && val.CurrentClip == clip && val.ClipFps > 0f) { return val.ClipFps; } 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; } 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_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_0104: 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_021a: 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_01fb: 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) 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 = 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_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: 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_0109: 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_0182: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0354: 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_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03f1: 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; } IEnumerable source = library.clips.Where((tk2dSpriteAnimationClip c) => c != null && !string.IsNullOrEmpty(c.name)); if (!string.IsNullOrEmpty(_clipSearch)) { source = source.Where((tk2dSpriteAnimationClip c) => c.name.ToLower().Contains(_clipSearch.ToLower())); } if (_lensSpriteId >= 0) { source = source.Where((tk2dSpriteAnimationClip c) => ClipUsesSprite(c, _lensColl, _lensSpriteId)); } List list = source.OrderBy((tk2dSpriteAnimationClip c) => c.name, StringComparer.OrdinalIgnoreCase).ToList(); GUI.contentColor = GUIHelper.ColFaint; GUILayout.Label($"{list.Count} / {library.clips.Length}", GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; 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 val = FindClip(library, recentClip); if (val != null) { DrawClipRow(e, library, val, currentClip, live); } } GUI.contentColor = GUIHelper.ColMuted; GUILayout.Label(Loc.T("All clips"), GUIHelper.LabelStyle, Array.Empty()); GUI.contentColor = Color.white; } foreach (tk2dSpriteAnimationClip item in list) { DrawClipRow(e, library, item, currentClip, live); } GUILayout.EndScrollView(); } private static void DrawClipRow(AnimEntry e, tk2dSpriteAnimation lib, tk2dSpriteAnimationClip clip, string currentClip, bool live) { //IL_0022: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_0102: 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_011a: 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_0155: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) ClipCoverage coverage = GetCoverage(lib, clip); GUILayout.BeginHorizontal(Array.Empty()); GUI.contentColor = ((coverage.Shared > 0) ? GUIHelper.ColConfirm : GUIHelper.ColBorderHi); GUILayout.Label(new GUIContent(DotFor(coverage.Shared, coverage.Frames), string.Format("{0}: {1}/{2}", Loc.T("shared replacements"), coverage.Shared, coverage.Frames)), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(16f) }); GUI.contentColor = ((coverage.PerClip > 0) ? GUIHelper.ColAccent : GUIHelper.ColBorderHi); GUILayout.Label(new GUIContent(DotFor(coverage.PerClip, coverage.Frames), string.Format("{0}: {1}/{2}", Loc.T("per-clip (Appliqué)"), coverage.PerClip, coverage.Frames)), GUIHelper.LabelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUIHelper.Width(16f) }); GUI.contentColor = Color.white; GUI.contentColor = ((clip.name == currentClip) ? 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; 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 string DotFor(int n, int total) { if (n > 0) { if (n < total) { return "◐"; } return "●"; } return "○"; } private static ClipCoverage GetCoverage(tk2dSpriteAnimation lib, tk2dSpriteAnimationClip clip) { if (_coverageIndexVersion != SpriteLoader.FileIndexVersion) { _coverage.Clear(); _coverageIndexVersion = SpriteLoader.FileIndexVersion; } (int, string) key = (((Object)lib).GetInstanceID(), clip.name); 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++; if (SpriteLoader.HasSpriteReplacement(spriteCollection, val2)) { value.Shared++; } 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_00ca: 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_020d: 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_0214: 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_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: 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_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Invalid comparison between Unknown and I4 //IL_022d: 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_0242: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_056c: 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_05d5: Unknown result type (might be due to invalid IL or missing references) //IL_05ce: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: 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_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) //IL_066d: Unknown result type (might be due to invalid IL or missing references) //IL_06a2: Unknown result type (might be due to invalid IL or missing references) //IL_06dc: Unknown result type (might be due to invalid IL or missing references) //IL_06d5: Unknown result type (might be due to invalid IL or missing references) //IL_0509: Unknown result type (might be due to invalid IL or missing references) //IL_052c: Unknown result type (might be due to invalid IL or missing references) //IL_0553: Unknown result type (might be due to invalid IL or missing references) //IL_0562: Unknown result type (might be due to invalid IL or missing references) //IL_0717: Unknown result type (might be due to invalid IL or missing references) //IL_0742: Unknown result type (might be due to invalid IL or missing references) //IL_0761: Unknown result type (might be due to invalid IL or missing references) //IL_079b: Unknown result type (might be due to invalid IL or missing references) //IL_0794: Unknown result type (might be due to invalid IL or missing references) //IL_07ba: Unknown result type (might be due to invalid IL or missing references) //IL_07e5: Unknown result type (might be due to invalid IL or missing references) //IL_080b: Unknown result type (might be due to invalid IL or missing references) //IL_084c: 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 = 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, Array.Empty()); 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 = def.materialInst ?? def.material; if ((Object)(object)val == (Object)null) { return; } string text = ((Object)val).name.Split(' ')[0]; string text2 = 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); } Process.Start(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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Invalid comparison between Unknown and I4 //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Expected O, but got Unknown //IL_034e: Unknown result type (might be due to invalid IL or missing references) 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, (RenderTextureReadWrite)1); Texture2D val; try { RenderTexture.active = temporary; GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)num8, 0f, (float)num9); TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(1f, 0f, 0f, 1f)); TexUtil.RotateMaterial.SetTexture("_MainTex", atlas); TexUtil.RotateMaterial.SetPass(0); 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_008c: 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_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Invalid comparison between Unknown and I4 //IL_01f7: 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_022f: 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_0264: 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_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_053b: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Invalid comparison between Unknown and I4 //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_08a0: Unknown result type (might be due to invalid IL or missing references) //IL_08d3: Unknown result type (might be due to invalid IL or missing references) //IL_09ae: 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_08f6: Unknown result type (might be due to invalid IL or missing references) //IL_0928: Unknown result type (might be due to invalid IL or missing references) //IL_097e: Unknown result type (might be due to invalid IL or missing references) //IL_09a2: Unknown result type (might be due to invalid IL or missing references) //IL_0a19: Unknown result type (might be due to invalid IL or missing references) //IL_0a3d: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) //IL_0de5: Unknown result type (might be due to invalid IL or missing references) //IL_0dea: 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 = 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(Array.Empty()); 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 + 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()); if (GUILayout.Button(Loc.T("Edit Current Sprite"), GUIHelper.ButtonStyle, Array.Empty())) { OpenSpriteForEdit(coll, val); } GUI.enabled = clip != null; if (GUILayout.Button(Loc.T("Detach for This Clip"), GUIHelper.ButtonStyle, Array.Empty())) { 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)) { Process.Start(text8); } } } GUI.enabled = 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.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; if (GUILayout.Button(Loc.T("Ready All for Edit"), GUIHelper.ButtonStyle, Array.Empty())) { int num2 = 0; for (int num3 = 0; num3 < clip.frames.Length; num3++) { tk2dSpriteAnimationFrame val3 = clip.frames[num3]; 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)(val4.materialInst ?? val4.material)).name.Split(' ')[0]; string text13 = Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection).name, text12, val4.name + ".png"); if (File.Exists(text13)) { num2++; 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); num2++; } } Plugin.Logger.LogInfo((object)$"[AnimCtrl] Copied {num2} sprites from '{clip.name}' ({clip.frames.Length} frames) to {SpriteLoader.LoadPath}"); for (int num4 = 0; num4 < clip.frames.Length; num4++) { tk2dSpriteAnimationFrame val5 = clip.frames[num4]; 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)(val6.materialInst ?? val6.material)).name.Split(' ')[0]; string text15 = Path.Combine(SpriteLoader.LoadPath, ((Object)spriteCollection2).name, path2, val6.name + ".png"); if (File.Exists(text15)) { Process.Start(text15); } } } _anchorCacheKey = null; } 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)) { Process.Start(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)) { Process.Start(text17); } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(Loc.T("Compile Animation from Workspace"), GUIHelper.ButtonStyle, Array.Empty())) { AnimCanvasCompile.CompileClip(clip); } Color backgroundColor = 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 = backgroundColor; GUILayout.EndHorizontal(); 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 static string _cachedLangString; 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; 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_0009: 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) return _cachedLangString ?? (_cachedLangString = ((object)Language.CurrentLanguage()/*cast due to .constrained prefix*/).ToString()); } 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.ContainsKey(lang) && TextCache[lang].ContainsKey(sheet)) { Plugin.Logger.LogDebug((object)("Invalidating text cache for sheet: " + sheet + ", lang: " + lang)); TextCache[lang].Remove(sheet); } } 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() { 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() { _cachedLangString = null; Loc.OnGameLanguageChanged(); } private static void GetTextPostfix(string key, string sheetTitle, ref string __result) { if (_dumpInProgress) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { string text = CurrentLangStringCached(); if (!TextCache.TryGetValue(text, out var value)) { value = new Dictionary>(); TextCache[text] = value; } if (!value.TryGetValue(sheetTitle, out var value2)) { value2 = (value[sheetTitle] = LoadTextSheet(sheetTitle, text)); } if (value2.TryGetValue(key, out var value3)) { __result = value3; RequestedOverrideKeys.Add(text + "|" + sheetTitle + "|" + key); } DialogueHandler.OnTextAccessed?.Invoke(sheetTitle, key, __result); } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Language.Get", heapBefore); } } private static Dictionary LoadTextSheet(string sheetTitle, string lang) { Dictionary dictionary = 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; } } } foreach (KeyValuePair item2 in LoadTextSheet(sheetTitle, lang, TextLoadPath)) { 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; foreach (string allOverrideKey in AllOverrideKeys) { if (!RequestedOverrideKeys.Contains(allOverrideKey)) { string[] array = allOverrideKey.Split('|', 3); string text = array[0]; string text2 = array[1]; string text3 = array[2]; string text4 = CurrentLangStringCached(); if (!(text != text4)) { 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; } } namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } } namespace Stitchwork { [BepInPlugin("Stitchwork", "Stitchwork", "0.8.0")] [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; 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 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_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: 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.0"); 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) { 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.")); } } LogInstallRoot(); PackManager.Initialize(); TexUtil.Initialize(); InitializeFolders(); AudioFileWatcher = new AudioFileWatcher(); SpriteFileWatcher = new SpriteFileWatcher(); TextFileWatcher = new TextFileWatcher(); 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); 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(); 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(); } AnimationController.ClearAnimators(); DialogueHandler.CheckForStaleKeys(); PackManager.OnSceneLoaded(((Scene)(ref scene)).name); T2DLoader.OnSceneLoaded(); T2DLoader.CheckForUninitializedSprites(); } private void OnSceneUnloaded(Scene scene) { T2DLoader.PruneStaleOriginals(); T2DLoader.PruneSceneState(); T2DLoader.ResetSceneSeed(); } 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(); } private IEnumerator DelayedReapply() { yield return (object)new WaitForSeconds(0.1f); Logger.LogDebug((object)"[SceneLoad] Running deferred ApplyReplacementsInScene (100 ms post-load)."); T2DLoader.ApplyReplacementsInScene(); } 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(); 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(); } 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(); 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(); T2DLoader.IsT2DLogActive = false; AudioHandler.IsAudioBrowserActive = false; try { if (ShowStatusOverlay) { StatusOverlay.Draw(); } DumpProgressOverlay.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 class StitchworkConfig { private readonly ConfigEntry _DumpSprites; private readonly ConfigEntry _ConvertSpritesheets; 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 _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 _AutoEnableIncrementalGC; private readonly ConfigEntry _IncrementalGCTimeSliceMs; private readonly ConfigEntry _EagerFocusReload; private readonly ConfigEntry _DevMode; private readonly ConfigEntry _T2DDiagWatchName; private readonly ConfigEntry _AppliqueProbeKey; 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 _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 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 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 ?? 1024; public bool AutoEnableIncrementalGC => _AutoEnableIncrementalGC?.Value ?? true; public int IncrementalGCTimeSliceMs => _IncrementalGCTimeSliceMs?.Value ?? 0; public bool EagerFocusReload => _EagerFocusReload?.Value ?? false; public bool DevMode => _DevMode?.Value ?? false; public KeyCode AppliqueProbeKey => (KeyCode)(((??)_AppliqueProbeKey?.Value) ?? 0); public bool EnableApplique => _EnableApplique?.Value ?? true; public string T2DDiagWatchName => _T2DDiagWatchName?.Value ?? ""; public bool LogT2DLookupMisses => _LogT2DLookupMisses.Value; public bool VerboseLogging => _VerboseLogging?.Value ?? false; 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 ?? "auto"; } set { if (_UILanguage != null) { _UILanguage.Value = value; } } } internal ConfigEntry UILanguageEntry => _UILanguage; 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_051b: Unknown result type (might be due to invalid IL or missing references) //IL_0525: Expected O, but got Unknown _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"); _DumpText = config.Bind("Dumping", "DumpText", false, "Enable dumping of text when the game starts."); _ConvertSpritesheets = config.Bind("Dumping", "ConvertSpritesheets", false, "Automatically convert modded spritesheets to individual Stitchwork-compatible PNGs."); _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."); _AppliqueProbeKey = config.Bind("Experimental", "AppliqueProbeKey", (KeyCode)288, "EXPERIMENTAL, DevMode only. Appliqué Phase-0 probe (docs/frame-decoupling-design.md): press once in-game to divert a reused animation frame to marker art via both candidate mechanisms and log the gate results ([Applique-P0] lines); press again to revert. Inert unless DevMode is enabled."); _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)."); _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.0"; } } 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}"); ReloadAudio = true; } } public class TextFileWatcher : IDisposable { public FileSystemWatcher TextWatcher; public List PackWatchers = new List(); public static volatile bool ReloadText; public TextFileWatcher() { TextWatcher = CreateWatcher(DialogueHandler.TextLoadPath); foreach (string pluginPackPath in Plugin.PluginPackPaths) { string path = Path.Combine(pluginPackPath, "Text"); if (Directory.Exists(path)) { PackWatchers.Add(CreateWatcher(path)); } } } 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}"); 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; public static bool BridgeAvailable { get { if (_fiHasPreloaded != null) { return _fiIsLoading != null; } 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; } } 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"; public static Result LastResult { get; private set; } private static string BootConfigPath => Path.Combine(Application.dataPath, "boot.config"); 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 at '" + bootConfigPath + "' — cannot auto-enable incremental GC. 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 Entry(string type, string key, string winnerPack, string loserPack) { Type = type; Key = key; WinnerPack = winnerPack; LoserPack = loserPack; } } 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 Record(string type, string key, string winnerPack, string loserPack) { _entries.Add(new Entry(type, key, winnerPack, loserPack)); } 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(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 { 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 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 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) { _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); } _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); } _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; } Plugin.Logger.LogWarning((object)"[GameBridge] No main-menu return handle resolved."); return false; } 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.Message + ". 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.Message)); 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.Message)); 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.Message + ". 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.Message + ". 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.Message)); 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); } } } 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() { 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 (!File.Exists(Path.Combine(Plugin.BasePath, "stitchwork.assetbundle"))) { list.Add(new Item("Asset bundle", Status.Warn, "stitchwork.assetbundle missing next to the DLL — canvas extract + sprite preview broken; reinstall")); } else if ((Object)(object)TexUtil.RotateMaterial == (Object)null) { list.Add(new Item("Asset bundle", Status.Warn, "file present but Rotate.shader failed to load — canvas extract + sprite preview degraded")); } else { list.Add(new Item("Asset bundle", Status.Ok, "Rotate.shader loaded")); } 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[] array3 = new string[5] { "Sprites", "Spritesheets", "Sounds", "Videos", "Text" }; foreach (string path2 in array3) { 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 { } 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 { public static void EnsureDirectoryExists(string path) { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } 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 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 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(); }; } } 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) ? (DialogueHandler.CurrentLangCode ?? "EN").ToLowerInvariant() : 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 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"); } LastSummary = stringBuilder.ToString(); logger.LogInfo((object)"[SelfTest] done."); } 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_010a: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.FloorToInt(uvs.Min((Vector2 uv) => uv.x) * (float)tex.width + 0.25f); int num2 = Mathf.CeilToInt(uvs.Max((Vector2 uv) => uv.x) * (float)tex.width - 0.25f); int num3 = Mathf.FloorToInt(uvs.Min((Vector2 uv) => uv.y) * (float)tex.height + 0.25f); int num4 = Mathf.CeilToInt(uvs.Max((Vector2 uv) => uv.y) * (float)tex.height - 0.25f); int num5 = math.min(num2 - num, tex.width); int num6 = math.min(num4 - num3, tex.height); return new Rect((float)num, (float)num3, (float)num5, (float)num6); } } 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; public static void Initialize() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown RotateMaterial = new Material(AssetBundle.LoadFromFile(Path.Combine(Plugin.BasePath, "stitchwork.assetbundle")).LoadAsset("Assets/Patchwork/Rotate.shader")); } 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) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown byte[] array = FileCache.ReadBytes(path); if (array == null) { return null; } Texture2D val = new Texture2D(2, 2); if (!ImageConversion.LoadImage(val, array)) { Plugin.Logger.LogWarning((object)("LoadFromPNG: Failed to decode image data from '" + path + "'")); return null; } ((Texture)val).wrapMode = (TextureWrapMode)1; val.Apply(false, true); ((Object)val).hideFlags = (HideFlags)32; return val; } 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"); 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) { 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(HeroController.instance?.playerData?.CurrentCrestID, Value), ConditionType.NailUpgrade => NailUpgradeMatches((HeroController.instance?.playerData?.nailUpgrades).GetValueOrDefault(), Value), ConditionType.PlayerData => EvaluatePlayerData(HeroController.instance?.playerData, 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 static readonly string[] ContentRoots = new string[5] { "Sprites", "Spritesheets", "Sounds", "Videos", "Text" }; private const int MaxPerRule = 12; 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); 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); } return list; } 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.0")); 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 class PackInfo { private string _stableId; public string Path { get; } public string Name { get; } public string Author { get; } public string Version { get; } public string Description { get; } public bool IsEnabled { get; set; } public bool IsLocal { 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 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, bool isLocal, string author = null, string version = null, string description = null) { Path = path; Name = name; IsLocal = isLocal; Author = author; Version = version; Description = description; } public PackInfo Clone() { return new PackInfo(Path, Name, IsLocal, Author, Version, Description) { IsEnabled = IsEnabled, Stats = Stats, Conditions = Conditions.Select((PackCondition c) => c.Clone()).ToList(), ReloadTrigger = ReloadTrigger }; } } 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 LocalPacksPath => Path.Combine(Plugin.BasePath, "Packs"); 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() { IOUtil.EnsureDirectoryExists(LocalPacksPath); 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, isLocal: false)); } } } if (Directory.Exists(LocalPacksPath)) { packSubfolderNames = Directory.GetDirectories(LocalPacksPath); foreach (string path in packSubfolderNames) { list.Add(ReadManifest(path, isLocal: true)); } } return list; } 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, bool isLocal) { 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, isLocal); } 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), isLocal, 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), isLocal); } } 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) { if (GameBridge.TriggerSceneReloadCurrent()) { Plugin.Logger.LogInfo((object)"[PackManager] Apply: watcher flags raised + scene reload dispatched via GameBridge."); } else { Plugin.Logger.LogInfo((object)"[PackManager] Apply: watcher flags raised (GameBridge scene reload unavailable — in-place only)."); } } 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.SpriteFileWatcher?.RebuildPackPngWatchers(); Plugin.AudioFileWatcher?.RebuildPackWatchers(); FileCache.Clear(); SpriteFileWatcher.ReloadSprites = true; SpriteFileWatcher.ReloadT2DSprites = true; AudioFileWatcher.ReloadAudio = true; TextFileWatcher.ReloadText = 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(packPath, "Spritesheets", "*.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; } } } 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; } 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_041c: Unknown result type (might be due to invalid IL or missing references) //IL_04fc: Unknown result type (might be due to invalid IL or missing references) //IL_0501: Unknown result type (might be due to invalid IL or missing references) //IL_0761: Unknown result type (might be due to invalid IL or missing references) //IL_0768: Expected O, but got Unknown //IL_078d: Unknown result type (might be due to invalid IL or missing references) //IL_07ce: Unknown result type (might be due to invalid IL or missing references) //IL_0815: Unknown result type (might be due to invalid IL or missing references) //IL_0847: Unknown result type (might be due to invalid IL or missing references) //IL_084c: Unknown result type (might be due to invalid IL or missing references) //IL_08be: Unknown result type (might be due to invalid IL or missing references) //IL_0a2f: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0324: 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) if (clip?.frames == null || clip.frames.Length == 0) { 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) { continue; } tk2dSpriteDefinition val2 = spriteCollection.spriteDefinitions[val.spriteId]; if (string.IsNullOrEmpty(val2.name) || val2.positions == null || val2.positions.Length < 4 || val2.uvs == null || val2.uvs.Length < 4 || val2.texelSize.x <= 0f || val2.texelSize.y <= 0f || !hashSet.Add(val2.name)) { continue; } Texture atlas = null; Vector2[] uvs = null; Vector3[] positions = null; if (fromCurrentRender || !SpriteLoader.TryGetVanilla(spriteCollection, val2, out atlas, out uvs, out positions)) { Material obj = val2.materialInst ?? val2.material; atlas = ((obj != null) ? obj.mainTexture : null); uvs = val2.uvs; positions = val2.positions; } if (!((Object)(object)atlas == (Object)null) && uvs != null && uvs.Length >= 4 && positions != null && positions.Length >= 4) { string materialName = ((val2.materialId >= 0 && spriteCollection.materials != null && val2.materialId < spriteCollection.materials.Length) ? ((Object)spriteCollection.materials[val2.materialId]).name.Split(' ')[0] : ""); frames.Add(new FrameGeom { SpriteName = val2.name, CollectionName = ((Object)spriteCollection).name, MaterialName = materialName, Atlas = atlas, Uvs = uvs, Positions = positions, TexelSize = val2.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 = val2.flipped }); } } 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)."); } 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 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.")); } int num5 = (int)((num2 - num) / texel.x) + 1; int num6 = (int)((num4 - num3) / texel.y) + 1; int num7 = Mathf.Max(Mathf.CeilToInt(marginFraction * (float)num5), marginFloorPx); int num8 = Mathf.Max(Mathf.CeilToInt(marginFraction * (float)num6), marginFloorPx); int num9 = num5 + 2 * num7; int num10 = num6 + 2 * num8; int num11 = (int)((0f - num) / texel.x) + num7; int num12 = (int)((0f - num3) / texel.y) + num8; 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: 5"); stringBuilder.AppendLine($"issuedCanvas: {{ w: {num9}, h: {num10} }}"); stringBuilder.AppendLine($"pivotInCanvas: {{ x: {num9 - 1 - num11}, y: {num12} }}"); stringBuilder.AppendLine("pixelsPerUnit: " + (1f / texel.x).ToString("0.####", invariantCulture)); stringBuilder.AppendLine("marginFraction: " + marginFraction.ToString(invariantCulture)); stringBuilder.AppendLine("frames:"); float num13 = 1f / texel.x; float num14 = 1f / texel.y; RenderTexture active = RenderTexture.active; RenderTexture temporary = RenderTexture.GetTemporary(num9, num10, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)1); Texture2D val3 = new Texture2D(num9, num10, (TextureFormat)4, false); Dictionary dictionary = new Dictionary(); TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(1f, 0f, 0f, 1f)); try { int num15 = 0; foreach (FrameGeom item in frames) { RenderTexture pointAtlasRT = GetPointAtlasRT(item.Atlas, dictionary); RenderTexture.active = temporary; GL.Clear(true, true, Color.clear); GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)num9, 0f, (float)num10); TexUtil.RotateMaterial.SetTexture("_MainTex", (Texture)(object)pointAtlasRT); TexUtil.RotateMaterial.SetPass(0); GL.Begin(7); GL.Color(Color.white); int[] array = new int[4] { 0, 1, 3, 2 }; foreach (int num16 in array) { GL.TexCoord(Vector2.op_Implicit(item.Uvs[num16])); GL.Vertex3((float)num11 + item.Positions[num16].x * num13, (float)num12 + item.Positions[num16].y * num14, 0f); } GL.End(); GL.PopMatrix(); val3.ReadPixels(new Rect(0f, 0f, (float)num9, (float)num10), 0, 0); val3.Apply(); FlipHorizontal(val3, num9, num10); string text2 = $"{num15:D3}_{T2DUtil.SanitizeForFilesystem(item.SpriteName)}.png"; File.WriteAllBytes(Path.Combine(text, text2), ImageConversion.EncodeToPNG(val3)); int num17 = Mathf.RoundToInt((float)num11 + item.PxMin * num13); int num18 = Mathf.RoundToInt((float)num11 + item.PxMax * num13) - num17; int num19 = num9 - num17 - num18; int num20 = Mathf.RoundToInt((float)num12 + item.PyMin * num14); int num21 = Mathf.RoundToInt((float)num12 + item.PyMax * num14) - num20; stringBuilder.AppendLine(" - spriteName: \"" + item.SpriteName + "\""); stringBuilder.AppendLine(" collection: \"" + item.CollectionName + "\""); stringBuilder.AppendLine(" material: \"" + item.MaterialName + "\""); stringBuilder.AppendLine(" dumpedFile: \"" + text2 + "\""); stringBuilder.AppendLine($" canvasRect: {{ x: {num19}, y: {num20}, w: {num18}, h: {num21} }}"); stringBuilder.AppendLine($" flipped: {item.Flipped}"); num15++; } } 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(Path.Combine(text, path), stringBuilder.ToString()); Plugin.Logger.LogInfo((object)($"[AnimWIP] '{clip.name}': {frames.Count} frame(s) → {num9}×{num10}, pivot ({num11},{num12}) " + "[source: " + (fromCurrentRender ? "current render" : "vanilla") + "] → " + text)); return text; } 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 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"; } private const byte AlphaThreshold = 16; private const int StrayTolerance = 2; 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)) { Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip?.name + "': no WIP folder — extract first.")); return list; } string text2 = 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; } text2 = clip.name; } string path = Path.Combine(text, T2DUtil.SanitizeForFilesystem(clip.name) + "_sidecar.yml"); if (!File.Exists(path)) { Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip.name + "': sidecar missing — re-extract.")); return list; } Sidecar sidecar = ParseSidecar(File.ReadAllLines(path)); if (sidecar == null || sidecar.CanvasW <= 0 || sidecar.CanvasH <= 0) { Plugin.Logger.LogWarning((object)("[AnimCompile] '" + clip.name + "': sidecar unparseable or missing canvas dims.")); return list; } Plugin.Logger.LogInfo((object)($"[AnimCompile] '{clip.name}': compiling {sidecar.Frames.Count} frame(s) from {text}" + ((text2 != null) ? (" → per-clip (Clips/" + text2 + "/)") : ""))); foreach (FrameEntry frame in sidecar.Frames) { FrameOutcome item = CompileFrame(text, sidecar, frame, text2); list.Add(item); Plugin.Logger.LogInfo((object)("[AnimCompile] " + item.Sprite + ": " + item.Kind + " — " + item.Detail)); } int num = 0; int num2 = 0; int num3 = 0; foreach (FrameOutcome item2 in list) { if (item2.Kind == "inbounds") { num++; } else if (item2.Kind == "spill") { num2++; } else { num3++; } } Plugin.Logger.LogInfo((object)$"[AnimCompile] '{clip.name}': done — {num} in-bounds, {num2} spill, {num3} skipped."); return list; } private static FrameOutcome CompileFrame(string dir, Sidecar sc, FrameEntry fe, string clipDest = null) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown string path = Path.Combine(dir, fe.DumpedFile); if (!File.Exists(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(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(); int num3 = 0; 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++) { if (pixels[i * width + j].a >= 16) { 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); File.WriteAllBytes(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"; File.WriteAllBytes(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 }; } 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_0018: Expected O, but got Unknown //IL_008b: 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_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00a9: Expected O, but got Unknown //IL_0066: 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) if (w <= 0 || h <= 0) { return ImageConversion.EncodeToPNG(new Texture2D(1, 1, (TextureFormat)4, false)); } 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 val = new Texture2D(w, h, (TextureFormat)4, false); val.SetPixels32(array); val.Apply(); byte[] result = ImageConversion.EncodeToPNG(val); Object.Destroy((Object)val); return result; } 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(); } } } 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]"; 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 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 void IndexFile(string rel, string fullPath, string sourcePack) { string[] array = rel.Split('/'); if (array.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)) { ConflictTracker.Record("clip", key, value.SourcePack, sourcePack); 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) { string arg = ((list2.Count > 0) ? string.Format("; {0} clip name(s) not loaded yet ({1}…)", list2.Distinct(StringComparer.OrdinalIgnoreCase).Count(), string.Join(", ", list2.Distinct(StringComparer.OrdinalIgnoreCase).Take(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_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: 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_00fd: Expected O, but got Unknown //IL_016d: 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) //IL_017f: Expected O, but got Unknown //IL_01c1: 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_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: 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_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: 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_0284: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Invalid comparison between Unknown and I4 //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: 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_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: 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_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_058b: Unknown result type (might be due to invalid IL or missing references) //IL_0631: 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_06c9: Unknown result type (might be due to invalid IL or missing references) //IL_06d0: Expected O, but got Unknown //IL_0669: Unknown result type (might be due to invalid IL or missing references) //IL_066e: Unknown result type (might be due to invalid IL or missing references) //IL_078a: Unknown result type (might be due to invalid IL or missing references) //IL_07a3: 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_07b7: Unknown result type (might be due to invalid IL or missing references) //IL_07b9: Unknown result type (might be due to invalid IL or missing references) //IL_07c0: Unknown result type (might be due to invalid IL or missing references) //IL_07c2: Unknown result type (might be due to invalid IL or missing references) //IL_07d6: Unknown result type (might be due to invalid IL or missing references) //IL_07d8: Unknown result type (might be due to invalid IL or missing references) //IL_07df: Unknown result type (might be due to invalid IL or missing references) //IL_07e1: Unknown result type (might be due to invalid IL or missing references) byte[] array; try { array = File.ReadAllBytes(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; } } internal static class AppliquePhase0 { internal const string ShadowNamePrefix = "Stitchwork_Shadow"; private static bool _applied; private static tk2dSpriteAnimator _animator; private static tk2dSpriteAnimationClip _clipB; private static tk2dSpriteCollectionData _shadow; private static readonly List<(tk2dSpriteAnimationClip clip, int idx, tk2dSpriteCollectionData coll, int id)> _rewritten = new List<(tk2dSpriteAnimationClip, int, tk2dSpriteCollectionData, int)>(); private static tk2dSpriteCollectionData _apColl; private static tk2dSpriteDefinition[] _apOrigDefs; private static tk2dSpriteAnimationClip _clipA2; private static bool _gate2BSeen; private static bool _gate2ASeen; public static void Toggle() { if (_applied) { Revert(); } else { Apply(); } } private static void Apply() { //IL_048d: Unknown result type (might be due to invalid IL or missing references) _rewritten.Clear(); _gate2BSeen = (_gate2ASeen = false); tk2dSpriteAnimator[] source = Object.FindObjectsByType((FindObjectsSortMode)0); _animator = ((IEnumerable)source).FirstOrDefault((Func)((tk2dSpriteAnimator a) => (Object)(object)a != (Object)null && ((Object)a).name == "Hero_Hornet(Clone)" && (Object)(object)a.Library != (Object)null)) ?? ((IEnumerable)source).FirstOrDefault((Func)((tk2dSpriteAnimator a) => (Object)(object)a != (Object)null && (Object)(object)a.Library != (Object)null)); if ((Object)(object)_animator == (Object)null) { Plugin.Logger.LogWarning((object)"[Applique-P0] No tk2dSpriteAnimator with a Library in scene — stand in-game and retry."); return; } tk2dSpriteAnimation library = _animator.Library; Plugin.Logger.LogInfo((object)$"[Applique-P0] Target animator '{((Object)_animator).name}', library '{((Object)library).name}', {library.clips.Length} clips."); int num = 0; for (int num2 = 0; num2 < library.clips.Length; num2++) { for (int num3 = num2 + 1; num3 < library.clips.Length; num3++) { if (library.clips[num2]?.frames != null && library.clips[num2].frames == library.clips[num3]?.frames) { num++; } } } Plugin.Logger.LogInfo((object)((num == 0) ? "[Applique-P0] GATE 5 PASS: no clip shares a frames[] array with another — per-clip rewrite is isolated by construction." : $"[Applique-P0] GATE 5 FAIL-ISH: {num} clip pair(s) share a frames[] array reference — rewrite needs deep-clone mitigation.")); Dictionary<(tk2dSpriteCollectionData, int), List<(tk2dSpriteAnimationClip, int)>> dictionary = new Dictionary<(tk2dSpriteCollectionData, int), List<(tk2dSpriteAnimationClip, int)>>(); tk2dSpriteAnimationClip[] clips = library.clips; foreach (tk2dSpriteAnimationClip val in clips) { if (val?.frames == null) { continue; } for (int num5 = 0; num5 < val.frames.Length; num5++) { tk2dSpriteAnimationFrame val2 = val.frames[num5]; if (!((Object)(object)val2.spriteCollection == (Object)null)) { (tk2dSpriteCollectionData, int) key = (val2.spriteCollection, val2.spriteId); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<(tk2dSpriteAnimationClip, int)>()); } value.Add((val, num5)); } } } List>> list2 = (from kv in dictionary.Where>>((KeyValuePair<(tk2dSpriteCollectionData coll, int id), List<(tk2dSpriteAnimationClip clip, int idx)>> kv) => kv.Value.Select(((tk2dSpriteAnimationClip clip, int idx) u) => u.clip).Distinct().Count() >= 2).Where>>(delegate(KeyValuePair<(tk2dSpriteCollectionData coll, int id), List<(tk2dSpriteAnimationClip clip, int idx)>> kv) { tk2dSpriteDefinition[] spriteDefinitions = kv.Key.coll.spriteDefinitions; if (kv.Key.id < 0 || kv.Key.id >= spriteDefinitions.Length) { return false; } tk2dSpriteDefinition val7 = spriteDefinitions[kv.Key.id]; if (val7 != null && !val7.complexGeometry) { Vector3[] positions = val7.positions; if (positions != null && positions.Length == 4) { Vector2[] uvs = val7.uvs; if (uvs != null && uvs.Length == 4 && val7.texelSize.x > 0f) { return val7.texelSize.y > 0f; } } } return false; }) orderby (int)kv.Key.coll.spriteDefinitions[kv.Key.id].flipped == 0 descending select kv).ToList(); if (list2.Count == 0) { Plugin.Logger.LogWarning((object)"[Applique-P0] No reused simple-geometry frame found in this library — try another scene/character."); return; } KeyValuePair<(tk2dSpriteCollectionData, int), List<(tk2dSpriteAnimationClip, int)>> keyValuePair = list2[0]; tk2dSpriteCollectionData item = keyValuePair.Key.Item1; tk2dSpriteDefinition val3 = item.spriteDefinitions[keyValuePair.Key.Item2]; List> list3 = (from u in keyValuePair.Value group u by u.clip).ToList(); _clipB = (list3.FirstOrDefault((IGrouping g) => g.Key != _animator.CurrentClip) ?? list3[0]).Key; string text = string.Join(", ", from g in list3 where g.Key != _clipB select "'" + g.Key.name + "'"); _shadow = BuildShadowCollection(item, val3); if ((Object)(object)_shadow == (Object)null) { return; } foreach (var (val4, num6) in keyValuePair.Value.Where<(tk2dSpriteAnimationClip, int)>(((tk2dSpriteAnimationClip clip, int idx) u) => u.clip == _clipB)) { _rewritten.Add((val4, num6, val4.frames[num6].spriteCollection, val4.frames[num6].spriteId)); val4.frames[num6].spriteCollection = _shadow; val4.frames[num6].spriteId = 0; } Plugin.Logger.LogInfo((object)("[Applique-P0] GATE 1 PASS (B): shadow collection built; sprite '" + val3.name + "' " + $"(flip {val3.flipped}) diverted in clip '{_clipB.name}' ({keyValuePair.Value.Count<(tk2dSpriteAnimationClip, int)>(((tk2dSpriteAnimationClip clip, int idx) u) => u.clip == _clipB)} frame(s)). " + "LOOK FOR: magenta X marker while '" + _clipB.name + "' plays; UNCHANGED art in " + text + " (gate 3).")); KeyValuePair<(tk2dSpriteCollectionData, int), List<(tk2dSpriteAnimationClip, int)>> keyValuePair2 = list2.Skip(1).FirstOrDefault>>((KeyValuePair<(tk2dSpriteCollectionData coll, int id), List<(tk2dSpriteAnimationClip clip, int idx)>> kv) => kv.Value.All(((tk2dSpriteAnimationClip clip, int idx) u) => u.clip != _clipB)); if ((Object)(object)keyValuePair2.Key.Item1 != (Object)null) { _apColl = keyValuePair2.Key.Item1; tk2dSpriteDefinition srcDef2 = _apColl.spriteDefinitions[keyValuePair2.Key.Item2]; tk2dSpriteDefinition val5 = ((IEnumerable)_apColl.spriteDefinitions).FirstOrDefault((Func)delegate(tk2dSpriteDefinition d) { if (d != null && d != srcDef2 && !d.complexGeometry) { Vector3[] positions = d.positions; if (positions != null && positions.Length == 4) { Vector2[] uvs = d.uvs; if (uvs != null) { return uvs.Length == 4; } return false; } } return false; }); if (val5 != null) { _apOrigDefs = _apColl.spriteDefinitions; tk2dSpriteDefinition[] array = (tk2dSpriteDefinition[])(object)new tk2dSpriteDefinition[_apOrigDefs.Length + 1]; _apOrigDefs.CopyTo(array, 0); array[_apOrigDefs.Length] = val5; _apColl.spriteDefinitions = array; int num7 = _apOrigDefs.Length; List> list4 = (from u in keyValuePair2.Value group u by u.clip).ToList(); _clipA2 = (list4.FirstOrDefault((IGrouping g) => g.Key != _animator.CurrentClip) ?? list4[0]).Key; foreach (var (val6, num8) in keyValuePair2.Value.Where<(tk2dSpriteAnimationClip, int)>(((tk2dSpriteAnimationClip clip, int idx) u) => u.clip == _clipA2)) { _rewritten.Add((val6, num8, val6.frames[num8].spriteCollection, val6.frames[num8].spriteId)); val6.frames[num8].spriteId = num7; } Plugin.Logger.LogInfo((object)($"[Applique-P0] GATE 1 PASS-SO-FAR (A): spriteDefinitions {_apOrigDefs.Length} → {array.Length} on " + $"'{((Object)_apColl).name}'; clip '{_clipA2.name}' frame(s) → appended id {num7} (donor art '{val5.name}'). " + "LOOK FOR: '" + val5.name + "''s art inside '" + _clipA2.name + "' where '" + srcDef2.name + "' used to show. If tk2d rejects the longer array, errors will follow this line.")); } else { Plugin.Logger.LogInfo((object)"[Applique-P0] Variant A skipped: no donor def found."); } } else { Plugin.Logger.LogInfo((object)"[Applique-P0] Variant A skipped: no second reused candidate outside clip B."); } _applied = true; Plugin.Logger.LogInfo((object)"[Applique-P0] APPLIED. Trigger the named clips; watch for GATE 2 lines. Press the key again to revert (gate 4)."); } private static tk2dSpriteCollectionData BuildShadowCollection(tk2dSpriteCollectionData srcColl, tk2dSpriteDefinition srcDef) { //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0153: 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_029a: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: 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_02c5: 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) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_032c: 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_0354: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_036a: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Expected O, but got Unknown //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03cd: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Expected O, but got Unknown float num = srcDef.positions.Min((Vector3 p) => p.x); float num2 = srcDef.positions.Max((Vector3 p) => p.x); float num3 = srcDef.positions.Min((Vector3 p) => p.y); float num4 = srcDef.positions.Max((Vector3 p) => p.y); int num5 = Mathf.Max(2, Mathf.RoundToInt((num2 - num) / srcDef.texelSize.x)); int num6 = Mathf.Max(2, Mathf.RoundToInt((num4 - num3) / srcDef.texelSize.y)); if (num5 > 2048 || num6 > 2048) { Plugin.Logger.LogWarning((object)$"[Applique-P0] Marker {num5}x{num6} implausible — aborting B."); return null; } Texture2D mainTexture = BuildMarkerTexture(num5, num6); Material val = new Material(((Object)(object)srcDef.materialInst != (Object)null) ? srcDef.materialInst : srcDef.material) { mainTexture = (Texture)(object)mainTexture }; ((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x20); float num7 = (srcDef.uvs.Min((Vector2 u) => u.x) + srcDef.uvs.Max((Vector2 u) => u.x)) * 0.5f; float num8 = (srcDef.uvs.Min((Vector2 u) => u.y) + srcDef.uvs.Max((Vector2 u) => u.y)) * 0.5f; float num9 = 0.5f / (float)num5; float num10 = 0.5f / (float)num6; Vector2[] array = (Vector2[])(object)new Vector2[4]; for (int num11 = 0; num11 < 4; num11++) { array[num11] = new Vector2((srcDef.uvs[num11].x > num7) ? (1f - num9) : num9, (srcDef.uvs[num11].y > num8) ? (1f - num10) : num10); } tk2dSpriteDefinition val2 = new tk2dSpriteDefinition { name = srcDef.name + "#applique", material = val, materialInst = val, materialId = 0, positions = (Vector3[])srcDef.positions.Clone(), uvs = array, normals = ((srcDef.normals != null) ? ((Vector3[])srcDef.normals.Clone()) : null), tangents = ((srcDef.tangents != null) ? ((Vector4[])srcDef.tangents.Clone()) : null), indices = ((srcDef.indices != null) ? ((int[])srcDef.indices.Clone()) : null), complexGeometry = false, flipped = (FlipMode)0, texelSize = new Vector2((num2 - num) / (float)num5, (num4 - num3) / (float)num6), boundsData = ((srcDef.boundsData != null) ? ((Vector3[])srcDef.boundsData.Clone()) : null), untrimmedBoundsData = ((srcDef.untrimmedBoundsData != null) ? ((Vector3[])srcDef.untrimmedBoundsData.Clone()) : null) }; GameObject val3 = new GameObject("Stitchwork_Shadow_" + ((Object)srcColl).name); Object.DontDestroyOnLoad((Object)val3); tk2dSpriteCollectionData val4 = val3.AddComponent(); val4.spriteDefinitions = (tk2dSpriteDefinition[])(object)new tk2dSpriteDefinition[1] { val2 }; val4.materials = (Material[])(object)new Material[1] { val }; val4.premultipliedAlpha = srcColl.premultipliedAlpha; return val4; } private static Texture2D BuildMarkerTexture(int w, int h) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_0094: 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_0095: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(w, h, (TextureFormat)4, false); ((Object)val).hideFlags = (HideFlags)32; ((Texture)val).filterMode = (FilterMode)0; Color32 val2 = default(Color32); ((Color32)(ref val2))..ctor(byte.MaxValue, (byte)0, (byte)200, byte.MaxValue); Color32 val3 = default(Color32); ((Color32)(ref val3))..ctor((byte)20, (byte)20, (byte)20, byte.MaxValue); Color32[] array = (Color32[])(object)new Color32[w * h]; for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { bool flag = j < 2 || i < 2 || j >= w - 2 || i >= h - 2; bool flag2 = ((j + i) & 0xF) < 3; array[i * w + j] = ((flag || flag2) ? val3 : val2); } } val.SetPixels32(array); val.Apply(false, false); return val; } public static void Tick() { if (!_applied || (Object)(object)_animator == (Object)null) { return; } tk2dBaseSprite component = ((Component)_animator).GetComponent(); if (!((Object)(object)component == (Object)null)) { if (!_gate2BSeen && (Object)(object)_shadow != (Object)null && (Object)(object)component.Collection == (Object)(object)_shadow) { _gate2BSeen = true; Plugin.Logger.LogInfo((object)("[Applique-P0] GATE 2 PASS (B): live sprite is bound to the shadow collection during '" + _animator.CurrentClip?.name + "'. If the magenta marker is visible and correctly placed, gates 2+3 hold for the shadow variant.")); } if (!_gate2ASeen && (Object)(object)_apColl != (Object)null && _apOrigDefs != null && (Object)(object)component.Collection == (Object)(object)_apColl && component.spriteId >= _apOrigDefs.Length) { _gate2ASeen = true; Plugin.Logger.LogInfo((object)$"[Applique-P0] GATE 2 PASS (A): live sprite resolved an APPENDED spriteId ({component.spriteId}) during '{_animator.CurrentClip?.name}' — append+rewrite accepted."); } } } private static void Revert() { foreach (var (val, num, spriteCollection, spriteId) in _rewritten) { val.frames[num].spriteCollection = spriteCollection; val.frames[num].spriteId = spriteId; } int count = _rewritten.Count; _rewritten.Clear(); if ((Object)(object)_apColl != (Object)null && _apOrigDefs != null) { _apColl.spriteDefinitions = _apOrigDefs; Plugin.Logger.LogInfo((object)$"[Applique-P0] Variant A reverted: '{((Object)_apColl).name}' spriteDefinitions restored to {_apOrigDefs.Length}."); } _apColl = null; _apOrigDefs = null; _clipA2 = null; _shadow = null; _clipB = null; _applied = false; Plugin.Logger.LogInfo((object)$"[Applique-P0] GATE 4: reverted {count} frame entr(ies). Replay the clips — vanilla art must be back everywhere."); } } 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 _completedAwaitingApply = new HashSet(StringComparer.OrdinalIgnoreCase); private static bool _applySweepScheduled; public static bool HasAudioReplacements { get { if (_indexBuilt) { return _soundIndex.Count > 0; } return false; } } 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 void PlayHelperPatch(AudioSource source, ulong delay) { if (_inAudioPlayChain || IsDiagnosticSource(source)) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)source.clip != (Object)null) { LogAudioCall("PlayHelper", source.clip, source); AudioHandler.OnAudioPlayed?.Invoke(source.clip, source); LoadAudio(source); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.PlayHelper", heapBefore); } } 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 void PlayInstancePrefix(AudioSource __instance) { if (IsDiagnosticSource(__instance) || _inAudioPlayChain) { return; } _inAudioPlayChain = true; if ((Object)(object)__instance == (Object)null || (Object)(object)__instance.clip == (Object)null) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { LogAudioCall("Play", __instance.clip, __instance); AudioHandler.OnAudioPlayed?.Invoke(__instance.clip, __instance); LoadAudio(__instance); } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.Play", heapBefore); } } public static void SourcePlayPostfix() { _inAudioPlayChain = false; } public static void PlayOneShotHelperPatch(AudioSource source, ref AudioClip clip, float volumeScale) { if (_inAudioPlayChain) { return; } long heapBefore = DevProfiler.SnapshotHeapBytes(); try { if ((Object)(object)clip != (Object)null) { LogAudioCall("PlayOneShot", clip, source); AudioHandler.OnAudioPlayed?.Invoke(clip, source); LoadAudio(ref clip); } } finally { DevProfiler.AddOpPositiveNetHeapDelta("Postfix.Audio.PlayOneShot", heapBefore); } } 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 (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)) { 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)) { 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.Message)); } } 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++; } 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); 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 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 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)) { 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 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 { IOUtilEnsureDir(outputPath); File.WriteAllText(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.0"); 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_0618: Unknown result type (might be due to invalid IL or missing references) //IL_061d: Unknown result type (might be due to invalid IL or missing references) //IL_0572: Unknown result type (might be due to invalid IL or missing references) //IL_0577: 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_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_0587: Unknown result type (might be due to invalid IL or missing references) //IL_0595: 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) //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: 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; } bool flag = false; try { PropertyInfo property = type.GetProperty("enabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { flag = (bool)property.GetValue(val3); } } catch { } sb.AppendLine($" '{TPath(val3.transform)}' enabled={flag}"); } num += array6.Length; } if (num == 0) { sb.AppendLine(" (no PostProcessVolume / Volume / PostProcessLayer found)"); } } catch (Exception ex) { sb.AppendLine(" [error: " + ex.Message + "]"); } sb.AppendLine(); sb.AppendLine("[Shader Globals — speculative probe]"); sb.AppendLine(" (probed by candidate names; non-default values are signal)"); bool flag2 = false; array5 = CandidateGlobalNames; foreach (string text2 in array5) { try { Color globalColor = Shader.GetGlobalColor(text2); if (globalColor.r != 0f || globalColor.g != 0f || globalColor.b != 0f || globalColor.a != 0f) { sb.AppendLine(" GlobalColor '" + text2 + "' = " + C(globalColor)); flag2 = true; } } catch { } try { float globalFloat = Shader.GetGlobalFloat(text2); if (Math.Abs(globalFloat) > 0.0001f) { sb.AppendLine($" GlobalFloat '{text2}' = {globalFloat:0.000}"); flag2 = true; } } catch { } try { Vector4 globalVector = Shader.GetGlobalVector(text2); if (((Vector4)(ref globalVector)).sqrMagnitude > 0.0001f) { sb.AppendLine(" GlobalVector '" + text2 + "' = " + ((Vector4)(ref globalVector)).ToString("F3")); flag2 = true; } } catch { } } if (!flag2) { 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(); } private static void IOUtilEnsureDir(string filePath) { string directoryName = Path.GetDirectoryName(filePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } } } public static class MaterialNeutralizer { private struct RendererSnapshot { public Renderer Renderer; public MaterialPropertyBlock Mpb; } private static readonly Dictionary _snapshots = new Dictionary(); private static readonly Dictionary _appliedPaths = new Dictionary(); public static string LastStatus = ""; public static int OverriddenCount => _snapshots.Count; public static int ApplyToRenderersByPath(string pathSubstring) { if (string.IsNullOrWhiteSpace(pathSubstring)) { LastStatus = "Empty path — nothing to do"; return 0; } int num = 0; StringBuilder stringBuilder = new StringBuilder(); Renderer[] array = Resources.FindObjectsOfTypeAll(); foreach (Renderer val in array) { if ((Object)(object)val == (Object)null) { continue; } string text = TransformPath(((Component)val).transform); if (text.IndexOf(pathSubstring, StringComparison.OrdinalIgnoreCase) < 0) { continue; } ApplyTo(val, text); num++; if (stringBuilder.Length < 256) { if (stringBuilder.Length > 0) { stringBuilder.Append("; "); } stringBuilder.Append(text); } } LastStatus = ((num == 0) ? ("No renderers matched '" + pathSubstring + "'") : $"Applied to {num} renderer(s). First few: {stringBuilder}"); Plugin.Logger.LogInfo((object)("[MaterialNeutralizer] " + LastStatus)); return num; } public static void ApplyTo(Renderer r, string pathHint = null) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown //IL_00dd: 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_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown if (!((Object)(object)r == (Object)null)) { int instanceID = ((Object)r).GetInstanceID(); if (!_snapshots.ContainsKey(instanceID)) { MaterialPropertyBlock val = new MaterialPropertyBlock(); r.GetPropertyBlock(val); _snapshots[instanceID] = new RendererSnapshot { Renderer = r, Mpb = val }; _appliedPaths[instanceID] = pathHint ?? TransformPath(((Component)r).transform); } MaterialPropertyBlock val2 = new MaterialPropertyBlock(); r.GetPropertyBlock(val2); val2.SetFloat("_CanLerpAmbient", 0f); val2.SetFloat("_AmbientLerp", 0f); val2.SetFloat("_CanDesaturate", 0f); val2.SetFloat("_Desaturation", 0f); val2.SetFloat("_CanHueShift", 0f); val2.SetFloat("_HueShift", 0f); val2.SetColor("_Color", Color.white); val2.SetColor("_CharacterTintColor", Color.white); r.SetPropertyBlock(val2); } } public static int RevertAll() { int num = 0; int num2 = 0; foreach (KeyValuePair snapshot in _snapshots) { Renderer renderer = snapshot.Value.Renderer; if ((Object)(object)renderer == (Object)null) { num2++; continue; } renderer.SetPropertyBlock(snapshot.Value.Mpb); num++; } _snapshots.Clear(); _appliedPaths.Clear(); LastStatus = $"Reverted {num} renderer(s)" + ((num2 > 0) ? $" ({num2} were destroyed since apply)" : ""); Plugin.Logger.LogInfo((object)("[MaterialNeutralizer] " + LastStatus)); return num; } public static int RevertByPath(string pathSubstring) { if (string.IsNullOrWhiteSpace(pathSubstring)) { LastStatus = "Empty path — nothing to revert"; return 0; } int num = 0; List list = new List(); foreach (KeyValuePair snapshot in _snapshots) { if ((_appliedPaths.TryGetValue(snapshot.Key, out var value) ? value : "(unknown path)").IndexOf(pathSubstring, StringComparison.OrdinalIgnoreCase) >= 0) { Renderer renderer = snapshot.Value.Renderer; if ((Object)(object)renderer != (Object)null) { renderer.SetPropertyBlock(snapshot.Value.Mpb); } list.Add(snapshot.Key); num++; } } foreach (int item in list) { _snapshots.Remove(item); _appliedPaths.Remove(item); } LastStatus = ((num == 0) ? ("No overrides matched '" + pathSubstring + "'") : $"Reverted {num} renderer(s) matching '{pathSubstring}'"); Plugin.Logger.LogInfo((object)("[MaterialNeutralizer] " + LastStatus)); return num; } private static string TransformPath(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(); } } internal static class ReloadCoordinator { internal static void Poll() { SpriteFileWatcher.Tick(); if (SpriteFileWatcher.ReloadSprites) { Plugin.Logger.LogDebug((object)"[Update] ReloadSprites flag set — triggering SpriteLoader.Reload()"); SpriteFileWatcher.ReloadSprites = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { SpriteLoader.Reload(); } finally { FileCache.EndCycle(); } float num = DevProfiler.StopOp("SpriteLoader.Reload"); 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"); Plugin.Logger.LogDebug((object)$"[Reload] T2DLoader.ReloadSpritesInScene completed in {num2:F1}ms"); 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"); 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"); } if (VideoHandler.ReloadVideos) { VideoHandler.ReloadVideos = false; DevProfiler.StartOp(); FileCache.BeginCycle(); try { VideoHandler.Reload(); } finally { FileCache.EndCycle(); } DevProfiler.StopOp("VideoHandler.Reload"); } GCUtil.BumpThresholdForStitchwork(); } } 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 const float FrameBudgetSec = 0.04f; private const float SceneArriveTimeoutSec = 90f; 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_006d: 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) if (_isTraversing) { return; } Plugin.Logger.LogInfo((object)"Starting scene traversal for full sprite dump..."); _isTraversing = true; _scenesSinceLastGC = 0; sceneQueue.Clear(); scenesProcessed = 0; StartedAt = Time.realtimeSinceStartup; Dictionary teleportMap = SceneTeleportMap.GetTeleportMap(); foreach (string key in teleportMap.Keys) { if (!sceneQueue.Contains(key) && (int)teleportMap[key].MapZone != 0) { Plugin.Logger.LogDebug((object)$"Enqueued scene: {key} : {teleportMap[key].MapZone}"); sceneQueue.Enqueue(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.realtimeSinceStartup + 90f; while (!_sceneArrived && Time.realtimeSinceStartup < deadline) { yield return null; } if (!_sceneArrived) { Plugin.Logger.LogWarning((object)$"[FullDump] '{next}' did not fire sceneLoaded within {90f:F0} s — skipped."); continue; } 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(); while (!unload.isDone) { yield return null; } _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.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 SpriteDumper { 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_01a8: 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_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Expected O, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) 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(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(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 Texture2D RotateToScreen(Texture2D src, 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_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_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Invalid comparison between Unknown and I4 //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_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_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_002c: 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_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_00ac: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0109: 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) Vector2 val; Vector2 val2; if ((int)flip != 1) { if ((int)flip == 2) { val = Vector2.down; val2 = Vector2.right; } else { val = Vector2.right; val2 = Vector2.up; } } else { val = Vector2.up; val2 = Vector2.left; } 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 val3 = new Texture2D(num2, num3, (TextureFormat)4, false); try { RenderTexture.active = temporary; GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)((Texture)temporary).height, (float)((Texture)temporary).width, 0f); TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(val.x, val.y, val2.x, val2.y)); Graphics.DrawTextureImpl(new Rect(0f, 0f, (float)((Texture)src).width, (float)((Texture)src).height), (Texture)(object)src, new Rect(0f, 0f, 1f, 1f), 0, 0, 0, 0, Color.white, TexUtil.RotateMaterial, 0); val3.ReadPixels(new Rect(0f, 0f, (float)((Texture)temporary).width, (float)((Texture)temporary).height), 0, 0); val3.Apply(); GL.PopMatrix(); return val3; } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } private static void DumpRotatedSprite(tk2dSpriteDefinition def, Texture2D spriteTex, Rect spriteRect, string baseDir, string collectionName, string matname) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_0058: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Invalid comparison between Unknown and I4 //IL_0099: 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_00a7: 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_00d7: 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_00fa: 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_0122: Expected O, but got Unknown //IL_013b: 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_0076: 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_0081: 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_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) 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); FlipMode flipped = def.flipped; Vector2 val2; Vector2 val3; if ((int)flipped != 1) { if ((int)flipped == 2) { val2 = Vector2.down; val3 = Vector2.right; } else { val2 = Vector2.right; val3 = Vector2.up; } } else { val2 = Vector2.up; val3 = Vector2.left; } TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(val2.x, val2.y, val3.x, val3.y)); Graphics.DrawTextureImpl(new Rect(0f, 0f, (float)((Texture)spriteTex).width, (float)((Texture)spriteTex).height), (Texture)(object)spriteTex, new Rect(0f, 0f, 1f, 1f), 0, 0, 0, 0, Color.white, TexUtil.RotateMaterial, 0); 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(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_0127: 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_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Expected O, but got Unknown //IL_016a: 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_01ce: Unknown result type (might be due to invalid IL or missing references) if (def == null || string.IsNullOrEmpty(def.name) || 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(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(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(); } private static readonly Dictionary> LoadedAtlases = new Dictionary>(); private static readonly Dictionary> LoadedAtlasesTextures = new Dictionary>(); 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 readonly HashSet _collectionsRevertedThisReload = new HashSet(); private static readonly Dictionary _perCollectionReplacements = new Dictionary(); public static string LoadPath => Path.Combine(Plugin.BasePath, "Sprites"); public static string AtlasLoadPath => Path.Combine(Plugin.BasePath, "Spritesheets"); private static bool _copyTextureToRTSupported { get { //IL_0017: 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_001f: Invalid comparison between Unknown and I4 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; } 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 (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_0265: 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_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Expected O, but got Unknown //IL_08e5: Unknown result type (might be due to invalid IL or missing references) //IL_08ea: Unknown result type (might be due to invalid IL or missing references) //IL_0912: Unknown result type (might be due to invalid IL or missing references) //IL_0917: Unknown result type (might be due to invalid IL or missing references) //IL_0919: Unknown result type (might be due to invalid IL or missing references) //IL_091c: Invalid comparison between Unknown and I4 //IL_0925: Unknown result type (might be due to invalid IL or missing references) //IL_092a: Unknown result type (might be due to invalid IL or missing references) //IL_092c: Unknown result type (might be due to invalid IL or missing references) //IL_0931: Unknown result type (might be due to invalid IL or missing references) //IL_091e: Unknown result type (might be due to invalid IL or missing references) //IL_0921: Invalid comparison between Unknown and I4 //IL_0620: Unknown result type (might be due to invalid IL or missing references) //IL_0625: Unknown result type (might be due to invalid IL or missing references) //IL_062a: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_063c: Expected O, but got Unknown //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_0959: Unknown result type (might be due to invalid IL or missing references) //IL_0935: Unknown result type (might be due to invalid IL or missing references) //IL_093a: Unknown result type (might be due to invalid IL or missing references) //IL_093c: Unknown result type (might be due to invalid IL or missing references) //IL_0941: Unknown result type (might be due to invalid IL or missing references) //IL_09d5: Unknown result type (might be due to invalid IL or missing references) //IL_09dc: Unknown result type (might be due to invalid IL or missing references) //IL_09e3: Unknown result type (might be due to invalid IL or missing references) //IL_09ea: Unknown result type (might be due to invalid IL or missing references) //IL_09f1: Unknown result type (might be due to invalid IL or missing references) //IL_09fb: Unknown result type (might be due to invalid IL or missing references) //IL_0a13: Unknown result type (might be due to invalid IL or missing references) //IL_0a1c: Unknown result type (might be due to invalid IL or missing references) //IL_0945: Unknown result type (might be due to invalid IL or missing references) //IL_094a: Unknown result type (might be due to invalid IL or missing references) //IL_094c: Unknown result type (might be due to invalid IL or missing references) //IL_0951: Unknown result type (might be due to invalid IL or missing references) //IL_0695: Unknown result type (might be due to invalid IL or missing references) //IL_0721: 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(); if ((!SceneTraverser.IsTraversing || _collectionsWithFiles.Contains(((Object)collection).name)) && !(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; int num2 = Array.IndexOf(collection.materials, val); if (num2 >= 0) { _originalTexturesByColIndex[(((Object)collection).name, num2)] = _originalTextures[instanceID]; _vanillaTexNamesByColIndex[(((Object)collection).name, num2)] = name2; } if (flag) { Plugin.Logger.LogWarning((object)("[SpriteLoader] mat '" + text + "' → vanilla texture '" + name2 + "'")); } _vanillaCaptureSourceId[instanceID] = instanceID2; } } int num3 = Array.IndexOf(collection.materials, val); 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; } else { val.mainTexture = (Texture)(object)value3; } continue; } if (!LoadedAtlases.ContainsKey(key)) { LoadedAtlases[key] = new HashSet(); } Dictionary value11; RenderTexture value12; if (LoadedAtlases[key].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)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 value9)) { value9 = (LoadedAtlasesTextures[key] = new Dictionary()); } if (!value9.TryGetValue(name, out var value10) || (Object)(object)value10 == (Object)null) { RenderTexture val5 = new RenderTexture(num4, num5, 0, (RenderTextureFormat)0, TexUtil.ReadWriteForSource(val4)) { hideFlags = (HideFlags)32, name = name }; value10 = (value9[name] = val5); } else if (((Texture)value10).width != num4 || ((Texture)value10).height != num5) { value10.Release(); ((Texture)value10).width = num4; ((Texture)value10).height = num5; value10.Create(); } if (atlasExpansionPlan != null) { RenderTexture active = RenderTexture.active; RenderTexture.active = value10; GL.Clear(false, true, Color.clear); RenderTexture.active = active; if (_copyTextureToRTSupported) { Graphics.CopyTexture(val4, 0, 0, 0, 0, val4.width, val4.height, (Texture)(object)value10, 0, 0, 0, 0); } else { RenderTexture active2 = RenderTexture.active; RenderTexture.active = value10; GL.PushMatrix(); GL.LoadPixelMatrix(0f, (float)((Texture)value10).width, (float)((Texture)value10).height, 0f); Graphics.DrawTexture(new Rect(0f, (float)(((Texture)value10).height - val4.height), (float)val4.width, (float)val4.height), val4); GL.PopMatrix(); RenderTexture.active = active2; } } else { Graphics.Blit(val4, value10); } if ((Object)(object)val3 != (Object)null) { Object.Destroy((Object)(object)val3); } if (atlasExpansionPlan != null) { ApplyExpansionPlan(collection, atlasExpansionPlan, instanceID, num3, ((Texture)value10).width, ((Texture)value10).height); _collectionsExpandedThisReload.Add(collection); } val.mainTexture = (Texture)(object)value10; } else if (LoadedAtlasesTextures.TryGetValue(key, out value11) && value11.TryGetValue(name, out value12)) { val.mainTexture = (Texture)(object)value12; } 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 value13)) { value13 = (LoadedSprites[key] = new Dictionary>()); } if (!value13.TryGetValue(name, out var value14)) { value14 = (value13[name] = new HashSet()); } if (!value14.Add(val7.name)) { continue; } _loadedSpriteCount++; Texture2D val8 = FindSprite(((Object)collection).name, instanceID, text, val7.name); if ((Object)(object)val8 == (Object)null) { continue; } _perCollectionReplacements.TryGetValue(((Object)collection).name, out var value15); _perCollectionReplacements[((Object)collection).name] = value15 + 1; Rect spriteRect = SpriteUtil.GetSpriteRect(val7, val.mainTexture); ((Rect)(ref spriteRect)).y = (float)val.mainTexture.height - ((Rect)(ref spriteRect)).y - ((Rect)(ref spriteRect)).height; FlipMode flipped = val7.flipped; Vector2 val9; Vector2 val10; if ((int)flipped != 1) { if ((int)flipped == 2) { val9 = Vector2.up; val10 = Vector2.left; } else { val9 = Vector2.right; val10 = Vector2.up; } } else { val9 = Vector2.down; val10 = Vector2.right; } Rect val11 = 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 val11))..ctor(((Rect)(ref spriteRect)).x, ((Rect)(ref spriteRect)).y + (float)num7, (float)((Texture)val8).width, (float)((Texture)val8).height); } } TexUtil.RotateMaterial.SetVector("_Basis", new Vector4(val9.x, val9.y, val10.x, val10.y)); Graphics.DrawTextureImpl(val11, (Texture)(object)val8, new Rect(0f, 0f, 1f, 1f), 0, 0, 0, 0, Color.white, TexUtil.RotateMaterial, 0); 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; } private static void RebuildFileIndex() { _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(Path.Combine(pluginPackPath2, "Spritesheets"), 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)); } } 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 (text2.IndexOf("_WIP/", StringComparison.OrdinalIgnoreCase) < 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)) { ConflictTracker.Record(conflictType, ConflictKey(conflictType, text2), value.Item2, sourcePack); } 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; } 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); } private 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; } 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 (string item2 in value.Keys.ToList()) { if (item2.StartsWith(atlasName)) { value[item2].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)) { foreach (string item2 in value.Where((string a) => a.StartsWith(atlasName)).ToList()) { value.Remove(item2); } } if (!LoadedSprites.TryGetValue(item, out var value2)) { continue; } foreach (string item3 in value2.Keys.Where((string a) => a.StartsWith(atlasName)).ToList()) { value2[item3].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() { RebuildFileIndex(); LoadedAtlases.Clear(); LoadedSprites.Clear(); _loadedSpriteCount = 0; _instanceKeyToName.Clear(); _nameInstanceCount.Clear(); _collectionsExpandedThisReload.Clear(); _collectionsRevertedThisReload.Clear(); _perCollectionReplacements.Clear(); tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if (!((Object)val).name.StartsWith("Stitchwork_Shadow", StringComparison.Ordinal)) { LoadCollection(val); } } if (_collectionsWithFiles.Count == 0 && _originalTextures.Count > 0) { int num = 0; Material[] array2 = Resources.FindObjectsOfTypeAll(); foreach (Material val2 in array2) { if (!((Object)(object)val2 == (Object)null)) { int instanceID = ((Object)val2).GetInstanceID(); if (_originalTextures.TryGetValue(instanceID, out var value) && (Object)(object)value != (Object)null && value.IsCreated() && (Object)(object)val2.mainTexture != (Object)(object)value) { val2.mainTexture = (Texture)(object)value; 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[] array3 = Resources.FindObjectsOfTypeAll(); foreach (tk2dBaseSprite val3 in array3) { if (!((Object)(object)val3 == (Object)null) && hashSet.Contains(val3.Collection)) { val3.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} sprite(s) blitted across " + $"{LoadedAtlases.Count} atlas(es){arg}")); 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_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Expected O, but got Unknown //IL_0203: 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(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(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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown //IL_00c6: 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(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(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_0057: 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) //IL_0068: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_03ee: Unknown result type (might be due to invalid IL or missing references) //IL_03f5: Expected O, but got Unknown //IL_041d: 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_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: 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_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Expected O, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Expected O, but got Unknown //IL_0296: 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: Expected O, but got Unknown //IL_00f5: 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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0116: 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_0126: 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) if (T2DUtil.IsT2DTexture(((Object)sprite.texture).name)) { 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"); GameObject val2 = null; Camera val3 = null; RenderTexture val4 = null; Texture2D val5 = null; Texture2D val6 = null; Sprite val7 = null; try { Sprite sprite2 = sprite; if (T2DLoader.TryGetOriginalPngBytes(((Object)sprite.texture).name, out var png)) { val6 = new Texture2D(2, 2, (TextureFormat)4, false); if (ImageConversion.LoadImage(val6, png) && ((Texture)val6).width == ((Texture)sprite.texture).width && ((Texture)val6).height == ((Texture)sprite.texture).height) { Texture2D obj = val6; Rect rect2 = sprite.rect; float x = sprite.pivot.x; rect = sprite.rect; float num4 = x / ((Rect)(ref rect)).width; float y = sprite.pivot.y; rect = sprite.rect; val7 = Sprite.Create(obj, rect2, new Vector2(num4, y / ((Rect)(ref rect)).height), sprite.pixelsPerUnit); sprite2 = val7; } } val.AddComponent().sprite = sprite2; val.layer = num3; Transform transform = val.transform; float x2 = sprite.pivot.x; rect = sprite.rect; float num5 = (x2 - ((Rect)(ref rect)).width / 2f) / sprite.pixelsPerUnit; float y2 = sprite.pivot.y; rect = sprite.rect; transform.position = new Vector3(num5, (y2 - ((Rect)(ref rect)).height / 2f) / sprite.pixelsPerUnit, 0f); val2 = new GameObject("TempCamera"); val3 = val2.AddComponent(); val3.clearFlags = (CameraClearFlags)2; val3.backgroundColor = new Color(0f, 0f, 0f, 0f); val3.orthographic = true; val3.cullingMask = 1 << num3; val3.orthographicSize = (float)num2 / sprite.pixelsPerUnit / 2f; ((Component)val3).transform.position = new Vector3(0f, 0f, -10f); val4 = new RenderTexture(num, num2, 0, (RenderTextureFormat)0); ((Texture)val4).filterMode = (FilterMode)0; val3.targetTexture = val4; val3.Render(); RenderTexture active = RenderTexture.active; RenderTexture.active = val4; val5 = new Texture2D(num, num2, (TextureFormat)5, false); val5.ReadPixels(new Rect(0f, 0f, (float)num, (float)num2), 0, 0); val5.Apply(); RenderTexture.active = active; File.WriteAllBytes(path2, ImageConversion.EncodeToPNG(val5)); return; } finally { if ((Object)(object)val3 != (Object)null) { val3.targetTexture = null; } if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } if ((Object)(object)val2 != (Object)null) { Object.DestroyImmediate((Object)(object)val2); } if ((Object)(object)val4 != (Object)null) { Object.DestroyImmediate((Object)(object)val4); } if ((Object)(object)val5 != (Object)null) { Object.DestroyImmediate((Object)(object)val5); } if ((Object)(object)val7 != (Object)null) { Object.DestroyImmediate((Object)(object)val7); } if ((Object)(object)val6 != (Object)null) { Object.DestroyImmediate((Object)(object)val6); } } } string text2 = T2DUtil.SanitizeForFilesystem(((Object)sprite.texture).name); string path3 = Path.Combine(DumpPath, text2 + ".png"); if (File.Exists(path3)) { return; } if (T2DLoader.TryGetOriginalPngBytes(((Object)sprite.texture).name, out var png2)) { File.WriteAllBytes(path3, png2); return; } Plugin.Logger.LogDebug((object)("[T2D] Dumping non-atlas texture: '" + ((Object)sprite.texture).name + "' -> " + text2 + ".png")); RenderTexture val8 = null; Texture2D val9 = null; try { val8 = TexUtil.GetReadable((Texture)(object)sprite.texture); val9 = new Texture2D(((Texture)val8).width, ((Texture)val8).height, (TextureFormat)5, false); RenderTexture active2 = RenderTexture.active; RenderTexture.active = val8; val9.ReadPixels(new Rect(0f, 0f, (float)((Texture)val8).width, (float)((Texture)val8).height), 0, 0); val9.Apply(); RenderTexture.active = active2; File.WriteAllBytes(path3, ImageConversion.EncodeToPNG(val9)); } finally { if ((Object)(object)val8 != (Object)null) { RenderTexture.ReleaseTemporary(val8); } if ((Object)(object)val9 != (Object)null) { Object.DestroyImmediate((Object)(object)val9); } } } } [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.Message)); return; } for (int i = 0; i < num && i < array.Length; i++) { Sprite val = array[i]; if ((Object)(object)((val != null) ? val.texture : null) != (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 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 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 bool _sceneSeedDone; 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 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; } } 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); } } private static void SeedFromScene() { if (_sceneSeedDone) { return; } _sceneSeedDone = true; SpriteRenderer[] array = Resources.FindObjectsOfTypeAll(); foreach (SpriteRenderer val in array) { if ((Object)(object)val != (Object)null) { Sprite sprite = val.sprite; if ((Object)(object)((sprite != null) ? sprite.texture : null) != (Object)null) { _knownRenderers.Add(val); } } } Image[] array2 = Resources.FindObjectsOfTypeAll(); foreach (Image val2 in array2) { if ((Object)(object)val2 != (Object)null) { Sprite sprite2 = val2.sprite; if ((Object)(object)((sprite2 != null) ? sprite2.texture : null) != (Object)null) { _knownImages.Add(val2); } } } _seededSprites = Resources.FindObjectsOfTypeAll(); } public static void ResetSceneSeed() { _sceneSeedDone = false; _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(); _sceneSeedDone = true; 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); } } } 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[] array = Resources.FindObjectsOfTypeAll(); foreach (tk2dSpriteCollectionData val in array) { if (val?.spriteDefinitions == null) { continue; } tk2dSpriteDefinition[] spriteDefinitions = val.spriteDefinitions; foreach (tk2dSpriteDefinition val2 in spriteDefinitions) { object obj; if (val2 == null) { obj = null; } else { Material material = val2.material; obj = ((material != null) ? material.mainTexture : null); } if (!((Object)obj == (Object)null) && !string.IsNullOrEmpty(val2.name)) { string name = ((Object)val2.material.mainTexture).name; if (dictionary.TryGetValue(name, out var value3)) { value3.Item4.Add(val2.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 val3) { if (!((Object)(object)((val3 != null) ? val3.texture : null) == (Object)null)) { Texture2D texture = val3.texture; int instanceID = ((Object)texture).GetInstanceID(); if (!spritesByTexId.TryGetValue(instanceID, out var value7)) { value7 = new HashSet(StringComparer.Ordinal); spritesByTexId[instanceID] = value7; texById[instanceID] = (Texture)(object)texture; } if (!string.IsNullOrEmpty(((Object)val3).name)) { value7.Add(((Object)val3).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() { 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() { 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_0078: 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) if (_loadedSprites.ContainsKey(key)) { ConflictTracker.Record("t2d-sprite", key, _t2dProviders.GetValueOrDefault(key), sourcePack); 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; 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) { //IL_0040: 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) Sprite val; if (Math.Abs(cached.pixelsPerUnit - targetPPU) <= 0.5f) { val = cached; } else { Sprite val2 = Sprite.Create(cached.texture, new Rect(0f, 0f, (float)((Texture)cached.texture).width, (float)((Texture)cached.texture).height), new Vector2(0.5f, 0.5f), targetPPU); ((Object)val2).name = ((Object)cached).name; ((Object)val2).hideFlags = (HideFlags)32; _ourSpriteIds.Add(((Object)val2).GetInstanceID()); _loadedSprites[key] = val2; val = val2; } if (vanillaTextureName != null && (Object)(object)val.texture != (Object)null && string.IsNullOrEmpty(((Object)val.texture).name)) { ((Object)val.texture).name = vanillaTextureName; } return val; } 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); } } } } internal static void HandleLoad(object spriteContainer, Sprite sprite) { //IL_01cc: 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_01ee: 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) if (_handling || !HasT2DReplacements || (Object)(object)((sprite != null) ? sprite.texture : null) == (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); } 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 && _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); } 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); } } 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 != null) ? value.texture : null) != (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}:{(Object)(object)val4 == (Object)null}"; 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 + "' " + $"texFakeNull={(Object)(object)val4 == (Object)null} — 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 != null) ? value.texture : null) != (Object)null) { SkippedTextureIds.Add(((Object)value.texture).GetInstanceID()); } } InitializedSpriteRenderers.Clear(); InvalidateUninitCache(); _replacedSrs.Clear(); _replacedImgs.Clear(); } public static void ReloadSpritesInScene() { List list = new List(_loadedSprites.Values); _loadedSprites.Clear(); _spriteNameToKey.Clear(); _t2dProviders.Clear(); _coveredCleanNames.Clear(); _emittedMissKeys.Clear(); ReplacedTextureIds.Clear(); SkippedTextureIds.Clear(); ClearSoftSkips(); _restoredNames.Clear(); T2DUtil.ClearCleanNameCache(); foreach (Sprite item in list) { if ((Object)(object)((item != null) ? item.texture : null) != (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(); if (list.Count > 0) { HashSet hashSet = new HashSet(list); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); Sprite[] array6 = Resources.FindObjectsOfTypeAll(); foreach (Sprite val4 in array6) { if ((Object)(object)val4 != (Object)null && !hashSet.Contains(val4) && !dictionary.ContainsKey(((Object)val4).name)) { dictionary[((Object)val4).name] = val4; } } 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 value2; Sprite val6 = (_loadedSprites.TryGetValue(KeyForSprite(val5.sprite), out value) ? value : (dictionary.TryGetValue(((Object)val5.sprite).name, out value2) ? value2 : null)); 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 value3; Sprite value4; Sprite val8 = (_loadedSprites.TryGetValue(KeyForSprite(val7.sprite), out value3) ? value3 : (dictionary.TryGetValue(((Object)val7.sprite).name, out value4) ? value4 : null)); 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, {dictionary.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) && list.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) && list.Contains(val10.sprite)) { hashSet2.Add(((Object)val10.sprite).GetInstanceID()); } } int num3 = 0; int num4 = 0; foreach (Sprite item2 in list) { if ((Object)(object)item2 == (Object)null) { continue; } if (hashSet2.Contains(((Object)item2).GetInstanceID())) { num4++; continue; } if ((Object)(object)item2.texture != (Object)null) { Object.Destroy((Object)(object)item2.texture); } Object.Destroy((Object)(object)item2); 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.")); } } 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) { return true; } 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", "m_FallbackFontAssetTable"); if (_fontAssetFallbackTableMember == null) { Plugin.Logger.LogDebug((object)"[T2D] TMProOld.TMP_FontAsset.fallbackFontAssetTable 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 (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)); if (T2DUtil.IsT2DTexture(((Object)tex).name)) { foreach (KeyValuePair spritesheetOverride in SpritesheetOverrides) { if (string.Equals(T2DUtil.CleanTextureName(spritesheetOverride.Key), text, StringComparison.OrdinalIgnoreCase) && spritesheetOverride.Value.Item2 == ((Texture)tex).width && spritesheetOverride.Value.Item3 == ((Texture)tex).height) { value = spritesheetOverride.Value; break; } } } 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; } Plugin.Logger.LogInfo((object)("[T2D] Fuzzy match for '" + ((Object)tex).name + "' → clean name '" + text + "' " + $"({((Texture)tex).width}x{((Texture)tex).height})")); } 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 text2 = "[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)text2); } else { Plugin.Logger.LogDebug((object)text2); } 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(Path.Combine(pluginPackPath, "Spritesheets", "T2D"), pluginPackPath); } ScanDirectory(AtlasLoadPath, null); void ScanDirectory(string dir, string sourcePack) { if (Directory.Exists(dir)) { string[] files = Directory.GetFiles(dir, "*.png", SearchOption.TopDirectoryOnly); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); namesAttempted.Add(fileNameWithoutExtension); if (SpritesheetOverrides.ContainsKey(fileNameWithoutExtension)) { ConflictTracker.Record("t2d-sheet", fileNameWithoutExtension, _sheetProviders.GetValueOrDefault(fileNameWithoutExtension), sourcePack); } else { bool flag = false; try { byte[] array = File.ReadAllBytes(text); 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 '" + text + "': " + 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.")); } } } } } } internal static void RegisterStandaloneSpritesheet(string textureName, string file, string sourcePack) { if (SpritesheetOverrides.ContainsKey(textureName)) { ConflictTracker.Record("t2d-sheet", textureName, _sheetProviders.GetValueOrDefault(textureName), sourcePack); return; } try { byte[] array = File.ReadAllBytes(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 void ConvertSpritesheet(Texture2D atlas, string cleanName) { //IL_00be: 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_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_0118: 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 active = RenderTexture.active; RenderTexture.active = temporary; try { int num = 0; Sprite[] array = Resources.FindObjectsOfTypeAll(); foreach (Sprite val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.texture == (Object)null || ((Object)val.texture).GetInstanceID() != ((Object)atlas).GetInstanceID() || string.IsNullOrEmpty(((Object)val).name)) { continue; } string path = Path.Combine(text, ((Object)val).name + ".png"); if (File.Exists(path)) { continue; } Rect rect = val.rect; if (!(((Rect)(ref rect)).width <= 0f) && !(((Rect)(ref rect)).height <= 0f)) { Texture2D val2 = new Texture2D((int)((Rect)(ref rect)).width, (int)((Rect)(ref rect)).height, (TextureFormat)4, false); try { val2.ReadPixels(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height), 0, 0); val2.Apply(); IOUtil.EnsureDirectoryExists(text); File.WriteAllBytes(path, ImageConversion.EncodeToPNG(val2)); num++; } finally { Object.Destroy((Object)(object)val2); } } } if (num > 0) { Plugin.Logger.LogDebug((object)$"[T2D] Converted spritesheet '{cleanName}' → {num} individual sprites in {text}"); } } finally { RenderTexture.active = active; RenderTexture.ReleaseTemporary(temporary); } } 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 readonly List Conflicts = new List(); public readonly Dictionary<(string Sheet, string Lang), Dictionary> Additions = new Dictionary<(string, string), Dictionary>(); public int AddedCount; public int IdenticalCount; 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 }; string[] directories = Directory.GetDirectories(path); foreach (string path2 in directories) { string fileName = Path.GetFileName(path2); string[] files = Directory.GetFiles(path2, "*.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++; } else { plan.Conflicts.Add(new Conflict { Sheet = fileName, Lang = fileNameWithoutExtension, Key = item.Key, RootValue = value, PackValue = item.Value }); } } } } 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 != 0) { 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) { 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); } 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" }; 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(Path.Combine(pluginPackPath, "Videos")); } 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_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: 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.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.ContainsKey(name)) { return VideoFileMap[name]; } foreach (string pluginPackPath in Plugin.PluginPackPaths) { if (Directory.Exists(Path.Combine(pluginPackPath, "Videos"))) { string[] files = Directory.GetFiles(Path.Combine(pluginPackPath, "Videos"), name + ".*", SearchOption.AllDirectories); if (files.Any()) { string text = "file:///" + files[0]; VideoFileMap[name] = text; return text; } } } string[] files2 = Directory.GetFiles(VideoLoadPath, name + ".*", SearchOption.AllDirectories); if (files2.Any()) { string text2 = "file:///" + files2[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(); } } 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) { __instance.videoPlayer.url = text; __instance.videoPlayer.clip = null; __instance.videoPlayer.playbackSpeed = 1f; if (Object.op_Implicit((Object)(object)config.AudioSource)) { 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 { Plugin.Logger.LogWarning((object)("Using custom video file '" + config.VideoReference.VideoFileName + "' with direct audio output. This may result in no audio being played.")); __instance.videoPlayer.audioOutputMode = (VideoAudioOutputMode)2; } __instance.videoPlayer.Prepare(); } } private static void PlayVideoPostfix(EmbeddedCinematicVideoPlayer __instance) { 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 = Path.Combine(pluginPackPath, "Spritesheets"); 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); string[] array = relativePath.Split(Path.DirectorySeparatorChar); 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")); 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")); 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")); DeferReloadT2DSprites(); return; } string text = array[^2]; string fileNameWithoutExtension2 = Path.GetFileNameWithoutExtension(array[^1]); 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 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); _closePrompt = false; _pendingProfileDelete = null; Rebuild(); } public static void Hide() { if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } } 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) 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(); 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); } } 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.0", 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_0592: Unknown result type (might be due to invalid IL or missing references) //IL_058b: 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_0624: Unknown result type (might be due to invalid IL or missing references) //IL_062e: Unknown result type (might be due to invalid IL or missing references) //IL_0618: Unknown result type (might be due to invalid IL or missing references) //IL_066b: Unknown result type (might be due to invalid IL or missing references) //IL_070c: 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