Alexa 6605f61047 feat(admin): IsAGRoom (IsRRO) toggle on room props, for the editable-Rec-Royale experiment
The Maker Pen permission check runs a room precondition that looks the
current room up in AGRoomRuntimeConfig — a table baked into the client's
resources.assets — and returns a per-original boolean. Rec Center and
Stunt Runner allow creation there; Rec Royale does not, so no blob, role or
server response can grant the pen while the client treats the room as
that original. The table is consulted only for rooms with IsRRO (our
IsAGRoom) set.

Expose IsAGRoom on rooms/{id}/props (+ a checkbox on the General tab) so a
CLONE of Rec Royale can be flipped to a non-original with a real save
attached; whether the client still loads the baked Frontier scene for a
non-original is what that experiment answers. docs/admin.md explains the
gate and the two possible outcomes.
2026-09-28 00:34:56 +03:00

DorkNet — march-2023-03-21

This is the December 2020.12.18 branch of DorkNet — a self-hostable reimplementation of the Rec Room backend tuned to the wire protocol of the Rec Room 2020.12.18 client build.

⚠️ Not affiliated with Rec Room Inc. or Against Gravity. See DISCLAIMER. DorkNet ships no Rec Room game assets or modified binaries — you supply your own legally-acquired Rec Room 2020.12.18 install.

If you're not sure which DorkNet branch you want, start at the main branch's chart. The launcher on main also auto-detects your install.


What this branch targets

Client build Rec Room 2020.12.18
Version key (sent as X-DorkNet-Version header) december_2020_12_18
Version plugin DorkNet.Server/Versions/Late2020/Late2020VersionPlugin.cs
Schema One EF Initial migration; SQLite + Postgres both work
Runtime topology Gateway-fronted microservices with dedicated service slices and a monolith fallback
Diverges from march-2020-03-10 by ~170 files

