fix(dxvk): deploy markerless native dxgi into prefix when gptk is active - #251
fix(dxvk): deploy markerless native dxgi into prefix when gptk is active#251dappermint wants to merge 7 commits into
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
frankea
left a comment
There was a problem hiding this comment.
Verified hands-on: simulated the merge on main, built the app target and WhiskyKit, and ran the full suite (267 tests pass, lint clean). Checked the marker mechanics against the real runtime: e_lfanew is 0x80 on the runtime's dxgi.dll, so the 16-byte write at 0x40 lands entirely in DOS-stub padding, and the loader really does key on that string. A failed strip is caught and degrades to today's builtin redirect. The switch-away paths are covered too: a backend change prunes the stale dxgi=n,b on the next launch, and without an override the loader prefers the builtin (the runtime dxgi carries no PREFER_NATIVE flag), so the lingering native copy is inert outside DXVK. 32-bit is untouched since GPTK never writes i386-windows.
One change before merging: guard deployCleanDXGIForDXVK on GPTKImporter.isDeployed() in addition to the file check. The store survives engine installs but the deployed payload does not, so after an update to a non-gptkCapable runtime the previous engine's originals/ persists (neither prepareOriginals nor remove runs), and every DXVK launch would copy the old engine's dxgi into the prefix and load it as native against the new runtime. isDeployed() closes that: when the payload is not deployed the builtin is already clean and the copy is pointless; when it is, the deploy just refreshed the backups.
Two smaller notes, non-blocking:
- The nvapi/nvngx widening of
disablingNVAPIis a second behavior change the PR body does not mention; worth a line there so it is discoverable from the squash. - If GPTK is later un-deployed, the stripped copy stays in the prefix and keeps loading as native under DXVK; it matches the restored builtin at that moment but skews after the next runtime update. Cheap hardening, fine as a follow-up: when the guard fails and a markerless dxgi sits in system32, remove it and let the
,bhalf load the builtin.
|
Follow-up: I now have a live #248 reproduction on this machine (GPTK 4.0b2 imported and deployed on Related finding from the same session, verified bidirectionally by experiment: on payload-less installs, a DXMT launch leaves its native |
DXVK-macOS ships no dxgi.dll, so enableDXVK never overwrote the native copy a DXMT deploy leaves in system32 and syswow64. A DXVK launch then paired DXVK's d3d11 with DXMT's dxgi under the n,b override, and that mix cannot create window swapchains (DXGI_ERROR_UNSUPPORTED, EGL_BAD_ALLOC in Chromium clients): Steam ran with no window at all after a bottle switched from DXMT to DXVK. Verified bidirectionally on a real bottle against v4.6.4-beta.1: planting DXMT's dxgi reproduces the failure on the next launch and removing it brings the window back. Details on #163. The removal stands down when the GPTK importer's originals backup exists, since the builtin behind the override is then Apple's forwarder and the prefix needs a clean native copy instead (#251 owns that case). Builtin-marked files are never touched.
DXVK-macOS ships no dxgi.dll, so enableDXVK never overwrote the native copy a DXMT deploy leaves in system32 and syswow64. A DXVK launch then paired DXVK's d3d11 with DXMT's dxgi under the n,b override, and that mix cannot create window swapchains (DXGI_ERROR_UNSUPPORTED, EGL_BAD_ALLOC in Chromium clients): Steam ran with no window at all after a bottle switched from DXMT to DXVK. Verified bidirectionally on a real bottle against v4.6.4-beta.1: planting DXMT's dxgi reproduces the failure on the next launch and removing it brings the window back. Details on #163. The removal stands down when the GPTK importer's originals backup exists, since the builtin behind the override is then Apple's forwarder and the prefix needs a clean native copy instead (#251 owns that case). Builtin-marked files are never touched.
|
pushed the heads up before you write the separate PR: that commit also handles the payload-less case, and i think it collides with what you are about to build. the guard's else branch removes a markerless guard GPTKImporter.isDeployed() else {
let sys32DXGI = bottle.url.appending(path: "drive_c").appending(path: "windows")
.appending(path: "system32").appending(path: "dxgi.dll")
try? FileManager.default.removeItem(at: sys32DXGI)
return
}that came from your second non-blocking note, the stripped copy going stale after a runtime update. but it fires on the same condition as your DXMT finding: payload-less install, DXVK enable, a native dxgi sitting in system32 from an earlier DXMT launch. so two differences worth deciding on, both in your favour if you want the separate PR anyway:
if your PR wants to own the payload-less case properly then say so and i will strip the else branch back to a bare separately, noted on the remote-pages residual bisecting to 11.15 to 11.16 and that it is not this PR's. i will pick that up on #163. |
|
Ran the real three-way merge onto current main (post #252/#253) and verified the mechanics hands-on. The deploy-and-strip core checks out: the marker string sits at 0x40 in both the runtime's builtin i386 dxgi.dll and the stored original, and stripping a copy of the real stored original changes exactly 16 bytes, leaving a file isNativePE accepts. I can also confirm 2f2594b's premise directly: this machine is in the stale-store state right now (originals/ populated, unix-side payload gone, isDeployed() false), which is exactly where an originals-exist predicate misfires and yours reads it right. So: right mechanism, but the branch needs a rebase onto current main, and the remaining issues all live at the seam with #253.
On my merged resolution the app builds, the full kit suite passes (267 tests in 36 suites), and lint is clean at strict, so nothing structural is in the way. Two notes on the body: "fixes #248" points at an issue that #253's merge already auto-closed, so reword or leave it as you prefer; and the end-to-end crash fix under a real deployed payload is still unverified on my side (no payload deployable on this machine), though the loader evidence and the bottle-clone experiments from #253 make it credible. |
DXVK-macOS ships no dxgi.dll and relies on Wine's DXGI implementation. When D3DMetal is deployed globally into the runtime tree, the builtin dxgi.dll becomes Apple's forwarder, which causes DXVK-based launchers (like Steam CEF) to fail during device creation. Copy Wine's backed-up clean dxgi.dll from the GPTK originals store into the prefix system32 and strip the 'Wine builtin DLL' marker at 0x40 so Wine loads it as a native PE when overridden.
The store outlives an engine install but the deployed payload does not, so a stale originals/ can belong to a previous engine. Without the payload the builtin is already Wine's own and there is nothing to work around. When the payload is undeployed, drop any stripped copy left in the prefix so the next runtime update does not load a stale native dxgi against the restored builtin.
enableDXVK now runs one reconciliation step instead of stacking a removal and a deploy. Which dxgi.dll the prefix needs depends on what the builtin is: with the D3DMetal payload deployed it is Apple's forwarder, which DXVK's d3d11 cannot pair with, so wine's backed-up original is installed and marker-stripped; without the payload the builtin is wine's own, so a native copy in the prefix is DXMT residue and is removed. The removal side is frankea#253's isNativePE-guarded both-arch helper, called rather than reimplemented. The old else branch did a blind removeItem on system32 only, which deleted wine's builtin-marked placeholder on every DXVK launch (a per-launch prefix mutation wineboot just undoes) and left syswow64 untouched, so a 32-bit title kept the bad DXVK-d3d11 plus DXMT-dxgi pairing. The payload predicate moves out of the removal helper and into the caller. Keying on originals/ alone misreads the stale-store state, where a previous engine's backups survive but the payload does not: the store says GPTK is active while the builtin is already wine's own. removeCleanDXGIForDXVK is dropped rather than wired into undeploy. It had no caller, and the reconciliation step already restores the prefix on the next DXVK launch without a second owner of that path. deployCleanDXGI and the reconciliation step take injectable originals and payload-state seams, mirroring removeStaleNativeDXGI, with tests covering the deployed, not-deployed and syswow64 cases.
2f2594b to
cdcbc4a
Compare
|
rebased and rewritten, going through your four points in order. 1. rebase. on current main now, no textual conflict with #253. the 2. one step, not a stack. 3. 4. seams and coverage. local checks, being upfront about what i could and could not run on this machine:
so please let ci be the source of truth on lint and the test suite. |
removing a markerless native dxgi.dll left the slot empty, and wine's loader
only reaches a builtin through its system32 placeholder: find_builtin_without_file
refuses every non-16-bit dll that has no file outside prefix bootstrap. with
nothing there LoadLibrary("dxgi.dll") fails in every process, chromium's gpu
process cannot bring up d3d11 and crash-loops into software rendering, and
steam's store, community and profile pages go black while the client chrome
and library keep rendering.
it only showed on a runtime whose wine.inf mtime matched the bottle's update
stamp, because any other runtime triggers a prefix update that silently
reinstalls the placeholder. that is why it read as a wine 11.16 regression:
runtime 4.6.4 is the first build that exactly matches the bottles it
populated. the wine side is unchanged.
restore the runtime's own marked copy into system32 and syswow64, which is
what wineboot installs, so the n,b override still defers to the builtin.
reconcileDXGIForDXVK threads the runtime folder through so the payload-absent
path restores it too, and the tests inject a runtime folder rather than
reading the installed one.
builds on #253. #248 was already closed by that merge, so this does not carry a fixes line; it finishes the gptk half of the same dxgi problem (#163).
problem
dxvk-macos ships no
dxgi.dlland relies on wine's dxgi implementation. when d3dmetal is deployed into the wine runtime, the builtindxgi.dllbecomes apple's forwarder. steam's cef gpu process under dxvk then pairs dxvk's natived3d11.dllwith that forwarder, swapchain creation fails, and the client crash-loops into a black window.copying wine's clean
dxgi.dllintosystem32does not help on its own: it still carries the 16-byte"Wine builtin DLL"marker at0x40, so the loader treats it as a builtin and redirects back to the forwarder.#253 handles the other direction (payload absent, dxmt residue in the prefix). before this pr the two were stacked: a blind
removeItemon system32 followed by a deploy, which deleted wine's builtin-marked placeholder on every dxvk launch and never touched syswow64.fix
enableDXVKnow runs one step,reconcileDXGIForDXVK(prefixRoot:gptkOriginalsDXGI:gptkPayloadIsDeployed:), keyed on whether the gptk payload is deployed:originals/intosystem32and strip the marker at0x40so wine loads it as a true native pe undern,b. only system32 is written; gptk deploys forwarders intox86_64-windowsonly, so the 32-bit builtin is still wine's own.removeStaleNativeDXGI(prefixRoot:), which sweeps both arches and skips anything builtin-marked, then put the runtime's own markeddxgi.dllback into the slot it emptied. wine's loader only reaches a builtin through its system32 placeholder (find_builtin_without_fileinntdll/loader.crefuses every non-16-bit dll that has no file outside prefix bootstrap), so an empty slot makesLoadLibrary("dxgi.dll")fail in every process. that is the black store, community and profile on v4.6.4 in wine build cannot execute gptk/d3dmetal payloads: unwinder lacks personality routine support in builtin modules #163: chromium's gpu process cannot bring up d3d11 and crash-loops into software. the copy is what wineboot installs, son,bstill defers to the builtin, and a marked file already in place is left alone.the predicate is the payload, not the store.
originals/outlives an engine install and the payload does not, so a stale store from a previous engine would otherwise read as "gptk active" and deploy a dxgi the current runtime never shipped. the store path and payload predicate are injectable parameters with production defaults;removeStaleNativeDXGIloses its store guard and parameter since the caller owns that decision now.removeCleanDXGIForDXVKis dropped. it had no caller, and the next dxvk launch reconciles the prefix anyway.also in this pr
disablingNVAPInow clearsnvapiandnvngxalongsidenvapi64, so launcher helper processes get every nvidia bridge dll disabled rather than just the 64-bit nvapi one.NVAPIBridgeTestsupdated to match.tests
eight cases in
WineDXVKResidueTestsdrivereconcileDXGIForDXVKandremoveStaleNativeDXGIdirectly: deployed installs the stripped original, deployed replaces existing residue, deployed leaves syswow64 alone, stale store replaces residue with the builtin in both arches, not-deployed keeps the builtin placeholder, deployed without a stored original is a no-op, removed residue is replaced by the builtin placeholder, and an existing placeholder is not rewritten. the removal tests inject a runtime folder rather than reading the installed one. the oldtestNativeDXGIIsKeptWhenGPTKOriginalsExistis replaced by the stale-store case, since that state now takes the removal path by design.