Files
totk-pc-recomp/tools/build_hactool.py
T
Valiantsin DzerakhandClaude Opus 5.5 ad70afaee0 Build hactool from the repository: tools/build_hactool.py fetches the pinned commit, applies tools/hactool/*.patch and links a static exe
The MacNativeGames BKTR and title-key changes and our UTF-8 manifest lived only in the maintainer's toolchain folder, so
nobody else could build the hactool that extraction and the installer need. The script's exe matches the one in use
byte for byte apart from its build time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:18:23 +03:00

76 lines
3.3 KiB
Python

"""Build hactool: toolchain/hactool/hactool.exe (SciresM/hactool at a pinned commit + tools/hactool/*.patch).
Fetches the pinned commit into toolchain/src/hactool, resets it (local changes and build output there are discarded),
applies portable.patch (MacNativeGames' Windows-relevant changes) and unicode.patch (ours), and builds a static exe
with MSYS2 UCRT64 gcc; mbedtls comes with hactool's tree. The exe and hactool's LICENSE go to toolchain/hactool, where
tools/extract.py and tools/make_release.py take them. Run with .venv.
python tools/build_hactool.py [--src DIR] [--out DIR]
"""
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
URL = 'https://github.com/SciresM/hactool.git'
COMMIT = '1d64a83450e025622f3468c28fc4164dad2c5ef6'
PATCHES = [ROOT / 'tools' / 'hactool' / n for n in ('portable.patch', 'unicode.patch')]
MSYS_BASH = Path(r'C:\msys64\usr\bin\bash.exe')
CONFIG_MK = ('CC = gcc\nEXEEXT = .exe\nCFLAGS = -O2 -Wall -Wextra -pedantic -std=gnu11\n'
'LDFLAGS = -lmbedtls -lmbedx509 -lmbedcrypto -static\n')
def git(src, *args):
subprocess.run(['git', '-C', str(src), *args], check=True)
def fetch(src):
"""src holds COMMIT with no local changes or untracked files."""
if not (src / '.git').is_dir():
src.mkdir(parents=True, exist_ok=True)
git(src, 'init', '-q')
git(src, 'remote', 'add', 'origin', URL)
head = subprocess.run(['git', '-C', str(src), 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
if head != COMMIT:
git(src, 'fetch', '-q', '--depth', '1', 'origin', COMMIT)
git(src, 'checkout', '-q', '-f', COMMIT)
git(src, 'clean', '-q', '-fdx')
def main():
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument('--src', type=Path, default=ROOT / 'toolchain' / 'src' / 'hactool', help='source checkout')
ap.add_argument('--out', type=Path, default=ROOT / 'toolchain' / 'hactool', help='where hactool.exe goes')
args = ap.parse_args()
if not MSYS_BASH.is_file():
sys.exit(f'{MSYS_BASH} not found: install MSYS2 with the UCRT64 gcc and make (docs/building.md)')
src, out = args.src.resolve(), args.out.resolve()
fetch(src)
for p in PATCHES:
git(src, 'apply', '--whitespace=nowarn', str(p))
(src / 'config.mk').write_text(CONFIG_MK, encoding='utf-8', newline='\n')
env = dict(os.environ, MSYSTEM='UCRT64', CHERE_INVOKING='1')
r = subprocess.run([str(MSYS_BASH), '-lc', f'make -j{os.cpu_count() or 4}'], cwd=src, env=env,
capture_output=True, text=True)
(src / 'build.log').write_text(r.stdout + r.stderr, encoding='utf-8')
if r.returncode:
sys.exit(f'make failed ({r.returncode}); log: {src / "build.log"}')
out.mkdir(parents=True, exist_ok=True)
shutil.copy2(src / 'hactool.exe', out / 'hactool.exe')
shutil.copy2(src / 'LICENSE', out / 'LICENSE')
banner = subprocess.run([str(out / 'hactool.exe')], capture_output=True, text=True)
if 'hactool (c) SciresM' not in banner.stdout + banner.stderr:
sys.exit(f'{out / "hactool.exe"} does not start')
print(f'{out / "hactool.exe"}: {COMMIT[:7]} + {", ".join(p.name for p in PATCHES)}')
if __name__ == '__main__':
main()