Subsystems present on this branch that the March branch doesn't have:

  • Chat threads + group DMs (ChatThreads, ChatThreadMembers)
  • Clubs surface (clubs.{apex}/* — announcements, categories, subscriptions)
  • Playlists (curated + user-created)
  • Weekly challenges
  • Room keys (purchasable room access)
  • Room roles (per-room co-owner / moderator / host grants)
  • Leaderboard channel metadata
  • Loading-screen tips

Quickstart

1. Install .NET 9 SDK and (optionally) dotnet-ef:

dotnet tool install --global dotnet-ef

2. Clone this branch:

git clone --branch december-2020-12-18 https://github.com/DorkSquadRR/DorkNet
cd DorkNet

3. Configure secrets. Drop in DorkNet.Server/appsettings.Local.json (gitignored) or export environment variables:

Config key Env var Required Notes
Jwt:Secret DORKNET_JWT_SECRET or Jwt__Secret yes >=64 random chars
Photon:AppId Photon__AppId yes Photon Realtime AppId (free at dashboard.photonengine.com)
Photon:VoiceAppId Photon__VoiceAppId no only if voice chat
Photon:CloudRegion Photon__CloudRegion no defaults to us
Database:Provider Database__Provider no sqlite (default) or postgres
ConnectionStrings:Default ConnectionStrings__Default postgres-only full Npgsql connection string
ConnectionStrings:Redis ConnectionStrings__Redis no enables SignalR backplane
Domain:Apex DORKNET_DOMAIN no defaults to localhost; set for production
S3:Endpoint S3__Endpoint production S3-compatible API endpoint for blobs/images
S3:AccessKey S3__AccessKey production S3 access key
S3:SecretKey S3__SecretKey production S3 secret key
S3:Region S3__Region no defaults to garage; use auto for R2

4. Boot a local standalone server:

dotnet run --project DorkNet.Server

First boot creates bin/Debug/net9.0/data/dorknet.db, runs every LegacyUpgrades pass, seeds canonical rooms / store catalog / club categories / playlists, and starts listening. You should see Application started. in the log; curl http://localhost:8080/healthz returns 200.

For the production microservices stack, Docker, and Dokploy deploy notes, see docs/deploy.md.


Patching your client

.\tools\install-melon.ps1 `
  -RecRoomPath "C:\Path\To\Recroom_Release_Data" `
  -PhotonAppId "<your-photon-realtime-app-id>" `
  -PhotonVoiceAppId "<your-photon-voice-app-id>"

This installs the MelonLoader mod (DorkNet.ClientMod) into the client's Mods/ folder and writes its config to MelonLoader/UserData/dorknet-clientmod.json. The mod rewrites all *.rec.net URIs to your configured DorkNet apex, swaps the Photon AppIds, and bypasses TLS verification on the client's HTTPS calls so self-signed certs work. (First run prints "launch the game once"; do that, then re-run with -ResumeBuild to finish — see the script's -? help.)

Standalone Quest build (experimental)

The December Quest APK is also IL2CPP and can build the same DorkNet.ClientMod source against LemonLoader's Android runtime. The current Quest path is still device-tested manually: build the mod as a net8 LemonLoader DLL, install it into a LemonLoader-patched APK, then sideload the resigned APK.

From this branch:

dotnet build .\DorkNet.ClientMod\DorkNet.ClientMod.csproj `
  -p:TargetFrameworks=net8.0 `
  -p:MelonLoaderDir="C:\path\to\LemonLoader\melon_data\MelonLoader"

Output lands at DorkNet.ClientMod\bin\Debug\net8.0\DorkNet.ClientMod.dll. Desktop builds still default to net6 and keep the historical flat output path.

Quest notes:

  • Target package: com.AgainstGravity.RecRoom.
  • Use a public HTTPS DorkNet host; keep "EnableTlsTrustBypass": false for Quest builds.
  • The Dec Photon hooks are discovered from the unique PUNNetworkManager methods returning AppSettings and AuthenticationValues, with the known Dec obfuscated names as fallback.
  • Standalone APK patching/resigning is not yet wrapped by install-melon.ps1; this is the porting path, not a public one-click installer yet.

Debugging (opt-in — DorkNet.DebugMod)

The shipping DorkNet.ClientMod carries only what's needed to connect and survive anti-cheat. All diagnostics — HTTP/request tracing, join/quit/studio/ room-load traces, the process-exception sink, auth-token tracing, the registration-dialog trace, and the in-game debug console — live in a separate DorkNet.DebugMod. Drop DorkNet.DebugMod.dll into the Mods folder next to DorkNet.ClientMod.dll to turn them on; remove it to ship a quiet, minimal client. Debug knobs are read from the same dorknet-clientmod.json.

Retail builds keep the RecRoom.Debugging.DebugConsole UI and serialized DebugConsoleCommandConfig list, but the public 2023 client strips the native command submit path. With DorkNet.DebugMod installed, set "EnableDebugConsole": true and relaunch: it force-toggles the console UI on a hotkey ("DebugConsoleToggleKey", default BackQuote = the ~ key), intercepts the dead submit callback, and invokes commands from the runtime command metadata directly. Type help in the console to list discovered commands. "DevCommands" may contain optional "Label=Command args" shortcuts. The feature stays off by default and also silences the retail CheatManager callbacks used by movement/time commands.

Desktop Screen Sharing FPS (opt-in)

The Maker Pen "Desktop Sharing Screen" (RecRoom.Tools.Productivity.DesktopScreenSharingDisplay) broadcasts at a baked refresh rate (~5 fps). Set "DesktopScreenShareFps" in dorknet-clientmod.json (e.g. 30) and relaunch to override it — the mod rewrites the gadget's screenShareImageRefreshFrequency at runtime and, when "DesktopScreenShareRaisePhotonRate" is true (default), lifts PhotonNetwork.SendRate/SerializationRate to match so the frames actually transmit (the image streams over OnPhotonSerializeView, capped at the serialization rate otherwise). This is global — it raises every object's network sync rate, so back it off on a busy room. "DesktopScreenShareResolution"/"DesktopScreenShareQuality" (both 0 = keep the prefab value) trade per-frame size for bandwidth if a high FPS saturates the link. Only the player sharing needs the override. Default 0 (off).

RRO quest team size (opt-in)

RRO quests ship with a baked 4-player GameConfigurationAsset team cap. Set "QuestMaxTeamSize" in dorknet-clientmod.json (for example 8) to raise that cap for the allowlisted quest configs in "QuestMaxTeamSizeRooms":

{
  "QuestMaxTeamSize": 8,
  "QuestMaxTeamSizeRooms": [
    "CrimsonCauldron",
    "Crescendo",
    "GoldenTrophy",
    "IsleOfLostSkulls",
    "TheRiseofJumbotron"
  ]
}

The quest host must run the mod: the host builds the game configuration that gets networked to joiners. The patch also relaxes the quest spawn filter for extra players, so player indexes above the four baked spawn slots can reuse existing quest spawn points instead of being left out. Default 0 (off).

Rec Royale night mode (opt-in)

Rec Royale never shipped a night variant, but the client carries the room "mood" engine (RecRoom.Core.Scene.SceneMoodSettings) and ready-made night MoodSetting assets in resources.assets. Set "RecRoyaleNightMode" in dorknet-clientmod.json and the mod applies one of those moods (sky dome, sun, fog, bloom and colour grading) as soon as the RecRoyale_Frontier scene finishes loading, and unlocks the weapon flashlight prefab while it is on:

{
  "RecRoyaleNightMode": true,
  "RecRoyaleNightMood": "Night_Calm_Outdoor_Mood",
  "RecRoyaleNightScenes": ["RecRoyale"],
  "RecRoyaleNightFlashlights": true,
  "RecRoyaleNightFog": false,
  "RecRoyaleNightExposure": -3.5,
  "RecRoyaleNightSunScale": 0.15,
  "RecRoyaleNightAmbientScale": 0.2,
  "RecRoyaleNightForcePostProcessing": true,
  "RecRoyaleNightMatchSunLights": true,
  "RecRoyaleNightDisableFog": false,
  "RecRoyaleNightHideObjects": ["Cloud"],
  "RecRoyaleNightToggleKey": "N"
}

RecRoyaleNightDisableFog turns fog off entirely while night is on (RenderSettings.fog forced off and the mood's fog block pushed out of range); it is ignored when RecRoyaleNightFog is true. RecRoyaleNightHideObjects hides every renderer whose object or material name contains one of the entries; the default hides Frontier's daytime cloud meshes (CloudPlane, Clouds, the _Cloud_RecRoyale_Frontier_Mat static batch). Both are restored on toggle-off or scene change.

The mood engine only ever drives RenderSettings.sun, which on Frontier is the sun-disc light, not the map's key light, so daylight would keep casting. RecRoyaleNightMatchSunLights (default true) copies the engine-driven sun's rotation and colour onto every other directional light and scales their intensity by RecRoyaleNightSunScale.

Two more RRO quirks the mod works around: the mood skybox material the engine swaps in (MoodConfig.SkyBoxMaterial) is not loaded outside custom-room scenes, so the mod builds one on the Custom/SkyBox shader and hands it to the engine (otherwise the sky is plain black and the engine refuses to touch sky colours or the sun); and post-exposure only shows when the scene Volume is on and the camera renders post-processing, which RecRoyaleNightForcePostProcessing (default true) forces while night mode is active.

Brightness: RRO maps are lightmapped, and baked lighting ignores the sun and ambient changes a mood makes, so the mod also darkens the mood data itself before applying it. RecRoyaleNightExposure is an EV offset added to the mood's colour-grading post-exposure (a full-frame post effect, so it dims lightmapped surfaces too; 0 = mood default), and RecRoyaleNightSunScale / RecRoyaleNightAmbientScale multiply the mood's sun and ambient intensities (1 = unchanged). Raise the exposure toward 0 if it is too dark.

RRO scenes have no room data blob, so the mood engine starts disabled there; the mod replays the room-data loader's bring-up (disable, capture the scene's own lighting, enable), points RenderSettings.sun at the scene's directional light (Frontier ships without the reference, which would otherwise skip every sun change), and re-captures once the mood skybox material is in place so the sky colours apply. RecRoyaleNightFog is off by default: the mood's fog block is swapped for one captured from the scene, so fog stays as the map author left it.

Shipped moods: Night_Calm_Outdoor_Mood ("Calm Night"), Night_Spooky_Outdoor_Mood ("Spooky Night"), Night_Wild_Outdoor_Mood ("Wild Night"), Night_StuntRunner_Outdoor_Mood and OuterSpace_Mood. RecRoyaleNightToggleKey is an optional UnityEngine.KeyCode name that flips night on/off in-game (off restores the scene's own mood); setting a key also arms the feature when RecRoyaleNightMode is false. This is purely client-side: the mod calls the mood engine's apply step directly and never touches the networked room-mood index, so other players keep their own view. Baked lightmaps are not changed, so interiors stay lit; the mood's colour grading does the darkening. Default false (off). Wire-level notes: docs/recroom-2023-scene-moods.md.


Code map

Folder Purpose
DorkNet.Server/ Shared ASP.NET Core controller/service stack, standalone fallback, monolith fallback
DorkNet.Models/ DTOs shared between server and client mod
DorkNet.Contracts/ Shared service names, route ownership, service-map options, and health/probe contracts
DorkNet.ServiceDefaults/ Shared service health, JSON, HTTP client, service identity, and route-guard setup
DorkNet.Gateway/ Edge reverse proxy with service-map, route-table, and service-health endpoints
DorkNet.Services.* Gateway-fronted service hosts for identity, rooms, notify, content, social, commerce, platform, moderation, and web
DorkNet.ClientMod/ MelonLoader IL2CPP mod — the client patcher
tools/ Installers (install-melon.ps1, install-legacy-client.ps1, remove-eac.ps1), Cloudflare tunnel templates
Dockerfile, Dockerfile.service, docker-compose.microservices*.yml Standalone fallback image, reusable service image, and local/Dokploy microservices entrypoints

See docs/architecture.md for the full mental model (project layout, request lifecycle, watch-mirror controller pattern, where to start contributing).

Client API contracts

Rec Room is shut down, so the clients can only be studied statically. docs/recroom-2023-client-api-complete.md is the complete contract for the March-2023 client: every route it can call, the HTTP verb, the request and response shapes, and a per-subsystem diff against what this server currently implements. Read it before adding or changing any endpoint that build touches — it also documents how to recover verbs and response shapes from the disassembly yourself.

The equivalent material for the December-2020 client is in the recroom-2020-client-* files alongside it.

Microservices

The production topology is the gateway-fronted compose stack. docker-compose.microservices.yml starts the gateway, identity, rooms, notify, content, social, commerce, platform, moderation, web, and monolith fallback service hosts plus Compose-managed Postgres and Redis for local testing. docker-compose.microservices.dokploy.yml is the Dokploy version; it adds a cloudflared sidecar so Cloudflare Tunnel can route the apex and wildcard hostnames to the gateway without Dokploy domain rows.

The gateway routes owned host/path slices to the dedicated service hosts. The web service owns the apex, www, admin, and feed static hosts. The monolith fallback is still present for unknown route families, so public client URLs remain stable while the split continues.

The compose file intentionally does not start object storage. Point it at your separate S3-compatible instance:

$env:DORKNET_S3_ENDPOINT="https://your-s3-endpoint"
$env:DORKNET_S3_ACCESS_KEY="..."
$env:DORKNET_S3_SECRET_KEY="..."
$env:DORKNET_S3_REGION="garage" # or auto for R2
docker compose -f docker-compose.microservices.yml up --build

For deployment details, including Dokploy env vars, Cloudflare Tunnel routing, external Postgres, and S3 setup, see docs/deploy.md.

For the browser admin console, its routes, auth model, and production troubleshooting, see docs/admin.md.


Database & migrations

One EF migration (Migrations/<timestamp>_Initial.cs) captures the schema. SQLite dev DBs apply it via Database.Migrate(); Postgres uses EnsureCreated() behind a transaction-scoped advisory lock. The entity model is the single source of truth — to add a column, edit the entity, boot, done (on SQLite — Postgres prod needs a migration).

Data transforms that can't be schema (seed renames, computed backfills) go in DorkNet.Server/Data/LegacyUpgrades.cs. See DorkNet.Server/Data/MIGRATIONS.md for the full discipline.

Tables added after the consolidated Initial migration (e.g. SignupCodes / PendingDevices) are created by an idempotent CREATE TABLE IF NOT EXISTS step in Startup/DatabaseBootstrap.cs so both providers pick them up on existing DBs without a new migration (the Postgres path is EnsureCreated-only and never replays migrations).

Signup codes

When account creation is disabled (admin Settings → Server), the only way in is an admin-issued single-use signup code: generate one in the admin panel's Settings → Signup codes tab (with a descriptor + optional expiry), hand it to the player, and they redeem it on the site's /join page — which creates their account bound to the device their game client reported, so the next launch logs straight in.

Everyone-is-friends toggle

For small servers where searching + friend-requesting each other is friction, admin Settings → Server → "Everyone is friends" makes every account a friend of every other account. It writes no relationship rows — the friend graph is synthesized at read time (RelationshipQueries.EffectiveFriendIdsAsync, gated on ServerSettings.GlobalFriendsEnabled), so flipping it off reverts instantly. Blocks are still honored and the system/coach account is excluded, as are auto-generated Player_NNN placeholder accounts that never set a real username (they only appear if there's a genuine friend row). Flipping it broadcasts RelationshipsInvalid to every connected watch, which calls Relationships.RefreshList and re-pulls api/relationships/v2/get — so players see everyone appear (or disappear) without relogging. It covers the friends list, the friends-online HUD, and room-move presence fan-out.


Single-server defaults (review before going public)

Several values are hardcoded or defaulted for the convenience of running a small, trusted, single-operator server. They work out of the box for a group of friends but should be reviewed / adapted before exposing the server to the public internet:

  • Client build version (appVersion). Hardcoded to this branch's client build (20230317) in the presence/match/notification responses (MatchPlayerController, PlayerPresenceController, NotificationService, RoomsController). It must match the build everyone runs, or players get a "version mismatch" and can't join each other. If you serve a different client build, change it (ideally hoist it to one shared constant).
  • No real authentication. Accounts are keyed by a per-install deviceId the client generates (PlayerService.GetOrCreateByDeviceAsync) — not a verified identity (Steam ID is deliberately ignored so emulators work). Anyone who can reach the server with a patched client gets/creates an account. Use Settings → Server → disable signups + the signup-code flow to gate who can join; there is no password wall by default.
  • First account becomes admin. The first account created on a fresh DB is granted IsAdmin so there's always a root admin. On a public server, create your admin account first and verify no one else beat you to it.
  • Online status = Everyone. Presence reports StatusVisibility = 0 (Everyone) so every player appears online to every other player, matching the everyone-is-friends convenience. A public server may instead want to persist and honor each player's own visibility preference.
  • Room / matchmaking capacity. New matchmade sessions and subrooms default to 8 players and are capped at 40 (GameSessionService, RoomSceneEntity.MaxPlayers, admin MaxCapacity clamp). The in-game "Max Player Count in This Subroom" slider raises it per room (slider ceiling 40).
  • Avatar unlock clamp. The wardrobe clamp defaults high (Avatar:MaxUnlockedItems 4000 / per-slot 1000) for this desktop client — tune in config if you change the ownership model.
  • Hosts / domain. The apex and per-service subdomains come from DomainConfig (Domain:Apex) / appsettings; set these to your own domain. The Everyone-is-friends, signup-disable, and similar switches live in admin Settings, not source.

None of these are security boundaries as shipped — treat the server as trusted LAN/friends infrastructure unless you harden the items above.


Contributing

See main's CONTRIBUTING guide for the project-wide rules.

Branch-specific: schema changes on this branch don't flow to march-2020-03-10 automatically. If a fix applies to both, cherry-pick it across. Don't rebase across branches.


License

AGPL-3.0. Hosted forks must publish their source.

S
Description
No description provided
Readme AGPL-3.0 24 MiB
Languages
C# 82.7%
TypeScript 11.2%
PowerShell 1.4%
Python 1.3%
JavaScript 1.2%
Other 2.1%