From cd3f8a2da3346e4f6b18d6de87e95fc40b2b8255 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Wed, 27 May 2026 18:27:41 +0300 Subject: [PATCH] Improve client mod patching setup --- .gitignore | 3 +- DorkNet.ClientMod/DorkNet.ClientMod.csproj | 29 +++-- DorkNet.ClientMod/Mod.cs | 120 +++++++++++++++++++-- 3 files changed, 134 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index ec03f92..a9ce31f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ ## .NET bin/ obj/ +build/ *.user *.suo .vs/ @@ -79,4 +80,4 @@ DorkSquad-recovered/ ProficioVII-recovered/ Recovered.zip Recovered/ -Recovered(*)/ \ No newline at end of file +Recovered(*)/ diff --git a/DorkNet.ClientMod/DorkNet.ClientMod.csproj b/DorkNet.ClientMod/DorkNet.ClientMod.csproj index 1390eae..341adf0 100644 --- a/DorkNet.ClientMod/DorkNet.ClientMod.csproj +++ b/DorkNet.ClientMod/DorkNet.ClientMod.csproj @@ -19,17 +19,32 @@ they would be derivative works of the game anyway, so we never reference them. Only MelonLoader's own DLLs are needed. - tools/build-patcher-zip.ps1 downloads the MelonLoader release zip - and points MelonLoaderDir at its extracted contents, so a fresh - contributor checkout builds without a local Rec Room install. - - To build outside the script (manual dev loop): - dotnet build -p:MelonLoaderDir= + By default the project downloads the pinned MelonLoader release + into DorkNet.ClientMod/build/ and references those DLLs. Pass + -p:MelonLoaderDir= only when you + deliberately want to use a local override. --> - $(MSBuildThisFileDirectory)..\build\melonloader + v0.6.6 + $(MSBuildThisFileDirectory)build\melonloader + $(MelonLoaderCacheRoot)\MelonLoader.x64.$(MelonLoaderVersion).zip + $(MelonLoaderCacheRoot)\MelonLoader + + + + + + $(MelonLoaderDir)\net6\MelonLoader.dll diff --git a/DorkNet.ClientMod/Mod.cs b/DorkNet.ClientMod/Mod.cs index a82d206..25ffdd7 100644 --- a/DorkNet.ClientMod/Mod.cs +++ b/DorkNet.ClientMod/Mod.cs @@ -42,6 +42,7 @@ public class Mod : MelonMod internal static string? DiagnosticsLogPath; private static readonly Dictionary ResolvedTypeCache = new(); private readonly HashSet _diagnosticPatchLabels = new(); + private bool _networkPatchesRegistered; private bool _diagnosticCoreRegistered; private bool _diagnosticGameComplete; private int _diagnosticRetryFrame; @@ -75,6 +76,7 @@ public class Mod : MelonMod Instance = this; Log.Msg("=== DorkNet ClientMod loading ==="); LoadConfig(); + RegisterNetworkPatches(); DiagnosticPatches.Write("[lifecycle] OnInitializeMelon"); } @@ -82,12 +84,7 @@ public class Mod : MelonMod { DiagnosticPatches.Write("[lifecycle] OnLateInitializeMelon"); Log.Msg("=== Registering client patches ==="); - TryPatch("Uri ctor (string)", typeof(Uri), "ctor", - new[] { typeof(string) }, - prefix: nameof(UriPatches.UriStringCtor_Prefix)); - TryPatch("Uri ctor (string, UriKind)", typeof(Uri), "ctor", - new[] { typeof(string), typeof(UriKind) }, - prefix: nameof(UriPatches.UriStringCtor_Prefix)); + RegisterNetworkPatches(); TryPatchByName("PhotonNetwork", "ConnectUsingSettings", prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix)); // Hook the watch's own AppSettings + AuthValues builders directly. @@ -223,6 +220,57 @@ public class Mod : MelonMod Log.Msg("=== Client patches registered ==="); } + private void RegisterNetworkPatches() + { + if (_networkPatchesRegistered) return; + + var complete = true; + complete &= TryPatch("Uri ctor (string)", typeof(Uri), "ctor", + new[] { typeof(string) }, + prefix: nameof(UriPatches.UriStringCtor_Prefix)); + complete &= TryPatch("Uri ctor (string, UriKind)", typeof(Uri), "ctor", + new[] { typeof(string), typeof(UriKind) }, + prefix: nameof(UriPatches.UriStringCtor_Prefix)); + complete &= TryPatchByName("BestHTTP.HTTPRequest", "Send", + prefix: nameof(UriPatches.HttpRequestSend_Prefix)); + complete &= TryPatchHttpManagerStringSendRequestOverloads(); + complete &= TryPatchByName("BestHTTP.HTTPManager", "SendRequest", + args: new[] { ResolveType("BestHTTP.HTTPRequest") ?? typeof(object) }, + prefix: nameof(UriPatches.HttpManagerSendRequestObject_Prefix)); + + _networkPatchesRegistered = complete; + } + + private bool TryPatchHttpManagerStringSendRequestOverloads() + { + var type = ResolveType("BestHTTP.HTTPManager"); + if (type is null) + { + Log.Warning("[patch-miss] BestHTTP.HTTPManager.SendRequest(string): type not found"); + return false; + } + + var prefix = GetPatchMethod(nameof(UriPatches.HttpManagerSendRequestString_Prefix)); + var patched = 0; + foreach (var method in AccessTools.GetDeclaredMethods(type)) + { + if (method.Name != "SendRequest") continue; + var parameters = method.GetParameters(); + if (parameters.Length == 0 || parameters[0].ParameterType != typeof(string)) continue; + HarmonyInstance.Patch(method, prefix: new HarmonyMethod(prefix)); + patched++; + } + + if (patched == 0) + { + Log.Warning("[patch-miss] BestHTTP.HTTPManager.SendRequest(string)"); + return false; + } + + Log.Msg($"[patch-ok] BestHTTP.HTTPManager.SendRequest(string) overloads x{patched}"); + return true; + } + public override void OnUpdate() { if (_diagnosticGameComplete) return; @@ -504,18 +552,70 @@ public class Mod : MelonMod internal static class UriPatches { + public static void HttpManagerSendRequestString_Prefix(ref string url) + { + RewriteString(ref url, "httpmanager-rewrite"); + } + + public static void HttpManagerSendRequestObject_Prefix(object request) + { + RewriteRequestUri(request, "httpmanager-request-rewrite"); + } + public static void UriStringCtor_Prefix(ref string uriString) + { + RewriteString(ref uriString, "uri-rewrite"); + } + + public static void HttpRequestSend_Prefix(object __instance) + { + RewriteRequestUri(__instance, "http-rewrite"); + } + + private static void RewriteRequestUri(object request, string label) + { + try + { + var uriProp = request.GetType().GetProperty("Uri", BindingFlags.Public | BindingFlags.Instance); + if (uriProp?.GetValue(request) is not Uri uri) return; + + var rewritten = RewriteUri(uri); + if (rewritten is null) return; + + uriProp.SetValue(request, rewritten); + Mod.Log.Msg($"[{label}] {uri} → {rewritten}"); + } + catch (Exception ex) + { + Mod.Log.Warning($"[{label}] failed: {ex.Message}"); + } + } + + private static void RewriteString(ref string uriString, string label) { if (string.IsNullOrEmpty(uriString)) return; var host = Mod.Cfg.ServerHost; if (string.IsNullOrEmpty(host)) return; if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return; var rewritten = uriString.Replace(".rec.net", "." + host); - if (rewritten != uriString) + if (rewritten == uriString) return; + Mod.Log.Msg($"[{label}] {uriString} → {rewritten}"); + uriString = rewritten; + } + + private static Uri? RewriteUri(Uri uri) + { + var host = Mod.Cfg.ServerHost; + if (string.IsNullOrEmpty(host)) return null; + if (!uri.Host.EndsWith(".rec.net", StringComparison.OrdinalIgnoreCase)) return null; + + var service = uri.Host[..^".rec.net".Length]; + if (string.IsNullOrWhiteSpace(service)) return null; + + return new UriBuilder(uri) { - Mod.Log.Msg($"[uri-rewrite] {uriString} → {rewritten}"); - uriString = rewritten; - } + Host = service + "." + host, + }.Uri; } }