using System.Reflection; using System.Runtime.CompilerServices; using HarmonyLib; var harmony = new Harmony("radicade.collider.regression"); foreach (var name in new[] { "DisposeBody", "Move", "Attach", "Unrelated" }) ColliderCastGuardPatch.Install(harmony, typeof(Targets).GetMethod(name)); Targets.DisposeBody(); Targets.Move(1, 2); Targets.Attach(null, true); try { Targets.Unrelated(); throw new Exception("Unrelated exception was swallowed"); } catch (InvalidOperationException ex) when (ex.Message == "unrelated") { } var dispose = Harmony.GetPatchInfo(typeof(Targets).GetMethod("DisposeBody")); var move = Harmony.GetPatchInfo(typeof(Targets).GetMethod("Move")); var attach = Harmony.GetPatchInfo(typeof(Targets).GetMethod("Attach")); if (dispose.Prefixes.Count != 0 || move.Prefixes.Count != 0 || attach.Prefixes.Count != 1) throw new Exception("Prefix was bound to an incompatible signature"); if (Targets.Calls != 3) throw new Exception("Original methods did not execute"); Console.WriteLine("PASS: zero-argument disposal, movement, collider prefix, cast handling, unrelated exception propagation"); var tool = new Tool(); var physical = new UnityEngine.Collider { Owner = tool, gameObject = new() { layer = 13 } }; var trigger = new UnityEngine.Collider { Owner = tool, isTrigger = true, gameObject = new() { layer = 13 } }; var nested = new UnityEngine.Collider { Owner = new Tool(), gameObject = new() { layer = 13 } }; var blocker = new UnityEngine.Collider { Owner = tool, gameObject = new() { layer = 14 } }; tool.Colliders = new[] { physical, trigger, nested, blocker }; ToolCollisionLayerRestore.Restore(tool); if (physical.gameObject.layer != 10 || trigger.gameObject.layer != 13 || nested.gameObject.layer != 13 || blocker.gameObject.layer != 14) throw new Exception("Collision-layer restoration changed the wrong colliders"); physical.gameObject.layer = 13; tool.MEDCLKFANNC = true; ToolCollisionLayerRestore.Restore(tool); if (physical.gameObject.layer != 13) throw new Exception("Collision-layer lock was ignored"); Console.WriteLine("PASS: dropped-item layer reset; triggers, nested tools, blockers and locks preserved"); public static class Targets { public static int Calls; [MethodImpl(MethodImplOptions.NoInlining)] public static void DisposeBody() { Calls++; throw new InvalidCastException("Unable to cast object of type CBKENBPBBPK"); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Move(float a, float b) { Calls++; } [MethodImpl(MethodImplOptions.NoInlining)] public static void Attach(AOCLEIAJHKP collider, bool enabled) { Calls++; } [MethodImpl(MethodImplOptions.NoInlining)] public static void Unrelated() => throw new InvalidOperationException("unrelated"); } // Minimal game stand-ins: these tests exercise real Harmony registration without launching Unity. public class AOCLEIAJHKP { } public class CBKENBPBBPK { public UnityEngine.GameObject JPBNIBGFKJO => null; } public class BPJMFIFDPJO { public UnityEngine.GameObject JPBNIBGFKJO => null; } namespace UnityEngine { public class GameObject { public string name = "test"; public int layer; } public class Collider { public bool isTrigger; public GameObject gameObject; public Tool Owner; public T GetComponentInParent() where T : class => Owner as T; } } public class Tool { public bool MEDCLKFANNC; public int EHNBBIAIJIK = 10; public int HeldCollisionLayer = 13, EquippedCollisionLayer = 18, AttachedCollisionLayer = 23; public UnityEngine.GameObject gameObject = new(); public UnityEngine.Collider[] Colliders = Array.Empty(); public T[] GetComponentsInChildren(bool includeInactive) => Colliders.Cast().ToArray(); } internal static class RadicadeEacBypassPlugin { internal static readonly TestLog LogSource = new(); } internal class TestLog { public void LogInfo(object value) { } public void LogWarning(object value) { } public void LogError(object value) => throw new Exception(value.ToString()); }