Skip to content

decomp: data in C, checked byte for byte in the boot ELF and every level - #179

Merged
mateuszklysz merged 59 commits into
mainfrom
decomp/data
Oct 14, 2026
Merged

mateuszklysz merged 59 commits into
mainfrom
decomp/data

Conversation

@mateuszklysz

@mateuszklysz mateuszklysz commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Before

Most game data (tables, constants, strings, level data) was raw bytes in the preserved blobs, reached through D_ labels declared ad hoc in each file with mixed types. The level data that was already in C was linked under the retail blob, so nothing checked it: for example, a wrong switch table in fun_0021abf8 passed. The report counted only code.

After

  • Data in C is proven.
    • Boot: RODATA_OVERLAYS, SDATA_OVERLAYS and DATA_OVERLAYS now carry (vma, offset, size). The link places the unit's section in place of the blob bytes and fails when the size differs, so the boot ELF hash covers the C data.
    • verify-baseline.sh fails when a label is linked away from its address.
    • Levels: every D_LNN_XXXXXXXX object defined in C has a row in config/overlays/us/data.tsv. make overlays compares its bytes, with pointers resolved, to what the level loads there (check_data in scripts/overlay_proof.py).
    • D_XXXXXXXX rows (src/overlays/shared/data/level_globals.c) are checked in every level.
  • Level data in C.
    • 3895 level data objects across levels 00–18, defined after the functions that read them.
    • Tables read as rows are typed structs. For example, the hero velocity curves are struct HeroVelocityKey, which hero_integrate_velocity reads.
  • Tooling.
    • scripts/data-refs.py catalogues the data the code touches (--catalog). It writes a definition plus its *_OVERLAYS entry or data.tsv row (--emit ADDR --into FILE [--level N]).
    • scripts/data-decls.py lists labels whose declarations still disagree, ratcheted in test_public_tools.py.
  • Headers. Named globals moved out of include/rnc/data_labels.h into subsystem headers (include/rnc/<subsystem>/...). Globals several level files share, whose meaning is unknown, are listed by address in include/rnc/overlay/level_labels.h.
  • Report and map.
    • report.json and decomp_map.json count data (total_data, matched_data, a data object per part). bytes_matching_c_zero says how much of the matched data is zeros: most of the overlay figure is large zero-filled level tables.
    • The progress map shows the boot ELF on the left 3/4 and the whole-game figure plus data in C on the right.
  • Docs. The Data sections in CONTRIBUTING.md, docs/code-cleanup.md, docs/overlays.md and docs/progress-metrics.md cover the steps, naming and placement rules, the proof and the metric.
  • Merged with the latest main. The configured .rodata sizes for printf_impl and _sceFs_Rcv_Intr are measured, and main's active_decode_buffer name is kept.

Verification

  • make elf ends with PASS: reconstructed boot ELF matches retail
  • make overlays ends with PASS: all 2809 overlay functions in C match retail, with their 17298 copies in other places, and all 3895 level data objects in C
  • make check passes (99 tool tests, script parse, report, map, layout checks)

The retail switch table at 0x1E8770 sends action 1 to the same place as
action 0 (break), not to the action 3 arm. The code bytes were the same
either way, and the preserved core_rdata blob hid the wrong table entry.
The .rodata, .sdata and .data of the units in RODATA_OVERLAYS,
SDATA_OVERLAYS and DATA_OVERLAYS used to be linked on top of the
preserved blobs, and the blob bytes won. A wrong value in C still gave
PASS, and data in a unit no table listed was dropped without a word.

Each entry now also gives its size. The blobs are linked as the pieces
between the C-owned ranges, so the gate hashes what the C compiled to.
The link fails when a C section is not its configured size, when an
entry names no configured unit, and when a unit defines data no entry
places. render_level_effects_and_screen_sprites gets back the entry for
its panel slot table, left under the unit's old name.
A D_LNN_XXXXXXXX object defined in src/overlays is compared with what
level NN loads at XXXXXXXX, every pointer in it resolved to that level's
address of its symbol (check_data, overlay-data-bytes-v1). make overlays
runs it for every data object in C, fails on data without a D_LNN label,
and checks config/overlays/us/data.tsv lists each one with its size.
total_data is the executable's preserved data and each level's .lit,
.data and vtables; matched_data is the data C defines and a proof checks:
the *_OVERLAYS entries (the boot gate) and config/overlays/us/data.tsv
(make overlays). bss is not counted.
BSS_OVERLAYS (VMA, size) and SBSS_OVERLAYS (VMA, offset, size) place a
unit's uninitialised variables; the preserved bss is linked as the pieces
around them and drops the splat labels the unit now defines. bss has no
file bytes, so the gate also checks that every D_/FUN_/jtbl_ label is
linked at its own address. Common symbols link with their unit's bss.

data-refs.py --emit now covers every boot data section (const for
core_rdata, section(".bss") for bss) and, with --level N, level data:
values from the level's records, pointers named after the level's
functions and data, and the row added to config/overlays/us/data.tsv.
A level's small bss sits in its .lit as zeros; the data proof accepts it.
data-refs --emit now prints the shortest exact float, one row per line
for nested arrays, and turns an extern in the target file into the
definition.
snd_current_buffer, frm_clock_time and vif_chain_flags were declared
with another type than their definition; D_0015EC80 also sat in
undefined_syms.txt, which overrides the C definition.
Every D_ label now has one declaration with one name: plain local
externs that repeat a header or another file are removed and their uses
renamed. A label used by several files is declared once in
include/rnc/data_labels.h (boot) or include/rnc/overlay/level_labels.h
(levels) until its subsystem is known.

Placements stay as they were: a file that reaches a label with sda,
NOT_SDA or MACRO_ADDR keeps one alias for it, named <name>_gp, _lui or
_macro when the plain name is also visible. A function that reads a
label through two names keeps both (the second name is a lever).

Labels whose declarations disagree on the type are left for the next
step.
scripts/data-decls.py lists the D_ labels that src/ and include/ declare
with more than one type or name (placement aliases _gp, _lui and _macro
count as one name). make check fails when the count grows; it is lowered
as each label is typed by hand.
Semaphore ids (sceCd, sceMc) are s32: they are compared with -1 and < 0.
create_moby compares spawn_frame with the frame counter unsigned, so it
casts there instead of changing the counter's type.
002deee0.c keeps its list: removing it changes the unit's code.
Each file's alias takes the label's real name, plus _gp, _lui or _macro
when the file needs another placement.
…jects)

The proof resolves a reference to data the file itself defines (the
assembler points at the section, gp-relative offsets minus gp0).
Hero step tables are int[4] in .sdata and the unk3E4 pointee is a
HeroVelocityKey; l16/l17 vertex count and vertex pointer tables hold their
counts and point at the named level data; offset vectors read by index are
float[4].
Hero step tables (0015F6D8, L05 0015F6E8) are int[4] in .sdata, the
0015F6E8 pointee is the char[4] the shared hero code declares, the l02
shorts read as ints are ints, the l02 offset vector is float[4], and the
l08 vertex/colour tables point at their named level data. Scalars drop the
{0} braces; l07 scroll phase no longer shadows its local.
# Conflicts:
#	configure.py
#	src/textbin/world/data/select_world_object_resource_tables.c
@mateuszklysz mateuszklysz added the enhancement New feature or request label Oct 14, 2026 — with Claude
@decomp-dev

decomp-dev Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Report for SCUS_971.99 (5fe15c8 - 29b2edc)

Total data: 12433372 bytes (+12433372 bytes)
📈 Matched data: 32.31% (+32.31%, +4017559 bytes)
📈 Linked data: 32.31% (+32.31%, +4017559 bytes)

No changes

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Decompilation progress

C_EXACT Change Matched bytes Functions C_FUZZY
All code 87.79 % — 3,113,144 / 3,546,268 B (—) 3,967 / 4,233 (—) 89.03 %
Boot executable 85.65 % — 302,216 / 352,840 B (—) 1,163 / 1,219 (—) 98.13 %
Game 84.51 % — 230,576 / 272,844 B (—) 866 / 905 (—) 98.40 %
SDK 89.55 % — 71,640 / 79,996 B (—) 297 / 314 (—) 97.21 %
Level overlays 88.02 % — 2,810,928 / 3,193,428 B (—) 2,804 / 3,014 (—) 88.02 %
Shared level code 79.95 % — 903,868 / 1,130,492 B (—) 1,438 / 1,583 (—) 79.95 %
Per-level code 92.44 % — 1,907,060 / 2,062,936 B (—) 1,366 / 1,431 (—) 92.44 %
Data in C Change Matched bytes
All data 32.31 % +32.31 pp 4,017,559 / 12,433,372 B (+4,017,559 B)
Boot executable 2.16 % +2.16 pp 15,091 / 699,136 B (+15,091 B)
Level overlays 34.11 % +34.11 pp 4,002,468 / 11,734,236 B (+4,002,468 B)

No function changed its match state compared with main (ce39091).

Compared with main (ce39091). C_FUZZY uses main's last measurement.

@mateuszklysz
mateuszklysz merged commit 9745b86 into main Oct 14, 2026
3 checks passed
@mateuszklysz
mateuszklysz deleted the decomp/data branch October 14, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant