#!/usr/bin/env python3
"""Extract the Qt Creator payload from a QtIFW .run installer without running
its GUI. The official .run for Qt Creator (this vintage) hard-gates the wizard
behind a mandatory Qt account login with no Skip option, so the GUI/script
automation route is a dead end. The installer binary is a self-extracting
QtIFW archive: an ELF stub with one or more embedded 7z streams appended.
The real payload (bin/, lib/, share/, libexec/) lives in one of those streams;
this scans for the 7z magic, tries each occurrence, and keeps the one whose
contents include bin/qtcreator.

Usage: extract_qtcreator_payload.py <installer.run> <dest_dir>
"""
import shutil
import sys
import tempfile
from pathlib import Path

import py7zr

MAGIC = b"7z\xbc\xaf\x27\x1c"


def find_offsets(data: bytes) -> list[int]:
    offsets = []
    start = 0
    while True:
        i = data.find(MAGIC, start)
        if i == -1:
            break
        offsets.append(i)
        start = i + 1
    return offsets


def main() -> int:
    if len(sys.argv) != 3:
        print(__doc__)
        return 2
    run_path = Path(sys.argv[1])
    dest_dir = Path(sys.argv[2])

    data = run_path.read_bytes()
    offsets = find_offsets(data)
    if not offsets:
        print("no 7z signature found in installer", file=sys.stderr)
        return 1

    with tempfile.TemporaryDirectory() as tmp:
        candidate = Path(tmp) / "candidate.7z"
        staging = Path(tmp) / "staging"
        for off in offsets:
            # Overwrite the same candidate file each iteration so failed
            # attempts don't accumulate on disk (offsets can be ~installer-size apart).
            candidate.write_bytes(data[off:])
            try:
                with py7zr.SevenZipFile(candidate, mode="r") as z:
                    names = z.getnames()
            except Exception:
                continue
            if "bin/qtcreator" not in names:
                continue
            print(f">> payload found at offset {off} ({len(names)} entries)")
            # Extract into a staging dir first and only move it into dest_dir
            # once extraction fully succeeds, so a crash/disk-full mid-extract
            # never leaves a partially-populated dest_dir that later runs
            # would mistake for a complete install.
            with py7zr.SevenZipFile(candidate, mode="r") as z:
                z.extractall(path=staging)
            if dest_dir.exists():
                shutil.rmtree(dest_dir)
            dest_dir.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(staging), str(dest_dir))
            print(f">> extracted to {dest_dir}")
            return 0

    print("no offset yielded a payload containing bin/qtcreator", file=sys.stderr)
    return 1


if __name__ == "__main__":
    raise SystemExit(main())
