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.
This commit is contained in:
Alexa
2026-10-15 20:29:32 +03:00
parent cebe7ae3ee
commit 975753deac
28 changed files with 3237 additions and 20 deletions
@@ -0,0 +1,5 @@
@echo off
rem One-click installer: runs install.ps1 next to this file.
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0install.ps1" %*
echo.
pause
+145
View File
@@ -0,0 +1,145 @@
<#
.SYNOPSIS
Install a DorkNet Rec Room .xapk onto a Meta Quest over adb.
.DESCRIPTION
Installs the APK, copies the OBB to the path the game expects, then launches
the app. Works in Windows PowerShell 5.1 and PowerShell 7. Run it with no
arguments from the package folder, or double-click install.cmd.
.PARAMETER Xapk
The package to install. Default: the only .xapk next to this script.
.PARAMETER Serial
adb serial of the headset when more than one device is connected.
.PARAMETER NoLaunch
Install only; don't start the app afterwards.
#>
[CmdletBinding()]
param(
[string]$Xapk,
[string]$Serial,
[switch]$NoLaunch
)
# adb writes progress to stderr; under 'Stop' Windows PowerShell would treat
# that as a failure, so exit codes are checked by hand instead.
$ErrorActionPreference = 'Continue'
Set-StrictMode -Version 2
Add-Type -AssemblyName System.IO.Compression, System.IO.Compression.FileSystem
function Say($m) { Write-Host "==> $m" -ForegroundColor Cyan }
function Fail($m) { Write-Host "ERROR: $m" -ForegroundColor Red; exit 1 }
# ---- find the package ----------------------------------------------------
if (-not $Xapk) {
$found = @(Get-ChildItem -Path $PSScriptRoot -Filter *.xapk -File)
if ($found.Count -eq 0) { Fail "no .xapk found next to $PSCommandPath; pass -Xapk <file>." }
if ($found.Count -gt 1) { Fail "more than one .xapk here; pass -Xapk <file>." }
$Xapk = $found[0].FullName
}
if (-not (Test-Path -LiteralPath $Xapk)) { Fail "package not found: $Xapk" }
$Xapk = (Resolve-Path -LiteralPath $Xapk).Path
# ---- find adb -----------------------------------------------------------
$adb = $null
$cmd = Get-Command adb -ErrorAction SilentlyContinue
if ($cmd) { $adb = $cmd.Source }
if (-not $adb) {
$roots = @($env:ANDROID_SDK_ROOT, $env:ANDROID_HOME, (Join-Path $env:LOCALAPPDATA 'Android\Sdk'), $PSScriptRoot) | Where-Object { $_ }
foreach ($r in $roots) {
foreach ($c in @((Join-Path $r 'platform-tools\adb.exe'), (Join-Path $r 'adb.exe'))) {
if (Test-Path $c) { $adb = $c; break }
}
if ($adb) { break }
}
}
if (-not $adb) {
Fail ("adb not found. Install Android platform-tools from " +
"https://developer.android.com/tools/releases/platform-tools and put adb on PATH " +
"(or unzip platform-tools next to this script).")
}
# ---- pick the headset ----------------------------------------------------
$lines = & $adb devices | Select-Object -Skip 1 | Where-Object { $_ -match '\S' }
$ready = @($lines | Where-Object { $_ -match "^(\S+)\s+device$" } | ForEach-Object { ($_ -split '\s+')[0] })
$unauth = @($lines | Where-Object { $_ -match '\sunauthorized$' })
if ($Serial) {
if ($ready -notcontains $Serial) { Fail "device $Serial is not connected and authorised (adb devices)." }
} elseif ($ready.Count -eq 1) {
$Serial = $ready[0]
} elseif ($ready.Count -gt 1) {
Fail ("more than one device connected: " + ($ready -join ', ') + ". Pass -Serial <id>.")
} elseif ($unauth.Count -gt 0) {
Fail 'the headset is connected but not authorised: put it on and accept "Allow USB debugging".'
} else {
Fail 'no headset found. Enable developer mode, connect it over USB, and accept "Allow USB debugging" in the headset.'
}
function Adb { & $adb -s $Serial @args }
$model = (Adb shell getprop ro.product.model | Out-String).Trim()
Say "headset: $model ($Serial)"
# ---- read the package ----------------------------------------------------
$zip = [System.IO.Compression.ZipFile]::OpenRead($Xapk)
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("dorknet-install-" + [guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
$me = $zip.GetEntry('manifest.json')
if (-not $me) { Fail 'not an XAPK: manifest.json missing.' }
$sr = New-Object System.IO.StreamReader($me.Open())
$manifest = $sr.ReadToEnd() | ConvertFrom-Json
$sr.Dispose()
$pkg = $manifest.package_name
Say "package: $($manifest.name) $pkg $($manifest.version_name) ($($manifest.version_code))"
function Extract($entry) {
$out = Join-Path $tmp ([System.IO.Path]::GetFileName($entry.FullName))
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $out, $true)
return $out
}
# ---- APK -------------------------------------------------------------
$apkEntry = $zip.GetEntry("$pkg.apk")
if (-not $apkEntry) { $apkEntry = @($zip.Entries | Where-Object { $_.FullName -like '*.apk' })[0] }
if (-not $apkEntry) { Fail 'no APK inside the package.' }
Say 'installing the app'
$apkFile = Extract $apkEntry
$res = (Adb install -r $apkFile 2>&1 | Out-String)
if ($res -match 'INSTALL_FAILED_UPDATE_INCOMPATIBLE|signatures do not match') {
Write-Host "An older copy of $pkg is installed with a different signing key." -ForegroundColor Yellow
Write-Host "It has to be removed first. Your account lives on the server, but local settings on the headset are lost." -ForegroundColor Yellow
$ans = Read-Host 'Uninstall it and continue? [y/N]'
if ($ans -notmatch '^(y|yes)$') { Fail 'cancelled.' }
Adb uninstall $pkg | Out-Null
$res = (Adb install -r $apkFile 2>&1 | Out-String)
}
if ($res -notmatch 'Success') { Fail ("app install failed:`n" + $res.Trim()) }
Remove-Item -Force $apkFile
# ---- OBB (expansion files) ------------------------------------------
foreach ($x in @($manifest.expansions)) {
if (-not $x) { continue }
$entry = $zip.GetEntry($x.file)
if (-not $entry) { Fail "package is missing $($x.file)." }
$dest = '/sdcard/' + $x.install_path
$have = (Adb shell "stat -c %s '$dest' 2>/dev/null" | Out-String).Trim()
if ($have -eq [string]$entry.Length) { Say "game data already on the headset, skipping ($([System.IO.Path]::GetFileName($dest)))"; continue }
Say ("copying game data ({0:N2} GB) - this takes a few minutes" -f ($entry.Length / 1GB))
$f = Extract $entry
Adb shell "mkdir -p '$([System.IO.Path]::GetDirectoryName($dest).Replace('\','/'))'" | Out-Null
Adb push $f $dest
if ($LASTEXITCODE) { Fail 'copying the game data failed (is there enough free space on the headset?).' }
Remove-Item -Force $f
}
} finally {
$zip.Dispose()
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}
Write-Host ''
Write-Host "Installed. In the headset: Library > Unknown Sources > $($manifest.name)." -ForegroundColor Green
if (-not $NoLaunch) {
Say 'launching (put the headset on)'
Adb shell monkey -p $pkg -c android.intent.category.LAUNCHER 1 2>&1 | Out-Null
}
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Install a DorkNet Rec Room .xapk onto a Meta Quest over adb (macOS / Linux).
#
# ./install.sh [package.xapk] [--serial <id>] [--no-launch]
#
# Needs adb (Android platform-tools) and unzip.
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
xapk="" serial="" launch=1
while [ $# -gt 0 ]; do
case "$1" in
--serial) serial="$2"; shift 2 ;;
--no-launch) launch=0; shift ;;
*) xapk="$1"; shift ;;
esac
done
say() { printf '\033[36m==> %s\033[0m\n' "$*"; }
fail() { printf '\033[31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
if [ -z "$xapk" ]; then
set -- "$here"/*.xapk
[ -e "$1" ] || fail "no .xapk found next to this script; pass it as an argument."
[ $# -eq 1 ] || fail "more than one .xapk here; pass the one to install."
xapk="$1"
fi
[ -f "$xapk" ] || fail "package not found: $xapk"
command -v unzip >/dev/null || fail "unzip is required."
adb="$(command -v adb || true)"
for c in "${ANDROID_SDK_ROOT:-}/platform-tools/adb" "${ANDROID_HOME:-}/platform-tools/adb" \
"$HOME/Library/Android/sdk/platform-tools/adb" "$HOME/Android/Sdk/platform-tools/adb" \
"$here/platform-tools/adb"; do
[ -n "$adb" ] && break
[ -x "$c" ] && adb="$c"
done
[ -n "$adb" ] || fail "adb not found. Install Android platform-tools: https://developer.android.com/tools/releases/platform-tools"
if [ -z "$serial" ]; then
ready=$("$adb" devices | awk 'NR>1 && $2=="device"{print $1}')
count=$(printf '%s' "$ready" | grep -c . || true)
if [ "$count" -eq 0 ]; then
if "$adb" devices | grep -q unauthorized; then
fail 'the headset is connected but not authorised: put it on and accept "Allow USB debugging".'
fi
fail 'no headset found. Enable developer mode, connect over USB, and accept "Allow USB debugging".'
fi
[ "$count" -eq 1 ] || fail "more than one device connected: $(echo $ready). Pass --serial <id>."
serial="$ready"
fi
a() { "$adb" -s "$serial" "$@"; }
say "headset: $(a shell getprop ro.product.model | tr -d '\r') ($serial)"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
manifest="$(unzip -p "$xapk" manifest.json)" || fail "not an XAPK: manifest.json missing."
field() { printf '%s' "$manifest" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:[[:space:]]*"\(.*\)"/\1/'; }
pkg="$(field package_name)"
[ -n "$pkg" ] || fail "manifest.json has no package_name."
say "package: $(field name) $pkg $(field version_name) ($(field version_code))"
say "installing the app"
unzip -q -o "$xapk" "$pkg.apk" -d "$tmp"
out="$(a install -r "$tmp/$pkg.apk" 2>&1 || true)"
if printf '%s' "$out" | grep -qE 'INSTALL_FAILED_UPDATE_INCOMPATIBLE|signatures do not match'; then
echo "An older copy of $pkg is installed with a different signing key; it has to be removed first."
echo "Your account lives on the server, but local settings on the headset are lost."
read -r -p "Uninstall it and continue? [y/N] " ans
case "$ans" in y|Y|yes) ;; *) fail "cancelled." ;; esac
a uninstall "$pkg" >/dev/null || true
out="$(a install -r "$tmp/$pkg.apk" 2>&1 || true)"
fi
printf '%s' "$out" | grep -q Success || fail "app install failed: $out"
rm -f "$tmp/$pkg.apk"
# Expansion files: every "install_path" in the manifest.
printf '%s' "$manifest" | grep -o '"install_path"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*:[[:space:]]*"\(.*\)"/\1/' |
while read -r path; do
dest="/sdcard/$path"
size="$(unzip -l "$xapk" "$path" | awk 'NR==4{print $1}')"
have="$(a shell "stat -c %s '$dest' 2>/dev/null" | tr -d '\r' || true)"
if [ -n "$size" ] && [ "$have" = "$size" ]; then say "game data already on the headset, skipping"; continue; fi
say "copying game data - this takes a few minutes"
unzip -q -o "$xapk" "$path" -d "$tmp"
a shell "mkdir -p '$(dirname "$dest")'"
a push "$tmp/$path" "$dest" || fail "copying the game data failed (is there enough free space on the headset?)."
rm -f "$tmp/$path"
done
echo
printf '\033[32mInstalled. In the headset: Library > Unknown Sources > %s.\033[0m\n' "$(field name)"
if [ "$launch" -eq 1 ]; then
say "launching (put the headset on)"
a shell monkey -p "$pkg" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || true
fi