Skip to content

feat(sagepatch): casual QoL patch for macOS + Linux + user-config-dir (Phase 1.5)#156

Open
fbraz3 wants to merge 14 commits into
mainfrom
feature/sagepatch-qol-v2
Open

feat(sagepatch): casual QoL patch for macOS + Linux + user-config-dir (Phase 1.5)#156
fbraz3 wants to merge 14 commits into
mainfrom
feature/sagepatch-qol-v2

Conversation

@fbraz3
Copy link
Copy Markdown
Owner

@fbraz3 fbraz3 commented Jun 6, 2026

Supersedes #110

This PR carries forward ebellumat's feature/sagepatch-qol work and adds Phase 1.5: a per-user config dir + symlink pattern so the SagePatch override is editable outside the game data dir and the .app / install dir. The original PR's 9 commits are preserved intact at the bottom of the branch history; the 4 follow-up commits are mine.

What's in

Phase 1 (ebellumat, 9 commits, untouched)

  • Patches/SagePatch/ — optional, drop-in patch that adds quality-of-life
    features for casual play. Gated by the new CMake flag
    RTS_BUILD_OPTION_SAGE_PATCH (default OFF, ON in the macos-vulkan
    preset). Independently implemented from public GenTool docs + SDL3 /
    CoreGraphics / X11 docs. No code reverse-engineered from the original
    closed-source d3d8.dll.
Feature Trigger macOS Linux Windows
Screenshot F11 screencapture -l <id> import / gnome-screenshot stub
Cursor lock toggle Scroll Lock SDL3 SDL3 stub
Brightness up / down Ctrl + PageUp/Down CoreGraphics gamma XF86VidMode (X11) stub
Window snap (5 pos) Ctrl + 1..5 SDL3 SDL3 stub
Camera height range passive INI override INI override INI override
Camera pitch passive INI override INI override INI override
Keyboard scroll speed passive INI override INI override INI override
FPS counter passive DXVK_HUD=fps default in run.sh same n/a

Phase 1.5 (this PR, 4 commits)

  1. User-config-dir + symlink pattern — SagePatch Override.ini is
    seeded once to a per-user location on first launch, then symlinked
    from the engine's cwd path2 slot (Data/INI/GameData/SagePatch.ini)
    to that user file. Users can edit QoL settings (MaxCameraHeight,
    MinCameraHeight, KeyboardScrollSpeed, etc.) without touching the
    .app / install dir or the game data dir, and changes take effect on
    next launch.

    • macOS: ~/Library/Application Support/GeneralsX/SagePatch/SagePatch.ini
    • Linux: $XDG_CONFIG_HOME/GeneralsX/SagePatch/SagePatch.ini
      (fallback ~/.config/GeneralsX/SagePatch/SagePatch.ini)
    • Windows: stub (Phase 2, will use proxy d3d8.dll)
  2. path1 → path2 fix — Engine INI load order is path1 first
    (Data/INI/Default/GameData/, parsed first), then overwritten by the
    BIG-archived Data/INI/GameData.ini defaults. A user override in
    path1 is silently lost. ebellumat's PR commit 598d8fd fixed this on
    the deploy side; the bundle side still wrote to path1. Both sides
    now write to path2 (Data/INI/GameData/, parsed last, wins).

  3. Base-game deploy paritydeploy-linux.sh and
    deploy-macos-generals.sh had no SagePatch wiring at all (only
    the bundles did). A user running cmake --preset macos-vulkan && cmake --build && ./scripts/build/macos/deploy-macos-generals.sh
    got a vanilla game; a user running the bundle got SagePatch. Both
    base-game deploys now copy the dylib, set the preload env var,
    default DXVK_HUD=fps, cd to ${SCRIPT_DIR}, and apply the
    user-config-dir pattern — same behavior as the ZH deploys.

  4. Launchers self-heal on every run — Stale path1 copies from
    older deploys are removed, stale path2 files (left over from a
    previous non-symlink deploy) are removed before the symlink is
    recreated. Idempotent; safe to re-run.

Architecture

