A Jenkinsfile builds, on push (only what changed) or on demand:
- the PC patcher zip (MelonLoader, the mod, config template, Steam stub),
- the full PC client (stock build + patcher + config),
- the Quest APK and .xapk.
Stock builds come out of the restic build archive on the FTP: restic
through its rclone backend, picked by snapshot tag per run and cached.
The FTP login and restic password are Jenkins credentials, and the
repository folders are environment settings, so nothing about the
archive is fixed in the repo.
ci/jenkins/Dockerfile is the Jenkins image with the toolchain added
(.NET SDK, Android NDK and build-tools, apktool, Python with LIEF,
restic, rclone), for the existing Jenkins service.
The build scripts move from PowerShell to Python so they run the same
on Windows, Linux and in CI:
- DorkNet.Quest/scripts/build.py replaces build.ps1 + package.ps1; it
can use a committed hook table (checked against the APK's
libil2cpp.so) instead of dump.cs, and takes a persistent keystore.
- tools/build_pc.py replaces build-patcher-zip.ps1 (now with the
2023 install layout) and adds the full-client build.
Setup steps are in ci/jenkins/README.md.
CircuitsV2 limits are served as UGC.CircuitsV2.Limit.* gameconfig keys and
are editable from admin Settings. Retail drops any send over 5 RPCs/frame
or 1024 bytes/60 frames; both network budgets now default to 1,000,000,000.
The creation limits are raised too, each against the hazard the client
binary shows for it:
- Chips 3000 -> 30000: the chip-culling manager preallocates four
List<Chip>(Chips), so the 1M clamp stays.
- Compute 50000 -> 500000: a per-frame budget with no allocation; the 10M
cap keeps its counters from wrapping.
- CloudValues 100 -> 1000, InkPerChip 15 -> 0: InkPerChip is an ink COST,
multiplied by min(chips, CloudValues), so lowering it is the increase.
The clamps tighten to 10000 / 30000 to keep that product from overflowing
(the room ink cap is a hard-coded 30000).
- UnculledChips / VisibleChips 500 -> 3000: render-cost counts only.
Room save uploads may now be up to 100 MB (Kestrel's 30 MB default would
413 a big circuits room). Contract and evidence:
docs/recroom-2023-circuitsv2-limits.md.
Also includes the Rec Royale night mode client feature
(docs/recroom-2023-scene-moods.md) and the admin blob enable/reset for
baked RRO sub-rooms.
Extends the parity check into the payload's nested objects — sub-room 0, the
first role, stats and the first tag each have their own generated reader, and a
key set that disagrees with one of those breaks the room just as hard as a
wrong top-level set.
It immediately found seven invented keys. Six on every sub-room (CurrentSave,
DefaultMatchmakingPolicy, LastModeratedSaveModerationState, ShouldAutoStageSaves,
StagedSubRoomDataSaveId, SubRoomUnityAssetId) and IsPrimaryGenre on every tag,
whose real shape is exactly {Tag, Type}. Six of the seven appear NOWHERE in the
decompile; SubRoomUnityAssetId exists but belongs to another DTO, not this one.
The extractor's minimum field count drops from 4 to 2, which is what let this
hide: a room tag has two fields, so a 4-field floor skipped its reader entirely
and the invented third key had nothing to be checked against. The alias rule in
fields_in() is what rejects false positives — the field count never was.
This does not explain the clone failure on its own: the captured response
carries all 41 top-level keys and is still rejected. But it removes the whole
class of "we send something the reader has no slot for" from the search.
Both bugs were the same shape: we answered HTTP 200 with a key set the client's
generated reader did not agree with, the reader threw mid-deserialise, and it
surfaced later as a bare "Failed to copy room" / a chat that never opened, with
no URL in the client log and no payload in the server log.
Rather than guess at fields, tools/extract-client-dto-keys.py reads the readers
out of the ISIL dump. Cpp2IL emits each generated reader's property names as
string literals in registration order (three spellings per field: Pascal,
camel, lower), so the extracted catalogue is the client's own contract. 156
reader types land in DorkNet.Server.Tests/data/client-dto-keys-2023.json, and
ClientDtoParityTests asserts our real responses against them — nothing missing,
nothing extra.
Chat (EKEFFNBIOHJ) was missing exactly one key: LatestMessage. It was only
emitted when a thread HAD messages, so it vanished from the one response where
it was needed — the thread that thread/withmembers has just created, which by
definition has none. It is now always present, null when there is nothing to
report; a registered slot wants a value and an absent key is not one.
Room details (GLEGPFFPDBE) was missing DataBlob, DataBlobHash, MaxPlayers,
ToxmodEnabled and RankingContext, and sent 15 keys the reader has no slot for.
The 15 are gone: UgcVersion, UgcSubVersion, MinUgcSubVersion, PublishState,
PublishStateAvailability, BoostCount, ExcludeFromLists, IsJuniorCreated,
IsRecRoomApproved, IsRoomLinkedToRecRoomStudio, IsStudioRoom, StudioSessionId,
RestrictedCircuitsAllowListNames, AllowNewUsers, AllowsNewUsers.
That deletion also removes a comment asserting the 2023 client reads UgcVersion
to build its asset-bundle URL. It does not — the literal appears nowhere in the
entire decompile, and neither do PublishState or BoostCount.
- Updated `url-coverage.md` to clarify the behavior of the `api/rooms/v2/myRecent` endpoint.
- Enhanced `StoreService.cs` to include new properties for color variants: `combination_id` and `descriptor`.
- Modified logic in `StoreService` to validate color variants based on the new descriptor instead of swatch GUID.
- Updated database bootstrap scripts to create new tables for `RoomConsumables` and `RoomConsumableOwnership`, including necessary indexes.
- Revised README to reflect changes in debugging options and the introduction of `DorkNet.DebugMod`.
- Updated documentation on request expectations to clarify server-side behavior for room visibility.
- Enhanced installation script to provide clearer instructions for enabling the debug console.
- Added a new document detailing the process for dumping avatar items and color variants from the Rec Room client.
- Removed Photon configuration fields from dorknet-clientmod.json.template.
- Added new backtrace and Azure Speech configuration endpoints in ConfigController.
- Updated response format for IsCreationEnabled and IsRenderingEnabled endpoints in CustomAvatarItemsController.
- Enhanced InfluencerPartnerProgramController to include additional player properties.
- Implemented RoomKeepsakes endpoint in KeepsakesController to retrieve keepsakes by room ID.
- Modified ReportsController to standardize response for Hile reporting.
- Added PlayerDataForMe and RoomsRequiring endpoints in RoomsController for enhanced player data retrieval.
- Introduced new dorm matchmaking methods in MatchController for player session management.
- Updated MatchPlayerController to handle exclusive login functionality.
- Revised install scripts to reflect changes in Photon AppId handling and patching process.
- Added new patch-client.ps1 script for applying static byte patches to the Rec Room client.
Resolve modify/delete conflicts in favor of the microservices scaffold:
re-add the Contracts, ServiceDefaults, Gateway, and Identity/Notify/Rooms
service projects that december had removed. Keep december's branding and
route-case updates, and preserve its DorkNet.ApiRouteCoverage tool in the
solution (union of both branches' projects).
MelonLoader (DorkNet.ClientMod) is the sole client patcher now, so drop
the BepInEx plugin and its BepInEx-only installer:
- delete the DorkNet.ClientPatch project + tools/install-plugin.ps1
- drop the project from DorkNet.sln
- README / architecture / deploy docs: present DorkNet.ClientMod
(install-melon.ps1) as the patcher instead of the BepInEx plugin
- fix install-melon.ps1 fallback hint that pointed at the deleted
install-plugin.ps1
- tidy stray 'BepInEx port' comments in Mod.cs / PhotonCustomAuthController
Legacy-cleanup paths that REMOVE an old BepInEx install (install-legacy-client.ps1,
the old_plugin_paths entry in build-patcher-zip.ps1) are kept intentionally.
MelonLoader 0.6.x scans <install root>\Mods\ for plugin DLLs and
ignores MelonLoader\Mods\, so 'plugin_dest' = 'MelonLoader/Mods'
in the manifest meant DorkNet.ClientMod.dll was never loaded —
Player.log showed '0 Mods loaded.' and the watch booted unpatched.
Mirrors march-2020-03-10 bc8bcd9. Stripping SteamStub DRM with
Steamless is necessary but not sufficient — Rec Room's
Plugins/steam_api64.dll still talks to Steam at runtime and
bails when Steam isn't loaded. Bundles Goldberg Steam Emulator
(~11 MB) into the patcher zip, plus a 7-byte steam_appid.txt
pointing at Steam app 471710 (Rec Room). New steam_stub block
in manifest.json tells the launcher where to install them.
The mod resolves every game-side type at runtime via
AccessTools.TypeByName, so the IL2CPP interop assemblies
(Assembly-CSharp, Photon3Unity3D, UnityEngine.*) in the csproj were
dead references. Dropped them entirely — only MelonLoader's own
DLLs are actually needed at compile time.
tools/build-patcher-zip.ps1 now downloads MelonLoader BEFORE the
dotnet build and points MelonLoaderDir at the extracted contents,
so a fresh contributor checkout builds the patcher zip cold with
no Rec Room install required.
Mirrors the march-2020-03-10 1f407ef change.
Targets Rec Room 2020.12.18 client.
Re-import after adding:
- DorkNet.ClientMod/dorknet-clientmod.json.template — config schema
the launcher's manifest-driven patcher renders into UserData/.
- tools/build-patcher-zip.ps1 — produces the dorknet-clientpatch-{key}.zip
Release asset the launcher fetches.
- .github/workflows/release.yml — auto-publishes both Release assets
on v1-december-* tag push.
Scrubbed via maintainer-side scrub-for-public.ps1: same drop rules as
the prior import, plus build-patcher-zip.ps1 added to the tools/
allow-list so it survives the scrub.