"""Verify the packaged bytes, including Android asset-name/compression behavior.""" import argparse import gzip import hashlib import json from pathlib import Path from zipfile import ZIP_STORED, ZipFile CONTENT_POLICY = json.loads( (Path(__file__).resolve().parents[2] / "shared" / "content-policy.json").read_text(encoding="utf-8") ) def verify_package(apk: Path) -> dict: with ZipFile(apk) as archive: manifest = json.loads(archive.read("assets/public/content/bootstrap-manifest.json")) checked = set() object_bytes = 0 for logical_path, entry in manifest["files"].items(): stored = entry.get("download", entry) if stored["url"] in checked: continue checked.add(stored["url"]) name = "assets/public" + stored["url"] data = archive.read(name) if len(data) != stored["bytes"] or hashlib.sha256(data).hexdigest() != stored["sha256"]: raise ValueError(f"Packaged object failed verification: {logical_path}") object_bytes += len(data) if "download" in entry: if archive.getinfo(name).compress_type != ZIP_STORED: raise ValueError(f"Android recompressed an already compressed object: {name}") data = gzip.decompress(data) if len(data) != entry["bytes"] or hashlib.sha256(data).hexdigest() != entry["sha256"]: raise ValueError(f"Expanded object failed verification: {logical_path}") dungeons = sum(pack["id"].startswith("dungeon:") and pack["bundledOnAndroid"] for pack in manifest["packs"]) expected_objects = {"assets/public" + url for url in checked} actual_objects = {name for name in archive.namelist() if name.startswith("assets/public/content/bundled/") and not name.endswith("/")} if actual_objects != expected_objects: raise ValueError("APK includes missing or unreferenced bundled objects.") manastorms = sum(pack["id"].startswith("manastorm:") and pack["bundledOnAndroid"] for pack in manifest["packs"]) arenas = sum(pack["id"] == "core:arenas" for pack in manifest["packs"]) pack_ids = sorted(pack["id"] for pack in manifest["packs"]) expected_pack_ids = sorted(CONTENT_POLICY["bootstrapPackIds"]) if pack_ids != expected_pack_ids or dungeons != 1 or manastorms != 0 or arenas != 0: raise ValueError( f"Unexpected bundled content: {dungeons} dungeons, {manastorms} Manastorm packs, {arenas} arena packs" ) if object_bytes > CONTENT_POLICY["maxBootstrapStoredBytes"]: raise ValueError("APK bootstrap content exceeds the stored-byte budget.") return { "apk": str(apk.resolve()), "apkBytes": apk.stat().st_size, "manifestVersion": manifest["version"], "logicalFiles": len(manifest["files"]), "verifiedUniqueObjects": len(checked), "storedObjectBytes": object_bytes, "bundledDungeons": dungeons, "bundledManastormPacks": manastorms, "bundledArenaPacks": arenas, "allOriginalHashesMatch": True, } if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("apk", type=Path) parser.add_argument("--report", type=Path) args = parser.parse_args() result = json.dumps(verify_package(args.apk), indent=2) + "\n" if args.report: args.report.parent.mkdir(parents=True, exist_ok=True) args.report.write_text(result, encoding="utf-8") print(result, end="")