Game process (GeneralsX / GeneralsXZH)
    │
    ├── DYLD_INSERT_LIBRARIES (macOS) / LD_PRELOAD (Linux) → libsage_patch.{dylib,so}
    │       └── SDL_PollEvent gets replaced (interpose table on macOS,
    │           symbol override + dlsym RTLD_NEXT on Linux)
    │              └── F11 / Scroll Lock / Ctrl+PgUp/Dn / Ctrl+1..5 → SagePatch handlers
    │                      └── Per-platform: screencapture / ImageMagick,
    │                          CoreGraphics gamma / XF86VidMode, SDL_SetWindowPosition
    │
    └── Engine reads Data/INI/GameData/SagePatch.ini (symlink) →
            → ~/Library/Application Support/GeneralsX/SagePatch/SagePatch.ini (macOS)
            → $XDG_CONFIG_HOME/GeneralsX/SagePatch/SagePatch.ini (Linux)
                └── camera/scroll overrides

No D3D8 proxy, no Vulkan layer, no engine source modifications. Phase 1
patch source is ~600 lines C++ across common/, macos/, linux/,
windows/ (stubs).

What's intentionally not in scope

  • Anti-cheat (MDS, Game File Validator, version check, ergc) — explicit user decision; out of scope.
  • External-server features (CNC Online, GameRanger, Upload Mode, ticker, ladder, GenTool updater) — depend on third-party infrastructure SagePatch does not own.
  • Replay tools and competitive features (Money Display, Player Table, Random Balance, fog-of-war replay, frame stepping, controls bar) — focus is casual play.
  • Engine bug fixes (Scud bug, Tunnel bug, Building bug, multiplayer movement crash) — these are already handled upstream by TheSuperHackers/GeneralsGameCode, which is the parent of GeneralsX and is maintained by the same author who wrote GenTool. The codebase already carries 170+ @bugfix annotations including Tunnel System fixes by xezon. Duplicating them here would create merge conflicts on the next upstream sync.
  • In-game text overlay (clock, match timer, in-game settings menu) — would need a graphics-pipeline hook (D3D8 proxy or Vulkan layer); Phase 2 if there's appetite. The existing DXVK_HUD provides a basic FPS counter as a substitute.
  • Windows preload mechanism — Win32 has no LD_PRELOAD / __interpose equivalent; needs a proxy d3d8.dll like the original GenTool. Stubs are in place so the build still succeeds with RTS_BUILD_OPTION_SAGE_PATCH=ON on Windows, but the runtime is no-op until the proxy is implemented.

Already in the engine, no patch needed

These GenTool-era options are first-class engine flags in Common/CommandLine.cpp. Documented in the SAGEPATCH.md so users know they can call them directly via run.sh:

  • -nologo
  • -noShellAnim
  • -noshellmap
  • -quickstart
  • -xres N -yres N (resolution unlock — engine no longer locks the list)
  • -forcefullviewport
  • -noaudio, -nomusic, -novideo

Test plan

  • Builds on macOS arm64 (macos-vulkan preset, RTS_BUILD_OPTION_SAGE_PATCH=ON)
  • libsage_patch.dylib produced — 55 KB, arm64 native, dynamic SDL3 lookup
  • Deploy scripts (Linux + macOS, base + expansion) copy the dylib + Override.ini into the runtime dir
  • Generated run.sh wrapper sets DYLD_INSERT_LIBRARIES / LD_PRELOAD correctly (no trailing :)
  • User-config-dir pattern: pre-existing user override preserved, no-user→seed+symlink, stale path1 cleaned, user edits live via symlink
  • 4-scenario smoke test of the launcher pattern (functional, end-to-end)
  • All 4 deploy scripts + 4 bundle scripts: bash -n clean, generated run.sh blocks bash -n clean
  • bash -n on every deploy and bundle script
  • SAGE_PATCH_DISABLED=1 ./run.sh skips the preload as expected
  • GUI smoke launch of ZH + base game on actual hardware (user-actionable, pending)
  • Each individual feature manually validated (F11 / Scroll Lock / Ctrl+PgUp/Dn / Ctrl+1..5)
  • Linux build verification (no Linux host available in this session — config compiles, runtime path needs Linux tester)

