1a788a8278
学生主要用 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 复用同一临时文件,不会在磁盘上堆多份安装包大小的临时拷贝。
82 lines
2.8 KiB
Python
Executable File
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())
|