"""Offline ABI verification against a mapped/decrypted image, never an on-disk encrypted EXE. Usage: python3 tests/verify_ember_movie_native.py path/to/game_image.bin [packages-directory] """ import re import struct import sys from pathlib import Path repo = Path(__file__).resolve().parents[1] data = Path(sys.argv[1]).read_bytes() def signature(file, name): source = (repo / file).read_text() pattern = re.search(r'constexpr auto ' + name + r'\s*=\s*signature skip GPU upload ui = signature('Sunrise/src/client/hooks/bootflow/ember_movie_ui.cpp', 'sig') assert ui == 0x132BD80 target(0x132B890, 0x353, 0x1278FF0) # native movie command is queued before either UI layer target(0x132B890, 0x3DF, ui) target(0x132B890, 0x40C, ui) for offset, expected in [(0x96, 0x4294D0), (0xD1, 0x423EF0), (0x14C, 0x4312D0), (0x157, 0x435AA0)]: target(load, offset, expected) for offset, expected in [(0x85, 0x42C650), (0x9F, 0x425310)]: target(end, offset, expected) assert data[end + 0x2E:end + 0x31] == bytes.fromhex('48 8B 05') assert end + 0x35 + struct.unpack_from(' kind 1; shared type-16 tag (type_info & F000 == 2000) -> kind 2. assert data[0x42694F:0x42696F] == bytes.fromhex( '8b 45 04 8b cb 48 89 7c 24 30 25 00 f0 00 00 33 ff 3d 00 20 00 00 40 0f 94 c7 45 33 c0 8d 57 01') target(0x426920, 0x4F, 0x433050) # Kind 2 is routed to root+10; ordinary metadata belongs in root+20. assert data[0x4313CD:0x4313E2] == bytes.fromhex( '83 3f 02 b9 10 00 00 00 8b 57 04 41 b8 20 00 00 00 44 0f 44 c1') # For stream type_info & 30000 == 10000, the load job maps offset|patch and # size|C0000000 directly. It bypasses the ordinary allocation/read branch. assert data[0x3592C6:0x3592EB] == bytes.fromhex( '8b c3 c1 e8 10 83 e0 03 83 f8 01 75 2f 41 0f b7 4d 20 41 81 cf 00 00 00 c0 8b 55 50 45 8b c7 48 0b d1 8b 4d 48') target(0x3591B0, 0x13B, 0x351D00) target(0x41A160, 0x16, 0x3597C0) # native video I/O opens this mapped package/patch target(0x41A160, 0x2C, 0x357DA0) # then obtains offset and byte length movie = 'Sunrise/src/client/hooks/ember_movies/ember_movies.cpp' start, stop, busy = (signature(movie, name) for name in ('startSig', 'stopSig', 'busySig')) for offset, expected in [(0x72, 0x41B040), (0x7A, 0x41A3C0), (0x8E, 0x41CD20)]: target(start, offset, expected) for offset, expected in [(0x18, 0x41D0C0), (0x25, 0x41A980)]: target(stop, offset, expected) assert busy == 0x41B420 target(busy, 0x48, 0x41AB70) if len(sys.argv) > 2: # Read only container metadata, without unpacking data or starting the game. latest = {} for path in Path(sys.argv[2]).glob('*.pkg'): with path.open('rb') as stream: header = stream.read(0x170) package = struct.unpack_from(' latest[package][0]: latest[package] = version, path, header for tag, expected in [(0x80BCA001, 0x80808495), (0x80BCA003, 0x80808495), (0x80BCA000, 0x80808499), (0x80BCA002, 0x80808499), (0x80B9EB33, 0x80809A88), (0x80B9EB34, 0x80809A88), (0x80BCA032, 0x80806B8F), (0x80BCA022, 0x80806B91), (0x80BCA025, 0x80806B91), (0x80BCA028, 0x80806B91), (0x80BCA02B, 0x80806B91), (0x80BCA02E, 0x80806B91), (0x80BCA031, 0x80806B91), (0x80BCA034, 0xFFFFFFFF), (0x80C7C000, 0xFFFFFFFF)]: # Tag package IDs include the bank: 80BCAxxx belongs to package 01E5. package = (tag >> 13) & 0x3FF _, path, header = latest[package] table = (struct.unpack_from('> 6) & 0x3F == 24 print('Installed movie metadata, compact streams, six surface containers and kind-1 package types verified.') tags = repo / 'build/first-encounter-audit/tags' catalog = (tags / '80BCA032.bin').read_bytes() for i, (container, definition) in enumerate(zip( [0x80BCA022,0x80BCA025,0x80BCA028,0x80BCA02B,0x80BCA02E,0x80BCA031], [0x80BCA021,0x80BCA024,0x80BCA026,0x80BCA029,0x80BCA02C,0x80BCA02F])): assert struct.unpack_from('