A BepInEx 6 (IL2CPP) plugin that points the Rec Room client at a self-hosted / private server.
It does this entirely client-side with Harmony 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.
⚠️ This disables anti-cheat and certificate validation on the client. Use at your own risk.
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
| Patch | File | Effect |
|---|---|---|
| Name-server redirect | Patches/SendRequestPatch.cs |
Intercepts BestHTTP requests and rewrites the host ns.rec.net → your configured server. Also provides optional HTTP request/response logging for development. |
| Photon override | Patches/PhotonPatches.cs |
Replaces the Realtime / Voice / Chat App IDs (and optionally the Photon name server + port) with your own. |
| EAC bypass | Patches/EACPatches.cs |
Forces EasyAntiCheat "ready" and stubs the challenge-response so the client connects without the official anti-cheat. |
| TLS bypass | Patches/DisableTLSPinning.cs |
Skips server-certificate validation so a custom server's cert is accepted. |
| Promise stub | Patches/PromisePatch.cs |
Allows custom global-metadata.dat files without the game crashing. |
| CheatManager handling | Plugin.cs |
Deactivates the in-game CheatManager (which would otherwise boot you from rooms) while keeping it resolvable for account creation / login. |
| DUID mismatch workaround | Patches/DUIDMismatchPatch.cs |
Forces the device-id mismatch check to "no mismatch" so the Create Account hang (below) is skipped. On by default; no-op on healthy machines. |
Some machines hang forever on Create Account. This turned out to be a genuinely nasty one, so it's worth documenting.
What happens: when the client's stored device id (DUID) differs from the one derived at runtime,
the client takes a "migration" path — it POSTs to PlayerReporting/v1/deviceId, the server answers
200 {"success":true}, and then the client stalls: it never makes the create_account OAuth call
and never persists the new id. Machines whose stored id already matches never take this path, which is
why the bug hits some players and not others (and is hard to reproduce if your own machine is fine).
The decision point is CheatManager.CheckForDUIDMismatch. Forcing it to return true reproduces
the hang on any machine; forcing it false skips the whole path. That false-forcing is the shipping
workaround, exposed as the Suppress DUID Mismatch config option, which is on by default. It's a
no-op on healthy machines (their real check already returns false) and skips the hang on affected ones.
Still unsolved: we have not found where the "old" device id in that POST actually comes from. It
survives deleting the entire HKCU\Software\Against Gravity\Rec Room registry key, and on the failing
run the local cm_did_ppk PlayerPref is never even read — so clearing local storage does not fix
it. The leading theory is that it's held server-side (recorded by the server from earlier reports)
and/or cached in memory from a server response, which would make the proper fix server-side. See
CLAUDE.md for the full investigation and the diagnostic tooling.
⚠️ Suppress DUID Mismatchis a workaround: it lets account creation through but does not repair a genuinely corrupt stored/served id — it just stops the client from acting on the mismatch.
- A Rec Room install set up with BepInEx 6 (IL2CPP, bleeding-edge), launched at least once so the IL2CPP interop assemblies have been generated under
BepInEx/interop/. - .NET 6 SDK to build the plugin.
- Your own server endpoints: a RecNet name server, and Photon app keys.
Looking for a custom RecNet server? Try https://github.com/djdevin/recflare
- Download the game using https://github.com/SteamRE/DepotDownloader. The manifest ID is
7859140924515540835.
Example:depotdownloader -app 471710 -depot 471711 -manifest 7859140924515540835You must use this specific version. - 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
- Download a release from /releases
- Drop the
.dllfile intoBepInEx/plugins/
The project references the game's interop DLLs, so the build needs to know where your Rec Room install lives. Set GamePath using any one of:
-
A local props file (recommended):
cp GamePath.props.example GamePath.props
then edit
GamePathinGamePath.propsto point at your Rec Room install root. This file is local-only and stays out of the repo. -
An environment variable:
set RECROOM_PATH=C:\Path\To\RecRoom # cmd $env:RECROOM_PATH = "C:\Path\To\RecRoom" # PowerShell
-
On the command line:
dotnet build -p:GamePath="C:\Path\To\RecRoom"
Then build:
dotnet buildThe 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 RecNetPlugin.dll into your Rec Room install's BepInEx/plugins/ folder after every build. (The copy will fail if Rec Room is running, since the DLL is locked — close the game and rebuild.) 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/.
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:
[Server]
RecNet NameServer Host— base URL of your RecNet name server (likehttps://ns.rec.net).
[Photon]
App Id Realtime— Photon Realtime App ID.App Id Voice— Photon Voice App ID.App Id Chat— Photon Chat App ID.
[Advanced]
Enabled Advanced Settings— must betrueto apply the custom Photon name server / port below.Photon NameServer— custom Photon name server host.Photon NameServer Port— custom port (0uses the default,4533).Debug— verbose HTTP request/response logging (only needed for development)⚠️ Debug logs include sensitive data (passwords, auth tokens). Be careful when sharing them.Suppress DUID Mismatch— skips the Create Account / DUID hang (see above). On by default; the only DUID option meant for normal use. Setfalseonly to observe the real mismatch for debugging.
The remaining [Advanced] DUID options — Simulate DUID Mismatch, Corrupt Stored DUID,
Restore Stored DUID, DeviceId Response Override, DeviceId Response Status — are diagnostic
tools used to investigate the hang. Leave them at their defaults unless you're debugging it; see
CLAUDE.md for what each one does.
| Path | Purpose |
|---|---|
Plugin.cs |
Plugin entry point, config bindings, Harmony bootstrap |
Patches/ |
Harmony patches (HTTP, EAC, TLS, Photon, DUID) |
CLAUDE.md |
Developer notes: build gotchas, IL2CPP/interop caveats, and the full DUID-hang investigation |
RecNetPlugin.csproj |
Build config + interop references (driven by GamePath), and the DeployPlugin post-build copy |
GamePath.props.example |
Template for your local GamePath.props |
Can I use this for my own Rec Room server?
Yes. That's the point.