Archived on 2026-10-16. Nothing new is made here: the engine work is finished, and the fixes now live only in the games' own folders. The per-game fixes, their sources, the table and this project's findings moved to MacGameFix, which installs them for CrossOver as CodeWeavers ships it, CrossOver Preview and Steam Play tools such as NotProton, keeps them applied and updates them. The releases here stay available as they are.
Makes Windows games show their cutscenes under CrossOver on Apple Silicon.
It used to die the moment a pre-rendered cutscene started. The cure is this project's own engine patch
mgvf-0001— 2D-capable buffers from winegstreamer's media source — and nothing at all is installed into the game's folder. Measured over about eleven minutes of play: two Media Foundation sessions, two.xmxfiles played, no exceptions.A green band along the bottom of the frame remains in some cutscenes, and it is unexplained. The buffer's current length, its maximum length and its contiguous length all agree, so the band is already in the frame when it arrives — that is as far as the measurement goes, and it is not a claim that every cause has been ruled out.
By default the app no longer modifies your CrossOver. It makes a copy, at your direction, and patches the copy, so that nobody takes a support complaint to CodeWeavers about a CrossOver we modified. Turning the copy off in Set up is the other route, and on that one a toolkit you ask for does go into the CrossOver you installed. How it runs is the whole flow.
Only the game-folder fixes, and no CrossOver patch at all: https://github.com/MathiasKowoll/MacGameFix, built from
app-gamefix/. It carries the fixes that live entirely in a game's folder and need nothing of the engine, so they work with CrossOver as it ships, CrossOver Preview and Steam Play tools such as NotProton.
More games than rows: the two KINGDOM HEARTS packages hold seven playable titles between them.
Most of these are video faults, which is what the name says. Several are not —
the rows whose Symptom column says the game does not start — and what stops each
one is answered the same way everything else here is fixed: by giving a better
answer to a call the game already makes. RESONANCE needs one thing besides, and
it is a setting rather than a monitor: it turns HDR10 on by itself on an HDR
display and starts to a black screen, so HDR10 0 goes into its ENGINESETTINGS
file. This page used to say it needed a 16:9 display; that was wrong, and the
wiki page
keeps the retraction. Tormented Souls 2 is the entry whose
fault is the game's own rather than the translation layer's: it keeps 16:9
resolutions and nothing else, and a laptop display has none.
| Game | Symptom | CrossOver | Motor | Status |
|---|---|---|---|---|
| METAL GEAR SOLID: Peace Walker | Dies the moment a pre-rendered cutscene starts | 26.3, our winegstreamer | Ours | Fixed -- crash cured; a green band in some cutscenes is unexplained |
| Mortal Shell 2 | Crash on the first cutscene | 26.3, our winegstreamer | Stock | Fixed |
| Life is Strange: Reunion | Freezes after a while, anywhere | 26.3, our winegstreamer | Stock | Runaway node walk stopped; no freeze-free session recorded |
| Life is Strange: Double Exposure | Freezes after a while, anywhere | 26.3, our winegstreamer | Stock | Guard installs; the freeze was never reproduced or cured |
| DYNASTY WARRIORS: ORIGINS | Cutscene runs with sound, picture black | 26.3, our winegstreamer | Stock† | Fixed |
| Beast of Reincarnation | Startup video plays with sound, no picture | 26.3, our winegstreamer | winevideo | Fixed |
| Persona 5 Strikers | Video never starts; sound only | 26.3, our winegstreamer | Stock | Fixed |
| NINJA GAIDEN 3: Razor's Edge | Will not start: "Insufficient VRAM" | 26.3, our winegstreamer | Stock | Starts, 60 fps, in-game cutscenes; the boot movie freezes, one click skips it |
| Nioh | Cutscene refuses to play, then crashes | 26.3, our winegstreamer | Stock† | Fixed |
| Nioh 2 | Cutscene refuses to play, then crashes | 26.3, our winegstreamer | Stock† | Fixed |
| Nioh 3 | Failed to play movie | 26.3, our winegstreamer | Stock† | Fixed |
| Wo Long: Fallen Dynasty | Cutscene runs with sound, picture black | 26.3, our winegstreamer | Stock† | Bridge installs; picture measured only on a patched engine |
| NieR Replicant ver.1.22474487139 | Crashes when the first video starts | 26.3, our winegstreamer | Stock | Fixed |
| KINGDOM HEARTS Dream Drop Distance | Cutscene runs with sound, picture solid green; a crash dialog on leaving | 26.3, our winegstreamer | Stock† | Picture restored; the exit dialog was measured on 1.5+2.5 |
| KINGDOM HEARTS 0.2 Birth by Sleep | Would not start from a launcher; ran fine launched by hand | 26.3, our winegstreamer | None | Works with nothing of ours -- fixed in the launcher |
| KINGDOM HEARTS HD 1.5+2.5 ReMIX | Cutscene runs with sound, picture solid green; a crash dialog on leaving | 26.3, our winegstreamer | Stock | Fixed |
| TMNT: Splintered Fate | Opens a window, then closes silently | 26.3, our winegstreamer | Stock | Fixed |
| Tormented Souls 2 | Fatal error before the first frame | 26.3, our winegstreamer | Stock | Fixed |
| Devil May Cry 5 | Crashes when a skill preview video plays | 26.3, our winegstreamer | Stock | Fixed |
| RESIDENT EVIL 2 | Crashes when a video plays | 26.3, our winegstreamer | Stock | Fixed |
| RESIDENT EVIL 3 | Crashes when a video plays | 26.3, our winegstreamer | Stock | Fixed |
| NINJA GAIDEN 4 | Says the VP9 codec is missing, then exits | 26.3, our winegstreamer | Stock | Fixed |
| RESONANCE: A PLAGUE TALE LEGACY | Fatal error: Shader Model 6.7 is not supported | 26.3, our winegstreamer | Stock | Plays at the desktop resolution with HDR10 off -- its cutscenes have never been visible |
| CONTROL Resonant | Windows light up in flat violet, cyan and yellow | 26.3, our winegstreamer | Stock | Plays with Metal 4 off; lit rooms behind windows show as dark glass |
| The Witcher 3: Wild Hunt | Black screen at start; DLSS said to be unsupported | 26.3, our winegstreamer | Stock | Reaches the menu and plays; DLSS works, frame generation does not. Ray tracing stays off since the game's 2026-10-08 update: one of its ray-tracing shaders uses a 64-bit atomic compare-exchange that Metal refuses to compile (measured on an M4 Max) |
| Marvel's Spider-Man 2 | Characters stay in a T-pose; the symbiote breaks; brief black blocks in the sandstorm | Preview 27, through NotProton | Stock | Fixed -- T-pose and symbiote; the black blocks were not seen again in a session with the barrier |
| God of War | White specks on characters in dim light | Preview 27, through NotProton | Stock | Fixed under Rosetta and under FEX; D3DMetal not measured |
The CrossOver column says what each row was measured on, and it is the same
answer on every row. The version is stable CrossOver 26.3, and the engine
those runs were made on carries this project's winegstreamer rather than the
one CodeWeavers ships — which is the engine the copy route in
How it runs produces. 26.3.0.39832 is the supported engine and
the only one: the winegstreamer pair inside the app was built against that
build, and runtime/install-engine-media.sh refuses any other.
CrossOver Preview is not supported. It is not an engine to run these games on, the table above records no run on it, and the copy route refuses it along with every build that is not 26.3.0.39832. It was measured against here for a long time, and the notes further down that say what it did are kept as history rather than as guidance — see Which CrossOver, and why we patch a copy.
Motor says what the engine itself has to carry. Stock is a CrossOver as
CodeWeavers shipped it, which is most of the table; a dagger on it means no run
on that row isolated the engine's own winegstreamer as the thing that carried
the video, so Stock there is read off the mechanism rather than established. It
is not a statement about the CrossOver cell beside it: a dagger neither adds a
qualifier to that cell nor takes one away. That cell records what the runs were
made on, which is the same on every row; Motor records what a fix needs an
engine to carry, so Stock beside it says the fix does not depend on ours.
The rows naming something else are the ones where the engine is part of the
fix — Beast of Reincarnation wants a winegstreamer carrying winevideo's
patches, and METAL GEAR SOLID: Peace Walker wants ours.
Each row links to a page in the wiki with that game's findings and fix. Why each fault happens, why each fix is shaped the way it is, and what was tried and did not work, is in Findings.
Tested on an M4 Max, macOS 27, against stable CrossOver 26.3 — copies of it
patched by this project, carrying our winegstreamer, which is what Set up
makes.
Apple's Game Porting Toolkit is the other half of that answer, and the two generations are not interchangeable. 4.0b2 is what these titles need as a general rule, and NINJA GAIDEN 4 is the exception at 3.0. A rule is not a run, though: the rows marked 3.0 only or 4.0b2 only in the GPTK column of the wiki's table are the ones measured refusing the other generation, and a row with no measurement of its own says so rather than inheriting the rule as a result.
Download MacGameVideoFix.app from Releases, or build it
yourself with app/build-app.sh, and open it. Set up asks four questions in
order, and the order is the point: a bottle runs under one CrossOver, so that
answer decides every answer after it.
-
Choose a CrossOver. On the copy route — the default — it is copied rather than modified, and the copy has to be made from CrossOver 26.3.0.39832: the
winegstreamerpair inside the app is built against that one engine, andruntime/install-engine-media.shrefuses any other and exits. Read your own build off this picker before you go any further: every CrossOver it offers is listed with its version beside its name, and the only entry without one is a copy this app already made, which is labelled patched by this app instead. The refusal comes after the copy has been made, so pointing it at another build leaves a half-finished bundle in~/Applications— copied, with none of the media libraries in it and never re-signed. The app reports the refusal, naming the build the libraries were built for and the one it found; delete that bundle rather than opening it.If that other build is the only CrossOver you have, take the other route rather than stopping here. Turn Work on a copy of CrossOver off in step 3 and the codecs are staged beside your bottle instead of going inside an engine. That route is not tied to one build: the staging is made from your own GStreamer install, for whichever CrossOver the bottle records. It needs GStreamer installed. Being able to run there is not the same as being measured there — every row in the table at the top of this file was measured on stable 26.3, so another build is untried rather than known to work, and CrossOver Preview is not supported at all. The route is described under CrossOver and bottles, without a copy.
-
Choose a bottle — the one the games run in.
-
Say what goes into it. By default the app copies the CrossOver you chose to
Crossover_MGVF.appin your own~/Applications, and patches the copy. In go thewinegstreamerpair this project builds, and the GStreamer plugins CrossOver does not ship — those into the engine's ownlib64/gstreamer-1.0, beside CrossOver's own. No bottle needsGST_PLUGIN_PATHand nothing is staged. Apple's Game Porting Toolkit goes in here too if you ask for it, with both halves of the toolkit it replaces kept inside the copy. Nothing of Apple's is carried by this project: the toolkit is taken from another CrossOver already installed on this Mac that ships a newer one than the CrossOver you chose, and the app offers the option only when it finds such an engine. Without one the toggle does not appear and the copy keeps the toolkit CrossOver shipped. -
Point it at your games and apply.
Play from the copy, every session. Crossover_MGVF.app in your own
~/Applications is the CrossOver you start games with from now on: open it from
Finder, pick the bottle you chose in step 2, and play there rather than in the
CrossOver you installed. This is not a first-launch formality that can be done
once and forgotten. The media libraries and the plugins went into that copy and
into nothing else, so a game started from the CrossOver you installed runs
without them — this time and every later time. Nothing was written into the
bottle either: on this route the plugins are inside the engine, so no bottle is
told anything. The copy carries an mgvf-origin.json, which is how the
installers recognise it later — a copy can be named anything, so the name is not
the identity.
On a second run, choose your original CrossOver again, not the copy. The copy appears in the same picker, labelled patched by this app, and Work on a copy stays on — so picking it would ask the app to copy a bundle over itself. It refuses rather than doing that, and says to point it at the CrossOver you installed.
Applying again rebuilds the copy from scratch rather than adding to it. The
app always passes --force, and the script removes Crossover_MGVF.app before
it copies, so what you end up with is a fresh copy of your original CrossOver
carrying this run's answers. Nothing from the run before survives it — including
a toolkit you asked for last time, which has to be asked for again if you still
want it.
The order the copy is made in is copy → every change → codesign → xattr -cr.
Signing before the last change is what makes Finder call a bundle "damaged",
which is a signature message rather than a date one, whatever the wording about
an unknown download date suggests. What to do when a copy comes out that way is
in Troubleshooting.
There is one way in now. The game picker and its drop zone are gone. They existed to point the app at a single title, and the scan does everything they did; two ways in, one of them strictly weaker, was a choice nobody should have had to make.
One game at a time still works, and it is the same gesture — drop that
game's folder on the window. The scan walks up to the library's common and
finds the game inside it, so pointing at one title and pointing at a whole
library are the same action with a different starting folder.
Identity is the shipping executable and nothing else. A folder is claimed as a supported game only when that file is really inside it. One that fails the test is not claimed, carries no checkbox, and says why. There is no continue-anyway.
An untried Unreal title cannot be attempted from the app. There used to be a
list entry for it, and it was removed rather than kept: with no shipping
executable to check against it matched any folder holding an Engine directory,
claimed to be a supported game, and put unrelated folder names on screen. The
route for one is runtime/install-runtime-fix.sh from a clone, which the wiki's
Running the scripts page covers.
The table carries rows that list does not, and for opposite reasons. METAL
GEAR SOLID: Peace Walker needs nothing picked — the patched engine is its
whole fix, and there is nothing to put beside the game. NINJA GAIDEN 3:
Razor's Edge is applied from a clone instead, with runtime/install-ng3-fix.sh,
which takes a bottle rather than a game folder; see
Running the scripts directly.
Revert puts everything back.
Turning the copy off in Set up is the other route, and it is the one the rest of this section describes. Then the codecs are staged beside a bottle instead, and a toolkit you ask for goes into the CrossOver you installed rather than into a copy of it.
The CrossOver and bottles… button opens the two things that then live outside the game folder. Stage codec borrows the decoders and the demuxer from your own GStreamer install, once per installed CrossOver. The list under it says which CrossOver each bottle runs under and which staging it points at, repairs a bottle whose CrossOver has changed since it was last configured, and lets you name the CrossOver a bottle uses when the automatic answer is wrong.
Both the button and the codec banner are hidden while you are working on a copy, because a copy carries the plugins inside the engine and staging a second set in front of it would put two GStreamer cores in one process.
The titles with a dash in the wiki's Codec column need nothing from either route.
There is a second way in, and it is the shorter one if more than one of these is installed. Find my games or Choose folder… points the app at a Steam library; it then scans, shows what it found and what it would do to each row, and applies the lot in one pass.
Nothing is read until you press Scan — choosing a folder names it rather than acts on it — and rows the app cannot act on carry no checkbox and say why instead. It exists because of the naming trap described below: Steam names install directories after the project rather than the game, which is the one thing a person has no reason to know.
That scan stays on the machine it runs on. Nothing it finds belongs in a published page or in a bug report.
Which folder a title actually lives in — for when a scan does not turn it up, and scan does not turn a title up.
For DYNASTY WARRIORS: ORIGINS, the folder holding DWORIGINS.exe — usually
steamapps/common/DWORIGINS.
For Persona 5 Strikers, the folder holding game.exe — usually
steamapps/common/P5S.
For an Unreal title, the game's own folder — the one with Engine in it:
…/steamapps/common/Sparta ← drop this one
├── Engine/
│ └── Binaries/ThirdParty/Ogg/Win64/ ← the fix rides in here
└── MortalShell2/
├── Binaries/Win64/ ← the shipping executable
└── Content/
The tell is Engine/Binaries/ThirdParty/Ogg/Win64. That is where the proxy DLL
goes, and an Unreal title that ships no libogg cannot take this fix at all —
the app says so rather than guessing.
Dropping the Steam library folder works too; the app looks one level down.
Note that Steam names install directories after the project, not the game.
Mortal Shell 2 lives under Sparta, Reunion under LifeisStrangeReunion, and
their executables are MortalShell2-Win64-Shipping.exe and
Iris-Win64-Shipping.exe respectively. Browse by path rather than by the name
on the store page — and since the app checks the executable against the title
you picked, it will tell you when the two disagree.
The app backs everything up first and has a Revert button. It shows a progress bar and streams the underlying scripts' output live, so you can see which file it is working on rather than staring at a frozen window.
A row that is already fixed is not offered for fixing again, and the reason is the one that rule always had: applying twice would move the proxy DLL aside as though it were the game's own, and lose the original.
Because the app is signed ad-hoc rather than notarised, macOS will refuse the first launch. Right click it and choose Open, then confirm.
-
Apple Silicon Mac. The app targets macOS 14 or later, which is what it will launch on. Every measurement here was made on macOS 27, so anything older is untried rather than known good.
-
Stable CrossOver 26.3.0.39832, and nothing else. That is the supported engine: the
winegstreamerpair in the app is built against that build, andruntime/install-engine-media.shrefuses any other and exits. CrossOver Preview is not supported, and neither is any other version. Every row in the table at the top of this file was measured on stable 26.3, and why the app patches a copy of it rather than the original is in the disclaimer. That installer checks the bundle's name as well as its version — a stockCrossOver.appand a copy this project has patched report the same version, so the name is the only thing that tells them apart.scripts/make-engine-copy.shinvokes that installer partway through, so a refusal leaves the copied bundle in~/Applicationswith none of the media libraries in it and the signing step never run. The optional controller-bus set,runtime/install-engine-controller.sh, has the same rule: built for 26.3.0.39832 and refused on anything else. -
A second CrossOver, if you want the newer toolkit. Apple's Game Porting Toolkit is not carried by this project. The app takes it from another CrossOver already installed on this Mac, and offers the option only when it finds one running a newer toolkit than the CrossOver you chose. 4.0b2 is what these titles need as a general rule — NINJA GAIDEN 4 is the exception and wants 3.0, which is what 26.3 already ships. Without such a second engine there is nothing to take from: a 26.3 keeps D3DMetal 3.0, and the rows the wiki's GPTK column marks 4.0b2 only have no way to run.
-
GStreamer, on one route only. The titles the wiki's Codec column marks need a decoder or a demuxer no CrossOver ships, and where that comes from depends on the answer you gave in Set up:
- Working on a copy — nothing to install. The plugins travel with the app
and go inside the engine copy, with their licences and their sha256 sums
recorded in
runtime/engine-payload/CODEC-LICENCES.md. - Not working on a copy — the official GStreamer runtime package,
1.24.13
(
gstreamer-1.0-1.24.13-universal.pkg; the development package is not needed), installed at/Library/Frameworks. Then nothing is redistributed: the decoder is borrowed from that install and staged in front of your CrossOver. Prefer that exact release over a later 1.24: it is what winevideo names, what is installed and working here, and what a launcher that owns its engine may require exactly. Earlier notes in this repository name 1.24.14 because that is what was installed when they were written.
Where the decoders come from, and why they are staged rather than patched into a CrossOver you installed, is in Findings.
- Working on a copy — nothing to install. The plugins travel with the app
and go inside the engine copy, with their licences and their sha256 sums
recorded in
Two settings live in cxbottle.conf rather than beside the game.
CX_GRAPHICS_BACKEND is a requirement rather than a preference, and it divides
these titles into groups that cannot share a bottle:
| Backend | Games |
|---|---|
d3dmetal |
the titles marked D3DMetal in the wiki's stack table |
dxmt |
the titles marked DXMT in the wiki's stack table |
dxvk |
the titles marked DXVK in the wiki's stack table |
So the DXMT group wants a bottle of its own. Steam libraries are shared between bottles, so a second bottle sees the same installed games without re-downloading anything. Why nothing but DXMT will do is on Persona 5 Strikers' page.
GST_PLUGIN_PATH is the staging route only. On a copy no bottle is told
anything: the plugins are inside the engine, and pointing a bottle at a second
set would put two GStreamer cores in one process. Without a copy the setting
points at the staged decoder, and the app writes it for you:
"GST_PLUGIN_PATH" = "…/MacGameVideoFix/gst-codecs/<CrossOver engine version>/x86_64/gstreamer-1.0"
The middle component is the CFBundleVersion of the CrossOver that bottle runs
under — the same string the bottle records as its own "Version" — and a
staging built for one engine is not usable under another. So the app writes the
line only into the bottle the title runs in, takes it back out of bottles that
have no use for it, and re-points a bottle whose CrossOver has changed. There is
nothing to edit by hand. Why the staging is per engine is in
Findings.
Both settings are read when the bottle starts, and a live wineserver keeps the
old copy — so close Steam completely, not just the game. A setting that has
not taken looks identical to a setting that did not work.
The app carries the installers and the prebuilt DLLs inside its bundle and runs them itself. Reproducing a fix by hand, or building a proxy for a title that has none, is a maintainer's job rather than a user's, and it is written up in Running the scripts directly.
The copy was refused, and there is half a bundle in ~/Applications. The
copy route serves CrossOver 26.3.0.39832 and refuses any other build, and that
check runs after the copy has been made. Delete the half-made
Crossover_MGVF.app: it has none of the media libraries in it and was never
re-signed, so it is not a CrossOver to open. Then either point step 1 at
26.3.0.39832 if you have it, or turn Work on a copy of CrossOver off and
take the staging route, which is not tied to that build.
macOS says Crossover_MGVF.app is damaged. That is Gatekeeper's wording for
a signature that does not validate, whatever it says about an unknown download
date, and what breaks the seal is a change made to the bundle after it was
signed. A copy in that state is not repaired in place: make it again from Set
up, which rebuilds it from your original CrossOver, and then leave the result
alone — changing anything inside a signed copy by hand is what puts it here. The
run ends by verifying its own signature and refuses to hand over a copy that
fails.
Steam's "verify integrity of game files" undoes this. It puts the game's own carrier DLL back and the proxy is gone. Same after a game patch. Run the fix again.
Still crashing in AllocateBuffer — the proxy is not being loaded. Check
what the app reports, and look for C:\ue5-media-fix.log in the bottle's
drive_c: if it does not exist, the DLL never ran.
Still freezing after a while — check the same log for the node guard's refusal line. If it is not there, the freeze is something else, and Diagnosing a new game covers how to look.
Persona 5 Strikers installs cleanly and still shows nothing — check what
lives outside the game folder: the bottle's backend has to be dxmt, and the
decoder has to be reachable — inside the engine if you are on a copy, and
pointed at by GST_PLUGIN_PATH if you are not. The bottle reads both when it
starts, so close Steam completely before retrying.
An Unreal title with no libogg cannot take the runtime patch — that fix
rides in on it.
A game that is not listed. Diagnosing a new game is the route: what to run, in what order, and what each answer rules out. Which of these faults are properties of an engine rather than of a title, and how a carrier DLL is picked, is in Findings.
If none of that helped, open an issue on Issues saying which game, which CrossOver build, which backend the bottle is set to, and what the log above did or did not contain.
Everything here is built from the source repository rather than from the app download.
app/build-app.shNeeds Xcode's Swift toolchain. Produces app/MacGameVideoFix.app, ad-hoc
signed, targeting arm64 macOS 14 and later.
The release ships these prebuilt. To build your own you need
llvm-mingw — or any
x86_64-w64-mingw32 toolchain — on PATH or in MINGW_BIN. The first argument
is the game's own untouched carrier DLL: its export table is read from it so the
forwarder list matches exactly.
runtime/build-proxy.sh "/path/to/<Game>/Engine/Binaries/ThirdParty/Ogg/Win64/<VS20xx>/libogg_64.dll" ue5-media-fix.c
runtime/build-proxy.sh "/path/to/DWORIGINS/libxess.dll" dwo-video-bridge.c
runtime/build-proxy.sh "/path/to/P5S/amd_ags_x64.dll" p5s-video-bridge.cName the source explicitly. build-proxy.sh defaults to ue5-runtime-fix.c,
which is the pre-merge Unreal source; ue5-media-fix.c is what the shipping
libogg_64.dll is built from. The VS20xx subfolder under Ogg/Win64 changes
between engine versions, so check what your copy has rather than assuming.
scripts/build-winegstreamer.sh --patches 0002 0003 0006 0008 mgvf-0001
scripts/make-engine-copy.sh --from /Applications/CrossOver.appThe first builds the winegstreamer pair from a CrossOver source tree with that
set of patches applied; the second makes the copy, puts the pair and the plugins
into it, signs it and clears its quarantine, in that order.
scripts/build-controller-bus.sh
scripts/install-controller-build.shThese two produce a second engine set, and it is optional: ten files from
the same tree with thirty-two patches of ours applied — winebus.sys,
setupapi.dll, ntoskrnl.exe, hidclass.sys, the five xinput DLLs, and
winebus.so, the unix half of winebus. The newest are mgvf-0031, a
lightbar colour and player number chosen per pad model and put into the light
changes a client already sends; mgvf-0032, which keeps wine's SDL bus from
opening a pad the hidraw route owns; mgvf-0033, which asks a Bluetooth
DualSense that wine holds seized to turn itself off after 20 minutes (by default) with no stick,
trigger or button input; mgvf-0034, which shares the pad with macOS by
default instead of seizing it; and mgvf-0035, which sends a chosen colour
again every two seconds, because macOS repaints the lightbar of a pad it
shares. What none of them has been measured to do is in
source-patches/README.md.
It has two halves. mgvf-0002, mgvf-0003 and mgvf-0004 let a Windows
client learn a controller is on Bluetooth. A DualSense then rumbles over Bluetooth, and its PS
button and touchpad work, as they always did over USB; trigger effects ride in
the same report, and a title that sends them over Bluetooth is reported working.
The same winebus.sys also carries mgvf-0005, a per-device registry option
— off by default — that presents a DualSense on Bluetooth as if it were on
USB, for Sony's libScePad and for Steam's plug in your controller dialog,
which both insist on a wired pad. Either DualSense, plain or Edge, can be
presented that way; runtime/engine-payload-controller/README.md says how to
turn it on. One live trace of the driver half exists, and in it the pad's
Bluetooth link died twenty-two milliseconds after the write; whether the write
caused it is not established, and mgvf-0007's header carries the session
that argues against it.
mgvf-0006 is the fourth, and it is why the set now carries a unix half:
macOS drives a connected DualSense itself, wine opened the same pad shared and
wrote to it too, and with two writers on the pad's single Bluetooth output pipe
macOS's writes timed out — measured from macOS's own log on 2026-09-08, 163
timeouts in a day and every one of them while a game was running under wine.
Link drops followed in that log. That the contention caused them, and that the
seize prevents them, is not measured, and the log can no longer be re-read;
what is measured since is that holding the PS button for about five seconds
drops a Bluetooth DualSense from the pad's own side, with or without wine.
wine seized such a pad by default until mgvf-0034, and
that cost exactly what it sounds like: while a bottle held the pad, macOS
and its own applications could not use it, until the bottle
closed. It had a second, unintended cost: macOS disconnects a Bluetooth pad
900 seconds after the last input it saw, and a seized pad shows it none, so a
pad connected before the bottle started was cut by macOS about fifteen minutes
after the last input macOS saw, in the middle of a game.
Since mgvf-0034 the pad is shared by default, and a registry value asks
for the seize per device. Shared, a DualSense Edge on Bluetooth went through
three sessions of play and a day of Bluetooth log with no drop during play,
macOS's idle cut acted only after play had ended, and the PS button, the lights
and the rumble worked. macOS does repaint a shared pad's lightbar when the front
application changes, which is why mgvf-0035 sends a chosen colour again.
That claim is the right one to make while a game runs: two programs writing
to one pad over one Bluetooth pipe is the anomaly, and of the two the game
the pad is in your hands for is the one that should win. What it costs is
not the exclusivity but its scope — it lasts as long as the bottle,
not as long as the game. Steam stays running long after a title exits and
the pad stays claimed until the bottle shuts down, so it is unavailable to
macOS through a stretch that is no longer a game. Narrowing the claim to
while a client has the device open is the improvement, and it has not been
made yet. Beside it there is one impression, and it is not a measurement:
with the set installed without mgvf-0006 the vibration felt noticeably
weaker, and felt right again once the four-file set was back — two engine
builds compared by hand, with nothing instrumented.
mgvf-0007 is the bill for mgvf-0005's lie. A client told the pad is wired
asks for the part of a DualSense a cable is for — its speaker, its headphone
jack, its microphone — and in the first trace of the emulation under a real
title the title's Sony library wrote two output reports through it, an
ordinary one and one asking for nothing but the pad's audio path, and 22
milliseconds after the second the pad's Bluetooth link was gone. The emulation
now refuses those requests on the pad's behalf: the flag bits that enable the
audio settings are cleared and the bytes they govern zeroed, and every other
byte — rumble, trigger effects, lightbar, player LEDs — goes to the pad
exactly as the client wrote it. That the request is what killed the link is
not established: the same pad, presented truthfully over Bluetooth, was
sent the same request twice in a two-hour session and the link stayed up. What
is measured is the order of events, and what the change rests on is that the
request asks for hardware the pad has only on a cable and that nothing under
wine is on the other end of it.
mgvf-0008 is an experiment rather than a fix — the only thing in this project that is, and it is marked as one
wherever it is named. In that same fatal trace, 51 milliseconds before the
audio request, the title's Sony library wrote a feature report to the pad,
and macOS's own log for the moment the link ended says the pad initiated the
parting: "Received disconnection indication ... reason 431". Not a link
failure and not macOS letting go. That trace holds exactly one feature write,
and the two-hour session in which the same pad worked perfectly on Bluetooth
under Steam holds none, and every session the pad left within seconds is one
that library was driving. So the emulation now answers a feature write
as if it had succeeded and sends no byte of it to the pad — answered and not
refused, because that library drops the pad itself when a feature write fails,
which would measure the library rather than the pad. It is what a fresh install
does, on purpose, because an experiment that has to be switched on is one
nobody runs; a per-device registry value puts the write back on the wire
without another build. That the write is what makes the pad leave is not
established — the pad answered a feature read 43 milliseconds after it — and
if the next trace shows the pad leaving at the same point anyway, this comes
back out.
mgvf-0009 is the eighth, and it is neither a fix nor an experiment
but a preference. A DualSense knows two ways to be told to vibrate, and a
game that drives the pad through Unreal's WinDualShock asks for one of them: in
a two-hour Bluetooth session with nothing lied to, the title wrote the pad's own
report itself 6493 times, 335 of those with a motor set, reaching 255 — and all
335 in the same mode, so the game is not asking for a weak effect. Compared
directly on the pad with a six-pulse ladder, the other mode at 255 felt
clearly stronger, and this mode at half strength felt like the game. That is a
perception, on one person's hand, with nothing instrumented, and it is the whole
of the evidence — so the rewrite is off unless a per-device registry value
asks for it, and a second value scales the motor bytes by a percentage for
anyone who wants more or less than the game asked for. It acts on every
route, the pad's own report as well as mgvf-0005's translation, because the
title measured writes that report itself; and over Bluetooth the packet's CRC is
computed again, because a pad ignores a report whose CRC does not cover its
bytes.
mgvf-0016 is that preference on a cable. It was written from a Bluetooth
trace and came out Bluetooth-shaped in two places that have nothing to do with
the preference: the common block starts at byte 3 of a 0x31, and the packet is
signed. A wired pad is written report 0x02 instead, whose common block starts
at byte 1 and which carries no CRC — so the two values were written for it, read,
and dropped, and plugging the pad in turned the setting off without saying so.
This adds the second envelope; the rewrite underneath is the same one, which is
what keeps mode and gain meaning the same thing on both transports. The fault
is measured — one trace holds both transports under one registry and shows the
values read for the Bluetooth pad and not for the wired one — and the benefit
is not: of 10,684 captured wired writes, not one asks the motors for anything,
so on everything seen so far this is a no-op waiting for a title that asks.
And then the other half of the set, which is a different problem. Everything
above is about a game that drives the pad as a DualSense. Most Windows games do
not: they ask XInput for "controller 1" and expect an Xbox pad, and XInput had no
motors to offer, because a DualSense does not keep its motors where an Xbox pad
does. mgvf-0010 gives the pad a haptics collection beside its own — the pad
keeps every byte of its descriptor and the motors arrive on a small device of
their own — mgvf-0011 offers that device to XInput, and mgvf-0012 teaches
xinput that a gamepad's axes and buttons come in two conventions rather than one.
So a game that has never heard of a DualSense rumbles one. It is off unless a
per-title registry value asks for it.
It costs nothing in frames, and that took the most work to be able to write.
The pad's Bluetooth output pipe drains about sixty-five reports a second, and a
title rumbling through XInput already spends most of those on packets of its own.
A second writer every frame offers more than the radio drains and everybody pays,
the game included — the first attempt cost three to five frames a second with
visible stutter. The answer was not to write less often but not to write at all:
since mgvf-0020 a motor change is stamped into a packet the game is already
sending, measured at 92–95% of every change carried that way with nothing of
ours reaching the link.
The rest of the set is what came out of using it, and
source-patches/README.md has each one with its measurements: which of the pad's
two vibration paths a packet asks for and what each feels like, a deadband that
compares the movement at the pad rather than the one the game asked for, the axis
order chosen by reading the descriptor instead of the vendor badge, and the
motors device published to xinput and to nothing else — which is what let a title
driving the pad through Sony's own library start with the switch on.
This half of the set is also published on its own, as a small application that installs it into one CrossOver and does nothing else:
https://github.com/MathiasKowoll/MacGamePadFix — built from
app-padfix/in this repository.
The set as a whole is an improvement rather than a fix: no row of the table
needs it and the Motor column does not change. It is installed and removed with
runtime/install-engine-controller.sh <app> install | --status | --restore,
which keeps CodeWeavers' ten files as .mgvf-stock, refuses any engine but the
one both stamp fields name, refuses while a bottle is running, and re-signs the
bundle itself. make-engine-copy.sh never installs it. The files ship stripped;
runtime/engine-payload-controller/README.md says what that means and what
check-builds.sh verifies about them.
The patches are in source-patches/, which is new in 5 — the build used to
point at another project's patch directory and could only run on a machine that
had that project installed. mgvf-0001 is ours and lives here in full. 0002,
0003, 0006 and 0008 are winevideo's, carried here unchanged with their
provenance and their licence, and each file opens with a header saying so; take
them from winevideo rather than from us
if you want them, because theirs is where they are maintained.
source-patches/README.md says what each one does and which needs which.
- CrossOver by CodeWeavers, and Wine underneath it.
- GStreamer, whose official macOS build
supplies the decoders and the demuxer CrossOver does not ship. On the staging
route they are borrowed from an installation you already have and nothing is
redistributed; on the copy route they travel with the app, and
runtime/engine-payload/CODEC-LICENCES.mdrecords what each file is, its licence and its sha256. - winevideo by Jfishin. None of this
would exist without it. Its patches are where these faults were first
identified: that Electra will accept NV12 and nothing else, that CrossOver
censors that format on macOS, that Electra decides in software by asking its
own platform handle, and that a D3D9 surface has to be bridged rather than
shared. The idea of importing codecs from the user's own official GStreamer
install rather than shipping any is winevideo's too, and where this project
does carry them — inside an engine copy — they are byte-identical copies of
that project's own build, listed by hash and licence in
runtime/engine-payload/CODEC-LICENCES.md. Some of the engine patches are theirs as well;source-patches/README.mdsays which. How the two projects differ, and what each reaches that the other cannot, is in Findings. - SpiderMan2Fix by macprotips. The Spider-Man 2 fix is theirs. They found that D3DMetal reads and writes typed buffers only up to element 2^28, made the game's wide views raw and rewrote the four shaders that use them, and their code ships here unchanged in what it does. What this project adds is a carrier that needs no DLL override, and a barrier after the volumetric fog copy that removed the sandstorm stage's black blocks. The barrier follows vkd3d-proton's.
- The God of War fix comes from a r/macgaming post, "God Of War 2018 White Dot Fix (through Crossover)". Its author found the EQUAL depth test behind the white specks with a d3d11.dll proxy, and showed that GREATER_EQUAL removes them. This project makes the same change without a DLL override and without copying DXMT's d3d11.dll.
- DXMT and vkd3d-proton, whose source made the root cause legible.
GPL-3.0-or-later. The tooling here exists because Wine, vkd3d and DXMT are free software that can be read and modified — copyleft keeps any derivative of this work equally available.
Unofficial community tooling, provided as-is. It modifies files in your game installation, and by default it makes and patches a copy of CrossOver; everything is backed up and reversible, and on that route the CrossOver you installed is not touched, but back up anything you care about first. With the copy turned off in Set up, a toolkit you ask for goes into the CrossOver you installed instead. Not affiliated with or endorsed by CodeWeavers, Apple, or any of the publishers or developers of the games listed here.
Do not use any of this on a game with anti-cheat. It patches a running process, which is exactly the behaviour anti-cheat exists to stop. Everything here is for single-player titles whose cutscenes do not play.
The copy is the answer to a support question, not a technical one. A CrossOver we have changed is not the CrossOver CodeWeavers shipped, and a complaint about the first should never arrive at their door as a complaint about the second. So by default the app leaves your install byte-identical and works on a copy you asked it to make. It also means the original is always there to reproduce against, which is how "the game update moved every address" was told apart from "our engine broke it".
History: why the project once named a single Preview build. CrossOver
Preview stopped being a supported engine on 2026-08-31. It was dropped rather
than half-supported: only titles patched without winegstreamer could have
worked on it, and the rest were out of the equation. What follows is what was
measured there while it was still the engine everything ran on, and it is kept
because a deleted measurement is not a correction. It is a record, not guidance
about where to run a game.
For a long time crossover-preview-arm64-20260821 was where everything was
measured, and the reason was narrower than it looked. Comparing the two installs
plugin by plugin on this machine, stable CrossOver 26.3 ships 17 GStreamer
plugins and that Preview shipped 19, and the two Preview had to itself were
matroska and osxaudio. Both builds decoded VP9 identically, through
applemedia and VideoToolbox; neither shipped libgstvpx or libgstlibav. So
Preview's advantage was a container one rather than a codec one: only it could
open a WebM. DYNASTY WARRIORS ships 355 .webm cutscenes and could not get as
far as a decoder on a stable CrossOver as CodeWeavers ships it, while Mortal
Shell 2 ships the same codec in .mp4, which isomp4 handles on both.
That gap is closed, and it was a plugin rather than an engine. Staging
libgstmatroska gives stable a demuxer too, and NINJA GAIDEN 4 plays on stock
26.3 with it. What each row still needs is per row, and the CrossOver and Motor
columns say it per row — and there is no single Game Porting Toolkit generation
that serves the whole table.