No description
  • C++ 80.8%
  • Python 17.8%
  • CMake 0.6%
  • Shell 0.6%
  • C 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-11 00:51:38 +09:00
config Emulate the XMA decoder (sound effects); fix creator tab jump table 2026-10-10 10:41:28 +09:00
docs Annotate fused multiply-adds with the exact double-then-round expression 2026-10-10 22:17:15 +09:00
runtime Keep lwarx/stwcx. updates atomic: helpers, prompt rule, lint 2026-10-10 21:57:46 +09:00
tools chat: on 429 retry at the server's Retry-After pace instead of escalating backoff 2026-10-11 00:51:38 +09:00
.gitignore Decomp tooling: coverage tracer, fork-diff verifier, model driver loop 2026-10-10 12:33:13 +09:00
CMakeLists.txt Readable-code framework: separate repo, registry generator, polish pass 2026-10-10 13:46:22 +09:00
LICENSE Setup guide, licenses and preservation roadmap; drop private xexpatch step 2026-10-10 11:06:32 +09:00
README.md Readable-code framework: separate repo, registry generator, polish pass 2026-10-10 13:46:22 +09:00
run-gdb.sh TerrariaRecomp: playable native port of Xbox 360 Terraria 1.09 2026-10-10 00:32:43 +09:00
run.sh TerrariaRecomp: playable native port of Xbox 360 Terraria 1.09 2026-10-10 00:32:43 +09:00
shell.nix Emulate the XMA decoder (sound effects); fix creator tab jump table 2026-10-10 10:41:28 +09:00
XenonRecomp-terraria.patch TerrariaRecomp: playable native port of Xbox 360 Terraria 1.09 2026-10-10 00:32:43 +09:00

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.nix provides clang, cmake, ninja, SDL2 (sdl2-compat), epoxy, xxhash, fmt, FFmpeg and the GL loader). Other distros work if you install the same packages; drop the nix-shell wrappers 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):

  1. The base game (the XBLA package). Extracted, it contains default.xex, game.exe.xex, HostLoader.dll, Content/, Runtime/, ArcadeInfo.xml and the achievement/dashboard PNGs.
  2. The title update for version 1.09 (TU 1.11.0.0). Extracted, it contains default.xexp, game.exe.xexp, HostLoader.dllp and 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

  1. Copy the executables:
    mkdir -p private
    cp <base_game_dir>/default.xex private/
    cp <tu_dir>/default.xexp private/
    
  2. Build the content tree (symlinks; your extracted folders must stay where they are): tools/setup_game.sh <base_game_dir> <tu_dir>
  3. Build XenonRecomp with our patch, then generate ppc/. The patch is made against XenonRecomp commit ddd128b:
    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 again
    
    regen.sh should end with no "outside function" errors. It takes a few minutes.
  4. 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'
    
  5. 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 to gdb.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 to run.log and screenshots to build/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 with tools/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 FileInformationClass argument; 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_mips set) keep their base level at block (16,0) or (0,16) of a shared 32x32 tile. Missing that made the 1x1 white.xpr read 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):

  • XMACreateContext hands out slots of a 320-context array in physical memory. Its address is published in MMIO 0x7FEA1800 (reg 0x600, read with lwbrx at 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's xma2 decoder, 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 of o1heap.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.