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 { 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 ServerUrl = new(ServerAddress); internal static FixedSetting TlsAllowedHosts = new(ServerHost); internal static FixedSetting RealtimeLog = new(false); internal static FixedSetting 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 --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), typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray), 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), typeof(int), typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray) }); PatchVerify(harmony, typeof(Il2CppSystem.Security.Cryptography.RSACryptoServiceProvider), "VerifyData", new[] { typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray), typeof(Il2CppSystem.Object), typeof(Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray) }); } 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 TargetMethods() { yield return AccessTools.Method(typeof(GBOAMDFCNJA), "PEEADMOKNLI"); yield return AccessTools.Method(typeof(GBOAMDFCNJA), "IBPPCNEAGDP"); } [HarmonyPrefix] internal static bool Bypass(ref MFBKDKPGMNP __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(), 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 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 = ""; 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 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 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(); var missing = new System.Collections.Generic.List(); 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 Seen = new System.Collections.Generic.Dictionary(); private static int _lines; private static System.Collections.Generic.IEnumerable 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 { 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"; } } } 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 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 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 ""; var owner = tool.gameObject; return (owner == null) ? "" : "'" + owner.name + "'"; } catch (Exception ex) { return ""; } } } 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 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; } }