When the server is pointed at the bucket CI publishes builds to
(DorkNet:Downloads:BaseUrl), the site lists them on a new Downloads page and
the Quest page offers "Latest build" next to "Choose file".
The server reads manifest.json and the files over HTTP on the internal network
and streams them through with Range support (DownloadsController); only files
the manifest lists are served. The xapk reader now reads from a byte source,
either the picked File or the server URL via Range requests, so the browser
installer streams the APK and OBB to the headset without downloading the
package first. Unset, both features stay hidden.
Security:
- roomcode PUT now requires instance ownership (any player could plant
or relocate codes on foreign private instances)
- /platformid/{accountId} requires auth (anonymous Steam/Oculus id
enumeration oracle)
- request tracing no longer captures successful /thread bodies by
default (chat messages landed unredacted in logs); opt in via
Trace__SuccessBodyPaths
- anonymous action-link renewal clamped to 720h per consume
Data integrity:
- buyPurchaseReminder upserts its objective sentinel (second purchase
threw UNIQUE after the wallet was debited)
- progression reward gift + idempotency sentinel commit in one
transaction (crash between them double-minted the reward)
- ClubPermissionsGalleryAndModeration migration defaults corrected to
the entity contract (CommentsJson '[]', ClubChatEnabled true); new
one-shot ClubChatDefaultRepair migration fixes databases that
applied the wrong defaults; bootstrap repairs '' CommentsJson rows
- admin subroom-maxplayers audit entry commits with the mutation
- account purge covers CustomAvatarItems (ownership deleted, created
items reassigned to the system account)
- duplicate photo reports ignored while one is unresolved
Stability:
- bootstrap only swallows the expected duplicate-column error; locked
databases and schema failures now fail startup instead of the first
endpoint
- clone blob write failure degrades to an empty sub-room instead of a
500 after the clone row committed
- resumed photo-feed cursor stamped DateTimeKind.Utc (Npgsql rejects
Unspecified for timestamptz)
- unreadcounts queries per requested room instead of materialising the
whole comment keyspace
Tests:
- destroys-the-probe-session template guard is case-insensitive
74/74 tests pass.
Adding DataBlob/DataBlobHash at room level for reader parity was wrong, and the
dorm is what proved it: rooms came back "the data in the room you are trying to
load is corrupt", and every room loaded fine before the key existed.
Which blob a room loads is decided per INSTANCE and per SUB-ROOM, not per room.
The matchmake response carries it (a dorm's is per-player,
dorm_p{id}_v*.dat) and each sub-room carries its own. A room-scoped answer to
the same question is one the client preferred, and for a dorm it named a file
that was never written.
A registered slot does not oblige us to fill it: Newtonsoft leaves an absent
key at its default, which is the state every room loaded in. The parity test
records both keys as deliberately omitted with the reason, so restoring them
takes a decision rather than a tidy-up.
Also drops the room-level/sub-room DataBlob invariant test added an hour ago —
there is no room-level DataBlob left for it to check.
Note for the clone bug: the captured response now shows the clone carries all
41 keys the reader registers and the client still rejects it, so key parity is
NOT that failure. It is a value the reader refuses, not a missing name.
When the client rejects a response it still answers HTTP 200 and throws while
reading it, so the server log shows a clean 200, the client shows a bare
"Failed to copy room" / an unopened chat with no URL, and neither side records
the payload. Every diagnosis then comes down to guessing which key the strict
reader tripped on.
Success bodies are now logged for /player, /thread and /clone, and the response
cap is raised to 8000 chars for those paths — a room-details payload is a few
KB and a 300-char truncation answers nothing. Everything else is unchanged:
failures are still always read, successes still discarded, bodies still pass
through RedactSecrets.
Override with the Trace__SuccessBodyPaths env var (comma-separated substrings,
empty to disable).
- Implemented ProgressionEventsController to handle progression event retrieval and active events.
- Created RoomConsumablesController for listing and purchasing room consumables.
- Developed RoomCurrenciesController for managing room currencies, including creation, updates, and balance retrieval.
- Added RoomEarningsDistributionsController to fetch earnings distribution data for rooms.
- Introduced ScreenShareReportsController for reporting screen share incidents.
- Established SubscriptionSeasonsController to provide current subscription season details and rewards.
- Launched UgcPurchasablesController for managing user-generated content purchasables, including bulk operations.
- Added new data entities for customization and room economy, including custom avatar items, room currencies, and UGC purchasables.
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.