#!/usr/bin/env python3 """Check that every uncompressed entry in a zip/APK starts at an aligned offset. Exists to *verify* rather than assume the zipalign decision recorded in sign-apk.sh: this repo has no `zipalign` (it is not a standalone nixpkgs attribute -- fdroid-repo-ci#1), on the strength of Android's own docs saying a build made through the Android Gradle Plugin already aligns its zip entries and that `zipalign` is only needed afterwards to check that, not to do it. `apksigner` itself does not check alignment at all -- signing and verifying a deliberately misaligned fixture produces no warning, measured while writing this script -- so nothing downstream catches a regression here. This does. Standard library only (`zipfile`, `struct`), like the rest of this repo's Python. No `zipalign` binary is invoked or required. Alignment only matters for *uncompressed* (STORED) entries: a compressed entry is inflated into a fresh buffer before use, so its position in the zip file is irrelevant. Two rules, matching what `zipalign`/`apksigner` apply: * `.so` (native libraries) must start on a 4096-byte boundary, so the loader can mmap them directly. * everything else stored must start on a 4-byte boundary. `zipfile.ZipInfo` records the *central directory's* header offset and extra field, which is not always identical to the *local* file header at that offset -- the two can carry different extra-field lengths. Reading the local header directly, the same way apksigcopier's realignment code does, is what makes this check exact rather than approximate. """ import argparse import struct import sys import zipfile LOCAL_HEADER_SIGNATURE = b"PK\x03\x04" def local_data_offset(fh, header_offset): """Where this entry's data actually starts, per its local file header.""" fh.seek(header_offset) sig = fh.read(4) if sig != LOCAL_HEADER_SIGNATURE: raise ValueError(f"no local file header at offset {header_offset} " f"(got {sig!r})") fh.seek(header_offset + 26) name_len, extra_len = struct.unpack("