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,225 @@
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;
var started = Environment.TickCount;
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;
}
var elapsed = Environment.TickCount - started;
log.LogInfo($"[AddrMap] mapped {mapped} methods from {images} images in {elapsed} ms -> {path} ({unmappable} without a mappable entry point)");
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));
}
}
+1430
View File
@@ -0,0 +1,1430 @@
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using RecRoom.AntiCheat;
using System.Linq;
using System.Runtime.InteropServices;
[BepInPlugin("com.radicade.eacbypass", "Radicade August Client", "2.0.0")]
public sealed class RadicadeEacBypassPlugin : BasePlugin
{
internal static ManualLogSource LogSource;
internal static ConfigEntry<string> ServerUrl, TlsAllowedHosts;
internal static Il2CppSystem.Threading.SynchronizationContext MainThreadContext;
internal static string ImageKeyId;
public override void Load()
{
LogSource = Log;
if (UnityEngine.Application.version != "20230825")
throw new NotSupportedException("This plugin requires Rec Room 20230825.");
MainThreadContext = Il2CppSystem.Threading.SynchronizationContext.Current;
Log.LogInfo($"[August] main-thread sync context: {(MainThreadContext != null ? MainThreadContext.GetType().Name : "NULL")}");
ServerUrl = Config.Bind("Connection", "ServerUrl", "https://localhost:3030", "Private server URL.");
if (!Uri.TryCreate(ServerUrl.Value, UriKind.Absolute, out var server) || server.Scheme != "https")
throw new InvalidOperationException("The August client requires an HTTPS server URL.");
TlsAllowedHosts = Config.Bind("TLS", "AllowedHosts", server.Host, "Exact private server hosts allowed by the embedded TLS integration.");
if (!EmbeddedTlsPatches.IsHostAllowed(server.Host))
throw new InvalidOperationException("TLS AllowedHosts must include the configured private server host.");
var metadata = Path.Combine(UnityEngine.Application.dataPath, "il2cpp_data", "Metadata", "global-metadata.interop.dat");
ImageKeyId = ImageSignatureRequest.DetectKeyId(File.ReadAllBytes(metadata));
if (ModuleHidingPolicy.Enabled)
{
var hidden = PebModuleHider.HideModule("winhttp.dll");
Log.LogInfo(hidden
? "[ModuleHide] winhttp.dll unlinked from PEB module lists (scanner-invisible)"
: "[ModuleHide] WARNING: winhttp.dll not found in PEB lists - scanner will still see it");
var wiped = PeHeaderWiper.WipeSignatures("winhttp.dll");
Log.LogInfo(wiped
? "[ModuleHide] winhttp.dll PE signatures wiped (MZ+PE) - region scans cannot classify it"
: "[ModuleHide] WARNING: PE signature wipe did not apply (signatures missing or VirtualProtect failed)");
}
else
{
Log.LogInfo("[ModuleHide] disabled by default (RADICADE_MODULE_HIDE=1 re-arms runs 14/15): winhttp.dll keeps its loader-list entry and its PE headers, so a module scan has nothing to call obscured.");
}
Log.LogInfo(ModuleHidingPolicy.DescribeModuleList());
HileGate.Enabled = true;
Log.LogInfo("[HileGate] armed - anti-cheat report POSTs are " + (HileGate.DropEnabled
? "dropped at the HTTP layer (RADICADE_HILE_GATE=drop)"
: "captured in full and then allowed through, so the report body is on the record (RADICADE_HILE_GATE=drop restores the drop)") + ".");
var harmony = new Harmony("com.radicade.eacbypass");
EmbeddedTlsPatches.Apply(harmony);
harmony.PatchAll(typeof(NameserverConnectionPatches));
WebSocketRedirectPatches.Apply(harmony);
harmony.PatchAll(typeof(AugustChallengePatches));
harmony.PatchAll(typeof(AugustLaunchDiagnostics));
harmony.PatchAll(typeof(AugustLaunchValidatorPatch));
RsaVerifyNeutralizerPatches.Apply(harmony);
harmony.PatchAll(typeof(FileCheckEntryPatch));
harmony.PatchAll(typeof(ExitInitiatorTracerPatch));
Log.LogInfo("[ExitInit] armed - managed exit initiators are blocked once a hile report has been dropped, and traced.");
try
{
harmony.PatchAll(typeof(ExitStateVetoPatch));
Log.LogInfo("[ExitVeto] patches applied; blockExitState enabled=" + ExitStateVetoPatch.Enabled
+ " (opt-in fallback only; a normal quit and server logouts work either way. RADICADE_EXIT_VETO=1 arms it)");
}
catch (Exception vetoEx)
{
Log.LogError("[ExitVeto] failed to apply - continuing without it: " + vetoEx);
}
try
{
harmony.PatchAll(typeof(ExitSequenceTracePatch));
harmony.PatchAll(typeof(UnityQuitVetoPatch));
harmony.PatchAll(typeof(EacNativeClientStubPatch));
harmony.PatchAll(typeof(EacHealthQueryLiePatch));
harmony.PatchAll(typeof(CheatManagerTracePatch));
harmony.PatchAll(typeof(CheatScanSuppressPatch));
Log.LogInfo("[CheatScan] verdict suppression enabled=" + CheatScanSuppressPatch.Enabled);
Log.LogInfo("[CheatType] cheat-detection suppression enabled=" + CheatDetectionSuppressPatch.Enabled
+ " (RADICADE_CHEAT_TYPE=0 restores the detection and the dorm drop that follows it)");
Log.LogInfo("[CheatDrop] dorm-drop guard enabled=" + CheatDormDropGuardPatch.Enabled
+ " (RADICADE_CHEAT_DROP=0 lets every drop run)");
try
{
harmony.PatchAll(typeof(CheatDetectionSuppressPatch));
Log.LogInfo("[CheatType] verdict handlers guarded=" + CheatDetectionSuppressPatch.Guarded
+ (CheatDetectionSuppressPatch.Unguarded == null
? " (all three)"
: " - UNGUARDED, not found on CheatManager: " + CheatDetectionSuppressPatch.Unguarded));
}
catch (Exception cheatTypeEx)
{
Log.LogError("[CheatType] verdict suppression NOT armed: " + cheatTypeEx.Message);
}
try
{
harmony.PatchAll(typeof(CheatDormDropGuardPatch));
Log.LogInfo("[CheatDrop] dorm drop (EFLCHFDDPJP) held back for the anti-cheat reason; "
+ "every other reason still drops the player.");
}
catch (Exception cheatDropEx)
{
Log.LogError("[CheatDrop] dorm-drop guard NOT armed: " + cheatDropEx.Message);
}
Log.LogInfo("[EacStub] EasyAntiCheat Hydra runtime neutralisation enabled=" + EacGate.Enabled
+ " (RADICADE_EAC_NEUTRALIZE=0 before launch leaves EAC alone)");
Log.LogInfo("[ExitTrace] shutdown-request stack dump armed; [QuitVeto] wantsToQuit veto enabled="
+ UnityQuitVetoPatch.Enabled);
}
catch (Exception quitEx)
{
Log.LogError("[ExitTrace] failed to apply - continuing without them: " + quitEx);
}
try
{
CrashAddressMap.Start();
Log.LogInfo("[AddrMap] enabled=" + CrashAddressMap.Enabled
+ " target=0x" + CrashAddressMap.TargetRva.ToString("x")
+ " (RADICADE_ADDR_MAP=0 disables; RADICADE_ADDR_TARGET=0x... re-aims it)");
}
catch (Exception mapEx)
{
Log.LogError("[AddrMap] failed to start: " + mapEx);
}
try
{
harmony.PatchAll(typeof(ToolCollisionResetGuardPatch));
Log.LogInfo("[ToolGuard] Tool.ResetToDefaultCollisionLayer skipped=" + ToolCollisionResetGuardPatch.Enabled
+ " (RADICADE_TOOL_RESET=0 before launch restores the original reset)");
}
catch (Exception toolEx)
{
Log.LogError("[ToolGuard] failed to apply - the room crash is NOT guarded: " + toolEx);
}
Log.LogInfo($"[August] Connection patches loaded for {ServerUrl.Value}; image key {ImageKeyId ?? "unknown"}.");
}
}
[HarmonyPatch]
internal static class AugustLaunchDiagnostics
{
[HarmonyPrefix, HarmonyPatch(typeof(NDIIPBKGGIG), "FIBOEONALKB")]
[HarmonyPatch(typeof(NDIIPBKGGIG), "IKHPLCCBENN")]
private static void Trace(JKBPKDAGEHN __0)
{
if (__0 == JKBPKDAGEHN.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), "MGEAHKJIIMO")]
private static bool Ready(ref bool __result)
{
__result = true;
return false;
}
}
[HarmonyPatch(typeof(ADJOODNBCKG.NFPDGJNDFOF), "GBAMNAKGEGD")]
internal static class AugustLaunchValidatorPatch
{
static readonly System.Threading.CancellationTokenSource Pump = new();
[HarmonyPrefix]
internal static bool Bypass(ADJOODNBCKG.NFPDGJNDFOF __instance)
{
var promise = __instance.promise;
if (promise == null)
return true;
RadicadeEacBypassPlugin.LogSource.LogInfo("[August] Bypassing native launch validator (hash check cannot pass in a patched install).");
var context = RadicadeEacBypassPlugin.MainThreadContext;
try
{
var before = Il2CppSystem.Threading.SynchronizationContext.Current;
RadicadeEacBypassPlugin.LogSource.LogInfo(
$"[August] validator thread context: {(before != null ? before.GetType().Name : "null")}; installing main-thread context");
if (context != null)
Il2CppSystem.Threading.SynchronizationContext.SetSynchronizationContext(context);
try
{
RadicadeEacBypassPlugin.LogSource.LogInfo(
$"[August] resolving: done={SafeState(promise, "EPCPPDDJCMO")} handlers={SafeHandlerCount(promise)}");
promise.OLKGFACPIPC();
RadicadeEacBypassPlugin.LogSource.LogInfo("[August] Launch-validation promise resolved; boot continues.");
}
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;
}
static int SafeHandlerCount(PFDOLDLEIBA promise)
{
try { return promise.HFPMLNDOGLF?.Count ?? 0; }
catch { return -1; }
}
static bool SafeState(PFDOLDLEIBA promise, string prop)
{
try
{
return prop switch
{
"EPCPPDDJCMO" => promise.EPCPPDDJCMO,
"MPJBGFFEEOP" => promise.MPJBGFFEEOP,
"NEIINCCHEPF" => promise.NEIINCCHEPF,
_ => false,
};
}
catch { 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))));
RadicadeEacBypassPlugin.LogSource.LogInfo(
$"[RsaVerify] patched {target.Name}.{methodName}({string.Join(", ", Array.ConvertAll(args, t => t.Name))})");
}
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(KKHDHILFOGO), "GGIFLDPHCIP");
yield return AccessTools.Method(typeof(KKHDHILFOGO), "GOCCLPJDJIC");
}
[HarmonyPrefix]
internal static bool Bypass(ref MCFFIFIPNKE<KKHDHILFOGO.BJGODLEIDGE> __result)
{
var result = KKHDHILFOGO.BJGODLEIDGE.NOMPJGOIAEB("radicade: file check bypassed");
__result = PFDOLDLEIBA.PLJINLOCHGM(result);
RadicadeEacBypassPlugin.LogSource.LogInfo("[FileCheck] entry bypassed - resolved success promise returned");
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(CCKNJCHHBLL), "BEFDPLAELIH");
yield return AccessTools.Method(typeof(CCKNJCHHBLL), "GIEEBIOOFML");
yield return AccessTools.Method(typeof(CCKNJCHHBLL), "FNOEHNDLHHG");
}
[HarmonyPrefix]
internal static bool TraceAndBlock()
{
var stack = "<unavailable>";
try { stack = Environment.StackTrace; } catch { }
var armed = HileGate.DroppedCount > 0;
RadicadeEacBypassPlugin.LogSource.LogWarning(armed
? "[ExitInit] blocked managed exit initiator - caller trace:\n" + stack
: "[ExitInit] allowed managed exit initiator (no report filed yet) - 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(CCKNJCHHBLL), "GBGMBMGDGGD", Type.EmptyTypes);
if (noArg != null) yield return noArg;
var flag = AccessTools.Method(typeof(CCKNJCHHBLL), "GBGMBMGDGGD", 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; }
}
internal static string DescribeModuleList()
{
try
{
using (var self = System.Diagnostics.Process.GetCurrentProcess())
{
var seen = false;
var path = string.Empty;
foreach (System.Diagnostics.ProcessModule module in self.Modules)
{
if (!string.Equals(module.ModuleName, "winhttp.dll", StringComparison.OrdinalIgnoreCase)) continue;
seen = true;
path = module.FileName ?? string.Empty;
break;
}
return seen
? "[ModuleList] winhttp.dll is enumerated by a Toolhelp module walk: " + path
: "[ModuleList] winhttp.dll is NOT enumerated by a Toolhelp module walk - a loaded image the loader lists do not know about is the most literal definition of obscured.";
}
}
catch (Exception ex)
{
return "[ModuleList] enumeration failed: " + ex.Message;
}
}
}
[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, "MJLAGDJLOBC", System.Type.EmptyTypes);
if (method == null) throw new System.MissingMethodException("CheatManager.MJLAGDJLOBC() 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.MJLAGDJLOBC() (#" + 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 EFLCHFDDPJP 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, "EFLCHFDDPJP", new[] { typeof(string) });
if (method == null)
throw new System.MissingMethodException("CheatManager.EFLCHFDDPJP(string) is gone - the dorm drop moved or was renamed, so the guard is NOT armed.");
return method;
}
[HarmonyPrefix]
private static bool Skip(string JELLDPBCBLG)
{
if (!Enabled) return true;
if (string.IsNullOrEmpty(JELLDPBCBLG) || JELLDPBCBLG.IndexOf(AntiCheatReason, StringComparison.Ordinal) < 0)
return true;
var count = ++_skips;
if (count <= 5 || count % 25 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning("[CheatDrop] skipped CheatManager.EFLCHFDDPJP('" + JELLDPBCBLG + "') (#" + 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;
}
}
}
[HarmonyPrefix]
private static void Trace(System.Reflection.MethodBase __originalMethod)
{
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 int _skips;
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;
var count = ++_skips;
if (count <= 5 || count % 50 == 0)
RadicadeEacBypassPlugin.LogSource.LogWarning(
"[ToolGuard] skipped Tool.ResetToDefaultCollisionLayer on " + Describe(__instance)
+ " (#" + count + ") - this method is the frame directly above the access violation "
+ "at GameAssembly.dll+0x470728d, which walks the tool's ECS collision-layer components. "
+ "Tool resets still apply; only the collision layer is left as it was.");
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(CCKNJCHHBLL), "GLNIKJNLCIB");
if (property != null) yield return property;
var raw = AccessTools.Method(typeof(CCKNJCHHBLL), "KALEFEDAHCC",
new[] { typeof(CCKNJCHHBLL.CCFIKLBABNB) });
if (raw != null) yield return raw;
}
[HarmonyPrefix]
private static bool Block(CCKNJCHHBLL.CCFIKLBABNB __0)
{
if (!Enabled) return true;
if (__0 == CCKNJCHHBLL.CCFIKLBABNB.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;
}
}
+34
View File
@@ -0,0 +1,34 @@
# Radicade August Client
Startup integration for Rec Room **20230825**, compiled against that build's
generated BepInEx interop assemblies. Requires the metadata-reader repair in
`tools/AugustInterop` first. It does not use the incompatible March UI patches.
Build from the repository root:
```powershell
dotnet build 2023Patch/RadicadeAugustClient/RadicadeAugustClient.csproj -c Release
```
Override `-p:BepInExRoot=<game>/BepInEx` for a different installation. No automatic
deployment occurs. Install `bin/Release/net6.0/RadicadeAugustClient.dll` into
`BepInEx/plugins`, with the old RadicadeEacBypass.dll disabled/backed up. The two
plugins share a config identity and must not be installed together.
Local config (`BepInEx/config/com.radicade.eacbypass.cfg`):
```ini
[Connection]
ServerUrl = https://localhost:3030
[TLS]
AllowedHosts = localhost
```
TLS exceptions are limited to the configured host allowlist. The plugin checks
the game version and requires HTTPS. It reads the normalized metadata copy only
to identify the image signature key. Native game files are not modified.
Verified: build succeeds, plugin loads, TLS succeeds, and server configuration
downloads. **Not yet playable:** the next client error is launch validation;
login, Photon multiplayer, watch UI and room loading are not verified or fully
ported. See `docs/2023-AUGUST-BUILD.md` for the current status and backups.
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<BepInExRoot Condition="'$(BepInExRoot)' == ''">C:\RecRoom\2023-08-29T06-53-33Z\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" />
</ItemGroup>
</Project>