diff --git a/include/recompiler/context.h b/include/recompiler/context.h index e7b4431..afc7515 100644 --- a/include/recompiler/context.h +++ b/include/recompiler/context.h @@ -107,6 +107,12 @@ namespace N64Recomp { bool fixed_address = false; // Only used in mods, indicates that the section shouldn't be relocated or placed into mod memory. bool globally_loaded = false; // Only used in mods, indicates that the section's functions should be globally loaded. Does not actually load the section's contents into ram. std::optional got_ram_addr = std::nullopt; + // Content hash for runtime identification when multiple sections + // share a link vram (e.g. pattern-synthesized decompressed + // sections). Nonzero only for those; emitted into + // 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; }; struct ReferenceSection { diff --git a/src/decompressed.cpp b/src/decompressed.cpp index 1b0d129..4488ac7 100644 --- a/src/decompressed.cpp +++ b/src/decompressed.cpp @@ -476,7 +476,8 @@ size_t add_decompressed_section(Context& context, uint32_t rom_wrapper, uint32_t vram, const std::string& section_name, - bool relocatable) + bool relocatable, + uint64_t content_hash) { if (blob.size() < 0x20) { std::fprintf(stderr, @@ -520,6 +521,7 @@ size_t add_decompressed_section(Context& context, section.name = section_name; section.executable = true; section.relocatable = relocatable; + section.content_hash = content_hash; if (!parse_fragment_relocs(blob, vram, section_index, section)) { return size_t(-1); @@ -768,24 +770,31 @@ bool synthesize_decompressed_patterns( if (hits.empty()) continue; - // Deduplicate by content hash. + // Deduplicate by content hash. Hash window is the first 0x100 + // bytes — measured at 95% uniqueness for Stadium's 0x8FF00000 + // slot. The runtime side uses the SAME window over the bytes + // Stadium decompressed into RDRAM, so build-time and runtime + // hashes match. (Smaller fragments hash their full body.) + constexpr size_t HASH_WINDOW = 0x100; std::unordered_map seen_hashes; size_t added = 0; size_t deduped = 0; for (auto& [wrap_off, body] : hits) { - uint64_t h = fnv1a_64(body.data(), body.size()); - auto it = seen_hashes.find(h); + const size_t window = std::min(HASH_WINDOW, body.size()); + const uint64_t content_hash = + fnv1a_64(body.data(), window); + auto it = seen_hashes.find(content_hash); if (it != seen_hashes.end()) { deduped++; continue; } - seen_hashes.emplace(h, wrap_off); + seen_hashes.emplace(content_hash, wrap_off); 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); + section_name, p.relocatable, content_hash); if (si == size_t(-1)) { std::fprintf(stderr, "decompressed: pattern %s — failed to add section " diff --git a/src/main.cpp b/src/main.cpp index fb37cd8..0ff68da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1058,9 +1058,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} }},\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 }},\n", section.rom_addr, section.ram_addr, section.size, section_funcs_array_name, - section_relocs_array_name, section_relocs_array_size, section_index); + section_relocs_array_name, section_relocs_array_size, section_index, + section.content_hash); // Write the section's functions. fmt::print(overlay_file, "static FuncEntry {}[] = {{\n", section_funcs_array_name);