Repository navigation
Library: a game's own madeira.cfg lines (Game details › This game's c… - #167
Merged
Merged
Conversation
…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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…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:dxmt = d3d11.mipClampBC=1(textures 4051 MB → 1050 MB) andenv.WINEDEBUG = err+all,err-virtual,fixme-all(95 % of its log was onefixme). Neither should apply to other games.fence-chain,async-submit,dxil-tess-max-factor) that other D3D12 games do not want.There is also a bug in how
dxmtreaches DXMT:ContentViewturnsdxmt = a=b;c=dintoa=b\nc=d. DXMT splitsDXMT_CONFIGon;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.MadeiraConfig.applyGame, called fromLibraryEntry.applyEnvironmenton every library launch (Dock launches included): when the text sets anything, it is written toApplication Support/madeira-game.cfgandMADEIRA_CFG_GAMEnames 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 intomadeira_cfg__scan(no behaviour change for madeira.cfg, BOM handling kept), andmadeira_cfg_getscans$MADEIRA_CFG_GAMEafter madeira.cfg (or the legacy files), so a key set there wins.madeira_cfg_int,madeira_cfg_boolandmadeira_cfg_sync_enginefollow automatically. This covers every key the native side reads (ntdll, wineserver, madsync, winemetal, the D3D12 runtime throughMadeiraCtl, the converter).WineProcessBridge.m: the game file'senv.NAME = valuelines are exported right after madeira.cfg's and before the fastsync default, so they win and a game'senv.MADEIRA_FASTSYNCis respected ([madeira-env] game NAME=valuein the log).ContentView.swift:dxmtoptions from madeira.cfg and from the game's lines are split on;and newlines and joined with;intoDXMT_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'sfence-chainbefore madeira.cfg's, matching what the D3D12 runtime uses.MadeiraConfig.parseis factored out ofall()for the game file.ConfigCatalog.generated.swiftregenerated (one new row,env.MADEIRA_CFG_GAME, marked as set by the app).docs/LIBRARY.mddescribes the section;tests/host/check-frontend.py'sMadeiraConfigstub gainsapplyGameand checks that a launch hands the lines over and a launch without any clears them.tests/host/check-cfg-game.py(new): compiles the productionmadeira_cfg.hand 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):
dxgi.customDeviceId=2544andd3d11.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 inDXMT_CONFIGthe same build passed the check (NVIDIA GeForce RTX 3060 (active)), which confirms the separator diagnosis.python3 tests/host/check-cfg-game.pyandtests/host/check-cfg-early-docs.pypass.Notes / risks
pool,wx,d3d9,desktop-size, ...) still come from madeira.cfg only;dxmtand the F pill'sfence-chainare the Swift readers that look at the game's lines. The footer says "wherever the runtime reads it".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.his 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.afor winemetal'sMadeiraCtlop 2, the converter service). Until then those components simply ignore the game file.project.pbxprojis unchanged.tests/host/check-config-catalog.pypasses with the dxmt/wine/FEX submodules checked out at the pinned commits.🤖 Generated with Claude Code
Claude-Session: https://claude.ai/code/session_0189oLHghpaYKLk4f786a6bc