From 81b4d024ebd06bbf4e5625033c2a9476909be7ca Mon Sep 17 00:00:00 2001 From: Pegasus Date: Wed, 9 Sep 2026 14:24:52 +0300 Subject: [PATCH] Seal source reference paths independently of consumer working directory (cherry picked from commit 24dd5a3f8baed46737b5e2c23ca3996e7e7cf581) --- tools/tasreplays/pepsiman.py | 14 +++++++++++--- tools/tasreplays/test_pepsiman.py | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tools/tasreplays/pepsiman.py b/tools/tasreplays/pepsiman.py index 45d14c35..d98f5cf7 100644 --- a/tools/tasreplays/pepsiman.py +++ b/tools/tasreplays/pepsiman.py @@ -45,6 +45,14 @@ digest=tekken3.digest write=tekken3.write_json command=tekken3.command +def control_binding_path(control, binding): + path=Path(binding['path']) + # Historical stock-control-v1 launchers accepted a reference relative to + # the campaign directory containing the control runs. Seal absolute paths + # so consumers launched from isolated case directories retain that meaning. + if not path.is_absolute(): path=control.parent/path + return path.resolve(strict=True) + def verify_control_identity(path, manifest, tail, role='stock'): if role not in {'stock','observer'}: raise ValueError('explicit stock/observer role required') if manifest.get('role',role)!=role: raise ValueError('source role differs from requested admission') @@ -53,7 +61,7 @@ def verify_control_identity(path, manifest, tail, role='stock'): raise ValueError('source manifest does not describe the completed original movie') bindings=manifest['bindings'] for binding in bindings: - target=Path(binding['path']) + target=control_binding_path(path,binding) # Legacy controls bound the host's writable config. A separately recorded # byte-identical copy preserves that input if EmuHawk rewrites it on exit. if target.name=='config.ini' and (path/'launch-config.json').exists(): @@ -376,11 +384,11 @@ def reference(args): observed/'manifest.json',stock/'manifest.json',*compared_files] for directory in [stock,observed]: manifest=json.loads((directory/'manifest.json').read_text()) - files += [Path(b['path']) if Path(b['path']).name!='config.ini' else directory/'launch-config.json' + files += [control_binding_path(directory,b) if Path(b['path']).name!='config.ini' else directory/'launch-config.json' for b in manifest['bindings']] files += [Path(b['path']) for b in json.loads((directory/'host-closure.json').read_text())] files += [Path(v['path']) for v in (stock_identity if directory==stock else observed_identity).values()] - files=sorted(set(files)) + files=sorted({p.resolve(strict=True) for p in files}) result={'schema':'pepsiman-independent-source-v1','movie_sha256':MOVIE_SHA,'bios_sha256':BIOS_SHA, 'observed_returns':endpoint,'original_inputs':FRAMES,'neutral_tail':endpoint-FRAMES, 'ram_pages':str(observed/'ram-pages.tsv'),'terminal_ram':str(raw), diff --git a/tools/tasreplays/test_pepsiman.py b/tools/tasreplays/test_pepsiman.py index 0e3ca0d6..5acc3ab3 100644 --- a/tools/tasreplays/test_pepsiman.py +++ b/tools/tasreplays/test_pepsiman.py @@ -22,6 +22,13 @@ def cue(): return '\n'.join(parts)+'\n' class PepsimanAdmission(unittest.TestCase): + def test_legacy_control_reference_keeps_campaign_base(self): + with tempfile.TemporaryDirectory() as temp: + campaign=Path(temp);stock=campaign/'stock';observed=campaign/'observed' + stock.mkdir();observed.mkdir();file=stock/'ram-frames.tsv';file.write_text('bound capture') + binding={'path':'stock/ram-frames.tsv','sha256':pepsiman.digest(file)} + resolved=pepsiman.control_binding_path(observed,binding) + self.assertTrue(resolved.is_absolute());self.assertEqual(resolved,file.resolve()) def test_seed_window_keeps_only_exact_bytes(self): original=bytes(256);target=bytearray(original);target[63]=1 seeds=[{'address':f'0x{0xBFC00000+i:08X}'} for i in [0,64,192,193]]