Files
2026-10-16 23:33:13 -07:00

349 lines
8.3 KiB
C#

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<MethodBase, string> LastCollider = new Dictionary<MethodBase, string>();
private static readonly HashSet<MethodBase> ColliderFrames = new HashSet<MethodBase>();
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<MethodBase> TargetMethods()
{
List<MethodBase> list = new List<MethodBase>();
(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<MethodBase> 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) ? "<unknown frame>" : (method.DeclaringType?.ToString() + "." + method.Name);
}
catch
{
return "<unknown frame>";
}
}
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;
}
}