Skip to content

Library: a game's own madeira.cfg lines (Game details › This game's c… - #167

Merged
willfaust merged 1 commit into
willfaust:mainfrom
bahacan16:pr/per-game-config
Oct 4, 2026
Merged

willfaust merged 1 commit into
willfaust:mainfrom
bahacan16:pr/per-game-config

Conversation

@bahacan16

Copy link
Copy Markdown
Contributor

…onfig)

Problem

Every runtime switch lives in one file, Documents/madeira.cfg, so it applies to every game. Trying a switch for one game means editing the file before that game and undoing it before the next. Real cases from device testing:

  • God of War (2018, D3D11) ran out of memory on New Game (texture memory 4 GB). It reached gameplay with dxmt = d3d11.mipClampBC=1 (textures 4051 MB → 1050 MB) and env.WINEDEBUG = err+all,err-virtual,fixme-all (95 % of its log was one fixme). Neither should apply to other games.
  • Ghost of Tsushima (D3D12) was tuned with D3D12 runtime keys (fence-chain, async-submit, dxil-tess-max-factor) that other D3D12 games do not want.

There is also a bug in how dxmt reaches DXMT: ContentView turns dxmt = a=b;c=d into a=b\nc=d. DXMT splits DXMT_CONFIG on ; only (src/util/config/config.cpp, str::split(confLine, ";")) and a newline is not whitespace to its line parser, so two options become one option with a broken value. Only a single option survives today.

Change

  • LibraryEntry.config (optional, so older library files decode): the game's own lines in madeira.cfg's syntax.
  • Game details › Advanced › This game's config: a navigation row (showing "None" or "N settings") that opens a monospaced text editor. The text is saved with the entry like the other fields.
  • MadeiraConfig.applyGame, called from LibraryEntry.applyEnvironment on every library launch (Dock launches included): when the text sets anything, it is written to Application Support/madeira-game.cfg and MADEIRA_CFG_GAME names that file; otherwise the variable is unset and the file removed, so one game's lines never leak into the next session. A launch from the developer interface clears it too. The launch log gets a [game-cfg] key=value ... line.
  • build/madeira_cfg.h: the line scan moves into madeira_cfg__scan (no behaviour change for madeira.cfg, BOM handling kept), and madeira_cfg_get scans $MADEIRA_CFG_GAME after madeira.cfg (or the legacy files), so a key set there wins. madeira_cfg_int, madeira_cfg_bool and madeira_cfg_sync_engine follow automatically. This covers every key the native side reads (ntdll, wineserver, madsync, winemetal, the D3D12 runtime through MadeiraCtl, the converter).
  • WineProcessBridge.m: the game file's env.NAME = value lines are exported right after madeira.cfg's and before the fastsync default, so they win and a game's env.MADEIRA_FASTSYNC is respected ([madeira-env] game NAME=value in the log).
  • ContentView.swift: dxmt options from madeira.cfg and from the game's lines are split on ; and newlines and joined with ; into DXMT_CONFIG; the log names each source. This also fixes the separator bug above for madeira.cfg alone.
  • FPSOverlay.swift: the F pill's initial state reads the game's fence-chain before madeira.cfg's, matching what the D3D12 runtime uses.
  • MadeiraConfig.parse is factored out of all() for the game file.
  • ConfigCatalog.generated.swift regenerated (one new row, env.MADEIRA_CFG_GAME, marked as set by the app).
  • docs/LIBRARY.md describes the section; tests/host/check-frontend.py's MadeiraConfig stub gains applyGame and checks that a launch hands the lines over and a launch without any clears them.
  • tests/host/check-cfg-game.py (new): compiles the production madeira_cfg.h and checks game-wins, fallthrough to madeira.cfg, last-line-wins, BOM + CRLF, a missing or empty variable, and the legacy layout; it also checks the export order in the bridge and the ; join.

Evidence

