Find where the mod loadout lives and why it is lost between launches #11

Closed
opened 2026-10-17 22:26:59 +00:00 by mintopia · 2 comments
Owner

What to build

Find out where Blur stores the player's mod loadout and why our port loses it between launches. Post the findings as a comment on this ticket.

Answer:

  • Where the loadout normally lives: the local save/profile, the online profile (the service ADR 0010 replaces), or only in memory.
  • Which path drops it in our port: save path resolution, a file I/O stub, a missing online-service call, or something else.
  • The recommended restore approach: fix the native path, add a host-side store, or use a Lua patch (ADR 0008). Include the trade-offs.

Acceptance criteria

  • Comment documents the storage location of the mod loadout, with evidence (trace, dump, or code address)
  • Comment names the exact cause of the loss on BLUR_ENGINE=recomp
  • Comment recommends one restore approach and says whether the follow-up ticket needs splitting
## What to build Find out where Blur stores the player's mod loadout and why our port loses it between launches. Post the findings as a comment on this ticket. Answer: - Where the loadout normally lives: the local save/profile, the online profile (the service ADR 0010 replaces), or only in memory. - Which path drops it in our port: save path resolution, a file I/O stub, a missing online-service call, or something else. - The recommended restore approach: fix the native path, add a host-side store, or use a Lua patch (ADR 0008). Include the trade-offs. ## Acceptance criteria - [ ] Comment documents the storage location of the mod loadout, with evidence (trace, dump, or code address) - [ ] Comment names the exact cause of the loss on `BLUR_ENGINE=recomp` - [ ] Comment recommends one restore approach and says whether the follow-up ticket needs splitting
Author
Owner

Findings

Short answer: the mod loadout is not in the local save. It lives in the online profile file MultiplayerProfile.save, a Demonware storage file (service 10). Our server stubs storage, so nothing is stored or returned. The profile resets at every login.

1. Where it lives (online profile)

  • Blur.exe has no local-file path for this file. Decompiled FUN_00bf5b60 (0xbf5b60, profile download state machine) passes "MultiplayerProfile.save" (ptr at 0x1102240) to FUN_00d6e340 with a buffer capped at 0x2000 bytes (8 KB).
  • FUN_00d6e340 (0xd6e340) calls FUN_00cf7770 = bdStorage::listFilesByOwner (VA cf7770 in docs/online-server-spec.md §4.2), and only if DAT_011b375c != 0 (logged in) and FUN_00ce3660() == 2 (connected). There is no CreateFile fallback. The upload twin is at 0xbf5e84.
  • docs/online-server-spec.md §4.2 (from logs): list at login, fetch if present, otherwise upload a fresh ~3.9 KB file, re-upload after XP/rank changes.
  • The local file is separate. .../bizarre creations/blur/save/blursave (magic 37 E3 CD AB, zlib, 50903 bytes inflated) is the single-player save; the BLURSAVE string at 0xee5a8c is referenced from 0xc203a1. The exe's save-struct names (GameInfo/TierInfo/EventInfo/VehicleInfo/ModInfo/RankInfo/BossInfo) are SP-campaign data. It is written once and holds no MP loadout.
  • Loadout script API (strings in Blur.exe, no matching Lua chunks): SetAbilityLoadout, SetNameOfLoadOut, ChangeLocalPlayerLoadout, ApplyDownloadedProfile, ResetMultiplayerProfile, UserProfileDownloaded, ContinueProfileDownload. The loadout is applied from the downloaded profile.

2. Why our port drops it (recomp)

  • Path: a missing online-service call, not save path resolution or file I/O. server/lsg.go implements only service 12/6 and 10/7. Every other task gets an empty success reply (logged unhandled service S task T).
  • listFilesByOwner (10/5) returns zero files, so the game treats the profile as new and uploads a fresh one. uploadFile (10/1) is acked but discarded, so the next login lists nothing again.
  • With BLUR_ONLINE_URL=off the logged-in gate fails and FUN_00d6e340 returns 0, so the profile is never loaded or saved.
  • Host-side file I/O is fine: a BLUR_ENGINE=recomp BLUR_FILETRACE=1 headless run showed no profile-related file access (only settings.xml probed, absent).

Implement storage service 10 in blur-online (tasks 1 upload, 4 getFileByID, 5 list): one file per (owner, name), overwrite on re-upload, stable file IDs, ~64 KB cap. This is exactly spec §4.2 and fits ADR 0010 ("storage of profiles" is listed as not implemented).

Option Verdict
Fix native path No native path exists
Lua patch (ADR 0008) No: the Lua chunks never see the file; the logic is native
Host-side store (intercept storage in the host) Works offline, but duplicates the server and needs the logged-in gate faked; defer
Server storage (recommended) Fixes loadout, XP and rank together; per-user and durable

