Files
scuc-qt-course/tools/extract_qtcreator_payload.py
张宗平 1a788a8278 移除 Docker 构建路线,改为 host 直装 Qt 5.14.2 + Qt Creator 4.11.0
学生主要用 Windows,Docker 方案不再需要:删掉 tools/docker/ 及 README/HANDOFF/
CMakeLists 里的相关引用(含一处已失效的 docker_run.sh 死链接)。

install_qt_host.sh 现在同时装期号匹配的 Qt Creator 4.11.0:官方 .run 安装器
强制要求 Qt 账号登录且无 Skip 选项,改用 tools/extract_qtcreator_payload.py
直接从 .run 里定位、解出内嵌的 7z payload,绕开账号登录。提取逻辑先解到
staging 目录再原子替换到位,避免中途失败留下部分安装但被判定为"已装";
候选 7z 复用同一临时文件,不会在磁盘上堆多份安装包大小的临时拷贝。
2026-07-02 13:02:37 +08:00

82 lines
2.8 KiB
Python
Executable File

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