From the fork, where the same mechanism has been in use since late September (iPhone 17 Pro Max, iOS 27):

  • God of War reached gameplay with the two lines above in its own file, while other games kept the defaults.
  • Ghost of Tsushima: two DXMT options joined with a newline (dxgi.customDeviceId=2544 and d3d11.metalSpatialUpscaleFactor=2.0) arrived as one option (Found config env: dxgi.customDeviceId=2544\nd3d11...), DXGI reported device 0 and the game's GPU check failed ("Unable to find active GPU"). With only the device id in DXMT_CONFIG the same build passed the check (NVIDIA GeForce RTX 3060 (active)), which confirms the separator diagnosis.
  • python3 tests/host/check-cfg-game.py and tests/host/check-cfg-early-docs.py pass.

Notes / risks

  • Keys the app itself reads in Swift at launch (pool, wx, d3d9, desktop-size, ...) still come from madeira.cfg only; dxmt and the F pill's fence-chain are the Swift readers that look at the game's lines. The footer says "wherever the runtime reads it".
  • Keys read once per app run (for example inproc-sync, which wineserver keeps for the whole run) only take effect if that game is the run's first session, as with madeira.cfg today.
  • madeira_cfg.h is header-only and compiled into several libraries; they need a rebuild for the game file to apply on their side (ntdll unix, wineserver/madsync, libdxmt_unix.a for winemetal's MadeiraCtl op 2, the converter service). Until then those components simply ignore the game file.
  • No new Swift file; project.pbxproj is unchanged.
  • tests/host/check-config-catalog.py passes with the dxmt/wine/FEX submodules checked out at the pinned commits.

🤖 Generated with Claude Code

Claude-Session: https://claude.ai/code/session_0189oLHghpaYKLk4f786a6bc

…onfig)

## Problem

Every runtime switch lives in one file, `Documents/madeira.cfg`, so it applies to every game. Trying a switch for one game means editing the file before that game and undoing it before the next. Real cases from device testing:

- **God of War (2018, D3D11)** ran out of memory on New Game (texture memory 4 GB). It reached gameplay with `dxmt = d3d11.mipClampBC=1` (textures 4051 MB → 1050 MB) and `env.WINEDEBUG = err+all,err-virtual,fixme-all` (95 % of its log was one `fixme`). Neither should apply to other games.
- **Ghost of Tsushima (D3D12)** was tuned with D3D12 runtime keys (`fence-chain`, `async-submit`, `dxil-tess-max-factor`) that other D3D12 games do not want.

There is also a bug in how `dxmt` reaches DXMT: `ContentView` turns `dxmt = a=b;c=d` into `a=b\nc=d`. DXMT splits `DXMT_CONFIG` on `;` only (`src/util/config/config.cpp`, `str::split(confLine, ";")`) and a newline is not whitespace to its line parser, so two options become one option with a broken value. Only a single option survives today.

## Change

