mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 15:08:47 -07:00
Park Photon CustomAuth injector
- Photon dashboard has CustomAuth disabled on the AppId now, so the watch never needs to attach userid/LoginLock to its Authenticate ops. Removed the harmony hook on NetworkingPeer.CallAuthenticate, the AuthValuesInjector_Prefix method, the GetCurrentLockToken helper, and the InjectAuthValues Cfg field + config load. Bumped the [config] log line to drop the InjectAuthValues field. - Both dorknet-clientmod.json.template copies (source + local-releases) lose the "InjectAuthValues" key. - Code moved verbatim to DorkNet.ClientMod/attic/AuthValuesInjector.cs.attic with a header explaining why it was parked and how to restore it. The .cs.attic suffix keeps the SDK's auto-globber from compiling it.
This commit is contained in:
+12
-82
@@ -68,8 +68,9 @@ public class Mod : MelonMod
|
|||||||
// any /goto has returned. Default matches the server's
|
// any /goto has returned. Default matches the server's
|
||||||
// appsettings.json Photon:CloudRegion ("eu").
|
// appsettings.json Photon:CloudRegion ("eu").
|
||||||
public static string PhotonCloudRegion = "eu";
|
public static string PhotonCloudRegion = "eu";
|
||||||
public static bool InjectAuthValues = true;
|
|
||||||
public static bool EnableTlsTrustBypass = true;
|
public static bool EnableTlsTrustBypass = true;
|
||||||
|
// Photon Custom Auth injector was parked 2026-05-28; see
|
||||||
|
// attic/AuthValuesInjector.cs.attic for the code + restore notes.
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
@@ -88,8 +89,8 @@ public class Mod : MelonMod
|
|||||||
RegisterNetworkPatches();
|
RegisterNetworkPatches();
|
||||||
TryPatchByName("PhotonNetwork", "ConnectUsingSettings",
|
TryPatchByName("PhotonNetwork", "ConnectUsingSettings",
|
||||||
prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix));
|
prefix: nameof(PhotonPatches.PhotonAppIdOverride_Prefix));
|
||||||
TryPatchByName("NetworkingPeer", "CallAuthenticate",
|
// NetworkingPeer.CallAuthenticate hook (Photon Custom Auth
|
||||||
prefix: nameof(PhotonPatches.AuthValuesInjector_Prefix));
|
// injector) parked — see attic/AuthValuesInjector.cs.attic.
|
||||||
TryPatchByName("BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls.ServerOnlyTlsAuthentication",
|
TryPatchByName("BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Tls.ServerOnlyTlsAuthentication",
|
||||||
"NotifyServerCertificate",
|
"NotifyServerCertificate",
|
||||||
prefix: nameof(TlsPatches.NotifyServerCertificate_Prefix));
|
prefix: nameof(TlsPatches.NotifyServerCertificate_Prefix));
|
||||||
@@ -286,10 +287,11 @@ public class Mod : MelonMod
|
|||||||
if (r.TryGetProperty("PhotonAppId", out v)) Cfg.PhotonAppId = v.GetString() ?? Cfg.PhotonAppId;
|
if (r.TryGetProperty("PhotonAppId", out v)) Cfg.PhotonAppId = v.GetString() ?? Cfg.PhotonAppId;
|
||||||
if (r.TryGetProperty("PhotonVoiceAppId", out v)) Cfg.PhotonVoiceAppId = v.GetString() ?? Cfg.PhotonVoiceAppId;
|
if (r.TryGetProperty("PhotonVoiceAppId", out v)) Cfg.PhotonVoiceAppId = v.GetString() ?? Cfg.PhotonVoiceAppId;
|
||||||
if (r.TryGetProperty("PhotonCloudRegion", out v)) Cfg.PhotonCloudRegion = v.GetString() ?? Cfg.PhotonCloudRegion;
|
if (r.TryGetProperty("PhotonCloudRegion", out v)) Cfg.PhotonCloudRegion = v.GetString() ?? Cfg.PhotonCloudRegion;
|
||||||
if (r.TryGetProperty("InjectAuthValues", out v)) Cfg.InjectAuthValues = v.GetBoolean();
|
|
||||||
if (r.TryGetProperty("EnableTlsTrustBypass", out v)) Cfg.EnableTlsTrustBypass = v.GetBoolean();
|
if (r.TryGetProperty("EnableTlsTrustBypass", out v)) Cfg.EnableTlsTrustBypass = v.GetBoolean();
|
||||||
|
// "InjectAuthValues" key in the template is now ignored —
|
||||||
|
// see attic/AuthValuesInjector.cs.attic.
|
||||||
Log.Msg($"[config] loaded: ServerHost={Cfg.ServerHost}, SingleOrigin={(string.IsNullOrEmpty(Cfg.SingleOriginBaseUrl) ? "<off>" : Cfg.SingleOriginBaseUrl)}, PhotonAppId={(string.IsNullOrEmpty(Cfg.PhotonAppId) ? "<unset>" : "<set>")}, " +
|
Log.Msg($"[config] loaded: ServerHost={Cfg.ServerHost}, SingleOrigin={(string.IsNullOrEmpty(Cfg.SingleOriginBaseUrl) ? "<off>" : Cfg.SingleOriginBaseUrl)}, PhotonAppId={(string.IsNullOrEmpty(Cfg.PhotonAppId) ? "<unset>" : "<set>")}, " +
|
||||||
$"InjectAuthValues={Cfg.InjectAuthValues}, EnableTlsTrustBypass={Cfg.EnableTlsTrustBypass}");
|
$"EnableTlsTrustBypass={Cfg.EnableTlsTrustBypass}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -745,83 +747,11 @@ internal static class PhotonPatches
|
|||||||
catch { return null; }
|
catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AuthValuesInjector_Prefix(object __instance)
|
// AuthValuesInjector_Prefix + GetCurrentLockToken parked 2026-05-28
|
||||||
{
|
// — see attic/AuthValuesInjector.cs.attic. Photon Custom Auth is
|
||||||
if (!Mod.Cfg.InjectAuthValues) return;
|
// now disabled at the AppId level in the Photon dashboard, so the
|
||||||
try
|
// watch no longer needs to attach userid+LoginLock to Photon
|
||||||
{
|
// Authenticate ops.
|
||||||
// Photon's AuthValues live on PhotonNetwork.AuthValues. The 2020
|
|
||||||
// watch never sets them itself, so we set them right before
|
|
||||||
// NetworkingPeer.CallAuthenticate forwards to the wire op. The
|
|
||||||
// userid is RecNet.Core.LocalAccountId, the LoginLock comes
|
|
||||||
// from PlayerPrefs key LoginLockTokenV2.
|
|
||||||
// RecNet.Core.LocalAccountId is declared `int` in the game
|
|
||||||
// build (dump.cs:573622 → 0x30 backing field). Convert via
|
|
||||||
// Convert.ToInt64 so boxing as int still gives us the long we
|
|
||||||
// need below; a direct (long) cast on boxed int would throw.
|
|
||||||
var coreType = FindGameType("RecNet.Core");
|
|
||||||
var accountIdObj = coreType?.GetProperty("LocalAccountId", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
|
|
||||||
var accountId = accountIdObj is null ? 0L : Convert.ToInt64(accountIdObj);
|
|
||||||
if (accountId <= 0)
|
|
||||||
{
|
|
||||||
Mod.Log.Msg("[auth-injector] skip — no LocalAccountId yet (pre-login Photon ping)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var photonNetwork = FindGameType("PhotonNetwork");
|
|
||||||
var avProp = photonNetwork?.GetProperty("AuthValues", BindingFlags.Public | BindingFlags.Static);
|
|
||||||
var existing = avProp?.GetValue(null);
|
|
||||||
if (existing is not null)
|
|
||||||
{
|
|
||||||
var userIdProp = existing.GetType().GetProperty("UserId");
|
|
||||||
var existingUserId = userIdProp?.GetValue(existing) as string;
|
|
||||||
if (!string.IsNullOrEmpty(existingUserId)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var token = GetCurrentLockToken();
|
|
||||||
|
|
||||||
// Build a new AuthenticationValues. The type lives in the
|
|
||||||
// ExitGames.Client.Photon.LoadBalancing namespace (game-side).
|
|
||||||
// CustomAuthenticationType.Custom = 0 in this build.
|
|
||||||
var authValuesType = FindGameType("ExitGames.Client.Photon.LoadBalancing.AuthenticationValues");
|
|
||||||
if (authValuesType is null) { Mod.Log.Warning("[auth-injector] AuthenticationValues type not found"); return; }
|
|
||||||
var av = Activator.CreateInstance(authValuesType);
|
|
||||||
var authTypeProp = authValuesType.GetProperty("AuthType");
|
|
||||||
if (authTypeProp is not null)
|
|
||||||
{
|
|
||||||
var customAuthValue = authTypeProp.PropertyType.IsEnum
|
|
||||||
? Enum.ToObject(authTypeProp.PropertyType, 0)
|
|
||||||
: Convert.ChangeType(0, authTypeProp.PropertyType);
|
|
||||||
authTypeProp.SetValue(av, customAuthValue);
|
|
||||||
}
|
|
||||||
authValuesType.GetProperty("UserId")?.SetValue(av, accountId.ToString());
|
|
||||||
var addParam = authValuesType.GetMethod("AddAuthParameter", new[] { typeof(string), typeof(string) });
|
|
||||||
addParam?.Invoke(av, new object[] { "userid", accountId.ToString() });
|
|
||||||
addParam?.Invoke(av, new object[] { "LoginLock", token ?? string.Empty });
|
|
||||||
|
|
||||||
avProp?.SetValue(null, av);
|
|
||||||
Mod.Log.Msg($"[auth-injector] set AuthValues userid={accountId} LoginLock={(string.IsNullOrEmpty(token) ? "<missing>" : "<set>")}");
|
|
||||||
}
|
|
||||||
catch (Exception ex) { Mod.Log.Warning($"[auth-injector] failed: {ex.Message}"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? GetCurrentLockToken()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var playerPrefs = FindGameType("UnityEngine.PlayerPrefs");
|
|
||||||
var getString = playerPrefs?.GetMethod("GetString", new[] { typeof(string), typeof(string) });
|
|
||||||
var raw = (string?)getString?.Invoke(null, new object[] { "LoginLockTokenV2", string.Empty });
|
|
||||||
if (string.IsNullOrEmpty(raw)) return null;
|
|
||||||
const string key = "\"LockToken\":\"";
|
|
||||||
var ix = raw.IndexOf(key, StringComparison.Ordinal);
|
|
||||||
if (ix < 0) return null;
|
|
||||||
ix += key.Length;
|
|
||||||
var end = raw.IndexOf('"', ix);
|
|
||||||
return end < 0 ? null : raw[ix..end];
|
|
||||||
}
|
|
||||||
catch { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Type? FindGameType(string name)
|
private static Type? FindGameType(string name)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
// AuthValuesInjector — PARKED 2026-05-28
|
||||||
|
// ─────────────────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Filename suffix is .cs.attic on purpose so the .NET SDK's globber
|
||||||
|
// doesn't pick it up. Don't rename to .cs unless you're putting the
|
||||||
|
// feature back.
|
||||||
|
//
|
||||||
|
// What this used to do
|
||||||
|
// --------------------
|
||||||
|
// Prefix-patched NetworkingPeer.CallAuthenticate so the 2020 watch
|
||||||
|
// would attach a Photon Custom-Auth payload (`userid` +
|
||||||
|
// `LoginLock`) to every Photon Authenticate op. The server's
|
||||||
|
// /photon/customauth controller (PhotonCustomAuthController.cs) read
|
||||||
|
// those parameters and validated them against the player's
|
||||||
|
// /player/login lock.
|
||||||
|
//
|
||||||
|
// Why it's parked
|
||||||
|
// ---------------
|
||||||
|
// Photon Cloud's Custom Auth probe was getting 403'd at the WAF in
|
||||||
|
// front of `auth.<apex>/photon/customauth` for hosts behind
|
||||||
|
// Cloudflare or Tunnelto's proxy. The Photon NameServer then returned
|
||||||
|
// `CustomAuthenticationFailed (ReturnCode 32755)` and the watch
|
||||||
|
// bounced back to the dorm before a session could start.
|
||||||
|
//
|
||||||
|
// The cleaner fix turned out to be:
|
||||||
|
// 1. Turn off Custom Authentication on the Photon AppId in the
|
||||||
|
// dashboard (or flip "Anonymous connect" to Allow), so Photon
|
||||||
|
// never calls our URL.
|
||||||
|
// 2. Stop sending custom-auth params from the client — that's the
|
||||||
|
// InjectAuthValues=false flag we shipped first. With the
|
||||||
|
// dashboard side off too, the injector code below has nothing
|
||||||
|
// to gate on, so it's removed from the live patch.
|
||||||
|
//
|
||||||
|
// How to put it back
|
||||||
|
// ------------------
|
||||||
|
// 1. Rename this file to AuthValuesInjector.cs (drop the .attic
|
||||||
|
// suffix). It compiles as part of DorkNet.ClientMod.
|
||||||
|
// 2. Reinstate the `InjectAuthValues` field on Mod.Cfg + the
|
||||||
|
// config-load line in LoadConfig.
|
||||||
|
// 3. Re-add the harmony patch hook in OnLateInitializeMelon:
|
||||||
|
// TryPatchByName("NetworkingPeer", "CallAuthenticate",
|
||||||
|
// prefix: nameof(PhotonPatches.AuthValuesInjector_Prefix));
|
||||||
|
// 4. Flip `"InjectAuthValues": true` in
|
||||||
|
// DorkNet.ClientMod/dorknet-clientmod.json.template (and the
|
||||||
|
// copy under local-releases/).
|
||||||
|
// 5. If you also want the server to enforce the lock, set
|
||||||
|
// Photon:RequireCustomAuth=true on the server (env
|
||||||
|
// Photon__RequireCustomAuth=true).
|
||||||
|
//
|
||||||
|
// The body below is verbatim what lived in Mod.cs's PhotonPatches
|
||||||
|
// class. `FindGameType` is the same helper still in Mod.cs.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace DorkNet.ClientMod.Attic;
|
||||||
|
|
||||||
|
internal static class AuthValuesInjector
|
||||||
|
{
|
||||||
|
public static void AuthValuesInjector_Prefix(object __instance)
|
||||||
|
{
|
||||||
|
if (!Mod.Cfg.InjectAuthValues) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Photon's AuthValues live on PhotonNetwork.AuthValues. The 2020
|
||||||
|
// watch never sets them itself, so we set them right before
|
||||||
|
// NetworkingPeer.CallAuthenticate forwards to the wire op. The
|
||||||
|
// userid is RecNet.Core.LocalAccountId, the LoginLock comes
|
||||||
|
// from PlayerPrefs key LoginLockTokenV2.
|
||||||
|
// RecNet.Core.LocalAccountId is declared `int` in the game
|
||||||
|
// build (dump.cs:573622 → 0x30 backing field). Convert via
|
||||||
|
// Convert.ToInt64 so boxing as int still gives us the long we
|
||||||
|
// need below; a direct (long) cast on boxed int would throw.
|
||||||
|
var coreType = FindGameType("RecNet.Core");
|
||||||
|
var accountIdObj = coreType?.GetProperty("LocalAccountId", BindingFlags.Public | BindingFlags.Static)?.GetValue(null);
|
||||||
|
var accountId = accountIdObj is null ? 0L : Convert.ToInt64(accountIdObj);
|
||||||
|
if (accountId <= 0)
|
||||||
|
{
|
||||||
|
Mod.Log.Msg("[auth-injector] skip — no LocalAccountId yet (pre-login Photon ping)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var photonNetwork = FindGameType("PhotonNetwork");
|
||||||
|
var avProp = photonNetwork?.GetProperty("AuthValues", BindingFlags.Public | BindingFlags.Static);
|
||||||
|
var existing = avProp?.GetValue(null);
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
var userIdProp = existing.GetType().GetProperty("UserId");
|
||||||
|
var existingUserId = userIdProp?.GetValue(existing) as string;
|
||||||
|
if (!string.IsNullOrEmpty(existingUserId)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var token = GetCurrentLockToken();
|
||||||
|
|
||||||
|
// Build a new AuthenticationValues. The type lives in the
|
||||||
|
// ExitGames.Client.Photon.LoadBalancing namespace (game-side).
|
||||||
|
// CustomAuthenticationType.Custom = 0 in this build.
|
||||||
|
var authValuesType = FindGameType("ExitGames.Client.Photon.LoadBalancing.AuthenticationValues");
|
||||||
|
if (authValuesType is null) { Mod.Log.Warning("[auth-injector] AuthenticationValues type not found"); return; }
|
||||||
|
var av = Activator.CreateInstance(authValuesType);
|
||||||
|
var authTypeProp = authValuesType.GetProperty("AuthType");
|
||||||
|
if (authTypeProp is not null)
|
||||||
|
{
|
||||||
|
var customAuthValue = authTypeProp.PropertyType.IsEnum
|
||||||
|
? Enum.ToObject(authTypeProp.PropertyType, 0)
|
||||||
|
: Convert.ChangeType(0, authTypeProp.PropertyType);
|
||||||
|
authTypeProp.SetValue(av, customAuthValue);
|
||||||
|
}
|
||||||
|
authValuesType.GetProperty("UserId")?.SetValue(av, accountId.ToString());
|
||||||
|
var addParam = authValuesType.GetMethod("AddAuthParameter", new[] { typeof(string), typeof(string) });
|
||||||
|
addParam?.Invoke(av, new object[] { "userid", accountId.ToString() });
|
||||||
|
addParam?.Invoke(av, new object[] { "LoginLock", token ?? string.Empty });
|
||||||
|
|
||||||
|
avProp?.SetValue(null, av);
|
||||||
|
Mod.Log.Msg($"[auth-injector] set AuthValues userid={accountId} LoginLock={(string.IsNullOrEmpty(token) ? "<missing>" : "<set>")}");
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Mod.Log.Warning($"[auth-injector] failed: {ex.Message}"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetCurrentLockToken()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var playerPrefs = FindGameType("UnityEngine.PlayerPrefs");
|
||||||
|
var getString = playerPrefs?.GetMethod("GetString", new[] { typeof(string), typeof(string) });
|
||||||
|
var raw = (string?)getString?.Invoke(null, new object[] { "LoginLockTokenV2", string.Empty });
|
||||||
|
if (string.IsNullOrEmpty(raw)) return null;
|
||||||
|
const string key = "\"LockToken\":\"";
|
||||||
|
var ix = raw.IndexOf(key, StringComparison.Ordinal);
|
||||||
|
if (ix < 0) return null;
|
||||||
|
ix += key.Length;
|
||||||
|
var end = raw.IndexOf('"', ix);
|
||||||
|
return end < 0 ? null : raw[ix..end];
|
||||||
|
}
|
||||||
|
catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindGameType is already defined inside Mod.cs's PhotonPatches
|
||||||
|
// class (private static) — when re-instating, either drop this
|
||||||
|
// private copy and inline the lookup, or move the helper to a
|
||||||
|
// shared internal class. The current shape preserves the original
|
||||||
|
// behaviour for an isolated paste-back.
|
||||||
|
private static Type? FindGameType(string name)
|
||||||
|
{
|
||||||
|
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var t = asm.GetType(name, throwOnError: false);
|
||||||
|
if (t is not null) return t;
|
||||||
|
var prefixed = asm.GetType("Il2Cpp" + name, throwOnError: false);
|
||||||
|
if (prefixed is not null) return prefixed;
|
||||||
|
var dot = name.IndexOf('.');
|
||||||
|
if (dot > 0)
|
||||||
|
{
|
||||||
|
var nsPrefixed = asm.GetType("Il2Cpp" + name.Substring(0, dot) + name.Substring(dot), throwOnError: false);
|
||||||
|
if (nsPrefixed is not null) return nsPrefixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,5 @@
|
|||||||
"PhotonAppId": "{PHOTON_APPID}",
|
"PhotonAppId": "{PHOTON_APPID}",
|
||||||
"PhotonVoiceAppId": "{PHOTON_VOICE_APPID}",
|
"PhotonVoiceAppId": "{PHOTON_VOICE_APPID}",
|
||||||
"PhotonCloudRegion": "{PHOTON_REGION}",
|
"PhotonCloudRegion": "{PHOTON_REGION}",
|
||||||
"InjectAuthValues": false,
|
|
||||||
"EnableTlsTrustBypass": true
|
"EnableTlsTrustBypass": true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user