From d637dcaf64d3d7d5817de409d581127f99d0d70e Mon Sep 17 00:00:00 2001 From: kumait Date: Fri, 21 Aug 2026 18:59:56 -0400 Subject: [PATCH] fix(ini): replay user GameData.ini after SagePatch overrides SagePatch.ini is loaded after Data\INI\GameData with INI_LOAD_OVERWRITE, so its generated defaults silently clobbered values from a user-authored loose Data/INI/GameData.ini (reported: MaxCameraHeight had no effect). Replay the loose file after SagePatch.ini when it exists, restoring user precedence. Installs without a loose GameData.ini are unaffected and the BIG-archived original is never reloaded. Also fix the percent sign in the generated terrain comment being eaten as a printf format specifier, and document the precedence rule in the generated header. Applied to both Generals and Zero Hour. Co-Authored-By: Claude Fable 5 --- .../GameEngine/Source/Common/GameEngine.cpp | 18 +++++++++++++++--- .../GameEngine/Source/Common/GameEngine.cpp | 18 +++++++++++++++--- docs/WORKLOG/2026-08-DIARY.md | 11 +++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index ccba389b1cb..7dc8343816e 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -412,6 +412,8 @@ void GameEngine::init() // GeneralsX @feature felipebraz 08/06/2026 Auto-create SagePatch.ini in user data dir with defaults. { + static const char *const USER_GAME_DATA_INI_PATH = "Data\\INI\\GameData.ini"; + AsciiString sagePatchPath = TheWritableGlobalData->getPath_UserData(); sagePatchPath.concat("SagePatch.ini"); @@ -424,8 +426,9 @@ void GameEngine::init() "; -----------------------------------------------------------------------------\n" "; SagePatch - Casual QoL overrides for GeneralsX\n" ";\n" - "; Loaded by the engine after the BIG-archived Data/INI/GameData.ini, so values\n" - "; here override (not append to) the originals.\n" + "; Loaded by the engine after the BIG-archived Data/INI/GameData.ini, so values here\n" + "; override (not append to) the originals. A loose Data/INI/GameData.ini that you author\n" + "; yourself is applied after this file and therefore still wins over it.\n" "; -----------------------------------------------------------------------------\n" "\n" "GameData\n" @@ -437,7 +440,7 @@ void GameEngine::init() " EnforceMaxCameraHeight = No\n" " ; Keyboard scroll - vanilla 0.5 is sluggish, double it.\n" " KeyboardScrollSpeedFactor = 1.0\n" - " ; ~5% more terrain drawn at max zoom to fix terrain pop-in.\n" + " ; ~5%% more terrain drawn at max zoom to fix terrain pop-in.\n" " TerrainDrawDistanceScale = 1.05\n" // GeneralsX @tweak felipebraz 20/06/2026 Default render FPS limit to 60 FPS in SagePatch.ini " UseFPSLimit = Yes\n" @@ -480,6 +483,15 @@ void GameEngine::init() } ini.load(sagePatchPath, INI_LOAD_OVERWRITE, nullptr); + + // GeneralsX @bugfix kumait 13/08/2026 SagePatch defaults must not clobber a user-authored + // Data/INI/GameData.ini. That loose file shadows the BIG-archived one when the engine loads + // GameData above, so replaying it here restores user precedence over SagePatch. Installs + // without a loose GameData.ini are unaffected, and the archived original is never reloaded. + if (TheLocalFileSystem->doesFileExist(USER_GAME_DATA_INI_PATH)) + { + ini.load(USER_GAME_DATA_INI_PATH, INI_LOAD_OVERWRITE, nullptr); + } } } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 28446c80f52..41d3f2b7b19 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -471,6 +471,8 @@ void GameEngine::init() // GeneralsX @feature felipebraz 08/06/2026 Auto-create SagePatch.ini in user data dir with defaults. // This replaces the run.sh copy approach with engine-managed defaults. { + static const char *const USER_GAME_DATA_INI_PATH = "Data\\INI\\GameData.ini"; + AsciiString sagePatchPath = TheWritableGlobalData->getPath_UserData(); sagePatchPath.concat("SagePatch.ini"); @@ -483,8 +485,9 @@ void GameEngine::init() "; -----------------------------------------------------------------------------\n" "; SagePatch - Casual QoL overrides for GeneralsX\n" ";\n" - "; Loaded by the engine after the BIG-archived Data/INI/GameData.ini, so values\n" - "; here override (not append to) the originals.\n" + "; Loaded by the engine after the BIG-archived Data/INI/GameData.ini, so values here\n" + "; override (not append to) the originals. A loose Data/INI/GameData.ini that you author\n" + "; yourself is applied after this file and therefore still wins over it.\n" "; -----------------------------------------------------------------------------\n" "\n" "GameData\n" @@ -496,7 +499,7 @@ void GameEngine::init() " EnforceMaxCameraHeight = No\n" " ; Keyboard scroll - vanilla 0.5 is sluggish, double it.\n" " KeyboardScrollSpeedFactor = 1.0\n" - " ; ~5% more terrain drawn at max zoom to fix terrain pop-in.\n" + " ; ~5%% more terrain drawn at max zoom to fix terrain pop-in.\n" " TerrainDrawDistanceScale = 1.05\n" // GeneralsX @tweak felipebraz 20/06/2026 Default render FPS limit to 60 FPS in SagePatch.ini " UseFPSLimit = Yes\n" @@ -539,6 +542,15 @@ void GameEngine::init() } ini.load(sagePatchPath, INI_LOAD_OVERWRITE, nullptr); + + // GeneralsX @bugfix kumait 13/08/2026 SagePatch defaults must not clobber a user-authored + // Data/INI/GameData.ini. That loose file shadows the BIG-archived one when the engine loads + // GameData above, so replaying it here restores user precedence over SagePatch. Installs + // without a loose GameData.ini are unaffected, and the archived original is never reloaded. + if (TheLocalFileSystem->doesFileExist(USER_GAME_DATA_INI_PATH)) + { + ini.load(USER_GAME_DATA_INI_PATH, INI_LOAD_OVERWRITE, nullptr); + } } } diff --git a/docs/WORKLOG/2026-08-DIARY.md b/docs/WORKLOG/2026-08-DIARY.md index ea18856fd90..535231a7c9a 100644 --- a/docs/WORKLOG/2026-08-DIARY.md +++ b/docs/WORKLOG/2026-08-DIARY.md @@ -100,3 +100,14 @@ ### Known Remaining Issue - `DX8Wrapper::Resize_And_Position_Window()` (`dx8wrapper.cpp`) hardcodes position `(0,0)` on SDL platforms where the Win32 path centers on the current monitor's work area, and passes physical pixels to the `SetWindowPos` compat shim, which forwards them to `SDL_SetWindowSize()`. The window-mode pass in `W3DDisplay` corrects both afterwards, so the visible result is right, but the window can briefly flash at the wrong geometry. Fixing it at source touches shared `Core/` code and needs Linux validation before it lands. + +## 13/08/2026 +### Stop SagePatch.ini From Overriding a User-Authored GameData.ini +- Reported symptom: `MaxCameraHeight` set in a loose `Data/INI/GameData.ini` had no effect in game. +- Root cause was load order in `GameEngine::init()`. The engine loads `Data\INI\Default\GameData` and `Data\INI\GameData` through `initSubsystem()`, then loads the auto-generated `SagePatch.ini` from the user data dir with `INI_LOAD_OVERWRITE`. SagePatch ships a hardcoded `GameData` block (`MaxCameraHeight = 350.0`, `MinCameraHeight = 100.0`, `EnforceMaxCameraHeight = No`, ...), so it silently overwrote whatever the user had authored. +- The loose `Data/INI/GameData.ini` shadows the BIG-archived copy, because `FileSystem::openFile()` resolves local before archive. The archived original is therefore never a factor once a user file exists, so the fix replays that loose file after `SagePatch.ini`, guarded by `TheLocalFileSystem->doesFileExist()`. Installs without a loose `GameData.ini` keep the previous behavior. +- Reloading is safe: under `INI_LOAD_OVERWRITE`, `GlobalData::parseGameDataDefinition()` is plain field assignment with no `newOverride()` push, and the extra load is not fed to `xferCRC`, so the startup light CRC is unchanged. +- Fixed the generated `~5% more terrain` comment, which was passed as a `fprintf` format string and lost the `%` and the following space in every `SagePatch.ini` written to date. +- Updated the generated header comment to document the precedence rule. +- Known trade-off: a loose `GameData.ini` must be a complete copy of the archived file (it shadows, not merges), so the replay makes *every* key in it authoritative — including stock values the user copied without meaning to change. Concretely, stock pins `FramesPerSecondLimit = 30`, so a user file based on the stock copy reverts SagePatch's 60 FPS default unless the user edits that line too. Keys absent from stock (e.g. `TerrainDrawDistanceScale`) keep their SagePatch values. +- Applied to both Generals and Zero Hour.