Improve client mod patching setup

This commit is contained in:
Alexa
2026-05-27 18:27:41 +03:00
parent e29382f611
commit cd3f8a2da3
3 changed files with 134 additions and 18 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
## .NET ## .NET
bin/ bin/
obj/ obj/
build/
*.user *.user
*.suo *.suo
.vs/ .vs/
@@ -79,4 +80,4 @@ DorkSquad-recovered/
ProficioVII-recovered/ ProficioVII-recovered/
Recovered.zip Recovered.zip
Recovered/ Recovered/
Recovered(*)/ Recovered(*)/
+22 -7
View File
@@ -19,17 +19,32 @@
they would be derivative works of the game anyway, so we never they would be derivative works of the game anyway, so we never
reference them. Only MelonLoader's own DLLs are needed. reference them. Only MelonLoader's own DLLs are needed.
tools/build-patcher-zip.ps1 downloads the MelonLoader release zip By default the project downloads the pinned MelonLoader release
and points MelonLoaderDir at its extracted contents, so a fresh into DorkNet.ClientMod/build/ and references those DLLs. Pass
contributor checkout builds without a local Rec Room install. -p:MelonLoaderDir=<path-to-extracted-MelonLoader> only when you
deliberately want to use a local override.
To build outside the script (manual dev loop):
dotnet build -p:MelonLoaderDir=<path-to-extracted-MelonLoader>
--> -->
<PropertyGroup> <PropertyGroup>
<MelonLoaderDir Condition="'$(MelonLoaderDir)' == ''">$(MSBuildThisFileDirectory)..\build\melonloader</MelonLoaderDir> <MelonLoaderVersion Condition="'$(MelonLoaderVersion)' == ''">v0.6.6</MelonLoaderVersion>
<MelonLoaderCacheRoot>$(MSBuildThisFileDirectory)build\melonloader</MelonLoaderCacheRoot>
<MelonLoaderZip>$(MelonLoaderCacheRoot)\MelonLoader.x64.$(MelonLoaderVersion).zip</MelonLoaderZip>
<MelonLoaderDir Condition="'$(MelonLoaderDir)' == ''">$(MelonLoaderCacheRoot)\MelonLoader</MelonLoaderDir>
</PropertyGroup> </PropertyGroup>
<Target Name="EnsureMelonLoaderReferences"
BeforeTargets="ResolveAssemblyReferences"
Condition="!Exists('$(MelonLoaderDir)\net6\MelonLoader.dll')">
<MakeDir Directories="$(MelonLoaderCacheRoot)" />
<DownloadFile
SourceUrl="https://github.com/LavaGang/MelonLoader/releases/download/$(MelonLoaderVersion)/MelonLoader.x64.zip"
DestinationFolder="$(MelonLoaderCacheRoot)"
DestinationFileName="MelonLoader.x64.$(MelonLoaderVersion).zip"
SkipUnchangedFiles="true" />
<Unzip SourceFiles="$(MelonLoaderZip)"
DestinationFolder="$(MelonLoaderCacheRoot)"
OverwriteReadOnlyFiles="true" />
</Target>
<ItemGroup> <ItemGroup>
<Reference Include="MelonLoader"> <Reference Include="MelonLoader">
<HintPath>$(MelonLoaderDir)\net6\MelonLoader.dll</HintPath> <HintPath>$(MelonLoaderDir)\net6\MelonLoader.dll</HintPath>
+110 -10
View File
@@ -42,6 +42,7 @@ public class Mod : MelonMod
internal static string? DiagnosticsLogPath; internal static string? DiagnosticsLogPath;
private static readonly Dictionary<string, Type> ResolvedTypeCache = new(); private static readonly Dictionary<string, Type> ResolvedTypeCache = new();
private readonly HashSet<string> _diagnosticPatchLabels = new(); private readonly HashSet<string> _diagnosticPatchLabels = new();
private bool _networkPatchesRegistered;
private bool _diagnosticCoreRegistered; private bool _diagnosticCoreRegistered;
private bool _diagnosticGameComplete; private bool _diagnosticGameComplete;
private int _diagnosticRetryFrame; private int _diagnosticRetryFrame;
@@ -75,6 +76,7 @@ public class Mod : MelonMod
Instance = this; Instance = this;
Log.Msg("=== DorkNet ClientMod loading ==="); Log.Msg("=== DorkNet ClientMod loading ===");
LoadConfig(); LoadConfig();
RegisterNetworkPatches();
DiagnosticPatches.Write("[lifecycle] OnInitializeMelon"); DiagnosticPatches.Write("[lifecycle] OnInitializeMelon");
} }
@@ -82,12 +84,7 @@ public class Mod : MelonMod
{ {
DiagnosticPatches.Write("[lifecycle] OnLateInitializeMelon"); DiagnosticPatches.Write("[lifecycle] OnLateInitializeMelon");
Log.Msg("=== Registering client patches ==="); Log.Msg("=== Registering client patches ===");
TryPatch("Uri ctor (string)", typeof(Uri), "ctor", RegisterNetworkPatches();
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));
TryPatchByName("PhotonNetwork", "ConnectUsingSettings", TryPatchByName("PhotonNetwork", "ConnectUsingSettings",
prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix)); prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix));
// Hook the watch's own AppSettings + AuthValues builders directly. // Hook the watch's own AppSettings + AuthValues builders directly.
@@ -223,6 +220,57 @@ public class Mod : MelonMod
Log.Msg("=== Client patches registered ==="); 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() public override void OnUpdate()
{ {
if (_diagnosticGameComplete) return; if (_diagnosticGameComplete) return;
@@ -504,18 +552,70 @@ public class Mod : MelonMod
internal static class UriPatches 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) 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; if (string.IsNullOrEmpty(uriString)) return;
var host = Mod.Cfg.ServerHost; var host = Mod.Cfg.ServerHost;
if (string.IsNullOrEmpty(host)) return; if (string.IsNullOrEmpty(host)) return;
if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return; if (uriString.IndexOf(".rec.net", StringComparison.OrdinalIgnoreCase) < 0) return;
var rewritten = uriString.Replace(".rec.net", "." + host); 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}"); Host = service + "." + host,
uriString = rewritten; }.Uri;
}
} }
} }