mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 23:49:02 -07:00
Radicade Public Code
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user