Files
DorkNet/DorkNet.Quest
Alexa 975753deac feat: DorkNet.Quest, a sideload patcher for the 2023 Quest client
Patches the Quest build of Rec Room 2023.03.21 (20230317 / 2128210, the
same appVersion as the PC client) to connect to a DorkNet server, using
one native hook library loaded via DT_NEEDED on libmain.so.

- host rewrite onto <svc>.<ServerHost> or a {service}-dev.example.com template
- Photon app ids and region injection (PhotonRegion must match PC)
- TLS / pinning / image-signature bypass, integrity callbacks neutralised
- no-Meta-account build; the server treats platform id 0/1/blank as a
  placeholder and keys those accounts by device id
- build.ps1 -Obb packages a DorkNet-<ver>.xapk with one-command
  installers for Windows and macOS/Linux; app is named via AppName
- README, docs/quest.md and NOTICE updated; credits RecFlare and
  And64InlineHook (MIT)

Tested on a Quest 2: all hooks arm, requests reach the dev server.
2026-10-15 20:29:32 +03:00
..

DorkNet.Quest

Sideload-patches the Meta Quest build of Rec Room 2023.03.21 (versionName 20230317, versionCode 2128210) to connect to a DorkNet server.

It is the Quest counterpart of DorkNet.ClientMod (the PC MelonLoader mod) plus tools/patch-client.ps1 (the PC Photon byte-patch). Android/IL2CPP has no MelonLoader, so everything is done in one injected native library (libdorknet.so): it is added as a DT_NEEDED entry on libmain.so so the loader maps it before Unity, then it installs arm64 inline hooks at addresses resolved from the build's own dump.cs.

This build reports appVersion 20230317 — the same version the PC client reports — so a Quest player patched with this tool shares rooms, presence and Photon shards with PC DorkNet players with no server-side version change.

This ships no Rec Room code or assets. You supply your own extracted store APK and OBB. It disables the client's certificate pinning and image-signature checks so a self-hosted server works; do not redistribute game binaries.

What it does

Hook Effect PC equivalent
HTTPManager.SendRequest rewrite <svc>.rec.net → <svc>.<ServerHost>, or into a {service} template such as {service}-dev.example.com for hyphen-style hosts (the name server supplies every other URL) ClientMod UriPatches
Photon AppSettings factory inject the operator's Realtime/Voice/Chat app ids and fixed region (PhotonRegion, must match the PC clients) tools/patch-client.ps1
PlatformSettings.get_AppID / get_MobileAppID return the operator's Meta app id (only when set) —
UnityTlsContext.VerifyCallback + BouncyCastle NotifyServerCertificate accept the self-hosted TLS chain ClientMod TlsPatches
three certificate-pin verifiers accept the chain (pins never match a self-hosted host) ClientMod DisableTLSPinning
image RSA/SHA1 verify accept Content-Signatures the server signs with its own key PC ImageSigning
hile-warning / unknown-DLL / file-hash / ToxMod callbacks neutralised so a re-signed, hook-loaded client is not warned/quit ClientMod AntiTamper/ToxMod/FileHash
integrity report + quit funnels log only — names any new rejection in logcat —
Oculus ovr_* platform calls (no-Meta build only) steer the entitlement/login path to the client's own credential screen —

Two builds, selected by OculusAppId

  • Set — the Meta build: Meta's real entitlement + login run against the app you registered; the platform hooks stay disarmed. Players need a Meta account, and your package + signing cert must be registered with that app.
  • Blank — the no-Meta-account build: the platform path is steered past Meta's checks to Rec Room's own username/password screen. It authenticates nothing — every headset sends the placeholder platform user id 1. DorkNet's server treats platform id 1 (and 0/blank) as a non-identity and keys accounts by deviceId instead (PlayerService.IsPlaceholderPlatformId), so players are not merged into one account and are never offered each other's "remembered logins". Never add a server rule that accepts platform id 1 as an identity.

Prerequisites

  • Android NDK r27c-class (ANDROID_NDK, or ndk/<ver> under ANDROID_SDK_ROOT) — compiles the hook library against bionic.
  • Android SDK build-tools (zipalign, apksigner) — ANDROID_SDK_ROOT.
  • apktool apktool.jar (APKTOOL_JAR, or next to ANDROID_SDK_ROOT).
  • JDK (keytool for the debug keystore; apksigner needs a JRE).
  • Python 3 with LIEF (pip install lief) — the DT_NEEDED injection.
  • Il2CppDumper — to produce dump.cs for the target APK (see below).
  • Your own store APK (com.AgainstGravity.RecRoom, versionCode 2128210) and its matching OBB.

On Windows with Controlled Folder Access on, the NDK linker and apksigner can't write under Documents. build.ps1 builds in %TEMP% by default; keep the work dir outside protected folders.

Build

# 1. Dump the target build (Il2CppDumper), once, into a scratch dir:
dotnet Il2CppDumper.dll `
  <extracted>\lib\arm64-v8a\libil2cpp.so `
  <extracted>\assets\bin\Data\Managed\Metadata\global-metadata.dat `
  <scratch>\dump

# 2. Configure:
copy DorkNet.Quest\dorknet-quest.cfg.example DorkNet.Quest\dorknet-quest.cfg
#   edit ServerHost, Photon app ids, (optional) OculusAppId, PackageName

# 3. Build the signed APK:
$env:ANDROID_SDK_ROOT = 'C:\path\to\android-sdk'
$env:ANDROID_NDK      = 'C:\path\to\android-sdk\ndk\27.2.12479018'
$env:APKTOOL_JAR      = 'C:\path\to\apktool.jar'
DorkNet.Quest\scripts\build.ps1 `
  -Apk    C:\path\to\RecRoom-store-2128210.apk `
  -DumpCs <scratch>\dump\dump.cs `
  -Obb    C:\path	o\main.2128210.com.AgainstGravity.RecRoom.obb
