# Daggerfall decompilation ![decompiled](https://img.shields.io/badge/decompiled-100.00%25-brightgreen) ![functions](https://img.shields.io/badge/functions-2297%20of%202297-brightgreen) ![FALL.EXE](https://img.shields.io/badge/FALL.EXE-matching-brightgreen) 100.00% of the game's own code (580076 of 580076 bytes, 2297 of 2297 functions) is matched C; the rebuilt `FALL.EXE` is byte-identical to 1.07.213. A matching decompilation of *The Elder Scrolls II: Daggerfall* (DOS, 1996), starting with the game executable `FALL.EXE` (version 1.07.213), built with Watcom C32 10.0a and run under the CauseWay DOS extender. The rebuild uses that same compiler, run under DOSBox-X. This repo contains no game files (executables or data) and no compiler. You supply the executables and your own copies of Watcom C/C++ 10.0a and, for the XnGine engine, Borland Turbo Assembler 4.0; the tools check the executables' SHA-1. - [docs/head_start.md](docs/head_start.md): the research plan this project started from - [docs/progress.md](docs/progress.md): a log of what has been found and done, newest last - [docs/xngine.md](docs/xngine.md): the XnGine engine (object 2): plan and log - [docs/xngine_readable.md](docs/xngine_readable.md): XnGine as readable C: how it runs, is declared and tested; [docs/engine/](docs/engine/): its data structures and the designs for its self-modifying code - [docs/play.md](docs/play.md): playing the headless game with tools/fallplay.py: commands, start points, UI facts - [docs/state.md](docs/state.md): where the game keeps its state in memory (player, character record, mode, location), with the evidence for each - [docs/naming.md](docs/naming.md): the naming convention for functions, globals and record fields - [docs/port.md](docs/port.md): the native port (arm64 macOS, SDL3): how it builds, its rules and status - [docs/natural_c.md](docs/natural_c.md): making the lifted C read like real C (structuring, typed globals, local names, declarations) and the Watcom 10.0a control-flow rules ## Status - `FALL.EXE` 1.07.213 reproduced from Bethesda's free release (`c49a2ceb…`). - Format: uncompressed LE behind the CauseWay stub. 3 objects, 37,520 fixups. - 3,315 functions found in object 1 (98.4% of its bytes decoded): 2,297 of them are the game's own C in 84 original source units (names recovered from `__FILE__` strings), the rest are libraries (MemCheck, Watcom runtime). Object 2 is the XnGine engine, hand-written asm assembled with Borland TASM 4.0: all of it is rebuilt from `src/xngine/` (113 modules, 719 functions, every address a symbol, the self-modifying code's patch fields named). - Compiler: Watcom C32 10.0a. Game code is unoptimised, with debug info: `-od -s -of+ -4r -d2`. - Toolchain: the real Watcom C32 10.0a `wcc386` (and `wasm` for the two assembly helpers) under DOSBox-X (`tools/wcc10.py`): one DOSBox-X run compiles all of `src/` in under a minute. - Sources: `src/lifted/` holds the functions `tools/lift.py` lifted and the compiler matched, one file per unit. It was generated until 2026-10-04 and is now ordinary source, with names and record structs edited in (`promote_lifted.py` refuses to overwrite it). `src/hand/` holds functions matched by hand, one per file; `src/.c` are hand-written units; `src/int.asm` is the original's assembly; `src/xngine/` is object 2, generated by `tools/xn_disasm.py`; `src/xngine_c/` is object 2 as literal C, generated by `tools/xn_c.py` and checked against 8,204 recorded calls (not part of FALL.EXE's build). `src/engine/` is object 2 as readable C (`tools/xn_rc.py`, docs/xngine_readable.md): every function written as C a person would write, checked against the same records and in lockstep frames of the game. - Matched game functions and the share of game code: see the badges at the top (updated by every successful `tools/build-and-verify.sh`); the rebuilt executable is byte-identical. ## Quick start ```sh # 1. Game files: Bethesda's DFInstall.zip in orig/, then run the 1.07.213 patcher # headless in DOSBox-X (brew install dosbox-x) tools/patch_213.sh orig/DFInstall.zip # 2. Python tools python3 -m venv .venv && .venv/bin/pip install -r requirements.txt # 3. The compiler: from your Watcom C/C++ 10.0a media, copy WATCOM/BIN, WATCOM/BINB and # WATCOM/H to third_party/watcom10/w10a/WATCOM/ (never committed) # The assembler for object 2: from your TASM 4.0 disks, unpack CMD16.PAK and CMDLINE.PAK # (LHA archives; tar -xf reads them) into third_party/tasm/BIN/ (never committed). # Without it the build keeps object 2's original bytes. # For the headless game (tools/fallemu.py): a patched Unicorn, about a minute to build, # which runs the 3D world ~70x faster than the pip one (needs cmake and ninja) tools/build_unicorn.sh # 4. Find functions and units, match a C file, then build and verify .venv/bin/python tools/find_functions.py .venv/bin/python tools/find_units.py .venv/bin/python tools/split.py # asm listings in asm/nonmatchings/ .venv/bin/python tools/lift_all.py # batch: lift, compile, check .venv/bin/python tools/settle_lifted.py # matched → src/lifted/ (how it was built; now frozen) tools/build-and-verify.sh ``` ## Tools | Tool | Purpose | |---|---| | `tools/patch_213.sh` | build the 1.07.213 `FALL.EXE` from `DFInstall.zip` | | `tools/wcc10.py` | run Watcom C32 10.0a (and WASM) under DOSBox-X; check files against `FALL.EXE` | | `tools/w10_try.py` | try many C variants of one function in one compiler run | | `tools/w10_frame.py` | predict 10.0a's stack-frame layout from a function's declarations | | `tools/le.py` | LE loader: objects, pages, fixups (from KKND-Decomp) | | `tools/find_functions.py` | function discovery → `config/functions.csv` | | `tools/split.py` | one symbolic asm listing per function → `asm/nonmatchings/` (gitignored) | | `tools/omf.py` | OMF object reader (from KKND-Decomp) | | `tools/match.py` | compile a C file and compare its functions with `FALL.EXE` | | `tools/cc_dis.py` | disassemble what the compiler makes of a C file | | `tools/find_units.py`, `tools/units.py` | recover the original source units; map an address to its unit | | `tools/lift.py`, `tools/lift_all.py` | lift `-od` code to C; batch lift, compile and check every game function (2 s) | | `tools/promote_lifted.py`, `tools/settle_lifted.py` | write matched lifted functions into `src/lifted/.c`, splitting out any that match only on their own | | `tools/update_readme_progress.py` | refresh the README badges from `build/progress.json` | | `tools/build-and-verify.sh` | splice all of `src/` into `FALL.EXE` and check the SHA-1 (`build_fall.py`) | | `tools/xn_disasm.py` | disassemble object 2 (XnGine) into TASM modules in `src/xngine/` that reassemble to the original | | `tools/xn_link.py` | assemble the XnGine modules with TASM 4.0 under DOSBox-X and check bytes and fixups | | `tools/fallemu.py` | run `FALL.EXE` headless (Unicorn): DOS, DPMI, timer, keyboard and mouse, scripted scenarios | | `tools/fallplay.py` | play the headless game a step at a time (snapshots, rewind, 2x screenshots, new code per step), for a model at the controls (walk, turn, go to and enter buildings); `FALLEMU_CHEAT=1` boots with the game's cheat mode | | `tools/fallcov.py` | code coverage of the headless game: which functions ran, per source unit and XnGine module | | `tools/fallfuzz.py` | coverage-guided random play: workers play random input from the save snapshots and keep every state that ran new code | | `tools/fallevidence.py` | evidence for naming: replays every play step and fuzz state with coverage, then gives each function the inputs that go with it running, its cluster, callers, callees and strings, and the asset evidence: files read, texts and sounds asked for, button boxes handled | | `tools/fallcall.py` | call any function in a live game from a safe point between frames: return value, functions run, files opened, globals changed, the screen it draws; `sweep` does it for many functions, each from a fresh game | | `tools/fallassets.py` | asset tracing: replays every evidence episode and loads every classic save with each file read (and each sound asked for) attributed to its validated guest call chain and named by record; `report` lists per asset class the loaders and consumers, per function the assets it reads | | `tools/asset_ids.py` | static asset evidence, no emulator: the TEXT.RSC records and sounds each function asks for by constant id (with the text or sound name), the screen button tables `{x0,y0,x1,y1,handler}` and the text-macro tables | | `tools/assets.py` | readers for the game's data files: BSA and DAGGER.SND records, TEXT.RSC text, TEXTURE archives, sound names (`config/sound_clips.csv`, from Daggerfall Unity) | | `tools/libmatch.py` | names the library region's Watcom runtime functions by matching them, relocations masked, against Watcom C32 10.0a's own libraries (extracted from the 10.0a CD into third_party/) | | `tools/structure.py` | rewrites the lifter's gotos as if/else, &&/||, while, do-while, for, break and continue, keeping each function byte-identical (it compiles every change with Watcom 10.0a) | | `tools/type_globals.py` | types `extern char g[];` globals as scalars, arrays or mixed types and removes their casts, verified per file | | `tools/protos.py` | makes each file's declarations of a function agree with its definition, keeping what still compiles to FALL.EXE's bytes; `unify` searches for one prototype every file matches with | | `tools/apply_names.py` | puts the confirmed and strong names from config/names.csv into src/ and include/, recording them in config/symbols.txt for the build (re-runnable; `revert` puts addresses back) | | `tools/names.py` | config/names.csv: names for functions, globals and record fields, each with a confidence and its evidence (check, merge, show, annotate) | | `tools/fallstate.py` | experiments that find game state in memory: screens, interaction modes, values read off the screen, counters that move in step | | `tools/memwatch.py` | memory watchdog: kills this repo's emulator processes, largest first, before the machine runs out of memory (the multi-worker tools start it themselves) | | `tools/build_unicorn.sh` | build the patched Unicorn `fallemu.py` uses (`tools/unicorn/dagger-unicorn.patch`): ~70x faster in the 3D world | | `tools/xn_trace.py`, `tools/xn_record.py` | XnGine code coverage and run-time code writes; record and replay single calls | | `tools/xn_abi.py` | XnGine's register interfaces (`config/xngine_abi.csv`): inputs, outputs, flags and clobbers per function, from liveness over both objects, checked on the record corpus by scrambling | | `tools/xn_rc.py` | XnGine as readable C (`src/engine/`): build with Watcom 10.0a, route the asm's calls to the C, test against the records by each function's interface, differential tests, lockstep frames against the asm (`frames`) | | `tools/xn_declcheck.py` | the `#pragma aux` interfaces the readable C's files give a function, against its definition's | | `tools/xn_c.py`, `tools/xn_cload.py` | XnGine as C: translate every function to literal C (`src/xngine_c/`), compile it with Watcom 10.0a, and replay the record corpus, differential tests or the game itself with XnGine in C | | `tools/port_build.py` | the native build (docs/port.md): the game's C with clang and SDL3, stubs and data generated for what the port lacks; `run` starts it on a game folder | | `tools/port_census.py` | the native build's 64-bit worklist: clang's diagnostics on the game's C, by kind and file | Several tools are adapted from [KKND-Decomp](https://github.com/Wyrelade/KKND-Decomp) (CC0).