diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs index e12d6ef82..5e33912f8 100644 --- a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs +++ b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs @@ -618,7 +618,7 @@ internal static void ProcessAssembly(EverestModuleMetadata meta, Assembly asm, T ctor = type.GetConstructor(new Type[] { typeof(EntityData), typeof(Vector2), typeof(EntityID) }); if (ctor != null) { - loader = (level, levelData, offset, entityData) => (Entity) ctor.Invoke(new object[] { entityData, offset, new EntityID(levelData.Name, entityData.ID + (patch_Level._isLoadingTriggers ? 10000000 : 0)) }); + loader = (level, levelData, offset, entityData) => (Entity) ctor.Invoke(new object[] { entityData, offset, (entityData as patch_EntityData).EntityID }); goto RegisterEntityLoader; } diff --git a/Celeste.Mod.mm/Patches/EntityData.cs b/Celeste.Mod.mm/Patches/EntityData.cs index b49b27bf4..9debf0ccf 100644 --- a/Celeste.Mod.mm/Patches/EntityData.cs +++ b/Celeste.Mod.mm/Patches/EntityData.cs @@ -12,5 +12,9 @@ class patch_EntityData : EntityData { return orig_Has(key); } + public EntityID EntityID; + internal void InitializeEntityID(string LevelName) { + EntityID = new EntityID(string.IsNullOrWhiteSpace(LevelName) ? EntityID.None.Level : LevelName, ID + (patch_LevelData._isRegisteringTriggers ? 10000000 : 0)); + } } } diff --git a/Celeste.Mod.mm/Patches/Level.cs b/Celeste.Mod.mm/Patches/Level.cs index 73af7ac16..6fc164316 100644 --- a/Celeste.Mod.mm/Patches/Level.cs +++ b/Celeste.Mod.mm/Patches/Level.cs @@ -613,8 +613,6 @@ private bool CheckForErrors() { private bool _IsInDoNotLoadIncreased(LevelData level, EntityData entity) => Session.DoNotLoad.Contains(new EntityID(level.Name, entity.ID + 20000000)); - [ThreadStatic] - internal static bool _isLoadingTriggers; } public static class LevelExt { @@ -686,7 +684,8 @@ public static void PatchLevelLoader(ILContext context, CustomAttribute attrib) { m_LoadStrings_Add.DeclaringType = t_LoadStrings; m_LoadStrings_ctor.DeclaringType = t_LoadStrings; - FieldReference f_isLoadingTriggers = context.Method.DeclaringType.FindField("_isLoadingTriggers")!; + + FieldDefinition f_EntityData_EntityID = MonoModRule.Modder.Module.GetType("Celeste.EntityData").Resolve().FindField("EntityID"); MethodReference m_IsInDoNotLoadIncreased = context.Method.DeclaringType.FindMethod("_IsInDoNotLoadIncreased")!; ILCursor cursor = new ILCursor(context); @@ -711,36 +710,23 @@ public static void PatchLevelLoader(ILContext context, CustomAttribute attrib) { cursor.Index++; } - // Reset to apply trigger loading patches + + // Reset to apply EntityID fix patches - replaces the isLoadingTriggers patch cursor.Index = 0; - int v_levelData = -1; - cursor.GotoNext(MoveType.Before, instr => instr.MatchLdloc(out v_levelData), instr => instr.MatchLdfld("Celeste.LevelData", "Triggers")); - // set global flag _isLoadingTriggers to true - cursor.EmitLdcI4(1); - cursor.EmitStsfld(f_isLoadingTriggers); - int v_entityData = -1; - cursor.GotoNext(instr => instr.MatchLdloc(out v_entityData), instr => instr.MatchLdfld("Celeste.EntityData", "ID")); - ILLabel continueLabel = null; - cursor.GotoNext(MoveType.After, instr => instr.MatchBrtrue(out continueLabel)); - // add - // || _IsInDoNotLoadIncreased(levelData, trigger) - // to if condition for continue to handle triggers that already add 10000000 to their DoNotLoad entry - cursor.EmitLdarg0(); - cursor.EmitLdloc(v_levelData); - cursor.EmitLdloc(v_entityData); - cursor.EmitCall(m_IsInDoNotLoadIncreased); - cursor.EmitBrtrue(continueLabel); - cursor.GotoNext(MoveType.AfterLabel, instr => instr.MatchLdloc(out _), instr => instr.MatchLdfld("Celeste.LevelData", "FgDecals")); - Instruction oldFinallyEnd = cursor.Next; - // set _isLoadingTriggers to false - cursor.EmitLdcI4(0); - Instruction newFinallyEnd = cursor.Prev; - cursor.EmitStsfld(f_isLoadingTriggers); - // fix end of finally block - foreach (ExceptionHandler handler in context.Body.ExceptionHandlers.Where(handler => handler.HandlerEnd == oldFinallyEnd)) { - handler.HandlerEnd = newFinallyEnd; - break; - } + // First instance of call EntityID.ctor is referenced to ldloca.s 19 = entityID (in Entities loop), we want to add `entityID = entity.EntityID` after it + // We also don't want to break mod parity by replacing instruction content + cursor.GotoNext(MoveType.After, i => i.MatchLdloc(18)); // this is the only way i found that the gotoNext works. someone could easily clean this up in the future. + cursor.Index++; // checking against call System.Void Celeste.EntityID::.ctor(System.String, System.Int32) from the MonoModRule class didn't work. + cursor.EmitLdloc(17); // emits entity + cursor.EmitLdfld(f_EntityData_EntityID); + cursor.EmitStloc(19); // stores to entityID + // Second instance of call EntityID.ctor is referenced to ldloca.s 48 = entityID3 (in Triggers loop), we want to add entityID3 = trigger.EntityID` after it + // We also don't want to break mod parity by replacing instruction content + cursor.GotoNext(MoveType.After, i => i.MatchLdloc(47)); + cursor.Index++; + cursor.EmitLdloc(46); // emits trigger + cursor.EmitLdfld(f_EntityData_EntityID); + cursor.EmitStloc(48); // stores to entityID3 // Reset to apply entity patches cursor.Index = 0; diff --git a/Celeste.Mod.mm/Patches/LevelData.cs b/Celeste.Mod.mm/Patches/LevelData.cs index 734839082..0999fc310 100644 --- a/Celeste.Mod.mm/Patches/LevelData.cs +++ b/Celeste.Mod.mm/Patches/LevelData.cs @@ -10,10 +10,15 @@ using MonoMod.Utils; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using Celeste; namespace Celeste { public class patch_LevelData : LevelData { + [ThreadStatic] + internal static bool _isRegisteringTriggers; + public Vector2? DefaultSpawn; public patch_LevelData(BinaryPacker.Element data) : base(data) { @@ -24,6 +29,7 @@ public patch_LevelData(BinaryPacker.Element data) : base(data) { [PatchLevelDataBerryTracker] [PatchLevelDataDecalLoader] [PatchLevelDataSpawnpointLoader] + [PatchLevelDataTriggerIDOffset] public extern void orig_ctor(BinaryPacker.Element data); [MonoModConstructor] @@ -40,7 +46,7 @@ private void CheckForDefaultSpawn(BinaryPacker.Element spawn, Vector2 coords) { // Optimise the method [MonoModReplace] private EntityData CreateEntityData(BinaryPacker.Element entity) { - EntityData entityData = new() { + patch_EntityData entityData = new() { Name = entity.Name, Level = this }; @@ -51,6 +57,7 @@ private EntityData CreateEntityData(BinaryPacker.Element entity) { { case "id": entityData.ID = (int) value; + entityData.InitializeEntityID(this.Name); break; case "x": entityData.Position.X = Convert.ToSingle(value, CultureInfo.InvariantCulture); @@ -124,6 +131,9 @@ class PatchLevelDataDecalLoader : Attribute { } [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchLevelDataSpawnpointLoader))] class PatchLevelDataSpawnpointLoaderAttribute : Attribute { } + [MonoModCustomMethodAttribute(nameof(MonoModRules.PatchLevelDataTriggerIDOffset))] + class PatchLevelDataTriggerIDOffsetAttribute : Attribute { } + static partial class MonoModRules { public static void PatchLevelDataBerryTracker(MethodDefinition method, CustomAttribute attrib) { @@ -259,5 +269,33 @@ public static void PatchLevelDataSpawnpointLoader(ILContext context, CustomAttri cursor.Emit(OpCodes.Callvirt, m_LevelDataCheckForDefaultSpawn); cursor.Emit(OpCodes.Ldloc, v_spawnCoords); } + + public static void PatchLevelDataTriggerIDOffset(ILContext context, CustomAttribute attrib) { + FieldDefinition f_LevelData__isRegisteringTriggers = context.Method.DeclaringType.FindField("_isRegisteringTriggers"); + + ILCursor cursor = new(context); + ILLabel oldLeave = null, endOfIfTriggers = null; + + cursor.GotoNext(i => i.MatchStloc(10)); + cursor.GotoPrev(MoveType.After, i => i.MatchBrfalse(out endOfIfTriggers)); + cursor.EmitLdcI4(1); + cursor.EmitStsfld(f_LevelData__isRegisteringTriggers); + cursor.GotoNext(i => i.MatchLeave(out oldLeave)); + ILCursor clone = cursor.Clone(); + cursor.GotoNext(i => i.MatchLdloc(7), i => true, i => i.MatchLdstr("bgdecals")); + Instruction oldFinallyEnd = cursor.Next; + ILLabel newLeave = cursor.MarkLabel(); + cursor.EmitLdcI4(0); + Instruction newFinallyEnd = cursor.Prev; + cursor.EmitStsfld(f_LevelData__isRegisteringTriggers); + cursor.EmitBr(oldLeave); + + foreach (ExceptionHandler handler in context.Body.ExceptionHandlers.Where(handler => handler.HandlerEnd == oldFinallyEnd)) { + handler.HandlerEnd = newFinallyEnd; + break; + } + + clone.Next.Operand = newLeave; + } } }