#!/usr/bin/env python3 """Verify Hermes and legacy Settings patches against representative dumps. The runtime patch must preserve Hermes shared string storage: only the Debug Settings label and the HBC footer may change. The Settings icon is deployed at the stock icon_setting.png path by the bootstrapper. Legacy bundles use profiled, equal-length UTF-8 label and icon replacements. """ from __future__ import annotations import hashlib import re import struct import sys from pathlib import Path REPO = Path(__file__).resolve().parents[1] DUMP_ROOT = Path("/Users/chenpy/Projects/Person/ps5-kylin/Sony Dumps") HOOK = (REPO / "source/shellui/src/hook_functions.cpp").read_text() HERMES_VERSIONS = ("9.00", "11.6") LEGACY_VERSIONS = ("4.03", "4.50", "4.51", "7.61", "8.0", "8.4") HERMES_MAGIC = bytes.fromhex("c61fbc03c103191f") LEGACY_MAGIC = bytes.fromhex("e5d10bfb") FOOTER_SIZE = 20 OLD_LABEL = ("\u2605Debug Settings").encode("utf-16le") NEW_LABEL = ("\u2605OnionHEN Tools").encode("utf-16le") LEGACY_OLD_LABEL = ("\u2605Debug Settings").encode() LEGACY_NEW_LABEL = ("\u2605OnionHEN Tools").encode() LEGACY_OLD_ICON = b"icon_setting" LEGACY_NEW_ICON = b"onionh_sicon" COMMON_PROTECTED_STRINGS = ( b"icon_setting", b"_settingInstance", ) VERSION_PROTECTED_STRINGS = { "9.00": ( b"assets/src/modules/devices/hunt/buttonAssignments/assets/icon", ), "11.6": (b"avatar-appear-offline-icon",), } def load_legacy_profiles() -> list[dict]: table = re.search( r"kLegacySettingsProfiles\[\] = \{(.*?)\n\};", HOOK, re.S ) if not table: raise SystemExit("legacy Settings profile table not found") profiles = [] for name, payload_size, label_offset, icon_offset in re.findall( r'\{"([^"]+)",\s*(0x[0-9a-fA-F]+),\s*' r'(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+)\}', table.group(1), ): profiles.append( { "name": name, "payload_size": int(payload_size, 16), "label_offset": int(label_offset, 16), "icon_offset": int(icon_offset, 16), } ) if not profiles: raise SystemExit("no legacy Settings profiles parsed") return profiles LEGACY_PROFILES = load_legacy_profiles() def find_all(data: bytes, needle: bytes) -> list[int]: offsets: list[int] = [] start = 0 while True: offset = data.find(needle, start) if offset < 0: return offsets offsets.append(offset) start = offset + 1 def load_hbc(version: str) -> bytearray: path = DUMP_ROOT / version / "NPXS40008.bin" raw = path.read_bytes() hbc_offset = raw.find(HERMES_MAGIC) if hbc_offset < 0: raise ValueError(f"{version}: Hermes magic not found") available = raw[hbc_offset:] if len(available) < 0x24: raise ValueError(f"{version}: truncated HBC header") file_length = struct.unpack_from(" len(available): raise ValueError(f"{version}: invalid HBC file length 0x{file_length:x}") return bytearray(available[:file_length]) def footer_is_valid(hbc: bytes) -> bool: footer_offset = len(hbc) - FOOTER_SIZE return hashlib.sha1(hbc[:footer_offset]).digest() == hbc[footer_offset:] def apply_settings_patch(hbc: bytearray) -> bool: label_offset = hbc.find(OLD_LABEL) if label_offset < 0: return False hbc[label_offset : label_offset + len(OLD_LABEL)] = NEW_LABEL footer_offset = len(hbc) - FOOTER_SIZE hbc[footer_offset:] = hashlib.sha1(hbc[:footer_offset]).digest() return True def verify_hermes_version(version: str) -> list[str]: errors: list[str] = [] original = load_hbc(version) if not footer_is_valid(original): errors.append("stock footer SHA-1 is invalid") if original.count(OLD_LABEL) != 1: errors.append(f"stock label count is {original.count(OLD_LABEL)}, expected 1") protected_strings = ( COMMON_PROTECTED_STRINGS + VERSION_PROTECTED_STRINGS[version] ) protected_offsets = { needle: find_all(original, needle) for needle in protected_strings } for needle, offsets in protected_offsets.items(): if not offsets: errors.append(f"stock protected string missing: {needle!r}") icon_offset = original.find(b"icon_setting") icon_range = range(icon_offset, icon_offset + len(b"icon_setting")) for collateral in VERSION_PROTECTED_STRINGS[version] + (b"_settingInstance",): collateral_offset = original.find(collateral) collateral_range = range( collateral_offset, collateral_offset + len(collateral) ) if ( icon_range.stop <= collateral_range.start or collateral_range.stop <= icon_range.start ): errors.append(f"expected shared storage not found: {collateral!r}") patched = bytearray(original) changed = apply_settings_patch(patched) if not changed: errors.append("first patch did not replace the label") return errors if not footer_is_valid(patched): errors.append("patched footer SHA-1 is invalid") label_offset = original.find(OLD_LABEL) footer_offset = len(original) - FOOTER_SIZE allowed = set(range(label_offset, label_offset + len(OLD_LABEL))) allowed.update(range(footer_offset, len(original))) unexpected = [ offset for offset, (old, new) in enumerate(zip(original, patched)) if old != new and offset not in allowed ] if unexpected: errors.append( f"bytes changed outside label/footer, first offset=0x{unexpected[0]:x}" ) for needle, offsets in protected_offsets.items(): if find_all(patched, needle) != offsets: errors.append(f"protected string changed: {needle!r}") if b"onionh_sicon" in patched: errors.append("deprecated onionh_sicon id appeared in Hermes HBC") second_pass = bytearray(patched) if apply_settings_patch(second_pass): errors.append("second patch unexpectedly replaced another label") if second_pass != patched: errors.append("second patch was not idempotent") print( f"[{'PASS' if not errors else 'FAIL'}] {version} NPXS40008 " f"footer={patched[-FOOTER_SIZE:].hex()}" ) return errors def locate_legacy_payload(data: bytes | bytearray) -> int | None: if data[: len(LEGACY_MAGIC)] == LEGACY_MAGIC: return 0 if data[:8] != b"RNPSHEDR" or len(data) < 0x20: return None declared = struct.unpack_from(" dict | None: for profile in LEGACY_PROFILES: if len(payload) != profile["payload_size"]: continue label_offset = profile["label_offset"] icon_offset = profile["icon_offset"] label = bytes(payload[label_offset : label_offset + len(LEGACY_OLD_LABEL)]) icon = bytes(payload[icon_offset : icon_offset + len(LEGACY_OLD_ICON)]) if label not in (LEGACY_OLD_LABEL, LEGACY_NEW_LABEL): continue if icon not in (LEGACY_OLD_ICON, LEGACY_NEW_ICON): continue return profile return None def apply_legacy_settings( data: bytearray, ) -> tuple[dict | None, int | None, int, int]: payload_offset = locate_legacy_payload(data) if payload_offset is None: return None, None, 0, 0 payload = data[payload_offset:] profile = match_legacy_profile(payload) if profile is None: return None, payload_offset, 0, 0 label_count = 0 icon_count = 0 label_offset = payload_offset + profile["label_offset"] icon_offset = payload_offset + profile["icon_offset"] if data[label_offset : label_offset + len(LEGACY_OLD_LABEL)] == LEGACY_OLD_LABEL: data[label_offset : label_offset + len(LEGACY_OLD_LABEL)] = LEGACY_NEW_LABEL label_count = 1 if data[icon_offset : icon_offset + len(LEGACY_OLD_ICON)] == LEGACY_OLD_ICON: data[icon_offset : icon_offset + len(LEGACY_OLD_ICON)] = LEGACY_NEW_ICON icon_count = 1 return profile, payload_offset, label_count, icon_count def verify_legacy_version(version: str) -> list[str]: errors: list[str] = [] path = DUMP_ROOT / version / "NPXS40008.bin" original = path.read_bytes() payload_offset = locate_legacy_payload(original) if payload_offset is None: return ["legacy payload not found"] original_payload = original[payload_offset:] profile = match_legacy_profile(original_payload) if profile is None: return [f"no profile for payload size 0x{len(original_payload):x}"] if locate_legacy_payload(original_payload) != 0: errors.append("direct legacy payload locator failed") old_label_offsets = find_all(original_payload, LEGACY_OLD_LABEL) if len(old_label_offsets) != 2: errors.append( f"stock UTF-8 label count is {len(old_label_offsets)}, expected 2" ) if profile["label_offset"] not in old_label_offsets: errors.append("profile does not target a stock Debug Settings label") if original_payload.find(LEGACY_OLD_ICON) != profile["icon_offset"]: errors.append("profile icon offset does not match stock icon_setting") patched = bytearray(original) matched, patched_offset, label_count, icon_count = apply_legacy_settings(patched) if matched != profile or patched_offset != payload_offset: errors.append("container patch matched the wrong profile or payload offset") if (label_count, icon_count) != (1, 1): errors.append( f"first patch counts label={label_count} icon={icon_count}, expected 1/1" ) if len(patched) != len(original): errors.append("legacy patch changed container length") label_start = payload_offset + profile["label_offset"] icon_start = payload_offset + profile["icon_offset"] allowed = set(range(label_start, label_start + len(LEGACY_OLD_LABEL))) allowed.update(range(icon_start, icon_start + len(LEGACY_OLD_ICON))) unexpected = [ offset for offset, (old, new) in enumerate(zip(original, patched)) if old != new and offset not in allowed ] if unexpected: errors.append( f"legacy bytes changed outside label/icon at 0x{unexpected[0]:x}" ) patched_payload = bytes(patched[payload_offset:]) remaining_old_labels = find_all(patched_payload, LEGACY_OLD_LABEL) expected_remaining = [ offset for offset in old_label_offsets if offset != profile["label_offset"] ] if remaining_old_labels != expected_remaining: errors.append("non-menu Debug Settings text changed") direct = bytearray(original_payload) _, direct_offset, direct_labels, direct_icons = apply_legacy_settings(direct) if direct_offset != 0 or (direct_labels, direct_icons) != (1, 1): errors.append("direct payload patch did not apply exactly once") if direct != patched[payload_offset:]: errors.append("direct and RNPS-container patch results differ") second_pass = bytearray(patched) _, _, second_labels, second_icons = apply_legacy_settings(second_pass) if (second_labels, second_icons) != (0, 0): errors.append("legacy second pass was not idempotent") if second_pass != patched: errors.append("legacy second pass changed bytes") print( f"[{'PASS' if not errors else 'FAIL'}] {version} NPXS40008 " f"profile={profile['name']} label=0x{profile['label_offset']:x} " f"icon=0x{profile['icon_offset']:x}" ) return errors def verify_source_contract() -> list[str]: errors: list[str] = [] bootstrapper = (REPO / "source/bootstrapper/source/main.cpp").read_text() if 'replace_all(buffer, size_ptr, buffer_capacity, "icon_setting"' in HOOK: errors.append("Hermes icon_setting replacement is still present") if "texture/icon_setting.png" not in bootstrapper: errors.append("bootstrapper does not deploy the stock Settings icon path") return errors def main() -> int: all_errors = verify_source_contract() for version in LEGACY_VERSIONS: try: errors = verify_legacy_version(version) except (OSError, ValueError, struct.error) as exc: errors = [str(exc)] all_errors.extend(f"{version}: {error}" for error in errors) for version in HERMES_VERSIONS: try: errors = verify_hermes_version(version) except (OSError, ValueError, struct.error) as exc: errors = [str(exc)] all_errors.extend(f"{version}: {error}" for error in errors) if all_errors: for error in all_errors: print(f"ERROR: {error}") print("OVERALL: FAIL") return 1 print("OVERALL: PASS") return 0 if __name__ == "__main__": sys.exit(main())