# NFL Head Coach 09 — static recompilation Version 0.8.2 A native Windows port of NFL Head Coach 09 (Xbox 360, title ID `45410841`), built by statically recompiling the game's PowerPC executable to C++ with [XenonRecomp](https://github.com/hedge-dev/XenonRecomp) and running it on a host layer that replaces the Xbox kernel, XAM, graphics, audio and input. No game data from the disc is included. You need your own copy of the disc, and nothing under `iso/`, `build/` or `ppc/` may be redistributed. The `hc27` folder holds the modern rosters and images HC27.exe plays with (see below), which may be redistributed. ## Installing Download `HC09Setup.exe` from the [releases page](https://github.com/tools81/nfl-head-coach-09-static-recomp/releases) and run it. The wizard: 1. checks for the build tools: Visual Studio 2022 or later (Community is fine) with *Desktop development with C++*, *C++ Clang Compiler for Windows* and *C++ CMake tools for Windows*, and [Git for Windows](https://git-scm.com/download/win); 2. asks for an install folder (a short path such as `C:\Games\HC09Recomp` is best) and your NFL Head Coach 09 disc image (`.iso`), which it checks is the right game; 3. offers **automatic update checks**: when enabled, the game checks this repository's latest release each time it starts and, if there is a newer one, asks before downloading and rebuilding; 4. downloads the source, XenonRecomp and FFmpeg, imports the game's executable from your disc image, recompiles it and builds `HC09.exe` (about 1 GB of downloads and 10 to 20 minutes), then adds a Start menu shortcut. The game is built on your PC because the recompiled executable is derived from the game's own code, which this project does not distribute. The disc image stays where it is: the game reads its data from it while you play. Settings live in `hc09.ini` in the install folder (`IsoPath`, `AutoUpdate`). Running setup again and choosing the same folder updates that installation to the latest release (keeping your saves), and is also how to change the disc image; a development clone on a git branch is built as it is instead. The setup log is `%TEMP%\HC09Setup.log`. ### Playing with today's rosters (HC27.exe) Setup builds a second game next to the original: **HC27.exe**, NFL Head Coach 09 with today's league. It has the current players with their ratings and contracts, the current head coaches, real player and coach portraits, updated team names and logos, and seasons that start in 2026. Everything it needs comes with the download (the `hc27` folder in the install folder), so there is nothing to import or set up. 1. Open your install folder (for example `C:\Games\HC09Recomp`) and go into `build\hc09`. 2. Double-click **HC27.exe**. To start it more easily later, right-click it and choose *Send to > Desktop (create shortcut)*. The Start menu shortcut setup made opens the original game, HC09.exe. 3. Start a new career or a Coach Now game: the new rosters are already in place. Good to know: - HC27 uses the same disc image as the original game, and the two keep separate saves (HC27's are in `build\userdata-hc27`), so a career in one never touches the other. - The rosters are up to date through week 8 of the 2026 season: trades, waiver claims, signings and releases since the ratings were taken are applied. Players the ratings don't have (most practice squad call-ups) are missing. - Updates (automatic, or running setup again) bring new versions of the rosters and images too. - If HC27 says the disc's default roster is not where it expects it, your disc image is a different version of the game from the one the rosters were made from; HC09.exe still works. - Draft picks get today's rookie contracts: when a career's draft ends, every pick is signed to the four-year deal his pick number pays, with no negotiating with agents. Rosters can then run past 53 players until the training camp cut day, as NFL offseason rosters do. - First-round picks come with the fifth-year option. In the off-season before a first-round pick's fourth year, he appears on the restricted free agent tender screen with two choices instead of the tenders: decline the option, or exercise it for a fifth year at his position's option salary (the average of the 3rd to 25th highest salaries at the position; the Pro Bowl tiers are not applied). The computer's teams decide on their own. A career started before this release needs its players' draft rounds set once: `python tools/roster_import/draft_rounds.py --save build/userdata-hc27/content/00000001/CAREER-/CAREER-`. - The original game is unchanged: HC09.exe ignores the `hc27` folder. ## Status | Stage | State | |---|---| | Disc extraction, XEX decryption/decompression | done | | Recompiler config (ABI helpers, setjmp/longjmp, jump tables, leaf-function bounds, data skips) | done — recompiler log is clean | | Missing PPC/VMX instructions in XenonRecomp (~40 opcodes), guarded integer division | implemented in `patches/`, not yet verified against hardware tests | | Host executable builds and links | done | | Memory, Rtl, TLS, time, strings/printf, XEX header, kernel variables | implemented | | Threads, waits, events, semaphores, mutants, timers | implemented (no APCs, no forced suspend) | | File system (disc read straight from the ISO, writable `build/userdata`) | implemented | | GPU command processor (ring buffer, fences, scratch write-back, per-CPU interrupts, vblank, swaps, shader/constant loads, predicated tiling) | implemented | | Physical memory windows (0xA0000000 / 0xC0000000 / 0xE0000000 aliased) | implemented (0xE0000000's 4 KiB offset not reproduced) | | Xenos shader microcode → HLSL translator | implemented; all 148 shaders captured from menus and gameplay translate and compile. Compiled with DXC (shader model 6.0, `dxcompiler.dll` and `dxil.dll` copied from the Windows SDK at build time) in the background and cached in `build/shadercache`: the 148 take about 5 s in all, at most 0.12 s each, against 52 s and up to 6 s with D3DCompile, which is used when DXC is missing or the GPU lacks shader model 6.0 | | D3D12 renderer | draws, depth/stencil, culling, blending, alpha test, textures (linear/tiled, full mip chains incl. packed mip tails, render-to-texture mips, anisotropic filtering, border colours, cube maps, volumes, stacked arrays with normalized layer coordinates, common formats, resolved depth), resolves (incl. tiled colour and depth strips and the red/blue copy swap), present, fast clears through multisampled EDRAM aliases, 4x MSAA for the multisampled surfaces the 3D scenes render to (`HC09_NO_MSAA=1` turns it off) | | Vulkan renderer (default) | from NFL Head Coach 09 for PC (`src/vk`): draws the game's Direct3D calls with Vulkan; up to 240 fps (`FrameRate`) and 4x resolution (`Scale`); falls back to D3D12 without Vulkan 1.3 | | Audio output | WASAPI shared mode, 5.1 mix downmixed to stereo | | XMA decoder | FFmpeg's XMA2 decoder (LGPL build in `thirdparty/ffmpeg`), 16-bit PCM into the title's output rings | | Music player (XMP) | no user soundtrack: always idle, controls accepted | | Controller input (XInput) | implemented, plus a keyboard/scripted virtual pad for player 1 | | Saves (XAM content packages) | implemented: packages are folders under `build/userdata/content//` | | Remaining kernel/XAM imports | 36 still stubbed (APCs, voice chat, Live UI, marketplace); none are called in normal play. System message boxes answer with their focused button at once | The game boots, plays its intro movie in a window, loads, renders the title screen, and reaches its menus (favourite-team prompt, main menu with its 3D background, team select) with controller or keyboard input, and plays a game: weekly coaching agenda, coin toss, and the in-game play-calling screen, where called plays run and the clock and downs advance. Career Mode (new career, season goals, the career hub, training camp, gameplan, then a preseason game in another stadium), the Xbox Live, My Coach and Media World menus, and a 21-minute unattended match (score and quarters advancing, memory flat) all run without errors. Menus and in-game screens run at about 30-35 fps with music and sound effects, and the profile saves (Quick Save, LS) to `build/userdata/content` and loads on the next start. ## Controls Player 1 is always connected. A real controller works; the keyboard also works while the game window has focus: Enter = Start, Esc = Back, Z/Space = A, X = B, C = X, V = Y, Q/E = LB/RB, 1/3 = LT/RT, F/H = left/right stick click, arrows = D-pad, WASD = left stick, IJKL = right stick. F11 or Alt+Enter toggles borderless fullscreen. The window can be resized freely; the picture keeps its 16:9 shape, with black bars where the window's shape differs. With the D3D12 renderer, `set HC09_RESOLUTION_SCALE=2` (up to 4) renders at that multiple of the game's 1280x720, e.g. 2560x1440, for a sharper picture in fullscreen on a high-resolution display. Menus and gameplay held 30-35 fps at 2x in testing; 1x is the default. With both renderers (see below), the 3D scenes get two effects the original does not have: ambient occlusion (soft shading where surfaces meet, such as a player's feet on the field or the folds of a coach's jacket) and reflections on the field. The turf is no mirror, so a reflection is drawn dark and with strong contrast, like a shadow cast by the player near where he stands, longest at low camera angles. Both work from the scene's depth buffer, so they cost little and need no change to the game's own shaders. Set their strength in `hc09.ini` (0 turns an effect off, 1 is the default): ```ini [Graphics] AmbientOcclusion=1 Reflections=1 ``` `set HC09_SSAO=` and `set HC09_SSR=` override the file for one run. The Vulkan renderer also has post-processing (bloom, colour grading and depth of field; see its settings below). ### Renderers: Vulkan (60 fps, 1440p) and D3D12 The game is drawn by a renderer built on Vulkan (it comes from the NFL Head Coach 09 for PC project; see `THIRD_PARTY_NOTICES.md`), which can run the game at 60 frames per second and at higher resolutions. By default it keeps the original game's pace (about 35 fps) at 720p. To get 60 fps at 1440p, open `hc09.ini` in your install folder with Notepad and add: ```ini [Graphics] FrameRate=60 Scale=2 ``` (If the file already has a `[Graphics]` section, add the two lines under it.) The settings: - `FrameRate`: frames per second, 30 to 240. Leave it out (or use 35) for the game's own pace. The game's clock runs in real time at any setting: plays and game time are not sped up. - `Scale`: the picture is drawn at this multiple of the game's 1280x720: 1 = 720p, 2 = 1440p, 3 = 4K. Higher looks sharper in fullscreen and needs a faster graphics card. - `VSync=off` presents without waiting for the display (may tear); on by default. - `Fullscreen=on` starts in borderless fullscreen (F11 or Alt+Enter still toggles it). - `MSAA`: anti-aliasing, `1` as the game asks (4x in 3D scenes, the default), `2` at most 2x, `0` off. Lower settings help slower cards at `Scale=3`. - `GPU`: on a PC with two graphics chips, `discrete`, `integrated`, or part of the card's name. - `Anisotropy`: texture filtering that keeps field lines and yard numbers sharp at the broadcast camera's low angles, `16` (the default) down to `2`, or `off` as the original game draws them. - `Upscaling`: how the picture is fitted to the window. `fsr` (the default) enlarges it with AMD FidelityFX Super Resolution 1, sharper than plain stretching on a screen larger than the picture (720p on a 1440p or 4K screen), and shrinks a larger picture (`Scale=2` on a 1080p screen) by averaging all the pixels each screen pixel covers. `bilinear` stretches it as before. - `Sharpening`: `0` (off) to `100`, `75` by default; applied whenever the picture is scaled. - `SmoothMotion`: `on` by default. The game draws players and the camera at the full frame rate, but the ball and a few objects only move 30 times a second; this draws them in even steps between those moves (about 1/30 s behind). `off` shows them as the game moves them. - `HDTextures`: `on` builds an HD texture pack on your PC as you play. The game saves each texture it loads into `build\hashed`, and when you close it, `HC09HDPack.exe` upscales the new ones 4x with [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) on your graphics card and saves them in `textures\hd`, where the game loads them from the next start. The first run downloads the upscaler (45 MB) into `thirdparty\realesrgan`. Data textures (normal maps, masks) are left as the game has them. The pack is made from your own copy of the game and stays on your PC. `off` by default. - `Bloom`, `ColorGrading` and `DepthOfField`: post-processing of the 3D scenes (never the menus or on-field graphics), each a strength where `0` is off and `1` the default. Bloom makes stadium lights and other near-white highlights glow softly. Colour grading adds a filmic roll-off into white (so the bloom never clips), a little contrast and colour, and a faint vignette. Depth of field softens what lies beyond the middle of the picture, as a TV camera's long lens does, focusing as the camera moves; it is `0` (off) by default, since it also softens the far side of the field during play. Try `DepthOfField=1`. - `HDR`: `on` shows the game in HDR on an HDR monitor (turn HDR on in Windows' display settings first; without it the game stays in SDR). The picture keeps its look, with SDR white at `HDRPaperWhite` nits (`200` by default; raise it if the game looks dim next to the desktop), while the 3D scene's highlights (stadium lights, sky, sunlit white uniforms, field paint) go brighter, up to `HDRPeak` nits (`1000` by default; set it to your monitor's peak brightness). The menus and on-field graphics stay at paper white. `off` by default. - `Crowd`: `on` by default. The game fills the stands with cards of 16 animated fans, so every fan of a kind moves in step with the others. This gives each seat its own moment of the fans' last second of movement, and mirrors and shades some of them, so the stands look less like a repeating pattern. `off` draws the crowd as the game does. - `AspectRatio`: for ultrawide screens, `21:9`, `32:9`, any `:`, or `auto` for your screen's shape; `16:9` (the game's own) by default. The 3D scenes fill the wider screen and show more at the sides, while the menus and on-field graphics keep their shape in the middle. Things just past the original picture's sides can occasionally pop in late, since the game still decides what to draw for 16:9. The Vulkan renderer needs a graphics card and driver with Vulkan 1.3 (any card from the last several years). On a PC without one, the game uses the older D3D12 renderer instead, which runs at the game's own pace. To use the D3D12 renderer anyway, add `Renderer=d3d12` under `[Graphics]`. HC27's rosters, portraits and logos work with both. `set HC09_RENDERER=d3d12` (or `vulkan`) picks a renderer for one run, and `HC09_FRAMERATE`, `HC09_SCALE`, `HC09_VSYNC`, `HC09_FULLSCREEN`, `HC09_NATIVE_MSAA`, `HC09_GPU`, `HC09_ANISOTROPY`, `HC09_UPSCALE`, `HC09_SHARPEN`, `HC09_SMOOTH_MOTION` and `HC09_ASPECT` (width / height) override the settings above the same way. `HC09_RESOLUTION_SCALE` is the D3D12 renderer's resolution. ## Rosters and replacement images `tools/roster_import` builds a roster from a modern ratings export and current coaching staffs (see its [README](tools/roster_import/README.md)), with player and coach portraits. **HC27.exe**, built alongside HC09.exe, is the game with that roster: it reads `hc27\ROSTER` in place of the disc's default roster (so new careers and the roster screens start with it, nothing to load), lays the patches in `hc27\disc` (`@.bin`, such as the league's finances that `league_salaries.py` writes) over the disc's files, draws the images in `hc27\textures` before those in `textures`, and keeps its saves apart in `build\userdata-hc27`. Its seasons start in 2026: the game's calendar still runs from 2008, so every year it shows (the clipboard's date, the salary cap years, draft classes, important dates) is 18 years later. Without `hc27\ROSTER` it reports what to build and exits. HC09.exe is unaffected by the `hc27` folder. The front end's "swappable" images (player and coach portraits, team logos, helmets and the other `headcoach/swappables/...` libraries) can be replaced without touching the disc: a PNG at `textures/swappables//.png` in the install folder (for HC27, also `hc27/textures/swappables/...`) is drawn instead of image `` of that library, at its own resolution (images more than twice the game's size are halved until they fit, since these are drawn without mip maps). A player's portrait id is `PSXP` in the roster. `HC09_NO_REPLACEMENTS=1` draws the game's own images; `HC09_TRACE_SWAPPABLES=1` logs each image the game loads (`[swappable] PlayerPortraits/1650 -> texture 0x161CF000`). Any other 2D texture (a logo in a menu, a helmet decal, a sign at the career campus) can be replaced by its contents: a PNG at `textures/hashed/.png` (for HC27, also `hc27/textures/hashed/...`) is drawn wherever the game uses that texture, with mip maps built for it. `HC09_DUMP_HASHED=1` saves every texture the game draws, once, as `build/hashed/.png`, in the colours it is drawn with, so the one to replace can be found and edited; the log names the swappable image a dumped texture holds, if any. Both kinds of replacement image hold the colours as drawn (a texture's channel order does not matter). HC27 can also rename a team (`TEAM_RENAMES` in `tools/roster_import/import_roster.py`; the Redskins are the Commanders): the game names a team's uniforms, helmets and career campus after its name in the roster and finds them by a hash of that name, so HC27 hashes the old name for them (`kRenamedTeams` in `src/common/edition.cpp`). ## Fixes to the original game - **Injured players heal after Play Creator again.** In the original game, opening Play Creator in a career left the game in its practice mode after Play Creator closed, and the injury system does nothing in practice mode: from then on no injury in the league counted down or healed, and games recorded no new ones. The mode in force before Play Creator is now restored when it closes (`src/game_fixes.cpp`). A career saved while affected is repaired by opening and closing Play Creator once. ## Known issues - Rendering: real MSAA only for multisampled surfaces used by predicated tiling (the 3D scenes); other multisampled surfaces render single-sampled. EDRAM aliasing is reproduced only for clear rectangles drawn into a multisampled surface that covers a single-sampled one. Alpha-to-coverage (`RB_COLORCONTROL` bit 4) is not implemented; the title does not enable it in the menus or in gameplay. - Predicated tiling is approximated for screen-sized targets: the first tile pass renders the whole frame, later passes skip their draws, and tile clears are skipped (the per-draw bin masks the GPU derives from screen-extent queries are not reproduced). - The video board and LED ribbon boards at the Colts' stadium (Lucas Oil) show a flat, flickering white/cyan slab. This is the game's own data, not the port: the `JUMBOTRON` effects reference source-art paths that were never exported (`.../textures/stadiums/scoreboards/jumbotron_COLOR.dds`, `jumbo_GRIME_color4.dds`, `jumbo_ads_color_COL`, ...), so the engine substitutes its grey `Missing.dds` for the LED pixel and grime masks, and the shader's 9x brightness then saturates the board. `HC09_ENGINE_LOG=1` shows the engine's own "Asset missing, using default" messages for them. Stadiums whose boards use other materials (e.g. Texas Stadium) render normally. ## Requirements - Visual Studio 2022 with *Desktop development with C++*, *C++ Clang Compiler for Windows* and *MSBuild support for LLVM (clang-cl)* - CMake ≥ 3.20 and Ninja - Python 3.12 (a project venv is used: `python -m venv .venv` then `.venv\Scripts\pip install pycryptodome capstone`) - FFmpeg 8.1 LGPL shared build (BtbN `ffmpeg-n8.1-latest-win64-lgpl-shared-8.1.zip`) extracted to `thirdparty/ffmpeg` (provides `include/`, `lib/` and the DLLs copied next to `HC09.exe`) ## Pipeline Run from the repository root. Put the disc image at `iso/NFL Head Coach 09.iso`. ```sh # 1. Build XenonRecomp with the HC09 instruction patch git clone --recursive https://github.com/hedge-dev/XenonRecomp thirdparty/XenonRecomp git -C thirdparty/XenonRecomp checkout ddd128b git -C thirdparty/XenonRecomp apply ../../patches/xenonrecomp.patch tools\build_xenonrecomp.bat # 2. Extract and decrypt the executable (the XEX key is read from the XenonRecomp checkout) python -I tools/xdvdfs.py extract "iso/NFL Head Coach 09.iso" build/extracted default.xex .venv/Scripts/python -I tools/xexdecrypt.py build/extracted/default.xex build/default.pe .venv/Scripts/python -I tools/xeximports.py build/extracted/default.xex build/default.pe --csv build/imports.csv # 3. Analyse and generate the recompiler config build/xenonrecomp/XenonAnalyse/XenonAnalyse.exe build/extracted/default.xex config/HC09_switch_tables.toml .venv/Scripts/python -I tools/gen_functions.py build/default.pe config/HC09_switch_tables.toml config/HC09_functions.toml build/imports.csv # Jump tables XenonAnalyse misses (dispatches without a bounds check): check, then add .venv/Scripts/python -I tools/check_switches.py build/default.pe config/HC09_switch_tables.toml .venv/Scripts/python -I tools/add_missing_switches.py build/default.pe config/HC09_switch_tables.toml 0x824374DC 0x8254D164 0x82596A14 0x825B1874 0x82E575D4 .venv/Scripts/python -I tools/make_config.py # 4. Recompile to C++ (into ppc/) mkdir ppc build/xenonrecomp/XenonRecomp/XenonRecomp.exe config/HC09.toml thirdparty/XenonRecomp/XenonUtils/ppc_context.h # 5. Build and run .venv/Scripts/python -I tools/gen_import_stubs.py build/imports.csv tools\build.bat build\hc09\HC09.exe ``` `HC09.exe` finds the project folder itself (it looks upward from its own location for `build\default.pe`), so it can be double-clicked in `build\hc09` or started from a shortcut. If something it needs is missing, it says so in a message box. It takes the disc image from its command line (`HC09.exe [image.pe] [default.xex] [disc.iso]`), else from `hc09.ini`, else from `iso/NFL Head Coach 09.iso`. The XenonRecomp commit is pinned in `patches/xenonrecomp.commit` (setup reads it from there). `build\installer\HC09Setup.exe --import ` does step 2's extraction and decryption without Python (the configs and `src/kernel/import_slots.inc` from steps 2 and 3 are committed, so the setup wizard skips those steps). ### Releases Setup and the in-game update check use the repository's latest GitHub release: tag it `v` with the version in `CMakeLists.txt` (`project(HC09Recomp VERSION ...)`), and attach `HC09Setup.exe`, which contains no game code. Build it standalone with either compiler: ```sh cmake -S tools/installer -B build/setup -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build/setup # -> build/installer/HC09Setup.exe ``` Installed copies offer the update the next time they start; accepting it checks out the release tag, re-patches XenonRecomp if the pin changed, and recompiles and rebuilds the game. ## Layout | Path | Contents | |---|---| | `config/HC09.base.toml` | Hand-written recompiler settings (edit this one) | | `config/HC09.toml` | Generated: base + `HC09_functions.toml` | | `config/HC09_switch_tables.toml` | Generated by XenonAnalyse | | `patches/` | XenonRecomp changes: new instructions, `bdnzf` fix | | `src/` | Host runtime: memory map, thread block, kernel/XAM imports | | `src/gpu/` | Command processor and the D3D12 renderer | | `src/vk/` | The Vulkan renderer and frame pacing (MIT, `src/vk/LICENSE`) and their adapter (`vk_adapter.cpp`) | | `src/common/` | Shared by the game and setup: `hc09.ini` settings, GitHub release check | | `tools/installer/` | `HC09Setup.exe`: install/update wizard (disc import, build pipeline) | | `tools/` | Extraction, decryption and analysis scripts | ## Debugging - Unimplemented imports log their first call: `[stub] Name (library) called from 0x...`. - Access violations inside guest memory print the faulting guest address. - `set HC09_DUMP_STACKS=20` dumps every guest thread's call stack after 20 seconds (`20,30` dumps at each time). Recompiled functions are named `sub_`, so these read as guest stacks. - `set HC09_WATCH=830DD0E1` puts a hardware write breakpoint on guest bytes (up to 4, comma-separated) and logs each write with the writer's stack. `*83229400+164` watches the 8 bytes at (pointer stored at 0x83229400) + 0x164, resolved 5 s after startup. `A6713ED4:4` watches 4 bytes (1, 2, 4 or 8, aligned); `HC09_WATCH_VALUE=` reports only writes that leave that value and `HC09_WATCH_AFTER=` arms the watches that late. Each distinct writer stack is printed once. `HC09_WATCH_READS=1` catches reads as well (e.g. which code uses a string in the image). - `set HC09_SCAN_TDB=40,230` lists, at those seconds after startup, the game's databases in guest memory that hold the table `HC09_SCAN_TABLE` (default `DRPK`, the draft picks), with its address and record count. `HC09_SCAN_WATCH=B4,BC` puts write watches at those offsets from the table found; `HC09_SCAN_BYTES=` lists where a text occurs in memory. - `set HC09_ENGINE_LOG=1` prints the engine's own debug messages (compiled out in retail) as `[engine] ...`, e.g. `Asset missing, using default: ` for every texture a material references that no loaded package provides. - `set HC09_TRACE_SQL=INJY,PIRE` logs each database query the game runs through its query call whose text names one of those tables or fields (written as the game spells them), as `[sql] ...` with its arguments, the values it returned and its result. Queries the game prepares ahead of time do not pass through it. - `set HC09_SAMPLE_THREAD=160` samples guest thread 0x160's stack every 2 ms for its whole life and prints each distinct call path once. - Indirect calls to code that was not recompiled as a function stop with `indirect call to 0x..., which is not a recompiled function`; access violations print the faulting stack with `ppc/*.cpp` line numbers. - `src/debug/hooks.cpp` overrides individual recompiled functions (`sub_
`) to log or patch them; the original stays callable as `__imp__sub_
`. - `set HC09_GPU_STATS=1` prints command-processor throughput, packet counts and its time split (draw / swap / idle) every 2 s, plus the share of wall time in each phase of a draw and texture-cache counters, and a `[gpu] memory` line: video memory in use, cached textures and resolve targets (with how many were released after a minute unused), resolves dropped as stale (the title loaded other data over their memory) and fetches a resolve could not serve. A `flushes (heap exhausted)` line counts the times a frame ran out of upload space, view descriptors or samplers, each of which makes the CPU wait for the GPU to finish. A `guest memory on the GPU` line counts the memory copied to the GPU and the title's writes to watched pages. With `HC09_GPU_MEMORY=0` it also logs a large texture whose change its sampled check missed (`changed unseen by its samples`). - The game's physical memory is mirrored in a 512 MiB GPU buffer: vertex shaders read their vertices from it and byte-swap them, and a compute shader untiles textures from it. A page is copied when a draw first needs it and again only after the game writes to it (copied pages are write-protected, and the first write to one marks it stale), so unchanged geometry and textures cost no CPU time per frame. `set HC09_GPU_MEMORY=0` swaps vertices and untiles textures on the CPU instead, noticing texture changes by hashing; that is also the fallback when the buffer cannot be created. - `set HC09_USERDATA=C:\path\folder` keeps saves and the cache in another folder (e.g. a fresh profile for `HC09_PRESS` scripts, which assume the first-start prompts). - `set HC09_DUMP_FRAME=300` traces every packet and draw of frame 300 (`13500/150`: frame 13500 and every 150th after it); `HC09_FIND_DWORDS=` searches guest memory for a dword sequence at that frame and `HC09_FIND_STRING=` for that text (case-insensitive), printing the surrounding characters. Frame dumps also log each draw's shader bool constants (`[frame] bools`). - `set HC09_DUMP_SHADERS=1` writes each new shader's microcode to `build/shaders/`; `build\hc09\XenosDisasm.exe [--hlsl] build\shaders\*.bin` disassembles, translates and compiles them. - `set HC09_SCREENSHOT=300,900` saves frames 300 and 900 (counted as `HC09_PRESS` counts them) as `build/screenshot_300.bmp` and so on (`python tools/bmp_to_png.py build/screenshot_300.bmp out.png`). With either renderer: Vulkan saves the window as shown, D3D12 its back buffer. - `set HC09_TRACE_NAMES=1` (HC27) prints each distinct asset name the game looks up in its archives (packages, models, textures), with the caller. - `set HC09_FXC=1` compiles shaders with D3DCompile instead of DXC; `HC09_SYNC_SHADERS=1` compiles each when first drawn instead of in the background (with `HC09_GPU_STATS=1`, printing how long each took). - `set HC09_EFFECTS_DEBUG=1` shows the ambient occlusion alone (white open, dark covered), `2` the reflections alone, `3` applies both effects to the left half of the screen only and `4` to every other frame, for comparisons. The first 3D scene logs the camera found (`[effects] first scene`). - `set HC09_D3D12_DEBUG=1` enables the D3D12 debug layer and prints each distinct message; `HC09_NO_RENDER=1` disables rendering. - `set HC09_AUDIO_RECORD=C:\path\out.wav` records everything sent to the speakers (48 kHz stereo). - `set HC09_AUDIO_STATS=1` prints XMA decoder counters, the output peak level and the sound card queue depth (normally about 25 ms) every 5 s; `HC09_NO_AUDIO=1` disables audio output; `HC09_XMA_DUMP=1` saves one context's packets to `build/xma_dump.bin` for `build\hc09\XmaTest.exe`. - `set HC09_PRESS=1800:start,1900:a` presses buttons at those frames (held 6 frames). - `set HC09_STACK_ON_OPEN=campus` prints the call stack of every file open whose path contains that text. - `set HC09_DRAW_SNAPSHOTS=1` with `HC09_DUMP_FRAME` saves the render target after every visible draw of that frame (`build/draw_.bmp`); `HC09_DUMP_TEXTURES=` saves the RGBA8 textures sampled from that frame on (`HC09_DUMP_TEXTURES=dump`: those sampled in the dump frame). `HC09_DRAW_SNAPSHOTS=-` limits the snapshots to those draws and `HC09_DRAW_SNAPSHOTS=s` to draws to that surface; `HC09_STENCIL_SNAPSHOTS=1` also saves the stencil plane (`build/stencil_.bmp`: bit 0 red, bit 1 green). Snapshot `` shows the result of the draw logged as ``. - `set HC09_DUMP_AFTER_RESOLVE=512x512+2@2500` dumps (as `HC09_DUMP_FRAME` would) the frame 2 frames after the first 512x512 colour resolve at or after frame 2500, for events whose frame number varies between runs. `HC09_TRACE_RESOLVES=1` logs every colour resolve; `HC09_SAVE_RESOLVES=512x512` saves each resolve of that size as `build/resolve__.bmp` (`512x512@2500-2510` only in those frames). - `set HC09_DUMP_VS_CONSTS=152-155` / `HC09_DUMP_PS_CONSTS=200-255` (with a frame dump) print that range of vertex or pixel shader constants for every draw. Frame dumps also log each draw's blend/depth/stencil registers, its index range and every bound texture (format, size, tiling, swizzle); `HC09_DUMP_VERTEX_DATA=1` adds the first 48 dwords of every vertex buffer (slow: it also prints texture slots read as vertex fetches). - `set HC09_TRACE_SURFACE=04020118` logs every draw to that render surface (`RB_SURFACE_INFO`) in any frame, with its index/depth/clip registers, and saves the target and depth buffer after each one (`build/surface_.bmp`, `build/surface__depth.bmp`). Debugging overrides: `HC09_SAMPLER_TEST=lod0|wrap|point`, `HC09_NO_DEPTH_TEST=1`, `HC09_FORCE_INDEXED=1`. - With a frame dump, `HC09_DUMP_TEXTURE_MEMORY=1` saves every sampled texture's raw guest memory (`build/texmem___f_x_p.bin`), `HC09_DUMP_VERTEX_MEMORY=1` every vertex buffer read (endian-swapped, `build/vbmem_
_s.bin`), and `HC09_PROBE_PIXEL=,` logs which draws change that render-target pixel (`[probe]`). - `set HC09_TRACE_RING=` logs ring-buffer batches from that time; `HC09_TRACE_FENCES=` logs fence writes (and ring indirect-buffer calls) once a fence value reaches that number; `HC09_TRACE_D3D=1` reports long D3D fence waits (with the D3D worker queues) and concurrent kickoffs; `HC09_TRACE_KICKS=` logs ring kicks; `HC09_TRACE_WAITS=1` logs each distinct `WAIT_REG_MEM`. Critical-section waits over 3 s are always logged (`[cs]`). `HC09_THREAD_STATS=1` prints each thread's CPU share every 5 s (the command processor is 0xC0, the XMA decoder 0xA0). - `set HC09_DUMP_MEMORY=1AD1D000:8000` (with `HC09_DUMP_FRAME`) saves raw physical memory as `build/mem_.bin`; `HC09_NO_E_WINDOW=1` leaves the 0xE0000000 view unmapped. - `set HC09_DUMP_MIPS=256x256f2` saves every mip level of textures of that size and Xenos format, as stored by the game (untiled, byte-swapped), to `build/mip____x.bin` (frame of the upload). `HC09_ORIGINAL_MIPS=1` keeps the stored mip levels of textures whose levels are not downscales of level 0 (such as the field's grass noise); by default those are rebuilt from level 0 (`[gpu] texture ... rebuilding them`). ## Analysis tools - `tools/peinfo.py` — sections and `.pdata` stats - `tools/find_abi.py` — locate `__savegprlr_14` and friends - `tools/xrefs.py` — find branches to an address - `tools/disasm.py` — disassemble a range - `tools/check_switches.py` — cross-check jump-table coverage