Documentation
with a clear path.
Start with the install path, choose a route with evidence, or go deep on the runtime. This index is generated from the current MetalSharp source docs.
Start here, then go deeper.
Graphics routes at a glance
Checking a game? Start with its compatibility notes. MetalSharp 1.1.0 has two runtimes from MetalSharp Wine, each with its own rule set, and either DMG can switch to the other. On the x86_64 runtime, D3DMetal comes from Game Porting Toolkit 4.0 beta 3 (3.0 below macOS 27); the FEX runtime runs Mullion, VKD3D-Proton, and WineMetalGL. Internal diagnostic routes stay in the detailed source notes. Open a document below to read one chapter at a time; section counts and read estimates help you choose a starting point.
x86_64 runtime macOS 15+ · Wine 11.17 under Rosetta 2 · download
DirectX → D3DMetal → MetalMullionA DXMT fork: D3D12, D3D11, and D3D10 straight to Metal, for 64-bit and 32-bit games.DirectX → Mullion → MetalVKD3D-Proton + DXVKD3D12 through VKD3D-Proton 5.3, and D3D11 down to D3D8 through DXVK, on MetalSharp's MoltenVK.DirectX → Vulkan → MetalWineMetalGLOpenGL 1.0–4.6 on Metal, in place of Apple's deprecated OpenGL.OpenGL → WineMetalGL → MetalFEX runtime macOS 15+ · native Arm64 Wine 11.20 · download
DirectX → Mullion → MetalVKD3D-Proton + DXVKD3D12 through VKD3D-Proton 5.3, and D3D11 down to D3D8 through DXVK, on MetalSharp's MoltenVK.DirectX → Vulkan → MetalWineMetalGLOpenGL 1.0–4.6 on Metal, in place of Apple's deprecated OpenGL.OpenGL → WineMetalGL → Metal
Using MetalSharp
User Guide
The FEX and x86_64 runtimes, install, play, game settings, Sharp Library, streaming, and logs.
Quick startDownload either DMG (both run on macOS 15+; switch runtimes later from the FEX | x86_64 toggle), open MetalSharp, start Wine Steam, install a game, then choose Play. Each game’s managed setup is called a bottle.
Updated: 2026-10-28
Install
MetalSharp 1.0.0 comes in two builds:
- x86_64 (
MetalSharp-<version>-arm64.dmg, macOS 15+): Wine 11.17 under Rosetta, with the D3DMetal, DXMT, D3D9, VKD3D and Mono/FNA routes. - Arm64 with FEX (
MetalSharp-<version>-arm64-FEX.dmg, macOS 26.4+, early access): MetalSharp Wine, a native Apple Silicon Wine that runs x86 games with FEX, with the Mullion, VKD3D-Proton, DXVK and OpenGL lanes. It needs no Rosetta.
- Download a DMG from Releases and drag MetalSharp into
/Applications, or runbrew install --cask metalsharp/tap/metalsharp. - Open it. If macOS blocks it, go to System Settings → Privacy & Security → Open Anyway.
- Let the setup wizard install the runtime (Wine, graphics libraries, redistributables). Homebrew is not required. The Arm64 FEX build installs MetalSharp Wine from the app itself, with nothing to download.
- Start Wine Steam, sign in, and install a Windows game. On the Arm64 FEX build the first start asks macOS for access to Documents and external drives, where games keep saves and Steam libraries.
The in-app updater asks which build to install when an update comes in both. On the Arm64 FEX build, Settings → Updates also offers the x86_64 build of the same version as a switch. Moving an x86_64 install to the Arm64 FEX build converts its Wine prefixes, keeping a backup, and shows a short welcome once. Moving back restores the backup.
Steam Games
Click Play on the Library page. MetalSharp picks a graphics route automatically; if a game doesn't run, pick another from the launch mode dropdown. The routes are listed in the README, and tested picks are in Supported Games.
Steam-Emu (Goldberg) lets supported games, mostly older ones, run offline without Wine Steam. Turn it on from the game card. MetalSharp backs up the original Steam DLLs as .orig and restores them when you turn it off.
Choose EXE: For Steam and Ubisoft games, open the game's Settings popover in the Library hero. For GOG and Epic games, use the gear on the game card. Pick an .exe inside the game's install folder. The choice is saved for that game.
Other Stores and Programs
The Sharp Library holds Windows apps, launchers, installers, and non-Steam games. It has tabs for Epic, GOG, and Game Jolt. For Ubisoft Connect, see the Ubisoft Connect guide. The Rockstar Games Launcher and Battle.net are covered below.
- Install Windows Program takes an
.exeor.msi. MetalSharp imports it directly, or runs the installer in its own bottle and picks up the installed app. - Rename an app with the pencil next to its title, and change its cover with Change Image (JPEG, PNG, or WebP).
- Tools → Open Folder opens the app's bottle in Finder. Install runtime components through the bottle tools; don't copy them in by hand.
Where Games Go
When you install Ubisoft Connect, Battle.net or the Rockstar Games Launcher, or set up GOG or Epic, MetalSharp asks whether games go on this Mac or on an external drive. Each launcher gets its own folder there:
| Launcher | Folder | In the launcher |
|---|---|---|
| Ubisoft Connect | MetalSharp Ubisoft Games |
drive Y: |
| Battle.net | MetalSharp Battle.net Games |
drive Y: |
| Rockstar Games Launcher | MetalSharp Rockstar Games |
drive R: |
| GOG | MetalSharp GOG Games |
each game in its own folder |
| Epic | MetalSharp Epic Games |
each game in its own folder |
On this Mac the folders are in ~/Games. On an external drive they're at the top of the drive, for example /Volumes/MyDrive/MetalSharp GOG Games. Network shares, disk images, Time Machine disks and read-only drives aren't offered.
- GOG and Epic install straight into their folder.
- Ubisoft Connect, Battle.net and Rockstar install games themselves. In the launcher's install or download settings, choose its drive (
Y:orR:) as the install location. MetalSharp doesn't change the launcher's own settings. If you already mapped that letter to a folder of your own, MetalSharp leaves it alone and uses the first free letter fromX:down instead. - Unplugged drive. If the drive isn't connected, games on it won't start, and GOG and Epic installs ask where games go. Plug it back in and start the launcher again. MetalSharp maps the drive letter each time a launcher starts.
- Changing the location only affects new installs. Games already installed somewhere else stay there and keep working. Nothing is moved.
Rockstar Games Launcher
The Rockstar Games Launcher lives in the Steam prefix (~/.metalsharp/prefix-steam), where Steam also installs it the first time you play a Steam copy of a Rockstar game. Set up Steam in MetalSharp first. MetalSharp doesn't start Steam for the launcher.
- Install. Launch the Rockstar Games Launcher from MetalSharp. If it isn't installed, MetalSharp downloads Rockstar's installer and runs it. Click through the installer and keep the default install location for the launcher itself. MetalSharp starts the launcher when it finishes. To install games into the folder you chose, pick drive
R:in the launcher (see Where Games Go). - Start and stop. The launcher runs on D3DMetal with WFDXCompat on the x86_64 build, and on Mullion on the Arm64 FEX build. Stop ends only the launcher, its helpers (Social Club, Rockstar Service) and the games it installed. Steam and your Steam games keep running.
- Play. Games the launcher installs show up in the library. Steam copies stay in your Steam library. Pick a launch route and press Play. MetalSharp starts the launcher if it isn't running, then the game. On the x86_64 build, a launcher already running on another game's route restarts on this one's, because games inherit the launcher's environment.
Battle.net
Battle.net runs in its own prefix (~/.metalsharp/prefix-battlenet) on the same lane as Ubisoft Connect: D3DMetal on the x86_64 build, Mullion on the Arm64 FEX build.
- Install. Launch Battle.net from MetalSharp. If it isn't installed, MetalSharp sets up the prefix, downloads Blizzard's installer and runs it. To install games into the folder you chose, pick drive
Y:in Battle.net's download settings (see Where Games Go). - Start and stop. Stop shuts down the whole Battle.net prefix, including its games.
- Play. Installed Blizzard and Activision games show up in the library. Pick a launch route and press Play. MetalSharp starts Battle.net if it isn't running and asks it to start the game, so sign in first. On the x86_64 build, a Battle.net already running on another route restarts on the game's route.
Uninstalling a launcher
Uninstalling a launcher stops it and its games first. If something won't stop, nothing is removed. Quit it and try again.
- Ubisoft Connect and Battle.net lose their whole prefix, cache, per-game settings and saved executable choices.
- Rockstar Games Launcher loses only its own folders in the Steam prefix (the launcher, Social Club and its ProgramData folder), its cache, per-game settings and the
R:drive MetalSharp mapped. Steam, your Steam games and Rockstar games installed through Steam are not touched. The launcher's registry entries in the Steam prefix stay; they're harmless. - GOG loses its prefix and saved executable choices. You stay signed in.
- Epic loses its game bottles, Legendary and its sign-in, so the page starts over at setup.
Your games folder (see Where Games Go) is kept unless you choose to delete it, and a reinstall uses it again. MetalSharp deletes it only if it's still the launcher's own MetalSharp <Name> Games folder in ~/Games or at the top of a connected drive, and not a link. Games installed anywhere else are never deleted.
Game Settings
The gear on the Library hero opens the selected game's settings. On both builds: MetalFX upscaling (off by default), msync and Steam-Emu. On the x86_64 build: controller input and x87 acceleration. On the Arm64 FEX build: the Metal HUD and a frame rate limit (30, 60 or off) for the Mullion and VKD3D-Proton lanes. Settings on the Arm64 FEX build also has a ray tracing switch for the VKD3D-Proton lane.
Controller Input (x86_64 build)
The Controller input setting sets which input shims MetalSharp deploys to the game folder and Steam prefix. The Arm64 FEX build has none: MetalSharp Wine's own XInput and DirectInput see controllers through SDL.
- Off (default): none.
- X: XInput (
xinput1_1–xinput1_4.dll,xinput9_1_0.dll). - D: DirectInput (
dinput.dll,dinput8.dll).
Switching modes removes the previous set. If the game shipped its own copy of a shim, MetalSharp backs it up and restores it.
Game Streaming
MetalSharp can stream games to a phone or tablet using Sunshine on the Mac and Moonlight on the device.
- Click Stream in the footer, then Install Sunshine, then Start Streaming Host.
- Install Moonlight (iOS, Android) and put the device on the same Wi-Fi network as the Mac.
- In Moonlight, tap your Mac. Enter the PIN it shows into MetalSharp and click Pair Device.
Sunshine on macOS is experimental, and gamepads don't work yet, so use Moonlight's touch controls. Approve Screen Recording for Sunshine on first use, or the stream will be black. Sunshine uses ports 47984–48010 (TCP and UDP). If you change the login in Sunshine's web UI (https://localhost:47990), delete ~/.metalsharp/streaming-creds.json and restart streaming. PINs work once; if pairing fails, tap the Mac again in Moonlight to get a new one.
Logs, Settings, and Uninstall
- Logs has live logs, crash reports, and recent log files.
- Settings covers Steam API sync, backend restart, cache cleanup, runtime maintenance, and updates. Updates download, verify, then ask before closing MetalSharp and stopping Steam/Wine. On the Arm64 FEX build, the cache rows are per lane (Mullion, VKD3D-Proton, DXVK). Mullion's Clear asks before also clearing the Metal cache that every Mullion game shares.
- Bugs: the Arm64 FEX build is early access. Report problems on GitHub with the game's log and crash report from Logs.
- Settings → Danger Zone → Uninstall MetalSharp removes all prefixes, bottles, Steam, the runtime, caches, and settings, then moves the app to the Trash.
Using MetalSharp
Ubisoft Connect
Install Ubisoft Connect and play its games.
Updated: 2026-10-28
-
Install. Open the Start Steam dropdown in the library header and choose to install Ubisoft Connect. MetalSharp creates a Ubisoft prefix and runs the installer. Keep the default install location so MetalSharp can find it.
-
Start and stop. Once installed, the dropdown shows Launch Ubisoft. Use the same dropdown to stop it. Connect runs on D3DMetal on the x86_64 build and on Mullion on the Arm64 FEX build.
- Install games. Sign in and install a game. To install into the folder you chose for Ubisoft games, click Change Location and choose drive
Y:(see Where Games Go). Without a chosen folder, the external drive with the most free space shows up asY:.
- Play. Installed games appear in the main library. Pick a launch route and press Play. Ubisoft Connect starts on its own if it isn't already running. Stop a game with Stop or ⌘⌥Q.
Using MetalSharp
Supported Games
Tested titles and the route each one uses.
Updated: 2026-10-28
Games confirmed playable by users or the developer, grouped by launch route. Many more likely work. Tested on an M4 MacBook Air (16 GB) from an external USB-C SSD. Routes are described in the README.
The tables are the x86_64 build's routes. The Arm64 FEX build (early access) runs the same games on its own lanes. D3DMetal titles move to VKD3D-Proton, DXMT titles to Mullion, and D3D9 titles to DXVK, as its rule set configs/mtsp-rules-fex.toml sets out.
Arm64 FEX Build — verified on MetalSharp Wine
- Dragon's Dogma 2 (2054970): Mullion
- Red Dead Redemption 2 (1174180): Mullion (runs best there)
- God of War Ragnarök (2322010): Mullion and VKD3D-Proton
- PEAK (3527290): VKD3D-Proton and Mullion
Sharp Library Sources
| Source | Tested game | Route |
|---|---|---|
| GameJolt | The Joy of Creation: Reborn | DXMT |
| GOG | Fall of Porcupine: Prologue | DXMT |
D3DMetal - D3D12, D3D11, D3D10 through Apple's D3DMetal Framework
| Game | AppID | Notes |
|---|---|---|
| Elden Ring | 1245620 | Offline Play |
| ARMORED CORE VI FIRES OF RUBICON | 1888160 | Offline Play |
| High On Life | 1583230 | Online Play |
| Cyberpunk 2077 | 1091500 | Online Play |
| Ghostrunner | 1139900 | Online Play |
| Star Wars Jedi: Fallen Order | 1172830 | Online Play |
| Control: Ultimate Edition | 870780 | Online Play |
| BeamNG.drive | 284160 | Online Play |
| MECCHA CHAMELEON | 4704690 | Online Play |
| Sons Of The Forest | 1326470 | Online Play |
| Subnautica 2 | 1962700 | Online Play |
| Overwatch 2 | 2357570 | Online Play |
| Sekiro: Shadows Die Twice | 814380 | Online Play |
| Sonic Frontiers | 1237320 | Online Play |
| Black Myth: Wukong | 2358720 | Online Play |
| Borderlands 3 | 397540 | Online Play |
| The Witcher 3: Wild Hunt | 292030 | Online Play (Remastered not running yet, must choose classic option in steam options before downloading and playing) |
| Yu-Gi-Oh! Master Duel | 1449850 | Online Play |
| Palworld | 1623730 | Online Play |
| Hogwarts Legacy | 990080 | Online Play |
| HELLDIVERS 2 | 553850 | Online Play, Medium Settings |
| Assassin's Creed Odyssey | 812140 | |
| Eve Online | 8500 | Launches through steam with Specific Flags that enable the launcher to load |
| Far Cry 5 | Ubisoft | |
| Far Cry 6 | Ubisoft | |
| TrackMania | Ubisoft | |
| Marvel Rivals | 2767030 | Launches through Steam with -Windowed to avoid a mouse bug |
| Baldur's Gate 3 | 1086940 | |
| Watch Dogs Legions | Ubisoft | |
| Red Dead Redemption 2 | 1174180 | Also runs on VKD3D (Vulkan). The first launch goes through Steam, which installs the Rockstar Games Launcher |
| Grand Theft Auto V Enhanced | 3240220 | Story mode. Also runs on VKD3D |
| Deadlock | 1422450 | Online Play |
VKD3D - D3D12, D3D11, D3D10, D3D9 -> Vulkan -> Metal
| Game | AppID | Notes |
|---|---|---|
| PEAK | 3527290 | Working |
| Hollow Knight: Silksong | 1030300 | |
| Schedule I | 3164500 | |
| Dark Deception | 332950 | |
| Portal2 | 620 | Steam-Emu Required |
| Mirror's Edge | 17410 | Sync-loading mitigation active. |
| Half-Life 2 | 220 | |
| Among Us | 945360 | Steam online play. |
| Dwarf Fortress | 975370 | |
| Caves Of Qud | 333640 | |
| Skyrim: Special Edition | 489830 |
DXMT — D3D11, D3D10 to Metal
| Game | AppID | Notes |
|---|---|---|
| Dark Souls 3 | 374320 | |
| Repo | 3241660 | |
| Cult of the Lamb | 1313140 | |
| The Wilds | 1028590 | |
| The Long Dark | 305620 | Ultra settings verified. |
| Subnautica | 264710 | |
| Subnautica: Below Zero | 848450 | |
| Rain World | 312520 | |
| Hollow Knight | 367520 | |
| Party Animals | 1260320 | Save bottle, launch direct with Steam. |
| Dave the Diver | 1868140 | |
| Totally Accurate Battle Simulator | 508440 | |
| Skul: The Hero Slayer | 1147560 | |
| Crab Game | 1782210 | |
| SkyIsland | 2302640 | |
| Lethal Company | 1966720 | |
| Insurgency | 222880 | Launch with -steam -secure flags. |
| Graveyard Keeper | 599140 | |
| Brawlhalla | 291550 | |
| PlateUp! | 1599600 | |
| Nine Sols | 1809540 | |
| Besiege | 346010 | |
| AmongUs | 945360 | |
| Team Fortress 2 | 440 | |
| Amid Evil | 673130 | |
| Octopath Traveler II | 1971650 | |
| Mind Scanners | 1389550 | |
| Dredge | 1562430 | |
| Blasphemous 2 | 2114740 | |
| Dead Cells | 588650 | Runs under WineMetalGL |
DXMT (32-bit) — D3D11, D3D10 to Metal, 32-bit prefix route
| Game | AppID | Notes |
|---|---|---|
| Hades | 1145360 | |
| The Binding Of Isaac: Rebirth | 250900 | |
| Ori and the Blind Forest: Definitive Edition | 387290 | |
| Nidhogg 2 | 535520 | |
| Balatro | 2379780 | |
| Mosa Lina | 2477090 | Runs under WineMetalGL |
D3D9 - Wine i386 D3D9 W/ x87 Acceleration
| Game | AppID | Notes |
|---|---|---|
| Portal 2 | 620 | Steam-Emu Required |
| Half Life 2 | 220 | |
| Undertale | 391540 |
Mono/FNA — XNA/FNA/MonoGame
| Game | AppID | Notes |
|---|---|---|
| Celeste | 504230 | FNA/XNA assets, FMOD shims, Steamworks shim. x86_64 Mono. Install wizard fallback paths for steam_api detection. |
| Terraria | 105600 | TerrariaLauncher/patcher support, x86_64 Mono, XNA/FNA assemblies. |
Using MetalSharp
Fix a Broken Update
Recover when an in-app update fails.
Fix a Broken Update
If your updater fails, you can fix it by doing the following:
1) Download update.sh
2) Close Metalsharp
3) Open the Terminal, and run /bin/bash ~/Downloads/update.sh --recover
The script reinstalls the build you have: the x86_64 build (MetalSharp-<version>-arm64.dmg) or the Arm64 FEX build
(MetalSharp-<version>-arm64-FEX.dmg, which carries MetalSharp Wine). It reuses a valid update DMG already downloaded by
MetalSharp or downloads the latest stable official release, verifies the DMG and Developer ID signature, and installs it
using the fixed updater. The x86_64 build needs a newer release than the one installed; the Arm64 FEX build can also
reinstall its own version. It then relaunches MetalSharp and
runs the normal migration handoff to restore the installation/runtime. The script asks before stopping MetalSharp and
Steam/Wine processes and may request administrator approval. Once recovered, use MetalSharp's in-app updater for later releases.
Working on MetalSharp
Architecture
The two runtimes, graphics routes, rule sets, Wine runtimes, and bottles.
Updated: 2026-10-28
How MetalSharp launches games, what it installs, and where state lives. Build instructions are in development.md and building-wine.md.
MetalSharp 1.0.0 comes in two builds from the same source. The backend picks its build at compile time (make VARIANT=fex defines MS_BACKEND_VARIANT), and the app follows the backend's variant in GET /status:
- Regular (
MetalSharp-<version>-arm64.dmg): the x86_64 Wine 11.17 runtime under Rosetta. Most of this document describes it. - Arm64 FEX (
MetalSharp-<version>-arm64-FEX.dmg): MetalSharp Wine, a native arm64 Wine 11.20 that runs x86 code with FEX. See Arm64 FEX Build.
Launch Flow
The C backend (~/.metalsharp/run/backend.sock) owns every launch. Route selection is data-driven: adding a game means editing its rule set, not backend code.
Rule sets
Each build has its own rule set, with a [rule_set] table naming the build, version and schema:
| Build | File | Pipelines |
|---|---|---|
| Regular (x86_64 Wine under Rosetta) | configs/mtsp-rules.toml |
dxmt, dxmt_32, d3d9, vkd3d, d3dmetal, wine_bare, fna_arm64, fna_x86 |
| Arm64 FEX (MetalSharp Wine) | configs/mtsp-rules-fex.toml |
dxmt/dxmt_32 (Mullion), vkd3d (VKD3D-Proton), dxvk, wine_bare (OpenGL), fna_x86 |
The regular backend compiles its rules into app/src-c/runtime/mtsp_catalog.c. The FEX backend reads mtsp-rules-fex.toml from ~/.metalsharp/configs, which setup stages from the app's Resources/configs, and applies its lanes over the compiled catalog. Rules the file doesn't name use the regular rule's lane (ms_mtsp_fex_lane). Don't edit the FEX file by hand: tools/rules/generate-fex-rules.py generates it from the regular rules with that same translation. D3DMetal and GPTK titles go to VKD3D-Proton, the D3D9 route to DXVK, and arm64 FNA titles to Mullion. Then the per-game lanes verified on MetalSharp Wine (FEX_VERIFIED in the generator) apply. CI runs the generator with --check and validates both files with tools/ci/validate-rules-toml.py.
Play clicked
-> app calls the backend (e.g. POST /steam/launch-game {"appid":<appid>,"launchMethod":"<pipeline>"})
-> backend resolves the route: TOML rule -> managed/FNA eligibility -> PE imports -> directory heuristics -> fallback
-> backend syncs and preflights the bottle, builds a LaunchRecipe
-> regular build: backend copies route DLLs beside the game executable and sets Wine/DYLD/cache env
Arm64 FEX build: backend writes the route to <game>/.metalsharp/launch.env and stages no DLLs
-> Wine (or native Mono) starts the game; Wine Steam stays alive in the background for Steamworks
Pipeline endpoints: GET /mtsp/pipelines, POST /mtsp/prepare.
Graphics Routes
The regular build's routes (the Arm64 FEX build's lanes are under Arm64 FEX Build):
| Route | APIs | Translation chain | DLLs staged beside the game |
|---|---|---|---|
| D3DMetal | D3D12 / 11 / 10 | DirectX -> D3DMetal -> Metal | Managed GPTK payload DLLs |
| DXMT | D3D11 / 10 (D3D12 lane) | DirectX -> DXMT -> winemetal -> Metal | d3d11.dll, dxgi.dll, d3d10core.dll, winemetal.dll; D3D12 adds d3d12.dll from lib/dxmt_m12 |
| DXMT (32) | 32-bit D3D11 / 10 | DirectX -> DXMT i386 -> Metal | i386 DXMT payload |
| D3D9 | D3D9 | Wine i386 D3D9 with x87 acceleration (DXMT launch/cache family) | lib/dxmt |
| VKD3D | D3D12 / 11 / 10 / 9 | DirectX -> vkd3d-proton / DXVK -> MoltenVK -> Metal | d3d12.dll, d3d12core.dll (vkd3d-proton); d3d11.dll, d3d10core.dll, d3d9.dll, dxgi.dll (DXVK); MoltenVK.dylib, MoltenVK_icd.json |
| Mono/FNA | XNA / FNA / MonoGame | Managed code -> native Mono + FNA -> Metal (no Wine) | See Mono/FNA below |
Internal route and lane IDs used in code, configs, and cache paths:
| ID | Meaning | Payload | Shader cache |
|---|---|---|---|
m12 |
D3D12 via DXMT | lib/dxmt_m12 |
shader-cache/m12/<appid>/ |
m11 |
D3D11 via DXMT | lib/dxmt |
shader-cache/m11/<appid>/ |
m10 |
D3D10 via DXMT | lib/dxmt |
shader-cache/m10/<appid>/ |
m9 |
D3D9 (auto-selected) | lib/dxmt |
shader-cache/m9/<appid>/ |
DXMT |
Auto-router selecting D3D12/11/10/9 from rules and PE evidence | ||
M32 |
32-bit Wine fallback for diagnostics/legacy records | ||
Steam, MacOS Steam |
Windows / native Steam client handoff for bootstrap and diagnostics | ||
WineBare |
Plain Wine for installers and custom apps |
Route switching: graphics cleanup removes only files that byte-match a managed payload (including D3DMetal's nvngx-on-metalfx.dll); modified or game-provided files stay. If a payload is incomplete, repair the runtime install.
D3DMetal uses ~/.metalsharp/runtime/d3dmetal-gptk4-beta2/ with the shared Steam prefix. Saving the route stages its DLLs; Play refreshes them, reconciles controller shims, and launches with SteamAppId/SteamGameId. D3DMETAL_FRAMEWORK_PATH names the framework executable; keep PE DLLs, Unix files, and framework from the same payload. Wine Steam started with Launch Steam inherits the D3DMetal environment; a running client is reused, not restarted.
Shader presets: shader-presets/dxmt-metal/ (M11) and shader-presets/dxmt-metal12/ (M12) hold pre-compiled caches named <appid>.db (SQLite cache_18 table of metallib blobs, air64-apple-macosx15.0.0, MSL 3.2). On launch deploy_preset_cache() copies a preset if no user cache exists, otherwise merges with INSERT OR IGNORE. To add one, copy ~/.metalsharp/shader-cache/<engine>/<appid>/shaders_320.db into the matching folder as <appid>.db.
Wine Runtime
Wine 11.17, x86_64 Unix host (Rosetta) with i386 + x86_64 PE WoW64. Check with ~/.metalsharp/runtime/wine/bin/wine --version; the version string alone doesn't identify the patched build, so see building-wine.md.
- MSYNC:
WINEMSYNCfollows themsyncsetting; the wineserver keeps its choice for its lifetime, so shut down managed Wine before changing it. - GSBASE: one canonical
ntdll.so. DXMT enables the TEB/TSD swap; D3DMetal and bare Wine keep legacy behavior unlessWINE_MACOS_GSBASE_SWAP=on. - Retina: on by default for the shared prefix (
RetinaMode, 192 DPI; off restores 96). Restart Wine Steam after changing it. metalsharp-winewrapper preserves the backend's route-specificWINEDLLPATH,DYLD_FALLBACK_LIBRARY_PATH, and Vulkan ICD settings.
WineMetalGL (OpenGL) comes from WineMetalGL 3.7.0 (winemetalgl-3.7.0-x86_64.tar.gz, in the runtime bundle since 0.80.0): its core, libwinemetalgl.dylib (x86_64), sits next to winemac.so, which loads it. The runtime's winemac.so, win32u.so and opengl32 (.so, and .dll for x86_64 and i386) are still WineMetalGL 2.2.0's patched Wine modules, built from MetalSharp's Wine; the 3.x core keeps the driver interface they load it through. The runtime also carries the 3.x Wine module (winemetalgl.so, and winemetalgl.dll for x86_64 and i386), which a prefix uses only with the module's forwarding opengl32.dll and opengl32=native; MetalSharp's prefixes do not install it. It gives programs OpenGL 4.6 core and compatibility contexts on Metal; WINEMETALGL=0 selects Apple's OpenGL. DOOM (2016) runs its OpenGL renderer on it. Executables listed in later_gl_entry_point_apps (steam_actions.c) get LaterEntryPoints in the Steam prefix's registry, for OpenGL loaders that require entry points of later GL versions. The old sidecar metalsharp-opengl.dylib is no longer in the runtime, and setup no longer requires it; nothing loaded it. Artifact hashes are in tools/bundles/wine-runtime-hashes.tsv.
Launch Environment
| Variable | Purpose |
|---|---|
WINEPREFIX |
Prefix location |
WINEDLLPATH, WINEDLLOVERRIDES |
PE DLL lookup and native/builtin overrides |
DYLD_FALLBACK_LIBRARY_PATH |
Unix library lookup for Wine and DXMT |
ROSETTA_ADVERTISE_AVX |
Rosetta reports translated AVX/AVX2 (no emulation added) |
DXMT_SHADER_CACHE_PATH, DXMT_CONFIG_FILE |
DXMT cache and config |
D3DMETAL_RUNTIME_DIR, D3DMETAL_FRAMEWORK_PATH |
D3DMetal payload root and framework executable |
SteamAppId, SteamGameId |
Steam identity for direct launches |
WINEMSYNC |
MSYNC selection |
Arm64 FEX Build
MetalSharp Wine (metalsharp/MetalSharp-Wine, v4.0 for MetalSharp 1.0.0) is Wine 11.20 built as a native arm64 macOS app. Its FEX fork runs x86_64 (ARM64EC) and i386 (WoW64) Windows code, so the build needs no Rosetta. The FEX DMG carries the signed and notarized MetalSharp-Wine.app unchanged in Resources/. Setup copies it with ditto to ~/.metalsharp/runtime/MetalSharp-Wine.app and makes it read-only: re-signing would drop its cross-architecture entitlement. ~/.metalsharp/runtime/wine is a facade over the app (wine_runtime.c). Its bin/ scripts run the app's wine, lib and share link to the app's, and etc/ holds MetalSharp's own mullion.conf, so code that runs runtime/wine/bin/metalsharp-wine runs MetalSharp Wine.
| Lane | APIs | Chain |
|---|---|---|
Mullion (dxmt, dxmt_32) |
D3D12 / 11 / 10 for x86_64; D3D11 / 10 for i386 | DirectX -> Mullion (a DXMT fork) -> Metal; the default lane |
VKD3D-Proton (vkd3d) |
D3D12 | DirectX -> VKD3D-Proton -> MoltenVK -> Metal; the regular build's D3DMetal and GPTK titles run here |
DXVK (dxvk) |
D3D11 / 10 / 9, x86_64 | DirectX -> DXVK -> MoltenVK -> Metal; the regular build's D3D9 route runs here |
OpenGL (wine_bare) |
OpenGL, Wine's Direct3D | Wine's wined3d over WineMetalGL; games with an OpenGL renderer are asked to use it |
- Routing: the backend writes each game's route to
<game>/.metalsharp/launch.env(and the executable's folder). MetalSharp Wine applies it however the game starts: from Steam (steam://run, so cloud saves sync), a launcher, or directly.launch-exeandlaunch-argsbeside it pick the executable and its arguments. MetalSharp Wine's loader routes D3D and DXGI DLLs to the lane's copies inside the app by name, so no DLLs are staged in game folders. - Rules:
configs/mtsp-rules-fex.toml(see Rule sets). - Caches: each game's caches go in
shader-cache/fex-<lane>/<appid>/andpipeline-cache/fex-<lane>/<appid>/(MULLION_SHADER_CACHE_PATH,VKD3D_SHADER_CACHE_PATH,DXVK_STATE_CACHE_PATH). Settings shows and clears them per lane. Metal's pipeline cache for Mullion games is shared by every Mullion game and lives in the user's cache folder ($(getconf DARWIN_USER_CACHE_DIR)mullion/<exe>/com.apple.metal), so Settings clears it only after asking. - Settings: per-lane frame rate limits (
/framerate, Mullion's inmullion.conf), the Metal HUD, and ray tracing on the VKD3D-Proton lane. There are no x87 or controller-input settings: FEX runs x87 code, and MetalSharp Wine's own XInput and DirectInput see controllers through SDL. - Prefixes: setup prepares each prefix once (
wineboot -u, VC++ 2022 x64 and x86, core fonts). Moving an x86_64 install to the FEX build backs up each prefix'ssystem.reg,user.reg,userdef.reganddrive_c/windowsto.metalsharp-x86_64-backup, then converts it. Moving back to the regular build restores that backup.
Updates Between Builds
migration.c picks a path from the running build, the build recorded in setup.json (runtime_variant; missing means the regular build) and its version (ms_migration_path_for):
| From -> to | Path |
|---|---|
| Regular -> regular | The regular build's migration |
| Regular (0.80.x or later) -> FEX | Full migration that converts the Steam, GOG, Ubisoft and bottle prefixes, then the one-time welcome; also when the FEX DMG is installed by hand |
| FEX -> FEX (1.0.0 -> later) | A light update: the new MetalSharp Wine and runtime pieces, user data in place, no prefix conversion |
| FEX -> regular | The regular migration after restoring the x86_64 backups; the read-only app is removed with the old runtime |
The updater (app/updater/update.sh) tells the builds apart by the DMG name (FEX in it) and the app's MetalSharpVariant. It installs a newer version, or the other build at the same version. --recover reinstalls the installed build, including the FEX build at its own version. While a migration is pending or running, the backend's startup MetalSharp Wine refresh does nothing, and an install lock (runtime/.metalsharp-wine.lock) keeps the two from replacing the app at once.
Filesystem Layout
~/.metalsharp/
├── runtime/
│ ├── wine/
│ │ ├── bin/ metalsharp-wine, wine, wineserver
│ │ ├── lib/wine/ x86_64-unix, x86_64-windows, i386-windows
│ │ ├── lib/dxmt/ DXMT baseline (x86_64-unix, x86_64-windows)
│ │ ├── lib/dxmt_m12/ isolated D3D12/DXGI/winemetal
│ │ ├── lib/moltenvk-vkmt/
│ │ └── etc/ dxmt.conf, vulkan/icd.d/MoltenVK_icd.json
│ ├── d3dmetal-gptk4-beta2/ wine/x86_64-windows, external/D3DMetal.framework
│ ├── mono-arm64/, mono-x86/ native Mono lanes
│ ├── host/ host runtime ABI dylib + manifest
│ └── redist/ redistributable sources
├── prefix-steam/ shared Wine Steam prefix (external libraries mount by drive letter)
├── prefix-ubisoft/ Ubisoft Connect prefix
├── bottles/
│ ├── steam_<appid>/bottle.json readiness record pointing at prefix-steam
│ ├── epic_<appName>/prefix per-title Epic prefix + manifest
│ ├── gog-prefix/prefix shared GOG prefix
│ └── <id>/prefix Sharp Library installer/app bottles
├── compatdata/<appid>/ metalsharp-compatdata.json, logs/launch-<ts>.log, assets/
├── epic/ legendary/ state, library.json, artwork/, processes/
├── tools/legendary/legendary-0.21.0
├── sharp-library/ games/ launcher-games/epic/location.txt
├── shader-cache/<lane>/<appid>/
├── cache/ bundles/, ubisoft-connect/artwork/, thegamesdb_config.json
└── logs/
Runtime Bundles
Assets come from the bundles release, into app/bundles/ when packaging and ~/.metalsharp/cache/bundles/ for installer fallback. Hashes are tracked in tools/bundles/asset-manifest.tsv.
| Asset | Contents |
|---|---|
metalsharp-graphics-dll.tar.zst |
Graphics/dll/dxmt/ (DXMT v0.80 baseline, the only source for DXMT payloads), dxvk/, vkd3d-proton/, d3dmetal/, wfdxcompat/ (the WFDXCompat launcher companion, staged to ~/.metalsharp/runtime/wfdxcompat/), and wfdxcompat-agility/ (WFDXCompat with its D3D12 frontend for Agility SDK titles, staged to ~/.metalsharp/runtime/wfdxcompat-agility/) |
metalsharp-runtime.tar.zst |
Patched Wine 11.17, host ABI, managed payloads including D3DMetal |
metalsharp-assets.tar.zst |
Mono, Goldberg, EAC toggle, shims |
metalsharp-scripts-tools.tar.zst |
Updater scripts, configs, native tools, CEF helpers |
metalsharp-steam.tar.zst |
Steam installer and CEF wrapper |
Both DXMT lanes record state in metalsharp-dxmt-runtime.json (schema metalsharp.dxmt-runtime.v2, baseline 0.65.0-dxmt-v0.80-baseline-v1). Diagnose drift with hashes, not version strings:
tools/bundles/verify-bundles.sh --require mac
tools/bundles/verify-bundles.sh --release
tools/bundles/verify-developer-sdk.sh dist/developer-sdk/metalsharp-d3d12-developer-sdk.tar.zst
Steam
Wine Steam owns account, downloads, and sessions; MetalSharp owns the game process, bottle, compatdata, logs, and runtime assets. macOS Steam has no Proton-style compatibilitytools.d, so MetalSharp launches the game itself.
- Launch:
POST /steam/launch->steam::launch_wine_steam(); games go throughPOST /steam/launch-game->prepare_steam_pipeline_env(). - Steam API staging:
steam_api64.dll,steam_api.dll,steamclient64.dll,steamclient.dll,GameOverlayRenderer64.dll,GameOverlayRenderer.dllbeside the executable when available. - CEF wrapper:
ensure_steam_launch_ready()redeployssteamwebhelper.exe(original kept assteamwebhelper_real.exe, marker.ms_wrapper_deployed) inprefix-steam/.../Steam/bin/cef/cef.win64/whenever Steam overwrites it. If login stops rendering, verify the wrapper hash before changing launch args. - Native macOS games: installs containing a
.appare labeled Native macOS, skip the bottle/route selector, and launch viaopen.
Compatdata is the per-game launch ledger; the Steam game bottle is the repair surface. POST /steam/compatdata {"appid":620,"pipeline":"vkd3d"} ensures the bottle, refreshes assets, and returns the record. Fields: appid, name, bottle_id, prefix_path, steam_prefix_path, install path, runtime profile, launch_pipeline, steam_identity_mode, compat_tool_name, launch_command_template, log dir, detected assets/components, and last launch log/pid/status.
Launchers and Stores
The installer classifier detects known launchers before generic .NET/WebView/MSI/PE heuristics, records known_launcher:<id> and launcher_name:<name> hints, and installs them through bare Wine so launchers never inherit game routes. Child games get their own bottle and route.
| Launcher | Profile |
|---|---|
| Minecraft Launcher | Java Launcher |
| EA App / Origin, Ubisoft Connect, Epic Games Launcher, Rockstar / Social Club | WebView (Gecko, WebView2, .NET 4.8, VC runtime, core fonts) |
| GOG Galaxy | Launcher |
CEF launchers (libcef.dll, chrome_*.pak, vk_swiftshader.dll, app.asar): the original becomes <name>_real.exe, <name>.exe is a wrapper relaunching with --in-process-gpu --disable-gpu, and metalsharp-cefchildhook.dll covers child processes. Evidence: POST /launcher/evidence {"family":"ea"|"ubisoft"}.
| Store | How it works | Backend routes |
|---|---|---|
| Ubisoft Connect | Installer from https://ubi.li/4vxt9 into prefix-ubisoft with D3DMetal + Steam's CEF args; the Ubisoft library location (library_locations.c) maps to Y:, or without one the writable external volume with the most free space. Games are found from the prefix registry; artwork falls back Ubisoft -> SteamGridDB -> extracted icon. Per-game routes; Mono/FNA and Steam Emu hidden. |
GET /ubisoft/status, /ubisoft/library; POST /ubisoft/launch, /stop, /launch-game, /save-pipeline |
| Epic | Legendary 0.21.0 (arm64, size/hash-verified) out of process. Each title needs Initialize Bottle; mouse mode No Recenter (default) or Mouse Auto. Optional TheGamesDB key supplies missing artwork. | GET /sharp-library/epic/{status,games,running,thegamesdb-api-key}; POST install-tool, auth, thegamesdb-api-key, logout, sync, install, progress, cancel, initialize, play, stop, stop-all, uninstall |
| GOG | Shared bottles/gog-prefix; Stop runs wineserver -k on it, so it stops every GOG game. |
|
| Game Jolt | Isolated process groups; routes D3DMetal, VKD3D, DXMT, DXMT (32), D3D9. |
Sharp Library apps run in isolated process groups tracked by app ID; Cmd+Opt+Q sends stop-all for every store. Runtime migration preserves the Ubisoft, Epic, and GOG prefixes and data, then runs wineboot -u and verifies the dosdevices/c: and z: mappings (failure is an explicit error).
Redistributables
Bottle components: vcrun2019, directx_jun2010, dotnet48, webview2, openal, xna, physx, wine-mono, gecko, corefonts. Graphics components (d3d9, d3d10, d3d11, d3d12, dxgi) verify from the runtime.
Steam bottles infer needs from _CommonRedist, CommonRedist, and installscript.vdf (e.g. DXSETUP.exe, xnafx40_redist.msi, oalinst.exe). Repair sources, in order: Steamworks Shared _CommonRedist in prefix-steam, bottles/*/installers/, runtime/redist/. MSIs run via msiexec /i, EXEs with known silent args; each repair writes a per-bottle log.
Mono/FNA
| Lane | Runtime | Version | Used for |
|---|---|---|---|
| Wine Mono | runtime/wine/share/mono/wine-mono-11.3.0 |
11.3.0 | CLR/bootstrapper apps inside Sharp Library bottles |
| Native Mono ARM64 | runtime/mono-arm64/bin/mono |
6.14.1 | Terraria-style FNA games |
| Native Mono x86_64 | runtime/mono-x86/bin/mono |
6.12.0 | Celeste-style legacy FNA games (Rosetta) |
The route stages FNA/XNA assemblies, native FNA3D/FAudio/SDL/Steam libraries, FMOD stubs, steam_appid.txt, Steamworks shims, and dllmap shims (libkernel32.dylib, libuser32.dylib, libCarbon.dylib, Carbon interpose, xaudio2_9.dylib, xinput1_4.dylib). Component names fna_arm64, fna_x86, mono-arm64, mono-x86 stay internal.
Shim sources live in src/fna/shims/:
csteamworks_shim.cbridges Steamworks.NET v10CSteamworks.dllcalls to modernlibsteam_api.dylib(RestartAppIfNecessary-> false,InitwrapsSteamAPI_InitFlat). Build:./build_csteamworks.sh.fmod_stub.c,fmodstudio_stub.care silent FMOD Studio 1.10 stubs (no arm64 build exists). Build:./build_fmod_stubs.sh.
Host Runtime ABI
include/metalsharp/HostRuntimeABI.h is the boundary between Windows-facing shims, Wine unixlibs, and macOS services (process/path identity, logging, Steam bridge, managed runtime config, capability reporting). Every struct starts with struct_size; callers reject a different major version and tolerate larger structs.
METALSHARP_HOST_ABI_VERSION_MAJOR / _MINOR
MetalSharpHostRuntimePaths bottle_id, bottle_prefix, game_install_path, log_path
MetalSharpSteamBridgeConfig appid, socket_path (<METALSHARP_HOME>/run/steam-bridge.sock)
MetalSharpManagedRuntimeConfig Mono root/lib dirs
MetalSharpHostCapabilities
metalsharp_host_get_abi_version() / _query_capabilities() / _self_test()
Shims read METALSHARP_MONO_LIB, METALSHARP_MONO_ROOT, METALSHARP_MONO_ASSEMBLY_DIR, METALSHARP_MONO_CONFIG_DIR (falling back to METALSHARP_HOME/HOME). The Steam bridge listens on the owner-only Unix socket $METALSHARP_HOME/run/steam-bridge.sock; the shim only connects to a listener running as the same user. Inspect with GET /runtime/host-abi. Built as libmetalsharp_host_runtime.dylib, staged by tools/package/create-host-runtime.sh, installed to runtime/host/; tested by tests/test_host_runtime_abi.cpp.
| Area | Source | Status |
|---|---|---|
| D3D11/DXGI and D3D9 Wine unix bridges | src/wine/*_pe.cpp, metalsharp_unix.mm, d3d9_unix.mm |
stable (the model for the ABI) |
| CoreAudio (XAudio2/DirectSound) | src/audio/ |
stable |
| GameController (XInput) | src/input/ |
stable |
| PE loader and Win32 shims | src/loader/, src/win32/kernel32/ |
stable |
| Steam bridge, kernel32/user32 shims | src/fna/shims/steam_shim.c, kernel32_shim.c, user32_shim.c |
prototype |
| Mono unixlib bridge | src/wine/mscoree_unix.c |
prototype |
| Runtime deploy glue | app/src-c/runtime/steam_actions.c, setup.c |
prototype |
| Steamworks offline, Carbon, FMOD shims | src/fna/shims/ (METALSHARP_FNA_STEAM_PASSTHROUGH=1 for native passthrough) |
game-specific |
| Anti-cheat database | AntiCheatDB.h, src/runtime/DRMDetector.cpp |
diagnostic |
Darwin sync map (src/runtime/host/DarwinSyncMap.cpp): Event, Semaphore, Mutex, CriticalSection, and in-process WaitAny map to pthread/dispatch primitives and ship. WaitAll, a ulock futex, and NtSync (no macOS /dev/ntsync) are not ready.
Vendor Trust Kit
Anti-cheat support is cooperative, never bypass or spoofing. tools/package/create-vendor-trust-kit.sh writes dist/vendor-trust-kit/ with this doc, policy docs, and a manifest.json (commit, version, included files) so a vendor can review the runtime identity, launch model, and logs. Before external use, add real notarization evidence, launch logs, Launch Doctor anti-cheat JSON, and vendor contact context.
Working on MetalSharp
Development
Build, run, test, package, and sign MetalSharp.
Updated: 2026-10-28
Build, run, test, and package MetalSharp from source. Requires Apple Silicon; native code targets macOS 14.0 (CMAKE_OSX_DEPLOYMENT_TARGET in CMakeLists.txt).
Prerequisites
xcode-select --install
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install cmake zstd
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Packaging the .app also needs full Xcode (actool compiles the Liquid Glass icon).
Clone
git clone --recurse-submodules https://github.com/metalsharp/MetalSharp.git
cd MetalSharp
Build
# Native engine (C++ D3D/Metal layer), x86_64 for Rosetta 2 PE translation
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON
cmake --build build --parallel $(sysctl -n hw.ncpu)
# C backend -> app/src-c/build/metalsharp-backend (the regular x86_64 build)
make -C app/src-c
# The Arm64 FEX build's backend, in its own build directory
make -C app/src-c VARIANT=fex BUILD_DIR="$PWD/app/src-c/build-fex"
# GPUI app (generate tilted dock artwork once first)
(cd app-gpui && swift generate-dock-art.swift)
cargo build --release --manifest-path app-gpui/Cargo.toml
The regular build's runtime bundles (Wine 11.17, D3DMetal, DXMT, Mono/FNA, Goldberg, Steam setup files) come from the bundles release; the Arm64 FEX build needs none, as MetalSharp-Wine.app carries its runtime:
./tools/dmg/create-bundles.sh
Building the app does not compile Wine; see Building MetalSharp Wine.
Run
cargo run --locked --manifest-path app-gpui/Cargo.toml # production: ~/.metalsharp
cargo run --locked --manifest-path app-gpui/Cargo.toml -- --connected-validation # isolated home
cargo run --locked --manifest-path app-gpui/Cargo.toml -- --preview # offline sample UI
cargo run --locked --manifest-path app-gpui/Cargo.toml -- --preview=2560x1440 # explicit offline viewport
The library has a sidebar, open by default and toggled from the header, with one page per launcher: Steam, Ubisoft, Battle.net, Rockstar, GOG, Epic and Windows Programs, plus Logs at the bottom. Every launcher page uses the Steam Deck view (hero and dock) with only that launcher's games; LibGame::page() in library_model.rs maps a game's source to its page. Steam, GOG, Epic and Windows Programs also have a Library/Collection toggle: Steam's Collection is the installed/uninstalled grid, and the others open the Sharp Library view for their source (sharp_deck.rs feeds their installed games to the deck). The header holds only the logo, sidebar toggle, search, theme and settings, with no fill or divider, over the page's hero art; each page's launcher button sits under it. The selected page and sidebar state are saved in gpui-ui-state.json.
Settings groups preferences into General, Integrations, Storage, Updates, and Advanced. General → Library & display includes window-size presets. Automatic uses most of the display while leaving desktop space; Large (4K) is capped at 2800 × 1700 logical points. Presets resize the app window and fit smaller displays without entering full screen. Production saves the selection alongside the other UI preferences in gpui-ui-state.json; offline previews keep changes in memory.
Preview dimensions use logical points; macOS applies the display's pixel density and may constrain the requested window to its available desktop area. The preview prints its actual viewport and scale factor at startup.
Production mode behaves like the installed app: it terminates any stale metalsharp-backend on ~/.metalsharp/run/backend.sock, starts a fresh one against ~/.metalsharp, and stops it on quit. Quit any other MetalSharp first.
Validation mode reads METALSHARP_GPUI_HOME (absolute, dedicated directory) and optionally METALSHARP_GPUI_BACKEND, and protects its backend with a per-launch session token. It refuses the production home.
The backend honors METALSHARP_HOME (default ~/.metalsharp) and serves HTTP only on the Unix socket $METALSHARP_HOME/run/backend.sock (directory 0700, socket 0600, peers must run as the same user); there is no TCP listener. Query it with curl --unix-socket ~/.metalsharp/run/backend.sock http://localhost/status. The socket path must fit in 103 bytes, so keep custom homes short.
Test
make -C app/src-c test # HTTP smoke, JSON, migration, SIGPIPE
make -C app/src-c VARIANT=fex BUILD_DIR="$PWD/app/src-c/build-fex" test # the Arm64 FEX build's suite
make -C app/src-c asan-test # same under sanitizers
cargo test --manifest-path app-gpui/Cargo.toml
A build-only browser harness is available with --features browser-fixture -- --browser-fixture (offline, fixed HTML) or -- --browser-steam-test (live Steam Store; only when explicitly needed).
Package
Ad-hoc signed app (no Apple Developer account needed):
ACTOOL=/Applications/Xcode.app/Contents/Developer/usr/bin/actool \
CARGO_TARGET_DIR=/tmp/metalsharp-target \
app-gpui/package-app.sh
open app-gpui/target/MetalSharp.app
Keep CARGO_TARGET_DIR on the internal disk; release builds can fail on an external volume. package-app.sh bundles the C backend, host runtime, tools, updater, configs, and the runtime archives from app/bundles under bundle id com.metalsharp.app.
DMG (MetalSharp-<version>-arm64.dmg):
tools/dmg/package-gpui-dmg.sh app-gpui/target/MetalSharp.app dist/gpui
The Arm64 FEX build packages the same way with METALSHARP_VARIANT=fex and METALSHARP_WINE_APP pointing at a signed MetalSharp-Wine.app. That app is copied unchanged into Resources/ and never re-signed. The DMG is MetalSharp-<version>-arm64-FEX.dmg, checked by tools/dmg/verify-fex-dmg.sh. Release CI downloads the pinned MetalSharp Wine release (METALSHARP_WINE_TAG in release.yml, v4.0 for 1.0.0) and checks it against SHA256SUMS.
package-local-preview.sh and package-local-testing-app.sh / package-local-testing-dmg.sh build the same app under separate bundle ids for side-by-side testing (they still use ~/.metalsharp and its backend socket). package-local-testing-fex-dmg.sh builds the Arm64 FEX build's testing DMG.
Release Signing
Releases must be Developer ID signed and notarized, or Gatekeeper blocks them. Set these GitHub Actions secrets:
MACOS_CERTIFICATE_P12(base64.p12) andMACOS_CERTIFICATE_PASSWORD- One notarization set:
APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_IDorAPPLE_API_KEY_P8_BASE64,APPLE_API_KEY_ID,APPLE_API_ISSUER
The release job runs tools/dmg/prepare-apple-signing.sh → package-app.sh with METALSHARP_GPUI_SIGN_IDENTITY (hardened runtime, tools/dmg/entitlements.mac.plist) → package-gpui-dmg.sh → sign-notarize-dmg.sh → verify-notarization.sh. Without the secrets, CI ships an ad-hoc DMG with a DMG-SIGNING.txt marker.
The in-app updater only accepts a DMG named MetalSharp-<version>-arm64.dmg (or MetalSharp-<version>-arm64-FEX.dmg for the Arm64 FEX build) containing com.metalsharp.app at that version, passing codesign --verify --deep --strict, and signed by the same Developer ID team as the installed app. Ad-hoc releases cannot be installed through the updater. It installs a newer version, or the other build at the same version (a build switch). If the release's FEX build fails, the release job ships the regular DMG without it.
Pre-commit Hook
Opt in once per clone:
ln -s ../../.github/hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
| Staged files | Check | If tool missing |
|---|---|---|
*.c, *.cpp, *.h, *.hpp, *.m, *.mm |
clang-format --dry-run --Werror |
fail |
app-gpui/**/*.rs |
cargo fmt --check |
fail |
configs/mtsp-rules.toml, configs/mtsp-rules-fex.toml, tools/rules/generate-fex-rules.py |
python3 tools/ci/validate-rules-toml.py and python3 tools/rules/generate-fex-rules.py --check |
fail |
*.sh |
shellcheck |
warn |
*.md |
python3 tools/ci/check-doc-freshness.py |
warn |
Skip with git commit --no-verify; CI runs the same checks.
Troubleshooting
cmakefails: checkxcode-select -preturns a path.can't find cratein release builds: moveCARGO_TARGET_DIRto the internal disk.- Missing bundles: rerun
./tools/dmg/create-bundles.sh. - App won't open:
xattr -cr /path/to/MetalSharp.app.
Working on MetalSharp
Building MetalSharp Wine
Build the patched Wine 11.17 runtime.
Updated: 2026-10-28
Builds the regular x86_64 build's Wine 11.17 runtime: x86_64 Unix host (runs under Rosetta) with i386 + x86_64 PE (WoW64). Build into a separate directory, never over ~/.metalsharp/runtime or a live Steam prefix.
The Arm64 FEX build's runtime, MetalSharp Wine (Wine 11.20 with FEX, Mullion, VKD3D-Proton, DXVK and WineMetalGL), is built and released separately in metalsharp/MetalSharp-Wine; the FEX DMG carries its signed MetalSharp-Wine.app unchanged.
Source
You need the prepared MetalSharp-patched Wine 11.17 tree (get it from the maintainer; no public URL yet). Stock Wine lacks MetalSharp's D3DMetal/DXMT loaders, macOS presentation, MSYNC (client and server must come from the same build), WoW64 bootstrap, launcher, and x87 changes. Apply tools/bundles/wine/0001-x87sidecar-cooperative.patch once before configuring.
D3DMetal, DXMT (v0.80), DXVK/VKD3D-Proton, and MoltenVK payloads are separate; make install does not provide them. D3DMetal launches need D3DMETAL_RUNTIME_DIR (payload root) and D3DMETAL_FRAMEWORK_PATH (external/D3DMetal.framework/D3DMetal). Preserve upstream copyright and license notices (see LICENSES/WINEFORGE-NOTICES.md).
Tools
xcode-select --install
softwareupdate --install-rosetta --agree-to-license
brew install autoconf automake bison flex pkgconf llvm mingw-w64 python
command -v i686-w64-mingw32-gcc x86_64-w64-mingw32-gcc
x86_64 Dependencies
Every library linked into Wine's Unix side needs an x86_64 slice; arm64 Homebrew libraries don't count. Provide a prefix $DEPS (include/, lib/, bin/, lib/pkgconfig/) with: FreeType, GnuTLS, FFmpeg, GStreamer + GLib, SDL2, Samba NetAPI (netapi.h, libnetapi.dylib, ntlm_auth), Vulkan loader + MoltenVK, OpenCL headers, and gettext. Check with lipo -archs "$DEPS/lib/libgnutls.dylib".
Configure and Build
Use absolute paths without spaces.
export ROOT="/Volumes/BuildSSD/MetalSharpWine"
export SRC="$ROOT/sources/wine-11.17-metalsharp"
export DEPS="$ROOT/deps/x86_64"
export BUILD="$ROOT/build/wine-x86_64"
export PREFIX="$ROOT/install/wine"
export X87_WINE_PATCH="/path/to/MetalSharp/tools/bundles/wine/0001-x87sidecar-cooperative.patch"
export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$(brew --prefix llvm)/bin:$DEPS/bin:$PATH"
export CC="clang -arch x86_64" CXX="clang++ -arch x86_64" CPP="clang -arch x86_64 -E" OBJC="clang -arch x86_64"
export CFLAGS="-O2 -isysroot $SDKROOT" CXXFLAGS="-O2 -isysroot $SDKROOT" OBJCFLAGS="-O2 -isysroot $SDKROOT"
export CPPFLAGS="-I$DEPS/include"
export LDFLAGS="-arch x86_64 -isysroot $SDKROOT -L$DEPS/lib -Wl,-rpath,$DEPS/lib"
export PKG_CONFIG_LIBDIR="$DEPS/lib/pkgconfig:$DEPS/share/pkgconfig"; unset PKG_CONFIG_PATH
export i386_CC="$(command -v i686-w64-mingw32-gcc)" x86_64_CC="$(command -v x86_64-w64-mingw32-gcc)"
export NETAPI_CFLAGS="-I$DEPS/include" NETAPI_LIBS="-L$DEPS/lib -lnetapi"
export OPENCL_CFLAGS="-I$DEPS/include" OPENCL_LIBS="-framework OpenCL"
mkdir -p "$BUILD" "$PREFIX" "$ROOT/logs"
git -C "$SRC" apply --check "$X87_WINE_PATCH" && git -C "$SRC" apply "$X87_WINE_PATCH"
(cd "$SRC" && autoreconf -fiv)
cd "$BUILD"
set -o pipefail
"$SRC/configure" \
--build=x86_64-apple-darwin --enable-archs=i386,x86_64 --prefix="$PREFIX" \
--disable-tests --disable-winemenubuilder \
--with-coreaudio --with-cups --with-ffmpeg --with-freetype \
--with-gettext --with-gnutls --with-gstreamer --with-mingw \
--with-netapi --with-opencl --with-opengl --with-pcap \
--with-pthread --with-sdl --with-vulkan \
--without-alsa --without-capi --without-dbus --without-gphoto \
--without-inotify --without-krb5 --without-oss --without-pulse \
--without-sane --without-udev --without-usb --without-v4l2 \
--without-wayland --without-x \
2>&1 | tee "$ROOT/logs/configure.log"
make -j"$(sysctl -n hw.ncpu)" 2>&1 | tee "$ROOT/logs/build.log"
make install 2>&1 | tee "$ROOT/logs/install.log"
Use --enable-archs=i386,x86_64, not --enable-win64. If configure can't find a dependency, set its *_CFLAGS/*_LIBS to the x86_64 prefix; don't force cache results. Use a fresh build directory when changing the source, arch, or dependencies.
x87 Sidecar
export X87SIDECAR_VERSION="v1.7.0"
export X87SIDECAR_SHA256="b768336e0ad556807156cecd533423285c8ec654f4fdb9c0623124d8b12c0865"
ARCHIVE="$ROOT/downloads/x87sidecar-$X87SIDECAR_VERSION.tar.xz"
OUT="$ROOT/downloads/x87sidecar-$X87SIDECAR_VERSION"
mkdir -p "$OUT"
curl --fail --location --proto '=https' --tlsv1.2 \
"https://github.com/athei/x87sidecar/releases/download/$X87SIDECAR_VERSION/x87sidecar.tar.xz" -o "$ARCHIVE"
printf '%s %s\n' "$X87SIDECAR_SHA256" "$ARCHIVE" | shasum -a 256 -c -
tar -xJf "$ARCHIVE" -C "$OUT"
xattr -d com.apple.quarantine "$OUT/x87sidecar" 2>/dev/null || true
install -m 0755 "$OUT/x87sidecar" "$PREFIX/bin/x87sidecar"
"$PREFIX/bin/x87sidecar" --probe
The sidecar is used only for i386 images when the launcher sets ROSETTA_X87_PATH. Keep it at the same version as the patch.
Validate
export DYLD_FALLBACK_LIBRARY_PATH="$DEPS/lib:$PREFIX/lib:$PREFIX/lib/wine/x86_64-unix"
"$PREFIX/bin/wine" --version # wine-11.17
file "$PREFIX/lib/wine/x86_64-windows/ntdll.dll" "$PREFIX/lib/wine/i386-windows/ntdll.dll"
export WINEPREFIX="$(mktemp -d "$ROOT/smoke-prefix.XXXXXX")" # never ~/.metalsharp/prefix-steam
export WINEARCH=wow64 WINESERVER="$PREFIX/bin/wineserver" WINEMSYNC=1
"$PREFIX/bin/wine" wineboot -u
"$PREFIX/bin/wine" cmd /c ver
"$PREFIX/bin/wineserver" -w
Never run wineserver -k against a live Steam prefix. make install output is a development install, not a relocatable bundle: audit load paths, include the host libraries it needs, and re-sign any modified Mach-O before shipping.