xenolithppc64be → c

static recompiler · linux only · clean room

Xbox 360 machine code
becomes C you can read.

xenolith reads a XEX container, decodes the PowerPC inside it, works out where the functions are, and writes C. It takes no per title configuration at all. Everything another tool would ask you to write by hand is recovered, or reported as unrecovered.

powerpcc
0x82090000mfspr r12, r8, r0ctx->r[12] = ctx->lr;
0x82090004stw r12, -8(r1)xenolith_store32(base, address, ...);
0x82090010lis r11, -32251ctx->r[11] = (uint64_t)(int64_t)(...);
0x8209001crlwinm r10, r4, 0, 31, 31ctx->r[10] = (uint32_t)ctx->r[4] & 1u;
0x82090020cmpli cr6, 0, r10, 0ctx->cr[6].eq = ... == (0ull);
0x82090028bc 12, 26 0x82090034if (ctx->cr[6].eq) { goto loc_82090034; }
  • 440instructions decoded
  • 39,350functions discovered
  • 98.0%lifted on the larger title
  • 0lines of per title configuration

What it does today

It turns a retail game into C that compiles. It does not yet turn one into a game you can play, and the gap between those two things is worth being precise about.

The emitted C is written against a runtime interface this project declares and does not implement. No guest memory is mapped, no import does anything, no threads exist. Around 1,460 addresses are declared without a definition, mostly functions that could not be lifted plus the register save and restore helpers. So it compiles, and it does not link.

Measured against two retail titles:

title Atitle B
functions discovered27,44711,903
executable words claimed95.3%86.9%
functions lifted to C26,908 (98.0%)11,195 (94.1%)
of those, import thunks156187
emitted C compilesyes, all of ityes, all of it

The point of it

An existing tool does this job and requires, per game, a hand written file naming every function boundary, the addresses of eight register helpers, setjmp and longjmp, and byte patterns to skip. Its companion emits a separate file of jump tables, which for one title runs to 852 entries across 30,250 lines.

This project accepts no per title configuration at all. Everything above is recovered, or reported as unrecovered. Nothing is guessed.

  • All eight register helper addresses are detected on both titles without being given any of them, matched against values two other projects recorded by hand.
  • 803 of the 852 jump tables are recovered and agree exactly with what that tool produced, with zero disagreements.
  • Every import record is read from the image and reported as a library and an ordinal.
  • An instruction the decoder does not recognize reports itself unknown, and a function holding an instruction the model cannot express is not emitted at all.

Nothing here is trusted because it looks right

Every layer is compared against something produced independently: the decoder against llvm-mc and GNU objdump, the analysis against jump tables worked out by hand elsewhere, the instruction model against 1.19 million instructions another project emitted, and the semantics against PowerPC hardware running the same instruction the emitted C does.

That last one found six real mistakes on its first run, including one where recording a result compared the low half of a register rather than the whole of it. How it is checked goes through each oracle and what only it can see.

Where to go next