Need for Speed: High Stakes (PS1)
OriginPlayStation
TechnologyNative C/C++ port (engine reimplementation)
Available forWindows, Linux, macOS
View official projectAbout the project
Need for Speed (NFS) es una franquicia de videojuegos de carreras y aventuras publicada por Electronic Arts. La serie se centra, principalmente, en las carreras callejeras ilícitas y, en general, les da el papel a los jugadores de completar varios tipos de carreras, infringiendo en más de una ocasión la ley local mientras evaden persecuciones policiales. La serie lanzó su primer título, The Need for Speed en 1994. El título proviene de una famosa cita de la película de 1986 Top Gun.
Official project information
nfs4-decomp — Need for Speed: High Stakes (PSX) matching decompilation
⚠️ NO LICENSE GRANTED — ALL RIGHTS RESERVED
This repository carries no open-source license. Viewing and forking on GitHub are the only permitted acts (GitHub ToS §D.5). Any other use — ports, builds, redistribution, re-hosting, or derivative works of any kind — is prohibited without the author's prior written authorization and constitutes copyright infringement. See LICENSE. Authorization requests: via the author's GitHub profile. Need for Speed IV and its game code/assets remain the property of Electronic Arts Inc.; no game assets or proprietary compiler binaries are distributed here.
📢 Official port project
The authorized port of this decompilation lives at XProger/NFSHS-PSX-decomp-port — NFS4 decomp project ports, by XProger. It is the only port carrying the author's written authorization under the LICENSE; any other port remains unauthorized.
A byte-matching decompilation of the PSX build of Need for Speed: High Stakes
(nfs4-f, EA 1999), in the Silent Hill decomp
mould: splat-split asm, INCLUDE_ASM-based mixed translation units, and objdiff
per-function matching against the original binary.
Goal: recompile each function to a byte-identical object (per-function 100% matching, objdiff/decomp.dev). This is the matching track — distinct from the sibling "compile + link + run" reconstruction (
reconstructed_headers/tree).
👉 Read METHODOLOGY.md FIRST
METHODOLOGY.md is the matching playbook — the two hard
rules (100%-only; the disasm-v4.txt oracle), the per-function workflow, the
technique catalog (symptom → fix), permuter usage, the data-mat pass, and the
live status. If you're an agent resuming after /compact, open it before
touching anything.
Status
The full pipeline is wired and proven end-to-end (splat → real PsyQ cc1 2.8.0
→ maspsx/aspsx 2.77 → GNU as → ELF → objdiff), CI-green on a Windows runner,
and live on decomp.dev.
Per-function matching is the active work. First module under attack is
game/common/aiinit (objdiff unit): 12/17 functions byte-perfect (50%);
overall 0.48%. See METHODOLOGY.md for the live table and
the remaining (gcc register-allocation–bound) near-misses.
Toolchain
The original was built with SN/PsyQ 4.3: cc1 GNU C 2.8.0 + ASPSX 2.77.
We drive the real cc1 and replace ASPSX with maspsx
- GNU
as(produces ELF for objdiff). Configured intools/build.py:
CC1PSX.EXE—C:/Temp/psq43/COMPILER/(real PsyQ compiler)maspsx.py—C:/Temp/maspsx-master/(--aspsx-version=2.77)mipsel-none-elf-{as,ld,objcopy}—C:/Tools/mips-ps1/mips/bin/(binutils 2.43)splat—C:/Temp/splat-main/(run under Python 3.12, which has rabbitizer/spimdisasm)
Build flags: cc1 -O2 -G4, maspsx --aspsx-version=2.77 -G4 (forces as -G0).
Layout
configs/nfs4.yaml splat config (PS-EXE @0x80010000, t_size 0x12E000)
configs/symbol_addrs.txt VA->name, generated from NFS4.MAP (tools/gen_symbols.py)
include/decomp/types.h s8/u32/q19_12 ... fixed-width + Q-format aliases
include/decomp/include_asm.h INCLUDE_ASM macro (maspsx-keep variant)
include/macro.inc glabel/jlabel/dlabel/alabel/endlabel (jlabel is global)
include/common.h master include for every src/*.c
src/main.c the main .text TU (C stubs + INCLUDE_ASM per fn)
asm/nonmatchings/main/*.s per-function disassembly
asm/data/*.s, asm/header.s data sections + PS-EXE header
linkers/nfs4.ld splat-generated linker script
rom/nfs4-f.exe the target (NOT committed — sha1 b48ea3452e64...)
rom/NFS4.MAP retail link map from the disc (NOT committed — md5
12326ebd37ed...)
rom/NFS4.SYM retail symbol file from the disc (NOT committed); tools/retail_sym.py
dumps it with tools/dumpsym/ into build/sym/ on first use
tools/build.py cpp->cc1->maspsx->as->ld->objcopy + byte-diff report
tools/gen_symbols.py NFS4.MAP -> symbol_addrs.txt (sanitizes . and case clashes)
tools/fix_gte.py rewrite GTE compute ops (rtps/mvmva/...) to .word
tools/audit_vtable_indexing.py reject _vf indexing that scales by a whole vtable row
Build
To produce the two disc files
NFS4.EXEandFRONT.BIN(byte-identical to retail) see BUILDING.md.
# 1. (re)generate symbols + split — only when configs change
py -3.12 tools/gen_symbols.py
py -3.12 C:/Temp/splat-main/split.py configs/nfs4.yaml
py -3.12 tools/fix_gte.py # GTE compute ops GNU as can't assemble
py -3.12 tools/audit_vtable_indexing.py # static vtable OOB regression audit
# 2. build + verify against rom/nfs4-f.exe
py -3.12 tools/build.py # full asm build + link + diff report
py -3.12 tools/build.py --skip-asm # C-only (objdiff / decomp.me style)
py -3.12 tools/build.py clean
Key solved gotchas
- cfront names (
_._14Xdtors,_vt.16Y) aren't valid C identifiers →gen_symbols.pyfolds illegal chars to_(byte-match only cares about addresses, not label spelling) and disambiguates case-insensitive clashes (CD_initvsCD_Initcollide as filenames on Windows). - GTE compute ops (rtps, rtpt, mvmva, nclip, gpf, ...) are unknown to GNU
as;fix_gte.pyrewrites them to.wordusing the exact bytes splat prints in each line's comment — zero encoding risk. - Jump tables live in the rodata object but target function-internal labels;
jlabelis defined global inmacro.incso the cross-object.word .L*references resolve (labels are link-time → no byte change).
Progress tracking (objdiff / decomp.dev)
objdiff.json diffs each unit's target (expected/ = asm build = original
bytes) against the base (build/ = --skip-asm, only decompiled C). Generate
the report locally:
py -3.12 tools/build.py --out expected --no-link # target objects (once)
py -3.12 tools/build.py --skip-asm # base objects
tools/objdiff/objdiff-cli.exe report generate -p . -o build/report.binpb -f proto
This is a per-object diff, so it does not depend on the whole-image link. Current overall: ~0.48 % and climbing as functions are matched one at a time (see METHODOLOGY.md for the per-function workflow and status).
decomp.dev ingests that report via CI:
.github/workflows/report.yml (windows runner, native cc1) builds it and uploads
the artifact nfs4-f_report. Then add the project on decomp.dev and install its
GitHub App. The workflow needs a one-time TOOLCHAIN_ZIP_URL secret = the toolchain
download link (PsyQ cc1 is copyrighted, so it's not committed). A Dropbox share
link works directly (the workflow forces dl=1) — see the workflow header.
Next steps (toward 100% match)
The active strategy is per-function matching, one function at a time, against
the objdiff expected/ (asm) target — see METHODOLOGY.md for
the workflow, technique catalog, and live status. In short:
- Finish the
aiinitunit (5 near-misses left, all gcc register-allocation / induction-variable bound — the hard tail). - Vendor the next reconstruction module as a new objdiff unit and repeat.
- Whole-image link/layout is a separate, later concern; matching is per-object and does not depend on it.