- Updated Sidebar component to consolidate navigation items under new categories: Moderation, Content, and Operations.
- Removed outdated pages and integrated their functionalities into tabs under Players, Activity, Content, and Settings.
- Introduced new Activity and Content pages to house Audit, Player Logs, Community Board, and Loading Tips with tabbed navigation.
- Created a unified Moderation page combining Players, Bans, and Reports with tabbed navigation.
- Refactored individual pages (e.g., Audit, Bans, CommunityBoard, etc.) to support embedded mode for better integration within modals.
- Added Tabs component for consistent tab navigation across multiple pages.
- Updated README to reflect changes in navigation and settings structure.
- Removed Grants page as it is not yet implemented.
Add POST players/{id}/username + a Username field in the player detail's
Profile tab. Changes only the unique @handle (not the display name) and
rejects a collision outright rather than auto-suffixing, so the admin
gets the exact handle they typed. Same 2-24 char letters/digits/_/-
rule as the account API; logged to the audit trail as old -> new.
While account creation is disabled, the only way in is now an
admin-issued single-use signup code (Discord-invite style), redeemed on
the public site's /join page.
Because the 2020 watch login is device-id-bound with no code field, the
flow captures it server-side instead: a new device refused at the
signups-disabled gate (PlatformLogin / account create) is stashed in a
PendingDevices table keyed by the real client IP (CF-Connecting-IP aware).
The /join page then shows the player the device their own client just
reported, they enter their code + username, and the account is minted
bound to that device — so the next launch logs straight in via the
existing device-id path. No watch changes.
- SignupCodeEntity (code, descriptor, optional expiry, redeemer) +
PendingDeviceEntity; idempotent CREATE TABLE in DatabaseBootstrap so
both providers pick them up without a new migration.
- SignupCodeService: generate / list / revoke / redeem + pending-device
capture, single-use + expiry + revoke validation.
- Admin: GET/POST signup-codes + revoke endpoints and a Signup codes
admin-ui page (generate with descriptor/expiry, status table, revoke).
- Public: anonymous /api/site/v1/join pending-devices + redeem, and a
/join site page (+ header link).
- README documents the flow + the post-Initial table-create approach.
The public site built room/profile image URLs against a hardcoded
img.rec.net fallback for any non-localhost host, so thumbnails loaded
from real Rec Room (Azure-fronted — hence the TiPMix / x-ms-routing-name
cookies the browser rejected) and DorkNet custom-room images could never
display. Derive the CDN apex from the live host instead (shared
imageApex helper in types.ts, used by profileImageUrl and Rooms.tsx) so
images come from this server's own img.{apex}.
Add the two RR-Original thumbnails as static CDN files in data/images so
img.{apex} serves them directly: image_BowlingAlley.png and
image_PaintballKeyArt.png (both real PNGs).
api/inventions/v1/creatorIds was a stub returning an empty list, so the
watch's GetCreatorIdsOfInventionsInRoom never learned which creators it
must request invention permissions for before spawning a room's
invention-based geometry. Resolve the real set from
InventionEntity.CreationRoomId and factor the room->inventions lookup the
/room endpoint already used into a shared ResolveRoomInventionsAsync
helper (direct matches unioned with the imported cluster). ByRoom now
calls the helper too. creatorIds returns List<int> of distinct creator
account ids.
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.
The 2020.12 watch's PlayerPresence.FriendlyPresence shows roomInstance.name
verbatim and defaults to the literal "^DormRoom" — it expects a player's
current room name to carry a leading ^. Serialize the RoomInstance wire
"name" as "^"+Name and strip it on deserialize (presence round-trips the
DTO through Redis), keeping the C# Name raw so the photonRoomId stays
single-^ and invite messages keep the plain name.
SQLite path now honors Database:SqlitePath config (Database__SqlitePath
env var). Falls back to the legacy <bin>/data/dorknet.db default so
existing Docker / standalone deploys are unchanged. Lets the Windows
launcher put the db under %APPDATA%\DorkNet — server cache reinstalls
no longer wipe player accounts / rooms.
Three carry-overs from the march branch (commits 80cc4ce, cd9d105) and
the launcher-side Vite build fix, all applicable to december without
modification:
1. Vite admin-ui + site configs switch from `base: '/'` to `base: './'`.
Built index.html now emits `<script src="./assets/...">` instead of
`/assets/...`, so the Easy Launcher's single-origin path-prefix mode
(`https://<apex>/__dn/admin/`) resolves assets under the prefix
instead of skipping it and 404'ing on the wrong server host.
2. Mod.ResolveType promoted from private to internal so PhotonPatches'
and SavePatches' local FindGameType helpers can delegate to the
broader assembly-scan fallback (was misssing IL2CPP-mangled type
names like the literal "PhotonNetwork"). Both helpers collapse to
one-line `=> Mod.ResolveType(name);`.
3. Parked AuthValuesInjector_Prefix into
DorkNet.ClientMod/attic/AuthValuesInjector.cs.attic. In december
this method was defined but **never wired to a harmony hook** in
OnLateInitializeMelon, so it was effectively dead code. Removed
the method + GetCurrentLockToken helper + Cfg.InjectAuthValues
field + LoadConfig reader + log mention. The .cs.attic suffix
keeps the SDK's auto-globber from compiling the parked file.
Restore notes inside the attic file describe how to re-enable —
for december the suggested hook is a postfix on
PUNNetworkManager.EMGGKBALALH (mutate the AuthValues there)
instead of march's NetworkingPeer.CallAuthenticate which never
fires in IL2CPP.
/api/avatar/v4/items truncated owned items by maxItems (was 30) and
maxPerSlot (was 4). The per-slot cap silently dropped any newly-bought
wardrobe item once a slot held 4+ entries — the 'I bought it but it's
gone after relog' symptom. Defaults raised to 100 / 20.
Clamp ranges left at [1,120] / [1,20] (prior maintainer note locked
these to watch render-budget testing — raising the ceiling without
retesting risks BrowsableAvatarItem / DynamicAvatarItemImposter
thrash inside the watch's wardrobe drawer).
Sort tiebreaker added: ThenByDescending(OriginalIndex) so when
truncation has to drop entries, it drops the OLDEST (starter seed)
items rather than the just-purchased one. GrantWardrobeAsync and
the gift-consume path both append new GUIDs to the end of
InventoryJson, so list index == purchase order.
/roomserver/roomsandplaylists/hot and the path-style sibling
/roomserver/hot_roomsandplaylists/{tags} now return rooms-only — the
watch's Playlists tab fetches via /api/curatedroomplaylists +
/roomserver/playlists/{id} independently (verified
FPNPBJBCMKB.txt:053, EJDCNGBEICB.txt:1659/2192), so dropping playlists
from the Hot endpoint just stops mixing them into the rooms tab.
Wire shape unchanged: HJKAOMOICJG reads TotalResults + Results
(HJKAOMOICJG.txt:039/045) and the MKAMHOIHOJK factory discriminates
on the roomId key (MKAMHOIHOJK.txt:014), accepting a Results list
that's 100% room entries.
Take cap raised from 12 to 100 — the old limit only surfaced the
dozen highest-HotScore rooms, hiding the rest of the public catalog.
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.