diff --git a/emulators/lime3ds.yml b/emulators/lime3ds.yml index 0a9f6042..71b49d8a 100644 --- a/emulators/lime3ds.yml +++ b/emulators/lime3ds.yml @@ -101,7 +101,7 @@ files: size: 273 validation: [size] note: "console region and serial number; loaded on a size check alone" - source_ref: "src/core/hle/service/cfg/cfg.cpp:689-709, src/core/hle/service/cfg/cfg.h:180-186" + source_ref: "src/core/hle/service/cfg/cfg.cpp:608-610, 689-709, src/core/hle/service/cfg/cfg.h:180-186" - name: LocalFriendCodeSeed_B path: nand/rw/sys/LocalFriendCodeSeed_B @@ -110,4 +110,4 @@ files: size: 272 validation: [size] note: "friend code generation seed; loaded on a size check alone" - source_ref: "src/core/hle/service/cfg/cfg.cpp:711-732, src/core/hle/service/cfg/cfg.h:188-193" + source_ref: "src/core/hle/service/cfg/cfg.cpp:604-606, 711-732, src/core/hle/service/cfg/cfg.h:188-193" diff --git a/emulators/mandarine.yml b/emulators/mandarine.yml index 4db41d2c..3470d747 100644 --- a/emulators/mandarine.yml +++ b/emulators/mandarine.yml @@ -111,7 +111,7 @@ files: size: 273 validation: [size] note: "console region and serial number; loaded on a size check alone, and paired with LocalFriendCodeSeed_B" - source_ref: "src/core/hle/service/cfg/cfg.cpp:689-709, src/core/hle/service/cfg/cfg.h:180-186" + source_ref: "src/core/hle/service/cfg/cfg.cpp:608-610, 689-709, src/core/hle/service/cfg/cfg.h:180-186" - name: LocalFriendCodeSeed_B path: nand/rw/sys/LocalFriendCodeSeed_B @@ -120,4 +120,4 @@ files: size: 272 validation: [size] note: "friend code generation seed; loaded on a size check alone, and paired with SecureInfo_A" - source_ref: "src/core/hle/service/cfg/cfg.cpp:711-732, src/core/hle/service/cfg/cfg.h:188-193" + source_ref: "src/core/hle/service/cfg/cfg.cpp:604-606, 711-732, src/core/hle/service/cfg/cfg.h:188-193" diff --git a/emulators/opentyrian.yml b/emulators/opentyrian.yml index 81e19c16..f471e4c8 100644 --- a/emulators/opentyrian.yml +++ b/emulators/opentyrian.yml @@ -240,40 +240,40 @@ files: # Level tileset shapes (loaded per-level from .lvl data) # The level file specifies a character via fgetc() at tyrian2.c:3039, # which maps to shapes{char}.dat at tyrian2.c:3079. - # Only 5 characters are used by actual levels: ), W, X, Y, Z. + # Episode 4 uses Z, W, X and } (lvlmast.c:128-131). # Note: shapeFile[34] in lvlmast.c is a DIFFERENT mechanism — # it maps indices to characters for newsh?.shp (enemy sprites), # NOT for shapes?.dat (level tilesets). # ----------------------------------------------------------- - - name: "shapes).dat" + - name: "shapes}.dat" category: game_data system: tyrian required: true - source_ref: "src/tyrian2.c:3039-3079" + source_ref: "src/tyrian2.c:3039, 3079, src/lvlmast.c:128-131" - name: "shapesw.dat" category: game_data system: tyrian required: true - source_ref: "src/tyrian2.c:3039-3079" + source_ref: "src/tyrian2.c:3039, 3079, src/lvlmast.c:128-131" - name: "shapesx.dat" category: game_data system: tyrian required: true - source_ref: "src/tyrian2.c:3039-3079" + source_ref: "src/tyrian2.c:3039, 3079, src/lvlmast.c:128-131" - name: "shapesy.dat" category: game_data system: tyrian required: true - source_ref: "src/tyrian2.c:3039-3079" + source_ref: "src/tyrian2.c:3039, 3079, src/lvlmast.c:128-131" - name: "shapesz.dat" category: game_data system: tyrian required: true - source_ref: "src/tyrian2.c:3039-3079" + source_ref: "src/tyrian2.c:3039, 3079, src/lvlmast.c:128-131" # ----------------------------------------------------------- # Compressed shape files (newshX.shp, loaded per-level) diff --git a/emulators/same_cdi.yml b/emulators/same_cdi.yml index 2c9dacc6..548eee4a 100644 --- a/emulators/same_cdi.yml +++ b/emulators/same_cdi.yml @@ -42,7 +42,7 @@ files: system: cdi required: true note: "Forced system. Main CPU BIOS (3 region variants via MAME BIOS switch) + servo/slave MCU dumps." - source_ref: "src/mame/drivers/cdi.cpp:428-443" + source_ref: "src/mame/drivers/cdi.cpp:829-443" contents: - name: cdi200.rom description: Magnavox CD-i 200 BIOS (default, US) @@ -100,7 +100,7 @@ files: system: cdi required: false note: "CD-i BIOS for Quizard arcade. MACHINE_IS_BIOS_ROOT. Subset of cdimono1 + slave MCU." - source_ref: "src/mame/drivers/cdi.cpp:630-640" + source_ref: "src/mame/drivers/cdi.cpp:836-640" contents: - name: cdi200.rom description: Magnavox CD-i 200 BIOS (same as cdimono1) @@ -124,7 +124,7 @@ files: system: cdi required: false note: "CD-i 910-17P Mini-MMC (PAL). MACHINE_NOT_WORKING. Not in .info firmware list." - source_ref: "src/mame/drivers/cdi.cpp:319-335" + source_ref: "src/mame/drivers/cdi.cpp:831-335" contents: - name: "philips__cd-i_2.1__mb834200b-15__26b_aa__9224_z01.tc574200.7211" description: CD-i 910-17P Mini-MMC BIOS (default) diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 4086e00d..dafb712d 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -746,7 +746,7 @@ def branch_in_url(url: str) -> str | None: SELF_CHECK_CONTEXT = 2 -def verify_at_pin(part: RefPart, pin_lines, tokens) -> PartResult: +def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResult: """Check a ref against its own revision instead of against HEAD. A profile pinned to a superseded tag documents a program HEAD no longer @@ -780,7 +780,12 @@ def verify_at_pin(part: RefPart, pin_lines, tokens) -> PartResult: return PartResult(part, "ANCHORED", None, None, None, []) # A ref that cites loading logic never spells the value out, so its absence # here proves nothing. Only finding the value somewhere else in the file - # shows the ref points at the wrong place. + # shows the ref points at the wrong place -- and only a hash carries that + # weight. A filename fragment matches by coincidence: `tyrian2` hits every + # line naming `tyrian2.c`, and a name built at runtime from a format + # string appears nowhere at all. + if not hash_tokens: + return PartResult(part, "ANCHORED", None, None, None, []) elsewhere = sorted( { index @@ -877,6 +882,7 @@ def build_report( entry.get("name", "") + (f" [{label}]" if label else ""), value, _anchor_tokens(entry), + entry_hashes(entry), ) for entry in (profile.get("files") or []) if isinstance(entry, dict) and entry.get("source_ref") @@ -1028,10 +1034,12 @@ def build_report( if self_check: staged = [ (entry_name, ref, reconcile_self_check([ - verify_at_pin(part, fetch(PIN, part.path, part.start), tokens) + verify_at_pin( + part, fetch(PIN, part.path, part.start), tokens, hashes + ) for part in split_source_ref(ref) ])) - for entry_name, ref, tokens in refs + for entry_name, ref, tokens, hashes in refs ] else: staged = [ @@ -1039,7 +1047,7 @@ def build_report( anchor_part(part, fetch, rename_getter, describe, tokens) for part in split_source_ref(ref) ]) - for entry_name, ref, tokens in refs + for entry_name, ref, tokens, hashes in refs ] shifts = dominant_shifts([p for _, _, parts in staged for p in parts]) diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 2c06db70..9c5ccaec 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -337,14 +337,18 @@ class TestVerifyAtPin(unittest.TestCase): def test_declared_value_carried_by_another_line(self): lines = ["load()", "", "", "", "", "", 'rom("cafebabe")'] part = RefPart("a.c", 1, 1, "a.c:1") - result = profile_sync.verify_at_pin(part, lines, ["cafebabe"]) + result = profile_sync.verify_at_pin( + part, lines, ["cafebabe"], ["cafebabe"] + ) self.assertEqual(result.status, "MOVED") self.assertEqual(result.start, 7) def test_declared_value_on_several_lines_is_ambiguous(self): lines = ["load()", "", "", "", 'a("cafebabe")', "", 'b("cafebabe")'] part = RefPart("a.c", 1, 1, "a.c:1") - result = profile_sync.verify_at_pin(part, lines, ["cafebabe"]) + result = profile_sync.verify_at_pin( + part, lines, ["cafebabe"], ["cafebabe"] + ) self.assertEqual(result.status, "AMBIGUOUS") def test_one_anchored_part_settles_the_others(self): @@ -386,6 +390,13 @@ class TestVerifyAtPin(unittest.TestCase): result = profile_sync.verify_at_pin(part, None, ["cafebabe"]) self.assertEqual(result.status, "EXTERNAL") + def test_a_name_fragment_elsewhere_is_not_evidence(self): + # Only a hash is unique enough to prove a ref points elsewhere. + lines = ["load()", "", "", "", "", "", "tyrian2.c line"] + part = RefPart("a.c", 1, 1, "a.c:1") + result = profile_sync.verify_at_pin(part, lines, ["tyrian2"], []) + self.assertEqual(result.status, "ANCHORED") + def test_missing_file(self): part = RefPart("a.c", 1, 1, "a.c:1") self.assertEqual(