- C++ 80.8%
- Python 17.8%
- CMake 0.6%
- Shell 0.6%
- C 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UXBFgjXLuQfa9yhgeusSwP |
||
| config | ||
| docs | ||
| runtime | ||
| tools | ||
| .gitignore | ||
| CMakeLists.txt | ||
| LICENSE | ||
| README.md | ||
| run-gdb.sh | ||
| run.sh | ||
| shell.nix | ||
| XenonRecomp-terraria.patch | ||
TerrariaRecomp
A native Linux port of Xbox 360 Terraria 1.09 (final title update, TU projectVersion 1.11.0.0). It uses static recompilation with XenonRecomp: the game's PowerPC code becomes C++, and it runs on a small runtime that reimplements the Xbox kernel, XAM, Xenos GPU and XAudio.
From console 1.01 on, the game is native PowerPC code in default.xex; the XNA game.exe is only a stub. That's why TerrariaOGC's C# code can't run 1.09 content, and why this project recompiles the real game instead.
Status
Playable from boot to in-world at 60 fps. Saving and loading of characters, worlds and settings works.
| Area | State |
|---|---|
| CPU | All 13,250 guest functions recompiled. 157 jump tables found by tools/find_switch_tables.py. |
| Kernel / XAM | Threads, sync, memory, file I/O, save content (~/.local/share/terraria-recomp/content/), profile settings, message boxes (auto-answered with the default button). |
| GPU | PM4 command processor; Xenos ucode → GLSL 4.30; OpenGL 4.5 renderer; EDRAM render targets with MSAA, fast-clear aliasing and GPU-side resolves. |
| Input | SDL game controller plus keyboard (see below). |
| Audio | Music and sound effects (XMA decoded with FFmpeg); see the audio notes. |
| Missing | On-screen keyboard (renaming uses the default name). Online play. |
What's in this repository
Only code written for this project, plus open-source code under its own license (see Licenses). There is no game code or data here: no XEX, no recompiled ppc/ output (it's derived from the game binary), no textures, sounds or strings. config/ holds addresses inside the game binary (function bounds, jump tables), not its contents. You need your own legally obtained copy of the Xbox 360 game and its title update.
Requirements
- Linux with Nix (
shell.nixprovides clang, cmake, ninja, SDL2 (sdl2-compat), epoxy, xxhash, fmt, FFmpeg and the GL loader). Other distros work if you install the same packages; drop thenix-shellwrappers below. - An OpenGL 4.5 GPU. Tested on a Radeon 780M with Mesa radeonsi.
- Python 3 (jump-table tools).
- Your own copy of Terraria: Xbox 360 Edition (XBLA, title ID
5841128F) and its final title update (TU 1.11.0.0, which is game version 1.09).
Getting the game files
You need two things from your console or your own backups, extracted to plain folders with any STFS/XContent package extractor (e.g. Velocity, Horizon, wxPirs or xextool):
- The base game (the XBLA package). Extracted, it contains
default.xex,game.exe.xex,HostLoader.dll,Content/,Runtime/,ArcadeInfo.xmland the achievement/dashboard PNGs. - The title update for version 1.09 (TU
1.11.0.0). Extracted, it containsdefault.xexp,game.exe.xexp,HostLoader.dllpand the updated content:Images/,Sounds/,music/,Fonts/,UI/,strings/,shaders/,backgrounds/,esrb.xpr,white.xpr.
The XEX files must be unmodified retail files; XenonRecomp decrypts and patches them itself. Check them with sha1sum:
| File | SHA-1 |
|---|---|
default.xex (base game) |
8683f11631b9f2f042ded07810caffbc26f2976f |
default.xexp (TU 1.11.0.0) |
d5848155e2924b051912b6b6cc6d10e5fe414cb2 |
Expected layout once set up (private/ and game/ are git-ignored; never commit or share them):
TerrariaRecomp/
private/default.xex # from the base game
private/default.xexp # from the title update
private/default_patched.xex # written by XenonRecomp in step 3
game/ # symlink tree: base game overlaid with the TU (step 2)
../XenonRecomp/ # cloned next to this repo (step 3)
Setup
- Copy the executables:
mkdir -p private cp <base_game_dir>/default.xex private/ cp <tu_dir>/default.xexp private/ - Build the content tree (symlinks; your extracted folders must stay where they are):
tools/setup_game.sh <base_game_dir> <tu_dir> - Build XenonRecomp with our patch, then generate
ppc/. The patch is made against XenonRecomp commitddd128b:git clone --recursive https://github.com/hedge-dev/XenonRecomp ../XenonRecomp git -C ../XenonRecomp checkout ddd128b && git -C ../XenonRecomp submodule update --init --recursive git -C ../XenonRecomp apply ../TerrariaRecomp/XenonRecomp-terraria.patch nix shell nixpkgs#cmake nixpkgs#ninja nixpkgs#clang -c sh -c \ 'cmake -S ../XenonRecomp -B ../XenonRecomp/build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ && ninja -C ../XenonRecomp/build' tools/regen.sh # applies the TU, finds jump tables, recompiles, fixes function bounds, recompiles againregen.shshould end with no "outside function" errors. It takes a few minutes. - Build the runtime (the recompiled code is large; the first build takes a while):
env -u LD_LIBRARY_PATH nix-shell shell.nix --run \ 'cmake -S . -B build/cmake -G Ninja && ninja -C build/cmake TerrariaRecomp' - Run
./run.sh. Saves go to~/.local/share/terraria-recomp/content/.
Running
./run.sh: play../run-gdb.sh: play under gdb. A crash writes a backtrace togdb.log; a guest crash also dumps guest registers.tools/bench.sh <seconds> "<keys>": headless run on the real GPU (SDL offscreen/EGL, no window) with scripted input. It logs fps and hitches torun.logand screenshots tobuild/shots. Example that loads the first saved world and walks right:SHOT_EVERY=600 tools/bench.sh 90 "start@18 b@22 a@26 a@30 a@34 a@38 right@50:10"
env -u LD_LIBRARY_PATH matters on NixOS: a host LD_LIBRARY_PATH breaks nix's own tools.
Controls
A controller works directly. The keyboard maps to the pad:
| Key | Pad | Key | Pad |
|---|---|---|---|
| WASD | left stick | IJKL | right stick |
| Space | A | Q | B |
| E | X | R | Y |
| Z / C | LB / RB | Shift / Ctrl | LT / RT |
| Arrows | D-pad | Enter / Tab | Start / Back |
| F / G | L3 / R3 |
Debug environment variables
| Variable | Effect |
|---|---|
TR_AUTOKEYS="start@20 a@30 right@40:5" |
Scripted presses: name@seconds[:hold]. Names: start back a b x y lb rb, up down dleft dright (D-pad), rt (right trigger), and left/right for the left stick. |
TR_SCREENSHOT=dir[:N] |
Save the front buffer every N frames (PPM). |
TR_DRAW_SHOTS=dir |
Dump the render target after every draw (slow). |
TR_GPU_LOG=N |
Log every draw/resolve on every Nth frame. |
TR_DUMP_SHADERS=dir |
Dump Xenos shader binaries; build/cmake/shadertest translates them offline. |
TR_TRACE=1 |
Log every kernel/XAM import call with its first four arguments. |
TR_NO_AUDIO=1 |
Don't start the audio driver. |
The renderer always logs fps every 300 frames, and any frame over 25 ms (hitch) along with its slowest draw.
Layout
config/Terraria.toml: recompiler config (helper addresses, jump tables, function bounds).ppc/: generated C++, not committed (it's derived from the game binary). Regenerate withtools/regen.sh.docs/ROADMAP.md: plan for turning the recompiled code into readable source.runtime/kernel/: memory (4 GB guest space with physical mirrors), heaps, kernel objects, imports, XAM, file system, save content.cpu/: guest threads, indirect calls.gpu/: command processor (gpu.cpp), shader translator, texture cache, GL renderer.gpu/xenia/holds vendored Xenia headers (BSD).apu/: XAudio driver.hid/: input.
tools/: jump-table analysis (find_switch_tables.py,fix_switch_functions.py),regen.sh,setup_game.sh,bench.sh,shadertest/.XenonRecomp-terraria.patch: 24 extra PPC instruction forms, plus switch codegen fixes (switch on.u32, unknown cases fall back to an indirect call).
Implementation notes
Things that took a while to find:
- NtAllocateVirtualMemory must work in 64 KB granules, or the game's heap corrupts itself.
- NtQueryDirectoryFile on the 360 has no
FileInformationClassargument; the mask and RestartScan come one slot earlier than on Windows. An empty mask means "everything". - Save content (
XamContentCreateEx) is opened with OPEN_ALWAYS. Containers rediscovered on disk need a display name, or the game's save menu corrupts its heap. - D3D fast clears are depth-only rect draws whose depth buffer aliases the color buffer in EDRAM. The renderer turns them into color clears.
- Resolves stay on the GPU. The destination texture is registered by guest address and handed to later texture fetches; guest memory isn't written.
- Shader registers: indexing the GLSL register array with a runtime value (
r[u_paramGen]) makes AMD drivers spill it to scratch memory, which costs about 9 ms per full-screen quad. Keep indices constant. - Packed mip tails: textures whose short side is <= 16 texels (with
packed_mipsset) keep their base level at block (16,0) or (0,16) of a shared 32x32 tile. Missing that made the 1x1white.xprread as transparent, which hid the yellow mining-target highlight. - Vertex upload: only the vertices a draw indexes are uploaded. Fetch constants often span a whole dynamic ring buffer.
Audio
Sound effects (Sounds/*.xma) are XMA, decoded on the console by the hardware XMA decoder. runtime/apu/audio.cpp emulates it (layout as in Xenia's xma_decoder):
XMACreateContexthands out slots of a 320-context array in physical memory. Its address is published in MMIO0x7FEA1800(reg 0x600, read withlwbrxat 0x82516664); the game derives context indices from it.- The game keeps a 96-byte shadow per context and copies dirty parts to the hardware context before kicking it (
sub_82517050). We hook that function and decode right after the kick, synchronously: whole 2 KB input packets go through FFmpeg'sxma2decoder, and 16-bit big-endian PCM goes into the context's output ring of 256-byte blocks. Lock and clear registers are ignored. - An input buffer stays valid until its last samples are in the ring. The game stops syncing a voice once it sees no valid input, so clearing it early cuts the sound.
- Loop points aren't implemented.
Next steps
See docs/ROADMAP.md: turning the recompiled code into readable, tested C++ for preservation, one function at a time.
Licenses
GPLv3 (LICENSE). The runtime is adapted from UnleashedRecomp (GPLv3, runtime/COPYING.UnleashedRecomp). Vendored code keeps its own license:
runtime/gpu/xenia/: Xenia, BSD-3-Clause (runtime/gpu/xenia/LICENSE).runtime/thirdparty/o1heap/: o1heap, MIT (header ofo1heap.h).
Terraria is © Re-Logic; the Xbox 360 edition was developed by Engine Software and published by 505 Games. This project contains none of their code or data and isn't affiliated with them. Please buy the game.