Boston
Meridan ReportBoston: Biotech, AI research, institutional finance

Advertisement · Why this ad?

Technology

Show HN: Black Ops II statically recompiled to native ARM64 macOS

Marcus Chen
Managing Editor · Wednesday, November 11, 2026 at 4:56 AM · 6 min read
Show HN: Black Ops II statically recompiled to native ARM64 macOS — Boston Technology news

bo2-mac Call of Duty: Black Ops II, statically recompiled from 32-bit x86 Windows to native ARM64 macOS. Campaign, Zombies and multiplayer against bots, built on your Mac from your own Steam copy. English · 中文 Abstract · Method · Limits · Repository · Legal Beta, 2026-11-03 · measured on one Apple M1 (4 performance + 4 efficiency cores, 16 GB) Campaign (Angola), Zombies (Town) and multiplayer (Hijacked, against bots) on an Apple M1, recorded while playing; the number in the top right corner is frames per second, and the screen recording itself costs some, mostly in the campaign. 80-second video (MP4, 20 MB) Important This repository contains no game file and no translated game code. It turns your own Steam copy of Black Ops II into an app for your Mac, on your Mac. The game's code ends up inside that app, so nobody can hand you a ready-made one: each player builds their own, and it may not be shared. Unofficial fan project, not affiliated with Activision, Treyarch or Microsoft. To play, buy the game. Beta: read what has and has not been proven . Abstract bo2-mac takes the three executables of Black Ops II, the campaign ( t6sp.exe ), Zombies ( t6zm.exe ) and multiplayer ( t6mp.exe ), and turns their x86 machine code into ARM64 machine code ahead of time. Remill lifts each function to LLVM IR, a chain of our own passes rewrites that IR, and the result is linked with a Win32 runtime written by hand, whose Direct3D 11 is drawn by Metal. There is no Wine and no JIT: the game runs as native code, in one process. About 150,000 functions are translated across the three executables. Only the short sequences that the copy protection hides run in a small x86 interpreter, and so does any code the translation missed, so a hole is built to cost speed rather than crash the game. On an Apple M1 the campaign runs at 51–60 frames per second , and every mission loads and hands over to the next one, down to the end credits. Zombies is played solo on six maps through the game's own menus at 57–60 . Multiplayer is played against bots through its real menus, eleven game modes, at 53–60 . One app, Black Ops II.app , opens the campaign, and the CAMPAIGN / ZOMBIES / MULTIPLAYER entries of each menu switch to the other game. Nothing is downloaded from anyone but you: the converter reads the executables of the copy you bought, checks them byte for byte, and builds the app on your machine. Results measured conditions Campaign 51–60 fps every mission loads and chains to the end credits; not all played end to end Zombies 57–60 fps (Die Rise 43–58) TranZit, Mob of the Dead, Nuketown, Origins, Buried, Die Rise, solo, through the real menus Multiplayer against bots 53–60 fps eleven game modes through the real menus: a full round, the scoreboard, back to the lobby Game world against real time ≈ 1.0× the game's own server clock against wall time Conversion on the player's Mac about 1 hour 45 on an M1 (the campaign alone: about 45 minutes) the campaign is playable as soon as it is converted; Zombies and multiplayer follow Disk peak about 10 GB , about 4 GB kept after the conversion, only the linked binaries stay Frame rate counts the drawables really presented to the screen, checked against the screen and the Metal HUD. What you can play The real main menu: NEW STORY, RESUME STORY, REPLAY MISSION. From angola to the end credits, the missions load and hand over to the next one. Strike Force missions start with a click. The six maps, solo: main menu, lobby, match, Game Over, scoreboard, then another match in the same process. DLC maps play if your Steam account owns them. Custom Games against bots from the real menus: CHOOSE CLASS, the HUD, a full round, the scoreboard and back to the lobby. The match rules are this project's own script. All three modes: keyboard and mouse, or a DualSense as XInput through GameController.framework; sound on; movement keys follow their position on your keyboard (ZQSD on AZERTY, WASD on QWERTY), unless you bound them yourself in the game's options. Pipeline In five steps, all on the player's Mac: Check. The converter accepts only the exact Steam build of each executable, checked by SHA-256. The launcher asks the Steam client on your Mac whether the signed-in account owns the game, and refuses otherwise. Catalogue and lift. Ghidra lists the functions of your own executable; Remill v6.0.1 lifts each one to LLVM IR. Five patches to the lifter are carried in tools/remill-patches/ , among them the x87 rounding mode, and one that forks a running Remill for each function instead of reloading its x86 semantics. Fold. Remill's IR is correct but slow: every guest memory access, flag and x87 push is a call or a store into a State structure. Our passes rewrite it before clang -O2 : guest reads and writes are inlined, lock sequences become hardware atomics, the guest ret stays inside the lifted code, and the x87 stack is promoted to locals. Runtime. runtime/ implements the Windows imports the game really calls, with exact stdcall arities (the Steam API ones taken from Valve's public headers). Guest memory is one 4 GiB mapping. Direct3D 11 is a fake-COM layer over Metal; DXBC shaders go through HLSLcc . XAudio2 goes to CoreAudio, and the videos are decoded natively with libvpx. Threads. Guest threads start as coroutines on one host thread, which removes most sources of non-determinism and makes a run replayable. The fast configuration moves the server, the render backend, sound mixing and decoding, and the job workers onto real host threads. ARM is not TSO, so this needed real memory barriers. The copy protection is executed, not bypassed. The game hides integrity checks behind hardware exceptions, single-step traps and code it decrypts into the heap. A small x86 machine built on Unicorn ( x86_guest_vm.cpp , heap_code.cpp ) runs exactly those sequences and hands control back to the recompiled code. No check result is forced and no key is placed by hand. A hole in the translation is built to cost speed, not a crash. When the game reaches x86 code that was never lifted, the same machine runs its original bytes ( interp.cpp ) and gives control back to the recompiled code at the next translated block, call or return. Each interpreted address is logged ( [INTERP] ). The online service is answered locally. Zombies and multiplayer expect Demonware; a responder written from the game's own requests ( dw_local.cpp ) answers the lobby, storage and ticket on your Mac. Nothing leaves the machine. Why not Wine, CrossOver, DXVK or DXMT? They translate Windows calls at run time and keep the x86 code under Rosetta. Here the x86 code itself is gone: it is ARM64 machine code before the game starts. DXVK and DXMT are PE DLLs that reach their native backend through Wine's unixlib mechanism; this port has no Wine, and its Direct3D 11 is its own layer over 32-bit guest memory. Only shader translation was needed from outside, and HLSLcc, a plain C++ library, covers it. Findings Each entry is a symptom seen on screen, the cause that was measured, and the effect of the fix. Thirteen findings , from the x87 rounding mode to a false start inside one instruction F1 · The lifter ignored the x87 rounding mode. In Zombies the prone view shook. fistp rounded to nearest where the game had asked for truncation, inside the network codec of the view height. The height received by the client was wrong on 8 samples out of 8, and on 0 out of 8 after a Remill patch and a re-lift of the codec. F2 · One reserved bit of the x87 control word. A campaign mission froze in an effects loop, only with real threads. The lifter stored bit 6 of the control word differently from real hardware. A one-line Remill patch and four re-lifted runtime functions fixed it, with no slowdown. F3 · A CPU feature flag changes the algorithm. Our IsProcessorFeature

Up nextGreg Kurtzer's Ginux is now a userspace kernelTechnology · 5 min read
You're offline — showing the last loaded page