Gaming [WIP] Tatsunoko vs. Capcom: Ultimate All-Stars on macOS Apple Silicon via static ARM64 recompilation

Hardy_Heron

Well-Known Member
Newcomer
Joined
Mar 6, 2017
Messages
64
Reaction score
8
Trophies
0
Age
51
Location
Valparaíso
Website
altf4.cl
XP
286
Country
Chile


Hi everyone,​


I’ve been working on a personal experiment with Tatsunoko vs. Capcom: Ultimate All-Stars on Wii, and I wanted to share the current state of the project because I think it may be interesting for people here who are into emulation, reverse engineering, static recompilation and preservation.

My goal was not simply to run the game through Dolphin.

What I wanted was to take my own legal backup of the game, extract the executable, statically recompile the PowerPC code to ARM64, and build a portable macOS application around it for Apple Silicon Macs.

At this point I have a working portable build that runs the game at roughly 60 FPS on my Mac mini M4, using Metal and an Xbox controller.

The final result behaves much more like a dedicated macOS application than a traditional emulator setup.



What I used​

The main tools involved were:

  • DolRecomp
  • ModernGekko
  • Dolphin code used through the ModernGekko runtime
  • LLVM 20
  • CMake
  • Ninja
  • Git
  • Homebrew
  • Apple Clang
  • macOS tools such as codesign, install_name_tool, otool, iconutil, etc.
The source game is my own legal USA backup of Tatsunoko vs. Capcom: Ultimate All-Stars.

The Wii executable is the usual:

main.dol
The important part of the process was recompiling that executable into an ARM64 dylib.

DolRecomp successfully processed the game with very good coverage. The recompilation report showed:

840838 instructions
1650 embedded data entries
unknown: 0
native: 100%
fallback: 0
The resulting module is an ARM64 Mach-O dylib loaded by the ModernGekko runtime.



What the final build looks like​

I ended up creating a fully portable folder like this:

Tatsunoko vs Capcom Portable/
├── Tatsunoko vs Capcom.app
└── UserData/
Inside the app bundle I packaged:

moderngekko-run
ARM64 recompiled game module
game data
Dolphin / ModernGekko runtime files
controller profile
lz4
zstd
native ARM64 launcher
The launcher resolves all paths relative to the application itself.

That means the build no longer depends on my development directories, username, Homebrew installation or SSD name.

I can move the entire folder to another external drive and run it from there.

I tested the portable build from an ExFAT SSD and it launches correctly.



Main technical problems I encountered​

This was definitely not a case of “compile and run”.

There were several problems along the way.

1. LLVM / Mach-O incompatibility​

The DolRecomp LLVM backend was trying to assign functions to sections such as:

.text.unlikely.*
This works in other object formats but caused problems when generating Mach-O binaries on macOS.

I ended up applying a small Apple-specific guard so that those sections are not assigned on macOS.

After that, the LLVM backend built correctly.



2. GXRuntime / ModernGekko ABI mismatch​

There was also a mismatch between the CPU state structures expected by ModernGekko and the version of GXRuntime included in the project.

I had to update the runtime ABI and add the missing fields used by the current runtime, including:

cycle_budget
There were also changes related to native-region queries.

Without matching these structures correctly, the recompiled code and the runtime were not communicating properly.



3. Native-region query problem​

This was probably the most important issue.

The recompiled executable performs a special host call to ask the runtime whether a memory region is handled by native recompiled code.

ModernGekko was not handling this query correctly.

The result was that execution became effectively stuck around the game entry point:

0x800062F0
I added handling for the special native-region query host call.

Conceptually, the runtime now receives the address being queried and checks whether that address belongs to a loaded native module.

Once that was fixed, the game started progressing properly instead of looping at startup.

That was the point where the project really started becoming viable.



4. libusb issue on macOS​

I also hit a crash involving libusb.

The problematic code path was related to pipe2.

On macOS the runtime could end up referencing pipe2 in a way that resulted in a null-function call.

I added an Apple-specific workaround so libusb uses the more traditional:

pipe + fcntl
path instead.

That removed another startup crash.



5. Bluetooth HLE crash​

The next major issue appeared in the Bluetooth emulation layer.

Some control messages were arriving with:

length = 0
Later code performed arithmetic equivalent to:

length - 3
which obviously underflowed and resulted in an invalid size.

For debugging I added a temporary fallback that uses the actual IO buffer size when the control-message length is zero.

Something roughly equivalent to:

if (ctrl.length == 0 && io_size > 0)
ctrl.length = io_size;
This made the game boot and run successfully.

However, I consider this a workaround, not a real fix.

One of the things I would really like feedback on is:

why are all of these Bluetooth control messages arriving with wLength / length set to zero in this setup?

The fallback works, but I would much rather understand the root cause.



Controller configuration​

The controller side also turned out to be interesting.

ModernGekko was initially creating a GameCube controller configuration that was not useful for this game.

I disabled the GC controller ports and configured the runtime around a sideways Wii Remote profile.

I tested several physical controllers.

The best result so far has been with an Xbox controller.

With that configuration I get approximately:

59 - 61 FPS
and gameplay feels stable.

I also tested a Switch Pro controller and a Machenike G5 Pro.

