Ace Combat 5 Recompilation

Ace Combat 5 Recompilation brings Ace Combat 5: The Unsung War to PC.

README

ps2recomp: Ace Combat 5

A static recompilation of Ace Combat 5: The Unsung War (PS2, NTSC-U) for Windows.

The game's main CPU code isn't emulated. A Python tool reads the original executable and translates every function into C ahead of time. GCC then compiles that together with a runtime that stands in for the rest of the console: the GS (drawn through Vulkan), the VU vector units, SPU2 audio, the IPU for the movies, the IOP modules, memory cards and controllers. What you get at the end is a normal ac5.exe.

The graphics are native too. The 3D (the aircraft, the cockpit, terrain, ground objects, the sky and clouds) doesn't go through an emulated Graphics Synthesizer. The vector programs the game runs on the VU1 to transform and light its models have been rewritten as native code, and the geometry is drawn as real GPU meshes, with vertex shaders, mipmapped textures and anisotropic filtering. The flight HUD, the radar and the radio captions are drawn at your window's resolution, so they stay sharp at any size. Whatever the native renderer doesn't cover yet (the menus, the hangar, some effects) still goes through the emulated GS, into the same frame.

The game is fully playable.

There's no game code or assets in this repo. You bring your own copy of the game and the recompiler builds the C code from it on your machine, which is also why generated/ is in .gitignore.

What you need

  • The game. The US release, serial SLUS-20851, as an ISO or as the extracted disc files. Other regions won't work because the config files are tied to addresses in the US executable.

  • 64-bit Windows and a GPU with a Vulkan driver. I build and play on Windows 10.

  • MSYS2, for GCC and SDL3. In the MSYS2 UCRT64 shell run:

    pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-sdl3 mingw-w64-ucrt-x86_64-pkgconf
    

    It has to be GCC 15 or newer. The generated code relies on guaranteed tail calls ([[gnu::musttail]]), and with an older compiler you get a CMake warning and an exe that can run out of stack.

  • CMake 3.20 or newer, and Ninja (pip install ninja is the easiest way to get it).

  • The Vulkan SDK. The build uses its glslc to compile the shaders.

  • Python 3. Only the standard library is used, there's nothing to pip install. I'm on 3.12.

Step 1: get the executable off the disc

The recompiler only needs one file from the disc, SLUS_208.51, which is the game's main executable. It's in the root of the ISO. Mount the ISO in Explorer (double-click it) or open it with 7-Zip, and copy that file somewhere.

Make sure it's the right one before going any further:

Get-FileHash .\SLUS_208.51 -Algorithm SHA256

You should get:

C3594227605307806592416DBD723BF5771952E279EE281A40DA305426667385

The file should also be exactly 3,634,092 bytes. If the hash doesn't match, stop here. You've got a different region or a modified executable, and the recompile won't line up with the config files.

Keep the ISO around. The executable is just the code, the game still loads its models, textures, sound and movies off the disc while you play.

Step 2: set up a terminal

Everything from here on is PowerShell, run from the root of this repo. Put MSYS2's UCRT64 bin folder at the front of your PATH first, so CMake can find gcc and pkg-config:

$env:PATH = "C:\msys64\ucrt64\bin;$env:PATH"

That only applies to the window you're in. Change the path if MSYS2 isn't installed in C:\msys64. If you only just installed the Vulkan SDK, open a new terminal so it picks up the VULKAN_SDK variable.

Step 3: recompile

$env:PYTHONPATH = "tools"
python -m ps2recomp "C:\path\to\SLUS_208.51" -o generated `
    --ida-db config/ida_db.json `
    --ida-seeds config/ida_seeds.json `
    --symbols config/sdk_symbols.json `
    --symbols config/manual_symbols.json `
    --overrides config/overrides.json `
    --hooks config/hooks.json

This takes about 15 seconds, and the last line should be:

emitted 10742 functions into 43 files in 15.0s

(the time will be different for you). That gives you a generated/ folder of about 50 MB:

  • ps2_code_0000.c through ps2_code_0042.c: the recompiled functions
  • ps2_func_table.c and ps2_funcs.h: the address to function table the runtime dispatches through
  • ps2_symbols.c: names for the functions that have one
  • ps2_image.bin: the executable's memory image, which the runtime loads at startup (ps2_image.c just records where it goes and how big it is)

Use all of those flags. There's a shorter version with only --ida-db sitting in an error message in CMakeLists.txt, don't go by that one. It does produce code that compiles, but it leaves out the overrides and hooks, and the game won't work without them. The overrides replace SDK functions that talk to hardware the PC doesn't have (SIF RPC, the CD drive, the pads and so on) with native handlers in runtime/src/ps2_hle_*.c. Recompile those faithfully and the game just sits there waiting on hardware that never answers.

