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.
Port the December signup-code feature to the 2020.03 branch.
While account creation is disabled, the only way in is now an
admin-issued single-use signup code, redeemed on the public site's /join
page. Because the 2020.03 watch login is device-id-bound with no code
field, 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 shows the player the
device their own client just reported; they enter code + username and the
account is minted bound to that device, so the next launch logs straight
in. No watch changes.
- SignupCodeEntity + PendingDeviceEntity; tables created via idempotent
CREATE TABLE IF NOT EXISTS at boot (Program.cs Postgres patch block +
SQLite path) since they post-date the migration chain.
- 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 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
2020.03 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 via a shared ResolveRoomInventionsAsync
helper: direct matches (created in-place) unioned with the imported
cluster (most-frequent non-local CreationRoomId among the room creator's
inventions, since zip-imported inventions keep their original Rec Room
room id). Returns List<int> of distinct creator account ids.
Port the admin-editable weekly-challenge feature to the 2020.03 branch
and fix the skin-reward bug it had on December: rewards now actually
land in the player's inventory instead of only granting XP + tokens.
Server config: add WeeklyChallenges* columns to ServerSettings (Postgres
post-EnsureCreated patches + SQLite column patches), and port the
ServerSettingsService weekly methods/records. The reward record carries
the store item Slug — the reliable handle December lacked — so the grant
can resolve the real item.
ProgressionController: CurrentChallenge + updateProgress now read the
configured slate and reward instead of a hardcoded one, keeping the
2020.03 ChallengeMap/ChallengeGift wire shapes (verified against the
march dump: ChallengeGift TypeDefIndex 11455). The Gift now sends
AvatarItemType (Outfit when an AvatarItemDesc is set) so the watch's
gift-card render binds the avatar preview. On completing the week's
challenges the configured skin/consumable is granted via the new
StoreService.GrantItemFreeBySlugAsync (same inventory path as a purchase,
no charge), gated behind the once-only reward sentinel.
Admin: GET/POST settings/weekly-challenges + reward-options (avatar +
consumable only — 2020.03 StoreService has no equipment payload helper),
and the admin-ui Server settings page to edit the slate + reward. The
reward picker keys on item slug rather than December's fragile
descriptor-string match.
Docs: README no longer lists weekly challenges as December-only;
url-coverage notes the config-driven challenge endpoints + skin grant.
Use base './' so built index.html references ./assets/... instead of
/assets/..., letting the SPAs work under the Easy Launcher's
single-origin path prefix (https://<apex>/__dn/admin/) as well as the
subdomain layout. Root-absolute paths would skip the prefix and 404.
Render the flag from {ENABLE_TLS_TRUST_BYPASS} instead of hardcoding
true, so the launcher's opt-in toggle controls it. Off by default
since Localtunnel/Tunnelto serve valid certs.
MelonLoader (DorkNet.ClientMod) is the sole client patcher now:
- delete the DorkNet.ClientPatch project + tools/install-plugin.ps1
- drop the project from DorkNet.sln
- README / deploy docs: present DorkNet.ClientMod (install-melon.ps1)
as the patcher instead of the BepInEx plugin
- drop the dead install-plugin.ps1 reference from Program.cs's JWT
error and install-melon.ps1's DRM-fallback hint
- tidy stray 'BepInEx port' comments
Legacy-cleanup paths that REMOVE an old BepInEx install
(install-legacy-client.ps1, build-patcher-zip.ps1's old_plugin_paths)
are kept intentionally.
The harmony hook on PhotonNetwork.ConnectUsingSettings registers in
March's IL2CPP build but never actually fires — the watch's connect
path goes through a different (mangled) entry point. Log confirms
[patch-ok] PhotonNetwork.ConnectUsingSettings but no
[photon-appid] override line, ever.
Fix moves the override out of the prefix-only path:
- New PhotonPatches.TryApplyOverride(reason) does the
PhotonServerSettings.AppID / VoiceAppID / region mutation and
flips an OverrideApplied flag once it succeeds. Idempotent.
- OnUpdate polls TryApplyOverride every 6th frame (~10 Hz) until
it succeeds, then stops. The "type not found yet" log is
throttled to once on first miss + every 300th miss after, so
the file doesn't drown if PhotonServerSettings stays unloaded.
- Mod.ResolveType made internal (was private) so PhotonPatches
+ SavePatches can use the broader assembly-scan fallback —
the local FindGameType copies were too narrow and missed
IL2CPP-mangled type names. Both FindGameType helpers collapse
to one-line delegates to Mod.ResolveType.
- Prefix on ConnectUsingSettings kept as a belt-and-braces hook
(passes reason="prefix"); harmless if it fires, fine if it
doesn't.
- Photon dashboard has CustomAuth disabled on the AppId now, so the
watch never needs to attach userid/LoginLock to its Authenticate
ops. Removed the harmony hook on NetworkingPeer.CallAuthenticate,
the AuthValuesInjector_Prefix method, the GetCurrentLockToken
helper, and the InjectAuthValues Cfg field + config load. Bumped
the [config] log line to drop the InjectAuthValues field.
- Both dorknet-clientmod.json.template copies (source +
local-releases) lose the "InjectAuthValues" key.
- Code moved verbatim to DorkNet.ClientMod/attic/AuthValuesInjector.cs.attic
with a header explaining why it was parked and how to restore it.
The .cs.attic suffix keeps the SDK's auto-globber from compiling it.
- ClientMod: add SingleOriginBaseUrl config + URI rewrite path so the
client can talk to one https://<apex>/__dn/<service>/... base
instead of demanding wildcard subdomains. ServerHost rewrite still
the fallback when SingleOriginBaseUrl is empty.
- ClientMod config: InjectAuthValues now defaults to false. The
server's PhotonCustomAuthController already fail-opens
(Photon:RequireCustomAuth=false), so anonymous bootstrap is the
cleaner default and avoids Photon's CustomAuthFailed loop when a
WAF returns 403 to Photon's probe.
- Server: SQLite path now reads Database:SqlitePath config
(Database__SqlitePath env). Falls back to <bin>/data/dorknet.db
so existing Docker / standalone deploys are unchanged. Lets the
Windows launcher put the db under %APPDATA%\DorkNet.
- Server: DomainConfig + ConfigService updates for the single-origin
apex.
- .gitignore: exclude local-releases.zip.
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.
Stripping SteamStub DRM with Steamless is necessary but not
sufficient — Rec Room's Plugins/steam_api64.dll still tries to
talk to Steam at runtime and bails when Steam isn't loaded. The
working test installs swap in Goldberg Steam Emulator (~11 MB),
which fakes a logged-in user without needing Steam running.
- DorkNet.ClientMod/steam-stub/ holds the stub DLL + a 7-byte
steam_appid.txt (Rec Room is Steam app 471710)
- build-patcher-zip.ps1 copies both into the zip and writes a new
steam_stub block in manifest.json so the launcher knows where to
install them
- Manifest schema is back-compat: launchers that don't know the
steam_stub block just ignore it
Without this, the launcher's Steamless step produced a runnable
exe that exited within a second of launch because of the Steam
API failure. With it, the unwrapped exe starts up cleanly.
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. (Previously the csproj defaulted
RecRoomDir to a hardcoded user path and failed for anyone else.)
Verified by deleting bin/obj and running the script from scratch
on a machine without RecRoomDir set — produces a 20 MB zip with
the same manifest/DLL/MelonLoader.zip layout as before.
Targets Rec Room 2020.03.10 client (also serves 2020.03.06).
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 dorknet-clientpatch-{key}.zip
for v1-march-* Release uploads.
- .github/workflows/release.yml — on tag push publishes both
dorknet-server-march_2020_03_10-win-x64.zip and
dorknet-clientpatch-march_2020_03_10.zip as Release assets.
Same scrub rules as december; tools/build-patcher-zip.ps1 added to
the maintainer-side allow-list so it survives.