mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 23:18:56 -07:00
140 lines
7.5 KiB
Markdown
140 lines
7.5 KiB
Markdown
# 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.
|