#!/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 """ 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())