From a789ba4db158b1c9c350e4b1d32f9727074e8ef8 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:16:24 +0100 Subject: [PATCH 01/35] add changes to make compilable for hk 1.5.12301 --- Assembly-CSharp/Assembly-CSharp.csproj | 47 ++----------------- Assembly-CSharp/Menu/MenuUtils.cs | 2 +- Assembly-CSharp/Mod.cs | 1 + Assembly-CSharp/ModHooks.cs | 12 ++--- Assembly-CSharp/ModListMenu.cs | 2 +- Assembly-CSharp/Patches/GameManager.cs | 1 + Assembly-CSharp/Patches/HeroController.cs | 3 +- Assembly-CSharp/Patches/Language.cs | 55 ++++++++++++----------- 8 files changed, 45 insertions(+), 78 deletions(-) diff --git a/Assembly-CSharp/Assembly-CSharp.csproj b/Assembly-CSharp/Assembly-CSharp.csproj index 337c16c9..d9493277 100644 --- a/Assembly-CSharp/Assembly-CSharp.csproj +++ b/Assembly-CSharp/Assembly-CSharp.csproj @@ -148,7 +148,7 @@ ..\override\mscorlib.dll - + ../Vanilla/netstandard.dll @@ -158,47 +158,8 @@ ../Vanilla/PlayMaker.dll False - - ../Vanilla/UnityEngine.dll - - - ../Vanilla/UnityEngine.AnimationModule.dll - - - ../Vanilla/UnityEngine.AssetBundleModule.dll - - - ../Vanilla/UnityEngine.AudioModule.dll - - - ../Vanilla/UnityEngine.CoreModule.dll - - - ../Vanilla/UnityEngine.ImageConversionModule.dll - - - ../Vanilla/UnityEngine.IMGUIModule.dll - - - ../Vanilla/UnityEngine.InputLegacyModule.dll - - - ../Vanilla/UnityEngine.JSONSerializeModule.dll - - - ../Vanilla/UnityEngine.ParticleSystemModule.dll - - - ../Vanilla/UnityEngine.Physics2DModule.dll - - - ../Vanilla/UnityEngine.TextRenderingModule.dll - - - ../Vanilla/UnityEngine.UI.dll - - - ../Vanilla/UnityEngine.UIModule.dll - + + + diff --git a/Assembly-CSharp/Menu/MenuUtils.cs b/Assembly-CSharp/Menu/MenuUtils.cs index af0a1de8..0bd47d9a 100644 --- a/Assembly-CSharp/Menu/MenuUtils.cs +++ b/Assembly-CSharp/Menu/MenuUtils.cs @@ -8,7 +8,7 @@ using UnityEngine; using UnityEngine.UI; using Patch = Modding.Patches; -using Lang = Language.Language; +using Lang = TeamCherry.Localization.Language; namespace Modding.Menu diff --git a/Assembly-CSharp/Mod.cs b/Assembly-CSharp/Mod.cs index 3e476940..c64f4abf 100644 --- a/Assembly-CSharp/Mod.cs +++ b/Assembly-CSharp/Mod.cs @@ -10,6 +10,7 @@ using MonoMod.Utils; using System.Linq; using Newtonsoft.Json.Linq; +using Language = TeamCherry.Localization; // ReSharper disable file UnusedMember.Global diff --git a/Assembly-CSharp/ModHooks.cs b/Assembly-CSharp/ModHooks.cs index 1ba7815f..1364fc68 100644 --- a/Assembly-CSharp/ModHooks.cs +++ b/Assembly-CSharp/ModHooks.cs @@ -61,10 +61,10 @@ static ModHooks() { string[] versionNums = Constants.GAME_VERSION.Split('.'); - gameVersion.major = Convert.ToInt32(versionNums[0]); - gameVersion.minor = Convert.ToInt32(versionNums[1]); - gameVersion.revision = Convert.ToInt32(versionNums[2]); - gameVersion.package = Convert.ToInt32(versionNums[3]); + gameVersion.major = versionNums.Length > 0 ? Convert.ToInt32(versionNums[0]) : 0; + gameVersion.minor = versionNums.Length > 1 ? Convert.ToInt32(versionNums[1]) : 0; + gameVersion.revision = versionNums.Length > 2 ? Convert.ToInt32(versionNums[2]) : 0; + gameVersion.package = versionNums.Length > 3 ? Convert.ToInt32(versionNums[3]) : 0; } catch (Exception e) { @@ -225,7 +225,9 @@ internal static void LogConsole(string message, LogLevel level) /// N/A internal static string LanguageGet(string key, string sheet) { - string res = Patches.Language.GetInternal(key, sheet); + // todo: fixme: yea i think you know what would need fixing + // string res = Patches.Language.GetInternal(key, sheet); + string res = TeamCherry.Localization.Language.Get(key, sheet); if (LanguageGetHook == null) return res; diff --git a/Assembly-CSharp/ModListMenu.cs b/Assembly-CSharp/ModListMenu.cs index fec0f388..0a7b6082 100644 --- a/Assembly-CSharp/ModListMenu.cs +++ b/Assembly-CSharp/ModListMenu.cs @@ -6,7 +6,7 @@ using UnityEngine.UI; using static Modding.ModLoader; using Patch = Modding.Patches; -using Lang = Language.Language; +using Lang = TeamCherry.Localization.Language; namespace Modding { diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 9510c2d5..90e6d013 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using UnityEngine; using UnityEngine.SceneManagement; +using Encryption = TeamCherry.SharedUtils.Encryption; // ReSharper disable all #pragma warning disable 1591, 649, 414, 169, CS0108, CS0626 diff --git a/Assembly-CSharp/Patches/HeroController.cs b/Assembly-CSharp/Patches/HeroController.cs index 8d9bee86..cf98aec6 100644 --- a/Assembly-CSharp/Patches/HeroController.cs +++ b/Assembly-CSharp/Patches/HeroController.cs @@ -528,6 +528,7 @@ private void LookForQueueInput() [MonoModIgnore] public event HeroController.TakeDamageEvent OnTakenDamage; + // todo: fixme: this had changes in the method, check if more shit changed [MonoModReplace] public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount, int hazardType) { @@ -667,7 +668,7 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount if (this.cState.wallSliding) { this.cState.wallSliding = false; - this.wallSlideVibrationPlayer.Stop(); + this.vibrationCtrl.StopWallSlide(); } if (this.cState.touchingWall) diff --git a/Assembly-CSharp/Patches/Language.cs b/Assembly-CSharp/Patches/Language.cs index 5fbd34c2..093f62b9 100644 --- a/Assembly-CSharp/Patches/Language.cs +++ b/Assembly-CSharp/Patches/Language.cs @@ -7,31 +7,32 @@ namespace Modding.Patches { - [MonoModPatch("global::Language.Language")] - public static class Language - { - [MonoModIgnore] - private static Dictionary> currentEntrySheets; - - public static string GetInternal(string key, string sheetTitle) - { - if (currentEntrySheets == null || !currentEntrySheets.ContainsKey(sheetTitle)) - { - Debug.LogError($"The sheet with title \"{sheetTitle}\" does not exist!"); - return string.Empty; - } - - if (currentEntrySheets[sheetTitle].ContainsKey(key)) - { - return currentEntrySheets[sheetTitle][key]; - } - - return "#!#" + key + "#!#"; - } - - public static string Get(string key, string sheetTitle) - { - return ModHooks.LanguageGet(key, sheetTitle); - } - } + // todo: fixme: yea i think you know what would need fixing +// [MonoModPatch("global::Language.Language")] +// public static class Language +// { +// [MonoModIgnore] +// private static Dictionary> currentEntrySheets; +// +// public static string GetInternal(string key, string sheetTitle) +// { +// if (currentEntrySheets == null || !currentEntrySheets.ContainsKey(sheetTitle)) +// { +// Debug.LogError($"The sheet with title \"{sheetTitle}\" does not exist!"); +// return string.Empty; +// } +// +// if (currentEntrySheets[sheetTitle].ContainsKey(key)) +// { +// return currentEntrySheets[sheetTitle][key]; +// } +// +// return "#!#" + key + "#!#"; +// } +// +// public static string Get(string key, string sheetTitle) +// { +// return ModHooks.LanguageGet(key, sheetTitle); +// } +// } } \ No newline at end of file From ad0ffab370efc022b69ebdc1b49fcbb68816e40b Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:35:55 +0100 Subject: [PATCH 02/35] it wasn't public, it was private --- Assembly-CSharp/Patches/HeroController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly-CSharp/Patches/HeroController.cs b/Assembly-CSharp/Patches/HeroController.cs index cf98aec6..2ad6fffe 100644 --- a/Assembly-CSharp/Patches/HeroController.cs +++ b/Assembly-CSharp/Patches/HeroController.cs @@ -668,7 +668,7 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount if (this.cState.wallSliding) { this.cState.wallSliding = false; - this.vibrationCtrl.StopWallSlide(); + ReflectionHelper.GetField("vibrationCtrl").StopWallSlide(); } if (this.cState.touchingWall) From bad9b709332acd10f980fe626af145e85668a9ba Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Tue, 16 Dec 2025 22:05:39 +0100 Subject: [PATCH 03/35] do fancy private stuff --- Assembly-CSharp/Patches/HeroController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Assembly-CSharp/Patches/HeroController.cs b/Assembly-CSharp/Patches/HeroController.cs index 2ad6fffe..c4926810 100644 --- a/Assembly-CSharp/Patches/HeroController.cs +++ b/Assembly-CSharp/Patches/HeroController.cs @@ -528,6 +528,9 @@ private void LookForQueueInput() [MonoModIgnore] public event HeroController.TakeDamageEvent OnTakenDamage; + [MonoModIgnore] + private HeroVibrationController vibrationCtrl; + // todo: fixme: this had changes in the method, check if more shit changed [MonoModReplace] public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount, int hazardType) @@ -668,7 +671,7 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount if (this.cState.wallSliding) { this.cState.wallSliding = false; - ReflectionHelper.GetField("vibrationCtrl").StopWallSlide(); + this.vibrationCtrl.StopWallSlide(); } if (this.cState.touchingWall) From dcfaa9f4e8ebcbc2737d928421a684eb49642d24 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 17 Dec 2025 01:55:53 +0100 Subject: [PATCH 04/35] language doesn't need to get patched if it doesn't exist in the first place, so it can just be created - no clue what to do with `Language.Language.Get(key)` though --- Assembly-CSharp/Language/Language.cs | 33 ++++++++++++++++++++++++ Assembly-CSharp/Menu/MenuUtils.cs | 2 +- Assembly-CSharp/Mod.cs | 1 - Assembly-CSharp/ModHooks.cs | 4 +-- Assembly-CSharp/ModListMenu.cs | 2 +- Assembly-CSharp/Patches/Language.cs | 38 ---------------------------- 6 files changed, 36 insertions(+), 44 deletions(-) create mode 100644 Assembly-CSharp/Language/Language.cs delete mode 100644 Assembly-CSharp/Patches/Language.cs diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs new file mode 100644 index 00000000..763b59cd --- /dev/null +++ b/Assembly-CSharp/Language/Language.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Modding; +using TeamCherry.Localization; +using UnityEngine; +using UObject = UnityEngine.Object; +using TLLanguage = TeamCherry.Localization.Language; + +namespace Language; + +public static class Language +{ + public static void LoadLanguage() => TLLanguage.LoadLanguage(); + public static void LoadAvailableLanguages() => TLLanguage.LoadAvailableLanguages(); + public static string[] GetLanguages() => TLLanguage.GetLanguages(); + public static bool SwitchLanguage(string langCode) => TLLanguage.SwitchLanguage(langCode); + public static bool SwitchLanguage(LanguageCode code) => TLLanguage.SwitchLanguage(code); + public static UObject GetAsset(string name) => TLLanguage.GetAsset(name); + public static LanguageCode CurrentLanguage() => TLLanguage.CurrentLanguage(); + public static string Get(string key) => TLLanguage.Get(key); + //public static string Get(string key, string sheetTitle) => TLLanguage.Get(key, sheetTitle); + public static IEnumerable GetSheets() => TLLanguage.GetSheets(); + public static IEnumerable GetKeys(string sheetTitle) => TLLanguage.GetKeys(sheetTitle); + public static bool Has(string key) => TLLanguage.Has(key); + public static bool Has(string key, string sheet) => TLLanguage.Has(key, sheet); + public static bool HasSheet(string sheet) => TLLanguage.HasSheet(sheet); + public static LanguageCode LanguageNameToCode(SystemLanguage name) => TLLanguage.LanguageNameToCode(name); + + public static string GetInternal(string key, string sheetTitle) => TLLanguage.Get(key, sheetTitle); + public static string Get(string key, string sheetTitle) + { + return ModHooks.LanguageGet(key, sheetTitle); + } +} diff --git a/Assembly-CSharp/Menu/MenuUtils.cs b/Assembly-CSharp/Menu/MenuUtils.cs index 0bd47d9a..af0a1de8 100644 --- a/Assembly-CSharp/Menu/MenuUtils.cs +++ b/Assembly-CSharp/Menu/MenuUtils.cs @@ -8,7 +8,7 @@ using UnityEngine; using UnityEngine.UI; using Patch = Modding.Patches; -using Lang = TeamCherry.Localization.Language; +using Lang = Language.Language; namespace Modding.Menu diff --git a/Assembly-CSharp/Mod.cs b/Assembly-CSharp/Mod.cs index c64f4abf..3e476940 100644 --- a/Assembly-CSharp/Mod.cs +++ b/Assembly-CSharp/Mod.cs @@ -10,7 +10,6 @@ using MonoMod.Utils; using System.Linq; using Newtonsoft.Json.Linq; -using Language = TeamCherry.Localization; // ReSharper disable file UnusedMember.Global diff --git a/Assembly-CSharp/ModHooks.cs b/Assembly-CSharp/ModHooks.cs index 1364fc68..0ea986d3 100644 --- a/Assembly-CSharp/ModHooks.cs +++ b/Assembly-CSharp/ModHooks.cs @@ -225,9 +225,7 @@ internal static void LogConsole(string message, LogLevel level) /// N/A internal static string LanguageGet(string key, string sheet) { - // todo: fixme: yea i think you know what would need fixing - // string res = Patches.Language.GetInternal(key, sheet); - string res = TeamCherry.Localization.Language.Get(key, sheet); + string res = Language.Language.GetInternal(key, sheet); if (LanguageGetHook == null) return res; diff --git a/Assembly-CSharp/ModListMenu.cs b/Assembly-CSharp/ModListMenu.cs index 0a7b6082..fec0f388 100644 --- a/Assembly-CSharp/ModListMenu.cs +++ b/Assembly-CSharp/ModListMenu.cs @@ -6,7 +6,7 @@ using UnityEngine.UI; using static Modding.ModLoader; using Patch = Modding.Patches; -using Lang = TeamCherry.Localization.Language; +using Lang = Language.Language; namespace Modding { diff --git a/Assembly-CSharp/Patches/Language.cs b/Assembly-CSharp/Patches/Language.cs deleted file mode 100644 index 093f62b9..00000000 --- a/Assembly-CSharp/Patches/Language.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using MonoMod; -using UnityEngine; - -// ReSharper disable All -#pragma warning disable 1591, CS0649 - -namespace Modding.Patches -{ - // todo: fixme: yea i think you know what would need fixing -// [MonoModPatch("global::Language.Language")] -// public static class Language -// { -// [MonoModIgnore] -// private static Dictionary> currentEntrySheets; -// -// public static string GetInternal(string key, string sheetTitle) -// { -// if (currentEntrySheets == null || !currentEntrySheets.ContainsKey(sheetTitle)) -// { -// Debug.LogError($"The sheet with title \"{sheetTitle}\" does not exist!"); -// return string.Empty; -// } -// -// if (currentEntrySheets[sheetTitle].ContainsKey(key)) -// { -// return currentEntrySheets[sheetTitle][key]; -// } -// -// return "#!#" + key + "#!#"; -// } -// -// public static string Get(string key, string sheetTitle) -// { -// return ModHooks.LanguageGet(key, sheetTitle); -// } -// } -} \ No newline at end of file From e4e10bc77adb57fcb5d8b5c647cb7692e120d111 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:35:05 +0100 Subject: [PATCH 05/35] use `MonoMod.MonoModLinkFrom` - but that requires all `TeamCherry.Localization.Language` to be done dynamically, via strings, as *all* references to it are replaced, even ones manually written here --- Assembly-CSharp/Language/Language.cs | 95 +++++++++++++++++++++------- 1 file changed, 71 insertions(+), 24 deletions(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 763b59cd..00f8bdc1 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -1,33 +1,80 @@ +using System; using System.Collections.Generic; -using Modding; -using TeamCherry.Localization; -using UnityEngine; +using System.Reflection; using UObject = UnityEngine.Object; -using TLLanguage = TeamCherry.Localization.Language; +using USystemLanguage = UnityEngine.SystemLanguage; namespace Language; +// for backwards compatibility +[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")] public static class Language { - public static void LoadLanguage() => TLLanguage.LoadLanguage(); - public static void LoadAvailableLanguages() => TLLanguage.LoadAvailableLanguages(); - public static string[] GetLanguages() => TLLanguage.GetLanguages(); - public static bool SwitchLanguage(string langCode) => TLLanguage.SwitchLanguage(langCode); - public static bool SwitchLanguage(LanguageCode code) => TLLanguage.SwitchLanguage(code); - public static UObject GetAsset(string name) => TLLanguage.GetAsset(name); - public static LanguageCode CurrentLanguage() => TLLanguage.CurrentLanguage(); - public static string Get(string key) => TLLanguage.Get(key); - //public static string Get(string key, string sheetTitle) => TLLanguage.Get(key, sheetTitle); - public static IEnumerable GetSheets() => TLLanguage.GetSheets(); - public static IEnumerable GetKeys(string sheetTitle) => TLLanguage.GetKeys(sheetTitle); - public static bool Has(string key) => TLLanguage.Has(key); - public static bool Has(string key, string sheet) => TLLanguage.Has(key, sheet); - public static bool HasSheet(string sheet) => TLLanguage.HasSheet(sheet); - public static LanguageCode LanguageNameToCode(SystemLanguage name) => TLLanguage.LanguageNameToCode(name); - - public static string GetInternal(string key, string sheetTitle) => TLLanguage.Get(key, sheetTitle); - public static string Get(string key, string sheetTitle) + static Language() { - return ModHooks.LanguageGet(key, sheetTitle); + TllType = Type.GetType("TeamCherry.Localization.Language, TeamCherry.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + TllLoadLanguage = TllType.GetMethod("LoadLanguage", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); + TllLoadAvailableLanguages = TllType.GetMethod("LoadAvailableLanguages", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); + TllGetLanguages = TllType.GetMethod("GetLanguages", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); + TllSwitchLanguageStr = TllType.GetMethod("SwitchLanguage", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllSwitchLanguageLc = TllType.GetMethod("SwitchLanguage", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(TeamCherry.Localization.LanguageCode) }, Array.Empty()); + TllGetAsset = TllType.GetMethod("GetAsset", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllCurrentLanguage = TllType.GetMethod("CurrentLanguage", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); + TllGet1 = TllType.GetMethod("Get", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllGetSheets = TllType.GetMethod("GetSheets", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); + TllGetKeys = TllType.GetMethod("GetKeys", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllHas1 = TllType.GetMethod("Has", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllHas2 = TllType.GetMethod("Has", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(string) }, Array.Empty()); + TllHasSheet = TllType.GetMethod("HasSheet", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); + TllLanguageNameToCode = TllType.GetMethod("LanguageNameToCode", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(USystemLanguage) }, Array.Empty()); + TllGet2 = TllType.GetMethod("Get", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(string) }, Array.Empty()); } -} + private static readonly Type TllType; + + private static readonly MethodInfo TllLoadLanguage; + public static void LoadLanguage() => TllLoadLanguage.Invoke(null, Array.Empty()); + + private static readonly MethodInfo TllLoadAvailableLanguages; + public static void LoadAvailableLanguages() => TllLoadAvailableLanguages.Invoke(null, Array.Empty()); + + private static readonly MethodInfo TllGetLanguages; + public static string[] GetLanguages() => (string[])(TllGetLanguages.Invoke(null, Array.Empty())); + + private static readonly MethodInfo TllSwitchLanguageStr; + public static bool SwitchLanguage(string langCode) => (bool)(TllSwitchLanguageStr.Invoke(null, new object[] { langCode })); + + private static readonly MethodInfo TllSwitchLanguageLc; + public static bool SwitchLanguage(TeamCherry.Localization.LanguageCode code) => (bool)(TllSwitchLanguageLc.Invoke(null, new object[] { code })); + + private static readonly MethodInfo TllGetAsset; + public static UObject GetAsset(string name) => (UObject)(TllGetAsset.Invoke(null, new object[] { name })); + + private static readonly MethodInfo TllCurrentLanguage; + public static TeamCherry.Localization.LanguageCode CurrentLanguage() => (TeamCherry.Localization.LanguageCode)(TllCurrentLanguage.Invoke(null, Array.Empty())); + + private static readonly MethodInfo TllGet1; + public static string Get(string key) => (string)(TllGet1.Invoke(null, new object[] { key })); + + private static readonly MethodInfo TllGetSheets; + public static IEnumerable GetSheets() => (IEnumerable)(TllGetSheets.Invoke(null, Array.Empty())); + + private static readonly MethodInfo TllGetKeys; + public static IEnumerable GetKeys(string sheetTitle) => (IEnumerable)(TllGetKeys.Invoke(null, new object[] { sheetTitle })); + + private static readonly MethodInfo TllHas1; + public static bool Has(string key) => (bool)(TllHas1.Invoke(null, new object[] { key })); + + private static readonly MethodInfo TllHas2; + public static bool Has(string key, string sheet) => (bool)(TllHas2.Invoke(null, new object[] { key, sheet })); + + private static readonly MethodInfo TllHasSheet; + public static bool HasSheet(string sheet) => (bool)(TllHasSheet.Invoke(null, new object[] { sheet })); + + private static readonly MethodInfo TllLanguageNameToCode; + public static TeamCherry.Localization.LanguageCode LanguageNameToCode(USystemLanguage name) => (TeamCherry.Localization.LanguageCode)(TllLanguageNameToCode.Invoke(null, new object[] { name })); + + private static readonly MethodInfo TllGet2; + public static string GetInternal(string key, string sheetTitle) => (string)(TllGet2.Invoke(null, new object[] { key, sheetTitle })); + + public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); +} \ No newline at end of file From 7a06e1279e0f72160fdcc2d237e3e4120cd66aaa Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Tue, 23 Dec 2025 17:25:22 +0100 Subject: [PATCH 06/35] adjust patches to methods how they appear in dnspy v6.2.0 --- Assembly-CSharp/Patches/GameManager.cs | 74 +++++----- Assembly-CSharp/Patches/HeroController.cs | 136 +++++++++++------- Assembly-CSharp/Patches/InputHandler.cs | 16 +-- Assembly-CSharp/Patches/MenuSetting.cs | 10 +- Assembly-CSharp/Patches/OnScreenDebugInfo.cs | 40 ------ .../Patches/PlayMakerUnity2DProxy.cs | 2 +- Assembly-CSharp/Patches/SceneManager.cs | 65 +++++---- Assembly-CSharp/Patches/StartManager.cs | 87 ++++++++++- Assembly-CSharp/Patches/TakeDamage.cs | 3 +- Assembly-CSharp/Patches/UIManager.cs | 2 + 10 files changed, 268 insertions(+), 167 deletions(-) delete mode 100644 Assembly-CSharp/Patches/OnScreenDebugInfo.cs diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 90e6d013..99526b73 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -197,9 +197,7 @@ public void SaveGame(int saveSlot, Action callback) text = JsonUtility.ToJson(obj); } - bool flag = this.gameConfig.useSaveEncryption && !Platform.Current.IsFileSystemProtected; - - if (flag) + if (this.gameConfig.useSaveEncryption && !Platform.Current.IsFileSystemProtected) { string graph = Encryption.Encrypt(text); BinaryFormatter binaryFormatter = new BinaryFormatter(); @@ -579,12 +577,12 @@ public IEnumerator LoadSceneAdditive(string destScene) AsyncOperation loadop = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(destScene, LoadSceneMode.Additive); loadop.allowSceneActivation = true; yield return loadop; - yield return UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(exitingScene); + UnityEngine.SceneManagement.SceneManager.UnloadScene(exitingScene); ModHooks.OnSceneChanged(destScene); this.RefreshTilemapInfo(destScene); if (this.IsUnloadAssetsRequired(exitingScene, destScene)) { - Debug.LogFormat(this, "Unloading assets due to zone transition", new object[0]); + Debug.LogFormat(this, "Unloading assets due to zone transition", Array.Empty()); yield return Resources.UnloadUnusedAssets(); } @@ -629,48 +627,46 @@ public void OnWillActivateFirstLevel() [MonoModIgnore] public extern void SetTimeScale(float timescale); + [MonoModIgnore] + private extern void SetPausedState(bool value); + // code has been copied from PauseGameToggle public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = false) { - if (!this.TimeSlowed) + if (this.TimeSlowed) + { + yield break; + } + if (!this.playerData.GetBool(nameof(PlayerData.disablePause)) && this.gameState == GlobalEnums.GameState.PLAYING) { - if (!this.playerData.GetBool(nameof(PlayerData.disablePause)) && this.gameState == GlobalEnums.GameState.PLAYING) + this.isPaused = true; + this.ui.SetState(GlobalEnums.UIState.PAUSED); + this.SetPausedState(true); + this.SetState(GlobalEnums.GameState.PAUSED); + if (HeroController.instance != null) { - this.gameCams.StopCameraShake(); - this.inputHandler.PreventPause(); - this.inputHandler.StopUIInput(); - this.actorSnapshotPaused.TransitionTo(0f); - this.isPaused = true; - this.SetState(GlobalEnums.GameState.PAUSED); - this.ui.AudioGoToPauseMenu(0.2f); - this.ui.UIPauseToDynamicMenu(screen); - if (HeroController.instance != null) - { - HeroController.instance.Pause(); - } - this.gameCams.MoveMenuToHUDCamera(); - this.SetTimeScale(0f); - yield return new WaitForSecondsRealtime(0.8f); - this.inputHandler.AllowPause(); + HeroController.instance.Pause(); } - else if (allowUnpause && this.gameState == GlobalEnums.GameState.PAUSED) + this.gameCams.MoveMenuToHUDCamera(); + this.inputHandler.PreventPause(); + this.inputHandler.StopUIInput(); + yield return new WaitForSecondsRealtime(0.3f); + this.inputHandler.AllowPause(); + } + else if (allowUnpause && this.gameState == GlobalEnums.GameState.PAUSED) + { + this.isPaused = false; + this.inputHandler.PreventPause(); + this.ui.SetState(GlobalEnums.UIState.PLAYING); + this.SetPausedState(false); + this.SetState(GlobalEnums.GameState.PLAYING); + if (HeroController.instance != null) { - this.gameCams.ResumeCameraShake(); - this.inputHandler.PreventPause(); - this.actorSnapshotUnpaused.TransitionTo(0f); - this.isPaused = false; - this.ui.AudioGoToGameplay(0.2f); - this.ui.SetState( GlobalEnums.UIState.PLAYING); - this.SetState( GlobalEnums.GameState.PLAYING); - if (HeroController.instance != null) - { - HeroController.instance.UnPause(); - } - MenuButtonList.ClearAllLastSelected(); - this.SetTimeScale(1f); - yield return new WaitForSecondsRealtime(0.8f); - this.inputHandler.AllowPause(); + HeroController.instance.UnPause(); } + MenuButtonList.ClearAllLastSelected(); + yield return new WaitForSecondsRealtime(0.3f); + this.inputHandler.AllowPause(); } yield break; } diff --git a/Assembly-CSharp/Patches/HeroController.cs b/Assembly-CSharp/Patches/HeroController.cs index c4926810..29306aed 100644 --- a/Assembly-CSharp/Patches/HeroController.cs +++ b/Assembly-CSharp/Patches/HeroController.cs @@ -73,6 +73,47 @@ public void Attack(AttackDirection attackDir) this.wallSlashing = true; this.slashComponent = this.wallSlash; this.slashFsm = this.wallSlashFsm; + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35))) + { + if ((this.playerData.GetInt(nameof(PlayerData.health)) == this.playerData.GetInt(nameof(PlayerData.CurrentMaxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) + { + if (this.transform.localScale.x > 0f) + { + this.grubberFlyBeam = this.grubberFlyBeamPrefabR.Spawn(this.transform.position); + } + else + { + this.grubberFlyBeam = this.grubberFlyBeamPrefabL.Spawn(this.transform.position); + } + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13))) + { + this.grubberFlyBeam.transform.SetScaleY(this.MANTIS_CHARM_SCALE); + } + else + { + this.grubberFlyBeam.transform.SetScaleY(1f); + } + } + if (this.playerData.GetInt(nameof(PlayerData.health)) == 1 && this.playerData.GetBool(nameof(PlayerData.equippedCharm_6)) && this.playerData.GetInt(nameof(PlayerData.healthBlue)) < 1) + { + if (this.transform.localScale.x > 0f) + { + this.grubberFlyBeam = this.grubberFlyBeamPrefabR_fury.Spawn(this.transform.position); + } + else + { + this.grubberFlyBeam = this.grubberFlyBeamPrefabL_fury.Spawn(this.transform.position); + } + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13))) + { + this.grubberFlyBeam.transform.SetScaleY(this.MANTIS_CHARM_SCALE); + } + else + { + this.grubberFlyBeam.transform.SetScaleY(1f); + } + } + } } else { @@ -94,7 +135,7 @@ public void Attack(AttackDirection attackDir) if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35))) { - if ((this.playerData.GetInt(nameof(PlayerData.health)) == this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) + if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) { if (this.transform.localScale.x < 0f) { @@ -144,7 +185,7 @@ public void Attack(AttackDirection attackDir) this.cState.upAttacking = true; if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35))) { - if ((this.playerData.GetInt(nameof(PlayerData.health)) == this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) + if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) { this.grubberFlyBeam = this.grubberFlyBeamPrefabU.Spawn(this.transform.position); this.grubberFlyBeam.transform.SetScaleY(this.transform.localScale.x); @@ -174,7 +215,7 @@ public void Attack(AttackDirection attackDir) this.cState.downAttacking = true; if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35))) { - if ((this.playerData.GetInt(nameof(PlayerData.health)) == this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) + if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.playerData.GetInt(nameof(PlayerData.maxHealth)) && !this.playerData.GetBool(nameof(PlayerData.equippedCharm_27))) || (this.joniBeam && this.playerData.GetBool(nameof(PlayerData.equippedCharm_27)))) { this.grubberFlyBeam = this.grubberFlyBeamPrefabD.Spawn(this.transform.position); this.grubberFlyBeam.transform.SetScaleY(this.transform.localScale.x); @@ -247,17 +288,16 @@ public void Attack(AttackDirection attackDir) [MonoModReplace] public void SoulGain() { - int mpcharge = this.playerData.GetInt("MPCharge"); int num; - if (mpcharge < this.playerData.GetInt("maxMP")) + if (this.playerData.GetInt(nameof(PlayerData.MPCharge)) < this.playerData.GetInt(nameof(PlayerData.maxMP))) { num = 11; - if (this.playerData.GetBool("equippedCharm_20")) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_20))) { num += 3; } - if (this.playerData.GetBool("equippedCharm_21")) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_21))) { num += 8; } @@ -265,22 +305,22 @@ public void SoulGain() else { num = 6; - if (this.playerData.GetBool("equippedCharm_20")) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_20))) { num += 2; } - if (this.playerData.GetBool("equippedCharm_21")) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_21))) { num += 6; } } - int mpreserve = this.playerData.GetInt("MPReserve"); + int mpreserve = this.playerData.GetInt(nameof(PlayerData.MPReserve)); num = Modding.ModHooks.OnSoulGain(num); this.playerData.AddMPCharge(num); GameCameras.instance.soulOrbFSM.SendEvent("MP GAIN"); - if (this.playerData.GetInt("MPReserve") != mpreserve) + if (this.playerData.GetInt(nameof(PlayerData.MPReserve)) != mpreserve) { this.gm.soulVessel_fsm.SendEvent("MP RESERVE UP"); } @@ -475,8 +515,7 @@ private void LookForQueueInput() && this.dashQueueSteps <= this.DASH_QUEUE_STEPS && this.CanDash() && this.dashQueuing - && !ModHooks.OnDashPressed() - && this.CanDash()) + && !ModHooks.OnDashPressed()) { this.HeroDash(); } @@ -531,7 +570,6 @@ private void LookForQueueInput() [MonoModIgnore] private HeroVibrationController vibrationCtrl; - // todo: fixme: this had changes in the method, check if more shit changed [MonoModReplace] public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount, int hazardType) { @@ -578,7 +616,7 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount mixer.StopAllEmissionsWithTag("heroAction"); } - bool flag = false; + bool carefreeShouldStopDamage = false; if (this.carefreeShieldEquipped && hazardType == 1) { if (this.hitsSinceShielded > 7) @@ -591,58 +629,58 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount case 1: if ((float) UnityEngine.Random.Range(1, 100) <= 10f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 2: if ((float) UnityEngine.Random.Range(1, 100) <= 20f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 3: if ((float) UnityEngine.Random.Range(1, 100) <= 30f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 4: if ((float) UnityEngine.Random.Range(1, 100) <= 50f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 5: if ((float) UnityEngine.Random.Range(1, 100) <= 70f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 6: if ((float) UnityEngine.Random.Range(1, 100) <= 80f) { - flag = true; + carefreeShouldStopDamage = true; } break; case 7: if ((float) UnityEngine.Random.Range(1, 100) <= 90f) { - flag = true; + carefreeShouldStopDamage = true; } break; default: - flag = false; + carefreeShouldStopDamage = false; break; } - if (flag) + if (carefreeShouldStopDamage) { this.hitsSinceShielded = 0; this.carefreeShield.SetActive(true); @@ -655,7 +693,7 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount } } - if (this.playerData.GetBool("equippedCharm_5") && this.playerData.GetInt("blockerHits") > 0 && hazardType == 1 && this.cState.focusing && !flag) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_5)) && this.playerData.GetInt(nameof(PlayerData.blockerHits)) > 0 && hazardType == 1 && this.cState.focusing && !carefreeShouldStopDamage) { this.proxyFSM.SendEvent("HeroCtrl-TookBlockerHit"); this.audioSource.PlayOneShot(this.blockerImpact, 1f); @@ -687,24 +725,24 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount if (this.cState.bouncing) { this.CancelBounce(); - this.rb2d.velocity = new Vector2(this.rb2d.velocity.x, 0f); + this.rb2d.linearVelocity = new Vector2(this.rb2d.linearVelocity.x, 0f); } if (this.cState.shroomBouncing) { this.CancelBounce(); - this.rb2d.velocity = new Vector2(this.rb2d.velocity.x, 0f); + this.rb2d.linearVelocity = new Vector2(this.rb2d.linearVelocity.x, 0f); } - if (!flag) + if (!carefreeShouldStopDamage) { this.audioCtrl.PlaySound(HeroSounds.TAKE_HIT); } damageAmount = ModHooks.AfterTakeDamage(hazardType, damageAmount); - if (!this.takeNoDamage && !this.playerData.GetBool("invinciTest")) + if (!this.takeNoDamage && !this.playerData.GetBool(nameof(PlayerData.invinciTest))) { - if (this.playerData.GetBool("overcharmed")) + if (this.playerData.GetBool(nameof(PlayerData.overcharmed))) { this.playerData.TakeHealth(damageAmount * 2); } @@ -714,9 +752,9 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount } } - if (this.playerData.GetBool("equippedCharm_3") && damageAmount > 0) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_3)) && damageAmount > 0) { - if (this.playerData.GetBool("equippedCharm_35")) + if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35))) { this.AddMPCharge(this.GRUB_SOUL_MP_COMBO); } @@ -742,32 +780,35 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount this.OnTakenDamage(); } - if (this.playerData.GetInt("health") == 0) + if (this.playerData.GetInt(nameof(PlayerData.health)) == 0) { base.StartCoroutine(this.Die()); + return; } else if (hazardType == 2) { base.StartCoroutine(this.DieFromHazard(HazardType.SPIKES, (!(go != null)) ? 0f : go.transform.rotation.z)); + return; } else if (hazardType == 3) { base.StartCoroutine(this.DieFromHazard(HazardType.ACID, 0f)); + return; } else if (hazardType == 4) { Debug.Log("Lava death"); + return; } else if (hazardType == 5) { base.StartCoroutine(this.DieFromHazard(HazardType.PIT, 0f)); + return; } - else - { - base.StartCoroutine(this.StartRecoil(damageSide, spawnDamageEffect, damageAmount)); - } + base.StartCoroutine(this.StartRecoil(damageSide, spawnDamageEffect, damageAmount)); + return; } - else if (this.cState.invulnerable && !this.cState.hazardDeath && !this.playerData.GetBool("isInvincible")) + else if (this.cState.invulnerable && !this.cState.hazardDeath && !this.playerData.GetBool(nameof(PlayerData.isInvincible))) { if (hazardType == 2) { @@ -784,8 +825,8 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount } else { - this.audioCtrl.PlaySound(HeroSounds.TAKE_HIT); - base.StartCoroutine(this.DieFromHazard(HazardType.SPIKES, (!(go != null)) ? 0f : go.transform.rotation.z)); + this.audioCtrl.PlaySound(HeroSounds.TAKE_HIT, false); + base.StartCoroutine(this.DieFromHazard(HazardType.SPIKES, (go != null) ? go.transform.rotation.z : 0f)); } } else if (hazardType == 3) @@ -793,14 +834,13 @@ public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount damageAmount = ModHooks.AfterTakeDamage(hazardType, damageAmount); this.playerData.TakeHealth(damageAmount); this.proxyFSM.SendEvent("HeroCtrl-HeroDamaged"); - if (this.playerData.GetInt("health") == 0) + if (this.playerData.GetInt(nameof(PlayerData.health)) == 0) { base.StartCoroutine(this.Die()); + return; } - else - { - base.StartCoroutine(this.DieFromHazard(HazardType.ACID, 0f)); - } + base.StartCoroutine(this.DieFromHazard(HazardType.ACID, 0f)); + return; } else if (hazardType == 4) { @@ -875,7 +915,7 @@ private Vector2 OrigDashVector() origVector = new Vector2 ( velocity, - (!this.cState.onGround) ? BUMP_VELOCITY_DASH : BUMP_VELOCITY + this.cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH ); } else @@ -888,7 +928,7 @@ private Vector2 OrigDashVector() origVector = new Vector2 ( -velocity, - (!this.cState.onGround) ? BUMP_VELOCITY_DASH : BUMP_VELOCITY + this.cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH ); } else @@ -912,7 +952,7 @@ private void Dash() Vector2 vector = OrigDashVector(); vector = ModHooks.DashVelocityChange(vector); - rb2d.velocity = vector; + rb2d.linearVelocity = vector; dash_timer += Time.deltaTime; } diff --git a/Assembly-CSharp/Patches/InputHandler.cs b/Assembly-CSharp/Patches/InputHandler.cs index 3e5a18b4..51cf80e0 100644 --- a/Assembly-CSharp/Patches/InputHandler.cs +++ b/Assembly-CSharp/Patches/InputHandler.cs @@ -21,30 +21,30 @@ public class InputHandler : global::InputHandler [MonoModIgnore] private GameManager gm; + [MonoModIgnore] + private extern void SetCursorVisible(bool value); + // Reverted cursor behavior [MonoModReplace] private void OnGUI() { Cursor.lockState = CursorLockMode.None; - if (isTitleScreenScene) + if (this.isTitleScreenScene) { Cursor.visible = false; return; } - - if (!isMenuScene) + if (this.isMenuScene) { - ModHooks.OnCursor(gm); + Cursor.visible = !this.controllerPressed; return; } - - if (controllerPressed) + if (!this.gm.isPaused) { Cursor.visible = false; return; } - - Cursor.visible = true; + Cursor.visible = !this.controllerPressed; } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/MenuSetting.cs b/Assembly-CSharp/Patches/MenuSetting.cs index 51f1b298..6f989f5b 100644 --- a/Assembly-CSharp/Patches/MenuSetting.cs +++ b/Assembly-CSharp/Patches/MenuSetting.cs @@ -55,15 +55,21 @@ public enum MenuSettingType VSync, // where did 13 go MonitorSelect = 14, - FrameCap, + SwitchFrameCap, ParticleLevel, ShaderQuality, + Dithering, + Noise, // HUH???? GameLanguage = 33, GameBackerCredits, NativeAchievements, + ControllerRumble = 37, + HudVisibility = 39, + CameraShake, NativeInput, - ControllerRumble, + XInput, + MFi, // peepoHappy CustomSetting } diff --git a/Assembly-CSharp/Patches/OnScreenDebugInfo.cs b/Assembly-CSharp/Patches/OnScreenDebugInfo.cs deleted file mode 100644 index a04d41a0..00000000 --- a/Assembly-CSharp/Patches/OnScreenDebugInfo.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading; -using MonoMod; -using UnityEngine; - -// ReSharper disable All -#pragma warning disable 1591, CS0626 - -namespace Modding.Patches -{ - [MonoModPatch("global::OnScreenDebugInfo")] - public class OnScreenDebugInfo : global::OnScreenDebugInfo - { - private extern void orig_Awake(); - - private void Awake() - { - if (ModLoader.LoadState == ModLoader.ModLoadState.NotStarted) - { - Logger.APILogger.Log("Main menu loading"); - ModLoader.LoadState = ModLoader.ModLoadState.Started; - - GameObject obj = new GameObject(); - DontDestroyOnLoad(obj); - - // Preload reflection - new Thread(ReflectionHelper.PreloadCommonTypes).Start(); - - // NonBouncer does absolutely nothing, which makes it a good dummy to run the loader - obj.AddComponent().StartCoroutine(ModLoader.LoadModsInit(obj)); - } - else - { - // Debug log because this is the expected code path - Logger.APILogger.LogDebug($"OnScreenDebugInfo: Already begun mod loading (state {ModLoader.LoadState})"); - } - - orig_Awake(); - } - } -} \ No newline at end of file diff --git a/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs b/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs index 9cd2d27b..5209f962 100644 --- a/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs +++ b/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs @@ -23,7 +23,7 @@ public void Start() } ModHooks.OnColliderCreate(gameObject); - RefreshImplementation(); + this.RefreshImplementation(); } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/SceneManager.cs b/Assembly-CSharp/Patches/SceneManager.cs index 677dfbbd..efc9876d 100644 --- a/Assembly-CSharp/Patches/SceneManager.cs +++ b/Assembly-CSharp/Patches/SceneManager.cs @@ -20,12 +20,14 @@ public class SceneManager : global::SceneManager [MonoModIgnore] private bool heroInfoSent; + // [MonoModIgnore] private extern void orig_Update(); [MonoModIgnore] private GameManager gm; //Added checks for null and an attempt to fix any missing references + // [MonoModReplace] private void Update() { if (this.gameplayScene) @@ -40,40 +42,51 @@ private void Update() orig_Update(); } + [MonoModIgnore] + private Transform borderLeft; + + [MonoModIgnore] + private Transform borderRight; + + [MonoModIgnore] + private Transform borderUp; + + [MonoModIgnore] + private Transform borderDown; + + // [MonoModIgnore] + private extern void orig_OnCameraAspectChanged(float aspect); + //add modhook to send the newly created borders to any mods that want them - [MonoModReplace] - private void DrawBlackBorders() + // [MonoModReplace] + private void OnCameraAspectChanged(float aspect) { - List borders = new List(); - GameObject gameObject = UnityEngine.Object.Instantiate(this.borderPrefab); - gameObject.transform.SetPosition2D(this.gm.sceneWidth + 10f, this.gm.sceneHeight / 2f); - gameObject.transform.localScale = new Vector2(20f, this.gm.sceneHeight + 40f); - borders.Add(gameObject); - - gameObject = UnityEngine.Object.Instantiate(this.borderPrefab); - gameObject.transform.SetPosition2D(-10f, this.gm.sceneHeight / 2f); - gameObject.transform.localScale = new Vector2(20f, this.gm.sceneHeight + 40f); - borders.Add(gameObject); - - gameObject = UnityEngine.Object.Instantiate(this.borderPrefab); - gameObject.transform.SetPosition2D(this.gm.sceneWidth / 2f, this.gm.sceneHeight + 10f); - gameObject.transform.localScale = new Vector2(40f + this.gm.sceneWidth, 20f); - borders.Add(gameObject); - - gameObject = UnityEngine.Object.Instantiate(this.borderPrefab); - gameObject.transform.SetPosition2D(this.gm.sceneWidth / 2f, -10f); - gameObject.transform.localScale = new Vector2(40f + this.gm.sceneWidth, 20f); - borders.Add(gameObject); + orig_OnCameraAspectChanged(aspect); - ModHooks.OnDrawBlackBorders(borders); - - foreach (var border in borders) + List borders = new List(); + if (this.borderLeft != null) + { + borders.Add(this.borderLeft.gameObject); + } + if (this.borderRight != null) + { + borders.Add(this.borderRight.gameObject); + } + if (this.borderUp != null) + { + borders.Add(this.borderUp.gameObject); + } + if (this.borderDown != null) { - UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(border, base.gameObject.scene); + borders.Add(this.borderDown.gameObject); } + ModHooks.OnDrawBlackBorders(borders); } + // [MonoModIgnore] private extern void orig_Start(); + + // [MonoModReplace] private void Start() { try diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs index 01b8dbb1..da475c89 100644 --- a/Assembly-CSharp/Patches/StartManager.cs +++ b/Assembly-CSharp/Patches/StartManager.cs @@ -1,6 +1,9 @@ -using System.Collections; +using System; +using System.Collections; +using System.Threading; using MonoMod; using UnityEngine; +using UObject = UnityEngine.Object; // ReSharper disable All #pragma warning disable 1591, CS0649 @@ -12,6 +15,33 @@ namespace Modding.Patches [MonoModPatch("global::StartManager")] public class StartManager : global::StartManager { + private extern void orig_Awake(); + + private void Awake() + { + orig_Awake(); + + if (ModLoader.LoadState == ModLoader.ModLoadState.NotStarted) + { + Logger.APILogger.Log("Main menu loading"); + ModLoader.LoadState = ModLoader.ModLoadState.Started; + + GameObject obj = new GameObject(); + DontDestroyOnLoad(obj); + + // Preload reflection + new Thread(ReflectionHelper.PreloadCommonTypes).Start(); + + // NonBouncer does absolutely nothing, which makes it a good dummy to run the loader + obj.AddComponent().StartCoroutine(ModLoader.LoadModsInit(obj)); + } + else + { + // Debug log because this is the expected code path + Logger.APILogger.LogDebug($"StartManager: Already begun mod loading (state {ModLoader.LoadState})"); + } + } + [MonoModIgnore] private bool confirmedLanguage; @@ -35,6 +65,7 @@ private IEnumerator Start() this.controllerImage.sprite = this.GetControllerSpriteForPlatform(this.platform); AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Menu_Title"); loadOperation.allowSceneActivation = false; + Platform.Current.SetSceneLoadState(true, false); bool showLanguageSelect = !this.CheckIsLanguageSet(); if (showLanguageSelect && Platform.Current.ShowLanguageSelect) { @@ -46,12 +77,64 @@ private IEnumerator Start() yield return base.StartCoroutine(this.LanguageSettingDone()); } - + TeamCherry.Localization.LanguageCode currentLanguage = Language.Language.CurrentLanguage(); + while (!Platform.Current.IsSharedDataMounted) + { + yield return null; + } + bool flag = false; + string text; + if (TeamCherry.Localization.LocalizationProjectSettings.TryGetSavedLanguageCode(out text)) + { + TeamCherry.Localization.LanguageCode languageEnum = TeamCherry.Localization.LocalizationSettings.GetLanguageEnum(text); + if (currentLanguage != languageEnum) + { + flag = true; + } + } + if (flag) + { + Language.Language.LoadLanguage(); + ChangeFontByLanguage[] array = UObject.FindObjectsByType(FindObjectsSortMode.None); + for (int i = 0; i < array.Length; i++) + { + array[i].SetFont(); + } + SetTextMeshProGameText[] componentsInChildren = base.GetComponentsInChildren(true); + for (int i = 0; i < componentsInChildren.Length; i++) + { + componentsInChildren[i].UpdateText(); + } + LogoLanguage[] componentsInChildren2 = base.GetComponentsInChildren(true); + for (int i = 0; i < componentsInChildren2.Length; i++) + { + componentsInChildren2[i].SetSprite(); + } + } this.startManagerAnimator.SetBool("WillShowControllerNotice", false); this.startManagerAnimator.SetBool("WillShowQuote", true); StandaloneLoadingSpinner loadSpinner = UnityEngine.Object.Instantiate(this.loadSpinnerPrefab); loadSpinner.Setup(null); + bool didWaitForPlayerPrefs = false; + while (!Platform.Current.IsPlayerPrefsLoaded) + { + if (!didWaitForPlayerPrefs) + { + didWaitForPlayerPrefs = true; + Debug.LogFormat("Waiting for PlayerPrefs load...", Array.Empty()); + } + yield return null; + } + if (!didWaitForPlayerPrefs) + { + Debug.LogFormat("Didn't need to wait for PlayerPrefs load.", Array.Empty()); + } + else + { + Debug.LogFormat("Finished waiting for PlayerPrefs load.", Array.Empty()); + } + Platform.Current.SetSceneLoadState(true, true); loadOperation.allowSceneActivation = true; yield return loadOperation; yield break; diff --git a/Assembly-CSharp/Patches/TakeDamage.cs b/Assembly-CSharp/Patches/TakeDamage.cs index fdd497ae..3210812e 100644 --- a/Assembly-CSharp/Patches/TakeDamage.cs +++ b/Assembly-CSharp/Patches/TakeDamage.cs @@ -11,6 +11,7 @@ public class TakeDamage : HutongGames.PlayMaker.Actions.TakeDamage [MonoModReplace] public override void OnEnter() { + base.OnEnter(); HitInstance hit = new HitInstance { Source = base.Owner, @@ -22,7 +23,7 @@ public override void OnEnter() MagnitudeMultiplier = this.MagnitudeMultiplier.Value, MoveAngle = this.MoveAngle.Value, MoveDirection = this.MoveDirection.Value, - Multiplier = ((!this.Multiplier.IsNone) ? this.Multiplier.Value : 1f), + Multiplier = (this.Multiplier.IsNone ? 1f : this.Multiplier.Value), SpecialType = (SpecialTypes) this.SpecialType.Value, IsExtraDamage = false }; diff --git a/Assembly-CSharp/Patches/UIManager.cs b/Assembly-CSharp/Patches/UIManager.cs index 0de510dd..b79be999 100644 --- a/Assembly-CSharp/Patches/UIManager.cs +++ b/Assembly-CSharp/Patches/UIManager.cs @@ -194,10 +194,12 @@ public enum MainMenuState MAIN_MENU, OPTIONS_MENU, GAMEPAD_MENU, + ADVANCED_GAMEPAD_MENU, KEYBOARD_MENU, SAVE_PROFILES, AUDIO_MENU, VIDEO_MENU, + ADVANCED_VIDEO_MENU, EXIT_PROMPT, OVERSCAN_MENU, GAME_OPTIONS_MENU, From e0e6d67a00058b2a8c2ebf1af1cb89393baed3cf Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:26:03 +0100 Subject: [PATCH 07/35] make use of game's vanilla binaries & don't have newtonsoft and mscorlib in output --- Assembly-CSharp/Assembly-CSharp.csproj | 31 +++++++++----------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/Assembly-CSharp/Assembly-CSharp.csproj b/Assembly-CSharp/Assembly-CSharp.csproj index d9493277..7a5ae4fe 100644 --- a/Assembly-CSharp/Assembly-CSharp.csproj +++ b/Assembly-CSharp/Assembly-CSharp.csproj @@ -10,6 +10,7 @@ true packages latest + true @@ -17,7 +18,6 @@ - @@ -72,10 +72,8 @@ - + - - @@ -141,25 +139,16 @@ - - ../Vanilla/Assembly-CSharp.dll - False - - - ..\override\mscorlib.dll - - - ../Vanilla/netstandard.dll - - - ../JsonNet/Newtonsoft.Json.dll - - - ../Vanilla/PlayMaker.dll - False - + + + + + + + + From e91596fa6e2471e4535fcb306f75f689fa925e47 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 31 Dec 2025 18:07:33 +0100 Subject: [PATCH 08/35] remove unused method --- Assembly-CSharp/Patches/InputHandler.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Assembly-CSharp/Patches/InputHandler.cs b/Assembly-CSharp/Patches/InputHandler.cs index 51cf80e0..2dd48dcf 100644 --- a/Assembly-CSharp/Patches/InputHandler.cs +++ b/Assembly-CSharp/Patches/InputHandler.cs @@ -21,9 +21,6 @@ public class InputHandler : global::InputHandler [MonoModIgnore] private GameManager gm; - [MonoModIgnore] - private extern void SetCursorVisible(bool value); - // Reverted cursor behavior [MonoModReplace] private void OnGUI() From bff85a4e58141f4958b9daf0bb703a8f32ee5269 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 31 Dec 2025 18:07:53 +0100 Subject: [PATCH 09/35] remove base onenter --- Assembly-CSharp/Patches/TakeDamage.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assembly-CSharp/Patches/TakeDamage.cs b/Assembly-CSharp/Patches/TakeDamage.cs index 3210812e..5a55ff7e 100644 --- a/Assembly-CSharp/Patches/TakeDamage.cs +++ b/Assembly-CSharp/Patches/TakeDamage.cs @@ -11,7 +11,6 @@ public class TakeDamage : HutongGames.PlayMaker.Actions.TakeDamage [MonoModReplace] public override void OnEnter() { - base.OnEnter(); HitInstance hit = new HitInstance { Source = base.Owner, From 7b64c07950b80c6b086b6e8f717788086bca6313 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 31 Dec 2025 18:24:32 +0100 Subject: [PATCH 10/35] made loading work better --- Assembly-CSharp/Patches/StartManager.cs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs index da475c89..933e1000 100644 --- a/Assembly-CSharp/Patches/StartManager.cs +++ b/Assembly-CSharp/Patches/StartManager.cs @@ -15,15 +15,16 @@ namespace Modding.Patches [MonoModPatch("global::StartManager")] public class StartManager : global::StartManager { + private bool startedPreloading = false; + private extern void orig_Awake(); private void Awake() { - orig_Awake(); - if (ModLoader.LoadState == ModLoader.ModLoadState.NotStarted) { Logger.APILogger.Log("Main menu loading"); + startedPreloading = true; ModLoader.LoadState = ModLoader.ModLoadState.Started; GameObject obj = new GameObject(); @@ -40,6 +41,8 @@ private void Awake() // Debug log because this is the expected code path Logger.APILogger.LogDebug($"StartManager: Already begun mod loading (state {ModLoader.LoadState})"); } + + orig_Awake(); } [MonoModIgnore] @@ -60,11 +63,17 @@ private void Awake() [MonoModIgnore] private extern IEnumerator LanguageSettingDone(); + [MonoModReplace] private IEnumerator Start() { this.controllerImage.sprite = this.GetControllerSpriteForPlatform(this.platform); - AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Menu_Title"); - loadOperation.allowSceneActivation = false; + + AsyncOperation loadOperation = null; + if (!startedPreloading) + { + loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Menu_Title"); + loadOperation.allowSceneActivation = false; + } Platform.Current.SetSceneLoadState(true, false); bool showLanguageSelect = !this.CheckIsLanguageSet(); if (showLanguageSelect && Platform.Current.ShowLanguageSelect) @@ -135,8 +144,11 @@ private IEnumerator Start() Debug.LogFormat("Finished waiting for PlayerPrefs load.", Array.Empty()); } Platform.Current.SetSceneLoadState(true, true); - loadOperation.allowSceneActivation = true; - yield return loadOperation; + if (!startedPreloading) + { + loadOperation.allowSceneActivation = true; + yield return loadOperation; + } yield break; } } From 9adc87856ff7a5ef710a8ff0c308478698bd3c3b Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:45:49 +0100 Subject: [PATCH 11/35] update hollowknight.version --- hollowknight.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hollowknight.version b/hollowknight.version index 8a7a4d76..68c21b76 100644 --- a/hollowknight.version +++ b/hollowknight.version @@ -1 +1 @@ -1.5.78.11833 \ No newline at end of file +1.5.12459 \ No newline at end of file From 86d0b1b4adf69a95fbf32c7c9fdb9e52a30a6826 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 11 Feb 2026 20:47:36 +0100 Subject: [PATCH 12/35] full canvas, no offsets & modloader obj --- Assembly-CSharp/Console.cs | 3 ++- Assembly-CSharp/Patches/StartManager.cs | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Assembly-CSharp/Console.cs b/Assembly-CSharp/Console.cs index d9fb51a4..a29911f8 100644 --- a/Assembly-CSharp/Console.cs +++ b/Assembly-CSharp/Console.cs @@ -76,7 +76,8 @@ public void Start() string.Join(string.Empty, _messages.ToArray()), _fontSize, TextAnchor.LowerLeft, - new CanvasUtil.RectData(new Vector2(-5, -5), Vector2.zero, Vector2.zero, Vector2.one), + //new CanvasUtil.RectData(new Vector2(-5, -5), Vector2.zero, Vector2.zero, Vector2.one), + new CanvasUtil.RectData(Vector2.zero, Vector2.zero, Vector2.zero, Vector2.one), _font ); diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs index 933e1000..dc9e9fd0 100644 --- a/Assembly-CSharp/Patches/StartManager.cs +++ b/Assembly-CSharp/Patches/StartManager.cs @@ -16,6 +16,7 @@ namespace Modding.Patches public class StartManager : global::StartManager { private bool startedPreloading = false; + private MonoBehaviour modLoaderObj = null; private extern void orig_Awake(); @@ -34,7 +35,7 @@ private void Awake() new Thread(ReflectionHelper.PreloadCommonTypes).Start(); // NonBouncer does absolutely nothing, which makes it a good dummy to run the loader - obj.AddComponent().StartCoroutine(ModLoader.LoadModsInit(obj)); + modLoaderObj = obj.AddComponent(); } else { @@ -149,6 +150,10 @@ private IEnumerator Start() loadOperation.allowSceneActivation = true; yield return loadOperation; } + else + { + modLoaderObj.StartCoroutine(ModLoader.LoadModsInit(modLoaderObj.gameObject)); + } yield break; } } From 05b3ccf72a1a5c255655f774dd4d818b97237789 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 18 Feb 2026 20:53:27 +0100 Subject: [PATCH 13/35] add one backwards-compatability property --- Assembly-CSharp/Patches/DesktopPlatform.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Assembly-CSharp/Patches/DesktopPlatform.cs diff --git a/Assembly-CSharp/Patches/DesktopPlatform.cs b/Assembly-CSharp/Patches/DesktopPlatform.cs new file mode 100644 index 00000000..c5548c15 --- /dev/null +++ b/Assembly-CSharp/Patches/DesktopPlatform.cs @@ -0,0 +1,18 @@ +using System; +using MonoMod; + +// ReSharper disable all +#pragma warning disable 1591, 649, 414, 169, CS0108, CS0626 + +namespace Modding.Patches +{ + [MonoModPatch("global::DesktopPlatform")] + public class DesktopPlatform : global::DesktopPlatform + { + [Obsolete("Please update your mod to the new HK version and use `RoamingSharedData` instead")] + public ISharedData EncryptedSharedData + { + get { return RoamingSharedData; } + } + } +} \ No newline at end of file From 207132db863a8b12bc2b39a21b4b98ba152f516b Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:08:48 +0100 Subject: [PATCH 14/35] who needs reflection if you have monomod fuckery --- Assembly-CSharp/Language/Language.cs | 130 +++++++------- Assembly-CSharp/Language/LanguageCode.cs | 211 +++++++++++++++++++++++ Assembly-CSharp/Patches/StartManager.cs | 2 +- 3 files changed, 276 insertions(+), 67 deletions(-) create mode 100644 Assembly-CSharp/Language/LanguageCode.cs diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 00f8bdc1..5f65cc57 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -4,77 +4,75 @@ using UObject = UnityEngine.Object; using USystemLanguage = UnityEngine.SystemLanguage; + namespace Language; // for backwards compatibility [MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")] public static class Language { - static Language() - { - TllType = Type.GetType("TeamCherry.Localization.Language, TeamCherry.Localization, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); - TllLoadLanguage = TllType.GetMethod("LoadLanguage", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); - TllLoadAvailableLanguages = TllType.GetMethod("LoadAvailableLanguages", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); - TllGetLanguages = TllType.GetMethod("GetLanguages", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); - TllSwitchLanguageStr = TllType.GetMethod("SwitchLanguage", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllSwitchLanguageLc = TllType.GetMethod("SwitchLanguage", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(TeamCherry.Localization.LanguageCode) }, Array.Empty()); - TllGetAsset = TllType.GetMethod("GetAsset", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllCurrentLanguage = TllType.GetMethod("CurrentLanguage", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); - TllGet1 = TllType.GetMethod("Get", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllGetSheets = TllType.GetMethod("GetSheets", BindingFlags.Public | BindingFlags.Static, null, Array.Empty(), Array.Empty()); - TllGetKeys = TllType.GetMethod("GetKeys", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllHas1 = TllType.GetMethod("Has", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllHas2 = TllType.GetMethod("Has", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(string) }, Array.Empty()); - TllHasSheet = TllType.GetMethod("HasSheet", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string) }, Array.Empty()); - TllLanguageNameToCode = TllType.GetMethod("LanguageNameToCode", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(USystemLanguage) }, Array.Empty()); - TllGet2 = TllType.GetMethod("Get", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(string) }, Array.Empty()); - } - private static readonly Type TllType; - - private static readonly MethodInfo TllLoadLanguage; - public static void LoadLanguage() => TllLoadLanguage.Invoke(null, Array.Empty()); - - private static readonly MethodInfo TllLoadAvailableLanguages; - public static void LoadAvailableLanguages() => TllLoadAvailableLanguages.Invoke(null, Array.Empty()); - - private static readonly MethodInfo TllGetLanguages; - public static string[] GetLanguages() => (string[])(TllGetLanguages.Invoke(null, Array.Empty())); - - private static readonly MethodInfo TllSwitchLanguageStr; - public static bool SwitchLanguage(string langCode) => (bool)(TllSwitchLanguageStr.Invoke(null, new object[] { langCode })); - - private static readonly MethodInfo TllSwitchLanguageLc; - public static bool SwitchLanguage(TeamCherry.Localization.LanguageCode code) => (bool)(TllSwitchLanguageLc.Invoke(null, new object[] { code })); - - private static readonly MethodInfo TllGetAsset; - public static UObject GetAsset(string name) => (UObject)(TllGetAsset.Invoke(null, new object[] { name })); - - private static readonly MethodInfo TllCurrentLanguage; - public static TeamCherry.Localization.LanguageCode CurrentLanguage() => (TeamCherry.Localization.LanguageCode)(TllCurrentLanguage.Invoke(null, Array.Empty())); - - private static readonly MethodInfo TllGet1; - public static string Get(string key) => (string)(TllGet1.Invoke(null, new object[] { key })); - - private static readonly MethodInfo TllGetSheets; - public static IEnumerable GetSheets() => (IEnumerable)(TllGetSheets.Invoke(null, Array.Empty())); - - private static readonly MethodInfo TllGetKeys; - public static IEnumerable GetKeys(string sheetTitle) => (IEnumerable)(TllGetKeys.Invoke(null, new object[] { sheetTitle })); - - private static readonly MethodInfo TllHas1; - public static bool Has(string key) => (bool)(TllHas1.Invoke(null, new object[] { key })); - - private static readonly MethodInfo TllHas2; - public static bool Has(string key, string sheet) => (bool)(TllHas2.Invoke(null, new object[] { key, sheet })); - - private static readonly MethodInfo TllHasSheet; - public static bool HasSheet(string sheet) => (bool)(TllHasSheet.Invoke(null, new object[] { sheet })); - - private static readonly MethodInfo TllLanguageNameToCode; - public static TeamCherry.Localization.LanguageCode LanguageNameToCode(USystemLanguage name) => (TeamCherry.Localization.LanguageCode)(TllLanguageNameToCode.Invoke(null, new object[] { name })); - - private static readonly MethodInfo TllGet2; - public static string GetInternal(string key, string sheetTitle) => (string)(TllGet2.Invoke(null, new object[] { key, sheetTitle })); - + public static void LoadLanguage() => COMPAT_LoadLanguage(); + public static void LoadAvailableLanguages() => COMPAT_LoadAvailableLanguages(); + public static string[] GetLanguages() => COMPAT_GetLanguages(); + public static bool SwitchLanguage(string langCode) => COMPAT_SwitchLanguage(langCode); + public static bool SwitchLanguage(TeamCherry.Localization.LanguageCode code) => COMPAT_SwitchLanguage(code); + public static UObject GetAsset(string name) => COMPAT_GetAsset(name); + public static LanguageCode CurrentLanguage() => (LanguageCode) COMPAT_CurrentLanguage(); + public static string Get(string key) => COMPAT_Get(key); + public static IEnumerable GetSheets() => COMPAT_GetSheets(); + public static IEnumerable GetKeys(string sheetTitle) => COMPAT_GetKeys(sheetTitle); + public static bool Has(string key) => COMPAT_Has(key); + public static bool Has(string key, string sheet) => COMPAT_Has(key, sheet); + public static bool HasSheet(string sheet) => COMPAT_HasSheet(sheet); + public static LanguageCode LanguageNameToCode(USystemLanguage name) => (LanguageCode) COMPAT_LanguageNameToCode(name); + public static string GetInternal(string key, string sheetTitle) => COMPAT_Get(key, sheetTitle); public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); + + // for backwards compatibility + // KEEP THIS BELOW THE Language CLASS!!! + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadLanguage()")] + [MonoMod.MonoModRemove] + public static extern void COMPAT_LoadLanguage(); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadAvailableLanguages()")] + [MonoMod.MonoModRemove] + public static extern void COMPAT_LoadAvailableLanguages(); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String[] GetLanguages()")] + [MonoMod.MonoModRemove] + public static extern string[] COMPAT_GetLanguages(); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(System.String)")] + [MonoMod.MonoModRemove] + public static extern bool COMPAT_SwitchLanguage(string langCode); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(TeamCherry.Localization.LanguageCode)")] + [MonoMod.MonoModRemove] + public static extern bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "UnityEngine.Object GetAsset(System.String)")] + [MonoMod.MonoModRemove] + public static extern UObject COMPAT_GetAsset(string name); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode CurrentLanguage()")] + [MonoMod.MonoModRemove] + public static extern TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage(); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String)")] + [MonoMod.MonoModRemove] + public static extern string COMPAT_Get(string key); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")] + [MonoMod.MonoModRemove] + public static extern string COMPAT_Get(string key, string sheetTitle); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetSheets()")] + [MonoMod.MonoModRemove] + public static extern IEnumerable COMPAT_GetSheets(); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetKeys(System.String)")] + [MonoMod.MonoModRemove] + public static extern IEnumerable COMPAT_GetKeys(string sheetTitle); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String)")] + [MonoMod.MonoModRemove] + public static extern bool COMPAT_Has(string key); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String,System.String)")] + [MonoMod.MonoModRemove] + public static extern bool COMPAT_Has(string key, string sheet); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasSheet(System.String)")] + [MonoMod.MonoModRemove] + public static extern bool COMPAT_HasSheet(string sheet); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode LanguageNameToCode(UnityEngine.SystemLanguage)")] + [MonoMod.MonoModRemove] + public static extern TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); } \ No newline at end of file diff --git a/Assembly-CSharp/Language/LanguageCode.cs b/Assembly-CSharp/Language/LanguageCode.cs new file mode 100644 index 00000000..5f3fce38 --- /dev/null +++ b/Assembly-CSharp/Language/LanguageCode.cs @@ -0,0 +1,211 @@ +namespace Language; + +// for backwards compatibility +public enum LanguageCode +{ + N = TeamCherry.Localization.LanguageCode.N, + AA = TeamCherry.Localization.LanguageCode.AA, + AB = TeamCherry.Localization.LanguageCode.AB, + AF = TeamCherry.Localization.LanguageCode.AF, + AM = TeamCherry.Localization.LanguageCode.AM, + AR = TeamCherry.Localization.LanguageCode.AR, + AR_SA = TeamCherry.Localization.LanguageCode.AR_SA, + AR_EG = TeamCherry.Localization.LanguageCode.AR_EG, + AR_DZ = TeamCherry.Localization.LanguageCode.AR_DZ, + AR_YE = TeamCherry.Localization.LanguageCode.AR_YE, + AR_JO = TeamCherry.Localization.LanguageCode.AR_JO, + AR_KW = TeamCherry.Localization.LanguageCode.AR_KW, + AR_BH = TeamCherry.Localization.LanguageCode.AR_BH, + AR_IQ = TeamCherry.Localization.LanguageCode.AR_IQ, + AR_MA = TeamCherry.Localization.LanguageCode.AR_MA, + AR_LY = TeamCherry.Localization.LanguageCode.AR_LY, + AR_OM = TeamCherry.Localization.LanguageCode.AR_OM, + AR_SY = TeamCherry.Localization.LanguageCode.AR_SY, + AR_LB = TeamCherry.Localization.LanguageCode.AR_LB, + AR_AE = TeamCherry.Localization.LanguageCode.AR_AE, + AR_QA = TeamCherry.Localization.LanguageCode.AR_QA, + AS = TeamCherry.Localization.LanguageCode.AS, + AY = TeamCherry.Localization.LanguageCode.AY, + AZ = TeamCherry.Localization.LanguageCode.AZ, + BA = TeamCherry.Localization.LanguageCode.BA, + BE = TeamCherry.Localization.LanguageCode.BE, + BG = TeamCherry.Localization.LanguageCode.BG, + BH = TeamCherry.Localization.LanguageCode.BH, + BI = TeamCherry.Localization.LanguageCode.BI, + BN = TeamCherry.Localization.LanguageCode.BN, + BO = TeamCherry.Localization.LanguageCode.BO, + BR = TeamCherry.Localization.LanguageCode.BR, + CA = TeamCherry.Localization.LanguageCode.CA, + CO = TeamCherry.Localization.LanguageCode.CO, + CS = TeamCherry.Localization.LanguageCode.CS, + CY = TeamCherry.Localization.LanguageCode.CY, + DA = TeamCherry.Localization.LanguageCode.DA, + DE = TeamCherry.Localization.LanguageCode.DE, + DE_AT = TeamCherry.Localization.LanguageCode.DE_AT, + DE_LI = TeamCherry.Localization.LanguageCode.DE_LI, + DE_CH = TeamCherry.Localization.LanguageCode.DE_CH, + DE_LU = TeamCherry.Localization.LanguageCode.DE_LU, + DZ = TeamCherry.Localization.LanguageCode.DZ, + EL = TeamCherry.Localization.LanguageCode.EL, + EN = TeamCherry.Localization.LanguageCode.EN, + EN_US = TeamCherry.Localization.LanguageCode.EN_US, + EN_AU = TeamCherry.Localization.LanguageCode.EN_AU, + EN_NZ = TeamCherry.Localization.LanguageCode.EN_NZ, + EN_ZA = TeamCherry.Localization.LanguageCode.EN_ZA, + EN_CB = TeamCherry.Localization.LanguageCode.EN_CB, + EN_TT = TeamCherry.Localization.LanguageCode.EN_TT, + EN_GB = TeamCherry.Localization.LanguageCode.EN_GB, + EN_CA = TeamCherry.Localization.LanguageCode.EN_CA, + EN_IE = TeamCherry.Localization.LanguageCode.EN_IE, + EN_JM = TeamCherry.Localization.LanguageCode.EN_JM, + EN_BZ = TeamCherry.Localization.LanguageCode.EN_BZ, + EO = TeamCherry.Localization.LanguageCode.EO, + ES = TeamCherry.Localization.LanguageCode.ES, + ES_MX = TeamCherry.Localization.LanguageCode.ES_MX, + ES_CR = TeamCherry.Localization.LanguageCode.ES_CR, + ES_DO = TeamCherry.Localization.LanguageCode.ES_DO, + ES_CO = TeamCherry.Localization.LanguageCode.ES_CO, + ES_AR = TeamCherry.Localization.LanguageCode.ES_AR, + ES_CL = TeamCherry.Localization.LanguageCode.ES_CL, + ES_PY = TeamCherry.Localization.LanguageCode.ES_PY, + ES_SV = TeamCherry.Localization.LanguageCode.ES_SV, + ES_NI = TeamCherry.Localization.LanguageCode.ES_NI, + ES_GT = TeamCherry.Localization.LanguageCode.ES_GT, + ES_PA = TeamCherry.Localization.LanguageCode.ES_PA, + ES_VE = TeamCherry.Localization.LanguageCode.ES_VE, + ES_PE = TeamCherry.Localization.LanguageCode.ES_PE, + ES_EC = TeamCherry.Localization.LanguageCode.ES_EC, + ES_UY = TeamCherry.Localization.LanguageCode.ES_UY, + ES_BO = TeamCherry.Localization.LanguageCode.ES_BO, + ES_HN = TeamCherry.Localization.LanguageCode.ES_HN, + ES_PR = TeamCherry.Localization.LanguageCode.ES_PR, + ET = TeamCherry.Localization.LanguageCode.ET, + EU = TeamCherry.Localization.LanguageCode.EU, + FA = TeamCherry.Localization.LanguageCode.FA, + FI = TeamCherry.Localization.LanguageCode.FI, + FJ = TeamCherry.Localization.LanguageCode.FJ, + FO = TeamCherry.Localization.LanguageCode.FO, + FR = TeamCherry.Localization.LanguageCode.FR, + FR_BE = TeamCherry.Localization.LanguageCode.FR_BE, + FR_CH = TeamCherry.Localization.LanguageCode.FR_CH, + FR_CA = TeamCherry.Localization.LanguageCode.FR_CA, + FR_LU = TeamCherry.Localization.LanguageCode.FR_LU, + FY = TeamCherry.Localization.LanguageCode.FY, + GA = TeamCherry.Localization.LanguageCode.GA, + GD = TeamCherry.Localization.LanguageCode.GD, + GL = TeamCherry.Localization.LanguageCode.GL, + GN = TeamCherry.Localization.LanguageCode.GN, + GU = TeamCherry.Localization.LanguageCode.GU, + HA = TeamCherry.Localization.LanguageCode.HA, + HI = TeamCherry.Localization.LanguageCode.HI, + HE = TeamCherry.Localization.LanguageCode.HE, + HR = TeamCherry.Localization.LanguageCode.HR, + HU = TeamCherry.Localization.LanguageCode.HU, + HY = TeamCherry.Localization.LanguageCode.HY, + IA = TeamCherry.Localization.LanguageCode.IA, + ID = TeamCherry.Localization.LanguageCode.ID, + IE = TeamCherry.Localization.LanguageCode.IE, + IK = TeamCherry.Localization.LanguageCode.IK, + IN = TeamCherry.Localization.LanguageCode.IN, + IS = TeamCherry.Localization.LanguageCode.IS, + IT = TeamCherry.Localization.LanguageCode.IT, + IT_CH = TeamCherry.Localization.LanguageCode.IT_CH, + IU = TeamCherry.Localization.LanguageCode.IU, + IW = TeamCherry.Localization.LanguageCode.IW, + JA = TeamCherry.Localization.LanguageCode.JA, + JI = TeamCherry.Localization.LanguageCode.JI, + JW = TeamCherry.Localization.LanguageCode.JW, + KA = TeamCherry.Localization.LanguageCode.KA, + KK = TeamCherry.Localization.LanguageCode.KK, + KL = TeamCherry.Localization.LanguageCode.KL, + KM = TeamCherry.Localization.LanguageCode.KM, + KN = TeamCherry.Localization.LanguageCode.KN, + KO = TeamCherry.Localization.LanguageCode.KO, + KS = TeamCherry.Localization.LanguageCode.KS, + KU = TeamCherry.Localization.LanguageCode.KU, + KY = TeamCherry.Localization.LanguageCode.KY, + LA = TeamCherry.Localization.LanguageCode.LA, + LN = TeamCherry.Localization.LanguageCode.LN, + LO = TeamCherry.Localization.LanguageCode.LO, + LT = TeamCherry.Localization.LanguageCode.LT, + LV = TeamCherry.Localization.LanguageCode.LV, + MG = TeamCherry.Localization.LanguageCode.MG, + MI = TeamCherry.Localization.LanguageCode.MI, + MK = TeamCherry.Localization.LanguageCode.MK, + ML = TeamCherry.Localization.LanguageCode.ML, + MN = TeamCherry.Localization.LanguageCode.MN, + MO = TeamCherry.Localization.LanguageCode.MO, + MR = TeamCherry.Localization.LanguageCode.MR, + MS = TeamCherry.Localization.LanguageCode.MS, + MT = TeamCherry.Localization.LanguageCode.MT, + MY = TeamCherry.Localization.LanguageCode.MY, + NA = TeamCherry.Localization.LanguageCode.NA, + NE = TeamCherry.Localization.LanguageCode.NE, + NL = TeamCherry.Localization.LanguageCode.NL, + NL_BE = TeamCherry.Localization.LanguageCode.NL_BE, + NO = TeamCherry.Localization.LanguageCode.NO, + OC = TeamCherry.Localization.LanguageCode.OC, + OM = TeamCherry.Localization.LanguageCode.OM, + OR = TeamCherry.Localization.LanguageCode.OR, + PA = TeamCherry.Localization.LanguageCode.PA, + PL = TeamCherry.Localization.LanguageCode.PL, + PS = TeamCherry.Localization.LanguageCode.PS, + PT = TeamCherry.Localization.LanguageCode.PT, + PT_BR = TeamCherry.Localization.LanguageCode.PT_BR, + QU = TeamCherry.Localization.LanguageCode.QU, + RM = TeamCherry.Localization.LanguageCode.RM, + RN = TeamCherry.Localization.LanguageCode.RN, + RO = TeamCherry.Localization.LanguageCode.RO, + RO_MO = TeamCherry.Localization.LanguageCode.RO_MO, + RU = TeamCherry.Localization.LanguageCode.RU, + RU_MO = TeamCherry.Localization.LanguageCode.RU_MO, + RW = TeamCherry.Localization.LanguageCode.RW, + SA = TeamCherry.Localization.LanguageCode.SA, + SD = TeamCherry.Localization.LanguageCode.SD, + SG = TeamCherry.Localization.LanguageCode.SG, + SH = TeamCherry.Localization.LanguageCode.SH, + SI = TeamCherry.Localization.LanguageCode.SI, + SK = TeamCherry.Localization.LanguageCode.SK, + SL = TeamCherry.Localization.LanguageCode.SL, + SM = TeamCherry.Localization.LanguageCode.SM, + SN = TeamCherry.Localization.LanguageCode.SN, + SO = TeamCherry.Localization.LanguageCode.SO, + SQ = TeamCherry.Localization.LanguageCode.SQ, + SR = TeamCherry.Localization.LanguageCode.SR, + SS = TeamCherry.Localization.LanguageCode.SS, + ST = TeamCherry.Localization.LanguageCode.ST, + SU = TeamCherry.Localization.LanguageCode.SU, + SV = TeamCherry.Localization.LanguageCode.SV, + SV_FI = TeamCherry.Localization.LanguageCode.SV_FI, + SW = TeamCherry.Localization.LanguageCode.SW, + TA = TeamCherry.Localization.LanguageCode.TA, + TE = TeamCherry.Localization.LanguageCode.TE, + TG = TeamCherry.Localization.LanguageCode.TG, + TH = TeamCherry.Localization.LanguageCode.TH, + TI = TeamCherry.Localization.LanguageCode.TI, + TK = TeamCherry.Localization.LanguageCode.TK, + TL = TeamCherry.Localization.LanguageCode.TL, + TN = TeamCherry.Localization.LanguageCode.TN, + TO = TeamCherry.Localization.LanguageCode.TO, + TR = TeamCherry.Localization.LanguageCode.TR, + TS = TeamCherry.Localization.LanguageCode.TS, + TT = TeamCherry.Localization.LanguageCode.TT, + TW = TeamCherry.Localization.LanguageCode.TW, + UG = TeamCherry.Localization.LanguageCode.UG, + UK = TeamCherry.Localization.LanguageCode.UK, + UR = TeamCherry.Localization.LanguageCode.UR, + UZ = TeamCherry.Localization.LanguageCode.UZ, + VI = TeamCherry.Localization.LanguageCode.VI, + VO = TeamCherry.Localization.LanguageCode.VO, + WO = TeamCherry.Localization.LanguageCode.WO, + XH = TeamCherry.Localization.LanguageCode.XH, + YI = TeamCherry.Localization.LanguageCode.YI, + YO = TeamCherry.Localization.LanguageCode.YO, + ZA = TeamCherry.Localization.LanguageCode.ZA, + ZH = TeamCherry.Localization.LanguageCode.ZH, + ZH_TW = TeamCherry.Localization.LanguageCode.ZH_TW, + ZH_HK = TeamCherry.Localization.LanguageCode.ZH_HK, + ZH_CN = TeamCherry.Localization.LanguageCode.ZH_CN, + ZH_SG = TeamCherry.Localization.LanguageCode.ZH_SG, + ZU = TeamCherry.Localization.LanguageCode.ZU, +} \ No newline at end of file diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs index dc9e9fd0..43a6f3fc 100644 --- a/Assembly-CSharp/Patches/StartManager.cs +++ b/Assembly-CSharp/Patches/StartManager.cs @@ -87,7 +87,7 @@ private IEnumerator Start() yield return base.StartCoroutine(this.LanguageSettingDone()); } - TeamCherry.Localization.LanguageCode currentLanguage = Language.Language.CurrentLanguage(); + TeamCherry.Localization.LanguageCode currentLanguage = (TeamCherry.Localization.LanguageCode) Language.Language.CurrentLanguage(); while (!Platform.Current.IsSharedDataMounted) { yield return null; From 9674f71b6086388d0562f5f2b2d53042b2d668b6 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:10:04 +0100 Subject: [PATCH 15/35] accidental double newline --- Assembly-CSharp/Language/Language.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 5f65cc57..eeaf5151 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -4,7 +4,6 @@ using UObject = UnityEngine.Object; using USystemLanguage = UnityEngine.SystemLanguage; - namespace Language; // for backwards compatibility From b74e35b409309d202103b9dce5c944265395e2b2 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:29:31 +0100 Subject: [PATCH 16/35] make comment clear make compat methods private --- Assembly-CSharp/Language/Language.cs | 35 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index eeaf5151..4bcbd1ba 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -27,51 +27,52 @@ public static class Language public static string GetInternal(string key, string sheetTitle) => COMPAT_Get(key, sheetTitle); public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); - // for backwards compatibility - // KEEP THIS BELOW THE Language CLASS!!! + // Keep these below the `[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")]`, as the reverse order would cause a cyclic loop of methods calling themselves + // thanks to MonoMod resolving these links top-down + // which would make a `LinkTo(TCLL)->LinkFrom(TCLL)` into a fully recursive function ⟳ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadLanguage()")] [MonoMod.MonoModRemove] - public static extern void COMPAT_LoadLanguage(); + private static extern void COMPAT_LoadLanguage(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadAvailableLanguages()")] [MonoMod.MonoModRemove] - public static extern void COMPAT_LoadAvailableLanguages(); + private static extern void COMPAT_LoadAvailableLanguages(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String[] GetLanguages()")] [MonoMod.MonoModRemove] - public static extern string[] COMPAT_GetLanguages(); + private static extern string[] COMPAT_GetLanguages(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(System.String)")] [MonoMod.MonoModRemove] - public static extern bool COMPAT_SwitchLanguage(string langCode); + private static extern bool COMPAT_SwitchLanguage(string langCode); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(TeamCherry.Localization.LanguageCode)")] [MonoMod.MonoModRemove] - public static extern bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code); + private static extern bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "UnityEngine.Object GetAsset(System.String)")] [MonoMod.MonoModRemove] - public static extern UObject COMPAT_GetAsset(string name); + private static extern UObject COMPAT_GetAsset(string name); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode CurrentLanguage()")] [MonoMod.MonoModRemove] - public static extern TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage(); + private static extern TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String)")] [MonoMod.MonoModRemove] - public static extern string COMPAT_Get(string key); + private static extern string COMPAT_Get(string key); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")] [MonoMod.MonoModRemove] - public static extern string COMPAT_Get(string key, string sheetTitle); + private static extern string COMPAT_Get(string key, string sheetTitle); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetSheets()")] [MonoMod.MonoModRemove] - public static extern IEnumerable COMPAT_GetSheets(); + private static extern IEnumerable COMPAT_GetSheets(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetKeys(System.String)")] [MonoMod.MonoModRemove] - public static extern IEnumerable COMPAT_GetKeys(string sheetTitle); + private static extern IEnumerable COMPAT_GetKeys(string sheetTitle); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String)")] [MonoMod.MonoModRemove] - public static extern bool COMPAT_Has(string key); + private static extern bool COMPAT_Has(string key); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String,System.String)")] [MonoMod.MonoModRemove] - public static extern bool COMPAT_Has(string key, string sheet); + private static extern bool COMPAT_Has(string key, string sheet); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasSheet(System.String)")] [MonoMod.MonoModRemove] - public static extern bool COMPAT_HasSheet(string sheet); + private static extern bool COMPAT_HasSheet(string sheet); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode LanguageNameToCode(UnityEngine.SystemLanguage)")] [MonoMod.MonoModRemove] - public static extern TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); + private static extern TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); } \ No newline at end of file From 7211d50d86514a9b4819f7df4a96309b87f36008 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 19:31:36 +0100 Subject: [PATCH 17/35] don't relink the entirety of TCLL, just the `get(str, str)` method --- Assembly-CSharp/Language/Language.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 4bcbd1ba..ca210c2f 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -7,14 +7,13 @@ namespace Language; // for backwards compatibility -[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")] public static class Language { public static void LoadLanguage() => COMPAT_LoadLanguage(); public static void LoadAvailableLanguages() => COMPAT_LoadAvailableLanguages(); public static string[] GetLanguages() => COMPAT_GetLanguages(); public static bool SwitchLanguage(string langCode) => COMPAT_SwitchLanguage(langCode); - public static bool SwitchLanguage(TeamCherry.Localization.LanguageCode code) => COMPAT_SwitchLanguage(code); + public static bool SwitchLanguage(LanguageCode code) => COMPAT_SwitchLanguage((TeamCherry.Localization.LanguageCode) code); public static UObject GetAsset(string name) => COMPAT_GetAsset(name); public static LanguageCode CurrentLanguage() => (LanguageCode) COMPAT_CurrentLanguage(); public static string Get(string key) => COMPAT_Get(key); @@ -25,6 +24,7 @@ public static class Language public static bool HasSheet(string sheet) => COMPAT_HasSheet(sheet); public static LanguageCode LanguageNameToCode(USystemLanguage name) => (LanguageCode) COMPAT_LanguageNameToCode(name); public static string GetInternal(string key, string sheetTitle) => COMPAT_Get(key, sheetTitle); + [MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")] public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); // Keep these below the `[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")]`, as the reverse order would cause a cyclic loop of methods calling themselves From 2252e7d3c851efce7b33b6dccc3506d8caf8e8f6 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 19:41:24 +0100 Subject: [PATCH 18/35] MonoModLinkFrom has only 1 arg --- Assembly-CSharp/Language/Language.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index ca210c2f..c7dd5533 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -24,7 +24,7 @@ public static class Language public static bool HasSheet(string sheet) => COMPAT_HasSheet(sheet); public static LanguageCode LanguageNameToCode(USystemLanguage name) => (LanguageCode) COMPAT_LanguageNameToCode(name); public static string GetInternal(string key, string sheetTitle) => COMPAT_Get(key, sheetTitle); - [MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")] + [MonoMod.MonoModLinkFrom("System.String TeamCherry.Localization.Language::Get(System.String,System.String)")] public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); // Keep these below the `[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")]`, as the reverse order would cause a cyclic loop of methods calling themselves From 32ab611e714181686e8368059293bb68dc90a604 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:04:39 +0100 Subject: [PATCH 19/35] not just desktopplatform with `EncryptedSharedData` --- Assembly-CSharp/Patches/DesktopPlatform.cs | 18 ------------------ Assembly-CSharp/Patches/Platform.cs | 9 ++++++++- 2 files changed, 8 insertions(+), 19 deletions(-) delete mode 100644 Assembly-CSharp/Patches/DesktopPlatform.cs diff --git a/Assembly-CSharp/Patches/DesktopPlatform.cs b/Assembly-CSharp/Patches/DesktopPlatform.cs deleted file mode 100644 index c5548c15..00000000 --- a/Assembly-CSharp/Patches/DesktopPlatform.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using MonoMod; - -// ReSharper disable all -#pragma warning disable 1591, 649, 414, 169, CS0108, CS0626 - -namespace Modding.Patches -{ - [MonoModPatch("global::DesktopPlatform")] - public class DesktopPlatform : global::DesktopPlatform - { - [Obsolete("Please update your mod to the new HK version and use `RoamingSharedData` instead")] - public ISharedData EncryptedSharedData - { - get { return RoamingSharedData; } - } - } -} \ No newline at end of file diff --git a/Assembly-CSharp/Patches/Platform.cs b/Assembly-CSharp/Patches/Platform.cs index 28b67b31..f7f811f0 100644 --- a/Assembly-CSharp/Patches/Platform.cs +++ b/Assembly-CSharp/Patches/Platform.cs @@ -1,4 +1,5 @@ -using MonoMod; +using System; +using MonoMod; // ReSharper disable all #pragma warning disable 1591, 108, 114 @@ -8,6 +9,12 @@ namespace Modding.Patches [MonoModPatch("global::Platform")] public abstract class Platform : global::Platform { + [Obsolete("Please update your mod to the new HK version and use `RoamingSharedData` instead")] + public ISharedData EncryptedSharedData + { + get { return RoamingSharedData; } + } + public static bool IsSaveSlotIndexValid(int slotIndex) => true; // ReSharper disable once UnusedMember.Global From ae7998bd2e855912898edeed756b45896a66099a Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:05:01 +0100 Subject: [PATCH 20/35] make it an obsolete warning --- Assembly-CSharp/Language/Language.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index c7dd5533..03c63d9f 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -6,7 +6,7 @@ namespace Language; -// for backwards compatibility +[Obsolete("Use `TeamCherry.Localization.Language` instead.")] public static class Language { public static void LoadLanguage() => COMPAT_LoadLanguage(); From fc2be8cd4eafaf3163af165493fac23b45377854 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 20:05:11 +0100 Subject: [PATCH 21/35] try to have type forwarding --- Assembly-CSharp/tk2d/tk2dForwarding.cs | 56 ++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 Assembly-CSharp/tk2d/tk2dForwarding.cs diff --git a/Assembly-CSharp/tk2d/tk2dForwarding.cs b/Assembly-CSharp/tk2d/tk2dForwarding.cs new file mode 100644 index 00000000..8e2230f1 --- /dev/null +++ b/Assembly-CSharp/tk2d/tk2dForwarding.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; + +// for backwards compatibility +[assembly:TypeForwardedTo(typeof(tk2dAnimatedSprite))] +[assembly:TypeForwardedTo(typeof(tk2dAssetPlatform))] +[assembly:TypeForwardedTo(typeof(tk2dBaseSprite))] +[assembly:TypeForwardedTo(typeof(tk2dBatchedSprite))] +[assembly:TypeForwardedTo(typeof(tk2dButton))] +[assembly:TypeForwardedTo(typeof(tk2dCamera))] +[assembly:TypeForwardedTo(typeof(tk2dCameraAnchor))] +[assembly:TypeForwardedTo(typeof(tk2dCameraResolutionOverride))] +[assembly:TypeForwardedTo(typeof(tk2dCameraSettings))] +[assembly:TypeForwardedTo(typeof(tk2dClippedSprite))] +[assembly:TypeForwardedTo(typeof(tk2dCollider2DData))] +[assembly:TypeForwardedTo(typeof(tk2dEditorSpriteDataUnloader))] +[assembly:TypeForwardedTo(typeof(tk2dFont))] +[assembly:TypeForwardedTo(typeof(tk2dFontChar))] +[assembly:TypeForwardedTo(typeof(tk2dFontData))] +[assembly:TypeForwardedTo(typeof(tk2dFontKerning))] +[assembly:TypeForwardedTo(typeof(Tk2dGlobalEvents))] +[assembly:TypeForwardedTo(typeof(tk2dLinkedSpriteCollection))] +[assembly:TypeForwardedTo(typeof(tk2dPixelPerfectHelper))] +[assembly:TypeForwardedTo(typeof(tk2dResource))] +[assembly:TypeForwardedTo(typeof(tk2dResourceTocEntry))] +[assembly:TypeForwardedTo(typeof(tk2dSlicedSprite))] +[assembly:TypeForwardedTo(typeof(tk2dSprite))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteAnimation))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteAnimationClip))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteAnimationFrame))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteAnimator))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteAttachPoint))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollection))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionData))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionDefault))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionDefinition))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionFont))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionPlatform))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteCollectionSize))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteColliderDefinition))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteColliderIsland))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteDefinition))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteFromTexture))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteGeomGen))] +[assembly:TypeForwardedTo(typeof(Tk2dSpriteSetKeywords))] +[assembly:TypeForwardedTo(typeof(tk2dSpriteSheetSource))] +[assembly:TypeForwardedTo(typeof(tk2dStaticSpriteBatcher))] +[assembly:TypeForwardedTo(typeof(tk2dSystem))] +[assembly:TypeForwardedTo(typeof(tk2dTextGeomGen))] +[assembly:TypeForwardedTo(typeof(tk2dTextMesh))] +[assembly:TypeForwardedTo(typeof(tk2dTextMeshData))] +[assembly:TypeForwardedTo(typeof(tk2dTiledSprite))] +[assembly:TypeForwardedTo(typeof(tk2dTileFlags))] +[assembly:TypeForwardedTo(typeof(tk2dTileMap))] +[assembly:TypeForwardedTo(typeof(tk2dTileMapData))] +[assembly:TypeForwardedTo(typeof(tk2dUpdateManager))] +[assembly:TypeForwardedTo(typeof(tk2dUtil))] \ No newline at end of file From a63c33e622ec85bc2b0cde65e6520a028ab88041 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:36:20 +0100 Subject: [PATCH 22/35] add postpatcher - to patch tk2d typeforwarders back --- .gitignore | 1 + Assembly-CSharp/Assembly-CSharp.csproj | 44 ++++++++---- Assembly-CSharp/{tk2d => }/tk2dForwarding.cs | 4 +- HollowKnight.Modding.API.sln | 6 ++ PostPatcher/App.config | 6 ++ PostPatcher/PostPatcher.csproj | 41 +++++++++++ PostPatcher/Program.cs | 74 ++++++++++++++++++++ PostPatcher/Properties/AssemblyInfo.cs | 9 +++ 8 files changed, 168 insertions(+), 17 deletions(-) rename Assembly-CSharp/{tk2d => }/tk2dForwarding.cs (96%) create mode 100644 PostPatcher/App.config create mode 100644 PostPatcher/PostPatcher.csproj create mode 100644 PostPatcher/Program.cs create mode 100644 PostPatcher/Properties/AssemblyInfo.cs diff --git a/.gitignore b/.gitignore index 2a2b1d90..faafeb70 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ ## files generated by popular Visual Studio add-ons. PrePatcher/Output +PostPatcher/Output # User-specific files *.suo diff --git a/Assembly-CSharp/Assembly-CSharp.csproj b/Assembly-CSharp/Assembly-CSharp.csproj index 7a5ae4fe..0add8292 100644 --- a/Assembly-CSharp/Assembly-CSharp.csproj +++ b/Assembly-CSharp/Assembly-CSharp.csproj @@ -17,7 +17,7 @@ - + @@ -30,8 +30,8 @@ C:/Program Files (x86)/Steam/steamapps/common/Hollow Knight $(HOME)/.local/share/Steam/steamapps/common/Hollow Knight - $(GamePath)/hollow_knight_Data/Managed - + $(GamePath)/hollow_knight_Data/Managed + mono @@ -41,12 +41,23 @@ + + + + + + + + + + + @@ -58,13 +69,13 @@ - + - + - + @@ -74,12 +85,12 @@ - + - + - + .dll @@ -93,14 +104,14 @@ - + - + full bin\$(Configuration)\Assembly-CSharp.mm.xml @@ -118,20 +129,23 @@ all - + all - + - + false + + false + - + diff --git a/Assembly-CSharp/tk2d/tk2dForwarding.cs b/Assembly-CSharp/tk2dForwarding.cs similarity index 96% rename from Assembly-CSharp/tk2d/tk2dForwarding.cs rename to Assembly-CSharp/tk2dForwarding.cs index 8e2230f1..c61eb2e7 100644 --- a/Assembly-CSharp/tk2d/tk2dForwarding.cs +++ b/Assembly-CSharp/tk2dForwarding.cs @@ -1,6 +1,6 @@ using System.Runtime.CompilerServices; -// for backwards compatibility +// for backwards compatibility, done by hand in PostPatcher [assembly:TypeForwardedTo(typeof(tk2dAnimatedSprite))] [assembly:TypeForwardedTo(typeof(tk2dAssetPlatform))] [assembly:TypeForwardedTo(typeof(tk2dBaseSprite))] @@ -53,4 +53,4 @@ [assembly:TypeForwardedTo(typeof(tk2dTileMap))] [assembly:TypeForwardedTo(typeof(tk2dTileMapData))] [assembly:TypeForwardedTo(typeof(tk2dUpdateManager))] -[assembly:TypeForwardedTo(typeof(tk2dUtil))] \ No newline at end of file +[assembly:TypeForwardedTo(typeof(tk2dUtil))] diff --git a/HollowKnight.Modding.API.sln b/HollowKnight.Modding.API.sln index a0c12c01..085230af 100644 --- a/HollowKnight.Modding.API.sln +++ b/HollowKnight.Modding.API.sln @@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrePatcher", "PrePatcher\PrePatcher.csproj", "{E06FBDDA-0A27-45EC-AC28-259C23715C50}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostPatcher", "PostPatcher\PostPatcher.csproj", "{CE61CBC8-9373-4C5B-BA15-DED48888DB73}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,6 +26,10 @@ Global {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Debug|Any CPU.Build.0 = Debug|Any CPU {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Release|Any CPU.ActiveCfg = Release|Any CPU {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Release|Any CPU.Build.0 = Release|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PostPatcher/App.config b/PostPatcher/App.config new file mode 100644 index 00000000..56efbc7b --- /dev/null +++ b/PostPatcher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PostPatcher/PostPatcher.csproj b/PostPatcher/PostPatcher.csproj new file mode 100644 index 00000000..477e2a48 --- /dev/null +++ b/PostPatcher/PostPatcher.csproj @@ -0,0 +1,41 @@ + + + Exe + net472 + true + PostPatcher + PostPatcher + Copyright © 2019 + bin\$(Configuration)\ + 9 + + + + full + + + + pdbonly + + + + + + + + + + + + + + + + + + + + all + + + diff --git a/PostPatcher/Program.cs b/PostPatcher/Program.cs new file mode 100644 index 00000000..fde8a782 --- /dev/null +++ b/PostPatcher/Program.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using MethodAttributes = Mono.Cecil.MethodAttributes; + +namespace Postpatcher +{ + // ReSharper disable once ClassNeverInstantiated.Global + internal class Program + { + private static void Main(string[] args) + { + if (args.Length < 2) + { + Console.WriteLine("Usage: PostPatcher.exe "); + return; + } + + int changes = 0; + + using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(args[0]); + + ForwardTk2dTypes(assembly); + + assembly.Write(args[1]); + + Console.WriteLine("Changed " + changes + " get/set calls"); + } + + private static void ForwardTk2dTypes(AssemblyDefinition assembly) + { + AssemblyNameReference assemblyNameReference = new AssemblyNameReference("TeamCherry.TK2D", null); + foreach (var typeName in new string[] + { + "tk2dAnimatedSprite", "tk2dAssetPlatform", "tk2dBaseSprite", "tk2dBatchedSprite", "tk2dButton", "tk2dCamera", "tk2dCameraAnchor", + "tk2dCameraResolutionOverride", "tk2dCameraSettings", "tk2dClippedSprite", "tk2dCollider2DData", "tk2dEditorSpriteDataUnloader", + "tk2dFont", "tk2dFontChar", "tk2dFontData", "tk2dFontKerning", "Tk2dGlobalEvents", "tk2dLinkedSpriteCollection", + "tk2dPixelPerfectHelper", "tk2dResource", "tk2dResourceTocEntry", "tk2dSlicedSprite", "tk2dSprite", "tk2dSpriteAnimation", + "tk2dSpriteAnimationClip", "tk2dSpriteAnimationFrame", "tk2dSpriteAnimator", "tk2dSpriteAttachPoint", "tk2dSpriteCollection", + "tk2dSpriteCollectionData", "tk2dSpriteCollectionDefault", "tk2dSpriteCollectionDefinition", "tk2dSpriteCollectionFont", + "tk2dSpriteCollectionPlatform", "tk2dSpriteCollectionSize", "tk2dSpriteColliderDefinition", "tk2dSpriteColliderIsland", + "tk2dSpriteDefinition", "tk2dSpriteFromTexture", "tk2dSpriteGeomGen", "Tk2dSpriteSetKeywords", "tk2dSpriteSheetSource", + "tk2dStaticSpriteBatcher", "tk2dSystem", "tk2dTextGeomGen", "tk2dTextMesh", "tk2dTextMeshData", "tk2dTiledSprite", "tk2dTileFlags", + "tk2dTileMap", "tk2dTileMapData", "tk2dUpdateManager", "tk2dUtil" + }) + { + var forwardedType = assembly.MainModule.ImportReference + ( + assembly.MainModule.AssemblyResolver + .Resolve(assemblyNameReference) + .MainModule.GetType(typeName) + ); + assembly.MainModule.ExportedTypes.Add + ( + new ExportedType + ( + "", + typeName, + assembly.MainModule, + assembly.Name + ) + { + Attributes = TypeAttributes.Public | TypeAttributes.Forwarder, + Scope = forwardedType.Scope + } + ); + } + } + } +} \ No newline at end of file diff --git a/PostPatcher/Properties/AssemblyInfo.cs b/PostPatcher/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..5010e0c8 --- /dev/null +++ b/PostPatcher/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e06fbdda-0a27-45ec-ac28-259c23715c51")] From f1703aabacf2c7476976ed226681897a254189ee Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:49:09 +0100 Subject: [PATCH 23/35] more methods for language --- Assembly-CSharp/Language/Language.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 03c63d9f..91c55d10 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -27,6 +27,10 @@ public static class Language [MonoMod.MonoModLinkFrom("System.String TeamCherry.Localization.Language::Get(System.String,System.String)")] public static string Get(string key, string sheetTitle) => Modding.ModHooks.LanguageGet(key, sheetTitle); + private static void DoSwitch(LanguageCode newLang) => COMPAT_DoSwitch((TeamCherry.Localization.LanguageCode) newLang); + private static bool HasLanguageFile(string lang, string sheetTitle) => COMPAT_HasLanguageFile(lang, sheetTitle); + private static string GetLanguageFileContents(string sheetTitle) => COMPAT_GetLanguageFileContents(sheetTitle); + // Keep these below the `[MonoMod.MonoModLinkFrom("TeamCherry.Localization.Language")]`, as the reverse order would cause a cyclic loop of methods calling themselves // thanks to MonoMod resolving these links top-down // which would make a `LinkTo(TCLL)->LinkFrom(TCLL)` into a fully recursive function ⟳ @@ -75,4 +79,13 @@ public static class Language [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode LanguageNameToCode(UnityEngine.SystemLanguage)")] [MonoMod.MonoModRemove] private static extern TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void DoSwitch(TeamCherry.Localization.LanguageCode)")] + [MonoMod.MonoModRemove] + private static extern TeamCherry.Localization.LanguageCode COMPAT_DoSwitch(TeamCherry.Localization.LanguageCode name); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasLanguageFile(System.String,System.String)")] + [MonoMod.MonoModRemove] + private static extern bool COMPAT_HasLanguageFile(string lang, string sheetTitle); + [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String GetLanguageFileContents(System.String)")] + [MonoMod.MonoModRemove] + private static extern string COMPAT_GetLanguageFileContents(string sheetTitle); } \ No newline at end of file From b08045f5c8ae6d682dbdd1b3006bb096b8d756d5 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Thu, 14 May 2026 00:44:12 +0200 Subject: [PATCH 24/35] move monomodrules and custom attributes into subfolder --- Assembly-CSharp/Patches/{ => Attributes}/MonoModRules.cs | 0 Assembly-CSharp/Patches/{ => Attributes}/RemoveMethodCall.cs | 2 +- .../Patches/{ => Attributes}/ReplaceMethodAttribute.cs | 2 +- Assembly-CSharp/Patches/HasComponent.cs | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename Assembly-CSharp/Patches/{ => Attributes}/MonoModRules.cs (100%) rename Assembly-CSharp/Patches/{ => Attributes}/RemoveMethodCall.cs (97%) rename Assembly-CSharp/Patches/{ => Attributes}/ReplaceMethodAttribute.cs (98%) diff --git a/Assembly-CSharp/Patches/MonoModRules.cs b/Assembly-CSharp/Patches/Attributes/MonoModRules.cs similarity index 100% rename from Assembly-CSharp/Patches/MonoModRules.cs rename to Assembly-CSharp/Patches/Attributes/MonoModRules.cs diff --git a/Assembly-CSharp/Patches/RemoveMethodCall.cs b/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs similarity index 97% rename from Assembly-CSharp/Patches/RemoveMethodCall.cs rename to Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs index 14c895b6..db716b30 100644 --- a/Assembly-CSharp/Patches/RemoveMethodCall.cs +++ b/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs @@ -7,7 +7,7 @@ using MonoMod; using MonoMod.Cil; -namespace Modding.Patches +namespace Modding.Patches.Attributes { /// /// diff --git a/Assembly-CSharp/Patches/ReplaceMethodAttribute.cs b/Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs similarity index 98% rename from Assembly-CSharp/Patches/ReplaceMethodAttribute.cs rename to Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs index f5302616..5b5afda9 100644 --- a/Assembly-CSharp/Patches/ReplaceMethodAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs @@ -7,7 +7,7 @@ using MonoMod; using MonoMod.Cil; -namespace Modding.Patches +namespace Modding.Patches.Attributes { /// /// diff --git a/Assembly-CSharp/Patches/HasComponent.cs b/Assembly-CSharp/Patches/HasComponent.cs index 378f3aa2..dd24f421 100644 --- a/Assembly-CSharp/Patches/HasComponent.cs +++ b/Assembly-CSharp/Patches/HasComponent.cs @@ -10,13 +10,13 @@ namespace Modding.Patches public class HasComponent : global::HutongGames.PlayMaker.Actions.HasComponent { [MonoModIgnore] - [RemoveMethodCall + [Attributes.RemoveMethodCall ( "HutongGames.PlayMaker.ReflectionUtils", "GetGlobalType" ) ] - [ReplaceMethod + [Attributes.ReplaceMethod ( "UnityEngine.GameObject, UnityEngine", "GetComponent", From a3a1b4f8d0ba0141765044081e98d213c68f6196 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Thu, 14 May 2026 02:05:44 +0200 Subject: [PATCH 25/35] add RawIlPatchAttribute & rework EnemyDeathEffects patch into IL form & rename RemoveMethodCall to -Attribute --- .../Patches/Attributes/RawIlPatchAttribute.cs | 58 ++++++++++++++++ ...odCall.cs => RemoveMethodCallAttribute.cs} | 4 +- Assembly-CSharp/Patches/EnemyDeathEffects.cs | 69 ++++++++++++++----- Assembly-CSharp/Patches/HasComponent.cs | 2 +- 4 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs rename Assembly-CSharp/Patches/Attributes/{RemoveMethodCall.cs => RemoveMethodCallAttribute.cs} (92%) diff --git a/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs b/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs new file mode 100644 index 00000000..7b7e7649 --- /dev/null +++ b/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; +using JetBrains.Annotations; +using Mono.Cecil; +using MonoMod; +using MonoMod.Cil; + +namespace Modding.Patches.Attributes +{ + /// + /// + /// MonoMod attribute for patching a method directly with IL + /// + [UsedImplicitly] + [MonoModCustomAttribute("RawIlPatch")] + public class RawIlPatchAttribute : Attribute + { + /// + /// + /// Patches a method directly with IL + /// + /// Type full name that does the IL patch + /// Method name that does the IL patch + public RawIlPatchAttribute(string type, string method) { } + } +} + +namespace MonoMod +{ + public static partial class MonoModRules + { + /// + /// Remove op + /// + /// Method to be patched + /// Attribute + [UsedImplicitly] + public static void RawIlPatch(MethodDefinition method, CustomAttribute attrib) + { + var context = new ILContext(method); + + string patcherTypeName = (string)attrib.ConstructorArguments[0].Value; + string patcherMethodName = (string)attrib.ConstructorArguments[1].Value; + + Type patcherType = Type.GetType(patcherTypeName); + + if (patcherType is null) + throw new InvalidOperationException("Couldn't find patcher type!"); + + MethodBase patcherMethod = patcherType?.GetMethod(patcherMethodName, AllBindingFlags/*, null, [typeof(ILContext)], null*/); + + if (patcherMethod is null) + throw new InvalidOperationException("Couldn't find patcher method!"); + + patcherMethod.Invoke(null, new[] { context }); + } + } +} \ No newline at end of file diff --git a/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs b/Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs similarity index 92% rename from Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs rename to Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs index db716b30..9b6f4e51 100644 --- a/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs +++ b/Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs @@ -15,7 +15,7 @@ namespace Modding.Patches.Attributes /// [UsedImplicitly] [MonoModCustomAttribute("RemoveMethodCall")] - public class RemoveMethodCall : Attribute + public class RemoveMethodCallAttribute : Attribute { /// /// @@ -23,7 +23,7 @@ public class RemoveMethodCall : Attribute /// /// Type full name /// Method name - public RemoveMethodCall(string type, string method) {} + public RemoveMethodCallAttribute(string type, string method) {} } } diff --git a/Assembly-CSharp/Patches/EnemyDeathEffects.cs b/Assembly-CSharp/Patches/EnemyDeathEffects.cs index 320fb607..97368e4f 100644 --- a/Assembly-CSharp/Patches/EnemyDeathEffects.cs +++ b/Assembly-CSharp/Patches/EnemyDeathEffects.cs @@ -1,4 +1,7 @@ -using MonoMod; +using Mono.Cecil; +using Mono.Cecil.Cil; +using MonoMod; +using MonoMod.Cil; // ReSharper disable All #pragma warning disable 1591, 0108, 0169, 0649, 0414 @@ -10,32 +13,60 @@ namespace Modding.Patches public class EnemyDeathEffects : global::EnemyDeathEffects { [MonoModIgnore] - private bool didFire; + [Attributes.RawIlPatch + ( + $"Modding.Patches.{nameof(EnemyDeathEffectsIlPatches)}, Assembly-CSharp.mm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + nameof(EnemyDeathEffectsIlPatches.RecieveDeathEvent_IL) + )] + public extern void RecieveDeathEvent(float? attackDirection, bool resetDeathEvent = false, bool spellBurn = false, bool isWatery = false); - public extern void orig_RecieveDeathEvent(float? attackDirection, bool resetDeathEvent = false, bool spellBurn = false, bool isWatery = false); + [MonoModIgnore] + [Attributes.RawIlPatch + ( + $"Modding.Patches.{nameof(EnemyDeathEffectsIlPatches)}, Assembly-CSharp.mm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + nameof(EnemyDeathEffectsIlPatches.RecordKillForJournal_IL) + )] + private extern void RecordKillForJournal(); + } - //Use this to hook into when an enemy dies. Check EnemyDeathEffects.didFire to prevent doing any actions on redundant invokes. - public void RecieveDeathEvent(float? attackDirection, bool resetDeathEvent = false, bool spellBurn = false, bool isWatery = false) + [MonoModIgnore] + public static class EnemyDeathEffectsIlPatches + { + [MonoModIgnore] + public static void RecieveDeathEvent_IL(ILContext il) { - ModHooks.OnRecieveDeathEvent(this, didFire, ref attackDirection, ref resetDeathEvent, ref spellBurn, ref isWatery); - - orig_RecieveDeathEvent(attackDirection, resetDeathEvent, spellBurn, isWatery); + // add a `ModHooks.OnRecieveDeathEvent(this, didFire, ref attackDirection, ref resetDeathEvent, ref spellBurn, ref isWatery);` at the start of the method + ILCursor cursor = new ILCursor(il); + + cursor.GotoNext(MoveType.Before, x => x.MatchLdarg(0)); + + // Insert a call to your custom method + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::EnemyDeathEffects), "didFire", true)); + cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[0]); // attackDirection + cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[1]); // resetDeathEvent + cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[2]); // spellBurn + cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[3]); // isWatery + cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(ModHooks), "OnRecieveDeathEvent", false)); } [MonoModIgnore] - private string playerDataName; + public static void RecordKillForJournal_IL(ILContext il) + { + // add a `ModHooks.OnRecordKillForJournal(this, this.playerDataName, $"killed{this.playerDataName}", $"kills{this.playerDataName}", $"newData{this.playerDataName}");` at the start of the method + ILCursor cursor = new ILCursor(il); - private extern void orig_RecordKillForJournal(); + cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0)); - private void RecordKillForJournal() - { - string boolName = "killed" + this.playerDataName; - string intName = "kills" + this.playerDataName; - string boolName2 = "newData" + this.playerDataName; - - ModHooks.OnRecordKillForJournal(this, playerDataName, boolName, intName, boolName2); - - orig_RecordKillForJournal(); + // Insert a call to your custom method + cursor.Emit(OpCodes.Ldarg_0); // this + cursor.Emit(OpCodes.Ldarg_0); // this + cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::EnemyDeathEffects), "playerDataName", true)); // .playerDataName + cursor.Emit(OpCodes.Ldloc_1); // killed text + cursor.Emit(OpCodes.Ldloc_2); // kills text + cursor.Emit(OpCodes.Ldloc_3); // newData text + cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(ModHooks), "OnRecordKillForJournal", false)); } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/HasComponent.cs b/Assembly-CSharp/Patches/HasComponent.cs index dd24f421..9f938309 100644 --- a/Assembly-CSharp/Patches/HasComponent.cs +++ b/Assembly-CSharp/Patches/HasComponent.cs @@ -10,7 +10,7 @@ namespace Modding.Patches public class HasComponent : global::HutongGames.PlayMaker.Actions.HasComponent { [MonoModIgnore] - [Attributes.RemoveMethodCall + [Attributes.RemoveMethodCallAttribute ( "HutongGames.PlayMaker.ReflectionUtils", "GetGlobalType" From 5fc168c484c32acb3361a75dcfa6fead9c12e9f3 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 00:56:19 +0200 Subject: [PATCH 26/35] do the first part of IL patches --- Assembly-CSharp/Language/Language.cs | 36 +++++------ .../Attributes/IEnumeratorIlPatchAttribute.cs | 61 +++++++++++++++++++ .../Patches/Attributes/RawIlPatchAttribute.cs | 9 ++- Assembly-CSharp/Patches/EnemyDeathEffects.cs | 29 +++------ 4 files changed, 93 insertions(+), 42 deletions(-) create mode 100644 Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs diff --git a/Assembly-CSharp/Language/Language.cs b/Assembly-CSharp/Language/Language.cs index 91c55d10..bf80b6ab 100644 --- a/Assembly-CSharp/Language/Language.cs +++ b/Assembly-CSharp/Language/Language.cs @@ -36,56 +36,56 @@ public static class Language // which would make a `LinkTo(TCLL)->LinkFrom(TCLL)` into a fully recursive function ⟳ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadLanguage()")] [MonoMod.MonoModRemove] - private static extern void COMPAT_LoadLanguage(); + extern private static void COMPAT_LoadLanguage(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadAvailableLanguages()")] [MonoMod.MonoModRemove] - private static extern void COMPAT_LoadAvailableLanguages(); + extern private static void COMPAT_LoadAvailableLanguages(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String[] GetLanguages()")] [MonoMod.MonoModRemove] - private static extern string[] COMPAT_GetLanguages(); + extern private static string[] COMPAT_GetLanguages(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(System.String)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_SwitchLanguage(string langCode); + extern private static bool COMPAT_SwitchLanguage(string langCode); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(TeamCherry.Localization.LanguageCode)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code); + extern private static bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "UnityEngine.Object GetAsset(System.String)")] [MonoMod.MonoModRemove] - private static extern UObject COMPAT_GetAsset(string name); + extern private static UObject COMPAT_GetAsset(string name); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode CurrentLanguage()")] [MonoMod.MonoModRemove] - private static extern TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage(); + extern private static TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String)")] [MonoMod.MonoModRemove] - private static extern string COMPAT_Get(string key); + extern private static string COMPAT_Get(string key); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")] [MonoMod.MonoModRemove] - private static extern string COMPAT_Get(string key, string sheetTitle); + extern private static string COMPAT_Get(string key, string sheetTitle); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetSheets()")] [MonoMod.MonoModRemove] - private static extern IEnumerable COMPAT_GetSheets(); + extern private static IEnumerable COMPAT_GetSheets(); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetKeys(System.String)")] [MonoMod.MonoModRemove] - private static extern IEnumerable COMPAT_GetKeys(string sheetTitle); + extern private static IEnumerable COMPAT_GetKeys(string sheetTitle); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_Has(string key); + extern private static bool COMPAT_Has(string key); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String,System.String)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_Has(string key, string sheet); + extern private static bool COMPAT_Has(string key, string sheet); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasSheet(System.String)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_HasSheet(string sheet); + extern private static bool COMPAT_HasSheet(string sheet); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode LanguageNameToCode(UnityEngine.SystemLanguage)")] [MonoMod.MonoModRemove] - private static extern TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); + extern private static TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void DoSwitch(TeamCherry.Localization.LanguageCode)")] [MonoMod.MonoModRemove] - private static extern TeamCherry.Localization.LanguageCode COMPAT_DoSwitch(TeamCherry.Localization.LanguageCode name); + extern private static void COMPAT_DoSwitch(TeamCherry.Localization.LanguageCode name); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasLanguageFile(System.String,System.String)")] [MonoMod.MonoModRemove] - private static extern bool COMPAT_HasLanguageFile(string lang, string sheetTitle); + extern private static bool COMPAT_HasLanguageFile(string lang, string sheetTitle); [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String GetLanguageFileContents(System.String)")] [MonoMod.MonoModRemove] - private static extern string COMPAT_GetLanguageFileContents(string sheetTitle); + extern private static string COMPAT_GetLanguageFileContents(string sheetTitle); } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs new file mode 100644 index 00000000..d9c5528a --- /dev/null +++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs @@ -0,0 +1,61 @@ +using System; +using System.Reflection; +using JetBrains.Annotations; +using Mono.Cecil; +using MonoMod; +using MonoMod.Cil; + +namespace Modding.Patches.Attributes +{ + /// + /// + /// MonoMod attribute for patching a method directly with IL + /// + [UsedImplicitly] + [MonoModCustomAttribute("IEnumeratorIlPatch")] + public class IEnumeratorIlPatchAttribute : Attribute + { + /// + /// + /// Patches a method directly with IL + /// + /// Method name that does the IL patch + public IEnumeratorIlPatchAttribute(string patcherMethod) { } + } +} + +namespace MonoMod +{ + public static partial class MonoModRules + { + /// + /// Remove op + /// + /// Method to be patched + /// Attribute + [UsedImplicitly] + public static void IEnumeratorIlPatch(MethodDefinition method, CustomAttribute attrib) + { + // var attr = method.GetCustomAttribute(); + // System.Console.WriteLine($"method.Attributes={method.Attributes}"); + // System.Console.WriteLine($"method.HasCustomAttributes={method.HasCustomAttributes}"); + // System.Console.WriteLine($"method.CustomAttributes={method.CustomAttributes}"); + var context = new ILContext(method); + + string patcherTypeName = $"Modding.Patches.{nameof(Modding.Patches.IlPatches)}, Assembly-CSharp.mm"; + string patcherMethodName = (string)attrib.ConstructorArguments[0].Value; + + Type patcherType = Type.GetType(patcherTypeName); + + if (patcherType is null) + throw new InvalidOperationException("Couldn't find patcher type!"); + + MethodBase patcherMethod = patcherType?.GetMethod(patcherMethodName, AllBindingFlags/*, null, [typeof(ILContext)], null*/); + + if (patcherMethod is null) + throw new InvalidOperationException("Couldn't find patcher method!"); + + patcherMethod.Invoke(null, new[] { context }); + } + } +} \ No newline at end of file diff --git a/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs b/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs index 7b7e7649..de7f0bd9 100644 --- a/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs @@ -19,9 +19,8 @@ public class RawIlPatchAttribute : Attribute /// /// Patches a method directly with IL /// - /// Type full name that does the IL patch - /// Method name that does the IL patch - public RawIlPatchAttribute(string type, string method) { } + /// Method name that does the IL patch + public RawIlPatchAttribute(string patcherMethod) { } } } @@ -39,8 +38,8 @@ public static void RawIlPatch(MethodDefinition method, CustomAttribute attrib) { var context = new ILContext(method); - string patcherTypeName = (string)attrib.ConstructorArguments[0].Value; - string patcherMethodName = (string)attrib.ConstructorArguments[1].Value; + string patcherTypeName = $"Modding.Patches.{nameof(Modding.Patches.IlPatches)}, Assembly-CSharp.mm"; + string patcherMethodName = (string)attrib.ConstructorArguments[0].Value; Type patcherType = Type.GetType(patcherTypeName); diff --git a/Assembly-CSharp/Patches/EnemyDeathEffects.cs b/Assembly-CSharp/Patches/EnemyDeathEffects.cs index 97368e4f..d9f02e33 100644 --- a/Assembly-CSharp/Patches/EnemyDeathEffects.cs +++ b/Assembly-CSharp/Patches/EnemyDeathEffects.cs @@ -1,5 +1,4 @@ -using Mono.Cecil; -using Mono.Cecil.Cil; +using Mono.Cecil.Cil; using MonoMod; using MonoMod.Cil; @@ -13,27 +12,19 @@ namespace Modding.Patches public class EnemyDeathEffects : global::EnemyDeathEffects { [MonoModIgnore] - [Attributes.RawIlPatch - ( - $"Modding.Patches.{nameof(EnemyDeathEffectsIlPatches)}, Assembly-CSharp.mm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", - nameof(EnemyDeathEffectsIlPatches.RecieveDeathEvent_IL) - )] - public extern void RecieveDeathEvent(float? attackDirection, bool resetDeathEvent = false, bool spellBurn = false, bool isWatery = false); + [Attributes.RawIlPatch(nameof(IlPatches.RecieveDeathEvent))] + extern public void RecieveDeathEvent(float? attackDirection, bool resetDeathEvent = false, bool spellBurn = false, bool isWatery = false); [MonoModIgnore] - [Attributes.RawIlPatch - ( - $"Modding.Patches.{nameof(EnemyDeathEffectsIlPatches)}, Assembly-CSharp.mm, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", - nameof(EnemyDeathEffectsIlPatches.RecordKillForJournal_IL) - )] - private extern void RecordKillForJournal(); + [Attributes.RawIlPatch(nameof(IlPatches.RecordKillForJournal))] + extern public static void RecordKillForJournal(string playerDataName); } [MonoModIgnore] - public static class EnemyDeathEffectsIlPatches + public static partial class IlPatches { [MonoModIgnore] - public static void RecieveDeathEvent_IL(ILContext il) + public static void RecieveDeathEvent(ILContext il) { // add a `ModHooks.OnRecieveDeathEvent(this, didFire, ref attackDirection, ref resetDeathEvent, ref spellBurn, ref isWatery);` at the start of the method ILCursor cursor = new ILCursor(il); @@ -48,11 +39,11 @@ public static void RecieveDeathEvent_IL(ILContext il) cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[1]); // resetDeathEvent cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[2]); // spellBurn cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[3]); // isWatery - cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(ModHooks), "OnRecieveDeathEvent", false)); + cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::Modding.ModHooks), "OnRecieveDeathEvent", false)); } [MonoModIgnore] - public static void RecordKillForJournal_IL(ILContext il) + public static void RecordKillForJournal(ILContext il) { // add a `ModHooks.OnRecordKillForJournal(this, this.playerDataName, $"killed{this.playerDataName}", $"kills{this.playerDataName}", $"newData{this.playerDataName}");` at the start of the method ILCursor cursor = new ILCursor(il); @@ -66,7 +57,7 @@ public static void RecordKillForJournal_IL(ILContext il) cursor.Emit(OpCodes.Ldloc_1); // killed text cursor.Emit(OpCodes.Ldloc_2); // kills text cursor.Emit(OpCodes.Ldloc_3); // newData text - cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(ModHooks), "OnRecordKillForJournal", false)); + cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::Modding.ModHooks), "OnRecordKillForJournal", false)); } } } \ No newline at end of file From 03b0282e508566d4199203816b860c4c7b20b665 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 12:25:00 +0200 Subject: [PATCH 27/35] have the attributes in the attributes namespaces but without attribute in the name --- .../{IEnumeratorIlPatchAttribute.cs => IEnumeratorIlPatch.cs} | 4 ++-- .../Attributes/{RawIlPatchAttribute.cs => RawIlPatch.cs} | 4 ++-- .../{RemoveMethodCallAttribute.cs => RemoveMethodCall.cs} | 4 ++-- .../{ReplaceMethodAttribute.cs => ReplaceMethod.cs} | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename Assembly-CSharp/Patches/Attributes/{IEnumeratorIlPatchAttribute.cs => IEnumeratorIlPatch.cs} (94%) rename Assembly-CSharp/Patches/Attributes/{RawIlPatchAttribute.cs => RawIlPatch.cs} (93%) rename Assembly-CSharp/Patches/Attributes/{RemoveMethodCallAttribute.cs => RemoveMethodCall.cs} (92%) rename Assembly-CSharp/Patches/Attributes/{ReplaceMethodAttribute.cs => ReplaceMethod.cs} (92%) diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs similarity index 94% rename from Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs rename to Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs index d9c5528a..fca24ea3 100644 --- a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatchAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs @@ -13,14 +13,14 @@ namespace Modding.Patches.Attributes /// [UsedImplicitly] [MonoModCustomAttribute("IEnumeratorIlPatch")] - public class IEnumeratorIlPatchAttribute : Attribute + public class IEnumeratorIlPatch : Attribute { /// /// /// Patches a method directly with IL /// /// Method name that does the IL patch - public IEnumeratorIlPatchAttribute(string patcherMethod) { } + public IEnumeratorIlPatch(string patcherMethod) { } } } diff --git a/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs similarity index 93% rename from Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs rename to Assembly-CSharp/Patches/Attributes/RawIlPatch.cs index de7f0bd9..06e1057e 100644 --- a/Assembly-CSharp/Patches/Attributes/RawIlPatchAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs @@ -13,14 +13,14 @@ namespace Modding.Patches.Attributes /// [UsedImplicitly] [MonoModCustomAttribute("RawIlPatch")] - public class RawIlPatchAttribute : Attribute + public class RawIlPatch : Attribute { /// /// /// Patches a method directly with IL /// /// Method name that does the IL patch - public RawIlPatchAttribute(string patcherMethod) { } + public RawIlPatch(string patcherMethod) { } } } diff --git a/Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs b/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs similarity index 92% rename from Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs rename to Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs index 9b6f4e51..db716b30 100644 --- a/Assembly-CSharp/Patches/Attributes/RemoveMethodCallAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/RemoveMethodCall.cs @@ -15,7 +15,7 @@ namespace Modding.Patches.Attributes /// [UsedImplicitly] [MonoModCustomAttribute("RemoveMethodCall")] - public class RemoveMethodCallAttribute : Attribute + public class RemoveMethodCall : Attribute { /// /// @@ -23,7 +23,7 @@ public class RemoveMethodCallAttribute : Attribute /// /// Type full name /// Method name - public RemoveMethodCallAttribute(string type, string method) {} + public RemoveMethodCall(string type, string method) {} } } diff --git a/Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs b/Assembly-CSharp/Patches/Attributes/ReplaceMethod.cs similarity index 92% rename from Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs rename to Assembly-CSharp/Patches/Attributes/ReplaceMethod.cs index 5b5afda9..df26c6e9 100644 --- a/Assembly-CSharp/Patches/Attributes/ReplaceMethodAttribute.cs +++ b/Assembly-CSharp/Patches/Attributes/ReplaceMethod.cs @@ -15,13 +15,13 @@ namespace Modding.Patches.Attributes /// [MonoModCustomAttribute("ReplaceMethod")] [UsedImplicitly] - internal class ReplaceMethodAttribute : Attribute + internal class ReplaceMethod : Attribute { /// /// /// Replace method call with alternate method call /// - public ReplaceMethodAttribute(string type1, string method1, string[] params1, string type2, string method2, string[] params2) { } + public ReplaceMethod(string type1, string method1, string[] params1, string type2, string method2, string[] params2) { } } } From 1e9f8dcfa7594dfb54edfde3eabb60d0d469d6b9 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 15:44:04 +0200 Subject: [PATCH 28/35] use emitdelegate --- Assembly-CSharp/Patches/EnemyDeathEffects.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assembly-CSharp/Patches/EnemyDeathEffects.cs b/Assembly-CSharp/Patches/EnemyDeathEffects.cs index d9f02e33..7578b203 100644 --- a/Assembly-CSharp/Patches/EnemyDeathEffects.cs +++ b/Assembly-CSharp/Patches/EnemyDeathEffects.cs @@ -39,7 +39,7 @@ public static void RecieveDeathEvent(ILContext il) cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[1]); // resetDeathEvent cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[2]); // spellBurn cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[3]); // isWatery - cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::Modding.ModHooks), "OnRecieveDeathEvent", false)); + cursor.EmitDelegate(global::Modding.ModHooks.OnRecieveDeathEvent); } [MonoModIgnore] @@ -57,7 +57,7 @@ public static void RecordKillForJournal(ILContext il) cursor.Emit(OpCodes.Ldloc_1); // killed text cursor.Emit(OpCodes.Ldloc_2); // kills text cursor.Emit(OpCodes.Ldloc_3); // newData text - cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::Modding.ModHooks), "OnRecordKillForJournal", false)); + cursor.EmitDelegate(global::Modding.ModHooks.OnRecordKillForJournal); } } } \ No newline at end of file From 9e1ba41495d304730d828340fad3b1d07ba412e9 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 15:45:21 +0200 Subject: [PATCH 29/35] start gamemanager il patches --- .../Patches/Attributes/IEnumeratorIlPatch.cs | 14 +- Assembly-CSharp/Patches/GameManager.cs | 368 ++++++++++-------- 2 files changed, 217 insertions(+), 165 deletions(-) diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs index fca24ea3..0c68d876 100644 --- a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs +++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Reflection; using JetBrains.Annotations; using Mono.Cecil; @@ -36,11 +37,12 @@ public static partial class MonoModRules [UsedImplicitly] public static void IEnumeratorIlPatch(MethodDefinition method, CustomAttribute attrib) { - // var attr = method.GetCustomAttribute(); - // System.Console.WriteLine($"method.Attributes={method.Attributes}"); - // System.Console.WriteLine($"method.HasCustomAttributes={method.HasCustomAttributes}"); - // System.Console.WriteLine($"method.CustomAttributes={method.CustomAttributes}"); - var context = new ILContext(method); + CustomAttribute iteratorAttribute = method.CustomAttributes.First + (x => x.AttributeType.FullName == "System.Runtime.CompilerServices.IteratorStateMachineAttribute"); + TypeReference stateMachineTypeRef = (TypeReference)iteratorAttribute.ConstructorArguments[0].Value; + TypeDefinition stateMachineTypeDef = stateMachineTypeRef.Resolve(); + MethodDefinition stateMachineMoveNext = stateMachineTypeDef.Methods.First(m => m.Name == "MoveNext"); + var context = new ILContext(stateMachineMoveNext); string patcherTypeName = $"Modding.Patches.{nameof(Modding.Patches.IlPatches)}, Assembly-CSharp.mm"; string patcherMethodName = (string)attrib.ConstructorArguments[0].Value; @@ -55,7 +57,7 @@ public static void IEnumeratorIlPatch(MethodDefinition method, CustomAttribute a if (patcherMethod is null) throw new InvalidOperationException("Couldn't find patcher method!"); - patcherMethod.Invoke(null, new[] { context }); + patcherMethod.Invoke(null, [context, stateMachineTypeDef]); } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 99526b73..5c8a7f67 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -1,10 +1,14 @@ +using Mono.Cecil.Cil; +using MonoMod; +using MonoMod.Cil; using System; using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; -using MonoMod; +using Mono.Cecil; using Newtonsoft.Json; using UnityEngine; using UnityEngine.SceneManagement; @@ -18,55 +22,54 @@ namespace Modding.Patches [MonoModPatch("global::GameManager")] public class GameManager : global::GameManager { - public extern void orig_OnApplicationQuit(); + private static string ModdedSavePath(int slot) => Path.Combine( + Application.persistentDataPath, + $"user{slot}.modded.json" + ); - public void OnApplicationQuit() + private UIManager _uiInstance; + + public UIManager ui { - orig_OnApplicationQuit(); - ModHooks.OnApplicationQuit(); + get + { + if (_uiInstance == null) _uiInstance = (UIManager)UIManager.instance; + return _uiInstance; + } + private set => _uiInstance = value; } - public extern void orig_LoadScene(string destScene); + private ModSavegameData moddedData; - public void LoadScene(string destScene) - { - destScene = ModHooks.BeforeSceneLoad(destScene); + [MonoModIgnore] + [Attributes.RawIlPatch(nameof(IlPatches.OnApplicationQuit))] + extern private void OnApplicationQuit(); - orig_LoadScene(destScene); + [MonoModIgnore] + [Attributes.RawIlPatch(nameof(IlPatches.LoadScene))] + extern public void LoadScene(string destScene); - ModHooks.OnSceneChanged(destScene); - } + [MonoModIgnore] + [Attributes.RawIlPatch(nameof(IlPatches.ClearSaveFile))] + extern public void ClearSaveFile(int saveSlot, Action callback); - public extern void orig_BeginSceneTransition(GameManager.SceneLoadInfo info); + [MonoModIgnore] + [Attributes.IEnumeratorIlPatch(nameof(IlPatches.PlayerDead))] + extern public IEnumerator PlayerDead(float waitTime); + [MonoModIgnore] + [Attributes.IEnumeratorIlPatch(nameof(IlPatches.LoadSceneAdditive))] + extern public IEnumerator LoadSceneAdditive(string destScene); + + // il patch just dies trying to resolve types for no reason? + public extern void orig_BeginSceneTransition(global::GameManager.SceneLoadInfo info); public void BeginSceneTransition(GameManager.SceneLoadInfo info) { info.SceneName = ModHooks.BeforeSceneLoad(info.SceneName); - orig_BeginSceneTransition(info); } - public extern void orig_ClearSaveFile(int saveSlot, Action callback); - - public void ClearSaveFile(int saveSlot, Action callback) - { - ModHooks.OnSavegameClear(saveSlot); - orig_ClearSaveFile(saveSlot, callback); - ModHooks.OnAfterSaveGameClear(saveSlot); - } - - public extern IEnumerator orig_PlayerDead(float waitTime); - - public IEnumerator PlayerDead(float waitTime) - { - ModHooks.OnBeforePlayerDead(); - yield return orig_PlayerDead(waitTime); - ModHooks.OnAfterPlayerDead(); - } - - #region SaveGame - - private ModSavegameData moddedData; + #region SaveGame & LoadGame [MonoModIgnore] private GameCameras gameCams; @@ -92,23 +95,6 @@ public IEnumerator PlayerDead(float waitTime) [MonoModIgnore] private extern void HideSaveIcon(); - private static string ModdedSavePath(int slot) => Path.Combine( - Application.persistentDataPath, - $"user{slot}.modded.json" - ); - - private UIManager _uiInstance; - - public UIManager ui - { - get - { - if (_uiInstance == null) _uiInstance = (UIManager)UIManager.instance; - return _uiInstance; - } - private set => _uiInstance = value; - } - [MonoModReplace] public void SaveGame(int saveSlot, Action callback) { @@ -180,12 +166,17 @@ public void SaveGame(int saveSlot, Action callback) try { - text = JsonConvert.SerializeObject(obj, Formatting.Indented, new JsonSerializerSettings() - { - ContractResolver = ShouldSerializeContractResolver.Instance, - TypeNameHandling = TypeNameHandling.Auto, - Converters = JsonConverterTypes.ConverterTypes - }); + text = JsonConvert.SerializeObject + ( + obj, + Formatting.Indented, + new JsonSerializerSettings() + { + ContractResolver = ShouldSerializeContractResolver.Instance, + TypeNameHandling = TypeNameHandling.Auto, + Converters = JsonConverterTypes.ConverterTypes + } + ); } catch (Exception e) { @@ -209,7 +200,7 @@ public void SaveGame(int saveSlot, Action callback) ( saveSlot, binary, - delegate (bool didSave) + delegate(bool didSave) { this.HideSaveIcon(); callback(didSave); @@ -222,7 +213,7 @@ public void SaveGame(int saveSlot, Action callback) ( saveSlot, Encoding.UTF8.GetBytes(text), - delegate (bool didSave) + delegate(bool didSave) { this.HideSaveIcon(); if (callback != null) @@ -264,30 +255,6 @@ public void SaveGame(int saveSlot, Action callback) } } - #endregion - - public extern void orig_SetupSceneRefs(bool refreshTilemapInfo); - - public void SetupSceneRefs(bool refreshTilemapInfo) - { - orig_SetupSceneRefs(refreshTilemapInfo); - - - if (IsGameplayScene()) - { - GameObject go = GameCameras.instance.soulOrbFSM.gameObject.transform.Find("SoulOrb_fill").gameObject; - GameObject liquid = go.transform.Find("Liquid").gameObject; - tk2dSpriteAnimator tk2dsa = liquid.GetComponent(); - tk2dsa.GetClipByName("Fill").fps = 15 * 1.05f; - tk2dsa.GetClipByName("Idle").fps = 10 * 1.05f; - tk2dsa.GetClipByName("Shrink").fps = 15 * 1.05f; - tk2dsa.GetClipByName("Drain").fps = 30 * 1.05f; - } - - } - - #region LoadGame - [MonoModReplace] public void LoadGame(int saveSlot, Action callback) { @@ -317,7 +284,8 @@ public void LoadGame(int saveSlot, Action callback) using FileStream fileStream = File.OpenRead(path); using var reader = new StreamReader(fileStream); string json = reader.ReadToEnd(); - this.moddedData = JsonConvert.DeserializeObject( + this.moddedData = JsonConvert.DeserializeObject + ( json, new JsonSerializerSettings() { @@ -343,12 +311,13 @@ public void LoadGame(int saveSlot, Action callback) Logger.APILogger.LogError(e); this.moddedData = new ModSavegameData(); } + ModHooks.OnLoadLocalSettings(this.moddedData); Platform.Current.ReadSaveSlot ( saveSlot, - delegate (byte[] fileBytes) + delegate(byte[] fileBytes) { bool obj; try @@ -371,13 +340,17 @@ public void LoadGame(int saveSlot, Action callback) try { - saveGameData = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() - { - ContractResolver = ShouldSerializeContractResolver.Instance, - TypeNameHandling = TypeNameHandling.Auto, - ObjectCreationHandling = ObjectCreationHandling.Replace, - Converters = JsonConverterTypes.ConverterTypes - }); + saveGameData = JsonConvert.DeserializeObject + ( + json, + new JsonSerializerSettings() + { + ContractResolver = ShouldSerializeContractResolver.Instance, + TypeNameHandling = TypeNameHandling.Auto, + ObjectCreationHandling = ObjectCreationHandling.Replace, + Converters = JsonConverterTypes.ConverterTypes + } + ); } catch (Exception e) { @@ -423,6 +396,25 @@ public void LoadGame(int saveSlot, Action callback) #endregion + extern public void orig_SetupSceneRefs(bool refreshTilemapInfo); + + public void SetupSceneRefs(bool refreshTilemapInfo) + { + orig_SetupSceneRefs(refreshTilemapInfo); + + + if (IsGameplayScene()) + { + GameObject go = GameCameras.instance.soulOrbFSM.gameObject.transform.Find("SoulOrb_fill").gameObject; + GameObject liquid = go.transform.Find("Liquid").gameObject; + tk2dSpriteAnimator tk2dsa = liquid.GetComponent(); + tk2dsa.GetClipByName("Fill").fps = 15 * 1.05f; + tk2dsa.GetClipByName("Idle").fps = 10 * 1.05f; + tk2dsa.GetClipByName("Shrink").fps = 15 * 1.05f; + tk2dsa.GetClipByName("Drain").fps = 30 * 1.05f; + } + } + #region GetSaveStatsForSlot [MonoModReplace] @@ -449,7 +441,7 @@ public void GetSaveStatsForSlot(int saveSlot, Action callback Platform.Current.ReadSaveSlot ( saveSlot, - delegate (byte[] fileBytes) + delegate(byte[] fileBytes) { if (fileBytes == null) { @@ -480,13 +472,17 @@ public void GetSaveStatsForSlot(int saveSlot, Action callback SaveGameData saveGameData; try { - saveGameData = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() - { - ContractResolver = ShouldSerializeContractResolver.Instance, - TypeNameHandling = TypeNameHandling.Auto, - ObjectCreationHandling = ObjectCreationHandling.Replace, - Converters = JsonConverterTypes.ConverterTypes - }); + saveGameData = JsonConvert.DeserializeObject + ( + json, + new JsonSerializerSettings() + { + ContractResolver = ShouldSerializeContractResolver.Instance, + TypeNameHandling = TypeNameHandling.Auto, + ObjectCreationHandling = ObjectCreationHandling.Replace, + Converters = JsonConverterTypes.ConverterTypes + } + ); } catch (Exception) { @@ -540,63 +536,6 @@ public void GetSaveStatsForSlot(int saveSlot, Action callback #endregion - #region LoadSceneAdditive - - [MonoModIgnore] - private bool tilemapDirty; - - [MonoModIgnore] - private bool waitForManualLevelStart; - - [MonoModIgnore] - public event GameManager.DestroyPooledObjects DestroyPersonalPools; - - [MonoModIgnore] - public event GameManager.UnloadLevel UnloadingLevel; - - [MonoModReplace] - public IEnumerator LoadSceneAdditive(string destScene) - { - Debug.Log("Loading " + destScene); - destScene = ModHooks.BeforeSceneLoad(destScene); - this.tilemapDirty = true; - this.startedOnThisScene = false; - this.nextSceneName = destScene; - this.waitForManualLevelStart = true; - if (this.DestroyPersonalPools != null) - { - this.DestroyPersonalPools(); - } - - if (this.UnloadingLevel != null) - { - this.UnloadingLevel(); - } - - string exitingScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name; - AsyncOperation loadop = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(destScene, LoadSceneMode.Additive); - loadop.allowSceneActivation = true; - yield return loadop; - UnityEngine.SceneManagement.SceneManager.UnloadScene(exitingScene); - ModHooks.OnSceneChanged(destScene); - this.RefreshTilemapInfo(destScene); - if (this.IsUnloadAssetsRequired(exitingScene, destScene)) - { - Debug.LogFormat(this, "Unloading assets due to zone transition", Array.Empty()); - yield return Resources.UnloadUnusedAssets(); - } - - GCManager.Collect(); - this.SetupSceneRefs(true); - this.BeginScene(); - this.OnNextLevelReady(); - this.waitForManualLevelStart = false; - Debug.Log("Done Loading " + destScene); - yield break; - } - - #endregion - #region LoadFirstScene [MonoModReplace] @@ -624,6 +563,7 @@ public void OnWillActivateFirstLevel() #endregion #region PauseToDynamicMenu + [MonoModIgnore] public extern void SetTimeScale(float timescale); @@ -637,6 +577,7 @@ public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = { yield break; } + if (!this.playerData.GetBool(nameof(PlayerData.disablePause)) && this.gameState == GlobalEnums.GameState.PLAYING) { this.isPaused = true; @@ -647,6 +588,7 @@ public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = { HeroController.instance.Pause(); } + this.gameCams.MoveMenuToHUDCamera(); this.inputHandler.PreventPause(); this.inputHandler.StopUIInput(); @@ -664,12 +606,15 @@ public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = { HeroController.instance.UnPause(); } + MenuButtonList.ClearAllLastSelected(); yield return new WaitForSecondsRealtime(0.3f); this.inputHandler.AllowPause(); } + yield break; } + #endregion [MonoModIgnore] @@ -681,10 +626,115 @@ public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = * Example use case: Start a co-routine that checks for an non null * sceneLoad then hooks up a callback to the "Finish" delegate to do something when the game has completed loading a scene. */ - [MonoModIgnore] + // [MonoModIgnore] public SceneLoad SceneLoad { get { return sceneLoad; } } } + + public static partial class IlPatches + { + [MonoModIgnore] + public static void OnApplicationQuit(ILContext il) + { + // add a `ModHooks.OnApplicationQuit();` at the end of the method + ILCursor cursor = new ILCursor(il); + + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); + cursor.MoveAfterLabels(); + cursor.EmitDelegate(global::Modding.ModHooks.OnApplicationQuit); + } + + [MonoModIgnore] + public static void LoadScene(ILContext il) + { + // add a `destScene = ModHooks.BeforeSceneLoad(destScene);` at the start and a `ModHooks.OnSceneChanged(destScene);` at the end of the method + ILCursor cursor = new ILCursor(il).Goto(0); + + // Insert a call to your custom method + cursor.Emit(OpCodes.Ldarg_1); + cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); + cursor.Emit(OpCodes.Starg, 1); + + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); + cursor.MoveAfterLabels(); + cursor.Emit(OpCodes.Ldarg_1); + cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); + } + + [MonoModIgnore] + public static void ClearSaveFile(ILContext il) + { + // add a `ModHooks.OnSavegameClear(saveSlot);` at the start and a `ModHooks.OnAfterSaveGameClear(saveSlot);` at the end of the method + ILCursor cursor = new ILCursor(il).Goto(0); + + // Insert a call to your custom method + cursor.Emit(OpCodes.Ldarg_1); + cursor.EmitDelegate(global::Modding.ModHooks.OnSavegameClear); + + // this goes just before both `ret`s + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); + cursor.MoveAfterLabels(); + cursor.Emit(OpCodes.Ldarg_1); + cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); + + // skip over the return + cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); + cursor.MoveAfterLabels(); + cursor.Emit(OpCodes.Ldarg_1); + cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); + } + + [MonoModIgnore] + public static void PlayerDead(ILContext il, TypeDefinition stateMachineTypeDef) + { + // add a `ModHooks.OnSavegameClear(saveSlot);` at the start and a `ModHooks.OnAfterSaveGameClear(saveSlot);` at the end of the method + ILCursor cursor = new ILCursor(il); + + // Insert a call to your custom method + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); + cursor.EmitDelegate(global::Modding.ModHooks.OnBeforePlayerDead); + + // this goes just before all the `ret`s + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); + cursor.MoveAfterLabels(); + cursor.EmitDelegate(global::Modding.ModHooks.OnAfterPlayerDead); + } + + [MonoModIgnore] + public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTypeDef) + { + // add a `destScene = ModHooks.BeforeSceneLoad(destScene);` at the start and a `ModHooks.OnSceneChanged(destScene);` in the middle of the method + ILCursor cursor = new ILCursor(il); + + // Insert a call to your custom method + cursor.GotoNext + ( + MoveType.AfterLabel, + x => x.MatchLdloc(1), + x => x.MatchLdcI4(1), + x => x.MatchStfld("tilemapDirty") + ); + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); + cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); + cursor.Emit(OpCodes.Stfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); + + // somewhere before `this.RefreshTilemapInfo(destScene);` + cursor.GotoNext + ( + MoveType.AfterLabel, + x => x.MatchLdloc(1), + x => x.MatchLdarg(0), + x => x.MatchLdfld(out _), // destScene field of statemachine type + x => x.MatchCallOrCallvirt(typeof(global::GameManager), "RefreshTilemapInfo") + ); + cursor.Emit(OpCodes.Ldarg_0); + cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); + cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); + } + } } \ No newline at end of file From d9f24b11012d2e5b9eb1aa4a7994f142c3657744 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 15:58:25 +0200 Subject: [PATCH 30/35] fix move after labels not working --- Assembly-CSharp/Patches/GameManager.cs | 44 ++++++++++++++++++++------ 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 5c8a7f67..71897ddf 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -22,10 +22,12 @@ namespace Modding.Patches [MonoModPatch("global::GameManager")] public class GameManager : global::GameManager { - private static string ModdedSavePath(int slot) => Path.Combine( - Application.persistentDataPath, - $"user{slot}.modded.json" - ); + private static string ModdedSavePath(int slot) => + Path.Combine + ( + Application.persistentDataPath, + $"user{slot}.modded.json" + ); private UIManager _uiInstance; @@ -63,6 +65,7 @@ public UIManager ui // il patch just dies trying to resolve types for no reason? public extern void orig_BeginSceneTransition(global::GameManager.SceneLoadInfo info); + public void BeginSceneTransition(GameManager.SceneLoadInfo info) { info.SceneName = ModHooks.BeforeSceneLoad(info.SceneName); @@ -135,13 +138,15 @@ public void SaveGame(int saveSlot, Action callback) { this.moddedData = new ModSavegameData(); } + ModHooks.OnSaveLocalSettings(this.moddedData); // save modded data try { var path = ModdedSavePath(saveSlot); - string modded = JsonConvert.SerializeObject( + string modded = JsonConvert.SerializeObject + ( this.moddedData, Formatting.Indented, new JsonSerializerSettings @@ -642,8 +647,10 @@ public static void OnApplicationQuit(ILContext il) ILCursor cursor = new ILCursor(il); cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.MoveAfterLabels(); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.EmitDelegate(global::Modding.ModHooks.OnApplicationQuit); + cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted } [MonoModIgnore] @@ -658,9 +665,11 @@ public static void LoadScene(ILContext il) cursor.Emit(OpCodes.Starg, 1); cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.MoveAfterLabels(); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); + cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted } [MonoModIgnore] @@ -675,16 +684,20 @@ public static void ClearSaveFile(ILContext il) // this goes just before both `ret`s cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.MoveAfterLabels(); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); + cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted // skip over the return cursor.GotoNext(); cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.MoveAfterLabels(); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); + cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted } [MonoModIgnore] @@ -695,12 +708,17 @@ public static void PlayerDead(ILContext il, TypeDefinition stateMachineTypeDef) // Insert a call to your custom method cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.EmitDelegate(global::Modding.ModHooks.OnBeforePlayerDead); + cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted // this goes just before all the `ret`s cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); - cursor.MoveAfterLabels(); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.EmitDelegate(global::Modding.ModHooks.OnAfterPlayerDead); + cursor.Emit(OpCodes.Ldc_I4_0); // apparently afterlabel doesn't work as wanted } [MonoModIgnore] @@ -717,11 +735,14 @@ public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTy x => x.MatchLdcI4(1), x => x.MatchStfld("tilemapDirty") ); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); cursor.Emit(OpCodes.Stfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); + cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted // somewhere before `this.RefreshTilemapInfo(destScene);` cursor.GotoNext @@ -732,9 +753,12 @@ public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTy x => x.MatchLdfld(out _), // destScene field of statemachine type x => x.MatchCallOrCallvirt(typeof(global::GameManager), "RefreshTilemapInfo") ); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); + cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted } } } \ No newline at end of file From b7e8ed99593bff3994871553eb275ad4dcfa0473 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 16:22:20 +0200 Subject: [PATCH 31/35] have IL patches for LoadFirstScene and OnWillActivateFirstLevel --- .../Patches/Attributes/IEnumeratorIlPatch.cs | 1 + Assembly-CSharp/Patches/GameManager.cs | 224 ++++++++---------- 2 files changed, 98 insertions(+), 127 deletions(-) diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs index 0c68d876..575e9732 100644 --- a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs +++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs @@ -3,6 +3,7 @@ using System.Reflection; using JetBrains.Annotations; using Mono.Cecil; +using Mono.Cecil.Cil; using MonoMod; using MonoMod.Cil; diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 71897ddf..72524b63 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -63,6 +63,14 @@ public UIManager ui [Attributes.IEnumeratorIlPatch(nameof(IlPatches.LoadSceneAdditive))] extern public IEnumerator LoadSceneAdditive(string destScene); + [MonoModIgnore] + [Attributes.IEnumeratorIlPatch(nameof(IlPatches.LoadFirstScene))] + extern public IEnumerator LoadFirstScene(); + + [MonoModIgnore] + [Attributes.RawIlPatch(nameof(IlPatches.OnWillActivateFirstLevel))] + extern public void OnWillActivateFirstLevel(); + // il patch just dies trying to resolve types for no reason? public extern void orig_BeginSceneTransition(global::GameManager.SceneLoadInfo info); @@ -402,12 +410,9 @@ public void LoadGame(int saveSlot, Action callback) #endregion extern public void orig_SetupSceneRefs(bool refreshTilemapInfo); - public void SetupSceneRefs(bool refreshTilemapInfo) { orig_SetupSceneRefs(refreshTilemapInfo); - - if (IsGameplayScene()) { GameObject go = GameCameras.instance.soulOrbFSM.gameObject.transform.Find("SoulOrb_fill").gameObject; @@ -420,153 +425,89 @@ public void SetupSceneRefs(bool refreshTilemapInfo) } } - #region GetSaveStatsForSlot - [MonoModReplace] public void GetSaveStatsForSlot(int saveSlot, Action callback) { if (!Platform.IsSaveSlotIndexValid(saveSlot)) { - Debug.LogErrorFormat - ( - "Cannot get save stats for invalid slot {0}", - new object[] - { - saveSlot - } - ); + Debug.LogErrorFormat("Cannot get save stats for invalid slot {0}", new object[] { saveSlot }); if (callback != null) { CoreLoop.InvokeNext(delegate { callback(null); }); } - return; } - - Platform.Current.ReadSaveSlot - ( - saveSlot, - delegate(byte[] fileBytes) + Platform.Current.ReadSaveSlot(saveSlot, delegate(byte[] fileBytes) + { + if (fileBytes == null) { - if (fileBytes == null) + if (callback != null) { - if (callback != null) - { - CoreLoop.InvokeNext(delegate { callback(null); }); - } - - return; + CoreLoop.InvokeNext(delegate { callback(null); }); } - + return; + } + try + { + bool flag = this.gameConfig.useSaveEncryption && !Platform.Current.IsFileSystemProtected; + string json; + if (flag) + { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + MemoryStream serializationStream = new MemoryStream(fileBytes); + string encryptedString = (string)binaryFormatter.Deserialize(serializationStream); + json = Encryption.Decrypt(encryptedString); + } + else + { + json = Encoding.UTF8.GetString(fileBytes); + } + SaveGameData saveGameData; try { - bool flag = this.gameConfig.useSaveEncryption && !Platform.Current.IsFileSystemProtected; - string json; - if (flag) - { - BinaryFormatter binaryFormatter = new BinaryFormatter(); - MemoryStream serializationStream = new MemoryStream(fileBytes); - string encryptedString = (string)binaryFormatter.Deserialize(serializationStream); - json = Encryption.Decrypt(encryptedString); - } - else - { - json = Encoding.UTF8.GetString(fileBytes); - } - - SaveGameData saveGameData; - try - { - saveGameData = JsonConvert.DeserializeObject - ( - json, - new JsonSerializerSettings() - { - ContractResolver = ShouldSerializeContractResolver.Instance, - TypeNameHandling = TypeNameHandling.Auto, - ObjectCreationHandling = ObjectCreationHandling.Replace, - Converters = JsonConverterTypes.ConverterTypes - } - ); - } - catch (Exception) - { - // Not a huge deal, this happens on saves with mod data which haven't been converted yet. - Logger.APILogger.LogWarn($"Failed to get save stats for slot {saveSlot} using Json.NET, falling back"); - - saveGameData = JsonUtility.FromJson(json); - } - - global::PlayerData playerData = saveGameData.playerData; - SaveStats saveStats = new SaveStats - ( - playerData.GetInt(nameof(PlayerData.maxHealthBase)), - playerData.GetInt(nameof(PlayerData.geo)), - playerData.GetVariable(nameof(PlayerData.mapZone)), - playerData.GetFloat(nameof(PlayerData.playTime)), - playerData.GetInt(nameof(PlayerData.MPReserveMax)), - playerData.GetInt(nameof(PlayerData.permadeathMode)), - playerData.GetBool(nameof(PlayerData.bossRushMode)), - playerData.GetFloat(nameof(PlayerData.completionPercentage)), - playerData.GetBool(nameof(PlayerData.unlockedCompletionRate)) - ); - if (callback != null) + saveGameData = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() { - CoreLoop.InvokeNext(delegate { callback(saveStats); }); - } + ContractResolver = ShouldSerializeContractResolver.Instance, + TypeNameHandling = TypeNameHandling.Auto, + ObjectCreationHandling = ObjectCreationHandling.Replace, + Converters = JsonConverterTypes.ConverterTypes + }); } - catch (Exception ex) + catch (Exception) { - Debug.LogError - ( - string.Concat - ( - new object[] - { - "Error while loading save file for slot ", - saveSlot, - " Exception: ", - ex - } - ) - ); - if (callback != null) - { - CoreLoop.InvokeNext(delegate { callback(null); }); - } + // Not a huge deal, this happens on saves with mod data which haven't been converted yet. + Logger.APILogger.LogWarn($"Failed to get save stats for slot {saveSlot} using Json.NET, falling back"); + saveGameData = JsonUtility.FromJson(json); + } + global::PlayerData playerData = saveGameData.playerData; + SaveStats saveStats = new SaveStats + ( + playerData.GetInt(nameof(PlayerData.maxHealthBase)), + playerData.GetInt(nameof(PlayerData.geo)), + playerData.GetVariable(nameof(PlayerData.mapZone)), + playerData.GetFloat(nameof(PlayerData.playTime)), + playerData.GetInt(nameof(PlayerData.MPReserveMax)), + playerData.GetInt(nameof(PlayerData.permadeathMode)), + playerData.GetBool(nameof(PlayerData.bossRushMode)), + playerData.GetFloat(nameof(PlayerData.completionPercentage)), + playerData.GetBool(nameof(PlayerData.unlockedCompletionRate)) + ); + if (callback != null) + { + CoreLoop.InvokeNext(delegate { callback(saveStats); }); } } - ); - } - - #endregion - - #region LoadFirstScene - - [MonoModReplace] - public IEnumerator LoadFirstScene() - { - yield return new WaitForEndOfFrame(); - this.OnWillActivateFirstLevel(); - this.LoadScene("Tutorial_01"); - ModHooks.OnNewGame(); - yield break; - } - - #endregion - - #region OnWillActivateFirstLevel - - public extern void orig_OnWillActivateFirstLevel(); - - public void OnWillActivateFirstLevel() - { - orig_OnWillActivateFirstLevel(); - ModHooks.OnNewGame(); + catch (Exception ex) + { + Debug.LogError($"Error while loading save file for slot {saveSlot} Exception: {ex}"); + if (callback != null) + { + CoreLoop.InvokeNext(delegate { callback(null); }); + } + } + }); } - #endregion - #region PauseToDynamicMenu [MonoModIgnore] @@ -760,5 +701,34 @@ public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTy cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted } + + [MonoModIgnore] + public static void LoadFirstScene(ILContext il, TypeDefinition stateMachineTypeDef) + { + // add a `ModHooks.OnNewGame();` at the end of the method + ILCursor cursor = new ILCursor(il); + + // Insert a call to your custom method + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1)); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); + cursor.Emit(OpCodes.Ldc_I4_0); // apparently afterlabel doesn't work as wanted + } + + [MonoModIgnore] + public static void OnWillActivateFirstLevel(ILContext il) + { + // add a `ModHooks.OnNewGame();` at the end of the method + ILCursor cursor = new ILCursor(il); + + // Insert a call to your custom method + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted + cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); + cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + } } } \ No newline at end of file From 104bcf74cfa9001590352806b4c8202480c73be0 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 16:26:37 +0200 Subject: [PATCH 32/35] remove attribute from name & add todo comments --- Assembly-CSharp/Patches/HasComponent.cs | 2 +- Assembly-CSharp/Patches/HealthManager.cs | 3 ++- Assembly-CSharp/Patches/HeroAnimationController.cs | 3 ++- Assembly-CSharp/Patches/InputHandler.cs | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Assembly-CSharp/Patches/HasComponent.cs b/Assembly-CSharp/Patches/HasComponent.cs index 9f938309..dd24f421 100644 --- a/Assembly-CSharp/Patches/HasComponent.cs +++ b/Assembly-CSharp/Patches/HasComponent.cs @@ -10,7 +10,7 @@ namespace Modding.Patches public class HasComponent : global::HutongGames.PlayMaker.Actions.HasComponent { [MonoModIgnore] - [Attributes.RemoveMethodCallAttribute + [Attributes.RemoveMethodCall ( "HutongGames.PlayMaker.ReflectionUtils", "GetGlobalType" diff --git a/Assembly-CSharp/Patches/HealthManager.cs b/Assembly-CSharp/Patches/HealthManager.cs index e222a423..54d4ebbb 100644 --- a/Assembly-CSharp/Patches/HealthManager.cs +++ b/Assembly-CSharp/Patches/HealthManager.cs @@ -11,7 +11,8 @@ public class HealthManager : global::HealthManager { [MonoModIgnore] public bool isDead; - + + // todo: make IL hook: add ModHooks before isDead check ///This may be used by mods to find new enemies. Check this isDead flag to see if they're already dead [MonoModReplace] protected IEnumerator CheckPersistence() diff --git a/Assembly-CSharp/Patches/HeroAnimationController.cs b/Assembly-CSharp/Patches/HeroAnimationController.cs index 1a8e8651..46a970e3 100644 --- a/Assembly-CSharp/Patches/HeroAnimationController.cs +++ b/Assembly-CSharp/Patches/HeroAnimationController.cs @@ -20,10 +20,11 @@ public class HeroAnimationController : global::HeroAnimationController [MonoModIgnore] private extern void UpdateAnimation(); + // todo: make IL hook: remove betaEnd pd check [MonoModReplace] private void Update() { - if (this.controlEnabled) + if (this.controlEnabled && !waitingToEnter) { this.UpdateAnimation(); } diff --git a/Assembly-CSharp/Patches/InputHandler.cs b/Assembly-CSharp/Patches/InputHandler.cs index 2dd48dcf..a9661846 100644 --- a/Assembly-CSharp/Patches/InputHandler.cs +++ b/Assembly-CSharp/Patches/InputHandler.cs @@ -21,6 +21,7 @@ public class InputHandler : global::InputHandler [MonoModIgnore] private GameManager gm; + // todo: make IL hook: add lockstate none before each ret // Reverted cursor behavior [MonoModReplace] private void OnGUI() From cbe52a5f065919444c6b80d65ed01cf0ba254765 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 16:27:25 +0200 Subject: [PATCH 33/35] add missing enum values and some [MonoModReplace] --- Assembly-CSharp/Mod.cs | 3 +- Assembly-CSharp/ModHooks.cs | 3 +- Assembly-CSharp/Patches/MenuSelectable.cs | 8 ++++ Assembly-CSharp/Patches/MenuSetting.cs | 3 +- Assembly-CSharp/Patches/Platform.cs | 2 + .../Patches/PlayMakerUnity2DProxy.cs | 1 + Assembly-CSharp/Patches/StartManager.cs | 5 +- Assembly-CSharp/Patches/UIButtonSkins.cs | 3 +- Assembly-CSharp/Patches/UIManager.cs | 46 ++++++++++--------- 9 files changed, 47 insertions(+), 27 deletions(-) diff --git a/Assembly-CSharp/Mod.cs b/Assembly-CSharp/Mod.cs index 3e476940..217698d8 100644 --- a/Assembly-CSharp/Mod.cs +++ b/Assembly-CSharp/Mod.cs @@ -10,6 +10,7 @@ using MonoMod.Utils; using System.Linq; using Newtonsoft.Json.Linq; +using Lang = Language.Language; // ReSharper disable file UnusedMember.Global @@ -188,7 +189,7 @@ public virtual void Initialize() { } /// change the text of the button to jump to this mod's menu. /// /// - public virtual string GetMenuButtonText() => $"{GetName()} {Language.Language.Get("MAIN_OPTIONS", "MainMenu")}"; + public virtual string GetMenuButtonText() => $"{GetName()} {Lang.Get("MAIN_OPTIONS", "MainMenu")}"; private void HookSaveMethods() { diff --git a/Assembly-CSharp/ModHooks.cs b/Assembly-CSharp/ModHooks.cs index 0ea986d3..88b303aa 100644 --- a/Assembly-CSharp/ModHooks.cs +++ b/Assembly-CSharp/ModHooks.cs @@ -12,6 +12,7 @@ using System.Linq; using Modding.Delegates; using Object = UnityEngine.Object; +using Lang = Language.Language; // ReSharper disable PossibleInvalidCastExceptionInForeachLoop // ReSharper disable SuggestVarOrType_SimpleTypes @@ -225,7 +226,7 @@ internal static void LogConsole(string message, LogLevel level) /// N/A internal static string LanguageGet(string key, string sheet) { - string res = Language.Language.GetInternal(key, sheet); + string res = Lang.GetInternal(key, sheet); if (LanguageGetHook == null) return res; diff --git a/Assembly-CSharp/Patches/MenuSelectable.cs b/Assembly-CSharp/Patches/MenuSelectable.cs index 5b2abbff..a906f8e6 100644 --- a/Assembly-CSharp/Patches/MenuSelectable.cs +++ b/Assembly-CSharp/Patches/MenuSelectable.cs @@ -60,6 +60,14 @@ public enum CancelAction ApplyVideoSettings, ApplyGameSettings, ApplyKeyboardSettings, + GoToExtrasMenu, + ApplyControllerSettings, + GoToExplicitSwitchUser, + ReturnToProfileMenu, + ApplyAdvancedVideoSettings, + ApplyAdvancedControllerSettings, + + // Added for the dynamic menu API CustomCancelAction } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/MenuSetting.cs b/Assembly-CSharp/Patches/MenuSetting.cs index 6f989f5b..c8ff0871 100644 --- a/Assembly-CSharp/Patches/MenuSetting.cs +++ b/Assembly-CSharp/Patches/MenuSetting.cs @@ -70,7 +70,8 @@ public enum MenuSettingType NativeInput, XInput, MFi, - // peepoHappy + + // Added for the dynamic menu API CustomSetting } } diff --git a/Assembly-CSharp/Patches/Platform.cs b/Assembly-CSharp/Patches/Platform.cs index f7f811f0..0bc59fe2 100644 --- a/Assembly-CSharp/Patches/Platform.cs +++ b/Assembly-CSharp/Patches/Platform.cs @@ -15,9 +15,11 @@ public ISharedData EncryptedSharedData get { return RoamingSharedData; } } + [MonoModReplace] public static bool IsSaveSlotIndexValid(int slotIndex) => true; // ReSharper disable once UnusedMember.Global + [MonoModReplace] protected string GetSaveSlotFileName(int slotIndex, SaveSlotFileNameUsage usage) { string text = slotIndex == 0 ? "user.dat" : $"user{slotIndex}.dat"; diff --git a/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs b/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs index 5209f962..3f671904 100644 --- a/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs +++ b/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs @@ -9,6 +9,7 @@ namespace Modding.Patches [MonoModPatch("global::PlayMakerUnity2DProxy")] public class PlayMakerUnity2DProxy : global::PlayMakerUnity2DProxy { + [MonoModReplace] public void Start() { if (!PlayMakerUnity2d.isAvailable()) diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs index 43a6f3fc..7a569ccd 100644 --- a/Assembly-CSharp/Patches/StartManager.cs +++ b/Assembly-CSharp/Patches/StartManager.cs @@ -4,6 +4,7 @@ using MonoMod; using UnityEngine; using UObject = UnityEngine.Object; +using Lang = Language.Language; // ReSharper disable All #pragma warning disable 1591, CS0649 @@ -87,7 +88,7 @@ private IEnumerator Start() yield return base.StartCoroutine(this.LanguageSettingDone()); } - TeamCherry.Localization.LanguageCode currentLanguage = (TeamCherry.Localization.LanguageCode) Language.Language.CurrentLanguage(); + TeamCherry.Localization.LanguageCode currentLanguage = (TeamCherry.Localization.LanguageCode) Lang.CurrentLanguage(); while (!Platform.Current.IsSharedDataMounted) { yield return null; @@ -104,7 +105,7 @@ private IEnumerator Start() } if (flag) { - Language.Language.LoadLanguage(); + Lang.LoadLanguage(); ChangeFontByLanguage[] array = UObject.FindObjectsByType(FindObjectsSortMode.None); for (int i = 0; i < array.Length; i++) { diff --git a/Assembly-CSharp/Patches/UIButtonSkins.cs b/Assembly-CSharp/Patches/UIButtonSkins.cs index 2feb7636..89cee43c 100644 --- a/Assembly-CSharp/Patches/UIButtonSkins.cs +++ b/Assembly-CSharp/Patches/UIButtonSkins.cs @@ -16,7 +16,8 @@ public class UIButtonSkins : global::UIButtonSkins private extern ButtonSkin GetButtonSkinFor(string buttonName); [MonoModIgnore] private extern ButtonSkin orig_GetButtonSkinFor(InputControlType inputControlType); - + + [MonoModIgnore] private InputHandler ih; public extern void orig_RefreshKeyMappings(); diff --git a/Assembly-CSharp/Patches/UIManager.cs b/Assembly-CSharp/Patches/UIManager.cs index b79be999..f7e22431 100644 --- a/Assembly-CSharp/Patches/UIManager.cs +++ b/Assembly-CSharp/Patches/UIManager.cs @@ -13,14 +13,35 @@ namespace Modding.Patches [MonoModPatch("global::UIManager")] public class UIManager : global::UIManager { + + private bool hasCalledEditMenus = false; + + public MenuScreen currentDynamicMenu { get; set; } + + private static Action _editMenus; + + public static event Action EditMenus + { + add + { + _editMenus += value; + if (_instance != null && _instance.hasCalledEditMenus) value(); + } + remove => _editMenus -= value; + } + + private Sprite LoadImage() => Assembly.GetExecutingAssembly().LoadEmbeddedSprite("Modding.logo.png", pixelsPerUnit: 100f); + + public static event Action BeforeHideDynamicMenu; + [MonoModIgnore] private static UIManager _instance; [MonoModIgnore] private InputHandler ih; - public MenuScreen currentDynamicMenu { get; set; } - + // todo: make IL hook: Debug.LogError + [MonoModReplace] public static UIManager get_instance() { if (UIManager._instance == null) @@ -41,22 +62,8 @@ public static UIManager get_instance() return UIManager._instance; } - public static event Action EditMenus - { - add - { - _editMenus += value; - if (_instance != null && _instance.hasCalledEditMenus) value(); - } - remove => _editMenus -= value; - } - - private static Action _editMenus; - public extern void orig_Awake(); - private Sprite LoadImage() => Assembly.GetExecutingAssembly().LoadEmbeddedSprite("Modding.logo.png", pixelsPerUnit: 100f); - public void Awake() { orig_Awake(); @@ -91,12 +98,9 @@ private void Start() var sr = clone.GetComponent(); sr.sprite = LoadImage(); } - - private bool hasCalledEditMenus = false; public extern IEnumerator orig_HideCurrentMenu(); - - public static event Action BeforeHideDynamicMenu; + public IEnumerator HideCurrentMenu() { if (((MainMenuState) this.menuState) == MainMenuState.DYNAMIC_MENU) @@ -187,7 +191,7 @@ public IEnumerator PauseToDynamicMenu(MenuScreen to) } } - [MonoModPatch("GlobalEnums.MainMenuState")] + [MonoModPatch("global::GlobalEnums.MainMenuState")] public enum MainMenuState { LOGO, From 3c259827734b273d0e75c3217a4e842f111d9729 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sat, 16 May 2026 18:49:30 +0200 Subject: [PATCH 34/35] remove a few comments to show why the opcode fuckery happens --- Assembly-CSharp/Patches/GameManager.cs | 86 ++++++++++++++------------ 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index 72524b63..f2499e43 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -579,6 +579,10 @@ public SceneLoad SceneLoad } } + // todo: apparently MoveType.AfterLabel doesn't work as wanted, so it works as a MoveType.Before, and we can just make: + // 1. the next instruction a OpCodes.Nop + // 2. put our stuff after the OpCodes.Nop + // 3. emit what we changed to a OpCodes.Nop public static partial class IlPatches { [MonoModIgnore] @@ -587,11 +591,11 @@ public static void OnApplicationQuit(ILContext il) // add a `ModHooks.OnApplicationQuit();` at the end of the method ILCursor cursor = new ILCursor(il); - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.EmitDelegate(global::Modding.ModHooks.OnApplicationQuit); - cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -605,12 +609,12 @@ public static void LoadScene(ILContext il) cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); cursor.Emit(OpCodes.Starg, 1); - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); - cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -624,21 +628,21 @@ public static void ClearSaveFile(ILContext il) cursor.EmitDelegate(global::Modding.ModHooks.OnSavegameClear); // this goes just before both `ret`s - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); - cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ret); // skip over the return cursor.GotoNext(); - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); - cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -648,18 +652,18 @@ public static void PlayerDead(ILContext il, TypeDefinition stateMachineTypeDef) ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.EmitDelegate(global::Modding.ModHooks.OnBeforePlayerDead); - cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ldloc_1); // this goes just before all the `ret`s - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0), x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterPlayerDead); - cursor.Emit(OpCodes.Ldc_I4_0); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ldc_I4_0); } [MonoModIgnore] @@ -671,35 +675,35 @@ public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTy // Insert a call to your custom method cursor.GotoNext ( - MoveType.AfterLabel, + MoveType.Before, x => x.MatchLdloc(1), x => x.MatchLdcI4(1), x => x.MatchStfld("tilemapDirty") ); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); cursor.Emit(OpCodes.Stfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); - cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ldloc_1); // somewhere before `this.RefreshTilemapInfo(destScene);` cursor.GotoNext ( - MoveType.AfterLabel, + MoveType.Before, x => x.MatchLdloc(1), x => x.MatchLdarg(0), x => x.MatchLdfld(out _), // destScene field of statemachine type x => x.MatchCallOrCallvirt(typeof(global::GameManager), "RefreshTilemapInfo") ); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); - cursor.Emit(OpCodes.Ldloc_1); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ldloc_1); } [MonoModIgnore] @@ -709,12 +713,12 @@ public static void LoadFirstScene(ILContext il, TypeDefinition stateMachineTypeD ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1)); - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchLdloc(1)); + cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0), x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); - cursor.Emit(OpCodes.Ldc_I4_0); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ldc_I4_0); } [MonoModIgnore] @@ -724,11 +728,11 @@ public static void OnWillActivateFirstLevel(ILContext il) ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; // apparently afterlabel doesn't work as wanted - cursor.GotoNext(); // apparently afterlabel doesn't work as wanted + cursor.GotoNext(MoveType.Before, x => x.MatchRet()); + cursor.Next.OpCode = OpCodes.Nop; + cursor.GotoNext(); cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); - cursor.Emit(OpCodes.Ret); // apparently afterlabel doesn't work as wanted + cursor.Emit(OpCodes.Ret); } } } \ No newline at end of file From 339b01f07d601622cd69772cc5ea52450678d135 Mon Sep 17 00:00:00 2001 From: SFGrenade <25555417+SFGrenade@users.noreply.github.com> Date: Sun, 17 May 2026 23:28:40 +0200 Subject: [PATCH 35/35] fix the afterlabel thing --- .../Patches/Attributes/IEnumeratorIlPatch.cs | 6 +- .../Patches/Attributes/RawIlPatch.cs | 3 +- Assembly-CSharp/Patches/EnemyDeathEffects.cs | 4 +- Assembly-CSharp/Patches/GameManager.cs | 56 ++++--------------- 4 files changed, 20 insertions(+), 49 deletions(-) diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs index 575e9732..ee204c50 100644 --- a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs +++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs @@ -6,6 +6,7 @@ using Mono.Cecil.Cil; using MonoMod; using MonoMod.Cil; +using MonoMod.Utils; namespace Modding.Patches.Attributes { @@ -58,7 +59,10 @@ public static void IEnumeratorIlPatch(MethodDefinition method, CustomAttribute a if (patcherMethod is null) throw new InvalidOperationException("Couldn't find patcher method!"); - patcherMethod.Invoke(null, [context, stateMachineTypeDef]); + context.Invoke(delegate(ILContext ctx) + { + patcherMethod.Invoke(null, [ctx, stateMachineTypeDef]); + }); } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs index 06e1057e..da34d26a 100644 --- a/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs +++ b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs @@ -4,6 +4,7 @@ using Mono.Cecil; using MonoMod; using MonoMod.Cil; +using MonoMod.Utils; namespace Modding.Patches.Attributes { @@ -51,7 +52,7 @@ public static void RawIlPatch(MethodDefinition method, CustomAttribute attrib) if (patcherMethod is null) throw new InvalidOperationException("Couldn't find patcher method!"); - patcherMethod.Invoke(null, new[] { context }); + context.Invoke(patcherMethod.CreateDelegate()); } } } \ No newline at end of file diff --git a/Assembly-CSharp/Patches/EnemyDeathEffects.cs b/Assembly-CSharp/Patches/EnemyDeathEffects.cs index 7578b203..36192cc3 100644 --- a/Assembly-CSharp/Patches/EnemyDeathEffects.cs +++ b/Assembly-CSharp/Patches/EnemyDeathEffects.cs @@ -29,7 +29,7 @@ public static void RecieveDeathEvent(ILContext il) // add a `ModHooks.OnRecieveDeathEvent(this, didFire, ref attackDirection, ref resetDeathEvent, ref spellBurn, ref isWatery);` at the start of the method ILCursor cursor = new ILCursor(il); - cursor.GotoNext(MoveType.Before, x => x.MatchLdarg(0)); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdarg(0)); // Insert a call to your custom method cursor.Emit(OpCodes.Ldarg_0); @@ -48,7 +48,7 @@ public static void RecordKillForJournal(ILContext il) // add a `ModHooks.OnRecordKillForJournal(this, this.playerDataName, $"killed{this.playerDataName}", $"kills{this.playerDataName}", $"newData{this.playerDataName}");` at the start of the method ILCursor cursor = new ILCursor(il); - cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0)); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0)); // Insert a call to your custom method cursor.Emit(OpCodes.Ldarg_0); // this diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs index f2499e43..538babb7 100644 --- a/Assembly-CSharp/Patches/GameManager.cs +++ b/Assembly-CSharp/Patches/GameManager.cs @@ -579,10 +579,6 @@ public SceneLoad SceneLoad } } - // todo: apparently MoveType.AfterLabel doesn't work as wanted, so it works as a MoveType.Before, and we can just make: - // 1. the next instruction a OpCodes.Nop - // 2. put our stuff after the OpCodes.Nop - // 3. emit what we changed to a OpCodes.Nop public static partial class IlPatches { [MonoModIgnore] @@ -591,11 +587,8 @@ public static void OnApplicationQuit(ILContext il) // add a `ModHooks.OnApplicationQuit();` at the end of the method ILCursor cursor = new ILCursor(il); - cursor.GotoNext(MoveType.Before, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); cursor.EmitDelegate(global::Modding.ModHooks.OnApplicationQuit); - cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -609,12 +602,9 @@ public static void LoadScene(ILContext il) cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); cursor.Emit(OpCodes.Starg, 1); - cursor.GotoNext(MoveType.Before, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); - cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -628,21 +618,15 @@ public static void ClearSaveFile(ILContext il) cursor.EmitDelegate(global::Modding.ModHooks.OnSavegameClear); // this goes just before both `ret`s - cursor.GotoNext(MoveType.Before, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); - cursor.Emit(OpCodes.Ret); // skip over the return cursor.GotoNext(); - cursor.GotoNext(MoveType.Before, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); cursor.Emit(OpCodes.Ldarg_1); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear); - cursor.Emit(OpCodes.Ret); } [MonoModIgnore] @@ -652,18 +636,12 @@ public static void PlayerDead(ILContext il, TypeDefinition stateMachineTypeDef) ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.Before, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchCallOrCallvirt(typeof(global::GameManager), "get_cameraCtrl")); cursor.EmitDelegate(global::Modding.ModHooks.OnBeforePlayerDead); - cursor.Emit(OpCodes.Ldloc_1); // this goes just before all the `ret`s - cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0), x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); cursor.EmitDelegate(global::Modding.ModHooks.OnAfterPlayerDead); - cursor.Emit(OpCodes.Ldc_I4_0); } [MonoModIgnore] @@ -675,35 +653,29 @@ public static void LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTy // Insert a call to your custom method cursor.GotoNext ( - MoveType.Before, + MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchLdcI4(1), x => x.MatchStfld("tilemapDirty") ); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad); cursor.Emit(OpCodes.Stfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); - cursor.Emit(OpCodes.Ldloc_1); // somewhere before `this.RefreshTilemapInfo(destScene);` cursor.GotoNext ( - MoveType.Before, + MoveType.AfterLabel, x => x.MatchLdloc(1), x => x.MatchLdarg(0), x => x.MatchLdfld(out _), // destScene field of statemachine type x => x.MatchCallOrCallvirt(typeof(global::GameManager), "RefreshTilemapInfo") ); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); cursor.Emit(OpCodes.Ldarg_0); cursor.Emit(OpCodes.Ldfld, stateMachineTypeDef.Fields.First(f => f.Name == "destScene")); cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged); - cursor.Emit(OpCodes.Ldloc_1); } [MonoModIgnore] @@ -713,12 +685,9 @@ public static void LoadFirstScene(ILContext il, TypeDefinition stateMachineTypeD ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.Before, x => x.MatchLdloc(1)); - cursor.GotoNext(MoveType.Before, x => x.MatchLdcI4(0), x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdloc(1)); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchLdcI4(0), x => x.MatchRet()); cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); - cursor.Emit(OpCodes.Ldc_I4_0); } [MonoModIgnore] @@ -728,11 +697,8 @@ public static void OnWillActivateFirstLevel(ILContext il) ILCursor cursor = new ILCursor(il); // Insert a call to your custom method - cursor.GotoNext(MoveType.Before, x => x.MatchRet()); - cursor.Next.OpCode = OpCodes.Nop; - cursor.GotoNext(); + cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet()); cursor.EmitDelegate(global::Modding.ModHooks.OnNewGame); - cursor.Emit(OpCodes.Ret); } } } \ No newline at end of file