using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using BestHTTP; using HarmonyLib; using Il2CppInterop.Runtime; internal static class HileGate { internal static bool Enabled; internal static int DroppedCount; public static bool DropEnabled { get; } = IsDropEnabled(); private static bool IsDropEnabled() { try { var raw = System.Environment.GetEnvironmentVariable("RADICADE_HILE_GATE"); if (string.IsNullOrWhiteSpace(raw)) return false; return raw.Trim().ToLowerInvariant() == "drop"; } catch { return false; } } private static bool _dumped; public static bool ShouldDrop(HTTPRequest request) { if (!Enabled || request?.Uri == null) return false; var path = request.Uri.AbsolutePath; if (path == null || !path.EndsWith("/api/PlayerReporting/v1/hile", System.StringComparison.OrdinalIgnoreCase)) return false; Dump(request); return DropEnabled; } public static void Dump(HTTPRequest request) { if (_dumped) return; _dumped = true; try { var body = ""; try { var raw = request.RawData; if (raw != null && raw.Length > 0) { body = System.Text.Encoding.UTF8.GetString(raw); if (body.Length > 2000) body = body.Substring(0, 2000) + "...[truncated]"; } else { var entity = request.GetEntityBody(); if (entity != null && entity.Length > 0) { var text = System.Text.Encoding.UTF8.GetString(entity); body = text + " (" + entity.Length + " bytes in the entity body)"; } else { body = ""; } } } catch (System.Exception bodyEx) { body = ""; } var caller = ""; try { caller = System.Environment.StackTrace; } catch { } RadicadeEacBypassPlugin.LogSource.LogWarning( "[HileGate] anti-cheat report captured (full wire form):\n " + request.MethodType + " " + request.Uri + "\n body: " + body + "\n managed caller stack:" + caller); } catch (System.Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[HileGate] capture failed: " + ex.Message); } } } [HarmonyPatch(typeof(HTTPManager), "SendRequest", new[] { typeof(HTTPRequest) })] internal static class NameserverConnectionPatches { [HarmonyPriority(Priority.Last)] private static bool Prefix(HTTPRequest request, ref HTTPRequest __result) { if (RequestFunnel.Handle(request)) return true; __result = request; return false; } } internal static class RequestFunnel { private static readonly HashSet Seen = new(); private static bool IsSocketScheme(string url) => url.StartsWith("ws:", StringComparison.OrdinalIgnoreCase) || url.StartsWith("wss:", StringComparison.OrdinalIgnoreCase); internal static bool Handle(HTTPRequest request) { if (HileGate.ShouldDrop(request)) { HileGate.DroppedCount++; RadicadeEacBypassPlugin.LogSource.LogWarning("[HileGate] dropped anti-cheat report POST (no response will arrive)."); return false; } try { var uri = request?.Uri?.ToString(); if (!string.IsNullOrEmpty(uri)) Diag.Trace("[Net] " + request.MethodType + " " + uri); } catch { } if (request?.Uri == null) return true; if (!Seen.Add(request.Pointer.ToInt64())) return true; var original = request.Uri.ToString(); var rewritten = IsSocketScheme(original) ? NameserverRedirect.RewriteWebSocket(original, RadicadeEacBypassPlugin.ServerUrl.Value) : NameserverRedirect.Rewrite(original, RadicadeEacBypassPlugin.ServerUrl.Value); var withKeyId = ImageSignatureRequest.AddKeyId(rewritten, RadicadeEacBypassPlugin.ServerUrl.Value, RadicadeEacBypassPlugin.ImageKeyId); if (withKeyId != rewritten) { Diag.Trace("[ImageSig] stated key id on " + withKeyId); } rewritten = withKeyId; if (rewritten != original) { request.Uri = new Il2CppSystem.Uri(rewritten); AlignHostHeader(request, rewritten); Diag.Trace("[Connection] Redirected nameserver request to the configured Radicade server."); } if (RealtimeDiagnostics.Verbose && IsSocketScheme(original)) { try { var outgoing = new System.Text.StringBuilder("[Realtime] socket request: " + original); var headers = request.Headers; if (headers != null) { foreach (var header in headers) { outgoing.Append(" | ").Append(header.Key).Append("="); var values = header.Value; if (values != null) foreach (var value in values) outgoing.Append(value).Append(","); } } RadicadeEacBypassPlugin.LogSource.LogWarning(outgoing.ToString()); } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[Realtime] socket request dump failed: " + ex.GetType().Name + ": " + ex.Message); } } HubSocketAuth.Capture(request); HubSocketAuth.CaptureFromLoginResponse(request, request.Uri.ToString()); HubSocketAuth.AuthenticateHubRequest(request, request.Uri.ToString()); HubSocketAuth.AuthenticateServerRequest(request, request.Uri.ToString()); if (Diag.Verbose) AuthGrantTrace.Report(request); HttpOutcomeTrace.Wrap(request); return true; } private static void AlignHostHeader(HTTPRequest request, string url) { if (request == null || string.IsNullOrEmpty(url)) return; Uri target; if (!Uri.TryCreate(url, UriKind.Absolute, out target) || string.IsNullOrEmpty(target.Host)) return; var authority = target.IsDefaultPort ? target.Host : target.Host + ":" + target.Port; try { var stated = request.GetHeaderValues("Host"); if (stated != null && stated.Count > 0) { if (stated.Count == 1 && stated[0] == authority) return; request.RemoveHeader("Host"); } request.SetHeader("Host", authority); } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[Connection] could not align the Host header on " + url + ": " + ex.GetType().Name + ": " + ex.Message); } } } internal static class AuthGrantTrace { private static readonly HashSet Reported = new(); private static readonly string[] Fields = { "account_id", "username", "password", "birthday", "avatar", "avatarJson", "email", "platform_auth", "device_id", "device_class", "client_id", "ver" }; internal static void Report(HTTPRequest request) { try { if (request?.Uri == null) return; if (!request.Uri.ToString().EndsWith("/auth/connect/token", StringComparison.OrdinalIgnoreCase)) return; if (!Reported.Add(request.Pointer.ToInt64())) return; var body = ReadBody(request); if (string.IsNullOrEmpty(body)) { RadicadeEacBypassPlugin.LogSource.LogWarning("[AuthGrant] connect/token body was unreadable; the grant is unknown for this request."); return; } string Value(string name) { foreach (var pair in body.Split('&')) { var separator = pair.IndexOf('='); if (separator <= 0) continue; if (!string.Equals(pair.Substring(0, separator), name, StringComparison.OrdinalIgnoreCase)) continue; return pair.Substring(separator + 1); } return null; } bool Present(string name) { var value = Value(name); return !string.IsNullOrWhiteSpace(value); } var grant = Value("grant_type") ?? ""; var present = new List(); foreach (var field in Fields) if (Present(field)) present.Add(field); RadicadeEacBypassPlugin.LogSource.LogInfo("[AuthGrant] connect/token grant_type=" + grant + "; fields present: " + (present.Count == 0 ? "" : string.Join(", ", present)) + " (values omitted)"); } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[AuthGrant] could not read the grant: " + ex.GetType().Name + ": " + ex.Message); } } private static string ReadBody(HTTPRequest request) { try { var raw = request.RawData; if (raw != null && raw.Length > 0) return System.Text.Encoding.UTF8.GetString(raw); } catch { } try { var entity = request.GetEntityBody(); if (entity != null && entity.Length > 0) return System.Text.Encoding.UTF8.GetString(entity); } catch { } return null; } } [HarmonyPatch] internal static class SendRequestImplFunnelPatch { private static IEnumerable TargetMethods() { var impl = AccessTools.Method(typeof(HTTPManager), "SendRequestImpl", new[] { typeof(HTTPRequest) }); return impl == null ? Enumerable.Empty() : new MethodBase[] { impl }; } private static void Prefix(object[] __args) { try { if (__args == null || __args.Length == 0) return; RequestFunnel.Handle(__args[0] as HTTPRequest); } catch (Exception ex) { Diag.Warn("[Net] funnel trace failed: " + ex.GetType().Name + ": " + ex.Message); } } } internal static class HttpOutcomeTrace { private static readonly HashSet Wrapped = new(); private static int _completed; internal static void Wrap(HTTPRequest request) { if (request == null) return; string url; string method; string path; string query; try { if (request.Uri == null) return; url = Shorten(request.Uri.ToString()); method = request.MethodType.ToString(); path = request.Uri.AbsolutePath; query = request.Uri.Query; } catch { return; } try { if (!Wrapped.Add(request.Pointer.ToInt64())) return; var original = request.Callback; request.Callback = DelegateSupport.ConvertDelegate( new Action((req, response) => { try { HttpTolerancePatches.RepairResponse(response, path, query); var completed = System.Threading.Interlocked.Increment(ref _completed); var status = -1; var bytes = 0; var reason = string.Empty; if (response != null) { status = response.StatusCode; try { var data = response.Data; if (data != null) bytes = data.Length; } catch { } try { if (!response.IsSuccess && !string.IsNullOrEmpty(response.Message)) reason = " reason=" + response.Message; } catch { } } Diag.Trace("[HttpStatus] #" + completed + " " + method + " " + url + " -> " + (response == null ? "NO RESPONSE" : status.ToString()) + " bytes=" + bytes + reason); if (RealtimeDiagnostics.Verbose && url.IndexOf("/notify/hub/v1", StringComparison.OrdinalIgnoreCase) >= 0) { try { var answer = new System.Text.StringBuilder(); answer.Append("[Realtime] socket answer: status=").Append(response == null ? -1 : status) .Append(" message=").Append(response == null ? "" : response.Message); var answerHeaders = response?.Headers; if (answerHeaders != null) { foreach (var header in answerHeaders) { answer.Append(" | ").Append(header.Key).Append("="); var values = header.Value; if (values != null) foreach (var value in values) answer.Append(value).Append(","); } } string body = null; try { body = response?.DataAsText; } catch { } if (!string.IsNullOrEmpty(body)) answer.Append(" | body=").Append(body.Length > 200 ? body.Substring(0, 200) : body); RadicadeEacBypassPlugin.LogSource.LogWarning(answer.ToString()); } catch (Exception ex) { RadicadeEacBypassPlugin.LogSource.LogWarning("[Realtime] socket answer dump failed: " + ex.GetType().Name + ": " + ex.Message); } } } catch { } finally { try { original?.Invoke(req, response); } catch { } } })); } catch (Exception ex) { Diag.Warn("[HttpStatus] could not instrument " + method + " " + url + ": " + ex.GetType().Name + ": " + ex.Message); } } private static string Shorten(string url) { try { var uri = new Uri(url); var path = uri.AbsolutePath; if (path.StartsWith("/auth/cachedlogin/forplatformid/", StringComparison.OrdinalIgnoreCase)) return "/auth/cachedlogin/forplatformid/{redacted}"; return path + (string.IsNullOrEmpty(uri.Query) ? string.Empty : "?" + uri.Query.Length + " chars"); } catch { return ""; } } }