Radicade Public Code

This commit is contained in:
JogmansonReal
2026-10-16 23:33:13 -07:00
commit 55175acdf6
832 changed files with 363261 additions and 0 deletions
@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using TMPro;
using UnityEngine.UI;
internal static class ClientTextRewrites
{
private static readonly (string From, string To)[] Rules =
{
("https://recroom.zendesk.com/home", "https://support.radicade.app/"),
("recroom.zendesk.com", "support.radicade.app"),
("https://www.recroom.com/privacy-policy/", "https://radicade.app/privacy-policy/"),
("https://recroom.com/terms-of-service", "https://radicade.app/terms-of-service/"),
("https://www.recroom.com/community/", "https://radicade.app/community/"),
};
private static readonly (string From, string To)[] ExactRules =
{
("This account has been locked for security reasons. Contact us at recroom.zendesk.com.",
LockedAccountSentence),
("This account has been locked for security reasons. Contact us at support.radicade.app.",
LockedAccountSentence),
};
private const string GenericLoginFailure = "Login failed. Please try again.";
private const string LockedAccountErrorCode = "ACCOUNT_PERMALOCKED";
internal const string LockedAccountSentence =
"This account has been locked for security reasons. Contact us at https://support.radicade.app.";
private static bool _rewriting;
internal static void Apply(Harmony harmony)
{
var mode = AsciiLower((Environment.GetEnvironmentVariable("RADICADE_CLIENT_TEXT") ?? string.Empty).Trim());
if (mode == "0" || mode == "off")
{
return;
}
bool onEnable = mode.Length == 0 || mode == "1" || mode == "all" || mode == "onenable";
bool onSet = mode.Length == 0 || mode == "1" || mode == "all" || mode == "settext";
var seen = new HashSet<MethodBase>();
var names = new List<string>();
if (onEnable)
{
Hook(harmony, seen, names, AccessTools.Method(typeof(TextMeshProUGUI), "OnEnable"), nameof(GraphicEvent), "TextMeshProUGUI.OnEnable");
Hook(harmony, seen, names, AccessTools.Method(typeof(Text), "OnEnable"), nameof(GraphicEvent), "UI.Text.OnEnable");
}
if (onSet)
{
Hook(harmony, seen, names, AccessTools.Method(typeof(TMP_Text), "set_text"), nameof(GraphicEvent), "TMP_Text.set_text");
Hook(harmony, seen, names, AccessTools.Method(typeof(TMP_Text), "SetTextInternal"), nameof(GraphicEvent), "TMP_Text.SetTextInternal");
Hook(harmony, seen, names, AccessTools.Method(typeof(Text), "set_text"), nameof(GraphicEvent), "UI.Text.set_text");
}
if (names.Count == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] no text hooks could be installed (mode=" + (mode.Length == 0 ? "all" : mode) + "); the watch UI keeps its original text.");
}
private static void Hook(Harmony harmony, HashSet<MethodBase> seen, List<string> names,
MethodBase target, string handler, string label)
{
if (target == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] hook target missing: " + label);
return;
}
if (!seen.Add(target)) return;
try
{
var method = new HarmonyMethod(typeof(ClientTextRewrites)
.GetMethod(handler, BindingFlags.Static | BindingFlags.NonPublic));
harmony.Patch(target, postfix: method);
names.Add(label);
}
catch (Exception ex)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[ClientText] could not hook " + label + ": "
+ ex.GetType().Name + ": " + ex.Message);
}
}
private static void GraphicEvent(object __instance)
{
try
{
if (__instance is not Il2CppObjectBase obj) return;
if (obj.TryCast<TMP_Text>() is { } tmp) { Fix(tmp); return; }
if (obj.TryCast<Text>() is { } txt) { Fix(txt); }
}
catch { }
}
private static void Fix(TMP_Text instance)
{
try
{
if (instance == null || _rewriting) return;
var current = instance.text;
var rewritten = Rewrite(current);
if (ReferenceEquals(current, rewritten) || string.Equals(rewritten, current, StringComparison.Ordinal)) return;
_rewriting = true;
try { instance.text = rewritten; } finally { _rewriting = false; }
}
catch { }
}
private static void Fix(Text instance)
{
try
{
if (instance == null || _rewriting) return;
var current = instance.text;
var rewritten = Rewrite(current);
if (ReferenceEquals(current, rewritten) || string.Equals(rewritten, current, StringComparison.Ordinal)) return;
_rewriting = true;
try { instance.text = rewritten; } finally { _rewriting = false; }
}
catch { }
}
private static string Rewrite(string value)
{
if (string.IsNullOrEmpty(value)) return value;
if (HubSocketAuth.LastLoginRefusalCode == LockedAccountErrorCode &&
IndexOfAsciiIgnoreCase(value, GenericLoginFailure, 0) >= 0)
{
var locked = Replace(value, GenericLoginFailure, LockedAccountSentence);
return locked;
}
if (IndexOfAsciiIgnoreCase(value, "recroom", 0) < 0 &&
IndexOfAsciiIgnoreCase(value, "radicade", 0) < 0) return value;
var result = value;
foreach (var (from, to) in ExactRules)
if (IndexOfAsciiIgnoreCase(result, from, 0) >= 0)
result = Replace(result, from, to);
foreach (var (from, to) in Rules)
if (IndexOfAsciiIgnoreCase(result, from, 0) >= 0)
result = Replace(result, from, to);
return result;
}
private static string Replace(string source, string from, string to)
{
var sb = new System.Text.StringBuilder(source.Length);
var i = 0;
while (i < source.Length)
{
var hit = IndexOfAsciiIgnoreCase(source, from, i);
if (hit < 0) { sb.Append(source, i, source.Length - i); break; }
sb.Append(source, i, hit - i).Append(to);
i = hit + from.Length;
}
return sb.ToString();
}
private static int IndexOfAsciiIgnoreCase(string haystack, string needle, int start)
{
if (haystack == null || needle == null) return -1;
var n = needle.Length;
var h = haystack.Length;
if (n == 0) return start <= h ? start : -1;
if (n > h) return -1;
var last = h - n;
for (var i = (start < 0 ? 0 : start); i <= last; i++)
{
var j = 0;
while (j < n && LowerAscii(haystack[i + j]) == LowerAscii(needle[j])) j++;
if (j == n) return i;
}
return -1;
}
private static char LowerAscii(char c) => c >= 'A' && c <= 'Z' ? (char)(c + 32) : c;
private static string AsciiLower(string s)
{
for (var i = 0; i < s.Length; i++)
if (s[i] >= 'A' && s[i] <= 'Z')
return string.Create(s.Length, s, (span, state) =>
{
for (var k = 0; k < state.Length; k++) span[k] = LowerAscii(state[k]);
});
return s;
}
}
@@ -0,0 +1,348 @@
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;
}
}
+47
View File
@@ -0,0 +1,47 @@
using System;
using System.Threading;
using HarmonyLib;
internal static class ConnectionGate
{
private const int WaitMilliseconds = 15000;
private static readonly ManualResetEventSlim Opened = new ManualResetEventSlim(false);
private static int _holds;
internal static void Install(Harmony harmony)
{
harmony.PatchAll(typeof(ConnectionGatePatch));
}
internal static void Open()
{
Opened.Set();
}
internal static void Hold()
{
if (Opened.IsSet) return;
var count = Interlocked.Increment(ref _holds);
RadicadeEacBypassPlugin.LogSource.LogWarning("[Patch] a server request arrived before the patch list finished; holding it until it does (hold #"
+ count + ").");
if (Opened.Wait(WaitMilliseconds)) return;
RadicadeEacBypassPlugin.LogSource.LogError("[Patch] the patch list did not finish within "
+ (WaitMilliseconds / 1000) + "s; releasing this request and every later one so the client is not held forever.");
Opened.Set();
}
}
[HarmonyPatch(typeof(BestHTTP.HTTPManager), "SendRequest", new[] { typeof(BestHTTP.HTTPRequest) })]
internal static class ConnectionGatePatch
{
[HarmonyPriority(Priority.First)]
internal static void Prefix()
{
ConnectionGate.Hold();
}
}
+191
View File
@@ -0,0 +1,191 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
internal static class CrashAddressMap
{
private const long DefaultTarget = 0x29a79ec;
internal static bool Enabled => !_disabled;
internal static long TargetRva => _target;
private static readonly bool _disabled = IsDisabled();
private static readonly long _target = ReadTarget();
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_ADDR_MAP");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
private static long ReadTarget()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_ADDR_TARGET");
if (string.IsNullOrWhiteSpace(raw)) return DefaultTarget;
raw = raw.Trim();
if (raw.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) raw = raw.Substring(2);
return long.TryParse(raw, System.Globalization.NumberStyles.HexNumber,
System.Globalization.CultureInfo.InvariantCulture, out var value) ? value : DefaultTarget;
}
catch { return DefaultTarget; }
}
internal static void Start()
{
if (!Enabled) return;
var thread = new System.Threading.Thread(Run) { IsBackground = true, Name = "RadicadeAddrMap" };
thread.Start();
}
private static void Run()
{
try { System.Threading.Thread.Sleep(5000); } catch { }
var log = RadicadeEacBypassPlugin.LogSource;
IntPtr baseAddress = IntPtr.Zero;
var moduleSize = 0L;
try
{
using (var self = System.Diagnostics.Process.GetCurrentProcess())
{
foreach (System.Diagnostics.ProcessModule module in self.Modules)
{
if (!string.Equals(module.ModuleName, "GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) continue;
baseAddress = module.BaseAddress;
moduleSize = module.ModuleMemorySize;
break;
}
}
}
catch (Exception ex)
{
log.LogError("[AddrMap] could not locate GameAssembly.dll: " + ex.Message);
return;
}
if (baseAddress == IntPtr.Zero || moduleSize <= 0)
{
log.LogError("[AddrMap] GameAssembly.dll is not loaded - nothing to map");
return;
}
var gameRoot = Path.GetDirectoryName(UnityEngine.Application.dataPath) ?? ".";
var path = Path.Combine(gameRoot, "BepInEx", "radicade-addr-map.txt");
var baseValue = baseAddress.ToInt64();
var mapped = 0;
var images = 0;
var unmappable = 0;
var bestBelow = -1L;
string bestBelowName = null;
long bestAbove = -1;
string bestAboveName = null;
var failures = 0;
try
{
using (var writer = new StreamWriter(path, false))
{
writer.WriteLine("# il2cpp RVA -> method map, written by RadicadeAugustClient (run 24)");
writer.WriteLine("# GameAssembly.dll base 0x" + baseValue.ToString("x") + ", mapped size 0x" + moduleSize.ToString("x"));
writer.WriteLine("# rva<TAB>image<TAB>Namespace.Class::Method - rva is relative to the base above");
unsafe
{
var domain = Il2CppInterop.Runtime.IL2CPP.il2cpp_domain_get();
if (domain == IntPtr.Zero)
{
log.LogError("[AddrMap] no il2cpp domain; nothing to map");
return;
}
uint assemblyCount = 0;
var assemblies = Il2CppInterop.Runtime.IL2CPP.il2cpp_domain_get_assemblies(domain, ref assemblyCount);
for (uint a = 0; a < assemblyCount; a++)
{
var image = Il2CppInterop.Runtime.IL2CPP.il2cpp_assembly_get_image(assemblies[a]);
if (image == IntPtr.Zero) continue;
images++;
string imageName;
try { imageName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_name(image)) ?? "?"; }
catch { imageName = "?"; }
var classCount = Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_class_count(image);
for (uint c = 0; c < classCount; c++)
{
var klass = Il2CppInterop.Runtime.IL2CPP.il2cpp_image_get_class(image, c);
if (klass == IntPtr.Zero) continue;
string className;
try
{
var namespaze = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_namespace(klass));
var name = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_name(klass));
className = string.IsNullOrEmpty(namespaze) ? name : namespaze + "." + name;
}
catch { className = "?"; failures++; }
var iterator = IntPtr.Zero;
IntPtr methodInfo;
while ((methodInfo = Il2CppInterop.Runtime.IL2CPP.il2cpp_class_get_methods(klass, ref iterator)) != IntPtr.Zero)
{
long entry;
try { entry = Marshal.ReadIntPtr(methodInfo).ToInt64(); }
catch { entry = 0; }
if (entry <= baseValue || entry >= baseValue + moduleSize)
{
unmappable++;
continue;
}
var rva = entry - baseValue;
string methodName;
try { methodName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_method_get_name(methodInfo)) ?? "?"; }
catch { methodName = "?"; failures++; }
writer.WriteLine("0x" + rva.ToString("x") + "\t" + imageName + "\t" + className + "::" + methodName);
mapped++;
var full = className + "::" + methodName;
if (rva <= _target && rva > bestBelow) { bestBelow = rva; bestBelowName = full; }
if (rva > _target && (bestAbove < 0 || rva < bestAbove)) { bestAbove = rva; bestAboveName = full; }
}
}
}
}
}
}
catch (Exception ex)
{
log.LogError("[AddrMap] pass failed after " + mapped + " methods: " + ex);
return;
}
if (bestBelow < 0)
{
log.LogWarning("[AddrMap] no method starts below the target 0x" + _target.ToString("x")
+ " - the crash is in code no managed method owns (il2cpp runtime or native plugin)."
+ (bestAboveName != null ? " Nearest above: 0x" + bestAbove.ToString("x") + " " + bestAboveName : string.Empty));
return;
}
var span = bestAbove > bestBelow ? bestAbove - bestBelow : -1;
log.LogWarning("[AddrMap] crash target 0x" + _target.ToString("x") + " -> containing method 0x" + bestBelow.ToString("x")
+ " " + bestBelowName + " (+0x" + (_target - bestBelow).ToString("x") + " into it)"
+ (span > 0 ? "; next method 0x" + bestAbove.ToString("x") + " " + bestAboveName + " (span 0x" + span.ToString("x") + ")" : string.Empty)
+ (failures > 0 ? "; " + failures + " name reads failed" : string.Empty));
}
}
@@ -0,0 +1,53 @@
using HarmonyLib;
using Il2CppInterop.Runtime;
using RRUI;
using RRUI.Data;
using UnityEngine;
internal static class DynamicUiPrefabRepair
{
private static bool reportedMissing;
internal static void Apply(Harmony harmony)
{
harmony.Patch(AccessTools.PropertyGetter(typeof(DynamicUIConfig), "Instance"),
postfix: new HarmonyMethod(typeof(DynamicUiPrefabRepair), nameof(Repair)));
}
private static void Repair(DynamicUIConfig __result)
{
if (__result == null) return;
var standard = __result.DynamicFloatPrefab_MultiSelectConfig;
var pro = __result.DynamicProUXFloatPrefab_MultiSelectConfig;
var fallback = Compatible(standard) ? standard : Compatible(pro) ? pro : null;
if (fallback == null)
{
if (!reportedMissing)
{
reportedMissing = true;
RadicadeEacBypassPlugin.LogSource.LogWarning(
"[DynamicUI] No compatible multi-select float prefab is available; leaving the missing mapping visible.");
}
return;
}
_ = __result.LandscapePrefabsByType;
_ = __result.PortraitPrefabsByType;
_ = __result.ProUXPrefabsByType;
RepairMap(__result.landscapePrefabsByType, fallback, "landscape");
RepairMap(__result.portraitPrefabsByType, fallback, "portrait");
RepairMap(__result.proUXPrefabsByType, fallback, "ProUX");
}
private static bool Compatible(GameObject prefab) => prefab != null &&
prefab.GetComponentInChildren<DynamicFloatInputFieldModelController_MultiSelectConfig>(true) != null;
private static void RepairMap(Il2CppSystem.Collections.Generic.Dictionary<Il2CppSystem.Type, GameObject> map,
GameObject fallback, string layout)
{
if (map == null) return;
var key = Il2CppType.Of<JNHHEBGEAMP>();
if (map.ContainsKey(key) && map[key] != null) return;
map[key] = fallback;
}
}
@@ -0,0 +1,42 @@
using HarmonyLib;
internal static class InviteOnlineFriendsPatch
{
internal static void Apply(Harmony harmony)
{
harmony.Patch(AccessTools.PropertyGetter(typeof(ECFHJOKNJIJ), "OHKPFEMEPAE"),
prefix: new HarmonyMethod(typeof(InviteOnlineFriendsPatch), nameof(EnableDialog)));
harmony.Patch(AccessTools.PropertyGetter(typeof(ECFHJOKNJIJ), "EBCNNMAJGBN"),
prefix: new HarmonyMethod(typeof(InviteOnlineFriendsPatch), nameof(IncomingDialog)));
}
private static bool EnableDialog(ECFHJOKNJIJ __instance, ref bool __result)
{
return ReadServerOverride(__instance, "Radicade.Invites.ShowOnlineFriendsBeforeJoining", ref __result);
}
private static bool IncomingDialog(ECFHJOKNJIJ __instance, ref bool __result)
{
return ReadServerOverride(__instance, "Radicade.Invites.UseRRUIDialog", ref __result);
}
private static readonly System.Collections.Generic.HashSet<string> logged = new();
private static bool ReadServerOverride(ECFHJOKNJIJ instance, string key, ref bool result)
{
try
{
var configs = instance.CCNKOPKDIJI;
if (configs == null || !configs.FNBNNDMHFCK) return true;
if (!bool.TryParse(configs.KFNCGHMLMAI(key), out var value)) return true;
result = value;
return false;
}
catch (System.Exception ex)
{
if (logged.Add(key + ":error"))
RadicadeEacBypassPlugin.LogSource.LogWarning("[InviteUI] config lookup failed: " + ex.Message);
return true;
}
}
}
@@ -0,0 +1,461 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
internal static class KeepsakeToolPatch
{
private static readonly Type Palette =
typeof(RecRoom.AGUI.ToolUIs.MakerPenPaletteMainScreen);
private static readonly Type Registry = typeof(PMJEHEPKBJL);
private static readonly Type ToolTypeEnum = typeof(AOCJJLANBMH);
private static readonly Type ToolConfig = typeof(RuntimeSpawnableToolConfig);
private static readonly Dictionary<string, int> RepeatedLines = new Dictionary<string, int>();
private static string Mode
=> (Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_TOOL")
?? (RadicadeEacBypassPlugin.KeepsakeToolEnabled?.Value == true ? "on" : "skip")).Trim();
private static bool Requested => Mode.ToLowerInvariant() switch
{
"on" or "true" or "1" or "yes" or "enable" or "enabled" => true,
"off" or "false" or "0" => true,
_ => false,
};
internal static bool Forcing => Mode.ToLowerInvariant() switch
{
"on" or "true" or "1" or "yes" or "enable" or "enabled" => true,
_ => false,
};
internal static void Apply(Harmony harmony)
{
if (!Requested)
{
return;
}
if (Palette == null || Registry == null || ToolTypeEnum == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
"[Keepsake] palette/registry/tool-type type not found (" +
$"palette={Palette?.FullName ?? "null"}, registry={Registry?.FullName ?? "null"}, " +
$"toolType={ToolTypeEnum?.FullName ?? "null"}) - keepsake patch skipped");
return;
}
Patch(harmony, Registry, "JBAINDGLMBO", new[] { ToolTypeEnum }, null, nameof(ConfigPostfix));
PatchByName(harmony, Registry, "LPBDCDIMBEN", nameof(PredicatePostfix));
PatchByNameOnFamily(harmony, Palette, "LKGMIHJOIID", nameof(PredicatePostfix));
PatchByNameOnFamily(harmony, Palette, "OLKPEGJNJGG", nameof(ConfigFilterPostfix));
PatchByNameOnFamily(harmony, Palette, "IBHJBFJMBFB", nameof(ConfigFilterPostfix));
Patch(harmony, Palette, "OnUsedPaletteItem", null, nameof(UsedPalettePrefix), null);
Audit(harmony);
}
private static void Audit(Harmony harmony)
{
var service = AccessTools.TypeByName("AMAOGLDGNLL");
foreach (var name in new[]
{
"MJDEEABFHPJ", "NCAKOLMAAEA", "CAFCLGFGKAO", "KHPJGNADGEE", "NGJMABOMHHB",
"FCOMKMPDEIO", "AEHHFFIKLG"
})
PatchByName(harmony, service, name, nameof(CallPostfix), 2);
if (!string.Equals(Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_AUDIT"), "1",
StringComparison.Ordinal)) return;
Log("[Keepsake] RADICADE_KEEPSAKE_AUDIT=1: instrumenting the room/progression models too");
foreach (var typeName in new[] { "RRUI.Data.RoomModel", "RRUI.Data.RoomListModel", "RRUI.Data.ProgressionEventsModel" })
{
var model = AccessTools.TypeByName(typeName);
foreach (var name in new[]
{
"IsKeepsakeRoom", "GetCachedAreAllKeepsakesCollected",
"GetTotalKeepsakeCountPerCategoryForRoom", "GetUncollectedKeepsakeCountPerCategoryForRoom",
"GetTotalKeepsakeCountForRoom", "GetUncollectedKeepsakeCountForRoom",
"IsKeepsakeRoomListUpsellItemOwned", "AHACLFABDEO", "CJPFGOBHGBB"
})
PatchByName(harmony, model, name, nameof(ModelPostfix), 4);
}
}
internal static object KeepsakeMember()
{
try
{
return ToolTypeEnum == null ? null : Enum.Parse(ToolTypeEnum, "KEEPSAKE");
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[Keepsake] KEEPSAKE enum member missing: " + e.Message);
return null;
}
}
internal static bool IsKeepsake(object toolType)
=> toolType != null && KeepsakeMember()?.Equals(toolType) == true;
internal static void Log(string message)
=> RadicadeEacBypassPlugin.LogSource.LogInfo(message);
private static void LogOnce(string message, int limit = 3)
{
var seen = RepeatedLines.TryGetValue(message, out var count) ? count + 1 : 1;
RepeatedLines[message] = seen;
if (seen <= limit) Log(message);
else if (seen == limit + 1) Log($"[Keepsake] (suppressing further copies of: {message})");
}
private static void ConfigPostfix(object[] __args, RuntimeSpawnableToolConfig __result)
{
var toolType = __args.Length > 0 ? __args[0] : null;
var keepsake = IsKeepsake(toolType);
if (!keepsake && !IsRestricted(__result)) return;
if (!keepsake || !Forcing) return;
var allowed = Field(__result, "spawnableToolTypesAllowedInRooms2");
if (Contains(allowed, KeepsakeMember())) return;
AddToCollection(allowed, KeepsakeMember());
}
private static void PredicatePostfix(MethodBase __originalMethod, object[] __args, ref bool __result)
{
var toolType = FirstToolType(__args);
var keepsake = IsKeepsake(toolType);
if (keepsake && Forcing && !__result)
{
__result = true;
}
}
private static void CallPostfix(MethodBase __originalMethod, object[] __args, object __result)
{
}
private static void ModelPostfix(MethodBase __originalMethod, object[] __args, object __result)
{
}
private static string DescribeResult(object result)
{
if (result == null) return "(null)";
var type = result.GetType();
try
{
if (type == typeof(bool) || type == typeof(int) || type == typeof(long))
return result.ToString();
var status = AccessTools.Property(type, "IsCompleted")?.GetValue(result) as bool?;
if (status == true)
{
var value = AccessTools.Property(type, "Result")?.GetValue(result);
return $"{type.Name}(completed) = {value}";
}
if (status == false) return $"{type.Name}(pending)";
}
catch
{
}
return type.Name;
}
private static void ConfigFilterPostfix(MethodBase __originalMethod, object[] __args, ref bool __result)
{
var config = __args.Length > 0 ? __args[0] : null;
var name = Field(config, "UniqueName")?.ToString() ?? "(unnamed tool)";
var keepsake = string.Equals(name, "KEEPSAKE", StringComparison.OrdinalIgnoreCase);
if (!keepsake) return;
if (!Forcing || __result) return;
__result = true;
}
private static void UsedPalettePrefix(object[] __args)
{
}
private static object FirstToolType(object[] args)
{
if (args == null) return null;
foreach (var arg in args)
{
if (arg == null) continue;
if (ToolTypeEnum.IsInstanceOfType(arg) || arg.GetType() == ToolTypeEnum) return arg;
}
return null;
}
private static bool IsRestricted(object config)
=> Field(config, "AvailableInMakerPen") is bool makerPen && !makerPen;
private static string Frames(int take = 10)
{
try
{
var frames = new StackTrace(true).GetFrames();
var sb = new StringBuilder();
var taken = 0;
foreach (var frame in frames)
{
var method = frame.GetMethod();
var owner = method?.DeclaringType?.Name ?? "?";
if (owner.Contains("KeepsakeToolPatch") || owner.Contains("Harmony")) continue;
sb.Append(owner).Append('.').Append(method?.Name ?? "?").Append(" <- ");
if (++taken >= take) break;
}
return sb.Length == 0 ? "(no frames)" : sb.ToString().TrimEnd(' ', '<', '-');
}
catch (Exception e)
{
return "(stack failed: " + e.GetType().Name + ")";
}
}
private static string DescribeList(object collection)
{
if (collection == null) return "(null)";
var type = collection.GetType();
var count = 0;
var keepsake = false;
var names = new StringBuilder();
try
{
var countValue = AccessTools.Property(type, "Count")?.GetValue(collection);
count = countValue is int n ? n : 0;
if (count == 0) return "0 item(s) []";
var indexer = type.GetProperty("Item");
for (var i = 0; i < count && i < 12; i++)
{
var item = indexer?.GetValue(collection, new object[] { i });
if (IsKeepsake(item)) keepsake = true;
names.Append(item).Append(',');
}
}
catch (Exception e)
{
return $"(list read failed: {e.GetType().Name}: {e.Message})";
}
return $"{count} item(s) [{names.ToString().TrimEnd(',')}]" + (keepsake ? " KEEPSAKE present" : "");
}
private static bool Contains(object collection, object item)
{
if (collection == null || item == null) return false;
try
{
var contains = AccessTools.Method(collection.GetType(), "Contains");
return contains?.Invoke(collection, new[] { item }) is true;
}
catch
{
return false;
}
}
private static bool AddToCollection(object collection, object item)
{
if (collection == null || item == null) return false;
try
{
var type = collection.GetType();
MethodInfo add = null;
foreach (var candidate in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (candidate.Name != "Add" || candidate.GetParameters().Length != 1) continue;
add = candidate;
break;
}
if (add == null) return false;
add.Invoke(collection, new[] { item });
return true;
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] could not add to {collection.GetType().Name}: {e.GetType().Name}: {e.Message}");
return false;
}
}
private static string Describe(object config)
{
if (config == null) return "(null config)";
var sb = new StringBuilder(config.GetType().Name).Append('{');
foreach (var name in new[]
{
"UniqueName", "AvailableInMakerPen", "AvailableToDevsOnly", "IsBetaItem",
"AvailableInSpawner", "CanSaveIntoInventions", "LimitInventionSaveForJuniors"
})
{
var value = Field(config, name);
if (value != null) sb.Append(name).Append('=').Append(value).Append(' ');
}
var allowed = Field(config, "spawnableToolTypesAllowedInRooms2");
if (allowed != null)
sb.Append("allowedInRooms=").Append(DescribeList(allowed)).Append(' ');
return sb.Append('}').ToString();
}
private static object Field(object instance, string name)
{
if (instance == null) return null;
try
{
var type = instance.GetType();
var property = AccessTools.Property(type, name);
if (property != null) return property.GetValue(instance);
return AccessTools.Field(type, name)?.GetValue(instance);
}
catch
{
return null;
}
}
private static void PatchByNameOnFamily(Harmony harmony, Type root, string method, string postfix)
{
var patched = 0;
foreach (var type in Family(root))
{
foreach (var found in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static | BindingFlags.Instance))
{
if (found.Name != method) continue;
if (Bind(harmony, found, null, postfix)) patched++;
}
}
if (patched == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] no method named {method} on {root.Name} or its nested types - not instrumented");
}
private static void PatchByName(Harmony harmony, Type target, string method, string postfix,
int maxArguments = 1)
{
if (target == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] type for {method} not found - not instrumented");
return;
}
var patched = 0;
foreach (var found in target.GetMethods(BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static | BindingFlags.Instance))
{
if (found.Name != method) continue;
var parameters = found.GetParameters();
if (parameters.Length > maxArguments) continue;
if (parameters.Any(p => p.ParameterType.IsByRef || p.IsOut || p.ParameterType.IsPointer)) continue;
if (maxArguments == 1 && (parameters.Length != 1 ||
(!ToolTypeEnum.IsAssignableFrom(parameters[0].ParameterType)
&& parameters[0].ParameterType != ToolTypeEnum))) continue;
if (Bind(harmony, found, null, postfix)) patched++;
}
}
private static IEnumerable<Type> Family(Type root)
{
yield return root;
Type[] nested;
try
{
nested = root.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
}
catch
{
yield break;
}
foreach (var type in nested)
foreach (var deeper in Family(type))
yield return deeper;
}
private static void Patch(Harmony harmony, Type target, string method, Type[] args,
string prefix, string postfix)
{
if (target == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning($"[Keepsake] type for {method} not found - not instrumented");
return;
}
try
{
var found = args == null
? AccessTools.Method(target, method)
: AccessTools.Method(target, method, args);
if (found == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] {target.Name}.{method} not found - not instrumented");
return;
}
Bind(harmony, found, prefix, postfix);
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] FAILED to instrument {target.Name}.{method}: {e.GetType().Name}: {e.Message}");
}
}
private static bool Bind(Harmony harmony, MethodBase found, string prefix, string postfix)
{
try
{
var self = typeof(KeepsakeToolPatch);
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
harmony.Patch(found,
prefix: prefix == null ? null : new HarmonyMethod(self.GetMethod(prefix, flags)),
postfix: postfix == null ? null : new HarmonyMethod(self.GetMethod(postfix, flags)));
return true;
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[Keepsake] FAILED to instrument {found.DeclaringType?.Name}.{found.Name}: " +
$"{e.GetType().Name}: {e.Message}");
return false;
}
}
}
+205
View File
@@ -0,0 +1,205 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using UnityEngine;
internal static class LogFloodGate
{
internal const double WindowSeconds = 30.0;
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static readonly Dictionary<string, Entry> Seen = new Dictionary<string, Entry>();
private static long _totalSuppressed;
private static bool _selfTestPending;
private static int _selfTestFired;
private sealed class Entry
{
public double WindowStart;
public int Suppressed;
public int ReportedAt;
}
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_LOG_GATE");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
internal static void Apply(Harmony harmony)
{
try
{
var target = AccessTools.Method(typeof(ODGGOALOLEG), "LogException",
new[] { typeof(Il2CppSystem.Exception), typeof(UnityEngine.Object) });
if (target == null)
{
Warn("[LogGate] ODGGOALOLEG.LogException(Il2CppSystem.Exception, UnityEngine.Object) not found - " +
"not armed; the per-frame exception flood stays as-is.");
return;
}
var prefix = AccessTools.Method(typeof(LogFloodGate), nameof(Prefix));
if (prefix == null)
{
Warn("[LogGate] patch method Prefix not found - not armed.");
return;
}
harmony.Patch(target, prefix: new HarmonyMethod(prefix));
}
catch (Exception e)
{
Warn($"[LogGate] failed to arm ({e.GetType().Name}: {e.Message}); the exception flood stays as-is.");
}
}
[HarmonyPrefix]
internal static bool Prefix(Il2CppSystem.Exception __0)
{
if (!Enabled) return true;
try
{
if (_selfTestPending && System.Threading.Interlocked.Exchange(ref _selfTestFired, 1) == 0)
{
_selfTestPending = false;
RunSelfTest();
}
var signature = Signature(__0);
if (signature == null) return true;
var now = Time.realtimeSinceStartup;
lock (Seen)
{
if (Seen.Count > 512) Evict(now);
if (!Seen.TryGetValue(signature, out var entry))
{
Seen[signature] = new Entry { WindowStart = now };
return true;
}
if (now - entry.WindowStart < WindowSeconds)
{
entry.Suppressed++;
_totalSuppressed++;
if (entry.Suppressed == 1)
Warn($"[LogGate] repeats of this exception are now suppressed for {WindowSeconds:0}s at a time: {signature}");
else if (entry.Suppressed - entry.ReportedAt >= 500)
{
entry.ReportedAt = entry.Suppressed;
Warn($"[LogGate] {entry.Suppressed} identical repeats of this exception suppressed so far " +
$"(session total {_totalSuppressed}): {signature}");
}
return false;
}
var dropped = entry.Suppressed;
entry.WindowStart = now;
entry.Suppressed = 0;
entry.ReportedAt = 0;
if (dropped > 0)
Warn($"[LogGate] {dropped} identical repeats were collapsed in the previous {WindowSeconds:0}s window " +
$"(session total {_totalSuppressed}); logging this occurrence in full: {signature}");
return true;
}
}
catch
{
return true;
}
}
private static void Evict(double now)
{
var stale = new List<string>();
foreach (var pair in Seen)
if (now - pair.Value.WindowStart > 600.0)
stale.Add(pair.Key);
foreach (var key in stale)
Seen.Remove(key);
}
private static string Signature(Il2CppSystem.Exception exception)
{
string type, message, stack;
try { type = exception.GetType().Name; } catch { type = "Exception"; }
try { message = exception.Message; } catch { message = "<message unreadable>"; }
try { stack = exception.StackTrace; } catch { stack = null; }
if (string.IsNullOrEmpty(message) && string.IsNullOrEmpty(stack)) return null;
return Trim(type + ": " + message, 150) + " @ " + FirstFrame(stack);
}
private static string FirstFrame(string stack)
{
if (string.IsNullOrEmpty(stack)) return "<no stack>";
var start = stack.IndexOf(" at ", StringComparison.Ordinal);
if (start < 0)
{
var newline = stack.IndexOf('\n');
return Trim(newline < 0 ? stack : stack.Substring(0, newline), 120);
}
var end = stack.IndexOf('\n', start);
return Trim(end < 0 ? stack.Substring(start) : stack.Substring(start, end - start), 120);
}
private static string Trim(string value, int max)
{
if (string.IsNullOrEmpty(value)) return string.Empty;
value = value.Replace('\r', ' ').Replace('\n', ' ').Trim();
return value.Length <= max ? value : value.Substring(0, max) + "...";
}
private static void Warn(string message)
{
try { RadicadeEacBypassPlugin.LogSource.LogWarning(message); } catch { }
}
internal static void RequestSelfTestIfAsked()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_LOG_GATE_SELFTEST");
if (string.IsNullOrWhiteSpace(raw)) return;
if (raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no") return;
_selfTestPending = true;
RadicadeEacBypassPlugin.LogSource.LogInfo(
"[LogGate] self-test armed: the next exception through the game handler fires five identical ones to prove the gate (RADICADE_LOG_GATE_SELFTEST).");
}
catch (Exception e)
{
Warn($"[LogGate] self-test could not be armed: {e.GetType().Name}: {e.Message}");
}
}
private static void RunSelfTest()
{
try
{
var before = _totalSuppressed;
var exception = new Il2CppSystem.Exception("radicade log gate self-test");
const int shots = 5;
for (var i = 0; i < shots; i++)
UnityEngine.Debug.LogException(exception);
var collapsed = _totalSuppressed - before;
Warn($"[LogGate] self-test: fired {shots} identical exceptions through the live game handler; {collapsed} were collapsed, " +
$"{shots - collapsed} reached the log " +
$"({(collapsed == shots - 1 ? "PASS" : "FAIL")} - the first of a burst always logs in full, the rest are suppressed).");
}
catch (Exception e)
{
Warn($"[LogGate] self-test failed to run: {e.GetType().Name}: {e.Message}");
}
}
}
+100
View File
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using UnityEngine;
/// <summary>
/// Drops known-noise log lines inside Unity's own logger, so they never reach the
/// console or BepInEx's LogOutput.log. The game funnels both Debug.LogError and
/// Debug.LogWarning through Engine.Logger.Log(LogType, object) - that is the
/// single place where a message can be refused before anyone formats it.
/// </summary>
internal static class LogNoiseFilter
{
private const string SelfTestProbe =
"The character used for Underline is not available in font asset [radicade-filter-self-test].";
internal static void Apply(Harmony harmony)
{
var prefix = AccessTools.Method(typeof(LogNoiseFilter), nameof(Prefix));
if (prefix == null)
{
Diag.Warn("[LogFilter] the filter method was not found; known-noise log spam stays as-is.");
return;
}
var patched = 0;
foreach (var target in Targets())
{
try
{
harmony.Patch(target, prefix: new HarmonyMethod(prefix));
patched++;
}
catch (Exception ex)
{
Diag.Warn("[LogFilter] could not patch " + target.DeclaringType?.Name + "." + target.Name +
" (" + ex.GetType().Name + ": " + ex.Message + ").");
}
}
if (patched == 0)
{
Diag.Warn("[LogFilter] no Unity log funnel could be patched; the RigidbodyEx and font-underline spam stays as-is.");
return;
}
Diag.Info("[LogFilter] armed on " + patched + " Unity log funnel(s): " + LogNoiseRules.PatternCount +
" known-noise pattern(s) are dropped before they are printed.");
SelfTest();
}
private static IEnumerable<MethodBase> Targets()
{
var log = AccessTools.Method(typeof(Logger), "Log",
new[] { typeof(LogType), typeof(Il2CppSystem.Object) });
if (log != null) yield return log;
var logWithContext = AccessTools.Method(typeof(Logger), "Log",
new[] { typeof(LogType), typeof(Il2CppSystem.Object), typeof(UnityEngine.Object) });
if (logWithContext != null) yield return logWithContext;
}
private static bool Prefix(object[] __args)
{
try
{
if (__args == null || __args.Length < 2) return true;
var message = __args[1];
if (message == null) return true;
return !LogNoiseRules.ShouldDrop(message.ToString());
}
catch
{
return true;
}
}
/// <summary>
/// Fires one probe line through Debug.LogWarning and checks that the rules saw
/// it. If they did not, the filter is not wired to this build's logger and the
/// log says so instead of pretending the spam is gone.
/// </summary>
private static void SelfTest()
{
try
{
var before = LogNoiseRules.DroppedCount;
UnityEngine.Debug.LogWarning(SelfTestProbe);
if (LogNoiseRules.DroppedCount > before)
Diag.Info("[LogFilter] self-test PASS: a probe log line was intercepted inside Unity's logger.");
else
Diag.Warn("[LogFilter] self-test FAIL: a probe log line reached the log, so this build's spam is not being filtered.");
}
catch (Exception ex)
{
Diag.Warn("[LogFilter] self-test could not run (" + ex.GetType().Name + ": " + ex.Message + ").");
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Threading;
/// <summary>
/// Log lines the client prints on every room load that say nothing a player can
/// act on and bury the lines that matter. Pure rules only, so they can be
/// exercised outside the game.
/// </summary>
internal static class LogNoiseRules
{
private static readonly string[] Noise =
{
// Every chair and couch the dorm/Rec Center spawns complains that its
// RigidbodyEx has no entity component - dozens of lines per room load,
// all identical in substance.
"has an RigidbodyEx but no",
// TMP asks for an underline glyph the legacy Diggity font does not carry;
// the text renders fine without it.
"The character used for Underline is not available in font asset",
};
private static int _dropped;
internal static int PatternCount => Noise.Length;
internal static int DroppedCount => Volatile.Read(ref _dropped);
internal static bool ShouldDrop(string message)
{
if (string.IsNullOrEmpty(message)) return false;
foreach (var pattern in Noise)
{
if (message.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) < 0) continue;
Interlocked.Increment(ref _dropped);
return true;
}
return false;
}
}
+1038
View File
@@ -0,0 +1,1038 @@
using BepInEx;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using RecRoom.AntiCheat;
using System.Linq;
using System.Runtime.InteropServices;
internal sealed class FixedSetting<T>
{
internal T Value { get; }
internal FixedSetting(T value) => Value = value;
}
[BepInPlugin("com.radicade.patch", "RadicadePatch", "3.0.0")]
public sealed class RadicadeEacBypassPlugin : BasePlugin
{
internal static ManualLogSource LogSource;
internal const string ServerAddress = "https://ns.radicade.app/";
internal const string ServerHost = "ns.radicade.app";
internal static FixedSetting<string> ServerUrl = new(ServerAddress);
internal static FixedSetting<string> TlsAllowedHosts = new(ServerHost);
internal static FixedSetting<bool> RealtimeLog = new(false);
internal static FixedSetting<bool> KeepsakeToolEnabled = new(true);
internal static Il2CppSystem.Threading.SynchronizationContext MainThreadContext;
internal static string ImageKeyId;
static string ReadMetadataKey(string path)
{
try
{
return File.Exists(path)
? ImageSignatureRequest.DetectKeyIdFromGameMetadata(File.ReadAllBytes(path))
: null;
}
catch (Exception e)
{
LogSource.LogWarning($"[ImageSig] key id detection failed for {Path.GetFileName(path)}: {e.GetType().Name}: {e.Message}");
return null;
}
}
void LogFixedConnection()
{
Log.LogInfo($"[TLS] fixed server {ServerUrl.Value}; AllowedHosts = '{TlsAllowedHosts.Value}'");
if (!EmbeddedTlsPatches.IsHostAllowed(ServerHost))
Log.LogError($"[TLS] built-in AllowedHosts '{TlsAllowedHosts.Value}' does not cover '{ServerHost}' - the embedded TLS stack will reject the server certificate.");
}
void WarnIfMetadataPatchedForAnotherServer(Uri server)
{
try
{
var marker = Path.Combine(UnityEngine.Application.dataPath, "radicade-patch.json");
if (!File.Exists(marker))
{
Log.LogWarning($"[Meta] no {marker}: this install was never metadata-patched, so the client still resolves the official hosts.");
return;
}
var text = File.ReadAllText(marker);
var key = text.IndexOf("\"serverUrl\"", StringComparison.Ordinal);
var colon = key < 0 ? -1 : text.IndexOf(':', key);
var open = colon < 0 ? -1 : text.IndexOf('"', colon + 1);
var close = open < 0 ? -1 : text.IndexOf('"', open + 1);
if (close < 0)
{
Log.LogWarning($"[Meta] {marker} has no readable serverUrl field; cannot confirm the patched server matches {server}.");
return;
}
var patched = text[(open + 1)..close].TrimEnd('/');
var wanted = server.GetLeftPart(UriPartial.Authority).TrimEnd('/');
if (patched.Equals(wanted, StringComparison.OrdinalIgnoreCase))
{
Log.LogInfo($"[Meta] metadata patched for {patched}; matches Connection:ServerUrl.");
return;
}
Log.LogWarning($"[Meta] this install's metadata was patched for '{patched}' but Connection:ServerUrl is '{wanted}'. "
+ "The client's own literals still point at the patched address, so requests will not reach the configured server. "
+ $"Re-patch from the pristine copy: python tools/patch-0831-client-metadata.py <game dir> --server {wanted} --apply");
}
catch (Exception e)
{
Log.LogWarning($"[Meta] metadata marker check failed: {e.GetType().Name}: {e.Message}");
}
}
public override void Load()
{
LogSource = Log;
if (UnityEngine.Application.version != "20230831")
throw new NotSupportedException("This plugin requires Rec Room 20230831.");
MainThreadContext = Il2CppSystem.Threading.SynchronizationContext.Current;
if (MainThreadContext == null)
Log.LogWarning("[Patch] no main-thread synchronization context was captured; promise resolution may stall.");
if (!Uri.TryCreate(ServerUrl.Value, UriKind.Absolute, out var server) || server.Scheme != "https")
throw new InvalidOperationException("The RadicadePatch client requires an HTTPS server URL.");
LogFixedConnection();
WarnIfMetadataPatchedForAnotherServer(server);
var metadataRoot = Path.Combine(UnityEngine.Application.dataPath, "il2cpp_data", "Metadata");
var gameKeyId = ReadMetadataKey(Path.Combine(metadataRoot, "global-metadata.dat"));
var interopKeyId = gameKeyId == null ? ReadMetadataKey(Path.Combine(metadataRoot, "global-metadata.interop.dat")) : null;
ImageKeyId = gameKeyId ?? interopKeyId;
if (ImageKeyId == null)
Log.LogWarning("[ImageSig] no image key id was found in the game metadata; rewritten URLs will not state one.");
var harmony = new Harmony("com.radicade.patch");
var steps = new (string Label, Action Apply)[]
{
("module hiding", ApplyModuleHiding),
("hile gate", ArmHileGate),
("connection gate", () => ConnectionGate.Install(harmony)),
("august challenge", () => harmony.PatchAll(typeof(AugustChallengePatches))),
("august launch diagnostics", () => harmony.PatchAll(typeof(AugustLaunchDiagnostics))),
("august launch validator", () => harmony.PatchAll(typeof(AugustLaunchValidatorPatch))),
("rsa verify neutraliser", () => RsaVerifyNeutralizerPatches.Apply(harmony)),
("file check", () => harmony.PatchAll(typeof(FileCheckEntryPatch))),
("eac native client stub", () => harmony.PatchAll(typeof(EacNativeClientStubPatch))),
("eac health query", () => harmony.PatchAll(typeof(EacHealthQueryLiePatch))),
("cheat scan suppression", () => harmony.PatchAll(typeof(CheatScanSuppressPatch))),
("cheat detection suppression", () => harmony.PatchAll(typeof(CheatDetectionSuppressPatch))),
("cheat dorm drop guard", () => harmony.PatchAll(typeof(CheatDormDropGuardPatch))),
("cheat manager trace", () => harmony.PatchAll(typeof(CheatManagerTracePatch))),
("exit initiator tracer", () => harmony.PatchAll(typeof(ExitInitiatorTracerPatch))),
("exit sequence trace", () => harmony.PatchAll(typeof(ExitSequenceTracePatch))),
("unity quit veto", () => harmony.PatchAll(typeof(UnityQuitVetoPatch))),
("exit state veto", () => harmony.PatchAll(typeof(ExitStateVetoPatch))),
("crash address map", CrashAddressMap.Start),
("tool collision reset guard", () => harmony.PatchAll(typeof(ToolCollisionResetGuardPatch))),
("collider cast guard", () => ColliderCastGuardPatch.Apply(harmony)),
("unbound prop freeze", () => harmony.PatchAll(typeof(UnboundPropFreezePatch))),
("keepsake tool", () => KeepsakeToolPatch.Apply(harmony)),
("dynamic ui prefab repair", () => DynamicUiPrefabRepair.Apply(harmony)),
("client text rewrites", () => ClientTextRewrites.Apply(harmony)),
("voice chat restriction capture", () => VoiceChatRestrictionPatches.ArmInstanceCapture(harmony)),
("voice chat ban flags", () => VoiceChatBanFlagPatches.Apply(harmony)),
("invite online friends", () => InviteOnlineFriendsPatch.Apply(harmony)),
("log flood gate", () => ApplyLogFloodGate(harmony)),
("realtime exit watch", () => RealtimeDiagnostics.ApplyExitWatch(harmony)),
("realtime diagnostics", () => RealtimeDiagnostics.Apply(harmony, RealtimeLog?.Value ?? true)),
("embedded tls", () => EmbeddedTlsPatches.Apply(harmony)),
("http response tolerance", () => HttpTolerancePatches.Apply(harmony)),
("log noise filter", () => LogNoiseFilter.Apply(harmony)),
("nameserver connection", () => harmony.PatchAll(typeof(NameserverConnectionPatches))),
("websocket redirect", () => WebSocketRedirectPatches.Apply(harmony)),
};
var failed = 0;
for (var i = 0; i < steps.Length; i++)
{
try
{
steps[i].Apply();
Log.LogInfo($"[Patch] {i + 1}/{steps.Length} {steps[i].Label}");
}
catch (Exception stepEx)
{
failed++;
Log.LogError($"[Patch] {i + 1}/{steps.Length} {steps[i].Label} failed: {stepEx}");
}
}
Log.LogInfo("PATCH LIST END");
if (failed > 0)
Log.LogWarning($"[Patch] {failed} of {steps.Length} steps failed, so the client may not behave as configured.");
ConnectionGate.Open();
}
void ApplyModuleHiding()
{
if (!ModuleHidingPolicy.Enabled)
return;
var hidden = PebModuleHider.HideModule("winhttp.dll");
if (!hidden)
Log.LogWarning("[ModuleHide] winhttp.dll was not found in the PEB module lists; a module scan will still see it.");
var wiped = PeHeaderWiper.WipeSignatures("winhttp.dll");
if (!wiped)
Log.LogWarning("[ModuleHide] the winhttp.dll PE signature wipe did not apply (signatures missing or VirtualProtect failed).");
if (hidden && wiped)
Log.LogInfo("[ModuleHide] winhttp.dll is unlinked from the PEB module lists and its PE signatures are wiped.");
}
void ArmHileGate()
{
HileGate.Enabled = true;
Log.LogInfo(HileGate.DropEnabled
? "[HileGate] armed: anti-cheat report POSTs are dropped at the HTTP layer."
: "[HileGate] armed: anti-cheat report POSTs are captured in full and then allowed through (RADICADE_HILE_GATE=drop drops them instead).");
}
void ApplyLogFloodGate(Harmony harmony)
{
LogFloodGate.Apply(harmony);
LogFloodGate.RequestSelfTestIfAsked();
}
}
[HarmonyPatch]
internal static class AugustLaunchDiagnostics
{
[HarmonyPrefix, HarmonyPatch(typeof(MILEHJBNIKI), "LMMMGKJCPOF")]
[HarmonyPatch(typeof(MILEHJBNIKI), "MBNHJLNEIKL")]
private static void Trace(FDJFJOBADEI __0)
{
if (__0 == FDJFJOBADEI.LAUNCH_VALIDATION_FAILED)
RadicadeEacBypassPlugin.LogSource.LogWarning("[August launch diagnostic] " + Il2CppSystem.Environment.StackTrace);
}
}
[HarmonyPatch]
internal static class AugustChallengePatches
{
[HarmonyPrefix, HarmonyPatch(typeof(EACManager), nameof(EACManager.GenerateChallengeResponse))]
private static bool Challenge(ref string __result)
{
__result = "radicade-eac-bypass";
return false;
}
[HarmonyPrefix, HarmonyPatch(typeof(EACManager), "JGFKOKIDHIN")]
private static bool Ready(ref bool __result)
{
__result = true;
return false;
}
}
[HarmonyPatch(typeof(IDLGLOLKLEB.MKGADIPJDLK), "IDDOELEGIFB")]
internal static class AugustLaunchValidatorPatch
{
static readonly System.Threading.CancellationTokenSource Pump = new();
[HarmonyPrefix]
internal static bool Bypass(IDLGLOLKLEB.MKGADIPJDLK __instance)
{
var promise = __instance.promise;
if (promise == null)
return true;
var context = RadicadeEacBypassPlugin.MainThreadContext;
try
{
var before = Il2CppSystem.Threading.SynchronizationContext.Current;
if (context != null)
Il2CppSystem.Threading.SynchronizationContext.SetSynchronizationContext(context);
try
{
promise.BEJNCICKNOM();
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogError($"[August] Resolve failed: {e}");
}
finally
{
Il2CppSystem.Threading.SynchronizationContext.SetSynchronizationContext(before);
}
}
catch (Exception outer)
{
RadicadeEacBypassPlugin.LogSource.LogError($"[August] Context install failed: {outer}");
}
return false;
}
}
internal static class RsaVerifyNeutralizerPatches
{
static readonly byte[] P1KeyModulus = System.Convert.FromBase64String(
"X07yXkxaaLcZ1wVXfkWjgFkkqdoLhDFm0GPODsF+Q47pSUlbLvtXGqStnyEJEIrQmgDiicAvCdGRq4lovr2l5sIPMaoyizsbVHBdwLUrCsji0RvSBnmvN+8KqQ8STnB4DP4pAsPilfD35def4WuX/xMCXB5+hQUVhv27HPV8Dj9XzHuJAijIM9UwDZmvUcECyiO4wv+TaZi2+ELBtaLCQR8Gm1ZPeDEwP62Ch6MJy0jx5pkvvD0KdF9Wye+3/Wx31Zn/Trdo9HL4sGFWPDM9H9kQhZd5wkTHuxpwGIIhlzIwvY2/pBGdZKP6fi1D2jROEmVkBDyhmYY9nO+s3/bndQ==");
static int p1Calls, totalCalls;
internal static void Apply(Harmony harmony)
{
PatchVerify(harmony, typeof(Il2CppSystem.Security.Cryptography.RSACryptoServiceProvider), "VerifyHash",
new[]
{
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>),
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>),
typeof(Il2CppSystem.Security.Cryptography.HashAlgorithmName),
typeof(Il2CppSystem.Security.Cryptography.RSASignaturePadding)
});
PatchVerify(harmony, typeof(Il2CppSystem.Security.Cryptography.RSACryptoServiceProvider), "VerifyHash",
new[]
{
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>),
typeof(int),
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>)
});
PatchVerify(harmony, typeof(Il2CppSystem.Security.Cryptography.RSACryptoServiceProvider), "VerifyData",
new[]
{
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>),
typeof(Il2CppSystem.Object),
typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<byte>)
});
}
static void PatchVerify(Harmony harmony, Type target, string methodName, Type[] args)
{
try
{
var m = AccessTools.Method(target, methodName, args);
if (m == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[RsaVerify] {target.Name}.{methodName}({args.Length} args) not found - skipped");
return;
}
harmony.Patch(m, prefix: new HarmonyMethod(typeof(RsaVerifyNeutralizerPatches).GetMethod(nameof(VerifyPrefix))));
}
catch (Exception e)
{
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[RsaVerify] failed to patch {target.Name}.{methodName}: {e.GetType().Name}: {e.Message}");
}
} public static bool VerifyPrefix(
Il2CppSystem.Security.Cryptography.RSACryptoServiceProvider __instance, ref bool __result)
{
totalCalls++;
var isP1 = false;
try
{
var modulus = __instance.ExportParameters(false).Modulus;
isP1 = modulus != null && modulus.Length == P1KeyModulus.Length && modulus.SequenceEqual(P1KeyModulus);
if (isP1)
p1Calls++;
}
catch
{
}
__result = isP1;
if (totalCalls <= 8)
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[ImageSig] RSA verify #{totalCalls} ({(isP1 ? "p1 key" : "game key")}) forced {(isP1 ? "true" : "false")}");
return false;
}
}
[HarmonyPatch]
internal static class FileCheckEntryPatch
{
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(GBOAMDFCNJA), "PEEADMOKNLI");
yield return AccessTools.Method(typeof(GBOAMDFCNJA), "IBPPCNEAGDP");
}
[HarmonyPrefix]
internal static bool Bypass(ref MFBKDKPGMNP<GBOAMDFCNJA.CMIGBOACMAG> __result)
{
var result = GBOAMDFCNJA.CMIGBOACMAG.NDGAGMGBOMD("radicade: file check bypassed");
__result = NOKBHGDKGNF.BGFCCOEKHOE(result);
return false;
}
}
internal static unsafe class PebModuleHider
{
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_BASIC_INFORMATION
{
public IntPtr Reserved1;
public IntPtr PebBaseAddress;
public IntPtr Reserved2_0;
public IntPtr Reserved2_1;
public IntPtr UniqueProcessId;
public IntPtr Reserved3;
}
[DllImport("ntdll.dll")]
private static extern int NtQueryInformationProcess(
IntPtr hProcess, int processInfoClass, ref PROCESS_BASIC_INFORMATION pbi,
int cb, out int returnLength);
private struct LIST_ENTRY { public IntPtr Flink, Blink; }
private const int OffInMemoryOrder = 0x10;
private const int OffInInitOrder = 0x20;
private const int OffBaseDllName = 0x58;
public static bool HideModule(string dllName)
{
var pbi = default(PROCESS_BASIC_INFORMATION);
int status = NtQueryInformationProcess(new IntPtr(-1), 0, ref pbi,
Marshal.SizeOf<PROCESS_BASIC_INFORMATION>(), out _);
if (status != 0 || pbi.PebBaseAddress == IntPtr.Zero)
return false;
byte* peb = (byte*)pbi.PebBaseAddress;
byte* ldr = *(byte**)(peb + 0x18);
if (ldr == null)
return false;
LIST_ENTRY* head = (LIST_ENTRY*)(ldr + 0x10);
byte* target = null;
for (LIST_ENTRY* n = (LIST_ENTRY*)head->Flink; n != head && target == null; n = (LIST_ENTRY*)n->Flink)
{
byte* entry = (byte*)n;
ushort* us = (ushort*)(entry + OffBaseDllName);
ushort lenBytes = *us;
char* buf = *(char**)(entry + OffBaseDllName + 8);
if (buf == null || lenBytes == 0)
continue;
string name = new string(buf, 0, lenBytes / 2);
if (name.Equals(dllName, StringComparison.OrdinalIgnoreCase))
target = entry;
}
if (target == null)
return false;
Unlink((LIST_ENTRY*)target);
Unlink((LIST_ENTRY*)(target + OffInMemoryOrder));
Unlink((LIST_ENTRY*)(target + OffInInitOrder));
return true;
}
private static void Unlink(LIST_ENTRY* e)
{
var flink = (LIST_ENTRY*)e->Flink;
var blink = (LIST_ENTRY*)e->Blink;
if (flink == e || blink == e)
return;
blink->Flink = e->Flink;
flink->Blink = e->Blink;
e->Flink = (IntPtr)e;
e->Blink = (IntPtr)e;
}
}
internal static unsafe class PeHeaderWiper
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr GetModuleHandleW(string moduleName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualProtect(IntPtr address, UIntPtr size, uint newProtect, out uint oldProtect);
public static bool WipeSignatures(string dllName)
{
IntPtr baseAddr = GetModuleHandleW(dllName);
if (baseAddr == IntPtr.Zero)
return false;
byte* p = (byte*)baseAddr;
int eLfanew = *(int*)(p + 0x3C);
if (eLfanew <= 0 || eLfanew > 0x1000)
return false;
if (!VirtualProtect(baseAddr, (UIntPtr)0x1000, 0x04 , out uint old))
return false;
bool wasMz = p[0] == (byte)'M' && p[1] == (byte)'Z';
bool wasPe = *(uint*)(p + eLfanew) == 0x00004550;
*(ushort*)p = 0;
*(uint*)(p + eLfanew) = 0;
VirtualProtect(baseAddr, (UIntPtr)0x1000, old, out _);
return wasMz && wasPe;
}
}
[HarmonyPatch]
internal static class ExitInitiatorTracerPatch
{
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(KJAMNFCDBIO), "GMAKAKECEDK");
yield return AccessTools.Method(typeof(KJAMNFCDBIO), "FLMPEJAIKFO");
yield return AccessTools.Method(typeof(KJAMNFCDBIO), "LHNNEFHHPBG");
}
[HarmonyPrefix]
internal static bool TraceAndBlock()
{
var armed = HileGate.DroppedCount > 0;
if (armed)
{
var stack = "<unavailable>";
try { stack = Environment.StackTrace; } catch { }
RadicadeEacBypassPlugin.LogSource.LogWarning("[ExitInit] blocked a managed exit initiator - caller trace:\n" + stack);
}
return !armed;
}
}
[HarmonyPatch]
[HarmonyPatch(typeof(BestHTTP.HTTPManager), "SendRequest", new[] { typeof(BestHTTP.HTTPRequest) })]
internal static class ExitSequenceTracePatch
{
private static int _dumped;
[HarmonyPriority(Priority.First)]
private static void Prefix(BestHTTP.HTTPRequest request)
{
try
{
var path = request?.Uri?.AbsolutePath;
if (path == null) return;
if (path.IndexOf("/matchmaking/player/logout", StringComparison.OrdinalIgnoreCase) < 0 &&
path.IndexOf("/playersettings/playersettings", StringComparison.OrdinalIgnoreCase) < 0)
return;
if (++_dumped > 4) return;
RadicadeEacBypassPlugin.LogSource.LogWarning(
"[ExitTrace] shutdown request " + path + " - managed caller stack:" + ExitTraceHelper.SafeStack());
}
catch (Exception traceEx)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[ExitTrace] dump failed: " + traceEx.Message);
}
}
}
[HarmonyPatch]
internal static class UnityQuitVetoPatch
{
internal static bool Enabled => _enabled;
private static readonly bool _enabled = IsEnabled();
private static int _blocks;
private static bool IsEnabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_QUIT_VETO");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "1" or "on" or "true" or "yes";
}
catch { return false; }
}
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var noArg = AccessTools.Method(typeof(KJAMNFCDBIO), "OFBGCPAHADA", Type.EmptyTypes);
if (noArg != null) yield return noArg;
var flag = AccessTools.Method(typeof(KJAMNFCDBIO), "OFBGCPAHADA", new[] { typeof(bool) });
if (flag != null) yield return flag;
}
[HarmonyPrefix]
private static bool Veto(ref bool __result)
{
if (!Enabled) return true;
__result = false;
var count = ++_blocks;
if (count <= 3 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning(
"[QuitVeto] wantsToQuit answered false (block #" + count + ") - Unity's quit is cancelled. " +
"This is the anti-cheat shutdown, not a user quit; the veto is armed by RADICADE_QUIT_VETO=1, set it to 0 to disable." +
ExitTraceHelper.SafeStack());
return false;
}
}
internal static class ModuleHidingPolicy
{
internal static bool Enabled { get; } = ReadFlag("RADICADE_MODULE_HIDE");
private static bool ReadFlag(string name)
{
try
{
var raw = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "1" or "on" or "true" or "yes";
}
catch { return false; }
}
}
[HarmonyPatch]
internal static class CheatScanSuppressPatch
{
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static int _skips;
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_CHEAT_SCAN");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
private static System.Reflection.MethodBase TargetMethod()
{
var type = AccessTools.TypeByName("CheatManager");
if (type == null) throw new System.MissingMemberException("CheatManager could not be resolved.");
var method = AccessTools.Method(type, "EKGOMPDGNEK", System.Type.EmptyTypes);
if (method == null) throw new System.MissingMethodException("CheatManager.EKGOMPDGNEK() is gone - the scan moved or was renamed.");
return method;
}
[HarmonyPrefix]
private static bool Skip()
{
if (!Enabled) return true;
var count = ++_skips;
if (count <= 3 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatScan] skipped CheatManager.EKGOMPDGNEK() (#" + count
+ ") - this is the pass behind the Obscured/winhttp.dll verdict, so no report and no shutdown follow from it.");
return false;
}
}
[HarmonyPatch]
internal static class CheatDetectionSuppressPatch
{
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static int _skips;
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_CHEAT_TYPE");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var type = AccessTools.TypeByName("CheatManager");
if (type == null) throw new System.MissingMemberException("CheatManager could not be resolved.");
var found = new System.Collections.Generic.List<string>();
var missing = new System.Collections.Generic.List<string>();
foreach (var name in new[] { "OnObscuredTypeCheatDetected", "OnTimeCheatDetected", "OnAdvancedMovementCheatDetected" })
{
var method = AccessTools.Method(type, name, System.Type.EmptyTypes);
if (method == null)
{
missing.Add(name);
continue;
}
found.Add(name);
yield return method;
}
_guarded = found.Count;
_unguarded = missing.Count == 0 ? null : string.Join(", ", missing);
}
internal static int Guarded => _guarded;
internal static string Unguarded => _unguarded;
private static int _guarded;
private static string _unguarded;
[HarmonyPrefix]
private static bool Skip(System.Reflection.MethodBase __originalMethod)
{
if (!Enabled) return true;
var count = ++_skips;
if (count <= 5 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatType] skipped CheatManager." + __originalMethod.Name + "() (#" + count
+ ") - this is a verdict handler that ends in the dorm drop with FOKOIBODNAD and the shutdown behind it, so the room survives the detection.");
return false;
}
}
[HarmonyPatch]
internal static class CheatDormDropGuardPatch
{
private const string AntiCheatReason = "385120";
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static int _skips;
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_CHEAT_DROP");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
private static System.Reflection.MethodBase TargetMethod()
{
var type = AccessTools.TypeByName("CheatManager");
if (type == null) throw new System.MissingMemberException("CheatManager could not be resolved.");
var method = AccessTools.Method(type, "FOKOIBODNAD", new[] { typeof(string) });
if (method == null)
throw new System.MissingMethodException("CheatManager.FOKOIBODNAD(string) is gone - the dorm drop moved or was renamed, so the guard is NOT armed.");
return method;
}
[HarmonyPrefix]
private static bool Skip(string __0)
{
if (!Enabled) return true;
if (string.IsNullOrEmpty(__0) || __0.IndexOf(AntiCheatReason, StringComparison.Ordinal) < 0)
return true;
var count = ++_skips;
if (count <= 5 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatDrop] skipped CheatManager.FOKOIBODNAD('" + __0 + "') (#" + count
+ ") - the anti-cheat's own drop, so the player stays where they are instead of being sent to the dormroom mid-session.");
return false;
}
}
[HarmonyPatch]
internal static class CheatManagerTracePatch
{
private const int PerMethodLimit = 4;
private const int LineLimit = 90;
private static readonly System.Diagnostics.Stopwatch Clock = System.Diagnostics.Stopwatch.StartNew();
private static readonly System.Collections.Generic.Dictionary<string, int> Seen = new System.Collections.Generic.Dictionary<string, int>();
private static int _lines;
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var outer = AccessTools.TypeByName("CheatManager");
if (outer == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatTrace] CheatManager not resolvable - nothing traced.");
yield break;
}
var types = new System.Collections.Generic.List<System.Type> { outer };
foreach (var nested in outer.GetNestedTypes(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
types.Add(nested);
foreach (var type in types)
{
foreach (var method in type.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly))
{
if (method.IsAbstract || method.ContainsGenericParameters) continue;
yield return method;
}
}
}
private static readonly bool Enabled = IsEnabled();
private static bool IsEnabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_CHEAT_TRACE");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "1" or "on" or "true" or "yes";
}
catch { return false; }
}
[HarmonyPrefix]
private static void Trace(System.Reflection.MethodBase __originalMethod)
{
if (!Enabled) return;
try
{
if (_lines >= LineLimit) return;
var key = __originalMethod.DeclaringType.Name + "." + __originalMethod.Name;
Seen.TryGetValue(key, out var count);
if (count >= PerMethodLimit) return;
Seen[key] = count + 1;
_lines++;
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatTrace] +" + Clock.ElapsedMilliseconds + "ms " + key);
}
catch { }
}
}
internal static class ExitTraceHelper
{
internal static string SafeStack()
{
try { return "\n" + Environment.StackTrace; } catch { return "\n<stack unavailable>"; }
}
}
internal static class EacGate
{
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static int _logCount;
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_EAC_NEUTRALIZE");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
internal static System.Type Find(string fullName)
{
try
{
var byName = System.Type.GetType(fullName + ", EasyAntiCheat.Client");
if (byName != null) return byName;
}
catch { }
try
{
var forced = typeof(EasyAntiCheat.Client.Hydra.Runtime).Assembly;
var direct = forced.GetType(fullName);
if (direct != null) return direct;
}
catch { }
try { return AccessTools.TypeByName(fullName); } catch { return null; }
}
private const int LogLimit = 8;
internal static void Log(string message)
{
try
{
if (++_logCount > LogLimit) return;
RadicadeEacBypassPlugin.LogSource.LogWarning(message);
}
catch { }
}
}
[HarmonyPatch]
internal static class EacNativeClientStubPatch
{
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var type = EacGate.Find("EasyAntiCheat.Client.Hydra.NativeClient");
if (type == null)
{
EacGate.Log("[EacStub] Hydra.NativeClient not resolvable - EAC runtime left untouched.");
yield break;
}
foreach (var method in type.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
if (method.Name == "Initialize")
yield return method;
}
[HarmonyPrefix]
private static bool Skip(System.Reflection.MethodBase __originalMethod)
{
if (!EacGate.Enabled) return true;
EacGate.Log("[EacStub] skipped " + __originalMethod.DeclaringType + "." + __originalMethod.Name
+ " - EasyAntiCheat's native client never starts, so its module scanner never runs.");
return false;
}
}
[HarmonyPatch]
internal static class EacHealthQueryLiePatch
{
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var native = EacGate.Find("EasyAntiCheat.Client.Hydra.NativeClient");
if (native != null)
foreach (var method in native.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
if (method.Name == "IsStartedWithEasyAntiCheat" && method.ReturnType == typeof(bool))
yield return method;
var runtime = EacGate.Find("EasyAntiCheat.Client.Hydra.Runtime");
if (runtime != null)
foreach (var method in runtime.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
if ((method.Name == "IsActive" || method.Name == "get_Initialized") && method.ReturnType == typeof(bool))
yield return method;
}
[HarmonyPrefix]
private static bool Lie(ref bool __result, System.Reflection.MethodBase __originalMethod)
{
if (!EacGate.Enabled) return true;
__result = true;
EacGate.Log("[EacStub] " + __originalMethod.DeclaringType + "." + __originalMethod.Name + " answered true.");
return false;
}
}
[HarmonyPatch]
[HarmonyPatch]
internal static class ToolCollisionResetGuardPatch
{
internal static bool Enabled => !_disabled;
private static readonly bool _disabled = IsDisabled();
private static bool IsDisabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_TOOL_RESET");
if (string.IsNullOrWhiteSpace(raw)) return false;
return raw.Trim().ToLowerInvariant() is "0" or "off" or "false" or "no";
}
catch { return false; }
}
private static System.Reflection.MethodBase TargetMethod()
{
var method = AccessTools.Method(typeof(Tool), "ResetToDefaultCollisionLayer", Type.EmptyTypes);
if (method == null)
throw new MissingMethodException(
"Tool.ResetToDefaultCollisionLayer() not found - the room crash guard is NOT armed.");
return method;
}
[HarmonyPrefix]
private static bool Skip(Tool __instance)
{
if (!Enabled) return true;
ToolCollisionLayerRestore.Restore(__instance);
return false;
}
private static string Describe(Tool tool)
{
try
{
if (tool == null) return "<null tool>";
var owner = tool.gameObject;
return (owner == null) ? "<no gameObject>" : "'" + owner.name + "'";
}
catch (Exception ex)
{
return "<describe failed: " + ex.GetType().Name + ">";
}
}
}
internal static class ExitStateVetoPatch
{
internal static bool Enabled => _enabled;
private static readonly bool _enabled = IsEnabled();
private static int _blocks;
private static bool IsEnabled()
{
try
{
var raw = Environment.GetEnvironmentVariable("RADICADE_EXIT_VETO");
if (string.IsNullOrWhiteSpace(raw)) return false;
raw = raw.Trim().ToLowerInvariant();
return raw is "1" or "on" or "true" or "yes";
}
catch { return false; }
}
private static System.Collections.Generic.IEnumerable<System.Reflection.MethodBase> TargetMethods()
{
var property = AccessTools.PropertySetter(typeof(KJAMNFCDBIO), "JGKJEDKNFHN");
if (property != null) yield return property;
var raw = AccessTools.Method(typeof(KJAMNFCDBIO), "EHPDJDJLPKM",
new[] { typeof(KJAMNFCDBIO.CNNHHNMPLJE) });
if (raw != null) yield return raw;
}
[HarmonyPrefix]
private static bool Block(KJAMNFCDBIO.CNNHHNMPLJE __0)
{
if (!Enabled) return true;
if (__0 == KJAMNFCDBIO.CNNHHNMPLJE.Running) return true;
var count = ++_blocks;
var caller = count == 1 ? ExitTraceHelper.SafeStack() : string.Empty;
if (count <= 3 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning(
$"[ExitVeto] blocked AppExitState -> {__0} (block #{count}); the session keeps running. " +
"This is the anti-cheat-driven exit, not a user quit - the veto is armed by RADICADE_EXIT_VETO=1, set it to 0 to disable." + caller);
return false;
}
}
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<AssemblyName>RadicadePatch</AssemblyName>
<RootNamespace>RadicadePatch</RootNamespace>
<BepInExRoot Condition="'$(BepInExRoot)' == ''">C:\RadicadeBeta\BepInEx</BepInExRoot>
</PropertyGroup>
<ItemGroup>
<Reference Include="$(BepInExRoot)\core\BepInEx.Core.dll;$(BepInExRoot)\core\BepInEx.Unity.IL2CPP.dll;$(BepInExRoot)\core\0Harmony.dll;$(BepInExRoot)\core\Il2CppInterop.Runtime.dll" Private="false" />
<Reference Include="$(BepInExRoot)\interop\*.dll" Private="false" />
<Compile Include="..\RadicadeEacBypass\EmbeddedTlsPatches.cs" Link="Shared\EmbeddedTlsPatches.cs" />
<Compile Include="..\RadicadeEacBypass\NameserverConnectionPatches.cs" Link="Shared\NameserverConnectionPatches.cs" />
<Compile Include="..\RadicadeEacBypass\NameserverRedirect.cs" Link="Shared\NameserverRedirect.cs" />
<Compile Include="..\RadicadeEacBypass\ImageSignatureRequest.cs" Link="Shared\ImageSignatureRequest.cs" />
<Compile Include="..\RadicadeEacBypass\HubSocketAuth.cs" Link="Shared\HubSocketAuth.cs" />
<Compile Include="..\RadicadeEacBypass\WebSocketRedirectPatches.cs" Link="Shared\WebSocketRedirectPatches.cs" />
<Compile Include="..\RadicadeEacBypass\Diag.cs" Link="Shared\Diag.cs" />
<Compile Include="..\RadicadeEacBypass\ResponseRepair.cs" Link="Shared\ResponseRepair.cs" />
<Compile Include="..\RadicadeEacBypass\HttpTolerancePatches.cs" Link="Shared\HttpTolerancePatches.cs" />
<Compile Include="..\RadicadeEacBypass\RealtimeDiagnostics.cs" Link="Shared\RealtimeDiagnostics.cs" />
<Compile Include="..\RadicadeEacBypass\VoiceChatRestrictionPatches.cs" Link="Shared\VoiceChatRestrictionPatches.cs" />
</ItemGroup>
</Project>
@@ -0,0 +1,40 @@
using UnityEngine;
internal static class ToolCollisionLayerRestore
{
private static int _failures;
internal static void Restore(Tool tool)
{
if (tool == null) return;
try
{
if (tool.MEDCLKFANNC) return;
int layer = (int)tool.EHNBBIAIJIK;
if (layer < 0 || layer > 31)
throw new InvalidOperationException("Invalid default collision layer " + layer);
foreach (var collider in tool.GetComponentsInChildren<Collider>(true))
{
if (collider == null || collider.isTrigger) continue;
if (collider.GetComponentInParent<Tool>() != tool) continue;
var owner = collider.gameObject;
if (!ShouldRestore(owner.layer, (int)tool.HeldCollisionLayer,
(int)tool.EquippedCollisionLayer, (int)tool.AttachedCollisionLayer)) continue;
if (owner.layer == layer) continue;
owner.layer = layer;
}
}
catch (Exception ex)
{
if (++_failures <= 5)
RadicadeEacBypassPlugin.LogSource.LogWarning("[ToolGuard] collision-layer restore failed: " + ex);
}
}
private static bool ShouldRestore(int layer, int held, int equipped, int attached)
{
return layer == held || layer == equipped || layer == attached
|| layer is 0 or 10 or 11 or 13 or 17 or 18 or 23 or 24 or 27;
}
}
@@ -0,0 +1,147 @@
using System;
using HarmonyLib;
using Il2CppInterop.Runtime;
using RecRoom;
using RecRoom.ObjectModel;
using UnityEngine;
[HarmonyPatch]
internal static class UnboundPropFreezePatch
{
private static readonly bool _disabled = IsDisabled();
private static int _alreadyInert;
private static bool _loggedNoRigidbody;
private static bool _loggedFailure;
private static readonly Type[] Companions = new Type[3]
{
typeof(EmbodiedObject),
typeof(RbexEntity),
typeof(TransformEntity)
};
internal static bool Enabled => !_disabled;
private static bool IsDisabled()
{
try
{
string environmentVariable = Environment.GetEnvironmentVariable("RADICADE_UNBOUND_PROP_FREEZE");
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;
}
}
[HarmonyPostfix]
[HarmonyPatch(typeof(RigidbodyEx), "Awake")]
private static void Postfix(RigidbodyEx __instance)
{
if (!Enabled || __instance == null)
{
return;
}
try
{
if (!IsBound(__instance))
{
Freeze(__instance);
}
}
catch (Exception ex)
{
if (!_loggedFailure)
{
_loggedFailure = true;
RadicadeEacBypassPlugin.LogSource.LogWarning((object)("[PropGuard] could not freeze an unbound prop (" + ex.GetType().Name + ": " + ex.Message + "); the prop is left exactly as the game built it."));
}
}
}
private static bool IsBound(RigidbodyEx rigidbodyEx)
{
Type[] companions = Companions;
foreach (Type type in companions)
{
try
{
if (rigidbodyEx.GetComponent(Il2CppType.From(type)) != null)
{
return true;
}
}
catch
{
return true;
}
}
return false;
}
private static void Freeze(RigidbodyEx rigidbodyEx)
{
Rigidbody unityRigidbody = rigidbodyEx.unityRigidbody;
if (unityRigidbody == null)
{
if (!_loggedNoRigidbody)
{
_loggedNoRigidbody = true;
}
try
{
rigidbodyEx.ClearUnityRigidbodyVelocity();
return;
}
catch
{
return;
}
}
if (unityRigidbody.isKinematic)
{
_alreadyInert++;
return;
}
unityRigidbody.isKinematic = true;
unityRigidbody.velocity = Vector3.zero;
unityRigidbody.angularVelocity = Vector3.zero;
}
private static string Describe(RigidbodyEx rigidbodyEx)
{
try
{
GameObject gameObject = ((Component)rigidbodyEx).gameObject;
return (gameObject == null) ? "<no game object>" : ("'" + gameObject.name + "'");
}
catch
{
return "<unnamed>";
}
}
}
@@ -0,0 +1,66 @@
using System;
using System.Reflection;
using HarmonyLib;
internal static class VoiceChatBanFlagPatches
{
private static bool _armed;
internal static void Apply(Harmony harmony)
{
if (_armed) return;
_armed = true;
Patch(harmony, "IsVoiceChatBanned", nameof(BannedPostfix));
Patch(harmony, "AllowedToVoiceChat", nameof(AllowedPostfix));
}
private static void Patch(Harmony harmony, string propertyName, string handler)
{
try
{
var getter = AccessTools.Method(typeof(SettingsModel), "get_" + propertyName)
?? AccessTools.Property(typeof(SettingsModel), propertyName)?.GetGetMethod(true);
if (getter == null)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[VoiceBanFlag] SettingsModel."
+ propertyName + " has no getter in this build, so the Audio page keeps its own value.");
return;
}
harmony.Patch(getter, postfix: new HarmonyMethod(typeof(VoiceChatBanFlagPatches)
.GetMethod(handler, BindingFlags.Static | BindingFlags.NonPublic)));
}
catch (Exception ex)
{
RadicadeEacBypassPlugin.LogSource.LogWarning("[VoiceBanFlag] could not patch SettingsModel."
+ propertyName + ": " + ex.GetType().Name + ": " + ex.Message);
}
}
private static void BannedPostfix(ref bool __result)
{
try
{
if (!VoiceChatRestrictionPatches.RestrictionInForce) return;
__result = true;
}
catch { }
}
private static void AllowedPostfix(ref bool __result)
{
try
{
if (!VoiceChatRestrictionPatches.RestrictionInForce) return;
__result = false;
}
catch { }
}
}
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0"?>
<Obfuscator>
<Var name="InPath" value="bin/Release/net6.0" />
<Var name="OutPath" value="bin/obfuscated" />
<Var name="BepInExRoot" value="C:\RadicadeBeta\BepInEx" />
<Var name="ExtraFrameworkFolders" value="$(BepInExRoot)\core;$(BepInExRoot)\interop" />
<Var name="KeepPublicApi" value="false" />
<Var name="HidePrivateApi" value="true" />
<Var name="HideStrings" value="true" />
<Var name="UseUnicodeNames" value="false" />
<Var name="ReuseNames" value="true" />
<Var name="RenameProperties" value="true" />
<Var name="RenameEvents" value="true" />
<Var name="RenameFields" value="true" />
<Var name="RegenerateDebugInfo" value="false" />
<Var name="SuppressIldasm" value="true" />
<Module file="$(InPath)/RadicadePatch.dll">
<SkipMethod name="Prepare" />
<SkipMethod name="Cleanup" />
<SkipMethod name="TargetMethod" />
<SkipMethod name="TargetMethods" />
<SkipMethod name="Prefix" />
<SkipMethod name="Postfix" />
<SkipMethod name="Transpiler" />
<SkipMethod name="Finalizer" />
<SkipMethod name="ReversePatch" />
<SkipMethod name="ILManipulator" />
<SkipMethod rx=".*(Prefix|Postfix|Transpiler|Finalizer)$" />
<SkipMethod name="EnableDialog" />
<SkipMethod name="GraphicEvent" />
<SkipMethod name="IncomingDialog" />
<SkipMethod name="NoteCollider" />
<SkipMethod name="Repair" />
<SkipMethod name="Swallow" />
<SkipMethod name="Block" />
<SkipMethod name="Bypass" />
<SkipMethod name="Challenge" />
<SkipMethod name="Lie" />
<SkipMethod name="Ready" />
<SkipMethod name="Skip" />
<SkipMethod name="Trace" />
<SkipMethod name="TraceAndBlock" />
<SkipMethod name="Veto" />
</Module>
</Obfuscator>