diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index fe822203..69f5491e 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -60,7 +60,9 @@ jobs:
macos) echo "RID=osx-x64" >> $GITHUB_ENV ;;
esac
- - uses: actions/setup-dotnet@v4
+ - uses: actions/setup-dotnet@v5
+ with:
+ dotnet-version: 10.x
- name: Setup MSBuild
run: |
sudo apt-get update -y
@@ -68,9 +70,13 @@ jobs:
- name: Restore Dependencies
run: |
dotnet restore
- - name: Build PrePatcher
+ - name: Build PrePatcher & PostPatcher
run: |
dotnet build PrePatcher -o PrePatcher/Output -p:Configuration=Release
+ dotnet build PostPatcher -o PostPatcher/Output -p:Configuration=Release
+ - name: Build TC assembly patches
+ run: |
+ dotnet build TeamCherry.Localization --runtime $RID -p:Configuration=Release
- name: Build Assembly-CSharp
run: |
dotnet build Assembly-CSharp --runtime $RID -p:Configuration=Release
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 337c16c9..4d1e8884 100644
--- a/Assembly-CSharp/Assembly-CSharp.csproj
+++ b/Assembly-CSharp/Assembly-CSharp.csproj
@@ -3,26 +3,33 @@
Modding
Assembly-CSharp.mm
net472
+
Hollow Knight - Mod API Enabled
Modding API
- Copyright © 2017
- bin\$(Configuration)\
+ Copyright © 2026
+ bin/$(Configuration)/
true
packages
latest
+ true
-
-
+
+
+
+
+
+
+
@@ -30,8 +37,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
@@ -44,12 +51,26 @@
+
+
+
+
+
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
@@ -60,11 +81,13 @@
-
+
+
+
-
+
@@ -72,16 +95,18 @@
-
+
-
+
+
+
-
+
.dll
@@ -95,19 +120,19 @@
-
+
-
-
+
+
full
bin\$(Configuration)\Assembly-CSharp.mm.xml
-
+
pdbonly
bin\$(Configuration)\Assembly-CSharp.mm.xml
@@ -120,20 +145,26 @@
all
-
+
all
-
+
-
+
+
+ true
+
false
+
+ false
+
-
+
@@ -141,64 +172,19 @@
-
- ../Vanilla/Assembly-CSharp.dll
- False
-
-
- ..\override\mscorlib.dll
-
-
- ../Vanilla/netstandard.dll
-
-
- ../JsonNet/Newtonsoft.Json.dll
-
-
- ../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/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/Language/Language.cs b/Assembly-CSharp/Language/Language.cs
new file mode 100644
index 00000000..2a51ed70
--- /dev/null
+++ b/Assembly-CSharp/Language/Language.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using UObject = UnityEngine.Object;
+using USystemLanguage = UnityEngine.SystemLanguage;
+
+// ReSharper disable All
+#pragma warning disable 1591, 0108, 0169, 0649, 0626
+
+namespace Language;
+
+[Obsolete("Use `TeamCherry.Localization.Language` instead.")]
+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(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);
+ public static string GetInternal(string key, string sheetTitle) => COMPAT_GetInternal(key, sheetTitle);
+ public static string Get(string key, string sheetTitle) => COMPAT_Get(key, sheetTitle);
+ 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);
+
+ 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 ⟳
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadLanguage()")]
+ [MonoMod.MonoModRemove]
+ extern private static void COMPAT_LoadLanguage();
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void LoadAvailableLanguages()")]
+ [MonoMod.MonoModRemove]
+ extern private static void COMPAT_LoadAvailableLanguages();
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String[] GetLanguages()")]
+ [MonoMod.MonoModRemove]
+ extern private static string[] COMPAT_GetLanguages();
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_SwitchLanguage(string langCode);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean SwitchLanguage(TeamCherry.Localization.LanguageCode)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_SwitchLanguage(TeamCherry.Localization.LanguageCode code);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "UnityEngine.Object GetAsset(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static UObject COMPAT_GetAsset(string name);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode CurrentLanguage()")]
+ [MonoMod.MonoModRemove]
+ extern private static TeamCherry.Localization.LanguageCode COMPAT_CurrentLanguage();
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static string COMPAT_Get(string key);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String GetInternal(System.String,System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static string COMPAT_GetInternal(string key, string sheetTitle);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String Get(System.String,System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static string COMPAT_Get(string key, string sheetTitle);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetSheets()")]
+ [MonoMod.MonoModRemove]
+ extern private static IEnumerable COMPAT_GetSheets();
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Collections.Generic.IEnumerable`1 GetKeys(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static IEnumerable COMPAT_GetKeys(string sheetTitle);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_Has(string key);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean Has(System.String,System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_Has(string key, string sheet);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasSheet(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_HasSheet(string sheet);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "TeamCherry.Localization.LanguageCode LanguageNameToCode(UnityEngine.SystemLanguage)")]
+ [MonoMod.MonoModRemove]
+ extern private static TeamCherry.Localization.LanguageCode COMPAT_LanguageNameToCode(USystemLanguage name);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Void DoSwitch(TeamCherry.Localization.LanguageCode)")]
+ [MonoMod.MonoModRemove]
+ extern private static void COMPAT_DoSwitch(TeamCherry.Localization.LanguageCode name);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.Boolean HasLanguageFile(System.String,System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static bool COMPAT_HasLanguageFile(string lang, string sheetTitle);
+ [MonoMod.MonoModLinkTo("TeamCherry.Localization.Language", "System.String GetLanguageFileContents(System.String)")]
+ [MonoMod.MonoModRemove]
+ extern private static string COMPAT_GetLanguageFileContents(string sheetTitle);
+}
\ 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..e560aa34
--- /dev/null
+++ b/Assembly-CSharp/Language/LanguageCode.cs
@@ -0,0 +1,214 @@
+// ReSharper disable All
+#pragma warning disable 1591, 0108, 0169, 0649, 0626
+
+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/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..f830174c 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 = TeamCherry.Localization.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 1ba7815f..c412dbad 100644
--- a/Assembly-CSharp/ModHooks.cs
+++ b/Assembly-CSharp/ModHooks.cs
@@ -25,7 +25,7 @@ namespace Modding
public class ModHooks
{
// Make sure this is in sync with `/moddingapi.version`.
- private const int _modVersion = 77;
+ private const int _modVersion = 78;
private static readonly string SettingsPath = Path.Combine(Application.persistentDataPath, "ModdingApi.GlobalSettings.json");
@@ -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,7 @@ 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);
+ 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 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/ModLoader.cs b/Assembly-CSharp/ModLoader.cs
index 6a0a19e6..69240f36 100644
--- a/Assembly-CSharp/ModLoader.cs
+++ b/Assembly-CSharp/ModLoader.cs
@@ -241,12 +241,14 @@ Assembly Resolve(object sender, ResolveEventArgs args)
// Setup dict of scene preloads
GetPreloads(orderedMods, scenes, toPreload, sceneHooks);
-
- if (toPreload.Count > 0 || sceneHooks.Count > 0)
+
+ /*if (toPreload.Count > 0 || sceneHooks.Count > 0)
{
Preloader pld = coroutineHolder.GetOrAddComponent();
yield return pld.Preload(toPreload, preloadedObjects, sceneHooks);
- }
+ }*/
+ Preloader pld = coroutineHolder.GetOrAddComponent();
+ yield return pld.Preload(toPreload, preloadedObjects, sceneHooks);
foreach (ModInstance mod in orderedMods)
{
diff --git a/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs
new file mode 100644
index 00000000..ee204c50
--- /dev/null
+++ b/Assembly-CSharp/Patches/Attributes/IEnumeratorIlPatch.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using JetBrains.Annotations;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using MonoMod;
+using MonoMod.Cil;
+using MonoMod.Utils;
+
+namespace Modding.Patches.Attributes
+{
+ ///
+ ///
+ /// MonoMod attribute for patching a method directly with IL
+ ///
+ [UsedImplicitly]
+ [MonoModCustomAttribute("IEnumeratorIlPatch")]
+ public class IEnumeratorIlPatch : Attribute
+ {
+ ///
+ ///
+ /// Patches a method directly with IL
+ ///
+ /// Method name that does the IL patch
+ public IEnumeratorIlPatch(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)
+ {
+ 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;
+
+ 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!");
+
+ context.Invoke(delegate(ILContext ctx)
+ {
+ patcherMethod.Invoke(null, [ctx, stateMachineTypeDef]);
+ });
+ }
+ }
+}
\ No newline at end of file
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/Attributes/RawIlPatch.cs b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs
new file mode 100644
index 00000000..da34d26a
--- /dev/null
+++ b/Assembly-CSharp/Patches/Attributes/RawIlPatch.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Reflection;
+using JetBrains.Annotations;
+using Mono.Cecil;
+using MonoMod;
+using MonoMod.Cil;
+using MonoMod.Utils;
+
+namespace Modding.Patches.Attributes
+{
+ ///
+ ///
+ /// MonoMod attribute for patching a method directly with IL
+ ///
+ [UsedImplicitly]
+ [MonoModCustomAttribute("RawIlPatch")]
+ public class RawIlPatch : Attribute
+ {
+ ///
+ ///
+ /// Patches a method directly with IL
+ ///
+ /// Method name that does the IL patch
+ public RawIlPatch(string patcherMethod) { }
+ }
+}
+
+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 = $"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!");
+
+ context.Invoke(patcherMethod.CreateDelegate());
+ }
+ }
+}
\ No newline at end of file
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/ReplaceMethod.cs
similarity index 91%
rename from Assembly-CSharp/Patches/ReplaceMethodAttribute.cs
rename to Assembly-CSharp/Patches/Attributes/ReplaceMethod.cs
index f5302616..df26c6e9 100644
--- a/Assembly-CSharp/Patches/ReplaceMethodAttribute.cs
+++ b/Assembly-CSharp/Patches/Attributes/ReplaceMethod.cs
@@ -7,7 +7,7 @@
using MonoMod;
using MonoMod.Cil;
-namespace Modding.Patches
+namespace Modding.Patches.Attributes
{
///
///
@@ -15,13 +15,13 @@ namespace Modding.Patches
///
[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) { }
}
}
diff --git a/Assembly-CSharp/Patches/EnemyDeathEffects.cs b/Assembly-CSharp/Patches/EnemyDeathEffects.cs
index 320fb607..0c03c064 100644
--- a/Assembly-CSharp/Patches/EnemyDeathEffects.cs
+++ b/Assembly-CSharp/Patches/EnemyDeathEffects.cs
@@ -1,4 +1,6 @@
-using MonoMod;
+using Mono.Cecil.Cil;
+using MonoMod;
+using MonoMod.Cil;
// ReSharper disable All
#pragma warning disable 1591, 0108, 0169, 0649, 0414
@@ -10,32 +12,52 @@ namespace Modding.Patches
public class EnemyDeathEffects : global::EnemyDeathEffects
{
[MonoModIgnore]
- private bool didFire;
+ [Attributes.RawIlPatch(nameof(IlPatches.EnemyDeathEffects_RecieveDeathEvent))]
+ extern public 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(nameof(IlPatches.EnemyDeathEffects_RecordKillForJournal))]
+ extern public static void RecordKillForJournal(string playerDataName);
+ }
- //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 partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void EnemyDeathEffects_RecieveDeathEvent(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.AfterLabel, 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.EmitDelegate(global::Modding.ModHooks.OnRecieveDeathEvent);
}
[MonoModIgnore]
- private string playerDataName;
+ public static void EnemyDeathEffects_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);
- private extern void orig_RecordKillForJournal();
+ cursor.GotoNext(MoveType.AfterLabel, 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.EmitDelegate(global::Modding.ModHooks.OnRecordKillForJournal);
}
}
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/GameManager.cs b/Assembly-CSharp/Patches/GameManager.cs
index 9510c2d5..0aff3db6 100644
--- a/Assembly-CSharp/Patches/GameManager.cs
+++ b/Assembly-CSharp/Patches/GameManager.cs
@@ -1,13 +1,18 @@
+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;
+using Encryption = TeamCherry.SharedUtils.Encryption;
// ReSharper disable all
#pragma warning disable 1591, 649, 414, 169, CS0108, CS0626
@@ -17,55 +22,65 @@ 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);
-
- public void LoadScene(string destScene)
- {
- destScene = ModHooks.BeforeSceneLoad(destScene);
+ private ModSavegameData moddedData;
- orig_LoadScene(destScene);
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.GameManager_OnApplicationQuit))]
+ extern private void OnApplicationQuit();
- ModHooks.OnSceneChanged(destScene);
- }
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.GameManager_LoadScene))]
+ extern public void LoadScene(string destScene);
- public extern void orig_BeginSceneTransition(GameManager.SceneLoadInfo info);
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.GameManager_ClearSaveFile))]
+ extern public void ClearSaveFile(int saveSlot, Action callback);
- public void BeginSceneTransition(GameManager.SceneLoadInfo info)
- {
- info.SceneName = ModHooks.BeforeSceneLoad(info.SceneName);
+ [MonoModIgnore]
+ [Attributes.IEnumeratorIlPatch(nameof(IlPatches.GameManager_PlayerDead))]
+ extern public IEnumerator PlayerDead(float waitTime);
- orig_BeginSceneTransition(info);
- }
+ [MonoModIgnore]
+ [Attributes.IEnumeratorIlPatch(nameof(IlPatches.GameManager_LoadSceneAdditive))]
+ extern public IEnumerator LoadSceneAdditive(string destScene);
- public extern void orig_ClearSaveFile(int saveSlot, Action callback);
+ [MonoModIgnore]
+ [Attributes.IEnumeratorIlPatch(nameof(IlPatches.GameManager_LoadFirstScene))]
+ extern public IEnumerator LoadFirstScene();
- public void ClearSaveFile(int saveSlot, Action callback)
- {
- ModHooks.OnSavegameClear(saveSlot);
- orig_ClearSaveFile(saveSlot, callback);
- ModHooks.OnAfterSaveGameClear(saveSlot);
- }
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.GameManager_OnWillActivateFirstLevel))]
+ extern public void OnWillActivateFirstLevel();
- public extern IEnumerator orig_PlayerDead(float waitTime);
+ // il patch just dies trying to resolve types for no reason?
+ public extern void orig_BeginSceneTransition(global::GameManager.SceneLoadInfo info);
- public IEnumerator PlayerDead(float waitTime)
+ public void BeginSceneTransition(GameManager.SceneLoadInfo info)
{
- ModHooks.OnBeforePlayerDead();
- yield return orig_PlayerDead(waitTime);
- ModHooks.OnAfterPlayerDead();
+ info.SceneName = ModHooks.BeforeSceneLoad(info.SceneName);
+ orig_BeginSceneTransition(info);
}
- #region SaveGame
-
- private ModSavegameData moddedData;
+ #region SaveGame & LoadGame
[MonoModIgnore]
private GameCameras gameCams;
@@ -91,23 +106,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)
{
@@ -148,13 +146,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
@@ -179,12 +179,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)
{
@@ -196,9 +201,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();
@@ -210,7 +213,7 @@ public void SaveGame(int saveSlot, Action callback)
(
saveSlot,
binary,
- delegate (bool didSave)
+ delegate(bool didSave)
{
this.HideSaveIcon();
callback(didSave);
@@ -223,7 +226,7 @@ public void SaveGame(int saveSlot, Action callback)
(
saveSlot,
Encoding.UTF8.GetBytes(text),
- delegate (bool didSave)
+ delegate(bool didSave)
{
this.HideSaveIcon();
if (callback != null)
@@ -265,30 +268,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)
{
@@ -318,7 +297,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()
{
@@ -344,12 +324,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
@@ -372,13 +353,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)
{
@@ -424,270 +409,293 @@ public void LoadGame(int saveSlot, Action callback)
#endregion
- #region GetSaveStatsForSlot
+ 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;
+ }
+ }
[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)
+ saveGameData = JsonConvert.DeserializeObject(json, new JsonSerializerSettings()
{
- // 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); });
- }
+ 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); });
}
}
- );
+ 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 LoadSceneAdditive
-
- [MonoModIgnore]
- private bool tilemapDirty;
-
- [MonoModIgnore]
- private bool waitForManualLevelStart;
+ #region PauseToDynamicMenu
[MonoModIgnore]
- public event GameManager.DestroyPooledObjects DestroyPersonalPools;
+ public extern void SetTimeScale(float timescale);
[MonoModIgnore]
- public event GameManager.UnloadLevel UnloadingLevel;
+ private extern void SetPausedState(bool value);
- [MonoModReplace]
- public IEnumerator LoadSceneAdditive(string destScene)
+ // code has been copied from PauseGameToggle
+ public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = false)
{
- Debug.Log("Loading " + destScene);
- destScene = ModHooks.BeforeSceneLoad(destScene);
- this.tilemapDirty = true;
- this.startedOnThisScene = false;
- this.nextSceneName = destScene;
- this.waitForManualLevelStart = true;
- if (this.DestroyPersonalPools != null)
+ if (this.TimeSlowed)
{
- this.DestroyPersonalPools();
+ yield break;
}
- if (this.UnloadingLevel != null)
+ if (!this.playerData.GetBool(nameof(PlayerData.disablePause)) && this.gameState == GlobalEnums.GameState.PLAYING)
{
- this.UnloadingLevel();
- }
+ this.isPaused = true;
+ this.ui.SetState(GlobalEnums.UIState.PAUSED);
+ this.SetPausedState(true);
+ this.SetState(GlobalEnums.GameState.PAUSED);
+ if (HeroController.instance != null)
+ {
+ HeroController.instance.Pause();
+ }
- string exitingScene = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
- AsyncOperation loadop = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(destScene, LoadSceneMode.Additive);
- loadop.allowSceneActivation = true;
- yield return loadop;
- yield return UnityEngine.SceneManagement.SceneManager.UnloadSceneAsync(exitingScene);
- ModHooks.OnSceneChanged(destScene);
- this.RefreshTilemapInfo(destScene);
- if (this.IsUnloadAssetsRequired(exitingScene, destScene))
+ 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)
{
- Debug.LogFormat(this, "Unloading assets due to zone transition", new object[0]);
- yield return Resources.UnloadUnusedAssets();
+ this.isPaused = false;
+ this.inputHandler.PreventPause();
+ this.ui.SetState(GlobalEnums.UIState.PLAYING);
+ this.SetPausedState(false);
+ this.SetState(GlobalEnums.GameState.PLAYING);
+ if (HeroController.instance != null)
+ {
+ HeroController.instance.UnPause();
+ }
+
+ MenuButtonList.ClearAllLastSelected();
+ yield return new WaitForSecondsRealtime(0.3f);
+ this.inputHandler.AllowPause();
}
- GCManager.Collect();
- this.SetupSceneRefs(true);
- this.BeginScene();
- this.OnNextLevelReady();
- this.waitForManualLevelStart = false;
- Debug.Log("Done Loading " + destScene);
yield break;
}
#endregion
- #region LoadFirstScene
+ [MonoModIgnore]
+ private SceneLoad sceneLoad;
+
+ /*
+ * This will allow modders to access the scene loader.
+ * Note that if there's no transition in progress, it will be null!
+ * 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]
+ public SceneLoad SceneLoad
+ {
+ get { return sceneLoad; }
+ }
+ }
- [MonoModReplace]
- public IEnumerator LoadFirstScene()
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void GameManager_OnApplicationQuit(ILContext il)
{
- yield return new WaitForEndOfFrame();
- this.OnWillActivateFirstLevel();
- this.LoadScene("Tutorial_01");
- ModHooks.OnNewGame();
- yield break;
+ // add a `ModHooks.OnApplicationQuit();` at the end of the method
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet());
+ cursor.EmitDelegate(global::Modding.ModHooks.OnApplicationQuit);
}
- #endregion
+ [MonoModIgnore]
+ public static void GameManager_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);
- #region OnWillActivateFirstLevel
+ // Insert a call to your custom method
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.BeforeSceneLoad);
+ cursor.Emit(OpCodes.Starg, 1);
- public extern void orig_OnWillActivateFirstLevel();
+ cursor.GotoNext(MoveType.AfterLabel, x => x.MatchRet());
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.OnSceneChanged);
+ }
- public void OnWillActivateFirstLevel()
+ [MonoModIgnore]
+ public static void GameManager_ClearSaveFile(ILContext il)
{
- orig_OnWillActivateFirstLevel();
- ModHooks.OnNewGame();
- }
+ // 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);
- #endregion
+ // 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
+ while (cursor.TryGotoNext(MoveType.AfterLabel, x => x.MatchRet()))
+ {
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.OnAfterSaveGameClear);
+ cursor.GotoNext();
+ }
+ }
- #region PauseToDynamicMenu
[MonoModIgnore]
- public extern void SetTimeScale(float timescale);
+ public static void GameManager_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);
- // code has been copied from PauseGameToggle
- public IEnumerator PauseToggleDynamicMenu(MenuScreen screen, bool allowUnpause = false)
+ // 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.EmitDelegate(global::Modding.ModHooks.OnAfterPlayerDead);
+ }
+
+ [MonoModIgnore]
+ public static void GameManager_LoadSceneAdditive(ILContext il, TypeDefinition stateMachineTypeDef)
{
- if (!this.TimeSlowed)
- {
- if (!this.playerData.GetBool(nameof(PlayerData.disablePause)) && this.gameState == GlobalEnums.GameState.PLAYING)
- {
- 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();
- }
- else if (allowUnpause && this.gameState == GlobalEnums.GameState.PAUSED)
- {
- 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();
- }
- }
- yield break;
+ // 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);
}
- #endregion
[MonoModIgnore]
- private SceneLoad sceneLoad;
+ public static void GameManager_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.EmitDelegate(global::Modding.ModHooks.OnNewGame);
+ }
- /*
- * This will allow modders to access the scene loader.
- * Note that if there's no transition in progress, it will be null!
- * 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]
- public SceneLoad SceneLoad
+ public static void GameManager_OnWillActivateFirstLevel(ILContext il)
{
- get { return sceneLoad; }
+ // 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.EmitDelegate(global::Modding.ModHooks.OnNewGame);
}
}
}
\ No newline at end of file
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",
diff --git a/Assembly-CSharp/Patches/HealthManager.cs b/Assembly-CSharp/Patches/HealthManager.cs
index e222a423..847c6f98 100644
--- a/Assembly-CSharp/Patches/HealthManager.cs
+++ b/Assembly-CSharp/Patches/HealthManager.cs
@@ -1,5 +1,8 @@
using System.Collections;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
using MonoMod;
+using MonoMod.Cil;
// ReSharper disable all
#pragma warning disable 1591, CS0108
@@ -10,20 +13,32 @@ namespace Modding.Patches
public class HealthManager : global::HealthManager
{
[MonoModIgnore]
- public bool isDead;
-
- ///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()
+ [Attributes.IEnumeratorIlPatch(nameof(IlPatches.HealthManager_CheckPersistence))]
+ extern protected IEnumerator CheckPersistence();
+ }
+
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void HealthManager_CheckPersistence(ILContext il, TypeDefinition stateMachineTypeDef)
{
- yield return null;
- //We insert the hook here because I think some enemys' FSMs need 1 frame to mark the "isDead" bool for things that it thinks should be dead.
- isDead = ModHooks.OnEnableEnemy( gameObject, isDead );
- if( this.isDead )
- {
- base.gameObject.SetActive( false );
- }
- yield break;
+ // add a `isDead = ModHooks.OnEnableEnemy( gameObject, isDead );` before the `this.isDead` check
+ ILCursor cursor = new ILCursor(il);
+
+ // Insert a call to your custom method
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdloc(1),
+ x => x.MatchLdfld("isDead")
+ );
+ cursor.Emit(OpCodes.Ldloc_1);
+ cursor.Emit(OpCodes.Ldloc_1);
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::HealthManager), "get_gameObject", true));
+ cursor.Emit(OpCodes.Ldloc_1);
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HealthManager), "isDead", true));
+ cursor.EmitDelegate(global::Modding.ModHooks.OnEnableEnemy);
+ cursor.Emit(OpCodes.Stfld, ReflectionHelper.GetFieldInfo(typeof(global::HealthManager), "isDead", true));
}
}
}
diff --git a/Assembly-CSharp/Patches/HeroAnimationController.cs b/Assembly-CSharp/Patches/HeroAnimationController.cs
index 1a8e8651..d266ab84 100644
--- a/Assembly-CSharp/Patches/HeroAnimationController.cs
+++ b/Assembly-CSharp/Patches/HeroAnimationController.cs
@@ -1,6 +1,8 @@
using System.Collections;
using GlobalEnums;
+using Mono.Cecil.Cil;
using MonoMod;
+using MonoMod.Cil;
using UnityEngine;
// ReSharper disable All
@@ -12,29 +14,33 @@ namespace Modding.Patches
public class HeroAnimationController : global::HeroAnimationController
{
[MonoModIgnore]
- private HeroControllerStates cState;
-
- [MonoModIgnore]
- private bool wasFacingRight;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroAnimationController_Update))]
+ extern private void Update();
+ }
+ public static partial class IlPatches
+ {
[MonoModIgnore]
- private extern void UpdateAnimation();
-
- [MonoModReplace]
- private void Update()
+ public static void HeroAnimationController_Update(ILContext il)
{
- if (this.controlEnabled)
- {
- this.UpdateAnimation();
- }
- else if (this.cState.facingRight)
- {
- this.wasFacingRight = true;
- }
- else
+ // remove the `this.pd.betaEnd` check at the end of the method
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("pd"),
+ x => x.MatchLdstr(nameof(global::PlayerData.betaEnd)),
+ x => x.MatchCallOrCallvirt(nameof(global::PlayerData.GetBool)),
+ x => x.MatchBrfalse(out ILLabel retLabel)
+ );
+ cursor.Emit(OpCodes.Ret);
+ while (cursor.Next.OpCode != OpCodes.Ret)
{
- this.wasFacingRight = false;
+ cursor.Remove();
}
+ cursor.Remove();
}
}
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/HeroController.cs b/Assembly-CSharp/Patches/HeroController.cs
index f0430b60..b36e14de 100644
--- a/Assembly-CSharp/Patches/HeroController.cs
+++ b/Assembly-CSharp/Patches/HeroController.cs
@@ -1,6 +1,8 @@
using System.Collections;
using GlobalEnums;
+using Mono.Cecil.Cil;
using MonoMod;
+using MonoMod.Cil;
using UnityEngine;
// ReSharper disable All
@@ -11,816 +13,57 @@ namespace Modding.Patches
[MonoModPatch("global::HeroController")]
public class HeroController : global::HeroController
{
- #region Attack()
-
- [MonoModIgnore]
- private float attackDuration;
-
- [MonoModIgnore]
- private PlayMakerFSM slashFsm;
-
- [MonoModIgnore]
- private float altAttackTime;
-
- [MonoModIgnore]
- private bool wallSlashing;
-
- [MonoModIgnore]
- private GameObject grubberFlyBeam;
-
[MonoModIgnore]
- private float MANTIS_CHARM_SCALE = 1.35f;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_Attack))]
+ extern private void Attack(AttackDirection attackDir);
[MonoModIgnore]
- private bool joniBeam;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_SoulGain))]
+ extern public void SoulGain();
[MonoModIgnore]
- public NailSlash wallSlash;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_LookForQueueInput))]
+ extern private void LookForQueueInput();
[MonoModIgnore]
- public NailSlash normalSlash;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_TakeDamage))]
+ extern public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount, int hazardType);
[MonoModIgnore]
- public NailSlash alternateSlash;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_StartMPDrain))]
+ extern public void StartMPDrain(float time);
[MonoModIgnore]
- public NailSlash upSlash;
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_Update))]
+ extern private void Update();
[MonoModIgnore]
- public NailSlash downSlash;
-
- [MonoModReplace]
- public void Attack(AttackDirection attackDir)
- {
- ModHooks.OnAttack(attackDir); //MOD API ADDED
- if (Time.timeSinceLevelLoad - this.altAttackTime > this.ALT_ATTACK_RESET)
- this.cState.altAttack = false;
- this.cState.attacking = true;
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_32)))
- this.attackDuration = this.ATTACK_DURATION_CH;
- else
- this.attackDuration = this.ATTACK_DURATION;
- if (this.cState.wallSliding)
- {
- 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.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
- {
- this.wallSlashing = false;
- if (attackDir == AttackDirection.normal)
- {
- if (!this.cState.altAttack)
- {
- this.slashComponent = this.normalSlash;
- this.slashFsm = this.normalSlashFsm;
- this.cState.altAttack = true;
- }
- else
- {
- this.slashComponent = this.alternateSlash;
- this.slashFsm = this.alternateSlashFsm;
- this.cState.altAttack = false;
- }
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35)))
- {
- if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.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 if (attackDir == AttackDirection.upward)
- {
- this.slashComponent = this.upSlash;
- this.slashFsm = this.upSlashFsm;
- this.cState.upAttacking = true;
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35)))
- {
- if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.playerData.CurrentMaxHealth && !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);
- this.grubberFlyBeam.transform.localEulerAngles = new Vector3(0f, 0f, 270f);
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13)))
- this.grubberFlyBeam.transform.SetScaleY(this.grubberFlyBeam.transform.localScale.y * this.MANTIS_CHARM_SCALE);
- }
- if (this.playerData.GetInt(nameof(PlayerData.health)) == 1 && this.playerData.GetBool(nameof(PlayerData.equippedCharm_6)) && this.playerData.GetInt(nameof(PlayerData.healthBlue)) < 1)
- {
- this.grubberFlyBeam = this.grubberFlyBeamPrefabU_fury.Spawn(this.transform.position);
- this.grubberFlyBeam.transform.SetScaleY(this.transform.localScale.x);
- this.grubberFlyBeam.transform.localEulerAngles = new Vector3(0f, 0f, 270f);
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13)))
- this.grubberFlyBeam.transform.SetScaleY(this.grubberFlyBeam.transform.localScale.y * this.MANTIS_CHARM_SCALE);
- }
- }
- }
- else if (attackDir == AttackDirection.downward)
- {
- this.slashComponent = this.downSlash;
- this.slashFsm = this.downSlashFsm;
- this.cState.downAttacking = true;
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_35)))
- {
- if ((this.playerData.GetInt(nameof(PlayerData.health)) >= this.playerData.CurrentMaxHealth && !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);
- this.grubberFlyBeam.transform.localEulerAngles = new Vector3(0f, 0f, 90f);
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13)))
- this.grubberFlyBeam.transform.SetScaleY(this.grubberFlyBeam.transform.localScale.y * this.MANTIS_CHARM_SCALE);
- }
- if (this.playerData.GetInt(nameof(PlayerData.health)) == 1 && this.playerData.GetBool(nameof(PlayerData.equippedCharm_6)) && this.playerData.GetInt(nameof(PlayerData.healthBlue)) < 1)
- {
- this.grubberFlyBeam = this.grubberFlyBeamPrefabD_fury.Spawn(this.transform.position);
- this.grubberFlyBeam.transform.SetScaleY(this.transform.localScale.x);
- this.grubberFlyBeam.transform.localEulerAngles = new Vector3(0f, 0f, 90f);
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_13)))
- this.grubberFlyBeam.transform.SetScaleY(this.grubberFlyBeam.transform.localScale.y * this.MANTIS_CHARM_SCALE);
- }
- }
- }
- }
- if (this.cState.wallSliding)
- {
- if (this.cState.facingRight)
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 180f;
- else
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 0f;
- }
- else if (attackDir == AttackDirection.normal && this.cState.facingRight)
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 0f;
- else if (attackDir == AttackDirection.normal && !this.cState.facingRight)
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 180f;
- else if (attackDir == AttackDirection.upward)
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 90f;
- else if (attackDir == AttackDirection.downward)
- this.slashFsm.FsmVariables.GetFsmFloat("direction").Value = 270f;
- this.altAttackTime = Time.timeSinceLevelLoad;
- ModHooks.AfterAttack(attackDir); //MOD API - Added
- if (!this.cState.attacking) return; //MOD API - Added
- this.slashComponent.StartSlash();
- if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_38)))
- this.fsm_orbitShield.SendEvent("SLASH");
- }
+ [Attributes.RawIlPatch(nameof(IlPatches.HeroController_DoAttack))]
+ extern private void DoAttack();
- #endregion
-
- #region SoulGain
-
- [MonoModIgnore]
- private GameManager gm;
-
- [MonoModReplace]
- public void SoulGain()
- {
- int mpcharge = this.playerData.GetInt("MPCharge");
- int num;
- if (mpcharge < this.playerData.GetInt("maxMP"))
- {
- num = 11;
- if (this.playerData.GetBool("equippedCharm_20"))
- {
- num += 3;
- }
-
- if (this.playerData.GetBool("equippedCharm_21"))
- {
- num += 8;
- }
- }
- else
- {
- num = 6;
- if (this.playerData.GetBool("equippedCharm_20"))
- {
- num += 2;
- }
-
- if (this.playerData.GetBool("equippedCharm_21"))
- {
- num += 6;
- }
- }
-
- int mpreserve = this.playerData.GetInt("MPReserve");
- num = Modding.ModHooks.OnSoulGain(num);
- this.playerData.AddMPCharge(num);
- GameCameras.instance.soulOrbFSM.SendEvent("MP GAIN");
- if (this.playerData.GetInt("MPReserve") != mpreserve)
- {
- this.gm.soulVessel_fsm.SendEvent("MP RESERVE UP");
- }
- }
-
- #endregion
-
- #region LookForQueueInput
-
- [MonoModIgnore]
- private bool isGameplayScene;
-
- [MonoModIgnore]
- private InputHandler inputHandler;
-
- [MonoModIgnore]
- private extern bool CanWallJump();
-
- [MonoModIgnore]
- private extern bool CanJump();
-
- [MonoModIgnore]
- private extern bool CanDoubleJump();
-
- [MonoModIgnore]
- private extern bool CanInfiniteAirJump();
-
- [MonoModIgnore]
- private extern bool CanDash();
-
- [MonoModIgnore]
- private extern bool CanAttack();
-
- [MonoModIgnore]
- private extern void DoWallJump();
-
- [MonoModIgnore]
- private extern void HeroJump();
-
- [MonoModIgnore]
- private extern void DoDoubleJump();
-
- [MonoModIgnore]
- private extern void CancelJump();
-
- [MonoModIgnore]
- private extern void ResetLook();
-
- [MonoModIgnore]
- private extern void HeroDash();
-
- [MonoModIgnore]
- private extern bool CanSwim();
-
- [MonoModIgnore]
- private extern void SetState(ActorStates newState);
-
- [MonoModIgnore]
- private HeroAudioController audioCtrl;
-
- [MonoModIgnore]
- private int jumpQueueSteps;
-
- [MonoModIgnore]
- private int doubleJumpQueueSteps;
-
- [MonoModIgnore]
- private bool doubleJumpQueuing;
-
- [MonoModIgnore]
- private int jumpReleaseQueueSteps;
-
- [MonoModIgnore]
- private bool jumpReleaseQueuing;
-
- [MonoModIgnore]
- private bool jumpQueuing;
-
- [MonoModIgnore]
- private int dashQueueSteps;
-
- [MonoModIgnore]
- private bool dashQueuing;
-
- [MonoModIgnore]
- private int attackQueueSteps;
-
- [MonoModIgnore]
- private bool attackQueuing;
-
- [MonoModIgnore]
- private int JUMP_QUEUE_STEPS = 2;
-
- [MonoModIgnore]
- private int DOUBLE_JUMP_QUEUE_STEPS = 10;
-
- [MonoModIgnore]
- private int ATTACK_QUEUE_STEPS = 5;
-
-
- [MonoModReplace]
- private void LookForQueueInput()
- {
- if (this.acceptingInput && !this.gm.isPaused && this.isGameplayScene)
- {
- if (this.inputHandler.inputActions.jump.WasPressed)
- {
- if (this.CanWallJump())
- {
- this.DoWallJump();
- }
- else if (this.CanJump())
- {
- this.HeroJump();
- }
- else if (this.CanDoubleJump())
- {
- this.DoDoubleJump();
- }
- else if (this.CanInfiniteAirJump())
- {
- this.CancelJump();
- this.audioCtrl.PlaySound(HeroSounds.JUMP);
- this.ResetLook();
- this.cState.jumping = true;
- }
- else
- {
- this.jumpQueueSteps = 0;
- this.jumpQueuing = true;
- this.doubleJumpQueueSteps = 0;
- this.doubleJumpQueuing = true;
- }
- }
-
- if (this.inputHandler.inputActions.dash.WasPressed && !ModHooks.OnDashPressed())
- {
- if (this.CanDash())
- {
- this.HeroDash();
- }
- else
- {
- this.dashQueueSteps = 0;
- this.dashQueuing = true;
- }
- }
-
- if (this.inputHandler.inputActions.attack.WasPressed)
- {
- if (this.CanAttack())
- {
- this.DoAttack();
- }
- else
- {
- this.attackQueueSteps = 0;
- this.attackQueuing = true;
- }
- }
-
- if (this.inputHandler.inputActions.jump.IsPressed)
- {
- if (this.jumpQueueSteps <= this.JUMP_QUEUE_STEPS && this.CanJump() && this.jumpQueuing)
- {
- this.HeroJump();
- }
- else if (this.doubleJumpQueueSteps <= this.DOUBLE_JUMP_QUEUE_STEPS && this.CanDoubleJump() && this.doubleJumpQueuing)
- {
- if (this.cState.onGround)
- {
- this.HeroJump();
- }
- else
- {
- this.DoDoubleJump();
- }
- }
-
- if (this.CanSwim())
- {
- if (this.hero_state != ActorStates.airborne)
- {
- this.SetState(ActorStates.airborne);
- }
-
- this.cState.swimming = true;
- }
- }
-
- if (this.inputHandler.inputActions.dash.IsPressed
- && this.dashQueueSteps <= this.DASH_QUEUE_STEPS
- && this.CanDash()
- && this.dashQueuing
- && !ModHooks.OnDashPressed()
- && this.CanDash())
- {
- this.HeroDash();
- }
-
- if (this.inputHandler.inputActions.attack.IsPressed && this.attackQueueSteps <= this.ATTACK_QUEUE_STEPS && this.CanAttack() && this.attackQueuing)
- {
- this.DoAttack();
- }
- }
- }
-
- #endregion
-
- #region TakeDamage
-
- [MonoModIgnore]
- private int hitsSinceShielded;
-
- [MonoModIgnore]
- private extern bool CanTakeDamage();
-
- [MonoModIgnore]
- private AudioSource audioSource;
-
- [MonoModIgnore]
- private extern void CancelAttack();
-
- [MonoModIgnore]
- private extern void CancelBounce();
-
- [MonoModIgnore]
- private extern void CancelRecoilHorizontal();
-
- [MonoModIgnore]
- private bool takeNoDamage;
-
- [MonoModIgnore]
- private float nailChargeTimer;
-
- [MonoModIgnore]
- private extern IEnumerator Die();
-
- [MonoModIgnore]
- private extern IEnumerator DieFromHazard(HazardType hazardType, float angle);
-
- [MonoModIgnore]
- private extern IEnumerator StartRecoil(CollisionSide impactSide, bool spawnDamageEffect, int damageAmount);
-
- [MonoModIgnore]
- public event HeroController.TakeDamageEvent OnTakenDamage;
-
- [MonoModReplace]
- public void TakeDamage(GameObject go, CollisionSide damageSide, int damageAmount, int hazardType)
- {
- damageAmount = ModHooks.OnTakeDamage(ref hazardType, damageAmount);
- bool spawnDamageEffect = true;
- if (damageAmount > 0)
- {
- if (BossSceneController.IsBossScene)
- {
- int bossLevel = BossSceneController.Instance.BossLevel;
- if (bossLevel != 1)
- {
- if (bossLevel == 2)
- {
- damageAmount = 9999;
- }
- }
- else
- {
- damageAmount *= 2;
- }
- }
-
- if (this.CanTakeDamage())
- {
- if (this.damageMode == DamageMode.HAZARD_ONLY && hazardType == 1)
- {
- return;
- }
-
- if (this.cState.shadowDashing && hazardType == 1)
- {
- return;
- }
-
- if (this.parryInvulnTimer > 0f && hazardType == 1)
- {
- return;
- }
-
- VibrationMixer mixer = VibrationManager.GetMixer();
- if (mixer != null)
- {
- mixer.StopAllEmissionsWithTag("heroAction");
- }
-
- bool flag = false;
- if (this.carefreeShieldEquipped && hazardType == 1)
- {
- if (this.hitsSinceShielded > 7)
- {
- this.hitsSinceShielded = 7;
- }
-
- switch (this.hitsSinceShielded)
- {
- case 1:
- if ((float) UnityEngine.Random.Range(1, 100) <= 10f)
- {
- flag = true;
- }
-
- break;
- case 2:
- if ((float) UnityEngine.Random.Range(1, 100) <= 20f)
- {
- flag = true;
- }
-
- break;
- case 3:
- if ((float) UnityEngine.Random.Range(1, 100) <= 30f)
- {
- flag = true;
- }
-
- break;
- case 4:
- if ((float) UnityEngine.Random.Range(1, 100) <= 50f)
- {
- flag = true;
- }
-
- break;
- case 5:
- if ((float) UnityEngine.Random.Range(1, 100) <= 70f)
- {
- flag = true;
- }
-
- break;
- case 6:
- if ((float) UnityEngine.Random.Range(1, 100) <= 80f)
- {
- flag = true;
- }
-
- break;
- case 7:
- if ((float) UnityEngine.Random.Range(1, 100) <= 90f)
- {
- flag = true;
- }
-
- break;
- default:
- flag = false;
- break;
- }
-
- if (flag)
- {
- this.hitsSinceShielded = 0;
- this.carefreeShield.SetActive(true);
- damageAmount = 0;
- spawnDamageEffect = false;
- }
- else
- {
- this.hitsSinceShielded++;
- }
- }
-
- if (this.playerData.GetBool("equippedCharm_5") && this.playerData.GetInt("blockerHits") > 0 && hazardType == 1 && this.cState.focusing && !flag)
- {
- this.proxyFSM.SendEvent("HeroCtrl-TookBlockerHit");
- this.audioSource.PlayOneShot(this.blockerImpact, 1f);
- spawnDamageEffect = false;
- damageAmount = 0;
- }
- else
- {
- this.proxyFSM.SendEvent("HeroCtrl-HeroDamaged");
- }
-
- this.CancelAttack();
- if (this.cState.wallSliding)
- {
- this.cState.wallSliding = false;
- this.wallSlideVibrationPlayer.Stop();
- }
-
- if (this.cState.touchingWall)
- {
- this.cState.touchingWall = false;
- }
-
- if (this.cState.recoilingLeft || this.cState.recoilingRight)
- {
- this.CancelRecoilHorizontal();
- }
-
- if (this.cState.bouncing)
- {
- this.CancelBounce();
- this.rb2d.velocity = new Vector2(this.rb2d.velocity.x, 0f);
- }
-
- if (this.cState.shroomBouncing)
- {
- this.CancelBounce();
- this.rb2d.velocity = new Vector2(this.rb2d.velocity.x, 0f);
- }
-
- if (!flag)
- {
- this.audioCtrl.PlaySound(HeroSounds.TAKE_HIT);
- }
-
- damageAmount = ModHooks.AfterTakeDamage(hazardType, damageAmount);
- if (!this.takeNoDamage && !this.playerData.GetBool("invinciTest"))
- {
- if (this.playerData.GetBool("overcharmed"))
- {
- this.playerData.TakeHealth(damageAmount * 2);
- }
- else
- {
- this.playerData.TakeHealth(damageAmount);
- }
- }
-
- if (this.playerData.GetBool("equippedCharm_3") && damageAmount > 0)
- {
- if (this.playerData.GetBool("equippedCharm_35"))
- {
- this.AddMPCharge(this.GRUB_SOUL_MP_COMBO);
- }
- else
- {
- this.AddMPCharge(this.GRUB_SOUL_MP);
- }
- }
-
- if (this.joniBeam && damageAmount > 0)
- {
- this.joniBeam = false;
- }
-
- if (this.cState.nailCharging || this.nailChargeTimer != 0f)
- {
- this.cState.nailCharging = false;
- this.nailChargeTimer = 0f;
- }
-
- if (damageAmount > 0 && this.OnTakenDamage != null)
- {
- this.OnTakenDamage();
- }
-
- if (this.playerData.GetInt("health") == 0)
- {
- base.StartCoroutine(this.Die());
- }
- else if (hazardType == 2)
- {
- base.StartCoroutine(this.DieFromHazard(HazardType.SPIKES, (!(go != null)) ? 0f : go.transform.rotation.z));
- }
- else if (hazardType == 3)
- {
- base.StartCoroutine(this.DieFromHazard(HazardType.ACID, 0f));
- }
- else if (hazardType == 4)
- {
- Debug.Log("Lava death");
- }
- else if (hazardType == 5)
- {
- base.StartCoroutine(this.DieFromHazard(HazardType.PIT, 0f));
- }
- else
- {
- base.StartCoroutine(this.StartRecoil(damageSide, spawnDamageEffect, damageAmount));
- }
- }
- else if (this.cState.invulnerable && !this.cState.hazardDeath && !this.playerData.GetBool("isInvincible"))
- {
- if (hazardType == 2)
- {
- if (!this.takeNoDamage)
- {
- damageAmount = ModHooks.AfterTakeDamage(hazardType, damageAmount);
- this.playerData.TakeHealth(damageAmount);
- }
-
- this.proxyFSM.SendEvent("HeroCtrl-HeroDamaged");
- if (this.playerData.GetInt("health") == 0)
- {
- base.StartCoroutine(this.Die());
- }
- else
- {
- this.audioCtrl.PlaySound(HeroSounds.TAKE_HIT);
- base.StartCoroutine(this.DieFromHazard(HazardType.SPIKES, (!(go != null)) ? 0f : go.transform.rotation.z));
- }
- }
- else if (hazardType == 3)
- {
- damageAmount = ModHooks.AfterTakeDamage(hazardType, damageAmount);
- this.playerData.TakeHealth(damageAmount);
- this.proxyFSM.SendEvent("HeroCtrl-HeroDamaged");
- if (this.playerData.GetInt("health") == 0)
- {
- base.StartCoroutine(this.Die());
- }
- else
- {
- base.StartCoroutine(this.DieFromHazard(HazardType.ACID, 0f));
- }
- }
- else if (hazardType == 4)
- {
- Debug.Log("Lava damage");
- }
- }
- }
- }
-
- #endregion
-
- [MonoModIgnore]
- private NailSlash slashComponent;
-
- [MonoModIgnore]
- private float focusMP_amount;
-
- private void orig_StartMPDrain(float time) { }
-
- public void StartMPDrain(float time)
- {
- orig_StartMPDrain(time);
- focusMP_amount *= ModHooks.OnFocusCost();
- }
-
- private void orig_Update() { }
-
- private void Update()
+ // il patch just dies trying to resolve types for no reason?
+ public extern void orig_CharmUpdate();
+ public void CharmUpdate()
{
- ModHooks.OnHeroUpdate();
- orig_Update();
+ orig_CharmUpdate();
+ ModHooks.OnCharmUpdate(playerData, this);
+ playerData.UpdateBlueHealth();
}
-
#region Dash()
+ // This is the original dash vector calculating code used by the game
+ // It is used to set the input dash velocity vector for the DashVectorHook
[MonoModIgnore]
- private float dash_timer;
-
- [MonoModIgnore]
- private extern void FinishedDashing();
+ private float BUMP_VELOCITY;
[MonoModIgnore]
- private Rigidbody2D rb2d;
+ private float BUMP_VELOCITY_DASH;
- // This is the original dash vector calculating code used by the game
- // It is used to set the input dash velocity vector for the DashVectorHook
+ [MonoModAdded]
private Vector2 OrigDashVector()
{
- const float BUMP_VELOCITY = 4f;
- const float BUMP_VELOCITY_DASH = 5f;
Vector2 origVector;
-
float velocity;
if (this.playerData.GetBool(nameof(PlayerData.equippedCharm_16)) && this.cState.shadowDashing)
{
@@ -839,11 +82,7 @@ private Vector2 OrigDashVector()
{
if (this.CheckForBump(CollisionSide.right))
{
- origVector = new Vector2
- (
- velocity,
- (!this.cState.onGround) ? BUMP_VELOCITY_DASH : BUMP_VELOCITY
- );
+ origVector = new Vector2(velocity, this.cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
@@ -852,20 +91,25 @@ private Vector2 OrigDashVector()
}
else if (this.CheckForBump(CollisionSide.left))
{
- origVector = new Vector2
- (
- -velocity,
- (!this.cState.onGround) ? BUMP_VELOCITY_DASH : BUMP_VELOCITY
- );
+ origVector = new Vector2(-velocity, this.cState.onGround ? BUMP_VELOCITY : BUMP_VELOCITY_DASH);
}
else
{
origVector = new Vector2(-velocity, 0f);
}
-
return origVector;
}
+ [MonoModIgnore]
+ private float dash_timer;
+
+ [MonoModIgnore]
+ private extern void FinishedDashing();
+
+ [MonoModIgnore]
+ private Rigidbody2D rb2d;
+
+ [MonoModReplace]
private void Dash()
{
AffectedByGravity(false);
@@ -879,32 +123,184 @@ private void Dash()
Vector2 vector = OrigDashVector();
vector = ModHooks.DashVelocityChange(vector);
- rb2d.velocity = vector;
+ rb2d.linearVelocity = vector;
dash_timer += Time.deltaTime;
}
#endregion
+ }
- #region CharmUpdate()
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void HeroController_Attack(ILContext il)
+ {
+ // remove the `this.pd.betaEnd` check at the end of the method
+ ILCursor cursor = new ILCursor(il).Goto(0);
+
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.OnAttack);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("slashComponent"),
+ x => x.MatchCallOrCallvirt(nameof(global::NailSlash.StartSlash))
+ );
+ var labelToJumpOverRet = cursor.DefineLabel();
+ labelToJumpOverRet.Target = cursor.Next;
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.AfterAttack);
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroController), nameof(global::HeroController.cState)));
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroControllerStates), nameof(global::HeroControllerStates.attacking)));
+ cursor.Emit(OpCodes.Brtrue_S, labelToJumpOverRet);
+ cursor.Emit(OpCodes.Ret);
+ }
- private extern void orig_CharmUpdate();
+ [MonoModIgnore]
+ public static void HeroController_SoulGain(ILContext il)
+ {
+ // add a `num = Modding.ModHooks.OnSoulGain(num);` near the end of the method
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld(nameof(global::HeroController.playerData)),
+ x => x.MatchLdloc(0),
+ x => x.MatchCallOrCallvirt(nameof(global::PlayerData.AddMPCharge))
+ );
+ cursor.Emit(OpCodes.Ldloc_0);
+ cursor.EmitDelegate(global::Modding.ModHooks.OnSoulGain);
+ cursor.Emit(OpCodes.Stloc_0);
+ }
- public void CharmUpdate()
+ [MonoModIgnore]
+ public static void HeroController_LookForQueueInput(ILContext il)
{
- orig_CharmUpdate();
- ModHooks.OnCharmUpdate(playerData, this);
- playerData.UpdateBlueHealth();
+ // add a `&& !Modding.ModHooks.OnDashPressed()` to two if (...)
+ ILCursor cursor = new ILCursor(il);
+
+ ILLabel labelForFirstIf = cursor.DefineLabel();
+ cursor.GotoNext
+ (
+ MoveType.After,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("inputHandler"),
+ x => x.MatchLdfld(nameof(global::InputHandler.inputActions)),
+ x => x.MatchLdfld(nameof(global::HeroActions.dash)),
+ x => x.MatchCallOrCallvirt("get_WasPressed"),
+ x => x.MatchBrfalse(out labelForFirstIf)
+ );
+ cursor.EmitDelegate(global::Modding.ModHooks.OnDashPressed);
+ cursor.Emit(OpCodes.Brtrue_S, labelForFirstIf);
+
+ ILLabel labelForSecondIf = cursor.DefineLabel();
+ cursor.GotoNext
+ (
+ MoveType.After,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("inputHandler"),
+ x => x.MatchLdfld(nameof(global::InputHandler.inputActions)),
+ x => x.MatchLdfld(nameof(global::HeroActions.dash)),
+ x => x.MatchCallOrCallvirt("get_IsPressed"),
+ x => x.MatchBrfalse(out labelForSecondIf)
+ );
+ cursor.EmitDelegate(global::Modding.ModHooks.OnDashPressed);
+ cursor.Emit(OpCodes.Brtrue_S, labelForSecondIf);
}
- #endregion
+ [MonoModIgnore]
+ public static void HeroController_TakeDamage(ILContext il)
+ {
+ // add a `damageAmount = Modding.ModHooks.OnTakeDamage(ref hazardType, damageAmount);` at the start and a `damageAmount = Modding.ModHooks.AfterTakeDamage(hazardType, damageAmount);` somewhere in the middle
+ ILCursor cursor = new ILCursor(il).Goto(0);
+
+ cursor.Emit(OpCodes.Ldarga_S, il.Method.Parameters[3]); // ref hazardType
+ cursor.Emit(OpCodes.Ldarg_3); // damageAmount
+ cursor.EmitDelegate(global::Modding.ModHooks.OnTakeDamage);
+ cursor.Emit(OpCodes.Starg_S, il.Method.Parameters[2]); // damageAmount
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld(nameof(global::HeroController.takeNoDamage)),
+ x => x.MatchBrtrue(out var _)
+ );
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[3]); // hazardType
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[2]); // damageAmount
+ cursor.EmitDelegate(global::Modding.ModHooks.AfterTakeDamage);
+ cursor.Emit(OpCodes.Starg_S, il.Method.Parameters[2]); // damageAmount
+
+ cursor.GotoNext
+ (
+ MoveType.After,
+ x => x.MatchLdcI4(3),
+ x => x.MatchBneUn(out var _)
+ ); // to skip over some parts
+ cursor.GotoNext
+ (
+ MoveType.After,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld(nameof(global::HeroController.takeNoDamage)),
+ x => x.MatchBrtrue(out var _)
+ );
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[3]); // hazardType
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[2]); // damageAmount
+ cursor.EmitDelegate(global::Modding.ModHooks.AfterTakeDamage);
+ cursor.Emit(OpCodes.Starg_S, il.Method.Parameters[2]); // damageAmount
+
+ cursor.GotoNext
+ (
+ MoveType.After,
+ x => x.MatchLdcI4(3),
+ x => x.MatchBneUn(out var _)
+ );
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[3]); // hazardType
+ cursor.Emit(OpCodes.Ldarg_S, il.Method.Parameters[2]); // damageAmount
+ cursor.EmitDelegate(global::Modding.ModHooks.AfterTakeDamage);
+ cursor.Emit(OpCodes.Starg_S, il.Method.Parameters[2]); // damageAmount
+ }
[MonoModIgnore]
- private extern void orig_DoAttack();
+ public static void HeroController_StartMPDrain(ILContext il)
+ {
+ // add a `this.focusMP_amount *= Modding.ModHooks.OnFocusCost();` at the end
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchRet()
+ );
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroController), "focusMP_amount"));
+ cursor.EmitDelegate(global::Modding.ModHooks.OnFocusCost);
+ cursor.Emit(OpCodes.Mul);
+ cursor.Emit(OpCodes.Stfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroController), "focusMP_amount"));
+ }
- public void DoAttack()
+ [MonoModIgnore]
+ public static void HeroController_Update(ILContext il)
{
- ModHooks.OnDoAttack();
- orig_DoAttack();
+ // add a `ModHooks.OnHeroUpdate();` at the start
+ ILCursor cursor = new ILCursor(il).Goto(0);
+
+ cursor.EmitDelegate(global::Modding.ModHooks.OnHeroUpdate);
+ }
+
+ [MonoModIgnore]
+ public static void HeroController_DoAttack(ILContext il)
+ {
+ // add a `ModHooks.OnDoAttack();` at the start
+ ILCursor cursor = new ILCursor(il).Goto(0);
+
+ cursor.EmitDelegate(global::Modding.ModHooks.OnDoAttack);
}
}
}
diff --git a/Assembly-CSharp/Patches/InputHandler.cs b/Assembly-CSharp/Patches/InputHandler.cs
index 3e5a18b4..cc38d366 100644
--- a/Assembly-CSharp/Patches/InputHandler.cs
+++ b/Assembly-CSharp/Patches/InputHandler.cs
@@ -1,4 +1,6 @@
+using Mono.Cecil.Cil;
using MonoMod;
+using MonoMod.Cil;
using UnityEngine;
#pragma warning disable 1591
@@ -10,41 +12,25 @@ namespace Modding.Patches
public class InputHandler : global::InputHandler
{
[MonoModIgnore]
- private bool isTitleScreenScene;
-
- [MonoModIgnore]
- private bool isMenuScene;
-
- [MonoModIgnore]
- private bool controllerPressed;
+ [Attributes.RawIlPatch(nameof(IlPatches.InputHandler_OnGUI))]
+ extern private void OnGUI();
+ }
+ public static partial class IlPatches
+ {
[MonoModIgnore]
- private GameManager gm;
-
- // Reverted cursor behavior
- [MonoModReplace]
- private void OnGUI()
+ public static void InputHandler_OnGUI(ILContext il)
{
- Cursor.lockState = CursorLockMode.None;
- if (isTitleScreenScene)
- {
- Cursor.visible = false;
- return;
- }
-
- if (!isMenuScene)
- {
- ModHooks.OnCursor(gm);
- return;
- }
+ // add a `Cursor.lockState = CursorLockMode.None;` before every (4) `ret`
+ ILCursor cursor = new ILCursor(il);
- if (controllerPressed)
+ // Insert a call to your custom method
+ while (cursor.TryGotoNext(MoveType.AfterLabel, x => x.MatchRet()))
{
- Cursor.visible = false;
- return;
+ cursor.Emit(OpCodes.Ldc_I4, CursorLockMode.None);
+ cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::UnityEngine.Cursor), "set_lockState", false));
+ cursor.GotoNext();
}
-
- Cursor.visible = true;
}
}
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/Language.cs b/Assembly-CSharp/Patches/Language.cs
deleted file mode 100644
index 5fbd34c2..00000000
--- a/Assembly-CSharp/Patches/Language.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System.Collections.Generic;
-using MonoMod;
-using UnityEngine;
-
-// ReSharper disable All
-#pragma warning disable 1591, CS0649
-
-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);
- }
- }
-}
\ No newline at end of file
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 51f1b298..c8ff0871 100644
--- a/Assembly-CSharp/Patches/MenuSetting.cs
+++ b/Assembly-CSharp/Patches/MenuSetting.cs
@@ -55,16 +55,23 @@ 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,
- // peepoHappy
+ XInput,
+ MFi,
+
+ // Added for the dynamic menu API
CustomSetting
}
}
diff --git a/Assembly-CSharp/Patches/NailSlash.cs b/Assembly-CSharp/Patches/NailSlash.cs
new file mode 100644
index 00000000..7523fb07
--- /dev/null
+++ b/Assembly-CSharp/Patches/NailSlash.cs
@@ -0,0 +1,38 @@
+using Mono.Cecil.Cil;
+using MonoMod;
+using MonoMod.Cil;
+using UnityEngine;
+
+// ReSharper disable All
+#pragma warning disable 1591, CS0626
+
+namespace Modding.Patches
+{
+ [MonoModPatch("global::NailSlash")]
+ public class NailSlash : global::NailSlash
+ {
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.NailSlash_OnTriggerEnter2D))]
+ extern private void OnTriggerEnter2D(Collider2D otherCollider);
+ }
+
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void NailSlash_OnTriggerEnter2D(ILContext il)
+ {
+ // add a `ModHooks.OnSlashHit(otherCollider, gameObject);` at the start
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchRet()
+ );
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::NailSlash), "get_gameObject", true));
+ cursor.EmitDelegate(global::Modding.ModHooks.OnSlashHit);
+ }
+ }
+}
\ No newline at end of file
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/OnTriggerEnter2D.cs b/Assembly-CSharp/Patches/OnTriggerEnter2D.cs
deleted file mode 100644
index c763f457..00000000
--- a/Assembly-CSharp/Patches/OnTriggerEnter2D.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using MonoMod;
-using UnityEngine;
-
-// ReSharper disable All
-#pragma warning disable 1591, CS0626
-
-namespace Modding.Patches
-{
- [MonoModPatch("global::NailSlash")]
- public class NailSlash : global::NailSlash
- {
- private extern void orig_OnTriggerEnter2D(Collider2D otherCollider);
-
- private void OnTriggerEnter2D(Collider2D otherCollider)
- {
- ModHooks.OnSlashHit(otherCollider, gameObject);
- orig_OnTriggerEnter2D(otherCollider);
- }
- }
-}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/Platform.cs b/Assembly-CSharp/Patches/Platform.cs
index 28b67b31..140cdeaf 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,9 +9,18 @@ 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; }
+ }
+
+ [MonoModReplace]
public static bool IsSaveSlotIndexValid(int slotIndex) => true;
+ // todo: this is the exact same as vanilla???
// 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 9cd2d27b..d60dfd78 100644
--- a/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs
+++ b/Assembly-CSharp/Patches/PlayMakerUnity2DProxy.cs
@@ -1,4 +1,6 @@
-using MonoMod;
+using Mono.Cecil.Cil;
+using MonoMod;
+using MonoMod.Cil;
using UnityEngine;
// ReSharper disable All
@@ -9,21 +11,28 @@ namespace Modding.Patches
[MonoModPatch("global::PlayMakerUnity2DProxy")]
public class PlayMakerUnity2DProxy : global::PlayMakerUnity2DProxy
{
- public void Start()
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.PlayMakerUnity2DProxy_Start))]
+ extern public void Start();
+ }
+
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void PlayMakerUnity2DProxy_Start(ILContext il)
{
- if (!PlayMakerUnity2d.isAvailable())
- {
- Debug.LogError
- (
- "PlayMakerUnity2DProxy requires the 'PlayMaker Unity 2D' Prefab in the Scene.\nUse the menu 'PlayMaker/Addons/Unity 2D/Components/Add PlayMakerUnity2D to Scene' to correct the situation",
- this
- );
- enabled = false;
- return;
- }
+ // add a `SetInt(nameof(healthBlue), GetInt(nameof(healthBlue)) + ModHooks.OnBlueHealth());` at the end
+ ILCursor cursor = new ILCursor(il);
- ModHooks.OnColliderCreate(gameObject);
- RefreshImplementation();
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchCallOrCallvirt(nameof(global::PlayMakerUnity2DProxy.RefreshImplementation))
+ );
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::PlayMakerUnity2DProxy), "get_gameObject", true));
+ cursor.EmitDelegate(global::Modding.ModHooks.OnColliderCreate);
}
}
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/PlayerData.cs b/Assembly-CSharp/Patches/PlayerData.cs
index f3531b85..54b1e9f1 100644
--- a/Assembly-CSharp/Patches/PlayerData.cs
+++ b/Assembly-CSharp/Patches/PlayerData.cs
@@ -1,4 +1,6 @@
+using Mono.Cecil.Cil;
using MonoMod;
+using MonoMod.Cil;
using UnityEngine;
// ReSharper disable All
@@ -10,9 +12,6 @@ namespace Modding.Patches
[MonoModPatch("global::PlayerData")]
public class PlayerData : global::PlayerData
{
- [MonoModIgnore]
- public static PlayerData instance { get; set; }
-
public void SetBoolInternal(string boolName, bool value)
{
ReflectionHelper.SetFieldSafe(this, boolName, value);
@@ -228,20 +227,47 @@ public void TakeHealth(int amount)
TakeHealthInternal(amount);
}
- public extern void orig_UpdateBlueHealth();
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.PlayerData_UpdateBlueHealth))]
+ extern public void UpdateBlueHealth();
+
+ [MonoModIgnore]
+ [Attributes.RawIlPatch(nameof(IlPatches.PlayerData_AddHealth))]
+ extern public void AddHealth(int amount);
+ }
- public void UpdateBlueHealth()
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void PlayerData_UpdateBlueHealth(ILContext il)
{
- orig_UpdateBlueHealth();
- SetInt(nameof(healthBlue), GetInt(nameof(healthBlue)) + ModHooks.OnBlueHealth());
+ // add a `SetInt(nameof(healthBlue), GetInt(nameof(healthBlue)) + ModHooks.OnBlueHealth());` at the end
+ ILCursor cursor = new ILCursor(il);
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchRet()
+ );
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldstr, nameof(global::PlayerData.healthBlue));
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldstr, nameof(global::PlayerData.healthBlue));
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::PlayerData), "GetInt", true));
+ cursor.EmitDelegate(global::Modding.ModHooks.OnBlueHealth);
+ cursor.Emit(OpCodes.Add);
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::PlayerData), "SetInt", true));
}
- public extern void orig_AddHealth(int amount);
-
- public void AddHealth(int amount)
+ [MonoModIgnore]
+ public static void PlayerData_AddHealth(ILContext il)
{
- amount = ModHooks.BeforeAddHealth(amount);
- orig_AddHealth(amount);
+ // add a `amount = ModHooks.BeforeAddHealth(amount);` at the start
+ ILCursor cursor = new ILCursor(il).Goto(0);
+
+ cursor.Emit(OpCodes.Ldarg_1);
+ cursor.EmitDelegate(global::Modding.ModHooks.BeforeAddHealth);
+ cursor.Emit(OpCodes.Starg, 1);
}
}
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/SceneManager.cs b/Assembly-CSharp/Patches/SceneManager.cs
index 677dfbbd..b77747fd 100644
--- a/Assembly-CSharp/Patches/SceneManager.cs
+++ b/Assembly-CSharp/Patches/SceneManager.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using Mono.Cecil.Cil;
using UnityEngine;
using MonoMod;
+using MonoMod.Cil;
// ReSharper disable All
#pragma warning disable 1591, 0108, 0169, 0649, 0414, CS0626
@@ -12,65 +14,42 @@ namespace Modding.Patches
public class SceneManager : global::SceneManager
{
[MonoModIgnore]
- private bool gameplayScene;
+ [Attributes.RawIlPatch(nameof(IlPatches.SceneManager_Update))]
+ extern private void Update();
[MonoModIgnore]
- private HeroController heroCtrl;
-
+ private Transform borderLeft;
[MonoModIgnore]
- private bool heroInfoSent;
-
- private extern void orig_Update();
-
+ private Transform borderRight;
[MonoModIgnore]
- private GameManager gm;
-
- //Added checks for null and an attempt to fix any missing references
- private void Update()
- {
- if (this.gameplayScene)
- {
- if (!this.heroInfoSent && this.heroCtrl != null && (this.heroCtrl.heroLight == null || this.heroCtrl.heroLight.material == null))
- {
- this.heroCtrl.SetDarkness(this.darknessLevel);
- this.heroInfoSent = true;
- }
- }
-
- orig_Update();
- }
+ private Transform borderUp;
+ [MonoModIgnore]
+ private Transform borderDown;
//add modhook to send the newly created borders to any mods that want them
- [MonoModReplace]
- private void DrawBlackBorders()
+ private extern void orig_OnCameraAspectChanged(float aspect);
+ 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);
+ orig_OnCameraAspectChanged(aspect);
- 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);
-
- ModHooks.OnDrawBlackBorders(borders);
-
- foreach (var border in borders)
+ List borders = new List();
+ if (this.borderLeft != null)
{
- UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(border, base.gameObject.scene);
+ 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)
+ {
+ borders.Add(this.borderDown.gameObject);
+ }
+ ModHooks.OnDrawBlackBorders(borders);
}
private extern void orig_Start();
@@ -84,4 +63,48 @@ private void Start()
{ }
}
}
+
+ public static partial class IlPatches
+ {
+ [MonoModIgnore]
+ public static void SceneManager_Update(ILContext il)
+ {
+ // add a branch around `this.heroCtrl.heroLight.material.SetColor("_Color", Color.white);`
+ ILCursor cursor = new ILCursor(il);
+
+ ILLabel forAfterChecks = cursor.DefineLabel();
+
+ cursor.GotoNext
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("heroCtrl"),
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld(nameof(global::SceneManager.darknessLevel)),
+ x => x.MatchCallOrCallvirt(nameof(global::HeroController.SetDarkness))
+ );
+ forAfterChecks.Target = cursor.Next;
+
+ cursor.GotoPrev
+ (
+ MoveType.AfterLabel,
+ x => x.MatchLdarg(0),
+ x => x.MatchLdfld("heroCtrl"),
+ x => x.MatchLdfld(nameof(global::HeroController.heroLight))
+ );
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::SceneManager), "heroCtrl"));
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroController), nameof(global::HeroController.heroLight)));
+ cursor.Emit(OpCodes.Ldnull);
+ cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::UnityEngine.Object), "op_Equality", false));
+ cursor.Emit(OpCodes.Brtrue_S, forAfterChecks);
+ cursor.Emit(OpCodes.Ldarg_0);
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::SceneManager), "heroCtrl"));
+ cursor.Emit(OpCodes.Ldfld, ReflectionHelper.GetFieldInfo(typeof(global::HeroController), nameof(global::HeroController.heroLight)));
+ cursor.Emit(OpCodes.Callvirt, ReflectionHelper.GetMethodInfo(typeof(global::UnityEngine.SpriteRenderer), "get_material", true));
+ cursor.Emit(OpCodes.Ldnull);
+ cursor.Emit(OpCodes.Call, ReflectionHelper.GetMethodInfo(typeof(global::UnityEngine.Object), "op_Equality", false));
+ cursor.Emit(OpCodes.Brtrue_S, forAfterChecks);
+ }
+ }
}
\ No newline at end of file
diff --git a/Assembly-CSharp/Patches/StartManager.cs b/Assembly-CSharp/Patches/StartManager.cs
index 01b8dbb1..fc965142 100644
--- a/Assembly-CSharp/Patches/StartManager.cs
+++ b/Assembly-CSharp/Patches/StartManager.cs
@@ -1,6 +1,10 @@
-using System.Collections;
+using System;
+using System.Collections;
+using System.Threading;
using MonoMod;
using UnityEngine;
+using UObject = UnityEngine.Object;
+using Lang = TeamCherry.Localization.Language;
// ReSharper disable All
#pragma warning disable 1591, CS0649
@@ -12,6 +16,37 @@ namespace Modding.Patches
[MonoModPatch("global::StartManager")]
public class StartManager : global::StartManager
{
+ private MonoBehaviour modLoaderObj = null;
+
+ private extern void orig_Awake();
+ private void Awake()
+ {
+ // i love working with self-contained libraries where one has to work around cyclic dependencies
+ ReflectionHelper.SetField(typeof(TeamCherry.Localization.Language), "LanguageGet", ModHooks.LanguageGet);
+
+ 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
+ modLoaderObj = obj.AddComponent();
+ }
+ 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;
@@ -30,28 +65,92 @@ public class StartManager : global::StartManager
[MonoModIgnore]
private extern IEnumerator LanguageSettingDone();
+ // todo: make IL hook: seems trivial enough?
+ [MonoModReplace]
private IEnumerator Start()
{
this.controllerImage.sprite = this.GetControllerSpriteForPlatform(this.platform);
- AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Menu_Title");
+ // AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Menu_Title");
+ AsyncOperation loadOperation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync("Quit_To_Menu");
loadOperation.allowSceneActivation = false;
- bool showLanguageSelect = !this.CheckIsLanguageSet();
- if (showLanguageSelect && Platform.Current.ShowLanguageSelect)
+ Platform.Current.SetSceneLoadState(true, false);
+ if (!this.CheckIsLanguageSet() && Platform.Current.ShowLanguageSelect)
{
yield return base.StartCoroutine(this.ShowLanguageSelect());
while (!this.confirmedLanguage)
{
yield return null;
}
-
yield return base.StartCoroutine(this.LanguageSettingDone());
}
-
+ TeamCherry.Localization.LanguageCode currentLanguage = (TeamCherry.Localization.LanguageCode) Lang.CurrentLanguage();
+ while (!Platform.Current.IsSharedDataMounted)
+ {
+ yield return null;
+ }
+ bool savedLanguageDifferentFromDefault = false;
+ string text;
+ if (TeamCherry.Localization.LocalizationProjectSettings.TryGetSavedLanguageCode(out text))
+ {
+ TeamCherry.Localization.LanguageCode languageEnum = TeamCherry.Localization.LocalizationSettings.GetLanguageEnum(text);
+ if (currentLanguage != languageEnum)
+ {
+ savedLanguageDifferentFromDefault = true;
+ }
+ }
+ if (savedLanguageDifferentFromDefault)
+ {
+ Lang.LoadLanguage();
+ ChangeFontByLanguage[] changeFontByLanguages = UObject.FindObjectsByType(FindObjectsSortMode.None);
+ for (int i = 0; i < changeFontByLanguages.Length; i++)
+ {
+ changeFontByLanguages[i].SetFont();
+ }
+ SetTextMeshProGameText[] setTextMeshProGameTexts = base.GetComponentsInChildren(true);
+ for (int i = 0; i < setTextMeshProGameTexts.Length; i++)
+ {
+ setTextMeshProGameTexts[i].UpdateText();
+ }
+ LogoLanguage[] logoLanguages = base.GetComponentsInChildren(true);
+ for (int i = 0; i < logoLanguages.Length; i++)
+ {
+ logoLanguages[i].SetSprite();
+ }
+ }
this.startManagerAnimator.SetBool("WillShowControllerNotice", false);
this.startManagerAnimator.SetBool("WillShowQuote", true);
+ /* ################################################################################################################################## */
+ // this.startManagerAnimator.SetTrigger("Start");
+ // int loadingIconNameHash = Animator.StringToHash("LoadingIcon");
+ // while (this.startManagerAnimator.GetCurrentAnimatorStateInfo(0).shortNameHash != loadingIconNameHash)
+ // {
+ // yield return null;
+ // }
+ /* ################################################################################################################################## */
+ UnityEngine.Object.Instantiate(this.loadSpinnerPrefab).Setup(null);
+ bool didWaitForPlayerPrefs = false;
+ while (!Platform.Current.IsPlayerPrefsLoaded)
+ {
+ if (!didWaitForPlayerPrefs)
+ {
+ didWaitForPlayerPrefs = true;
+ Debug.LogFormat("Waiting for PlayerPrefs load...", Array.Empty