mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
Radicade Public Code
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user