launcher: EAC-aware exe pick + optional TLS trust bypass

Launch RecRoom.exe directly on 2020.12 EAC builds (skip the
Recroom_Release.exe EAC wrapper); fall back to whichever single
binary exists. Add an opt-in "trust self-signed TLS certs" toggle
that flows through ClientPatcher into the ClientMod config, off by
default since Localtunnel/Tunnelto serve valid certs.
This commit is contained in:
Alexa
2026-05-31 12:32:16 +03:00
parent 43b27e674a
commit 0a76a4ec13
5 changed files with 88 additions and 16 deletions
+11
View File
@@ -79,6 +79,17 @@ public sealed class AppState
[JsonPropertyName("launchInScreenMode")]
public bool LaunchInScreenMode { get; set; }
/// <summary>When true, the patched ClientMod skips BouncyCastle's
/// TLS chain validation — needed only for self-signed certs on a
/// LAN-only / dev tunnel. Localtunnel + Tunnelto both terminate
/// TLS with valid certs, so the default is off. Leaving it on
/// triggers a CLR detour-compile crash (0x80131506) on the
/// december build — see project_build_layout_per_branch +
/// MonoMod's CompileMethodHook comment in
/// DorkNet.ClientMod/Mod.cs.</summary>
[JsonPropertyName("enableTlsTrustBypass")]
public bool EnableTlsTrustBypass { get; set; }
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
+6 -3
View File
@@ -59,10 +59,11 @@ public sealed class ClientPatcher
string photonRegion,
string apexHost,
bool singleOrigin = false,
bool enableTlsTrustBypass = false,
CancellationToken ct = default)
{
return Task.Run(() => ApplyCore(patcherDir, recRoomDataPath,
photonAppId, photonVoiceAppId, photonRegion, apexHost, singleOrigin), ct);
photonAppId, photonVoiceAppId, photonRegion, apexHost, singleOrigin, enableTlsTrustBypass), ct);
}
private PatchResult ApplyCore(
@@ -72,7 +73,8 @@ public sealed class ClientPatcher
string photonVoiceAppId,
string photonRegion,
string apexHost,
bool singleOrigin)
bool singleOrigin,
bool enableTlsTrustBypass)
{
var log = new System.Text.StringBuilder();
try
@@ -220,7 +222,8 @@ public sealed class ClientPatcher
.Replace("{PHOTON_VOICE_APPID}",
string.IsNullOrEmpty(photonVoiceAppId) ? photonAppId ?? "" : photonVoiceAppId)
.Replace("{PHOTON_REGION}", string.IsNullOrEmpty(photonRegion) ? "us" : photonRegion)
.Replace("{SINGLE_ORIGIN_BASE_URL}", singleOrigin ? $"https://{apexHost}" : "");
.Replace("{SINGLE_ORIGIN_BASE_URL}", singleOrigin ? $"https://{apexHost}" : "")
.Replace("{ENABLE_TLS_TRUST_BYPASS}", enableTlsTrustBypass ? "true" : "false");
File.WriteAllText(configDest, rendered);
log.AppendLine($"[patch] wrote config: {Path.GetFileName(configDest)}");
}
+50 -11
View File
@@ -4,15 +4,32 @@ using System.IO;
namespace DorkNet.Launcher.Backend;
/// <summary>Launches the user's already-patched Rec Room install
/// directly via <c>Recroom_Release.exe</c>. Never goes through Steam —
/// DorkNet users may not own the game on Steam.</summary>
/// directly. Never goes through Steam — DorkNet users may not own
/// the game on Steam.
///
/// <para><b>2020.03 vs 2020.12 build layout:</b></para>
/// <list type="bullet">
/// <item>2020.03 (pre-EAC): single binary <c>Recroom_Release.exe</c>
/// alongside <c>Recroom_Release_Data/</c>. Run that directly.</item>
/// <item>2020.12+ (EAC): the real Unity binary is <c>RecRoom.exe</c>
/// with its data in <c>RecRoom_Data/</c>; <c>Recroom_Release.exe</c>
/// is the Easy Anti-Cheat launcher wrapper that Steam invokes. Run
/// <c>RecRoom.exe</c> directly to skip EAC entirely — EAC never
/// loads into the game process, no binary patching required (this is
/// the same approach <c>tools/remove-eac.ps1</c> documents on the
/// per-version branch).</item>
/// </list>
/// Detection: an <c>EasyAntiCheat/</c> folder next to the binaries
/// flags the EAC layout. Falls back to whichever exe exists if the
/// heuristic is ambiguous.</summary>
public static class RecRoomLauncher
{
/// <summary>The picked install path is the <c>Recroom_Release_Data</c>
/// folder; the executable lives at <c>&lt;parent&gt;\Recroom_Release.exe</c>.
/// Returns true if Process.Start succeeded. <paramref name="screenMode"/>
/// passes Rec Room's <c>+forcemode:screen</c> arg so the game opens
/// in 2D desktop mode instead of trying to bind a headset.</summary>
/// <summary>The picked install path is the <c>*_Data</c> folder;
/// the executable lives at its parent. Returns true if
/// <see cref="Process.Start(ProcessStartInfo)"/> succeeded.
/// <paramref name="screenMode"/> passes Rec Room's
/// <c>+forcemode:screen</c> arg so the game opens in 2D desktop
/// mode instead of trying to bind a headset.</summary>
public static bool TryLaunch(string recRoomDataPath, bool screenMode, out string error)
{
error = "";
@@ -27,13 +44,15 @@ public static class RecRoomLauncher
error = $"Couldn't resolve the parent directory of '{recRoomDataPath}'.";
return false;
}
var exe = Path.Combine(parent, "Recroom_Release.exe");
if (!File.Exists(exe))
var exe = PickExecutable(parent);
if (exe is null)
{
error = $"Recroom_Release.exe not found at '{exe}'. " +
"Pick the *_Data folder that sits next to it.";
error = $"Neither RecRoom.exe nor Recroom_Release.exe found next to '{recRoomDataPath}'. " +
"Pick the *_Data folder that sits beside the game binary.";
return false;
}
try
{
Process.Start(new ProcessStartInfo
@@ -51,4 +70,24 @@ public static class RecRoomLauncher
return false;
}
}
/// <summary>Pick the right game exe for the install. Prefer
/// <c>RecRoom.exe</c> on EAC builds (so we skip the EAC launcher
/// wrapper). Fall back to whichever single binary exists when only
/// one is present.</summary>
private static string? PickExecutable(string parent)
{
var recRoomExe = Path.Combine(parent, "RecRoom.exe");
var legacyExe = Path.Combine(parent, "Recroom_Release.exe");
var hasEac = Directory.Exists(Path.Combine(parent, "EasyAntiCheat"));
// EAC build: RecRoom.exe is the actual game; Recroom_Release.exe
// is the EAC launcher wrapper. Use the former — it never loads
// EAC into the process.
if (hasEac && File.Exists(recRoomExe)) return recRoomExe;
// Only-one-present cases.
if (File.Exists(legacyExe)) return legacyExe;
if (File.Exists(recRoomExe)) return recRoomExe;
return null;
}
}