A decompilation of the Metrowerks CodeWarrior toolchain. Currently supported versions:
| Compiler | Version | Internal version | Exact functions | Linked functions | Linked bytes | Notes |
|---|---|---|---|---|---|---|
| GameCube 2.0 | GC_2_0 |
2.4.7 build 92 | 4016 / 4065 | 2850 / 4068 (70.1%) | 54.3% | |
| GameCube 2.0p2 | GC_2_0p2 |
2.4.7 build 92 | 4019 / 4068 | 2853 / 4071 (70.1%) | 54.3% | GameCube 2.0 patched for BFBB |
| GameCube 2.5 | GC_2_5 |
2.4.7 build 105 | 4026 / 4076 | 2860 / 4079 (70.1%) | 54.3% | |
| GameCube 2.6 | GC_2_6 |
2.4.7 build 107 | 4027 / 4077 | 2860 / 4080 (70.1%) | 54.4% | |
| GameCube 2.7 | GC_2_7 |
2.4.7 build 108 | 4033 / 4084 | 2848 / 4087 (69.7%) | 54.2% |
-
Exact: the compiled function has identical assembly to the original binary.
-
Linked: the functions whose translation unit links identically to the original executable.
-
Every function that isn't currently exact has been proven equivalent (see:
tools/prove.py), compiles from source alone, and has been checked against a number of decompilations to make sure it produces identical assembly to the original binary. -
Currently only the C/C++ compiler has been decompiled (
mwcceppc.exe). See future work for more information. -
2.0p2 is not an official binary; it models the observed side effects of compiling the Battle for Bikini Bottom decompilation against GameCube 2.0 since it seems to have been compiled with a unique patched compiler. See below for more information.
- Linux (x86-64, or arm64 for the clang build alone), macOS or Windows.
- Python 3.11 or newer, with uv installed.
- The ninja build system.
- LLVM 16 or newer:
clang,lld-link,llvm-rcandllvm-dlltool(Debian and Ubuntu:clang,lldandllvm).
Everything else (the original compilers, library code used straight from CodeWarrior discs, and various tooling) are downloaded automatically and verified against a pinned SHA-256.
uv sync
source .venv/bin/activate
python configure.py
ninjaBy default, this builds every supported compiler with clang under build/VERSION/clang/mwcceppc.exe, as well as
compile_commands.json for LSP support. This should be a drop-in replacement to the original compiler and can be used
the same as before. It also has support for UTF-8 -> Shift-JIS support by setting MWCC_SJIS=1 in your environment,
superceding the need for sjiswrap.
configure.py --version GC_2_6 configures only the specified versions to be built using clang.
configure.py --mwcc sets up the original matching workflow instead, and ninja then builds every supported compiler
and verifies it matches the original binary. It builds build/VERSION/source-only/mwcceppc.exe from just the source
code, which may be equivalent but may be non-matching.
configure.py --matching (implies --mwcc) builds build/VERSION/mwcceppc.exe using a combination of matched/linked
TUs and original objects; 1:1 to the original compiler. It also builds mwcceppc-dwarf.exe which has DWARF 4 metadata
embedded, plus mwcceppc-debug.exe + mwcceppc-debug.pdb which contains debug information, either of which can be used
in IDA Pro, Ghidra, or Binary Ninja.
Every tool in tools/ runs with the venv's python and explains itself with --help.
status.py VERSION --todo: the units nearest to linking, and what blocks each.fn.py: a function's original code, its diff against the compiled code, and alternative definitions tried in parallel.unit.py,headers.py,includes.py: move functions, declarations and includes to where they belong.rename.py: rename anything everywhere: code, configuration and symbols.evidence.py: what each function is called, from the original's asserts and the Wii compilers' symbols.verify.pyandregressions.py: check every version, and what stopped matching since your saved baseline.layout.py,relink.py: lay out the original's symbols, and see which sources link into it.prove.py: prove an inexact function equivalent to the original's.equivalence.py: compile a decompilation project's sources with two compilers and compare the objects.clang_check.py,truefalse.py,update_assert_lines.py: source checks and fix-ups.bindings.py,validate.py,compilers.py,crosscheck.py,port_version.py,gen_options.py: the configuration, and how the original was built.
The majority of the source code matches the original compiler, which defines what bugs exist when compiling code. All
support needed to build equivalent objects with a modern clang compiler has been wrapped inside the #else version of
#ifdef __MWERKS__. Some of the quirks that had to be accounted for (includes compiler flags) include:
- Floating point. The original computes on the x87 at 64-bit precision;
include/x87.hemulates it in integer arithmetic, so results never depend on the host. - Undefined behaviour. Signed and pointer arithmetic wrap, aliasing and null checks are not assumed, and locals read before being set read as zero.
- The original's own memory. Where the original's output depends on its addresses, on data past the end of its tables or on stale stack contents (the bugs below), the clang build reproduces the original's values.
These are some of the known bugs of the original Metrowerks compilers (clang reproduces these as best it can):
- Code generation
- A
boolconversion is dropped when a pointer's target type sits at an address ending in00(short double*in 2.5 to 2.7). - A load through a pointer to const is reused across stores through other pointers, and in 2.5 and later hoisted out
of loops:
while (*p > 0) { (*q)--; }never ends whenp == q. - Peephole's
L_Sdecides whether to delete a store from two stack words it never set. - The 7450, 601 and 821 scheduling models read past the end of their tables.
- A float literal with a decimal exponent past about ±384 reads past MSL's table:
1e401is 0. - A source file name of 64 or 65 characters overruns its buffer, and can change the code.
- A
- Hangs and crashes
#pragma scheduling 821with two FP-unit instructions,mflramong them, never finishes.- Very long
-oorprecompile_targetnames, andstruct ::Einside a class template, crash the compiler.
- Not reproducible, because they vary with the host:
- two padding bytes in some C++ exception tables (decomp-toolkit's
extab_paddingsets them); - a stack address in a local class's mangled name;
- the bytes of precompiled headers;
- the x87's starting precision on Windows (53 bits, against 64 under wibo and in the clang build).
- two padding bytes in some C++ exception tables (decomp-toolkit's
- Where the clang build differs: it keeps going where the original crashes, and does not follow a
#pragma schedulingthat switches models within one file.
Battle for Bikini Bottom's retail code was built with a 2.0 that no release matches. Its decompilations have used
binary patches of 2.0 (2.0p1, and bfbb-vibe's 2.0p1g). 2.0p2 is the same idea done using the decompiled source code:
2.0 with rules of its own, each one measured on BFBB's code. Functions matching retail:
| Compiler | bfbb (of 7673) | bfbb-vibe (of 7673) | Ratatouille's shared code (of 321) |
|---|---|---|---|
| 2.0p1 | 5474 | 6994 | 208 |
| 2.0p1g | 5724 | 7579 | 217 |
| 2.0p2 | 5731 | 7594 | 217 |
A GameCube prototype of Ratatouille using the same engine was used as an independent check to deduce the required behavior by compiling it using BFBB's settings.
Here is everything that got changed from 2.0, all aimed at globals addressed directly in small data (sym@sda21), which
BFBB's developers seemingly required a custom patch to the compiler for to treat more conservatively:
- floating-point literals are not const, and stores may change them;
- a store to a small-data global may change any memory a pointer reaches;
- the scheduler orders loads from small data like loads through a pointer;
- a store to a global array's element forgets the small-data globals;
- a load of a small-data global stays inside its loop;
- two of 2.5's alias analysis changes.
The "stays inside its loop" rule also covers const globals, and only one function needs that: zParPTankSpawnSnow,
whose snow_life is declared const in the bfbb decompilation but sits in writable data in the retail game, and so is
probably not const at all. With that fixed, the rule may not need to cover const globals; that is for BFBB decomp to
figure out.
To use 2.0p2 in a BFBB decompilation, put build/GC_2_0p2/clang/mwcceppc.exe in place of the project's 2.0p1
compiler.
Comparing against the retail game also shows a few places where bfbb's source differs from what the original must have been:
x--; if (x == 0)should beif (--x == 0)inxSGShutdownand ten other functions;snow_lifeshould not beconst;NPAR_TubeSpiralMagic's colours should bestatic constlocals.
- Format C with
clang-format -i, and Python withruff formatandruff check(the venv's pinned versions); CI checks both and builds every version with clang. - Braces on every
if,else,for,whileanddobody; flags areTRUEandFALSE. - After editing a header, run
tools/verify.py; MWCC's code can change with unrelated declarations. - Put clang-only code under
#ifdef __MWERKS__ ... #else ... #endif. - Rerun
tools/prove.pyon any inexact function you change.
In scope, with no promise of when:
- the other GameCube compilers: 1.0 to 1.3.2 (decompiled separately, to be rewritten and merged) and the 3.0 alphas;
- the Wii compilers, whose public symbols give real names from the start;
- the linker and assembler;
- CodeWarrior's other platforms, such as Nintendo DS and PlayStation 2.
- Ninji's decompilation of the CodeWarrior Pro 7 Mac OS X compiler (front end 2.4.5) was used as the basis for the majority of the code here, including names and structure, reworked for what the 2.X compilers actually do.
- rayanht/mwcc (GC 1.X) and
henriquegemignani/mwcc (
build-1.3) were used as a reference for what earlier GameCube compilers were doing and shaped the overall repo. - decomp.dev's compiler archive, wibo, objdiff, UnshieldSharp and the Internet Archive's CodeWarrior collection.
This decompilation is dedicated to the public domain (CC0, LICENSE).