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. These six cards map the public routes; D3DMetal uses the managed GPTK 4 beta 2 payload and Wine 11.17, while 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.
DirectX → DXMT → MetalDXMT(32)The same DXMT route in a dedicated 32-bit/i386 prefix.32-bit DirectX → DXMT → MetalD3D9Older D3D9 titles through Wine i386 D3D9 with x87 acceleration.D3D9 → Wine D3D9 → MetalVKD3DD3D12/D3D11/D3D10/D3D9 via Vulkan and MoltenVK.DirectX → Vulkan → MetalD3DMetalD3D11/D3D12 through the managed GPTK 4 beta 2 payload and Wine 11.17.DirectX → D3DMetal → MetalMono/FNAXNA/FNA/MonoGame through native Mono and shims.XNA/FNA → Mono → Metal
Using MetalSharp
User Guide
Install, play, Sharp Library, controllers, streaming, and logs.
Quick startDownload the DMG, open MetalSharp, start Wine Steam, install a game, then choose Play. Each game’s managed setup is called a bottle.
Updated: 2026-10-05
Install
- Download the 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.
- Start Wine Steam, sign in, and install a Windows game.
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, Game Jolt, and emulators. For Ubisoft Connect, see the Ubisoft Connect guide.
- 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.
Controller Input
The Controller selector in the sidebar sets which input shims MetalSharp deploys to the game folder and Steam prefix:
- 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.
- 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-05
-
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.
- Install games. Sign in and install a game. External drives show up in Ubisoft Connect as drive
Y:. To install to one, click Change Location, choose the drive shown as/, go toVolumes/<drive name>/, and create aUbisoftfolder.
- 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
Emulators
PCSX2, RPCS3, shadPS4, and SharpEmu in the Sharp Library.
Updated: 2026-10-05
MetalSharp installs and runs four console emulators from the Sharp Library. Each one runs out of process in its own managed environment, separate from any standalone install of the same emulator.
| Emulator | Console | Status |
|---|---|---|
| PCSX2 | PlayStation 2 | Stable |
| RPCS3 | PlayStation 3 | Stable |
| shadPS4 | PlayStation 4 | Experimental — a listed game may not be playable |
| SharpEmu | PlayStation 5 | Experimental research — most games do not run |
BIOS, firmware, and games must come from hardware and content you own. MetalSharp ships none of them.
Managed Environment
Every emulator uses the same layout under ~/.metalsharp/emulators/<name>/:
├── current -> versions/<tag> # active build
├── previous -> versions/<tag> # rollback target
├── versions/<tag>/ # read-only emulator build
├── home/ # isolated HOME: config, saves, caches
├── downloads/ staging/ sessions/ logs/
├── environment.json library.json
- Isolation: the emulator runs with
home/asHOME, so its settings, saves, and caches never touch a standalone install. - Updates: MetalSharp downloads only official stable releases. It verifies size and SHA-256, rejects unsafe archives, checks the binary, and switches
currentatomically. If any step fails, the active version stays put. Activation waits until running games exit. - Controls: check for updates, install, pin the current build, skip a release, roll back to
previous, or remove the runtime. - Removal: removing the runtime deletes only the version trees and downloads. Home data, logs, and your game folders stay.
- Game folders: added folders are references. Removing one only edits
library.json; nothing on disk is deleted. Symlinked directories are skipped. - Processes: each launch gets its own process group, log, and session record. Stop and status check that the PID still belongs to the emulator, and records survive backend restarts.
The backend API is GET /emulators plus /sharp-library/<name>/... routes for status, games, scan, launch, stop, and update actions.
PCSX2 (PS2)
Uses the official signed and notarized macOS app, which runs under Rosetta on Apple Silicon. The upstream signature is checked again at every launch.
- Open Sharp Library → PCSX2 and install the runtime.
- Import a BIOS dumped from your own PS2 (guide). Only
.binfiles from 4 to 8 MiB with a recognized region are accepted. - In PCSX2 Setup, choose the controller for ports 1 and 2 and the renderer (Automatic, Metal, OpenGL, Vulkan, or Software).
- Add a disc image or a game folder (dumping guide).
Files: ISO, BIN, IMG, MDF, GZ, CSO, ZSO, CHD, and homebrew ELF. Picking a single file indexes only that file; picking a folder scans it recursively.
Differences:
- PCSX2's own updater is turned off.
- A configuration backup is taken before each update.
- Rolling back warns first because savestates can depend on the version.
- Titles come from the disc serial when the image is uncompressed, and from the filename otherwise.
RPCS3 (PS3)
- Open Sharp Library → RPCS3 and install the runtime. Apple Silicon and Intel Macs each get their matching official build.
- Download Firmware opens Sony's PS3 update page. Install the downloaded
PS3UPDAT.PUPfrom MetalSharp. - Install PS3 packages (
.pkg) or add folders of game dumps.
Differences:
- Games are discovered in the isolated
dev_hdd0/gamefolder and in any folders you add. Title and ID come fromPARAM.SFO, andICON0.PNGis used as artwork. - Games launch fullscreen with no RPCS3 GUI.
- Release metadata is cached for 12 hours. Check RPCS3 skips the cache.
shadPS4 (PS4) — experimental
Requires Apple Silicon with Rosetta 2. The official macOS build is x86_64.
- Open Sharp Library → shadPS4 and install the runtime.
- Add folders containing your own game dumps. A game is found when its folder contains
CUSAxxxxx/eboot.binandsce_sys/param.sfo. Patch folders are de-duplicated. - Optionally, import decrypted firmware modules (
.sprx) and fonts from a console you own.
Differences:
- Upstream macOS builds are unsigned, so MetalSharp ad-hoc signs them locally after verifying them.
- The emulator runs from its version folder so its bundled Vulkan driver (KosmicKrisp) resolves correctly.
SharpEmu (PS5) — experimental
Requires macOS 26+, Rosetta 2 on Apple Silicon, lsar/unar, and at least 1 GiB of free space. Vulkan runs through the bundled MoltenVK.
- Open Sharp Library → SharpEmu and confirm the install.
- Add game folders. A game is found when its folder contains
eboot.bin(a decrypted ELF or fake-signed SELF) with aparam.json, and its title ID has the formPPSA#####.
Differences:
- The layout adds
state/(saves, configs, folders),cache/(.NET, AMPR, Vulkan), andwritable/. All of them survive updates and removal. - The upstream archive is unsigned and its release asset can change in place. MetalSharp pins the exact release and asset IDs, quarantines changed metadata, and ad-hoc signs the build locally.
- Networking is off by default. Games run under
sandbox-execwith all network access denied, and launch fails if the sandbox can't be confirmed. Turning on Allow unrestricted guest networking asks for a second confirmation at every launch. - A game whose
eboot.binchanged since the last scan must be rescanned before it can launch.
Using MetalSharp
Supported Games
Tested titles and the route each one uses.
Updated: 2026-10-06
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.
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 |
| 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 | Starts the Rockstar Games Launcher directly with WFDXCompat; also runs on VKD3D (Vulkan) |
| Grand Theft Auto V Enhanced | 3240220 | Story mode. Starts the Rockstar Games Launcher directly; BattlEye launcher swapped for the game exe; Agility SDK D3D12 loading through WFDXCompat; also runs on VKD3D |
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 |
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 |
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. |
Working on MetalSharp
Architecture
Launch routes, Wine runtime, bottles, and bundles.
Updated: 2026-10-06
How MetalSharp launches games, what it installs, and where state lives. Build instructions are in development.md and building-wine.md; emulator providers are in emulators.md.
Launch Flow
The C backend (127.0.0.1:9274) owns every launch. Route selection is data-driven: adding a game means editing configs/mtsp-rules.toml, not backend code.
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
-> backend copies route DLLs beside the game executable and sets Wine/DYLD/cache env
-> 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
| 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) is built into the runtime from WineMetalGL v0.1.0 (WineMetalGL-0.1.0.tar.zst, SHA-256 1796266dc1fe43bb15050851d2a3f43d53c1ae961c7596b193cc9b47a465a553). The sidecar metalsharp-opengl.dylib is rebuilt as x86_64 and loaded by winemac.so; guest opengl32.dll ships for x86_64 and i386. Compatibility GL is the default; VKMT_OPENGL_METAL_EXPERIMENTAL=1 enables the GLSL/SPIR-V/Metal path. Build with tools/bundles/build-winemetalgl-x86.sh OUTPUT_DIR; 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 |
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; a writable external volume maps to Y:. 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, port (default 18733)
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), and METALSHARP_STEAM_BRIDGE_PORT. 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-05
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
make -C app/src-c
# 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
Download the runtime bundles (Wine 11.17, D3DMetal, DXMT, Mono/FNA, Goldberg, Steam setup files) from the bundles release:
./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: :9274, ~/.metalsharp
cargo run --locked --manifest-path app-gpui/Cargo.toml -- --connected-validation # isolated home on :9276
cargo run --locked --manifest-path app-gpui/Cargo.toml -- --preview # offline sample UI
Production mode behaves like the installed app: it terminates any stale metalsharp-backend on 9274, starts a fresh one against ~/.metalsharp, and stops it on quit. Quit any other MetalSharp first.
Validation mode reads METALSHARP_GPUI_PORT (default 9276), 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 port and home.
The backend alone honors METALSHARP_PORT (default 127.0.0.1:9274) and METALSHARP_HOME (default ~/.metalsharp).
Test
make -C app/src-c test # HTTP smoke, JSON, migration, SIGPIPE, emulator update transactions
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
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 port 9274).
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 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.
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 |
python3 tools/ci/validate-rules-toml.py |
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-05
Builds the 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.
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.