# -> %TEMP%\dorknet-quest-build\dorknet-recroom-20230317.apk
# -> %TEMP%\dorknet-quest-build\DorkNet-Quest-20230317\   (with -Obb)

With -Obb, the build also runs scripts/package.ps1, which writes a release folder ready to hand to players:

File Purpose
DorkNet-20230317.xapk standard XAPK: manifest.json, the APK, the OBB at its install path, icon.png
install.cmd / install.ps1 one-command install on Windows (double-click install.cmd)
install.sh one-command install on macOS / Linux
README.txt player instructions

To package an APK you already built, run scripts/package.ps1 -Apk <apk> -Obb <obb>. The package contains game binaries from your store copy, so only share it with people who own the game.

build.ps1 chains: resolve targets → generate headers → compile libdorknet.so → apktool decode → package rename + LAUNCHER + manifest → app name (AppName) → drop the library in + DT_NEEDED it onto libmain.so → apktool build → zipalign → apksigner (v2+v3). It generates a fresh debug keystore in the work dir on first run. Keep that keystore — Meta ties tokens to app id + package

  • signing cert, so re-signing with a different key breaks any Meta registration. It is never committed.

Rebasing onto a different build

Obfuscated Assembly-CSharp names are re-randomised each release but shared with the PC build of the same release, so the PC decompile is the oracle. For a new build:

  1. Dump it with Il2CppDumper.
  2. Re-run scripts/resolve_targets.py --dump dump.cs --il2cpp libil2cpp.so --version-name <v> --version-code <c> --objdump <ndk>/llvm-objdump and confirm every target resolves, is ≥ 20 bytes, and has a real frame-setup prologue. Adjust targets/spec.json signatures if a name/shape changed (cross-check against the PC decompile first).
  3. Commit the new targets/<v>-<c>.json.

The library refuses to arm any target whose 20-byte fingerprint does not match the running libil2cpp.so, so a table can never be applied to the wrong build.

Install

From a release folder, one command does everything (finds adb and the headset, installs the APK, copies the OBB to the right path, skips the OBB if it is already there, and launches the app):

.\install.cmd                   # Windows (or double-click it)
./install.sh                    # macOS / Linux

Both take an optional package path, -Serial/--serial for a specific headset and -NoLaunch/--no-launch. SideQuest and other XAPK installers can open the .xapk directly. The app shows up as AppName (default DorkNet) under Library > Unknown Sources.

By hand:

PKG=net.dorknet.RecRoom      # your PackageName
VC=2128210
adb uninstall $PKG 2>/dev/null
adb install -r dorknet-recroom-20230317.apk
adb shell mkdir -p /sdcard/Android/obb/$PKG
adb push main.2128210.com.AgainstGravity.RecRoom.obb \
  /sdcard/Android/obb/$PKG/main.$VC.$PKG.obb

The on-device OBB filename must be main.<versionCode>.<package>.obb — Unity rebuilds that exact path at runtime, so the source file's own name is irrelevant, only the destination is.

Check it on the headset

Wear the headset — off-head it sleeps within seconds and never finishes init, which looks like a crash.

adb logcat -c
adb shell am start -n net.dorknet.RecRoom/com.unity3d.player.UnityPlayerActivity
adb logcat -d -s DORKNET

A healthy boot logs: DorkNet.Quest hooks loaded, init: build 20230317/..., every armed <target>, then intercepted https://ns.rec.net/ -> https://ns.<host>/, unitytls verify: forcing 0x8 -> SUCCESS, [http<-] 200 ..., and photon appsettings: RT=... Voice=... Chat=....

Symptom Likely cause
REFUSING <target>: ... fingerprint mismatch wrong APK for this target table — rebuild from the matching store APK, or rebase
install fails -124 / NO_CERTIFICATES signing/align step — rebuild with build.ps1, don't hand-zip
boots to an empty world OBB missing or named for the wrong versionCode
[integrity] report reason=... NetworkCertificateMismatch then a quit TLS bypass off, or a pin hook didn't arm
[http<-] shows a Cloudflare challenge page edge WAF blocked the request before your origin
multiplayer never connects Photon app ids not set in the cfg
Quest and PC players never see each other's rooms PhotonRegion differs from the PC ClientMod PhotonCloudRegion

Layout

Path Purpose
src/dorknet_quest.cpp all hooks + init
src/config.h / config.cpp compile-time defaults + runtime cfg loader
src/third_party/And64InlineHook/ vendored arm64 inline hooker (MIT, © Rprop)
targets/spec.json target signatures (class header + method), build-independent
targets/<ver>-<code>.json resolved RVAs + fingerprints for one build
scripts/resolve_targets.py dump.cs + libil2cpp.so → target table (+ prologue check)
scripts/gen_targets_header.py target table → targets_generated.h
scripts/gen_config_header.py dorknet-quest.cfg → config_generated.h
scripts/inject_needed.py LIEF DT_NEEDED injection
scripts/build.ps1 one-shot: store APK → signed patched APK (+ release package with -Obb)
scripts/package.ps1 patched APK + OBB → .xapk + installers
scripts/installer/ install.ps1, install.cmd, install.sh copied into each package
dorknet-quest.cfg.example config template; copy to dorknet-quest.cfg (git-ignored)

Credits

  • RecFlare by djdevin. Their Quest client showed the way this patcher is built: one native hook library loaded through a DT_NEEDED entry on libmain.so, and which client checks need hooking. They target a newer build (20230414), so every hook target here was re-derived from the 20230317 build; no RecFlare code is included.
  • And64InlineHook © Rprop (MIT) — vendored under src/third_party/.
  • Il2CppDumper, LIEF.