A from-scratch matching decompilation of 東方永夜抄 ~ Imperishable Night (Touhou 8), 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 1116 game functions are decompiled and match the original byte for
byte (just compare: 1130/1130, counting separately emitted inline
copies and folded symbols).
The standalone executable (just exe) is built from this source alone,
boots and plays, and is byte for byte the original: build/th08.exe has the original's SHA-1,
2044fea52667be4f61bcd78efc8f7d63fbbc244f. That includes every
section, the import tables, the PE headers and the Rich header, which
counts the original's 63 C++ objects because the source is split into
63 objects. Where th08's bytes don't show a seam, the splits follow
th06's and th07's objects for the same code and data, and three have
neither (each new file's first lines say which). All 136 named writable
globals sit at their original addresses too, including function statics
absent from the public link map (python3 scripts/check_exe_layout.py).
Only 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.
Getting there took the original's link inputs as well as its code. The
build reconstructs game-function and readonly-data placement with the
linker's /ORDER option, generated from symbols.csv, exe_constants.csv
and the compiled objects; this does not establish that the original
build used /ORDER. The C runtime's objects and the import objects load
in the original's order, and /OPT:ICF keeps the original's copy of
every identical function, because the objects mention, and give the
linker, what the original's did, including code /OPT:REF dropped:
exe-only Unused* functions stand in for that dropped code, and a few
functions sit in a different order in their files. The original's
precompiled-header layout remains unknown.
config/exe_constants.csv records 1143 compiler constant/string COMDAT
addresses recovered through the original operands and our object
relocations. After a vanilla build, python3 scripts/recover_exe_constants.py
regenerates it; scripts/check_exe_layout.py verifies every recorded
address. All recorded game readonly constants and initialized globals
are identical, including the FileBase vtable's CRT __purecall pointers.
just check-exe builds and enforces whole-file equality;
just compare-exe gives the detailed report. See
executable parity for the reconstruction details
and the earlier checkpoints.
just replay uses the shared replay harness (tools/replay/, from
th06) with th08's format and tracer to compare complete playback runs.
See replay parity for setup, the 59-recording world
record corpus, saved-trace audits and validation limits. Replay checks
require natural completion and recorded stage scores as well as exact
original/rebuild trace equality. All 59 recordings pass, and their
traces match both original runs.
Ghidra finds 2225 functions. As in th06 and th07, the game code is mostly
compiled without optimization, so its functions start with push ebp; mov ebp, esp, with a few objects optimized for size; the statically
linked D3DX8 and CRT code is optimized. That puts the game code at
0x402000–0x475298: 1116 functions, about 460 KB. D3DX8's tables
follow it, then the DirectX import thunks, D3DX8 and the CRT.
just analyze # writes build/functions_100d.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.00d, released 2004-09-19, the last official update:
- 32-bit PE, image base
0x400000, entry point0x4a619e,.textis 0xb1b78 bytes and starts at0x402000, a page later than th06 and th07 .datais 0x1413840 bytes in memory but only 0x6e00 on disk, so most of it is zero-initialized globals. A.data1section of 0x8e0 bytes follows it.- Imports
d3d8,DSOUND,DINPUT8,WINMM,GDI32,USER32,KERNEL32,ADVAPI32(registry reads) andole32. 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 | 63 | Game code |
| C compiler 13.00 | 9466 | 130 | MSVC CRT |
| MASM 7.00 | 9466 | 48 | MSVC CRT |
| C++ compiler 13.00 | 9177 | 69 | DirectX libraries, mostly D3DX8 |
| C compiler 13.00 | 9177, 9178 | 3, 3 | Static libraries |
| Import library 7.00 | 9210 | 12 | Platform SDK import libraries |
| Import library | 9111 | 7 | DirectX import libraries |
| Linker 7.00 | 9466 |
This is the th06 and th07 toolchain: Visual C++ .NET 2002 (MSVC 7.0,
cl 13.00.9466). Unlike th07, no object was compiled with /GL. The
game code still includes DirectX 8.0's headers, but the DirectX
libraries are not a DirectX SDK's: they are the ones Visual Studio .NET
2002 itself installs in Vc7\PlatformSDK\lib\prerelease. Their
d3dx8.lib objects say 9177 and their d3d8, dinput8 and dsound import
libraries 9111, which no DirectX 8.1 SDK provides (9178 and 9210), and
D3DX8's Intel-compiled objects bring the compiler banners that fill the
original's 0x1e00 bytes of headers. They are not loose files on the
media: they come with the merge module zDxF_pre_b.common.msm in
disc 1's vs_setup.msi, which tools/msi_extract.py unpacks as
tools/psdk_libs.py does for the Platform SDK. The standalone exe links
them from C:\msvc7\PlatformSDK\Lib\prerelease in the Wine prefix.
The compiler flags start as th07's and change as functions show
otherwise.
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); library functions keep the names Ghidra's function ID gives them. - Write C++ for a function in
src/, in the style of 2004-era code.just decompile [ADDR...]writes Ghidra's pseudocode tobuild/decomp/<address>.cto read next to the disassembly. - 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. The patched executable is not the original, so the game's own check of it quits the game after about a minute, and rejects thescore.datrecords and replays a patched game wrote:FEAT=orig_checksum just runmakes the DLL claim the original's checksum. - Standalone (
just exe): links the same objects, without the hook DLL, intobuild/th08.exe, a game that needs nothing from the original executable. The game's data and global objects are defined in the files that own them, underTH08_EXE; the DLL uses the original's instead.just exe STEPSruns it likejust run, andjust compare-execompares it with the original.docs/exe-parity.mdhas how it matches.
The decompiled code runs inside the original game. tools/patch_exe.py adds
one import, th08.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
config/ symbols.csv: address, size, name and mangled symbol;
exe_constants.csv: the readonly constants' addresses
tools/ the shared decomp-tools: build, compare, exe patcher, symbol
seeding, Ghidra scripts, the replay harness and the just recipes
project.toml th08's settings for the tools: exe, flags, link order
scripts/ th08's own: its build hook, the exe constants' recovery and
layout check, the replay hook, tracer, fixtures, audit 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 - 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.