The rest, in short:

  • ida_db.json and ida_seeds.json are function boundaries and entry points exported from IDA. When I tested the recompiler on the SDK sample programs, it found roughly two thirds of the functions on its own and about 97% with the IDA data.
  • sdk_symbols.json names the PS2 SDK functions linked into the game, found by signature matching. manual_symbols.json is the handful the matcher couldn't place.
  • hooks.json hangs native handlers off a few of the game's own functions (scene changes, file opens, sound loading, the radio queue). They run first, then the original code carries on as normal.

If you want to actually read the output, add --comments. Every line then gets the original MIPS instruction written next to it. The files come out about half again as big, but the code is exactly the same.

Step 4: build

cmake -S . -B build/gcc -G Ninja -DCMAKE_BUILD_TYPE=Release `
    -DCMAKE_C_COMPILER=C:/msys64/ucrt64/bin/gcc.exe `
    -DCMAKE_CXX_COMPILER=C:/msys64/ucrt64/bin/c++.exe
cmake --build build/gcc

The shaders get compiled into a shaders folder right next to ac5.exe, and that's where the game looks for them. If you ever move the exe somewhere else, take that folder with it.

Give it some time. Every generated file is basically one gigantic function and GCC takes its time with them. It takes me 3 to 5 minutes on a 12-thread CPU. There shouldn't be any warnings.

When it's done you'll have:

  • build/gcc/ac5.exe: the game
  • build/gcc/shaders/: the compiled shaders
  • build/gcc/gsreplay.exe: a dev tool that replays graphics captures, not needed to play

Step 5: play

Still in the repo root:

.\build\gcc\ac5.exe --data generated --disc "C:\path\to\Ace Combat 5 - The Unsung War (USA) (En,Ja).iso" --watchdog 0
  • --data is the folder that has ps2_image.bin in it, so generated.
  • --disc takes the ISO or a folder with the extracted disc.
  • --watchdog 0 switches the watchdog off. Normally the runtime gives up if the game hasn't delivered a frame in 10 seconds. That's useful when something's hung during debugging, not so much when you're playing.

The window stays black for about 20 seconds before the first picture. That's normal, give it a moment. The log goes to stderr. In PowerShell 7 you can save it by sticking 2> ac5_log.txt on the end. The older Windows PowerShell 5.1 mangles stderr when you redirect it like that, so if that's what you have, run the same command from cmd instead. --verbose makes it log a lot more (and it really is a lot).

If you start ac5.exe from anywhere other than that terminal (double-clicking it in Explorer, say), Windows won't be able to find the SDL3 and pthread DLLs. Copy SDL3.dll and libwinpthread-1.dll from C:\msys64\ucrt64\bin into build\gcc, next to ac5.exe, and it'll start from anywhere. vulkan-1.dll already comes with your graphics driver. Keep in mind it still needs --data and --disc, so a shortcut with those arguments filled in is the easiest way to launch it outside a terminal.

Where your stuff goes:

  • Saves are written to a saves folder inside whatever folder you started the game from. The memory card files get created the first time the game touches them. Set PS2_SAVE_DIR if you want them somewhere else. These are this runtime's own format, not PCSX2 memory cards, so you can't bring saves over from an emulator.
  • Settings are saved to ac5_settings.ini next to ac5.exe.
  • Compiled graphics pipelines are cached in ac5_pipelines.cache and ac5_pipelines.keys next to ac5.exe, so a state that was compiled once doesn't cause a stutter the next time it shows up. Deleting them is harmless, they just get rebuilt.
Controls

Controllers go through SDL, so anything SDL recognizes as a gamepad should just work. Buttons map by position: the bottom face button is Cross, right is Circle, left is Square, top is Triangle. Bumpers are L1/R1, triggers are L2/R2, clicking the sticks gives L3/R3, and Back and Start are Select and Start.

Keyboard defaults:

PS2Keyboard
D-padArrow keys
Cross, Circle, Square, TriangleX, S, Z, A
L1, R1Q, E
L2, R21, 3
Left stickNumpad 8, 4, 2, 6 (W also works for up)
Start, SelectEnter, Right Shift

You can rebind all of it in the settings menu.

Other keys:

  • F4 opens and closes the settings menu
  • F11 or Alt+Enter toggles fullscreen
  • Esc quits, or closes the settings menu if it's open
  • F6 to F10 are debugging hotkeys (captures and state dumps), you can ignore them

Mods

Mods go in a mods folder in whatever folder you start the game from, one folder per mod. Nothing gets repacked or rebuilt. The game reads its files through a layered file system, and a mod is just another layer on top of the disc. That works the same with the ISO and with extracted disc files.

