#!/usr/bin/env python3 """Parse _ExtensionsInfo._ExtensionZippedInfo blobs. Observed structure starts with a 4-byte marker, followed by a 20-byte SHA1 key that points to a ConfigCAS file. The rest contains small extension metadata, including a UTF-16LE serialized text fragment. """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any from inspect_1c_sql_files import GUID_RE def extract_utf16le_fragment(data: bytes) -> str: starts = [pos for pos in (data.find(b"{\x00"), data.find(b'"\x00#\x00"\x00')) if pos >= 0] if not starts: return "" start = min(starts) fragment = data[start:] if len(fragment) % 2: fragment = fragment[:-1] try: return fragment.decode("utf-16-le", errors="ignore").strip("\x00") except UnicodeError: return "" def parse_blob(path: Path, cas_dir: Path | None) -> dict[str, Any]: data = path.read_bytes() root_key = data[4:24].hex() if len(data) >= 24 else "" text = extract_utf16le_fragment(data) cas_path = cas_dir / root_key if cas_dir and root_key else None return { "file_name": path.name, "path": str(path), "bytes": len(data), "marker_hex": data[:4].hex(), "root_cas_key": root_key, "root_cas_exists": bool(cas_path and cas_path.is_file()), "root_cas_path": str(cas_path) if cas_path and cas_path.is_file() else None, "text_fragment": text, "guids": sorted(set(match.lower() for match in GUID_RE.findall(text))), } def main() -> int: parser = argparse.ArgumentParser(description="Parse exported _ExtensionZippedInfo blobs.") parser.add_argument("input", type=Path, help="A blob file or directory with *_ExtensionZippedInfo.bin files.") parser.add_argument("--cas-dir", type=Path) parser.add_argument("--output", type=Path, required=True) args = parser.parse_args() files = [args.input] if args.input.is_file() else sorted(args.input.glob("*ExtensionZippedInfo.bin")) items = [parse_blob(path, args.cas_dir) for path in files] report = { "schema": "onec_extension_zipped_info.v1", "input": str(args.input), "cas_dir": str(args.cas_dir) if args.cas_dir else None, "file_count": len(items), "root_cas_found_count": sum(1 for item in items if item["root_cas_exists"]), "items": items, } args.output.parent.mkdir(parents=True, exist_ok=True) args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") print( json.dumps( { "output": str(args.output), "files": len(items), "root_cas_found": report["root_cas_found_count"], }, ensure_ascii=False, ) ) return 0 if __name__ == "__main__": raise SystemExit(main())