Radicade Public Code

This commit is contained in:
JogmansonReal
2026-10-16 23:33:13 -07:00
commit 55175acdf6
832 changed files with 363261 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
# Forced new-account onboarding: what the 2026-10-12 runs proved
The client-side force (`2023Patch/RadicadeSeptemberClient/ForcedNewAccountOnboarding.cs`) has been
removed. This file records what it measured, because the measurements are the useful part and
several of them contradict the guesses that built it.
The conclusion they point at: **this is a server-side problem.** The 2023 client decides everything
about the title screen from data the server sends it, and every attempt to override that decision
inside the client either did nothing or produced a state the client was never designed to be in.
## 1. Where the title screen decision is actually made
From the Beta install's `BepInEx/LogOutput.log`, in this order, before the token request:
```
[Onboarding] enter KFGCEPNLFBA (BaseRouteFlowModel route-flow update)
[Onboarding] enter NHAJFLJDHBA (route-flow getter)
[Onboarding] exit NHAJFLJDHBA => HHGLIBCDIDA
[Onboarding] enter get_IsInAccountCreationState
[Onboarding] exit get_IsInAccountCreationState => false <- and then "Welcome back!"
[Net] Post /auth/connect/token [AuthGrant] grant_type=cached_login
```
So the question the client asks itself is "am I mid-account-creation?", answered **before** it
decides how to sign in, from the player-settings markers it holds:
- `Recroom.AccountCreation.HasStarted`
- `Recroom.AccountCreation.HasFinished`
and it reads those back out of `GET /playersettings/playersettings`. Answer `false`, and the client
takes the cached-account route and resumes with `grant_type=cached_login`.
`get_IsInAccountCreationState` and `get_IsInCachedAccountState` are both `public bool` getters on
`RRUI.Data.TitleScreenFlowModel` — cheap to patch, and the first one is definitely called. Overriding
it was tried: it answers the question the *client* asks, but the client is not the only thing that
decides, so the result was a different sign-in, not a different flow (see 3).
## 2. The flow update crashes when the cached-account list is not empty
`RRUI.Data.TitleScreenFlowModel.OFOIOHEFGAB()` is the client's own title-screen update. In the runs
where the server returned a real cached account, it threw, immediately after that response arrived:
```
[Net] Get /auth/cachedlogin/forplatformid/{redacted} -> 200 bytes=179
[Onboarding] enter OFOIOHEFGAB
Exception: Il2CppInterop.Runtime.Il2CppException: System.NullReferenceException
at RRUI.Data.TitleScreenFlowModel.OFOIOHEFGAB ()
```
In the run where the same endpoint answered `[]` (2 bytes, no account), the same method did **not**
throw, and no `[ForceOnboard] ... OFOIOHEFGAB threw` line was recorded all run. Same client build,
same plugins; the only difference was that one response.
That member is what moves the title screen to its next page. While it throws, no page — the
client's or a patch's — is applied, which is why "the hook installed but the screen did not move"
looked like a patching problem for so long. It is worth finding out whether the client is being
handed a cached account that is missing something the real service always sends.
What the server sends for that endpoint today (live, `forplatformid/0/76561198624498265`):
```json
[{ "accountId": 18, "platform": 0, "platformId": "76561198624498265",
"lastLoginTime": "2026-10-12T14:41:14.8984545", "requirePassword": false }]
```
## 3. Overriding the state changes the sign-in, not the flow
With `get_IsInAccountCreationState` forced to `true`:
```
[ForceOnboard] forced get_IsInAccountCreationState false -> true
[HttpStatus] #6 Get /auth/cachedlogin/forplatformid/{redacted} -> 200 bytes=2 (empty)
[AuthGrant] connect/token grant_type=create_account (was: cached_login)
[HttpStatus] #8 Post /auth/connect/token -> 200 bytes=703
... token claims carry "userId":"19" ...
[HttpStatus] #26 Post /matchmaking/player/login -> 200
```
The client left the cached-account path, which is what was wanted, and then **provisioned a brand-new
account** rather than being put through the flow for the account that already existed. The creation
pages' own hooks (`SubmitAvatarCustomizationAndGoToNext`, `SubmitAccountCreationBirthdayAndGoToNext`,
`AcceptCodeOfConductAndGoToNext`, `LaunchGameAccountCreation`) never fired: the client did not walk
the pages, it just made an account and launched.
So forcing the state gets a new account, not onboarding. The account that *needs* onboarding is
chosen by the server, from `CachedLogins` and the creation markers.
## 4. Two server-side things found on the way
**The standalone guard cannot fire for this client.** `Server/Auth/Cached/CachedLoginAPI.cs`:
```csharp
if (platform == Platforms.Standalone) return Ok(new List<CachedLogin>());
```
`Platforms.Standalone` is `7`, but this client identifies as platform `0` (**Steam**, with a SteamID64
as its platform id — `"rn.plat":"0"`, `"rn.platid":"76561198624498265"` in its own token). That guard
therefore never applies to the build being tested, and the row is returned.
**Two SteamIDs appear for the same machine.** `...24498265` (account 18, the one with the cached
row) and `...23533377` (the identity that created account 19). A platform change explains it, but it
means `CachedLogins` is keyed on the *platform identity*, not on the machine, and a device that
changes platform looks like a brand-new device to every "has this device been here before?" rule in
`AuthAPI` and `PlayerUtils`.
## 5. Client interop facts worth keeping
`RRUI.Data.TitleScreenFlowModel`, from `BepInEx/interop/Assembly-CSharp.dll` (metadata, not inferred):
| Member | Signature |
|---|---|
| `HBKLANJACMF` | `void (JPDJBDGFIGP)` — the page navigator; its parameter is *named* `ENNJBOCEGDH`, which is why a named-value-type prefix was rejected by HarmonyX |
| `DBHMKMHNJOO` | `JPDJBDGFIGP ()` — the reported page |
| `get_IsInAccountCreationState` | `bool ()` |
| `get_IsInCachedAccountState` | `bool ()` |
| `OFOIOHEFGAB` | `void ()` — the title-screen update |
| `KFGCEPNLFBA` | `void ()` — the route-flow update |
| `get_LocalAccount` | `INMPLDLFHAF ()` |
| `LaunchGameCachedAccount` / `LaunchGameAccountCreation` | `void ()` |
| `SubmitAccountCreationBirthdayAndGoToNext`, `SubmitAccountCreationUsernameAndGoToNext`, `SubmitAccountCreationPasswordAndGoToNext`, `AcceptCodeOfConductAndGoToNext`, `SubmitAvatarCustomizationAndGoToNext` | `void ()` |
Page enum `JPDJBDGFIGP`: `TITLE_SCREEN_NEW_ACCOUNT = 8`, `TITLE_SCREEN_CACHED_ACCOUNT = 9`,
`TITLE_SCREEN_INTERRUPTED_ACCOUNT_CREATION = 10`, `AVATAR_CUSTOMIZATION = 18`, `BIRTHDAY = 19`,
`USERNAME_NON_JUNIOR = 20`, `USERNAME_JUNIOR = 21`, `PASSWORD = 22`.
`TitleScreenFlowModel` also exposes `initialBootPage`, `titleScreenNewAccountPage`,
`titleScreenCachedAccountPage`, `titleScreenInterruptedAccountCreationPage`, `avatarCustomizationPage`,
`birthdayPage`, `usernamePage`, `passwordPage`, `accountCreationCodeOfConductPage`,
`accountCreationLaunchGamePage` as `TitleScreenPage` properties, and the flow navigates by
`RRUI.Navigation.Route` through `RRUI.Data.BaseRouteFlowModel`.
## 6. The one thing not to repeat
The force was gated on a marker file, and an early build wrote that marker when the flow reached its
own end **regardless of whether anything had been forced**. A plain walk of the vanilla flow
therefore consumed the arming, and every later boot printed
`the marker file is present, so nothing will be forced` — which is exactly what the 01:26, 10:41 and
10:43 logs all say. Two test runs in a row were spent on a force that was switched off by its own
bookkeeping, and the gate now says so in the log rather than in a comment.