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()); + } + yield return null; + } + if (!didWaitForPlayerPrefs) + { + Debug.LogFormat("Didn't need to wait for PlayerPrefs load.", Array.Empty()); + } + else + { + Debug.LogFormat("Finished waiting for PlayerPrefs load.", Array.Empty()); + } + + //modLoaderObj.StartCoroutine(ModLoader.LoadModsInit(modLoaderObj.gameObject)); + yield return ModLoader.LoadModsInit(modLoaderObj.gameObject); - StandaloneLoadingSpinner loadSpinner = UnityEngine.Object.Instantiate(this.loadSpinnerPrefab); - loadSpinner.Setup(null); + Platform.Current.SetSceneLoadState(true, true); loadOperation.allowSceneActivation = true; yield return loadOperation; yield break; diff --git a/Assembly-CSharp/Patches/SuppressPreloadException/CameraLockArea.cs b/Assembly-CSharp/Patches/SuppressPreloadException/CameraLockArea.cs index 311a06d2..c295efda 100644 --- a/Assembly-CSharp/Patches/SuppressPreloadException/CameraLockArea.cs +++ b/Assembly-CSharp/Patches/SuppressPreloadException/CameraLockArea.cs @@ -12,6 +12,26 @@ namespace Modding.Patches [MonoModPatch("global::CameraLockArea")] public class CameraLockArea : global::CameraLockArea { + [MonoModIgnore] + private bool hasGotRefs; + + [MonoModReplace] + private void GetRefs() + { + if (this.hasGotRefs) + { + return; + } + this.gcams = Modding.Patches.SuppressPreloadException.GameCameras.instance; + if (this.gcams == null) + { + return; + } + this.cameraCtrl = this.gcams.cameraController; + this.camTarget = this.gcams.cameraTarget; + this.hasGotRefs = true; + } + [MonoModIgnore] private SuppressPreloadException.GameCameras gcams; [MonoModIgnore] @@ -31,13 +51,12 @@ public class CameraLockArea : global::CameraLockArea [MonoModIgnore] private extern bool ValidateBounds(); + [MonoModReplace] private IEnumerator Start() { - this.gcams = SuppressPreloadException.GameCameras.instance; - if (this.gcams == null) + this.GetRefs(); + if (!this.hasGotRefs) yield break; - this.cameraCtrl = this.gcams.cameraController; - this.camTarget = this.gcams.cameraTarget; Scene scene = this.gameObject.scene; if (this.cameraCtrl == null) yield break; diff --git a/Assembly-CSharp/Patches/TakeDamage.cs b/Assembly-CSharp/Patches/TakeDamage.cs index fdd497ae..5a55ff7e 100644 --- a/Assembly-CSharp/Patches/TakeDamage.cs +++ b/Assembly-CSharp/Patches/TakeDamage.cs @@ -22,7 +22,7 @@ public override void OnEnter() MagnitudeMultiplier = this.MagnitudeMultiplier.Value, MoveAngle = this.MoveAngle.Value, MoveDirection = this.MoveDirection.Value, - Multiplier = ((!this.Multiplier.IsNone) ? this.Multiplier.Value : 1f), + Multiplier = (this.Multiplier.IsNone ? 1f : this.Multiplier.Value), SpecialType = (SpecialTypes) this.SpecialType.Value, IsExtraDamage = false }; diff --git a/Assembly-CSharp/Patches/UIButtonSkins.cs b/Assembly-CSharp/Patches/UIButtonSkins.cs index 2feb7636..89cee43c 100644 --- a/Assembly-CSharp/Patches/UIButtonSkins.cs +++ b/Assembly-CSharp/Patches/UIButtonSkins.cs @@ -16,7 +16,8 @@ public class UIButtonSkins : global::UIButtonSkins private extern ButtonSkin GetButtonSkinFor(string buttonName); [MonoModIgnore] private extern ButtonSkin orig_GetButtonSkinFor(InputControlType inputControlType); - + + [MonoModIgnore] private InputHandler ih; public extern void orig_RefreshKeyMappings(); diff --git a/Assembly-CSharp/Patches/UIManager.cs b/Assembly-CSharp/Patches/UIManager.cs index 0de510dd..f7e22431 100644 --- a/Assembly-CSharp/Patches/UIManager.cs +++ b/Assembly-CSharp/Patches/UIManager.cs @@ -13,14 +13,35 @@ namespace Modding.Patches [MonoModPatch("global::UIManager")] public class UIManager : global::UIManager { + + private bool hasCalledEditMenus = false; + + public MenuScreen currentDynamicMenu { get; set; } + + private static Action _editMenus; + + public static event Action EditMenus + { + add + { + _editMenus += value; + if (_instance != null && _instance.hasCalledEditMenus) value(); + } + remove => _editMenus -= value; + } + + private Sprite LoadImage() => Assembly.GetExecutingAssembly().LoadEmbeddedSprite("Modding.logo.png", pixelsPerUnit: 100f); + + public static event Action BeforeHideDynamicMenu; + [MonoModIgnore] private static UIManager _instance; [MonoModIgnore] private InputHandler ih; - public MenuScreen currentDynamicMenu { get; set; } - + // todo: make IL hook: Debug.LogError + [MonoModReplace] public static UIManager get_instance() { if (UIManager._instance == null) @@ -41,22 +62,8 @@ public static UIManager get_instance() return UIManager._instance; } - public static event Action EditMenus - { - add - { - _editMenus += value; - if (_instance != null && _instance.hasCalledEditMenus) value(); - } - remove => _editMenus -= value; - } - - private static Action _editMenus; - public extern void orig_Awake(); - private Sprite LoadImage() => Assembly.GetExecutingAssembly().LoadEmbeddedSprite("Modding.logo.png", pixelsPerUnit: 100f); - public void Awake() { orig_Awake(); @@ -91,12 +98,9 @@ private void Start() var sr = clone.GetComponent(); sr.sprite = LoadImage(); } - - private bool hasCalledEditMenus = false; public extern IEnumerator orig_HideCurrentMenu(); - - public static event Action BeforeHideDynamicMenu; + public IEnumerator HideCurrentMenu() { if (((MainMenuState) this.menuState) == MainMenuState.DYNAMIC_MENU) @@ -187,17 +191,19 @@ public IEnumerator PauseToDynamicMenu(MenuScreen to) } } - [MonoModPatch("GlobalEnums.MainMenuState")] + [MonoModPatch("global::GlobalEnums.MainMenuState")] public enum MainMenuState { LOGO, MAIN_MENU, OPTIONS_MENU, GAMEPAD_MENU, + ADVANCED_GAMEPAD_MENU, KEYBOARD_MENU, SAVE_PROFILES, AUDIO_MENU, VIDEO_MENU, + ADVANCED_VIDEO_MENU, EXIT_PROMPT, OVERSCAN_MENU, GAME_OPTIONS_MENU, diff --git a/Assembly-CSharp/Preloader.cs b/Assembly-CSharp/Preloader.cs index 468c4393..22e37bdd 100644 --- a/Assembly-CSharp/Preloader.cs +++ b/Assembly-CSharp/Preloader.cs @@ -87,7 +87,10 @@ Dictionary>> sceneHooks break; } - yield return CleanUpPreloading(); + if (toPreload.Count > 0 || sceneHooks.Count > 0) + { + yield return CleanUpPreloading(); + } UnmuteAllAudio(); Logger.APILogger.LogError($"Finished preloading in {stopwatch.ElapsedMilliseconds / 1000:F2}s"); @@ -105,6 +108,11 @@ private IEnumerator DoPreloadAssetBundle Dictionary>> sceneHooks ) { + if (toPreload.Count <= 0 && sceneHooks.Count <= 0) + { + yield break; + } + const string PreloadBundleName = "modding_api_asset_bundle"; string preloadJson = JsonConvert.SerializeObject @@ -226,6 +234,11 @@ private IEnumerator DoPreloadRepackedScenes Dictionary>> sceneHooks ) { + if (toPreload.Count <= 0 && sceneHooks.Count <= 0) + { + yield break; + } + const string PreloadBundleName = "modding_api_scene_bundle"; string preloadJson = JsonConvert.SerializeObject(toPreload.ToDictionary(k => k.Key, v => v.Value.SelectMany(x => x.Preloads).Distinct())); @@ -297,6 +310,11 @@ private IEnumerator DoPreloadScenes float progressBeta = 0 ) { + if (toPreload.Count <= 0 && sceneHooks.Count <= 0) + { + yield break; + } + List sceneNames = toPreload.Keys.Union(sceneHooks.Keys).ToList(); Dictionary scenePriority = new(); Dictionary sceneAsyncOperationHolder = new(); diff --git a/Assembly-CSharp/Properties/AssemblyInfo.cs b/Assembly-CSharp/Properties/AssemblyInfo.cs deleted file mode 100644 index 3be76136..00000000 --- a/Assembly-CSharp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.InteropServices; -[assembly: ComVisible(false)] -[assembly: Guid("1cacea6a-d114-46b1-8c47-f2f92062b844")] diff --git a/HollowKnight.Modding.API.sln b/HollowKnight.Modding.API.sln index a0c12c01..373de31f 100644 --- a/HollowKnight.Modding.API.sln +++ b/HollowKnight.Modding.API.sln @@ -3,12 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp\Assembly-CSharp.csproj", "{1CACEA6A-D114-46B1-8C47-F2F92062B844}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp/Assembly-CSharp.csproj", "{1CACEA6A-D114-46B1-8C47-F2F92062B844}" ProjectSection(ProjectDependencies) = postProject {E06FBDDA-0A27-45EC-AC28-259C23715C50} = {E06FBDDA-0A27-45EC-AC28-259C23715C50} + {CE61CBC8-9373-4C5B-BA15-DED48888DB73} = {CE61CBC8-9373-4C5B-BA15-DED48888DB73} EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrePatcher", "PrePatcher\PrePatcher.csproj", "{E06FBDDA-0A27-45EC-AC28-259C23715C50}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamCherry.Localization", "TeamCherry.Localization/TeamCherry.Localization.csproj", "{01234567-89AB-CDEF-0123-456789ABCDEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrePatcher", "PrePatcher/PrePatcher.csproj", "{E06FBDDA-0A27-45EC-AC28-259C23715C50}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostPatcher", "PostPatcher/PostPatcher.csproj", "{CE61CBC8-9373-4C5B-BA15-DED48888DB73}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -24,6 +29,14 @@ Global {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Debug|Any CPU.Build.0 = Debug|Any CPU {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Release|Any CPU.ActiveCfg = Release|Any CPU {E06FBDDA-0A27-45EC-AC28-259C23715C50}.Release|Any CPU.Build.0 = Release|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE61CBC8-9373-4C5B-BA15-DED48888DB73}.Release|Any CPU.Build.0 = Release|Any CPU + {01234567-89AB-CDEF-0123-456789ABCDEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01234567-89AB-CDEF-0123-456789ABCDEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01234567-89AB-CDEF-0123-456789ABCDEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01234567-89AB-CDEF-0123-456789ABCDEF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/PostPatcher/App.config b/PostPatcher/App.config new file mode 100644 index 00000000..56efbc7b --- /dev/null +++ b/PostPatcher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/PostPatcher/PostPatcher.csproj b/PostPatcher/PostPatcher.csproj new file mode 100644 index 00000000..e3078398 --- /dev/null +++ b/PostPatcher/PostPatcher.csproj @@ -0,0 +1,50 @@ + + + Exe + net472 + + true + PostPatcher + PostPatcher + Copyright © 2026 + bin/$(Configuration)/ + latest + true + + + + full + pdbonly + + + + + + + + + + + + + all + + + + + + + + + + + + + + + + + + + + diff --git a/PostPatcher/Program.cs b/PostPatcher/Program.cs new file mode 100644 index 00000000..5a7951f2 --- /dev/null +++ b/PostPatcher/Program.cs @@ -0,0 +1,74 @@ +using System; +using System.Linq; +using Mono.Cecil; + +namespace Postpatcher +{ + // ReSharper disable once ClassNeverInstantiated.Global + internal class Program + { + private static void Main(string[] args) + { + if (args.Length < 2) + { + Console.WriteLine("Usage: PostPatcher.exe "); + return; + } + + int forwarders = 0; + + using AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(args[0]); + + // forwarders += ForwardTypes(assembly, "TeamCherry.BuildBot.dll", "TeamCherry.BuildBot", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.Cinematics.dll", "TeamCherry.Cinematics", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.Localization.dll", "HutongGames.PlayMaker.Actions", "HutongGames.PlayMaker.Actions"); + forwarders += ForwardTypes(assembly, "TeamCherry.Localization.dll", "TeamCherry.Localization", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.NestedFadeGroup.dll", "HutongGames.PlayMaker.Actions", "HutongGames.PlayMaker.Actions"); + forwarders += ForwardTypes(assembly, "TeamCherry.NestedFadeGroup.dll", "TeamCherry.NestedFadeGroup", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.SharedUtils.dll", "TeamCherry.SharedUtils", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.TK2D.dll", "", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.TK2D.dll", "tk2dRuntime", ""); + forwarders += ForwardTypes(assembly, "TeamCherry.TK2D.dll", "tk2dRuntime.TileMap", ""); + + assembly.Write(args[1]); + + Console.WriteLine("Added " + forwarders + " type forwarders"); + } + + private static int ForwardTypes(AssemblyDefinition outAssembly, string sourcePath, string fromNameSpace, string toNameSpace) + { + int forwarders = 0; + using AssemblyDefinition sourceAssembly = AssemblyDefinition.ReadAssembly(sourcePath); + AssemblyNameReference nameReference = new AssemblyNameReference(sourceAssembly.Name.Name, sourceAssembly.Name.Version); + if (outAssembly.MainModule.AssemblyReferences.All(x => x.Name != sourceAssembly.Name.Name)) + { + outAssembly.MainModule.AssemblyReferences.Add(nameReference); + } + foreach (TypeDefinition type in sourceAssembly.MainModule.Types) + { + if (!type.IsPublic) continue; + if (type.Namespace != fromNameSpace) continue; + if (outAssembly.MainModule.GetType(type.Namespace, type.Name) != null) continue; + if (outAssembly.MainModule.ExportedTypes.Any(e => e.Namespace == type.Namespace && e.Name == type.Name)) continue; + var forwardedType = outAssembly.MainModule.ImportReference(type); + outAssembly.MainModule.ExportedTypes.Add + ( + new ExportedType + ( + toNameSpace, + type.Name, + outAssembly.MainModule, + outAssembly.Name + ) + { + Attributes = TypeAttributes.Public | TypeAttributes.Forwarder, + Scope = forwardedType.Scope + } + ); + forwarders++; + } + + return forwarders; + } + } +} \ No newline at end of file diff --git a/PrePatcher/PrePatcher.csproj b/PrePatcher/PrePatcher.csproj index 57f3cbcb..3f4b6c3a 100644 --- a/PrePatcher/PrePatcher.csproj +++ b/PrePatcher/PrePatcher.csproj @@ -2,40 +2,49 @@ Exe net472 + true PrePatcher PrePatcher - Copyright © 2019 - bin\$(Configuration)\ - 9 + Copyright © 2026 + bin/$(Configuration)/ + latest + true - - full - - - - pdbonly + + full + pdbonly - + - - - - - - - all + + + + + + + + + + + + + + + + + diff --git a/PrePatcher/Properties/AssemblyInfo.cs b/PrePatcher/Properties/AssemblyInfo.cs deleted file mode 100644 index 854da53c..00000000 --- a/PrePatcher/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Runtime.InteropServices; - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e06fbdda-0a27-45ec-ac28-259c23715c50")] diff --git a/TeamCherry.Localization/Patches/Language.cs b/TeamCherry.Localization/Patches/Language.cs new file mode 100644 index 00000000..f26eabcf --- /dev/null +++ b/TeamCherry.Localization/Patches/Language.cs @@ -0,0 +1,46 @@ +using MonoMod; +using System.Collections.Generic; +using UnityEngine; + +// ReSharper disable all +#pragma warning disable 1591, 649, 414, 169, CS0108, CS0626 + +namespace Modding.Patches +{ + [MonoModPatch("global::TeamCherry.Localization.Language")] + public static class Language + { + [MonoModIgnore] + private static Dictionary> _currentEntrySheets; + + [MonoModAdded] + 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 + "#!#"; + } + + [MonoModReplace] + public static string Get(string key, string sheetTitle) + { + if (LanguageGet != null) + return LanguageGet(key, sheetTitle); + return GetInternal(key, sheetTitle); + } + + [MonoModAdded] + public delegate string LanguageGetFunc(string key, string sheetTitle); + [MonoModAdded] + public static LanguageGetFunc LanguageGet = null; + } +} diff --git a/TeamCherry.Localization/TeamCherry.Localization.csproj b/TeamCherry.Localization/TeamCherry.Localization.csproj new file mode 100644 index 00000000..32e381a6 --- /dev/null +++ b/TeamCherry.Localization/TeamCherry.Localization.csproj @@ -0,0 +1,48 @@ + + + Modding + TeamCherry.Localization.mm + net472 + + Hollow Knight - Mod API Enabled + Modding API + Copyright © 2026 + bin/$(Configuration)/ + true + packages + latest + true + + + + full + bin\$(Configuration)\TeamCherry.Localization.mm.xml + + + pdbonly + bin\$(Configuration)\TeamCherry.Localization.mm.xml + + + + + all + + + + + + + + + + + + + + + + + + + + diff --git a/hollowknight.version b/hollowknight.version index 8a7a4d76..f36952bd 100644 --- a/hollowknight.version +++ b/hollowknight.version @@ -1 +1 @@ -1.5.78.11833 \ No newline at end of file +1.5.12620 \ No newline at end of file diff --git a/moddingapi.version b/moddingapi.version index aafd756e..61ae7069 100644 --- a/moddingapi.version +++ b/moddingapi.version @@ -1 +1 @@ -v77 \ No newline at end of file +v78 \ No newline at end of file