using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using HarmonyLib; using UnityEngine; internal static class ColliderCastGuardPatch { internal static void Apply(Harmony harmony) { if (!Enabled) return; int installed = 0; foreach (var method in TargetMethods()) { try { Install(harmony, method); installed++; } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogError("[ColliderGuard] failed on " + Describe(method) + ": " + ex); } } } internal static void Install(Harmony harmony, MethodBase method) { var prefix = IsColliderFrame(method) ? new HarmonyMethod(typeof(ColliderCastGuardPatch), nameof(NoteCollider)) : null; harmony.Patch(method, prefix: prefix, finalizer: new HarmonyMethod(typeof(ColliderCastGuardPatch), nameof(Swallow))); } private static readonly bool _disabled = IsDisabled(); private static int _swallows; private static readonly Dictionary LastCollider = new Dictionary(); private static readonly HashSet ColliderFrames = new HashSet(); private static readonly string[] ColliderParameterTypes = new string[2] { "AOCLEIAJHKP", "AKCGGCOFMHJ" }; private static long _inspections; private static readonly string[] CastTypes = new string[3] { "BPJMFIFDPJO", "CBKENBPBBPK", "AOCLEIAJHKP" }; private const string RbexAssembly = "RecRoom.Rbex.Runtime"; private const string GameAssembly = "Assembly-CSharp"; private static readonly (string Assembly, string Type, string Method, int Args, string Role)[] Specs = new(string, string, string, int, string)[5] { ("RecRoom.Rbex.Runtime", "NFMLBBHIOHJ", "JAMGDJKMPGM", 2, "locomotion: attach/detach a collider child"), ("RecRoom.Rbex.Runtime", "NPDJILHGCBI", "JAMGDJKMPGM", 2, "locomotion: collider-child update (twin implementation)"), ("RecRoom.Rbex.Runtime", "MOBAMIJAHPJ", "JDAFCHGGCBG", 1, "teardown: unregister a collider from its body"), ("RecRoom.Rbex.Runtime", "CBKENBPBBPK", "JDIBOGNGEFK", 0, "teardown: body Dispose -> collider walk"), ("Assembly-CSharp", "RecRoom.Core.Locomotion.PlayerMovement", "AuthorityUpdateMovement", 2, "locomotion: authority tick (backstop for every caller of the cast)") }; internal static bool Enabled => !_disabled; [HarmonyPrefix] private static void NoteCollider(object __0, MethodBase __originalMethod) { try { if (__0 == null || __originalMethod == null || !ColliderFrames.Contains(__originalMethod)) { return; } long num = ++_inspections; if (num > 200 && num % 50 != 0L) { return; } string text = DescribeCollider(__0); if (text != null) { if (__originalMethod != null) { LastCollider[__originalMethod] = text; } } } catch { } } private static string DescribeCollider(object collider) { string text = null; try { CBKENBPBBPK val = (CBKENBPBBPK)((collider is CBKENBPBBPK) ? collider : null); if (val != null) { text = NameOf(val.JPBNIBGFKJO); } else { BPJMFIFDPJO val2 = (BPJMFIFDPJO)((collider is BPJMFIFDPJO) ? collider : null); if (val2 != null) { text = NameOf(val2.JPBNIBGFKJO); } } } catch { } string name; try { name = collider.GetType().Name; } catch { return null; } if (text != null) { return name + " on '" + text + "'"; } return name; } private static string NameOf(GameObject gameObject) { try { return gameObject == null ? null : gameObject.name; } catch { return null; } } private static bool IsDisabled() { try { string environmentVariable = Environment.GetEnvironmentVariable("RADICADE_COLLIDER_GUARD"); if (string.IsNullOrWhiteSpace(environmentVariable)) { return false; } bool result; switch (environmentVariable.Trim().ToLowerInvariant()) { case "0": case "off": case "false": case "no": result = true; break; default: result = false; break; } return result; } catch { return false; } } private static IEnumerable TargetMethods() { List list = new List(); (string, string, string, int, string)[] specs = Specs; for (int i = 0; i < specs.Length; i++) { (string, string, string, int, string) tuple = specs[i]; foreach (MethodBase item in Resolve(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4)) { list.Add(item); if (IsColliderFrame(item)) { ColliderFrames.Add(item); } } } if (list.Count == 0) { RadicadeEacBypassPlugin.LogSource.LogWarning((object)("[ColliderGuard] none of the " + Specs.Length + " known collider frames resolved - the physics cast failure is NOT guarded.")); } return list; } private static bool IsColliderFrame(MethodBase method) { try { ParameterInfo[] parameters = method.GetParameters(); if (parameters.Length == 0) { return false; } string name = parameters[0].ParameterType.Name; string[] colliderParameterTypes = ColliderParameterTypes; foreach (string b in colliderParameterTypes) { if (string.Equals(name, b, StringComparison.Ordinal)) { return true; } } } catch { } return false; } private static Type FindType(string assemblyName, string typeName) { try { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (string.Equals(assembly.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase)) { Type type = assembly.GetType(typeName, throwOnError: false); if (type != null) { return type; } } } catch { } } } catch { } try { return Type.GetType(typeName + ", " + assemblyName, throwOnError: false); } catch { } try { return Assembly.Load(new AssemblyName(assemblyName)).GetType(typeName, throwOnError: false); } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning((object)("[ColliderGuard] " + assemblyName + " could not be loaded: " + ex.Message)); return null; } } private static IEnumerable Resolve(string assemblyName, string typeName, string methodName, int args) { var type = FindType(assemblyName, typeName); if (type == null) yield break; foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) if (method.Name == methodName && method.GetParameters().Length == args) yield return method; } [HarmonyFinalizer] private static Exception Swallow(Exception __exception, MethodBase __originalMethod) { if (__exception == null || !Enabled) { return __exception; } if (!IsPhysicsCastFailure(__exception)) { return __exception; } int num = ++_swallows; if (num <= 5 || num % 100 == 0) { RadicadeEacBypassPlugin.LogSource.LogWarning((object)("[ColliderGuard] caught a collider cast failure in " + Describe(__originalMethod) + " (#" + num + ")" + DescribeArgument(__originalMethod) + ": " + __exception.GetType().Name + ": " + __exception.Message + " - the failed operation was aborted; remaining work in that method did not run.")); } return null; } private static string DescribeArgument(MethodBase method) { try { if (method != null && LastCollider.TryGetValue(method, out var value)) { return " collider=" + value; } } catch { } return string.Empty; } private static string Describe(MethodBase method) { try { return (method == null) ? "" : (method.DeclaringType?.ToString() + "." + method.Name); } catch { return ""; } } private static bool IsPhysicsCastFailure(Exception exception) { string text; try { text = exception.Message ?? string.Empty; } catch { return false; } if (text.IndexOf("Unable to cast object of type", StringComparison.OrdinalIgnoreCase) < 0) { return false; } string[] castTypes = CastTypes; foreach (string value in castTypes) { if (text.IndexOf(value, StringComparison.Ordinal) >= 0) { return true; } } return false; } }