#!/usr/bin/env python3 """Prepare Image + initramfs + DTB for the Quest Pro kexec staging layout. The Android boot image is optional; it is only needed when the kernel or initramfs is taken from it. Only writes host files. Does not contact the headset or execute a boot. """ import argparse import gzip import hashlib import json import struct import subprocess import zlib from pathlib import Path def align(n, a): return (n + a - 1) & -a def unpack(data): if len(data) < 1660 or data[:8] != b"ANDROID!": raise ValueError("not an Android boot image") ksize, _, rsize, _, ssize, _, _, page, version, _ = struct.unpack_from("<10I", data, 8) if version not in (0, 1, 2): raise ValueError("only boot header versions 0, 1 and 2 are supported") if page not in (2048, 4096, 8192, 16384) or not ksize: raise ValueError("invalid page or kernel size") if ssize: raise ValueError("second-stage bootloader payloads are not supported") roff = align(page + ksize, page) soff = align(roff + rsize, page) if soff + ssize > len(data) or roff + rsize > len(data): raise ValueError("truncated boot image") kernel = data[page:page + ksize] if kernel.startswith(b"\x1f\x8b"): # Gzip streams with appended DTBs need separate handling, not guesses. kernel = gzip.decompress(kernel) if len(kernel) < 64 or kernel[56:60] != b"ARM\x64": raise ValueError("kernel is not an uncompressed/gzip ARM64 Image") text_offset, image_size, flags = struct.unpack_from(" 0x2F80000: raise ValueError("image does not fit this Quest Pro staging layout") initrd = a.initramfs.read_bytes() if a.initramfs else original dtb = a.live_dtb.read_bytes() if len(dtb) < 40 or struct.unpack_from(">I", dtb)[0] != 0xD00DFEED: raise ValueError("invalid live DTB") if len(initrd) > 0x200000 or len(dtb) > 0x200000: raise ValueError("initramfs/DTB exceeds staging limits") # Leave a single exclusion window for source, descriptor and control pages. check_ram(a.iomem.read_text(), 0x90000000, 0x3400000) a.output.mkdir(parents=True, exist_ok=True) (a.output / "Image").write_bytes(kernel) if original: (a.output / "original-ramdisk.gz").write_bytes(original) (a.output / "initramfs").write_bytes(initrd) dtpath = a.output / "boot.dtb" dtpath.write_bytes(dtb) args = a.cmdline if a.cmdline is not None else report["cmdline"] subprocess.run(["fdtput", "-t", "s", str(dtpath), "/chosen", "bootargs", args], check=True) # The target kernel takes over the apps watchdog from the loader instead of # probing it again; this matches every validated boot. if not a.keep_watchdog: subprocess.run(["fdtput", "-t", "s", str(dtpath), "/soc/qcom,wdt@17c10000", "status", "disabled"], check=True) for name, address in (("linux,initrd-start", 0x93000000), ("linux,initrd-end", 0x93000000 + len(initrd))): subprocess.run(["fdtput", "-t", "x", str(dtpath), "/chosen", name, "0", f"{address:x}"], check=True) report.update(kernel_address=0x90080000, initrd_address=0x93000000, dtb_address=0x93200000, initrd_size=len(initrd), dtb_size=dtpath.stat().st_size, effective_cmdline=args, replacement_initramfs=a.initramfs is not None, crc32={name: f"{zlib.crc32(content):08x}" for name, content in (("Image", kernel), ("initramfs", initrd), ("boot.dtb", dtpath.read_bytes()))}, status="prepared only; no boot attempted") (a.output / "manifest.json").write_text(json.dumps(report, indent=2) + "\n") print(json.dumps(report, indent=2)) if __name__ == "__main__": main()