For reasons I have not yet fully investigated, those configurations caused the framerate to fluctuate much more aggressively, sometimes dropping into the 20s and 30s.

So for the current portable build I am treating the Xbox controller as the reference configuration.

I would also be interested to hear if anyone has seen similar performance differences related to SDL controller devices or polling behavior.



Making it truly portable​

Once the game itself worked, I wanted to remove my development environment from the equation.

moderngekko-run originally depended on Homebrew paths such as:

/opt/homebrew/opt/lz4/lib/liblz4.1.dylib
/opt/homebrew/opt/zstd/lib/libzstd.1.dylib
I copied those libraries into the app bundle and rewrote the dependencies to use:

@executable_path
The result is that the final build no longer needs Homebrew installed.

It also does not require Dolphin or ModernGekko to be installed separately.

The launcher itself is a small native ARM64 program that locates:

Runtime
Game
Module
UserData
relative to its own bundle path.

This means I can copy the folder to another SSD without changing any paths.



Current result​

Right now the build:

  • runs on Apple Silicon
  • is ARM64
  • uses Metal
  • runs close to 60 FPS
  • works well with Xbox controllers
  • does not require Homebrew
  • does not require Dolphin installed
  • does not require ModernGekko installed
  • does not require Rosetta
  • can run directly from an external SSD
  • behaves like a normal macOS .app
The target is currently:

macOS 13+
Apple Silicon only
M1 / M2 / M3 / M4 and future compatible ARM64 Macs


Is this a native port?​

I would not call it a pure native port.

I also would not describe it as normal emulation.

The main game executable has been statically recompiled from PowerPC to ARM64, but ModernGekko / Dolphin still provide the environment the game expects:

  • Wii memory behavior
  • IOS services
  • graphics
  • audio
  • input
  • USB
  • Bluetooth
  • other hardware abstractions
So the most accurate description, at least in my view, would be:

a static ARM64 recompilation compatibility port using a Dolphin-derived HLE runtime
It sits somewhere between a conventional emulator setup and a traditional source-code port.



Why I think this approach is interesting​

The most obvious advantage is not necessarily raw performance.

Dolphin is already extremely optimized.

What I find interesting is the possibility of creating game-specific compatibility builds.

Once the executable is statically recompiled, it becomes possible to think about things like:

  • game-specific patches
  • native ARM64 optimizations
  • cleaner macOS integration
  • standalone .app packages
  • controller profiles designed specifically for one game
  • custom resolutions
  • widescreen fixes
  • frame pacing improvements
  • reducing runtime overhead
  • replacing more emulated/HLE paths with native implementations
  • eventually identifying which parts of Dolphin are actually necessary for a specific title
For me, that is the really interesting part.

Instead of asking:

“How well does this emulator run thousands of Wii games?”
the question becomes:

“How far can we push compatibility and native execution for this one specific game?”


Things I still want to investigate​

There are several areas I consider unfinished:

  1. Understand the real cause of the Bluetooth control-message length issue.
  2. Investigate the controller-dependent performance differences.
  3. Review the self-modifying-code report from DolRecomp.
    The game has several possible patching regions, and although the runtime currently reports no SMC failure during normal execution, I do not want to assume those regions are harmless.
  4. Reduce the size of the portable package.
  5. Test on more Apple Silicon machines, especially M1 and M2 systems.
  6. Test different macOS versions.
  7. Clean up the temporary diagnostic patches.
  8. Investigate whether more of the Dolphin runtime can eventually be removed or specialized specifically for TvC.


Looking for feedback / contributors​

I’m posting this because I would really appreciate input from people who understand Dolphin internals, Wii IOS, Bluetooth HLE, PowerPC recompilation, LLVM or static recompilation projects.

In particular, I would love opinions on:

  • the Bluetooth length == 0 behavior
  • the native-region host-call implementation
  • whether the SMC regions in TvC are likely to become relevant later
  • the controller-related performance differences
  • whether there are better approaches for packaging the runtime on macOS
  • possible ways to reduce the amount of Dolphin code needed for a single-game build
I’m still learning a lot of this as I go.

I do not come from a professional software-development background, so part of the project has been learning how all of these pieces fit together.

I built this with a lot of experimentation, log analysis and assistance from ChatGPT, especially for understanding crashes, preparing small patches and navigating the ModernGekko / DolRecomp codebase.

If anyone here wants to help investigate specific issues, suggest cleaner fixes, or experiment with other Wii titles using the same approach, I would be very interested in collaborating.

Even just technical criticism would be useful.

I’m especially interested in hearing from anyone who has experience with:

  • Dolphin internals
  • static recompilation
  • Wii hardware / IOS
  • ARM64
  • LLVM
  • macOS Mach-O binaries
The project is already playable, but I think there is a lot more potential here.



Legal note: I am working exclusively from my own legally obtained copy and personal backup of Tatsunoko vs. Capcom: Ultimate All-Stars. I am not distributing the game, ROMs, extracted assets or other proprietary game content. Any public release of the technical work would need to separate patches, source code, configuration and open-source components from copyrighted game data.
 
Last edited by Hardy_Heron,

Site & Scene News

Popular threads in this forum