A mod can:

  • replace files, including files inside DATA.PAC, by dropping them in its files/ folder under the name the game uses for them
  • change the game's named tuning values with a params.txt
  • apply PCSX2 .pnach patches that change data (code patches can't work on recompiled code, and get refused with a message)
  • run Lua scripts that hook the game's functions, read and write its memory, react to frames and scene changes, and rewrite the controller input
  • run native code from a DLL, but only if its mod.toml says native = true

mods/README.md explains all of it. The Lua API is runtime/include/ac5mod.h, and that header stays documented on purpose.

To name files inside DATA.PAC you need config/pac_names.txt, which is already in the repo. If you want to rebuild it, python -m modkit.export_names generates it from datapack.bin in the PS4 release. python -m modkit extract pulls files out of the archive under the same names, so you have something to start from. Both need PYTHONPATH pointing at tools.

If the game misbehaves, set PS2_NO_MODS=1 first. That turns the whole mod layer off, and if the problem is still there, it isn't a mod. PS2_MOD_DIR loads mods from a different folder. The log ends with a summary of every mod, conflict, hook and patch.

tests/mods has the mods I test the mod layer with. python tools/make_test_mods.py adds the ones that need files from your own disc, because those can't be in the repo.

Optional: recompile the VU1 microprograms as well

The PS2's VU1 runs small vector programs that the game uploads to it while it's running. They aren't in the executable as code, so ps2recomp never sees them. The ones the native renderer has replaced never run at all, and by default the runtime interprets the rest. You can record the ones the game really uses and compile those to C too, which is faster for whatever the native renderer doesn't cover yet.

  1. Record them. Set the census variable and play like normal:

    $env:PS2_VU_CENSUS = "1"
    .\build\gcc\ac5.exe --data generated --disc "C:\path\to\game.iso" --watchdog 0
    

    Get through the menus and into a mission, because some of the programs only get uploaded once you're actually flying. A short run that never leaves the menus only records the menu programs. Quit with Esc when you're done, and on the way out it writes out\vu_programs\ (a manifest.json plus a .bin for each program).

  2. Turn them into C. PYTHONPATH still needs to point at tools for this:

    Remove-Item Env:PS2_VU_CENSUS
    python -m vurecomp --programs out/vu_programs -o generated/ps2_vu1_progs.inc
    

    python -m vurecomp --programs out/vu_programs --stats shows what got recorded, if you're curious.

  3. Rebuild. There's a catch here: Ninja won't notice the new file on its own, because ps2_vu.c only includes it if it exists and nothing depended on it before. A plain cmake --build just says "no work to do". Touch ps2_vu.c first so it gets recompiled:

    (Get-Item runtime\src\ps2_vu.c).LastWriteTime = Get-Date
    cmake --build build/gcc
    

Next time the game draws something with the VU1, the log should get a line like vu1: 15 recompiled microprograms available (15 is what a census through a full mission gave me, yours depends on how far you played). If you ever want to compare against the interpreter, set PS2_VU_RECOMP=0.

Regenerating the config files

You don't need to. Everything in config/ is already generated and committed, so you don't need IDA or the PS2 SDK to build. If you want to redo them anyway:

  • tools/ida/export_db.py and tools/ida/export_seeds.py are IDA scripts that produce ida_db.json and ida_seeds.json.
  • tools/identify_sdk.py rebuilds sdk_symbols.json by matching the game against the SDK's EE libraries. Point PS2SDK_DIR at your SDK and AC5_DISC at the extracted disc folder first.

What's in here

  • tools/ps2recomp/: the recompiler (ELF in, C out)
  • tools/vurecomp/: the VU1 microprogram recompiler
  • tools/: test and check scripts
  • runtime/: everything that stands in for the console, plus the settings menu and the mod layer
  • runtime/src/rn/: the native renderer
  • runtime/shaders/: the GLSL shaders, compiled at build time
  • config/: the IDA export, symbol tables, overrides and hooks
  • third_party/imgui/: Dear ImGui, used for the settings menu
  • third_party/lua/: Lua 5.4.9, used for mod scripts
  • tools/modkit/: reads DATA.PAC by file name
  • mods/: where mods go, see mods/README.md
  • tests/mods/: the mods the mod layer is tested with
  • generated/: the recompiler's output, which you create in step 3

Troubleshooting

  • CMake says Cannot find source file: .../generated/ps2_func_table.c. You haven't done step 3 yet, or the output went somewhere other than generated.
  • CMake can't find Vulkan, sdl3 or glslc. Either the PATH line from step 2 didn't run in that window, the MSYS2 packages or the Vulkan SDK aren't installed, or the terminal was already open when you installed the SDK.
  • CMake warns that the compiler has no musttail attribute. Your GCC is too old, you need 15 or newer.
  • ac5.exe won't start and complains about a missing DLL. See the DLL note in step 5.
  • The log says vk: cannot open shader. The shaders folder isn't next to ac5.exe anymore. Put it back, or set PS2_SHADER_DIR to wherever the .spv files are.
  • The game quits by itself with ==== WATCHDOG: the guest delivered no field for 10 seconds. You left out --watchdog 0.

Legal

Ace Combat is a trademark of Bandai Namco Entertainment. This project isn't affiliated with or endorsed by them in any way. No game files are included, and I won't share any, so please don't ask.

The code in this repo is released under the Apache License 2.0, see LICENSE. Dear ImGui is MIT licensed and keeps its own license in third_party/imgui/LICENSE.txt. Lua is MIT licensed too, see third_party/lua/LICENSE.html.