Split the follow-up: yes, two tickets.

  1. Server: storage tasks 1/4/5 plus the bdFileInfo/bdFileData result layouts (§4.2 and §8 open item: exact uploadFile result and whether getFileByID is used). Discover the layouts with BLUR_DWLOG=1.
  2. Optional, offline: local fallback so a loadout survives with BLUR_ONLINE_URL=off. Only if offline play is wanted.

Caveat

Not verified live: I could not reach the multiplayer loadout screen unattended (headless run stops at the splash movie), so "the loadout is inside MultiplayerProfile.save" is an inference. It rests on the download path above (the only profile persistence in the binary), the 8 KB cap vs ~3.9 KB profile, and the loadout API names. Confirm in ticket 1 by logging the uploaded blob after changing a loadout.

## Findings **Short answer:** the mod loadout is not in the local save. It lives in the online profile file `MultiplayerProfile.save`, a Demonware storage file (service 10). Our server stubs storage, so nothing is stored or returned. The profile resets at every login. ### 1. Where it lives (online profile) - Blur.exe has no local-file path for this file. Decompiled `FUN_00bf5b60` (0xbf5b60, profile download state machine) passes `"MultiplayerProfile.save"` (ptr at 0x1102240) to `FUN_00d6e340` with a buffer capped at 0x2000 bytes (8 KB). - `FUN_00d6e340` (0xd6e340) calls `FUN_00cf7770` = `bdStorage::listFilesByOwner` (VA cf7770 in `docs/online-server-spec.md` §4.2), and only if `DAT_011b375c != 0` (logged in) and `FUN_00ce3660() == 2` (connected). There is no `CreateFile` fallback. The upload twin is at 0xbf5e84. - `docs/online-server-spec.md` §4.2 (from logs): list at login, fetch if present, otherwise upload a fresh ~3.9 KB file, re-upload after XP/rank changes. - The local file is separate. `.../bizarre creations/blur/save/blursave` (magic `37 E3 CD AB`, zlib, 50903 bytes inflated) is the single-player save; the BLURSAVE string at 0xee5a8c is referenced from 0xc203a1. The exe's save-struct names (`GameInfo/TierInfo/EventInfo/VehicleInfo/ModInfo/RankInfo/BossInfo`) are SP-campaign data. It is written once and holds no MP loadout. - Loadout script API (strings in Blur.exe, no matching Lua chunks): `SetAbilityLoadout`, `SetNameOfLoadOut`, `ChangeLocalPlayerLoadout`, `ApplyDownloadedProfile`, `ResetMultiplayerProfile`, `UserProfileDownloaded`, `ContinueProfileDownload`. The loadout is applied from the downloaded profile. ### 2. Why our port drops it (recomp) - Path: a missing online-service call, not save path resolution or file I/O. `server/lsg.go` implements only service 12/6 and 10/7. Every other task gets an empty success reply (logged `unhandled service S task T`). - `listFilesByOwner` (10/5) returns zero files, so the game treats the profile as new and uploads a fresh one. `uploadFile` (10/1) is acked but discarded, so the next login lists nothing again. - With `BLUR_ONLINE_URL=off` the logged-in gate fails and `FUN_00d6e340` returns 0, so the profile is never loaded or saved. - Host-side file I/O is fine: a `BLUR_ENGINE=recomp BLUR_FILETRACE=1` headless run showed no profile-related file access (only `settings.xml` probed, absent). ### 3. Recommended restore Implement storage service 10 in `blur-online` (tasks 1 upload, 4 getFileByID, 5 list): one file per (owner, name), overwrite on re-upload, stable file IDs, ~64 KB cap. This is exactly spec §4.2 and fits ADR 0010 ("storage of profiles" is listed as not implemented). | Option | Verdict | |---|---| | Fix native path | No native path exists | | Lua patch (ADR 0008) | No: the Lua chunks never see the file; the logic is native | | Host-side store (intercept storage in the host) | Works offline, but duplicates the server and needs the logged-in gate faked; defer | | **Server storage (recommended)** | Fixes loadout, XP and rank together; per-user and durable | **Split the follow-up: yes, two tickets.** 1. Server: storage tasks 1/4/5 plus the bdFileInfo/bdFileData result layouts (§4.2 and §8 open item: exact uploadFile result and whether getFileByID is used). Discover the layouts with `BLUR_DWLOG=1`. 2. Optional, offline: local fallback so a loadout survives with `BLUR_ONLINE_URL=off`. Only if offline play is wanted. ### Caveat Not verified live: I could not reach the multiplayer loadout screen unattended (headless run stops at the splash movie), so "the loadout is inside MultiplayerProfile.save" is an inference. It rests on the download path above (the only profile persistence in the binary), the 8 KB cap vs ~3.9 KB profile, and the loadout API names. Confirm in ticket 1 by logging the uploaded blob after changing a loadout.
Author
Owner

Completed and merged by Harmonic (task 12).

Completed and merged by Harmonic (task 12).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
mintopia/blur#11
No description provided.