mirror of
https://github.com/N64Recomp/N64Recomp.git
synced 2026-05-15 07:29:46 -05:00
decompressed: per-variant synthetic link identities for pattern fragments
Path 2 of the pattern-fragment dispatch architecture: each variant of a [[input.decompressed_section_pattern]] now gets a unique link-time ram_addr from a synthetic vram pool (0xC0000000+, KSEG2/KSEG3 — unused by N64 software so it can't collide with engine-resident sections like RSP at 0xA4000000+). Why: when multiple variants share a single canonical link bucket (e.g. all stadium_models pattern variants at 0x8FF00000), runtime fragment-vaddr resolution via gFragments[id] is single-pointer and ambiguous when more than one variant is host-resident at the same time. Per-variant synthetic ram_addrs make each variant's RELOC_HI16 / RELOC_LO16 emit produce a unique 0xCXXXXXXX literal at runtime, giving variant-internal references unambiguous identity without depending on caller PC, host stack walks, or data-context tracking. Implementation: - add_decompressed_section accepts an override_link_ram_addr param. The bytes-encoded `vram` (= canonical link bucket) is passed to parse_fragment_relocs and discover_function_bounds (so jump tables resolve correctly against the body's encoded references), while section.ram_addr is set to the override. The two roles of vram are cleanly separated. - New original_pattern_id field on Section. Populated for synthetic- link variants with the original game-side fragment id derived from the pattern's canonical bucket (e.g. 0xEF for stadium_models). Lets the runtime candidate filter know which game id should include this synthetic section as a candidate, eliminating cross- pattern hash-collision misregistration. - main.cpp emit: section_load_table now writes original_pattern_id into the SectionTableEntry initializer. - decompressed.cpp pattern loop: every unique variant now gets synthetic ram_addr = 0xC0000000 + variant_idx * 0x100000 (1 MB stride, ~286 KB largest observed variant). For Stadium's 279 unique variants the pool occupies 0xC0000000..0xCDB00000, well within the runtime-side 512-bucket capacity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
745046ca43
commit
6f9649c7e7
|
|
@ -113,6 +113,16 @@ namespace N64Recomp {
|
|||
// recomp_overlays.inl's SectionTableEntry.content_hash. Computed
|
||||
// as FNV-1a-64 of the first 0x40 bytes of the section's body.
|
||||
uint64_t content_hash = 0;
|
||||
// Original game-side fragment id for pattern variants whose
|
||||
// section.ram_addr was reassigned to a synthetic per-variant
|
||||
// identity (e.g. a pattern variant for stadium_models keeps
|
||||
// original_pattern_id=0xEF even though its ram_addr is in the
|
||||
// synthetic-vram pool). Allows the runtime
|
||||
// register_runtime_fragment to know "this synthetic-pool section
|
||||
// is a candidate for game id X" without having to derive it from
|
||||
// ram_addr (which no longer encodes the original id).
|
||||
// 0xFFFFFFFF means "not a pattern variant" (no override applies).
|
||||
uint32_t original_pattern_id = 0xFFFFFFFFu;
|
||||
};
|
||||
|
||||
struct ReferenceSection {
|
||||
|
|
|
|||
|
|
@ -480,8 +480,23 @@ size_t add_decompressed_section(Context& context,
|
|||
uint32_t vram,
|
||||
const std::string& section_name,
|
||||
bool relocatable,
|
||||
uint64_t content_hash)
|
||||
uint64_t content_hash,
|
||||
uint32_t override_link_ram_addr = 0,
|
||||
uint32_t original_pattern_id = 0xFFFFFFFFu)
|
||||
{
|
||||
// `vram` is the BYTES-ENCODED vram — what the body's R_MIPS_HI16/LO16
|
||||
// / R_MIPS_32 / J/JAL targets are encoded relative to. The CFG walker
|
||||
// and reloc parser need this value (otherwise jump-table entries
|
||||
// resolve to the wrong section, etc.).
|
||||
//
|
||||
// `override_link_ram_addr` (if non-zero) is the section's LINK
|
||||
// IDENTITY — what gets stored in section.ram_addr and used at
|
||||
// runtime as section_addresses[N]'s initial value. For pattern
|
||||
// variants we want this DIFFERENT from `vram` so multiple variants
|
||||
// can have unique link identities while sharing the canonical
|
||||
// bytes-encoded vram.
|
||||
const uint32_t link_ram_addr =
|
||||
(override_link_ram_addr != 0) ? override_link_ram_addr : vram;
|
||||
if (blob.size() < 0x20) {
|
||||
std::fprintf(stderr,
|
||||
"decompressed: section %s blob smaller than FRAGMENT header\n",
|
||||
|
|
@ -544,13 +559,19 @@ size_t add_decompressed_section(Context& context,
|
|||
|
||||
Section section{};
|
||||
section.rom_addr = synthetic_rom;
|
||||
section.ram_addr = vram;
|
||||
// Section identity (link_ram_addr) may differ from the bytes-encoded
|
||||
// vram (`vram`) for pattern variants that get assigned a synthetic
|
||||
// per-variant link identity. The reloc parser stays with the
|
||||
// bytes-encoded vram so target_section_offset values are correct
|
||||
// intra-section byte distances.
|
||||
section.ram_addr = link_ram_addr;
|
||||
section.size = reloc_offset;
|
||||
section.bss_size = 0;
|
||||
section.name = section_name;
|
||||
section.executable = true;
|
||||
section.relocatable = relocatable;
|
||||
section.content_hash = content_hash;
|
||||
section.original_pattern_id = original_pattern_id;
|
||||
|
||||
if (!parse_fragment_relocs(blob, vram, section_index, section)) {
|
||||
return size_t(-1);
|
||||
|
|
@ -816,6 +837,29 @@ bool synthesize_decompressed_patterns(
|
|||
std::unordered_map<uint64_t, size_t> seen_hashes;
|
||||
size_t added = 0;
|
||||
size_t deduped = 0;
|
||||
// Path 2: every unique pattern variant gets its own synthetic
|
||||
// per-variant ram_addr in the 0xC0000000+ sentinel pool. The
|
||||
// bytes-encoded vram (parsing/CFG) stays at p.vram for ALL
|
||||
// variants — only section.ram_addr (the link identity) changes,
|
||||
// so each variant's RELOC_HI16/LO16 macros emit a unique
|
||||
// 0xCXXXXXXX literal at runtime and the synthetic resolver
|
||||
// can translate that literal back to the variant's runtime
|
||||
// RDRAM buffer.
|
||||
//
|
||||
// Pool placement: 0xC0000000 is KSEG2, unused by N64 software,
|
||||
// so the sentinel is "obviously invalid as an N64 vaddr" and
|
||||
// can only be handled by the recomp synthetic resolver. KSEG1
|
||||
// (0xA0000000) was tried first but collides with the engine's
|
||||
// RSP code section at 0xA4000040.
|
||||
//
|
||||
// Stride 0x00100000 = 1 MB per variant. With Stadium's 219
|
||||
// variants, the pool occupies 0xC0000000..0xCDB00000 — well
|
||||
// within the 256-slot capacity (kSyntheticBucketCount on the
|
||||
// runtime side). If the variant count grows past 256 in some
|
||||
// future game, both sides need to bump the pool size.
|
||||
const uint32_t kSyntheticPoolBase = 0xC0000000u;
|
||||
const uint32_t kSyntheticPoolStride = 0x00100000u;
|
||||
size_t probe_variant_idx = 0;
|
||||
for (auto& [wrap_off, body] : hits) {
|
||||
const size_t window = std::min(HASH_WINDOW, body.size());
|
||||
const uint64_t content_hash =
|
||||
|
|
@ -827,11 +871,29 @@ bool synthesize_decompressed_patterns(
|
|||
}
|
||||
seen_hashes.emplace(content_hash, wrap_off);
|
||||
|
||||
// Original game-side fragment id derived from the pattern's
|
||||
// canonical bucket. All variants of this pattern share the
|
||||
// same original id (e.g. 0xEF for stadium_models). Stored
|
||||
// on each section so the runtime can filter synthetic
|
||||
// candidates to the matching id and avoid cross-pattern
|
||||
// hash-collision misregistration.
|
||||
const uint32_t orig_id =
|
||||
((p.vram & 0x0FF00000u) >> 0x14) - 0x10u;
|
||||
|
||||
// Per-variant synthetic link identity. The bytes-encoded
|
||||
// vram (used for parsing/CFG) stays at p.vram — only this
|
||||
// link identity changes per variant.
|
||||
const uint32_t variant_link_addr =
|
||||
kSyntheticPoolBase +
|
||||
uint32_t(probe_variant_idx) * kSyntheticPoolStride;
|
||||
probe_variant_idx++;
|
||||
|
||||
const std::string section_name = fmt::format(
|
||||
"{}__rom_{:X}", base_name, wrap_off);
|
||||
size_t si = add_decompressed_section(
|
||||
context, body, wrap_off, p.vram,
|
||||
section_name, p.relocatable, content_hash);
|
||||
section_name, p.relocatable, content_hash,
|
||||
variant_link_addr, orig_id);
|
||||
if (si == size_t(-1)) {
|
||||
// Hard failure: the section's bytes can't be bounded
|
||||
// by our CFG walk (or some other unrecoverable parse
|
||||
|
|
|
|||
|
|
@ -1027,10 +1027,10 @@ int main(int argc, char** argv) {
|
|||
std::string section_relocs_array_size = section_relocs.empty() ? "0" : fmt::format("ARRLEN({})", section_relocs_array_name);
|
||||
|
||||
// Write the section's table entry.
|
||||
section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .relocs = {4}, .num_relocs = {5}, .index = {6}, .content_hash = 0x{7:016X}ull }},\n",
|
||||
section_load_table += fmt::format(" {{ .rom_addr = 0x{0:08X}, .ram_addr = 0x{1:08X}, .size = 0x{2:08X}, .funcs = {3}, .num_funcs = ARRLEN({3}), .relocs = {4}, .num_relocs = {5}, .index = {6}, .content_hash = 0x{7:016X}ull, .original_pattern_id = 0x{8:08X}u }},\n",
|
||||
section.rom_addr, section.ram_addr, section.size, section_funcs_array_name,
|
||||
section_relocs_array_name, section_relocs_array_size, section_index,
|
||||
section.content_hash);
|
||||
section.content_hash, section.original_pattern_id);
|
||||
|
||||
// Write the section's functions.
|
||||
fmt::print(overlay_file, "static FuncEntry {}[] = {{\n", section_funcs_array_name);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user