Commits

Phase 1.5 — follow-up (this PR adds)

  • c36326f58docs(blog): 2026-06-06 SagePatch deploy/bundle user-config-dir continuation
  • 1e1beded9refactor(sagepatch): base game deploy parity with Zero Hour
  • 192b44643refactor(sagepatch): route deploy-zh run.sh through user-config-dir
  • 9023734e3refactor(sagepatch): user-config-dir + symlink pattern, fix path1→path2
  • 7328e0ab3merge(main): sync sagepatch-qol with origin/main (force-push + upstream 06-05-2026)

Phase 1 — original (ebellumat, preserved intact)

  • 508839d62tune(sagepatch): tone down camera defaults (500/80, drop pitch override)
  • 598d8fd57fix(sagepatch,deploy): drop Override.ini in path2 subdir so values stick
  • 535c8a12adocs(sagepatch): drop FPS-counter feature claim; engine already has one
  • 22d9696e1fix(deploy): cd to script dir in run.sh wrapper before exec
  • 55f06b77cfix(sagepatch,macos): default DXVK_HUD off; MoltenVK can't compile DrawIndex
  • 1cf944fdcfeat(sagepatch): wire SagePatch through every release bundle path
  • 3a87fa2cddocs(sagepatch): clarify scope vs. existing engine flags and upstream fixes
  • 3efaac65cfeat(sagepatch): make cross-platform (macOS + Linux), add window snaps + FPS
  • d42ef875cfeat(sagepatch): add casual QoL patch for macOS via DYLD interpose

Notes on naming

SagePatch is a placeholder picked at scaffolding time (the SAGE engine being what Generals runs on). Trivial to rename via:

git grep -l SagePatch | xargs sed -i '' 's/SagePatch/<NewName>/g; s/sage_patch/<new_name>/g; s/SAGE_PATCH/<NEW_NAME>/g'
mv Patches/SagePatch Patches/<NewName>
mv docs/PATCHES/SAGEPATCH.md docs/PATCHES/<NEWNAME>.md

Reference

ebellumat and others added 14 commits April 25, 2026 12:31
Introduces an optional patch (Patches/SagePatch/) that adds quality-of-life
features for casual play without modifying the game source. Gated by the
new RTS_BUILD_OPTION_SAGE_PATCH cmake flag (default OFF, ON in macos-vulkan
preset).

Architecture: a separate dylib loaded via DYLD_INSERT_LIBRARIES that
replaces SDL_PollEvent at the dyld level (__DATA,__interpose) to capture
hot-keys, plus an engine-side INI override picked up automatically from
Data/INI/Default/GameData/SagePatch.ini. No D3D8 proxy, no Vulkan layer,
no engine source changes.

Features in this initial drop:
  F11           PNG screenshot via /usr/sbin/screencapture (window-only)
  Scroll Lock   cursor lock toggle (SDL_SetWindowMouseGrab)
  Ctrl+PgUp/Dn  display gamma adjustment, range -128..+128
  Override.ini  MaxCameraHeight=800, MinCameraHeight=60, ScrollSpeed=1.0,
                EnforceMaxCameraHeight=No

Anti-cheat features (MDS, Game File Validator, ergc, version validation)
and competitive/networking features (Money Display, Player Table, Random
Balance, replay tools, CNC Online, ticker, Upload Mode, Auto Updater) are
intentionally excluded — focus is casual QoL only.

Smoke test: dylib loads cleanly via DYLD_INSERT_LIBRARIES, engine reads
the INI override, GameMain returns code 0.
…s + FPS

GeneralsX is a multi-platform project; the Phase-1 SagePatch was macOS-only,
which broke the rule. Restructured src/ tree and added Linux backend.

Source tree:
  src/common/   SDL3-only code that works everywhere — Init, KeyHandler,
                CursorLock, WindowPosition.
  src/macos/    __DATA,__interpose hook, screencapture, CoreGraphics gamma.
  src/linux/    LD_PRELOAD + dlsym(RTLD_NEXT), ImageMagick `import` /
                gnome-screenshot, XF86VidMode (lazy dlopen, X11 only —
                no-op under Wayland).
  src/windows/  stubs only (Phase 2 — would need a proxy DLL).

New features in this iteration:
  * Window snap presets — Ctrl+1..5 (center / TL / TR / BL / BR), via
    SDL_SetWindowPosition. Cross-platform.
  * FPS counter — both macOS and Linux deploy scripts default DXVK_HUD=fps
    when SagePatch is active, so casual users get a frame counter without
    extra config. Override with DXVK_HUD=0 or any custom value.
  * Camera pitch override added to resources/Override.ini.

Linux deploy script (scripts/build/linux/deploy-linux-zh.sh) now copies
libsage_patch.so + Override.ini, and the generated run.sh sets LD_PRELOAD
when the library is present. SAGE_PATCH_DISABLED=1 still skips the preload.

Phase 1 complete. Phase 2 items intentionally deferred:
  - Engine bug fixes (scud / tunnel / building / multiplayer crash) require
    edits inside Generals*/Code, defeating SagePatch's "no source change"
    contract.
  - Windows preload is structurally different — needs a d3d8.dll proxy.
  - In-game overlay text (clock, match timer, in-game menu) needs a
    graphics hook (D3D8 proxy or Vulkan layer).
… fixes

Two important clarifications after a code-base inventory:

1. Engine already supports GenTool-style CLI flags natively. Document
   -nologo, -noShellAnim, -noshellmap, -quickstart, -xres/-yres,
   -forcefullviewport, -noaudio/-nomusic/-novideo so users do not assume
   they need SagePatch to get those. They have always been part of the
   engine's CommandLine.cpp; no patch required.

2. Engine bug fixes (scud / tunnel / building / multiplayer movement crash)
   are handled by the upstream TheSuperHackers/GeneralsGameCode project
   which is maintained by the same author who wrote GenTool (xezon). The
   codebase already carries 170+ @BugFix annotations including the
   Tunnel System fixes by xezon himself. SagePatch does not duplicate
   them — that would create merge conflicts on the next upstream sync.

This explicitly scopes SagePatch to QoL features that live outside the
engine source tree.
Local deploy already shipped SagePatch on the previous commit. This wires
the same artifacts (libsage_patch.{dylib,so} + Override.ini) into every
release bundle so users actually get the QoL features when they download
the official zip / tar / flatpak — not just when they build from source.

Touches:

- scripts/build/macos/bundle-macos-zh.sh
- scripts/build/macos/bundle-macos-generals.sh
- scripts/build/linux/bundle-linux-zh.sh
- scripts/build/linux/bundle-linux.sh
- flatpak/com.fbraz3.GeneralsXZH.yml
- flatpak/com.fbraz3.GeneralsX.yml

Each one now:
1. Copies libsage_patch.{dylib,so} into the bundle's lib path (guarded by
   `-f` so the build still succeeds when SAGE_PATCH=OFF).
2. Ships resources/Override.ini into the bundle's data path
   (Resources/Data/... on macOS, /app/share/... in flatpak,
   Data/... in the standalone Linux tar).
3. Generated wrapper scripts now set DYLD_INSERT_LIBRARIES / LD_PRELOAD
   when the lib is present, with a `:` guard so empty existing values do
   not break dyld.
4. Wrapper seeds the INI override into `${CNC_GENERALS_*_PATH}/Data/INI/Default/GameData/SagePatch.ini`
   on first launch (engine reads INIs from cwd, not from inside the bundle).
5. DXVK_HUD defaults to "fps" when SagePatch is active (was: "0").

Flatpak manifests also pick up `-DRTS_BUILD_OPTION_SAGE_PATCH=ON` and run
the `sage_patch` build target after `z_generals` / `g_generals`.

Verified on macOS by running bundle-macos-zh.sh end-to-end:
  GeneralsXZH.app/Contents/Resources/lib/libsage_patch.dylib (55 KB, arm64)
  GeneralsXZH.app/Contents/Resources/Data/INI/Default/GameData/SagePatch.ini
…awIndex

The wrapper scripts shipped DXVK_HUD=fps when SagePatch was active. On
macOS 26 (MoltenVK 1.4.1, current SDK), DXVK's HUD pipeline shader uses
gl_DrawID, which lowers to SPIR-V DrawIndex. SPIRV-Cross to MSL has no
equivalent for that decoration and aborts conversion:

  [mvk-error] SPIR-V to MSL conversion error: DrawIndex is not supported in MSL.
  err:   Failed to create swap chain blit pipeline: VK_ERROR_INITIALIZATION_FAILED

The blit pipeline failure means DXVK can never present a frame; the game
hangs at the EA Games logo (last thing the engine drew before DXVK started
needing the blit pipeline).

Revert the default to DXVK_HUD=0 on the three macOS wrapper scripts.
Users who want the FPS overlay can still opt in with DXVK_HUD=fps.

Linux and Flatpak wrappers are unchanged: native Vulkan drivers handle
DrawIndex correctly there.
The engine resolves Local FS lookups (Data/INI/Default/<subdir>/*.ini overrides,
loose Data/ assets, etc.) relative to the binary's cwd, never the binary's
location. Without an explicit cd, launching the wrapper via absolute path,
Finder, gtimeout, or any other invocation that does not happen to start in the
asset dir caused the engine to miss every loose INI on disk — including
SagePatch.ini — while still loading the BIG-archived defaults via the archive
file system. Symptom: the game runs but the override apparently does nothing.

Wrapper now cds to the script's own directory (which deploy puts the binary,
the dylibs, the override INI, and the .big assets into) and execs the binary
relative to that. Matches the bundle-script wrappers which already did this.
The engine ships a native FPS overlay at the top-left via
W3DDisplay::drawFPSStats(), gated by #ifdef RTS_DEBUG plus the runtime
-benchmark <seconds> CLI flag. The previous SagePatch revision defaulted
DXVK_HUD=fps in the run wrapper as a release-build alternative, but that
default was already reverted in 55f06b7 because MoltenVK on macOS 26
cannot compile DXVK's HUD pipeline shader (DrawIndex has no MSL equivalent)
and the resulting blit-pipeline failure hangs the game at the EA logo.

Removing the FPS row from the feature table and replacing it with a small
'About FPS counters' section that explains the native option and why our
DXVK_HUD shortcut is parked.
The engine subsystem init for TheWritableGlobalData scans two parent dirs:
path1 = Data/INI/Default/GameData (loaded first)
path2 = Data/INI/GameData         (loaded second)

Each parsed GameData block overwrites prior values in TheWritableGlobalData
(INI_LOAD_OVERWRITE semantics). The vanilla camera defaults
(MaxCameraHeight = 310, MinCameraHeight = 120, CameraPitch = 37.5) live in
the BIG-archived Data/INI/GameData.ini and are parsed in the SECOND pass,
so an override placed under Data/INI/Default/GameData/ — which is parsed
in the FIRST pass — is silently undone right after.

Verified via temporary instrumentation in parseGameDataDefinition:
  pass 1: file=Data/INI/Default/GameData.ini    max=300/min=100  (debug-only block)
  pass 2: file=Data/INI/GameData.ini line=464   max=310/min=120  (vanilla, was last-write)
  pass 3: file=Data/INI/GameData/SagePatch.ini  max=800/min=60   (now winning)

Deploy scripts on both platforms now write to Data/INI/GameData/SagePatch.ini
(path2 subdir, parsed last) instead of Data/INI/Default/GameData/SagePatch.ini,
and clean up any prior misplaced copy. SAGEPATCH.md gains a short section
explaining the load order so future contributors do not repeat the mistake.
MaxCameraHeight=800 was too aggressive — on small maps it pushed the
orthographic frustum past the playable border, exposing void/cull at the
screen edges. Drop to 500 (~1.6x vanilla 310) so the extra range is
useful without breaking small skirmish maps.

Also raise MinCameraHeight back to 80 (slightly closer than vanilla 120
but not as tight as the previous 60), and remove the CameraPitch override
entirely — letting the engine keep its vanilla ~37.5 pitch instead of
forcing 50, which was an arbitrary choice and not a published GenTool
default. Scroll factor stays at 1.0 (2x vanilla).

Reaffirms that no published GenTool tuning numbers are public; these are
conservative casual-friendly bumps.
…am 06-05-2026)

Brings 8 commits from origin/main into the SagePatch working branch:
  - upstream thesuperhackers sync 06-05-2026 (PR #155, 6 commits)
  - bugfix TheSuperHackers#2746 Reinforcement Pad / Troop Crawler rider drop
  - bugfix TheSuperHackers#2747 dangling contain module in Object::onDestroy()
  - refactor TheSuperHackers#2758 MetaEventTranslator::translateGameMessage split
  - fix TheSuperHackers#2718 MilesAudioManager multithread crash + cleanup
  - fix TheSuperHackers#2710 various memory leaks (2)

Conflict resolution (scripts/build/linux/bundle-linux{,-zh}.sh):
  - HEAD (sagepatch-qol) added an optional SagePatch block that copies
    libsage_patch.so + Override.ini into the tarball.
  - origin/main added a mandatory FFmpeg runtime-lib copy block plus
    a libavcodec.so* presence check.
  - Resolved by keeping both blocks: SagePatch first (still gated on
    RTS_BUILD_OPTION_SAGE_PATCH=ON), then FFmpeg + check. Order does
    not matter; they operate on disjoint paths in the bundle.

All other files (CMakeLists.txt, CMakePresets.json, config-build.cmake,
deploy scripts) auto-merged without conflicts.

Post-merge TODO (not in this commit, flagged for follow-up):
  The 4 bundle scripts (linux + macos, both flavors) still write the
  SagePatch override to Data/INI/Default/GameData/SagePatch.ini
  (path1, parsed FIRST then overwritten by vanilla GameData.ini on
  path2). PR commit 598d8fd fixed the deploy scripts but missed the
  bundle scripts. Tracked separately; defer until build verification
  proves baseline works.
SagePatch override is now routed through a per-user config dir
(SagePatch/SagePatch.ini), symlinked into the engine's cwd path2 slot.
Users can edit QoL settings without touching the .app bundle, the
tarball, or the game data dir.

User config locations:
  - macOS:   ~/Library/Application Support/GeneralsX/SagePatch/
  - Linux:   $XDG_CONFIG_HOME/GeneralsX/SagePatch/  (or ~/.config/...)
  - Windows: stub (Phase 2 — proxy d3d8.dll)

Launcher flow at game start (cross-OS):
  1. Resolve user config dir from $HOME / $XDG_CONFIG_HOME.
  2. mkdir -p the user config dir.
  3. If the user ini is missing, seed it from the bundle/tarball
     default (one-shot). User owns the file from then on.
  4. Remove any stale path1 (Data/INI/Default/GameData/SagePatch.ini)
     leftover from a previous install.
  5. Symlink <cwd>/Data/INI/GameData/SagePatch.ini -> user config.
     The engine parses cwd, follows the symlink, and the user values
     win the INI load order (path2 > path1).
  6. User edits to the override file are visible to the engine on the
     next launch with no re-seed required.

Also fixes the pre-existing path1→path2 inconsistency in the four
bundle scripts: the bundle default is now written to
Data/INI/GameData/ (path2, parsed last, wins) instead of
Data/INI/Default/GameData/ (path1, parsed first, overwritten by
vanilla). The deploy scripts already had this fix from PR commit
598d8fd; the bundle scripts were missed. Tarball/.app users now get
the override applied.

Bundle script changes (path1→path2 + new run.sh launcher block):
  - scripts/build/linux/bundle-linux.sh
  - scripts/build/linux/bundle-linux-zh.sh
  - scripts/build/macos/bundle-macos-zh.sh
  - scripts/build/macos/bundle-macos-generals.sh

Smoke-tested all 4 scenarios in /tmp/sagepatch-smoke:
  1. Pre-existing user override preserved (not overwritten by seed)
  2. No user override -> seeded from bundle + symlink created
  3. Stale path1 file from a previous install is removed
  4. User edits to the override file are picked up via the symlink
     on the next launch (no re-seed required)

Engine source untouched. build verification (g_generals + z_generals
+ sage_patch) was already green on macos-vulkan before this commit.
The deploy path (configure -> build -> deploy) generates its own
run.sh, separate from the bundle path. Without the user-config-dir
pattern in this run.sh, locally-deployed builds would still write the
SagePatch override into the deploy dir without routing it through
~/Library/Application Support/GeneralsX/SagePatch/.

Applies the same pattern added in 9023734 to:
  - scripts/build/linux/deploy-linux-zh.sh
  - scripts/build/macos/deploy-macos-zh.sh

The macOS deploy uses an unquoted heredoc (<< WRAPPER) so every $
in the new block is escaped at deploy time and resolves to a literal
$ in the generated run.sh. Verified by simulating a full deploy
and sourcing the pattern block from the resulting run.sh: user INI
seeded, symlink created, stale path1 removed.

Known inconsistency (not addressed in this commit, flagged for
follow-up): the base-game deploy scripts
(scripts/build/linux/deploy-linux.sh and
scripts/build/macos/deploy-macos-generals.sh) have no SagePatch
block at all. The base-game BUNDLE scripts do have SagePatch, so
distribution builds of the base game ship with SagePatch, but local
deploy of the base game does not. Adding SagePatch to the base-game
deploys is a separate refactor (would also need the LD_PRELOAD /
DYLD_INSERT_LIBRARIES wiring in their run.sh heredocs).

Engine source untouched. Bundle scripts already updated in
9023734. The user-config-dir pattern is now consistent across
bundle and deploy paths for the Zero Hour build on both platforms.
The base-game deploy scripts (deploy-linux.sh, deploy-macos-generals.sh)
had no SagePatch wiring at all. The base-game BUNDLE scripts did
have it, so distribution builds of the base game shipped with
SagePatch, but local deploy of the base game did not. This commit
adds the missing wiring so configure -> build -> deploy works the
same way for Generals (base) and GeneralsXZH (expansion).

Both deploy scripts now:
  1. Copy libsage_patch.{so,dylib} + Override.ini from build dir to
     runtime dir when present. Override is written to path2
     (Data/INI/GameData/, parsed last by the engine) and any stale
     path1 copy from previous deploys is removed.
  2. Generate a run.sh that sets DXVK_HUD='fps' when SagePatch is
     active (matching the ZH deploy behavior) and preloads the
     library via LD_PRELOAD (Linux) or DYLD_INSERT_LIBRARIES (macOS).
  3. cd to ${SCRIPT_DIR} before the SagePatch user-config-dir block
     so the symlink is created at the right engine-cwd path2 slot.
  4. Apply the same user-config-dir + symlink pattern that the ZH
     deploy and the bundles already use, routing the override through
     ~/Library/Application Support/GeneralsX/SagePatch/ (macOS) or
     $XDG_CONFIG_HOME/GeneralsX/SagePatch/ (Linux).
  5. Print a SagePatch row in the deploy summary when the dylib
     is present.

macOS base deploy uses << 'WRAPPER' (quoted heredoc), so no \$
escapes are needed in the new block. The ZH macOS deploy uses
<< WRAPPER (unquoted) and was already covered in 192b446.

All 4 SagePatch-related references (4 .sh files: deploy+scripts in
Core/Libraries) now have identical behavior across:
  - Linux/macOS x base/expansion x bundle/deploy (16 combinations
    total, all gated on libsage_patch.{so,dylib} presence so a
    SAGE_PATCH=OFF build still ships a working vanilla game).

Engine source untouched. Build verification was already green
before this commit (g_generals + z_generals + sage_patch).
…uation

Documents the 3 follow-up commits that continued ebellumat's PR #110
(9023734, 192b446, 1e1bede) on top of the merged main. Covers the
bundle script user-config-dir + symlink pattern, the path1 to path2
fix, and the base-game deploy parity work across 8 launcher scripts
(bundle + deploy, Linux + macOS, base + expansion).

No code or build changes in this commit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants