#!/usr/bin/env python3 """Read-only exact current five-addon PAK inspection; optional new /tmp report dir. No extraction, native launch, package modification or qualification upgrade. """ from pathlib import Path import argparse,hashlib,json,sys,zipfile ROOT=Path('/home/jakub/devel/escapistsrereintroduced') SESSION=Path('/tmp/erri-road-interception-packages') sys.path.insert(0,str(ROOT/'tools')) import native_package as package import pak_inventory import packed_external AUTHORED_SHA='acf02e18c1f23e2f68d52393e38c34b9603ce75b895413d270606e043f519a2d' ORIGINAL_SHA='2527b5a9bc4ac22549e7430a99e8598d14c7ad8ff79943c6937cc15c904212b6' def require(value,message): if not value:raise ValueError(message) def exact_payload(project,entries,authored): actual={'addons/'+project+'/'+entry['path']:entry['sha256'] for entry in entries} require(len(actual)==len(entries),'Duplicate PAK paths') prefix='addons/'+project+'/' expected={name:sha for name,sha in authored.items() if name.startswith(prefix) and not name.endswith('.meta') and name!=prefix+'addon.gproj'} require(bool(expected),'Missing expected addon content: '+project) missing=sorted(set(expected)-set(actual));extra=sorted(set(actual)-set(expected));changed=sorted(name for name in expected.keys()&actual.keys() if actual[name]!=expected[name]) require(actual==expected,'PAK/source mismatch '+project+': missing='+repr(missing[:8])+' extra='+repr(extra[:8])+' changed='+repr(changed[:8])) return len(actual) def verify(source_zip=None): require(SESSION==SESSION.resolve() and SESSION.is_dir(),'Session redirected') for name in ('authored-before.json','package-report.json'): require((SESSION/name).is_file() and not (SESSION/name).is_symlink(),'Baseline file redirected: '+name) require(package.digest(SESSION/'authored-before.json')==AUTHORED_SHA,'Frozen authored manifest changed') require(package.digest(SESSION/'package-report.json')==ORIGINAL_SHA,'Original failed package report changed') authored=json.loads((SESSION/'authored-before.json').read_text()) require(package.inventory(ROOT)==authored and package.copied_authored_snapshot(SESSION/'source',authored)==authored,'Current/captured authored source differs from freeze') artifacts=packed_external.inventory(SESSION/'packages') # exact five directories and fifteen regular runtime files; new AI closure report={'kind':'EXACT DEVELOPMENT FIVE-ADDON PAYLOAD/SOURCE REVIEW','authored_manifest_sha256':AUTHORED_SHA,'original_package_report_sha256':ORIGINAL_SHA,'source_addon_files':len(authored),'projects':{},'artifacts':artifacts,'all_payloads_match':False,'build_qualified':False,'release_qualified':False,'native_consumption_verified':False} inventories={} for project in packed_external.PROJECTS: folder=SESSION/'packages'/project require(artifacts[project+'/addon.gproj']['sha256']==authored['addons/'+project+'/addon.gproj'],'Packed project bytes differ from authored project: '+project) value=pak_inventory.inventory(folder/'data.pak') require(value['archive_sha256']==artifacts[project+'/data.pak']['sha256'],'PAK changed between runtime and content inventory: '+project) count=exact_payload(project,value['entries'],authored) inventories[project]=value;report['projects'][project]={'entries':count,'exact_paths_and_hashes_match':True} if source_zip is not None: require(source_zip.is_file() and not source_zip.is_symlink(),'Source ZIP must be a regular file') zip_sha=package.digest(source_zip) with zipfile.ZipFile(source_zip) as archive: manifest=json.loads(archive.read('BUNDLE-MANIFEST.json')) require(manifest.get('kind')=='DEVELOPMENT SOURCE BUNDLE' and manifest.get('native_build') is False,'Not a development source bundle') require({name:sha for name,sha in manifest['files'].items() if name.startswith('addons/')}==authored,'Source ZIP does not match this frozen package input') require(set(manifest['files'])==set(authored)|{'BUNDLE-README.md','EXTERNAL-ADDONS.json'},'Unexpected development source bundle payload') require(json.loads(archive.read('EXTERNAL-ADDONS.json'))==json.loads((SESSION/'external-addons-policy.json').read_text()),'Source ZIP external policy differs from frozen build') require(len(archive.namelist())==len(set(archive.namelist())) and set(archive.namelist())==set(manifest['files'])|{'BUNDLE-MANIFEST.json'},'Source ZIP has duplicate/unmanifested entries') for name,sha in manifest['files'].items():require(hashlib.sha256(archive.read(name)).hexdigest()==sha,'Source ZIP payload hash mismatch: '+name) require(package.digest(source_zip)==zip_sha,'Source ZIP changed during review') report['matching_source_zip']={'path':str(source_zip),'sha256':zip_sha} require(packed_external.inventory(SESSION/'packages')==artifacts,'Runtime files changed during content review') require(package.inventory(ROOT)==authored and package.copied_authored_snapshot(SESSION/'source',authored)==authored,'Source changed during content review') require(package.digest(SESSION/'authored-before.json')==AUTHORED_SHA and package.digest(SESSION/'package-report.json')==ORIGINAL_SHA,'Original baseline changed during content review') report['all_payloads_match']=True return report,inventories def main(): parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--output',type=Path,help='Optional new direct /tmp child for JSON inventories/review');parser.add_argument('--source-zip',type=Path);args=parser.parse_args() if args.output is not None: output=args.output require(output.is_absolute() and output.parent==Path('/tmp') and output==output.resolve() and not output.exists() and not output.is_symlink(),'Output must be a new canonical direct /tmp child') report,inventories=verify(args.source_zip) if args.output is not None: args.output.mkdir(exist_ok=False) for project,value in inventories.items():package.save(args.output/(project+'-pak-inventory.json'),value) package.save(args.output/'payload-source-review.json',report) print(json.dumps(report,indent=2)) return 0 if __name__=='__main__': try:raise SystemExit(main()) except (OSError,ValueError,pak_inventory.PakError,zipfile.BadZipFile) as error:print('REFUSED: '+str(error),file=sys.stderr);raise SystemExit(1)