mirror of
https://github.com/djdevin/recnet-plugin.git
synced 2026-09-08 06:31:29 -07:00
Compare commits
27 Commits
0.0.3-alpha
..
0.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c78a0187a | |||
| ff9749483a | |||
| 4b413728a7 | |||
| c28ec48bbb | |||
| e36f1e447e | |||
| 733a13682d | |||
| 65a3560fc7 | |||
| 9148e7b6dd | |||
| ede406e25b | |||
| 35d0a4281a | |||
| 9bafbcd188 | |||
| f56ed0d8de | |||
| dcb7faec5d | |||
| 837543f5e2 | |||
| 6855abe9f6 | |||
| 9c6448a3f4 | |||
| d0299ae50e | |||
| 095b199568 | |||
| 0e53f35fb6 | |||
| e1459b2de2 | |||
| 2448d8daae | |||
| b4e83bddbf | |||
| 5a8b4b2d52 | |||
| 78a9a2e824 | |||
| f70ed0c548 | |||
| a442466f98 | |||
| 7c103ac7e1 |
+7
-3
@@ -1,3 +1,7 @@
|
||||
GamePath.props
|
||||
obj
|
||||
bin
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
/.idea
|
||||
CannedNet.Client.sln.DotSettings.user
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CannedNet.Client", "CannedNet.Client\CannedNet.Client.csproj", "{B6704474-3934-43AE-9E89-E5851F6A266B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B6704474-3934-43AE-9E89-E5851F6A266B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6704474-3934-43AE-9E89-E5851F6A266B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6704474-3934-43AE-9E89-E5851F6A266B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6704474-3934-43AE-9E89-E5851F6A266B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1 @@
|
||||
GamePath.props
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AssemblyName>RecNetPlugin</AssemblyName>
|
||||
<Product>RecNetPlugin</Product>
|
||||
<AssemblyName>CannedNet.Client</AssemblyName>
|
||||
<Product>My first plugin</Product>
|
||||
<Version>1.0.0</Version>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
@@ -12,7 +12,7 @@
|
||||
https://nuget.bepinex.dev/v3/index.json;
|
||||
https://nuget.samboy.dev/v3/index.json
|
||||
</RestoreAdditionalProjectSources>
|
||||
<RootNamespace>RecNetPlugin</RootNamespace>
|
||||
<RootNamespace>CannedNet.Client</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- GamePath = root of a Rec Room install whose BepInEx/interop/ has been populated.
|
||||
@@ -3,7 +3,7 @@ using RecRoom.AntiCheat;
|
||||
using System.Text;
|
||||
using Il2CppSystem;
|
||||
|
||||
namespace RecNetPlugin.Patches;
|
||||
namespace CannedNet.Client.Patches;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class EACPatches
|
||||
@@ -23,7 +23,7 @@ public static class EACPatches
|
||||
if (!string.IsNullOrEmpty(PGCINMIEBJP))
|
||||
__result = Convert.ToBase64String(Encoding.UTF8.GetBytes(PGCINMIEBJP));
|
||||
else
|
||||
__result = Convert.ToBase64String(Encoding.UTF8.GetBytes("nothing"));
|
||||
__result = Convert.ToBase64String(Encoding.UTF8.GetBytes("i hate this"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
using HarmonyLib;
|
||||
using Org.BouncyCastle.Crypto.Tls;
|
||||
|
||||
namespace RecNetPlugin.Patches;
|
||||
namespace CannedNet.Client.Patches;
|
||||
|
||||
/**
|
||||
Disables TLS certificate pinning. Even though we connect over SSL it seems some certificates
|
||||
might be pinned.
|
||||
*/
|
||||
public class DisableTLSPinning
|
||||
public class FuckOffTLS
|
||||
{
|
||||
[HarmonyPatch(typeof(LegacyTlsAuthentication), "NotifyServerCertificate")]
|
||||
public class TlsPatch
|
||||
@@ -17,4 +13,4 @@ public class DisableTLSPinning
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using ExitGames.Client.Photon;
|
||||
using HarmonyLib;
|
||||
using Photon.Realtime;
|
||||
|
||||
namespace RecNetPlugin.Patches;
|
||||
namespace CannedNet.Client.Patches;
|
||||
|
||||
/**
|
||||
Patches Photon to use the App IDs and server hostname/port specified in the plugin config.
|
||||
*/
|
||||
[HarmonyPatch(typeof(GPFPFDBGCEK), "AMOHMPKKGHL")]
|
||||
public class PhotonPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
private static void Postfix(ref AppSettings __result)
|
||||
{
|
||||
Plugin.Log.LogInfo("okay im patching now");
|
||||
|
||||
if (__result != null)
|
||||
{
|
||||
__result.AppIdRealtime = Plugin.AppIdRT.Value;
|
||||
@@ -1,11 +1,5 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace RecNetPlugin.Patches;
|
||||
|
||||
/**
|
||||
* This allows the global-metadata.dat to be different on the client
|
||||
* patched to allow a different modulus so we can sign images.
|
||||
*/
|
||||
[HarmonyPatch(typeof(JAPJPGNBMNM), "JOKECJKBJGD")]
|
||||
public static class PromisePatch
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using BestHTTP;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
|
||||
namespace RecNetPlugin.Patches;
|
||||
namespace CannedNet.Client.Patches;
|
||||
|
||||
/**
|
||||
Intercept a variety of HTTP requests and rewrite them to point to our own custom server.
|
||||
@@ -20,7 +20,6 @@ public class SendRequestPatch
|
||||
"/data/heartbeat",
|
||||
"/identify",
|
||||
"/httpapi",
|
||||
"/data/event",
|
||||
};
|
||||
|
||||
private static bool IsIgnoredForLogging(string url)
|
||||
@@ -31,16 +30,6 @@ public class SendRequestPatch
|
||||
return false;
|
||||
}
|
||||
|
||||
// Cap logged bodies so a large response/request doesn't flood the log.
|
||||
private const int MaxLoggedBodyLength = 1000;
|
||||
|
||||
private static string Truncate(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s) || s.Length <= MaxLoggedBodyLength)
|
||||
return s;
|
||||
return s.Substring(0, MaxLoggedBodyLength) + $"... <truncated {s.Length - MaxLoggedBodyLength} chars>";
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(HTTPManager), "SendRequest", [typeof(HTTPRequest)])]
|
||||
public class ConnectToRecNetPatch
|
||||
{
|
||||
@@ -55,10 +44,10 @@ public class SendRequestPatch
|
||||
if (entityBody == null)
|
||||
body = "<none>";
|
||||
else if (IsBinaryContentType(request.GetFirstHeaderValue("content-type")) || LooksBinary(entityBody))
|
||||
body = BinaryPreview(entityBody);
|
||||
body = "<binary>";
|
||||
else
|
||||
body = System.Text.Encoding.UTF8.GetString(entityBody);
|
||||
Plugin.Log.LogInfo($"[HTTP] {request.MethodType} {request.Uri.AbsoluteUri} body={Truncate(body)}");
|
||||
Plugin.Log.LogInfo($"[HTTP] {request.MethodType} {request.Uri.AbsoluteUri} body={body}");
|
||||
}
|
||||
|
||||
var host = request.Uri.Host;
|
||||
@@ -103,7 +92,7 @@ public class SendRequestPatch
|
||||
text = resp.DataAsText;
|
||||
if (string.IsNullOrEmpty(text)) text = "<empty>";
|
||||
}
|
||||
var msg = $"[HTTP] <- {resp.StatusCode} {url} body={Truncate(text)}";
|
||||
var msg = $"[HTTP] <- {resp.StatusCode} {url} body={text}";
|
||||
if (resp.StatusCode is >= 200 and < 300)
|
||||
Plugin.Log.LogInfo(msg);
|
||||
else
|
||||
@@ -139,31 +128,6 @@ public class SendRequestPatch
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render the leading bytes of a binary body as text so structured framing (e.g. multipart form
|
||||
// boundaries and part headers) stays readable, while raw bytes are shown as \xNN escapes. Capped
|
||||
// at MaxLoggedBodyLength since the interesting framing is at the front.
|
||||
private static string BinaryPreview(byte[] data)
|
||||
{
|
||||
if (data.Length == 0) return "<binary empty>";
|
||||
|
||||
var sb = new System.Text.StringBuilder(MaxLoggedBodyLength + 32);
|
||||
sb.Append("<binary ").Append(data.Length).Append(" bytes> ");
|
||||
var i = 0;
|
||||
// Cap on rendered length, not byte count: escapes expand a byte to 4 chars, so this keeps the
|
||||
// preview near MaxLoggedBodyLength and avoids a second pass by Truncate at the log site.
|
||||
for (; i < data.Length && sb.Length < MaxLoggedBodyLength; i++)
|
||||
{
|
||||
var b = data[i];
|
||||
if (b == 0x09 || b == 0x0A || b == 0x0D || (b >= 0x20 && b < 0x7F))
|
||||
sb.Append((char)b);
|
||||
else
|
||||
sb.Append("\\x").Append(b.ToString("x2"));
|
||||
}
|
||||
if (i < data.Length)
|
||||
sb.Append($"... <truncated {data.Length - i} bytes>");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Content sniff for raw request bytes — the Content-Type header isn't reliably set at
|
||||
// SendRequest time (e.g. multipart form bodies set it lazily, and the body still embeds the
|
||||
// raw image), so look at the bytes: a NUL byte, or a high ratio of non-text control bytes in
|
||||
@@ -1,15 +1,20 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Text.Json;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using BepInEx.Unity.IL2CPP.Utils.Collections;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace RecNetPlugin;
|
||||
namespace CannedNet.Client;
|
||||
|
||||
[BepInPlugin("net.rec.plugin", "RecNet Plugin", "1.0.0")]
|
||||
[BepInPlugin("lapis.cannednet.client", "CannedNet Client", "1.0.0")]
|
||||
public class Plugin : BasePlugin
|
||||
{
|
||||
internal static new ManualLogSource Log;
|
||||
@@ -33,7 +38,7 @@ public class Plugin : BasePlugin
|
||||
EnableAdvancedSettings = Config.Bind("Advanced", "Enabled Advanced Settings", false, "Allows other fields below in the advanced section to be modified.");
|
||||
PhotonHostname = Config.Bind("Advanced", "Photon NameServer", "", "Custom Photon NameServer");
|
||||
PhotonPort = Config.Bind("Advanced", "Photon NameServer Port", 0, "Custom Photon NameServer Port (if 0, it will be default)");
|
||||
ServerHostname = Config.Bind("Server", "RecNet NameServer Host", "https://ns.rec.net", "Host for the RecNet NameServer.");
|
||||
ServerHostname = Config.Bind("Server", "RecNet NameServer Host", "https://ns.lapis.codes", "Host for the RecNet NameServer.");
|
||||
Debug = Config.Bind("Advanced", "Debug", false, "Show debug logs (HTTP tracing, etc. WARNING: will include sensitive information such as passwords and auth tokens in the logs, be careful when sharing them!)");
|
||||
|
||||
Harmony.CreateAndPatchAll(typeof(Plugin).Assembly);
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 djdevin
|
||||
Copyright (c) 2026 Lapis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,18 +1,10 @@
|
||||
# RecNet Plugin
|
||||
# CannedNet Client
|
||||
|
||||
A [BepInEx 6](https://github.com/BepInEx/BepInEx) (IL2CPP) plugin that points the Rec Room client at a self-hosted / private server.
|
||||
A [BepInEx 6](https://github.com/BepInEx/BepInEx) (IL2CPP) plugin that points the **Rec Room** client at a self-hosted / private "CannedNet" server instead of the official Rec Room backend.
|
||||
|
||||
It does this entirely client-side with [Harmony](https://harmony.pardeike.net/) patches — no game files are modified on disk (except global-metadata.dat - needed for image signatures). The plugin rewrites the RecNet name-server lookups, swaps in your own Photon credentials, and disables the client-side guards (EasyAntiCheat, TLS certificate pinning) that would otherwise reject a non-official server.
|
||||
It does this entirely client-side with [Harmony](https://harmony.pardeike.net/) patches — no game files are modified on disk. The plugin rewrites the RecNet name-server lookups, swaps in your own Photon credentials, and disables the client-side guards (EasyAntiCheat, TLS certificate pinning) that would otherwise reject a non-official server.
|
||||
|
||||
> ⚠️ This disables anti-cheat and certificate validation on the client. Use at your own risk.
|
||||
|
||||
## Safety
|
||||
|
||||
Using BepInEx plugins may cause anti-virus scanners or Windows Defender to pick it up as a threat.
|
||||
|
||||
If you don't trust the complied .DLL, you can build it yourself.
|
||||
|
||||
See https://github.com/djdevin/recnet-plugin#from-source
|
||||
> ⚠️ **For private/experimental servers only.** This redirects traffic away from official Rec Room infrastructure and disables anti-cheat and certificate validation on the client. Do not use it against `rec.net` or any service you don't control. Use at your own risk.
|
||||
|
||||
## What it does
|
||||
|
||||
@@ -31,16 +23,14 @@ See https://github.com/djdevin/recnet-plugin#from-source
|
||||
- **.NET 6 SDK** to build the plugin.
|
||||
- Your own server endpoints: a RecNet name server, and [Photon](https://www.photonengine.com) app keys.
|
||||
|
||||
_Looking for a custom RecNet server?_ Try https://github.com/djdevin/recflare
|
||||
_Looking for a custom RecNet server?_ Try https://github.com/CannedNet/CannedNet.Client
|
||||
|
||||
## Installing
|
||||
|
||||
1. Download the game using https://github.com/SteamRE/DepotDownloader. The manifest ID is `7859140924515540835`.
|
||||
Example: `depotdownloader -app 471710 -depot 471711 -manifest 7859140924515540835`
|
||||
**You must use this specific version.**
|
||||
3. Install BepInEx to the game. See https://docs.bepinex.dev/articles/user_guide/installation/index.html. **Note that you must use version 6!**
|
||||
|
||||
Alternatively, use the [RecFlare client](https://github.com/djdevin/recflare-client)
|
||||
2. Install BepInEx to the game. See https://docs.bepinex.dev/articles/user_guide/installation/index.html. **Note that you must use version 6!**
|
||||
|
||||
### From release
|
||||
|
||||
@@ -76,7 +66,7 @@ dotnet build
|
||||
|
||||
The build validates that `GamePath` is set and that `$(GamePath)\BepInEx\interop` exists, and fails with a clear message otherwise.
|
||||
|
||||
A post-build step (the `DeployPlugin` target in the `.csproj`) automatically copies the built `RecNetPatcher.dll` into your Rec Room install's `BepInEx/plugins/` folder after every build. Since `GamePath` already points at your install, you don't need to copy anything by hand — just `dotnet build` and launch the game.
|
||||
A post-build step (the `DeployPlugin` target in the `.csproj`) automatically copies the built `CannedNet.Client.dll` into your Rec Room install's `BepInEx/plugins/` folder after every build. Since `GamePath` already points at your install, you don't need to copy anything by hand — just `dotnet build` and launch the game.
|
||||
|
||||
If you need the DLL elsewhere, it's also left in `bin/Debug/net6.0/`.
|
||||
|
||||
@@ -84,10 +74,10 @@ If you need the DLL elsewhere, it's also left in `bin/Debug/net6.0/`.
|
||||
|
||||
Start the game for the first time. In `BepInEx` you should now see a `config` folder. If not, verify BepInEx installation and version.
|
||||
|
||||
Inside `config`, edit the `net.rec.plugin.cfg` file and update as needed:
|
||||
Inside `config`, edit the `lapis.cannednet.client.cfg` file and update as needed:
|
||||
|
||||
**[Server]**
|
||||
- `RecNet NameServer Host` — base URL of your RecNet name server (like `https://ns.rec.net`).
|
||||
- `RecNet NameServer Host` — base URL of your RecNet name server (default `https://ns.lapis.codes`).
|
||||
|
||||
**[Photon]**
|
||||
- `App Id Realtime` — Photon Realtime App ID.
|
||||
@@ -106,8 +96,8 @@ Inside `config`, edit the `net.rec.plugin.cfg` file and update as needed:
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `Plugin.cs` | Plugin entry point, config bindings, Harmony bootstrap |
|
||||
| `Patches/` | Harmony patches (HTTP, EAC, TLS, Photon) |
|
||||
| `RecNetPlugin.csproj` | Build config + interop references (driven by `GamePath`), and the `DeployPlugin` post-build copy |
|
||||
| `Patches/` | Harmony patches (networking, EAC, TLS, Photon) |
|
||||
| `CannedNet.Client.csproj` | Build config + interop references (driven by `GamePath`), and the `DeployPlugin` post-build copy |
|
||||
| `GamePath.props.example` | Template for your local `GamePath.props` |
|
||||
|
||||
## FAQ
|
||||
@@ -116,10 +106,6 @@ Inside `config`, edit the `net.rec.plugin.cfg` file and update as needed:
|
||||
|
||||
Yes. That's the point.
|
||||
|
||||
## Credits
|
||||
|
||||
Based on https://github.com/CannedNet/CannedNet.Client
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
Reference in New Issue
Block a user