- **`LibraryEntry.config`** (optional, so older library files decode): the game's own lines in madeira.cfg's syntax.
- **Game details › Advanced › This game's config**: a navigation row (showing "None" or "N settings") that opens a monospaced text editor. The text is saved with the entry like the other fields.
- **`MadeiraConfig.applyGame`**, called from `LibraryEntry.applyEnvironment` on every library launch (Dock launches included): when the text sets anything, it is written to `Application Support/madeira-game.cfg` and `MADEIRA_CFG_GAME` names that file; otherwise the variable is unset and the file removed, so one game's lines never leak into the next session. A launch from the developer interface clears it too. The launch log gets a `[game-cfg] key=value ...` line.
- **`build/madeira_cfg.h`**: the line scan moves into `madeira_cfg__scan` (no behaviour change for madeira.cfg, BOM handling kept), and `madeira_cfg_get` scans `$MADEIRA_CFG_GAME` after madeira.cfg (or the legacy files), so a key set there wins. `madeira_cfg_int`, `madeira_cfg_bool` and `madeira_cfg_sync_engine` follow automatically. This covers every key the native side reads (ntdll, wineserver, madsync, winemetal, the D3D12 runtime through `MadeiraCtl`, the converter).
- **`WineProcessBridge.m`**: the game file's `env.NAME = value` lines are exported right after madeira.cfg's and before the fastsync default, so they win and a game's `env.MADEIRA_FASTSYNC` is respected (`[madeira-env] game NAME=value` in the log).
- **`ContentView.swift`**: `dxmt` options from madeira.cfg and from the game's lines are split on `;` and newlines and joined with `;` into `DXMT_CONFIG`; the log names each source. This also fixes the separator bug above for madeira.cfg alone.
- **`FPSOverlay.swift`**: the F pill's initial state reads the game's `fence-chain` before madeira.cfg's, matching what the D3D12 runtime uses.
- **`MadeiraConfig.parse`** is factored out of `all()` for the game file.
- `ConfigCatalog.generated.swift` regenerated (one new row, `env.MADEIRA_CFG_GAME`, marked as set by the app).
- `docs/LIBRARY.md` describes the section; `tests/host/check-frontend.py`'s `MadeiraConfig` stub gains `applyGame` and checks that a launch hands the lines over and a launch without any clears them.
- **`tests/host/check-cfg-game.py`** (new): compiles the production `madeira_cfg.h` and checks game-wins, fallthrough to madeira.cfg, last-line-wins, BOM + CRLF, a missing or empty variable, and the legacy layout; it also checks the export order in the bridge and the `;` join.

## Evidence

From the fork, where the same mechanism has been in use since late September (iPhone 17 Pro Max, iOS 27):

- God of War reached gameplay with the two lines above in its own file, while other games kept the defaults.
- Ghost of Tsushima: two DXMT options joined with a newline (`dxgi.customDeviceId=2544` and `d3d11.metalSpatialUpscaleFactor=2.0`) arrived as one option (`Found config env: dxgi.customDeviceId=2544\nd3d11...`), DXGI reported device 0 and the game's GPU check failed ("Unable to find active GPU"). With only the device id in `DXMT_CONFIG` the same build passed the check (`NVIDIA GeForce RTX 3060 (active)`), which confirms the separator diagnosis.
- `python3 tests/host/check-cfg-game.py` and `tests/host/check-cfg-early-docs.py` pass.

## Notes / risks

