Files
Drew T 757bd82a0f feat(phase-31): S79 #4 — scattered-.bss split at link-prepare (psyq_bss_split): SYS.o→libgpu2, VM_F.o→snd12, GS_001.o→libgs8 LINKED; libgpu_used retired
The §9.1 "scattered .bss commons" exclusion class (Phase 8 → P31) is closed 3/3. New
tools/psyq_bss_split.py (own ELF32 REL reader/writer) cuts an object's packed .bss into
per-base NOBITS pieces: bases derived from the game bytes per HI16/LO16 pair, references
walked in offset order into single-base runs, cuts snapped to symbol starts (the linker
scattered SYMBOLS), symbols moved, a LOCAL section symbol per piece inserted, relocs
retargeted with the addend rewritten in the immediates, self-diffed. It runs inside the one
prepare step shared by psyq_link.link_object / psyq_link_region.build_region /
psyq_integrate.integrate (prepare_object before classify), re-derived every build.

GS_001.o was certified "5 interleaved bases, NOT splittable" by the S77 probe, which grouped
by BASE; by RUN it is six symbol-aligned pieces. All seven cuts across the three objects are
confirmed by the other objects' by-name recoveries (_que 0x800C5510, _svm_sreg_buf
0x800B9B58, PSDBASEX/CLIP2/PSDBASEY/POSITION/GsDRAWENV). R39 negative control: 235 placed
objects across 9 curated dirs, 0 refusals, exactly 3 splits (a libcd .bss+size end pointer
refused the first build → reference problems are fatal only when a split is needed).

Wiring: yaml 800c→libgpu2, sgap_6→sgap_6+snd12, gsgap3→libgs8 (comments rewritten);
LIBGPU_ELF := .run/obj40/libgpu (curated libgpu_used retired); libgs 34 objs/8 blocks
(make_libgs.sh +GS_001); snd 63/12 (make_snd_used.py exclusions 4→3). src/800c.c and
src/gsgap3.c removed (Sony code hand-matched as REAL/verbatim), sgap_6.c keeps only
func_8003FA54; splat-emitted libgpu2.c/libgs8.c/snd12.c stubs for the no-SDK fallback.

Verified: main 143dbb89f34491258bbc27810d0a12ec8b43a8dd WITH the SDK objects and WITHOUT
them from a fresh extract; make tools-health OK; R22 fleet clean extract-all 212/212 +
check-all 213/213. Metrics: main REAL 886→839, LINKED 1,040→1,150, VERBATIM 85→29, stubs 29
(unchanged); game-code weighted 91.1% (40,895/44,870) — both terms lost the 3,667 SDK ins;
the remainder is still exactly the 3,975-ins open-stub sum. Verbatim manifest --update
200→33 rows (subtractive). Docs: cookbook §489 (+index), psyq-worklist rows + "S78 task #4",
SETUP S79 R21 table, decision-log S79 addendum, accelerators S79, CURRENT_PHASE S79 FINAL 🛑.
2026-09-04 17:19:29 -06:00

81 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Build the curated .run/obj40/snd_used dir = the combined libspu+libsnd sound region (Phase 8).
libspu and libsnd are tightly interleaved in 0x8003A444..0x8004239C (the tail of the 800 subseg), so
they are linked as ONE combined region rather than two passes. This:
- places both libraries' objects (psyq_identify) and merges them by vram,
- for an aliased address (>1 object, same masked .text) picks the object whose linked .text
byte-matches the EXE (psyq_link.link_object) — the real one,
- EXCLUDES 3 ADDRESSES that don't reconcile in the combined region (ALL candidates there stay
byte-identical stubs — excluding by address, not name, since the alias twin fails identically):
0x3C438 (S_R/S_W), 0x3D424 (S_GRMDT/FB/T) — commons referenced at a minority address the
region's single defsym can't satisfy (cookbook §9.1, cross-object form; these objects have NO
.bss of their own — psyq_bss_probe — so the S78 split does not apply to them),
0x3D94C (S_IH/UT_RON) — false placement: 0x3D94C is INSIDE libsnd SSSTART.o.
0x3FA64 (VM_F.o, 237 ins) was the 4th exclusion (scattered-.bss commons) until P31 S78 #4: its
`.bss` is now SPLIT at link-prepare (psyq_bss_split, cookbook §489) and it links as snd12.
- copies the survivors into .run/obj40/snd_used.
The build is byte-identical with OR without snd_used (stub fallback), so a fresh clone need not run
this unless it wants the SDK objects linked. Regenerate: tools/psyq_build_libs.sh LIBSPU LIBSND first.
"""
import os, re, shutil, subprocess, sys
from collections import defaultdict
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from psyq_link import link_object # noqa: E402
# EXE-curation tool: --exe/--vram-base default to the retail EXE's values (threaded explicitly into
# psyq_identify + link_object, which require them post-T8). RLO/RHI/EXCLUDE_ADDR are the EXE's sound
# region + the 4 unreconcilable addresses (EXE-specific; an overlay would supply its own).
EXE = "extracted/retail/SLUS_007.26"
VRAM_BASE = 0x8000F800
RLO, RHI = 0x8003A444, 0x8004239C
EXCLUDE_ADDR = {0x8003C438, 0x8003D424, 0x8003D94C} # see module docstring (VM_F 0x8003FA64 rejoined S78 #4)
def place(lib, exe, vram_base):
out = subprocess.check_output(["python3", "tools/psyq_identify.py", f".run/obj40/{lib}",
"--vram-base", hex(vram_base), "--exe", exe], text=True)
d = {}
for ln in out.splitlines():
m = re.match(r"\s+0x([0-9A-Fa-f]+)\s+(\S+\.o)\s+\((\d+) ins\)", ln)
if m:
d[m.group(2)] = int(m.group(1), 16)
return d
def main():
import argparse
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--exe", default=EXE)
ap.add_argument("--vram-base", default=hex(VRAM_BASE))
args = ap.parse_args()
exe_path, vram_base = args.exe, int(args.vram_base, 0)
exe = open(exe_path, "rb").read()
byaddr = defaultdict(list)
for lib in ("libspu", "libsnd"):
for nm, a in place(lib, exe_path, vram_base).items():
byaddr[a].append((lib, nm))
dst = ".run/obj40/snd_used"
shutil.rmtree(dst, ignore_errors=True)
os.makedirs(dst)
n = 0
for a in sorted(byaddr):
if not (RLO <= a < RHI) or a in EXCLUDE_ADDR:
continue
cands = byaddr[a]
pick = next(((lib, nm) for lib, nm in cands
if link_object(f".run/obj40/{lib}/{nm}", a, name=nm, exe_bytes=exe,
vram_base=vram_base)["ok"]),
cands[0])
lib, nm = pick
shutil.copy(f".run/obj40/{lib}/{nm}", f"{dst}/{nm}")
n += 1
print(f"snd_used: {n} objects (libspu+libsnd combined; {len(EXCLUDE_ADDR)} addresses excluded: "
f"{[hex(x) for x in sorted(EXCLUDE_ADDR)]})")
if __name__ == "__main__":
main()