A from-scratch matching decompilation of 東方紅魔郷 ~ the Embodiment of Scarlet Devil (Touhou 6), built and verified on Linux through Wine.
The goal is C++ source that compiles, with the original toolchain, into a binary that is byte-for-byte identical to the original executable.
This repository contains no game code or data. You need your own copy of
the game, see orig/README.md.
This is an independent, clean-room implementation. It is written from the binary alone: no code, names, struct layouts or function lists are taken from other decompilations or ports of Touhou games. Names come from our own analysis or, for library code, from the libraries themselves.
All 511 of the game's functions are decompiled and match the original,
all 241,285 bytes. The standalone exe (just exe) is the whole game,
built from source, and byte-for-byte identical to the original:
just exe && sha1sum build/th06.exe
# e61b4f4fea3802e926ef307f45166599c3e86555, as in orig/th06_102h.sha1Only the link timestamp is copied from the original, and the
resources, the game's data, are taken from it; the code, data, imports,
headers and Rich header come out of the build.
just check-exe builds and enforces whole-file equality;
just compare-exe gives the detailed report. See
executable parity for how it matches and the
exe's optional features.
just replay uses the shared replay harness (tools/replay/) with
th06's format and tracer to compare complete playback runs. See
replay parity for setup, the 17-recording world
record corpus and validation limits. Replay checks require natural
completion and recorded stage scores as well as exact original/rebuild
trace equality. All 17 recordings pass, and their traces match both
original runs.
Ghidra finds 1383 functions. The game code is compiled without
optimization, so its functions start with push ebp; mov ebp, esp; the
statically linked D3DX8 and CRT code is optimized. That puts the game
code at 0x401000–0x43d158, with the archive code last. Ghidra's
function ID recognizes the CRT from about 0x45b000 on.
just analyze # writes build/functions_102h.csvjust progress renders it: every game function, by address and as a
treemap by size, coloured by whether it is decompiled and matching.
just hooks makes every commit redraw it and include it.
Retail 1.02h, released 2002-12-01:
- 32-bit PE, image base
0x400000, entry point0x45d746,.textis 0x68a5f bytes .datais 0x2719fc bytes in memory but only 0x6000 on disk, so most of it is zero-initialized globals- Imports
d3d8,DSOUND,DINPUT8,WINMM,GDI32,USER32andKERNEL32. OnlyDirect3DCreate8is imported from D3D, so D3DX8 is statically linked.
Read from the Rich header (tools/peinfo.py):
| Tool | Build | Objects | Probably |
|---|---|---|---|
| C++ compiler 13.00 | 9466 | 48 | Game code |
| C compiler 13.00 | 9466 | 143 | MSVC CRT |
| MASM 7.00 | 9466 | 45 | MSVC CRT |
| C++ compiler 13.00 | 8830 | 67 | Static libraries, probably D3DX8 |
| Older linker objects | 8755 | 10 | Static libraries |
| Import library 7.00 | 9210 | 8 | Import libraries (DirectX 8 SDK, Vc7\PlatformSDK) |
| Linker 7.00 | 9466 |
This means Visual C++ .NET 2002 (MSVC 7.0, cl 13.00.9466) with the
DirectX 8 SDK. Only the roughly 48 C++ objects at build 9466 need to be
decompiled. The CRT and the SDK libraries are linked as-is.
The import libraries for user32, gdi32, winmm and kernel32 (and
uuid, which supplies GUID_NULL) are Visual Studio .NET 2002's own
(Vc7\PlatformSDK\Lib, import library build 9210 and C compiler build
9178 in the Rich header), not a later Platform SDK's: they decide the
import hints and the Rich header. They are not loose files on the CDs
but sit in a cabinet inside vs_setup.msi on disc 1. just psdk-libs ZIP extracts them from the install media (a zip holding
cd1/VSPROD1.iso) into C:\msvc7\PlatformSDK\Lib in the Wine prefix,
which the build searches before C:\psdk\Lib.
Everything runs headless, so it works over SSH. just lists the recipes.
- Analyze the binary with Ghidra in headless mode (
just analyze). New functions are added toconfig/symbols.csvwith placeholder names (Func43be50); CRT functions keep the names Ghidra's function ID gives them. - Write C++ for a function in
src/, in the style of 2002-era code.just decompile [ADDR...]writes Ghidra's pseudocode tobuild/decomp/<address>.cto read next to the disassembly (all functions take about 80 seconds). - Record the function and every original function and global it
uses in
config/symbols.csv: address, size, our name, and the mangled name, which the link error for an unresolved external gives. - Compare (
just compare): builds withcl.exeunder Wine and checks each function against the original byte for byte. Calls and global addresses are resolved throughsymbols.csvand compared too; only references to unnamed data such as string literals are masked.just compare SYMBOLshows a disassembly diff. - Play (
just run): runs the original game with the new code hooked in, headless under Xvfb, to check that the code behaves correctly as well as matching. Steps drive it and take screenshots, e.g.just run wait:12 key:Escape key:z wait:2 shot:menuwritesbuild/shots/menu.png. - Standalone (
just exe): links the same objects, without the hook DLL, intobuild/th06.exe, a game that needs nothing from the original executable. It links the real D3DX8, the static CRT and the SDK import libraries, and carries the original's icon and header fields.just exe STEPSruns it likejust run.docs/exe-parity.mdhas how it matches and its optional features (FEAT=).
The decompiled code runs inside the original game. tools/patch_exe.py adds
one import, th06.dll, to a copy of the executable. The DLL is
built from the same objects that just compare checks. On load it
overwrites each decompiled function in the original with a jump to the
new code.
Calls from new code to functions that are not decompiled yet go through
generated thunks (push address; ret) to the original. Globals are
absolute symbols at their original addresses. Both are generated from
symbols.csv. The DLL has no C runtime of its own: CRT functions belong
in symbols.csv too, so the game and the new code share one heap.
orig/ your game files (not tracked) and their checksums
src/ decompiled source; src/dll/ is the hook DLL's own code,
src/exe/ the standalone exe's optional features' code
config/ symbols.csv: address, size, name and mangled symbol
tools/ the shared decomp-tools: build, compare, exe patcher, symbol
seeding, Ghidra scripts, the replay harness and the just recipes
project.toml th06's settings for the tools: exe, flags, link order
scripts/ th06's own: the English patch, its build hook, the DAT
archive format, the replay hook, tracer and tests
replays/ world record replays for just replay, their manifest and README
docs/ progress image, executable and replay parity
build/ outputs; build/game/ is a runnable copy of the patched game
tools/ is the decomp-tools repository, shared with the other games, as
a git submodule: clone with --recursive, or run
git submodule update --init tools after cloning, and just tools-init
to put it on its main branch. A change to the tools is a commit made in
tools/ and sent with just tools-push; just tools-pull brings in the
other games' changes. Either way, commit the new tools pointer here.
- Wine (32-bit, with its
msidb), Python 3, just, binutils (objdump), 7-Zip - For
just runandjust replay: Xvfb, xdotool, ImageMagick, 32-bit Mesa OpenGL (libgl1-mesa-dri:i386,libglx-mesa0:i386) and theja_JP.UTF-8locale, without which the game can't find its Shift-JIS named data files - Visual C++ .NET 2002, the Windows XP SP2 Platform SDK and the
DirectX 8.0 SDK in the Wine prefix at
.wine/(bring your own copies) - Ghidra (for analysis, optional)
Touhou Project and 東方紅魔郷 are by ZUN / Team Shanghai Alice. This is an unofficial fan project. It contains no assets or code from the original game, and it is not affiliated with or endorsed by ZUN or Team Shanghai Alice.
The source in this repository is licensed under the GNU General Public
License, version 3 only (GPL-3.0-only), see LICENSE. The
license covers our code, not the original game or its data.