- Keys the app itself reads in Swift at launch (`pool`, `wx`, `d3d9`, `desktop-size`, ...) still come from madeira.cfg only; `dxmt` and the F pill's `fence-chain` are the Swift readers that look at the game's lines. The footer says "wherever the runtime reads it".
- Keys read once per app run (for example `inproc-sync`, which wineserver keeps for the whole run) only take effect if that game is the run's first session, as with madeira.cfg today.
- `madeira_cfg.h` is header-only and compiled into several libraries; they need a rebuild for the game file to apply on their side (ntdll unix, wineserver/madsync, `libdxmt_unix.a` for winemetal's `MadeiraCtl` op 2, the converter service). Until then those components simply ignore the game file.
- No new Swift file; `project.pbxproj` is unchanged.
- `tests/host/check-config-catalog.py` passes with the dxmt/wine/FEX submodules checked out at the pinned commits.

---
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0189oLHghpaYKLk4f786a6bc
Signed-off-by: bahacan16 <190844990+bahacan16@users.noreply.github.com>
willfaust pushed a commit that referenced this pull request Oct 4, 2026
#167)

Squashed from #167.

Signed-off-by: bahacan16 <190844990+bahacan16@users.noreply.github.com>
willfaust added a commit that referenced this pull request Oct 4, 2026
Frame generation (#171) is no longer the only other one: the AVX choice
(#172) and a game's own config lines (#167) export settings too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@willfaust willfaust closed this Oct 4, 2026
@willfaust willfaust reopened this Oct 4, 2026
@willfaust
willfaust merged commit a70caf9 into willfaust:main Oct 4, 2026
spitefulowl added a commit to spitefulowl/Madeira that referenced this pull request Oct 4, 2026
…ot read

madeira.cfg's `dxmt = a=b;c=d` line used to reach DXMT with every ';'
turned into a newline (ml1095). DXMT splits DXMT_CONFIG on ';' only
(util/config/config.cpp), and its value scanner does not stop at '\n', so
the first key swallowed the rest of the line: a multi-option line never
worked. Since willfaust#167 the options of madeira.cfg's dxmt line and of a library
game's own config are joined with ';', which fixes that. Two things were
still missing:

- DXMT reads the variable into a MAX_PATH buffer (util_env.cpp getEnvVar)
  and gets nothing at all from a value longer than 259 characters, so a
  long line silently lost every option. The joined value of both sources is
  now measured, and an error is logged past 259 characters. Pieces starting
  with '#' are dropped: DXMT skips them, but they counted against that
  limit.
- Each source's log line says how many options it passed:
  `DXMT config: ... via madeira.cfg dxmt (N options)`, and `via the game's
  config (N options)`.

The Settings catalog note for `dxmt` and docs/LIBRARY.md say the same.

Tagged ml1255. The ';' join was verified on the device before willfaust#167 (the
owner confirmed every option of a two-option line arrived, and the log read
`(2 options)`); the length check of the joined value is built only.
check-cfg-game passes.

Renumbered from ml1154 to ml1255: upstream uses those numbers for other
work. Device logs from before the renumbering show the old tags.

Signed-off-by: spitefulowl <spitefulowll@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
spitefulowl added a commit to spitefulowl/Madeira that referenced this pull request Oct 4, 2026
A library game exports its own engine switches before the session starts
(LibraryEntry.applyEnvironment): MADEIRA_FASTSYNC and MADEIRA_FASTSYNC_SEM
(Fast synchronization, Fast semaphore waits), MADEIRA_CPU_COUNT,
DXMT_D9_ANISO_LIMIT, FEX_X87REDUCEDPRECISION (Reduced-precision x87),
MADEIRA_DINPUT_PAD (Controller: XInput and DirectInput), MADEIRA_FEX_AVX
(AVX and AVX2, willfaust#172) and MADEIRA_FRAMEGEN (Frame generation, willfaust#171). The
bridge then exported madeira.cfg's env.* lines with overwrite, so a cfg line
for the same key replaced the game's choice: `env.MADEIRA_FASTSYNC = auto`
undid a game's "Fast synchronization: off", and `env.MADEIRA_FEX_AVX = 0`
would undo its "AVX and AVX2", although its details page says the game's
setting applies. The values also stayed in the app's environment after the
session, so a later launch in the same app run (MADEIRA_ONE_SESSION_PER_RUN=0)
inherited them.

Now a cfg line for one of these keys is skipped, and logged as
`[madeira-env] ml1184 KEY=<game's> kept (the game's own setting);
madeira.cfg's <value> ignored`, when the game set that key for this launch;
the keys are unset when the session ends. Which keys the game set is noted
once, before the first cfg line is exported: testing getenv() at each line
would take a key's earlier cfg line for the game's own setting, so with two
lines for one key Wine would run with the first value while Settings
(madeira.cfg is last-line-wins) shows the last. A game's own config lines
(This game's config, willfaust#167) are exported after this loop and still win over
both; docs/LIBRARY.md states the order.

MADEIRA_DINPUT_PAD joined the list later (ml1240): the per-game DirectInput
choice now beats the cfg key too, the Session menu says that the choice
applies from the next launch (the DirectInput device is made at launch), and
docs/CONTROLLERS.md names it the picker's second choice (it said third).

check-frontend gains the DirectInput export checks, a source check that the
per-launch keys are noted before the cfg loop, and one that every key
applyEnvironment exports is in the list and unset at the session's end, so a
new switch on the game's page cannot be left out; check-dock-gdi-section
searches the longer export loop to its end.

Tagged ml1184 and ml1240. Built only: the 2026-09-30 and 2026-10-03 builds
carried ml1184/ml1240 with the per-line test, but no device log shows a kept
line; the snapshot before the loop is syntax-checked with the iOS SDK.
check-frontend and check-dock-gdi-section pass.

Signed-off-by: spitefulowl <spitefulowll@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants