Files
Radicade/docs/QUEST-20230907-TEST.md
T
2026-10-16 23:33:13 -07:00

154 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Quest 20230907 server test
## Current investigation: post-login `doll` and saved-account fallback (2026-10-07)
After the user supplied a valid birthday, login proceeded further. At 19:18:22 client emitted fatal quit 1550502249 after a Hile report; VPS message was `33489407,7`. Exact September disassembly locates the comma-pair formatter in HGEONHBDDLK.PLGOEDKHHBI (0x4f91ffc), gated by `AntiHile.QD`, reporting type 701. Live config verified that key=true. GameConfigAPI now overrides only that key=false; two focused tests pass for seeded true and missing-key cases, including preservation of unrelated values and stored data. Default Debug test build hit a locked DLL; isolated QuestConfigTest build passed. Commit 04826ca pushed to origin/2023 after cherry-picking the tested local 0b7894b onto remote 2a2df52 in artifacts/quest-config-integration. Primary dirty workspace preserved. VPS deployment and gameplay retest pending. Separate crm/me/config exception observed; not established as the cause of this shutdown.
FreshHTTP test at 19:07 passed the formerly failing current-saves response, outfits, and image verification (placeholder acceptance logged 19:07:54.993). No fatal quit or hex parsing error appeared through 19:09; heartbeats continued. Unity explicitly logged `Junior: False` at 19:07:38.968 but `meta junior login error` at 19:07:50.632. User screenshot confirms false database flags, JuniorState=0, and an unset birthday (year 0001). BuildSelfAccount recalculates juniorState=PendingOverride (2) when birthday is absent/year<=1900 regardless of stored zero. Client TitleScreenFlowModel.EJKBHHGALEH -> HBGNIICMAMG -> BKJMLBBNEFL.FBOEAHDMPJF checks the self-account state at +0xa8 for 2 before falling back to its Junior boolean, then checks Meta hardware. This supports incomplete birthday onboarding as the remaining restriction, not an actual true Junior flag. No birthday, Junior flag, or restriction check has been modified.
ImageCompat login at 19:02:38 authenticated successfully (token 200 and self-account 200), but failed before image loading. At 19:02:42.494 the custom-avatar-current-saves response began (200) without a matching chunk end. At 19:02:42.635 Unity reported `Can't parse '' as a hex number!`; manual login returned `doll` at 19:02:43.596. A later matchmaking heartbeat hit the same parsing error. No image-placeholder acceptance or fatal-quit event occurred during this attempt, so the image fix remains unverified. Filtered capture: image-compat-events.log.
FreshHTTP.apk is a one-variable transport experiment on top of ImageCompat: HTTPManager.SendRequest sets IsKeepAlive=false via its runtime setter only for https://ns.radicade.app/ requests. This tests connection reuse as a cause; it does not establish the root cause or suppress parser errors. TLS and response validation are unchanged. Native compilation and APK signature verification passed. Device login test pending.
ImageKey test at 18:52 confirmed the request hint reached the VPS and changed the failure from `Signature malformed` to `Signature verification failed`. Headset log `image-key-events.log` records this failure followed by fatal quit code 1550502249. User supplied matching VPS reports. The server's SignatureData is a fixed 256-byte placeholder, not a signature of the returned image.
Built, apksigner-verified, and installed ImageCompat.apk in place, preserving data. Exact-build image verifier HFFEEGBKKFK.DGDJIHJHHBN at RVA 0x6deb934 is reached after header/key-id parsing. The hook recognizes only the exact server placeholder byte array and accepts it in this image-only path; all other signatures call the original verifier. This intentionally relaxes image authenticity checking for that placeholder, does not provide cryptographic image authentication, and does not alter TLS certificate validation or fatal-quit behavior. Source: tools/quest-http-inspect.c and tools/quest-image-placeholder.h. Prologue verified against original ELF and compilation passed -Wall -Wextra -Werror. First alignment attempt failed in sandbox; elevated alignment succeeded. Login outcome pending. No server update required for this test.
Confirmed next trigger from user-provided VPS reports: two `Obcured` reports say `Signature malformed` for the same image URL with `sig=p1`. QuitInspect captured two fatal quits with code 1550502249 and a delegate-invocation caller; shutdown's SpeedHackDetector exception follows them. Live `/images/{name}` returned key-id `KEY:RSA:p1.rec.net`, whereas patched Quest metadata expects `.radicade.internal`. This is an image-signature key-ID mismatch, not proof of a Junior account restriction or TLS certificate-pin failure. Existing PC ImageSignatureRequest.AddKeyId already solves that header selection mismatch with `rcsig`.
Built, verified and installed ImageKey.apk in place. Added an exact-prologue-checked HTTPManager.SendRequest hook (RVA `0x688e200`) that adds the expected rcsig only to `https://ns.radicade.app/images/` requests, preserves resize/signature query parameters, and uses the IL2CPP write barrier when replacing the URI. Existing hinted/fragment URLs are left unchanged. Original TLS and RSA verification remain active; the hook activates after the diagnostic 30-second delay. A live request with that parameter returned HTTP 200 and the correct `.radicade.internal` key ID. Headset acceptance remains pending; the server still supplies static signature bytes, so successful signature cryptographic verification is not established. Capture: `artifacts/quest-tls-diagnostic/image-key-events.log`.
After deployment, PID 21799 received `/outfits/me` HTTP 200 at 14:21:37 and progressed to version check and additional startup requests. The user saw “Account not supported” before exit. Two `/api/PlayerReporting/v1/hile` requests preceded exit; Android recorded signal 9 after the activity stopped at 14:21:48. A public account-2 probe returned isJunior=false, which does not establish private self-account state. The client's SessionManager message table maps error 83 to the Meta Junior restriction text, but the triggering code has not yet been captured.
At the user's request, inspected https://github.com/recflare/quest at `43efb7b9f46a335e978af5520979d8a4d65e81f2` in `artifacts/recflare-quest-reference`. It targets April 2023, not this September build. Its certificate-pinning and fatal-quit diagnostics suggest a separate post-TLS integrity check worth testing; they do not establish our cause. No repository build scripts or bypass hooks were run. Added an independently implemented exact-prologue-checked trace at this client's SessionManager.FatalApplicationQuit RVA `0x3a1c0b4`, logging code/caller/reason length only and preserving behavior. Signed/verified QuitInspect.apk installed in place. Activation is not yet confirmed because the app exited before diagnostic output; user asked to keep headset awake at the password page. No new server changes or account edits made.
The user reported the VPS updated after `a808239`. An unauthenticated live check of `/outfits/me` now returns 401 instead of 404, consistent with the new protected route being deployed. Reopened HttpInspect-v3 on the connected Quest as PID 21799; filtered capture is `artifacts/quest-tls-diagnostic/http-after-outfit-deploy.log`. Authenticated response/client acceptance is still pending a manual login.
Server fix committed and pushed to origin/2023 as `a808239`: authenticated `GET /outfits/me` now wraps the existing account avatar in the September current-outfit DTO. Exact-client metadata resolved the request state machine `MOEENPJECCH` returning `FMLIPOPOKDJ`; generated formatter `JOJEHOPNNIN` defines DataVersion, LegacyData, CustomizationSettings, Selections, Slot, Name, Accessibility and ThumbnailFileName. Legacy formatter `DEAAHJJLAAE` defines SelectionsV1, SelectionsV2, FaceFeatures, SkinColor and HairColor. The initial adapter uses version 0 legacy data, current slot 0 and an empty modern selections list. Client acceptance of the response remains pending VPS deployment and a headset test. All three QuestOutfitsTests passed, covering account isolation, auth rejection, JSON shape and missing-avatar initialization.
Later capture at 14:04:24–26 reproduced empty chunk-length errors on `/datacollection/data/event` and `/matchmaking/player/heartbeat`, after the 14:02:29 login failure. The intermittent transport issue is therefore not proven fixed. Avatar descriptor warnings also remain unresolved. The field inspector now supports secondary Android users (active user 10) and records RecNet interface fields as well as runtime fields; it reads type metadata only.
HttpInspect-v3 captured a complete manual login attempt at 14:02:21–29. Password token, account/me and matchmaking login all returned HTTP 200. Every observed chunked response completed, including the avatar catalog. `/outfits/me` returned HTTP 404 at 14:02:24; the client reported `AMIIDJOEAMH: HTTP Error 404` and manual-login `doll` at 14:02:29. This attempt did not reproduce the earlier empty chunk-length parse error. The client also logged invalid legacy avatar-item descriptors, a separate compatibility concern. The missing outfit route is the current concrete server lead; its response contract is being inspected before implementation.
Follow-up: the user confirmed the password page but signing in exited PID 14518 with signal 11 at 13:57:06. The v2 diagnostic was unsafe: optimized call sites for `ReadChunkLength` do not preserve its unused receiver argument, so interpreting that argument as an HTTPResponse can dereference invalid memory. Do not reuse HttpInspect or HttpInspect-v2. Updated the hook to `ReadChunked` (RVA `0x6d941e0`), whose receiver is actually consumed, and verified its four copied prologue instructions against the original binary. Built and signature-verified HttpInspect-v3.apk, installed in place, and launched PID 16445. This corrects the identified diagnostic defect; the original post-login `doll` issue remains unresolved pending a captured attempt. Filtered continuous capture is saved locally in `artifacts/quest-tls-diagnostic/http-v3-events.log`.
The user successfully interacted with the v3 password form using Touch controllers. Supplied server logs show the headset password grant accepted, followed by authenticated account and matchmaking requests. Password authentication itself succeeded; gameplay has not been reached. At 01:26:07 the client reported `doll` with BestHTTP `ReadChunkLength` failing to parse an empty string as hexadecimal. Successful server request logs do not identify which response failed in the client.
Added `tools/quest-http-inspect.c`, an exact-prologue-checked experimental native hook that logs HTTP chunk reads using response identity, status and URL path only. It does not log passwords, authorization headers, query strings or response bodies. The diagnostic activates after 30 seconds; this timing remains a diagnostic workaround.
A subsequent saved-account auto-login followed `TitleScreenFlowModel.MNKODPJLNDD`, a different error callback from the initial cached-login path, and showed error 3 before exit. The client patch now redirects that callback at RVA `0x39c67e0` to the existing password page (`GoToLogin`, `0x39bbd80`) as well. Server cached-login rejection remains intact.
Built, signed, verified and installed `artifacts/quest-tls-diagnostic/HttpInspect-v2.apk` as an in-place update to `com.radicade.questtls`. No uninstall or data clearing occurred. Quest PID 14518 logged cached-login error 3 at 13:55:58, then activated the chunk diagnostic at 13:56:11 and remained running. Visual confirmation of the password fallback and a fresh manual login with the diagnostic active are pending. No additional server change or deployment was made for this diagnostic.
## v3 controller dispatch test (2026-10-07)
The user confirmed v2 displays the password form but physical Touch controllers do not interact, although they work in the Meta menu. Static analysis resolved `InputManager.FMJDLCGGGKP`'s virtual call at `0x460a254` as CurrentPlatform (same vtable slot used by `get_CurrentPlatformIsOculusType`). Its dispatch table at RVA `0xc35a550` maps Standalone=7 to return no VR input (`0x460a2b0`). Oculus=1 dispatches to `0x460a2c0`, which selects the existing Quest input handler for hardware type 5.
The v3 script changes only that table entry in addition to v2 changes. It preserves password-based platform-7 server authentication and selects Oculus input using the original hardware check. Installed signed `StandaloneLogin-v3.apk` in the separate diagnostic package without uninstalling. Patched IL2CPP SHA-256 is `1a646b3c85ecf8101e7c131cd87012ab6a9764d01685addee03d2251a811243b`. Signature verification passed; controller interaction and end-to-end password login require headset testing. No additional server changes were made.
## v2 prototype reaches password page (2026-10-07)
Server commit `835534b` was pushed to origin/2023 and the user deployed it. A credential-free live probe returned the specific standalone password-required response, confirming the new guard is active. Those two deployment-probe cached-login 401s were not headset password attempts.
The expanded read-only inspector saved `artifacts/quest-tls-diagnostic/quest-method-map.tsv`. Runtime method names resolve the Quest-specific original initialization call at `0x3d09f14` to `RecRoom.LocalPlayerController.SetVRDisplayMode`; v1 skipped it. The v2 patch preserves original initialization through that call, then invokes standalone account initialization with the original stack frame correctly restored. It also redirects `TitleScreenFlowModel.DALDIFKKDDA` to its existing `GoToLogin` method. This error redirect is currently broad (all title-screen errors), so this remains an experimental build.
Installed `StandaloneLogin-v2.apk` into `com.radicade.questtls` without uninstalling. PID 15954 reported Standalone/OculusQuest, received the expected cached-login 401, and stayed running. An ADB screenshot visually confirmed the username/password page, rather than the error screen. Controller interaction and successful password authentication remain pending user testing. No password was supplied or logged. The original-package TLS-fixed APK and both OBBs remain available. Signature verification, ARM64 stub disassembly, 16 KiB alignment, and Python syntax checks passed.
## Independent-login prototype: not playable; rolled back (2026-10-07)
Added local server support for wire platform 7 (`Standalone`) with password-only login. It clears caller-supplied account identity before resolving the username, generates a local `radicade-quest:` device namespace, ignores bearer headers for this password grant, rejects cached-login/refresh/signup grants, and suppresses unauthenticated standalone cached-account discovery. Existing whitelist and anti-cheat checks remain active. These are experimental server changes, not deployed or pushed. Refresh and new-account onboarding are not implemented; an existing Radicade account with a password is required. With whitelist enabled, the derived local identity must currently be explicitly allowed.
All 26 `AuthOnboardingFlowTests` passed, including five standalone cases covering correct/incorrect passwords, correct token identity, hidden cached accounts, and rejection of device-only cached/refresh/signup requests.
`tools/patch-quest-standalone-login.py` reproduces a failed client experiment: three exact-build checked branches redirect Oculus initialization/auth parameters/platform enum to existing standalone implementations. Only 12 bytes in `libil2cpp.so` differ from the TLS-fixed clone. The signed APK `artifacts/quest-tls-diagnostic/StandaloneLogin.apk` passed v2/v3 verification and 16 KiB alignment. On Quest (PID 12596), it passed platform initialization and logged `Platform: Standalone`, `Hardware: OculusQuest`, but then attempted cached login, received HTTP 401, and displayed error code 3. The user reported no usable hands. A screenshot confirmed the error screen; the cause of missing interaction is not established. This is NOT a working Quest login implementation, and the initialization replacement must not be promoted to a release.
Restored `RadicadeQuest-ScopedTLS-Test.apk` over the separate diagnostic package without uninstalling (ADB Success), then launched the unchanged original-package TLS-fixed app. Both OBBs and private data were retained. Before another client test, trace Quest input initialization and the title-screen transition to password login separately; do not simply replace the full initialization method again. The user reports deploying by pushing Git and gives `C:\radicade-server` as the server folder. No deployment workflow was found in this checkout; production update/restart mechanics are not yet verified.
## Latest result: TLS fixed; Meta account initialization remains blocked (2026-10-07)
The currently installed original-package APK is `artifacts/quest-tls-diagnostic/RecRoomQuest-ScopedTLS-Test.apk`, SHA-256 `064fbf29f4c3340abf45863664fd84af1404a74b46c64fa08d733f40654bdffa`. It retains package `com.AgainstGravity.RecRoom`, versionCode `2378787`, and the original patched metadata. The only changed APK payload is `lib/arm64-v8a/libunity.so`; signing metadata also changes. The user explicitly approved replacing the previous differently signed test app and its resulting private-data deletion. The OBB was restored and its SHA-256 again matched `ef856a56bfe1667f00435c2b07fb465766196c02bac8b220c7cc36db926998fc`.
Native instrumentation found that platform-root X509 validation of `ns.radicade.app` succeeded (`result=0`, `raw_flags=0`, `unity_flags=0`), but the configured managed verification callback subsequently returned `0x8`. This demonstrates an additional client-side rejection; its managed implementation/pinning policy has not been identified. Changing server TLS versions or certificate issuers did not address it.
`tools/diagnose-quest-tls.py --radicade-system-trust` routes only the exact hostname `ns.radicade.app` through Unity's existing default verifier (including platform CA chain and hostname validation), preserving the configured callback for all other hosts. It does not return unconditional verification success. The script accepts only the exact known Unity library SHA-256, uses checked empty RX-segment padding, and preserves existing native instructions outside the three hook sites and RX segment length fields. Without that option it only adds diagnostic logging. The native library SHA-256 for the scoped variant is `37c5709cca6b5dff019e16685a819d50d064d3a4c19f45d642cb5383011e23d7`.
Both the separate diagnostic package (`com.radicade.questtls`) and the original-package scoped build passed TLS and loaded game configuration. The original-package run (PID 6353) remained running past the former exit point, but logged `GetLoggedInUser failed: The user isn't signed in or their account state wasn't in a recoverable state. (2|-1)`, followed by `Unable to load Oculus user account`. Meta's platform logs also show `OBTAIN_TOKEN_3P` failing with HTTP 400 for this package. No account proof, platform identity, entitlement result, or server authentication policy was forged or changed. Gameplay/login is not yet working.
The original signing password was unavailable. The new local diagnostic signing key and password file are in `artifacts/quest-tls-diagnostic/diagnostic.p12` and `diagnostic.password`, excluded by that directory's `.gitignore`; retain these for non-destructive future updates. A separate diagnostic app and its OBB remain installed. Signed v2/v3 verification, 16 KiB alignment, original-package payload comparison, checked ARM64 disassembly, and on-device positive TLS tests passed. Rejection-path behavior has not yet been independently exercised with an invalid server certificate.
The VPS currently serves ZeroSSL and has Caddy debug enabled. It is running in the user's foreground PowerShell window. The older sections below describe the investigation history, not the current installed build.
### Platform integration investigation (2026-10-07)
Added `tools/quest-platform-inspect.c`, a read-only JNI diagnostic which enumerates selected runtime class methods and field offsets without reading account values or invoking authentication methods. Installed it only in the separate `com.radicade.questtls` diagnostic app as `artifacts/quest-tls-diagnostic/PlatformInspect.apk`. The original-package TLS-fixed APK and both OBBs were retained. The first inspector run raced Unity initialization and aborted; delaying inspection by 30 seconds allowed the second run (PID 10871) to complete. This delay is a diagnostic workaround, not a general initialization synchronization guarantee.
The complete successful method inventory is `artifacts/quest-tls-diagnostic/platform-methods.txt`; selected ARM64 disassembly is `platform-disassembly.txt`. Confirmed actual Quest RVAs: `OculusPlatformManager.Initialize` = `0x3d09d8c`, `OculusPlatformManager.GetAuthParams` = `0x3d0a930`, `StandalonePlatformManager.Initialize` = `0x43d6f0c`, and `PlatformManager.GetAuthParams` = `0x396ad48`. The standalone manager exists in the Android build; its platform getter returns enum value 7 and its PC-platform getter returns true. Therefore simply swapping the entire manager would change device behavior and is not yet a validated Quest integration. The server's current Platforms enum does not define value 7. No platform methods or server authentication behavior were changed by this diagnostic.
Next implementation work must preserve Quest hardware behavior and add authenticated Radicade account credentials/session validation before enabling an independent login path. The existing server device-code implementation auto-approves a configured Studio account; it must not be reused as Quest account authorization. A successful metadata inspection does not establish gameplay compatibility or successful login.
### Confirmed remaining Meta failure
The user confirmed their Meta profile and other online apps work. Correlated platform logs for the original-package fixed build show `https://graph.oculus.com/authenticate_application` returning HTTP 400, OAuth error code `27`, subcode `1891543`, title `Unofficial Application`. The response explicitly identifies the app as modified and disables platform functionality. This is an app-authorization rejection, not evidence that the user's headset account needs resetting. Changing Caddy or reinstalling the same modified APK cannot supply the original publisher's signing identity. Remaining work requires a separately designed Radicade sign-in integration (for example, server-validated account login/device pairing or an appropriately registered Meta app), preserving account authentication rather than fabricating a Meta user/proof or accepting arbitrary platform IDs.
The supplied folder is an apktool-decoded Android package plus an OBB asset tree. Its APK manifest is `com.AgainstGravity.RecRoom`, Quest/Quest 2 6DoF, ARM64, and the loaded Unity version literal is `20230907` (not the desktop `20230831` build).
## What is ready
`tools/patch-quest-client-metadata.py` recognizes this exact Quest metadata layout, resolves its live IL2CPP string-literal table, and redirects its bootstrap/auth/site/image URLs to `https://ns.radicade.app`. It keeps the image-signature tail `.radicade.internal`. It is dry-run by default, verifies the rebuilt table against every original literal, writes a `.orig` backup and a patch marker only with `--apply`, and refuses unknown/mismatched builds.
From the repository root, first preview the changes:
```powershell
python tools/patch-quest-client-metadata.py 'C:\RecRoom\RecRoom-Quest-20230907' --server https://ns.radicade.app
```
The image rule targets the server's signed `/images/img/{name}` route; the domain's `/images/avatar/` is not the image endpoint and returns a large generic response. The account's `GET /` nameserver advertises all other service URLs under the same server.
## Earlier state: NSC test APK installed and launched on Quest 3
The Quest 3 is connected via ADB. The local test-signed build was installed after uninstalling the prior test-signed package (the original Store app had already been removed earlier). The app was launched as a VR activity on-device; Unity initialized the OpenXR/Oculus runtime and reached `Connecting to RecNet`. Its TLS request still fails with `Mono.Security.Interface.TlsException: Handshake failed - error code: UNITYTLS_INTERNAL_ERROR, verify result: 4294957312`, then the RecNet nameserver query reports `summer` and the app exits around 17 seconds after start.
A second APK variant adds the official Let’s Encrypt Root YR cross-signed-by-ISRG-Root-X1 certificate as a raw resource and trusts it only for `ns.radicade.app`, while retaining Android system trust and disallowing cleartext. It was aligned for 16 KiB pages and signed with a separate local test key; Apktool build, `apksigner` v2/v3 verification, `zipalign`, package/version/ARM64 checks and inspection of the packaged NSC all passed. Installing it required uninstalling the previous local test package because its signing certificate differed; the user's consent was obtained. The matching OBB was restored from the PC copy and SHA-256 verified identical (`ef856a56bfe1667f00435c2b07fb465766196c02bac8b220c7cc36db926998fc`). This certificate-resource experiment did not change the TLS error or startup exit.
The currently installed APK is `C:\\Users\\user\\Downloads\\RadicadeQuest20230907\\RecRoomQuest-radicade-nsc-test.apk` (package `com.AgainstGravity.RecRoom`, version `20230907`, versionCode `2378787`). Its narrow Android trust config is `apk/res/xml/network_sec_config.xml`, and the cross-signed CA resource is `apk/res/raw/isrg_root_yr_by_x1.pem`. This test shows that adding an Android Network Security Config trust anchor does not fix this Unity `MobileAuthenticatedStream` TLS handshake, so do not keep changing Android trust settings or weaken TLS validation; investigate the TLS chain/protocol at UnityTLS or server-side and keep certificate validation enabled.
The headset OBB at `/sdcard/Android/obb/com.AgainstGravity.RecRoom/main.2378787.com.AgainstGravity.RecRoom.obb` is present and hash-matches the local build. Uninstalling the test app removed the on-headset OBB directory, so the local copy was pushed back and verified after install.
## Earlier APK/OBB build notes
The pre-existing working tree had a decoded Quest APK (`apk/`) and separately decoded OBB asset tree (`obb/`), not the original archives or publisher signing key. The untouched source under `C:\\Users\\user\\Downloads\\RecRoom-Quest-20230907` was not modified. A separate working copy under `C:\\Users\\user\\Downloads\\RadicadeQuest20230907` has the metadata patch applied; Apktool 3.0.3 built its APK, which was page-aligned and signed with local-only debug keys. Package, version, min SDK 25, target SDK 32 and ARM64 native code were validated. A new `tools/package-quest-obb.py` validated all 22,199 OBB entries and built the matching expansion-file name (1,394,848,868 bytes); the on-headset copy now has a verified matching hash. Android platform-tools 37.0.1, build-tools 36.1.0 and Apktool were downloaded into `%LOCALAPPDATA%\\Radicade\\QuestToolchain`.
The remaining significant functional blocker is identity: this APK is built for Oculus/Meta and its IL2CPP metadata contains Oculus Platform APIs (`GetUserProof`/`GetLoggedInUser`-related flows), but the server currently only verifies Steam tickets when `platform == Steam`. Account creation also accepts only platform IDs prefixed `75` or `76`, and the existing allowlist is named/configured as a Steam ID list. The server version-check route currently allows all versions, but `20230907` is not in `DBConfig.SupportedGameVersions`; this does not prove the rest of the server protocol works. The endpoint patch does not add Meta identity verification, change server authentication policy, or port the Windows BepInEx/Harmony plugin to Android.
The metadata patch remains reproducible with the dry-run command above. The current test APK and matching expansion file are installed and have been run on the Quest 3; the app still exits after UnityTLS handshake failure during nameserver discovery. New Meta account onboarding is not supported/verified; existing linked-user login is unknown.
## Safe next steps
1. Keep this test APK/OBB and current headset install available for controlled TLS investigation. Do not uninstall/reinstall again without a reason: that deletes app-private data and Android also removes the OBB directory.
2. Investigate the failure within Unity's `MobileAuthenticatedStream`/UnityTLS against the exact chain served by Caddy (leaf `ns.radicade.app` → YR1 → Root YR cross-signed by ISRG Root X1). Do not disable certificate or hostname validation; the domain-scoped Android NSC root addition had no effect.
3. If the TLS request succeeds, resolve and test Meta/Oculus identity support with the Radicade server before attempting account creation. Do not weaken authentication based on an assumed Oculus ID shape.
## TLS 1.2-only test (2026-10-06 Pacific)
The user identified the server as a netcup Windows VPS, running C:\Caddy\caddy.exe v2.11.4 with C:\Caddy\Caddyfile. The live Radicade site already used key_type rsa2048. The user added protocols tls1.2 tls1.2. Reload initially failed because Caddy was no longer running; the user started it in a foreground PowerShell window.
External OpenSSL checks confirmed that TLS 1.2 succeeded with certificate verification OK and TLS 1.3 was rejected with protocol-version alert 70. A fresh launch of the existing Quest APK (PID 29072) still produced UNITYTLS_INTERNAL_ERROR, verify result 4294957312, followed by nameserver error summer and OnApplicationQuit at approximately 15.2 seconds. The process was absent afterward. TLS 1.2-only did not resolve startup; certificate-chain compatibility remains unproven. No APK reinstall or OBB change was performed. The temporary TLS restriction should be reverted on the VPS; rollback has not yet been confirmed. Caddy currently depends on the user's foreground PowerShell process remaining running.
Rollback confirmed: the user successfully reloaded via 127.0.0.1:2019, and an external TLS 1.3 handshake subsequently succeeded with certificate verification OK. The TLS 1.2-only restriction is no longer active.
## ZeroSSL chain test
After the user changed the Radicade site's ACME issuer to ZeroSSL and reloaded successfully, an external OpenSSL probe confirmed the served chain changed to ns.radicade.app -> ZeroSSL RSA DV SSL CA 2 -> Sectigo Public Server Authentication Root R46 -> USERTrust RSA Certification Authority, with verification OK. The new chain was active before the Quest launch.
The existing Quest APK was launched again (PID 30187). It reached Connecting to RecNet at 9.44 seconds, reported the identical UNITYTLS_INTERNAL_ERROR / verify result 4294957312 at 13.16 seconds, then nameserver error summer and OnApplicationQuit at 15.14 seconds. The process exited. Changing issuer did not fix startup. This weakens a Let's Encrypt-specific explanation but does not establish the exact validation failure. ZeroSSL remains configured on the VPS; no rollback has been performed.
Read-only inspection confirmed that the test APK's live metadata literals 41405 (auth) and 41411 (nameserver) both resolve to https://ns.radicade.app. The headset reports Android 14, and both /system/etc/security/cacerts and /apex/com.android.conscrypt/cacerts contain certificate files. This does not establish that Unity successfully loads or trusts those files. Further diagnosis should correlate Caddy TLS handshake errors with a fresh Quest launch before making additional certificate or client changes.
## Correlated Caddy debug and headset trust-store checks (2026-10-07 Pacific)
Quest PID 31620 again failed at about 13.29 seconds and quit at 15.27 seconds. The user supplied Caddy debug lines at 07:06:07 UTC showing SNI ns.radicade.app, supported protocol versions [771,770,769] (maximum TLS 1.2), successful selection of the ZeroSSL certificate, and handshake EOF. EOF alone does not identify the client's reason for closing.
The numeric Unity verify result 4294957312 is signed -9984 (-0x2700), matching Mbed TLS's certificate verification failed code; this correspondence suggests certificate validation failure but does not identify the precise Unity verification flag or callback.
Pulled the headset's 134 public system CA certificates to artifacts/quest-system-cacerts and built artifacts/quest-system-ca-bundle.pem. OpenSSL TLS 1.2 verification of the live Radicade server succeeded with this CAfile, -no-CApath, -no-CAstore, -verify_return_error, and -verify_hostname ns.radicade.app. This establishes that the served chain can validate with the headset's public system roots using OpenSSL, not that Unity loads these roots or validates identically. Investigation should focus on Unity's trust loading/custom validation and chain-building behavior rather than assume a missing system root or TLS version mismatch. Caddy debug remains enabled; ZeroSSL remains active.