Problem
Two independent defects in the same write path.
No read-merge. GameLauncher.cs:1422 builds a fresh GeneralsOnlineSettings;
GameSettingsService.cs:258-259 serializes it and calls File.WriteAllTextAsync.
LoadGeneralsOnlineSettingsAsync (:208-234) exists but the launch path never calls it, and
neither model declares [JsonExtensionData]. Any key the user set inside GeneralsOnline that
GenHub does not model is discarded on every Zero Hour launch.
No lock. The semaphore guarding LoadOptionsAsync/SaveOptionsAsync
(GameSettingsService.cs:64-145) does not cover the GeneralsOnline load/save at :208-269.
Concurrent launches race on a non-atomic WriteAllTextAsync.
The same class of loss exists on the INI side: ApplyToOptions builds a new dictionary at
GameSettingsMapper.cs:455 and replaces AdditionalSections["TheSuperHackers"] at :473,
dropping unknown keys — which contradicts the preservation behaviour documented in
docs/dev/game-settings-architecture.md:13-34.
Separately, GameSettingsViewModel.CreateGeneralsOnlineSettings (:1163-1194) populates only
the GeneralsOnline and nested properties, so a manual save writes defaults over the user's
inherited TheSuperHackers choices.
Approach
- Load-merge-write for settings.json, or add
[JsonExtensionData] so unknown keys survive.
- Extend the existing semaphore to the GeneralsOnline path, and write via temp-file replace.
- Decide what profile isolation should mean for keys GenHub has no opinion on. The current
answer is "discard them", which reads as accidental rather than chosen.
- Same treatment for the TheSuperHackers INI section, or correct the doc to match the code.
Problem
Two independent defects in the same write path.
No read-merge.
GameLauncher.cs:1422builds a freshGeneralsOnlineSettings;GameSettingsService.cs:258-259serializes it and callsFile.WriteAllTextAsync.LoadGeneralsOnlineSettingsAsync(:208-234) exists but the launch path never calls it, andneither model declares
[JsonExtensionData]. Any key the user set inside GeneralsOnline thatGenHub does not model is discarded on every Zero Hour launch.
No lock. The semaphore guarding
LoadOptionsAsync/SaveOptionsAsync(
GameSettingsService.cs:64-145) does not cover the GeneralsOnline load/save at:208-269.Concurrent launches race on a non-atomic
WriteAllTextAsync.The same class of loss exists on the INI side:
ApplyToOptionsbuilds a new dictionary atGameSettingsMapper.cs:455and replacesAdditionalSections["TheSuperHackers"]at:473,dropping unknown keys — which contradicts the preservation behaviour documented in
docs/dev/game-settings-architecture.md:13-34.Separately,
GameSettingsViewModel.CreateGeneralsOnlineSettings(:1163-1194) populates onlythe GeneralsOnline and nested properties, so a manual save writes defaults over the user's
inherited TheSuperHackers choices.
Approach
[JsonExtensionData]so unknown keys survive.answer is "discard them", which reads as accidental rather than chosen.