mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
An update whose lift ran again drops the compile step's record before the compile runs, so a compile that then stopped left state.json without it, build.built_version said '' and the launcher's next start showed the first-run screen and asked for the dump. build.installed now also counts a folder that holds the exe of the build before and game files a rebuild can start from: the window opens on home, where Rebuild the Game reads "Not Finished" and Play says which build it starts, and an update's rebuild and the command line's go on with the game. Begin Installation into a folder whose game files are already unpacked builds on from them instead of reading the dump again. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
132 lines
5.5 KiB
Python
132 lines
5.5 KiB
Python
"""The launcher without its window, for scripts, tests and anyone who prefers a console:
|
|
|
|
python -m launcher.cli check is there a newer release?
|
|
python -m launcher.cli update download and stage it, then finish in the new version
|
|
python -m launcher.cli tools install the build tools and zbic
|
|
python -m launcher.cli install --dump F --game D build the game (the window's Begin Installation)
|
|
python -m launcher.cli rebuild [--game D] rebuild after an update (the window's Rebuild the Game)
|
|
python -m launcher.cli fonts [--dump F] decode the game's font for the window
|
|
python -m launcher.cli continue-update OLD what the new version runs after `update`
|
|
|
|
In an installed launcher: python\\python.exe app\\<version>\\launcher\\cli.py <command> (this file puts the release
|
|
folder on sys.path itself, as start.pyw does). Exit code 0 on success, 1 on failure, 3 when cancelled.
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
if __name__ == '__main__' and not __package__:
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
__package__ = 'launcher' # noqa: A001 — lets the relative imports below work when run as a script
|
|
|
|
from . import build, gamefiles, jobs, versions # noqa: E402
|
|
from .context import Context # noqa: E402
|
|
from .paths import Layout # noqa: E402
|
|
|
|
|
|
def follow(job):
|
|
"""Run a job here, printing each step's state as it changes."""
|
|
seen = {}
|
|
job.start()
|
|
while job.result is None:
|
|
for s in job.steps:
|
|
key = (s.state, s.value if s.state != jobs.RUNNING else int(s.fraction * 20))
|
|
if seen.get(s.id) != key:
|
|
seen[s.id] = key
|
|
print(f'{s.label}: {s.value}' + (f' ({s.detail})' if s.detail and s.state == jobs.RUNNING else ''),
|
|
flush=True)
|
|
time.sleep(0.2)
|
|
for s in job.steps:
|
|
if seen.get(s.id, (None,))[0] != s.state:
|
|
print(f'{s.label}: {s.value}', flush=True)
|
|
if job.result == 'failed':
|
|
print(f'error: {job.error}', file=sys.stderr, flush=True)
|
|
return {'ok': 0, 'failed': 1, 'cancelled': 3}[job.result]
|
|
|
|
|
|
def main(argv=None):
|
|
ap = argparse.ArgumentParser(prog='launcher.cli', description=__doc__.splitlines()[0])
|
|
ap.add_argument('--root', help='the launcher folder (default: this release\'s, or build/launcher-dev)')
|
|
sub = ap.add_subparsers(dest='cmd', required=True)
|
|
sub.add_parser('check')
|
|
sub.add_parser('update')
|
|
sub.add_parser('tools')
|
|
p = sub.add_parser('install')
|
|
p.add_argument('--dump', required=True)
|
|
p.add_argument('--game', required=True)
|
|
p = sub.add_parser('rebuild')
|
|
p.add_argument('--game')
|
|
p = sub.add_parser('fonts')
|
|
p.add_argument('--dump', default='')
|
|
p.add_argument('--game', default='')
|
|
p = sub.add_parser('continue-update')
|
|
p.add_argument('old')
|
|
a = ap.parse_args(argv)
|
|
ctx = Context(Layout(root=a.root) if a.root else None, echo=False)
|
|
s = ctx.settings
|
|
|
|
if a.cmd == 'check':
|
|
job = jobs.CheckJob(ctx).run_inline()
|
|
if job.result != 'ok':
|
|
print(f'error: {job.error}', file=sys.stderr)
|
|
return 1
|
|
r = job.release
|
|
if not r:
|
|
print('no release found')
|
|
elif jobs.update_available(ctx, r):
|
|
print(f'update available: {versions.display(r.version)} (you have {ctx.version})')
|
|
else:
|
|
print(f'up to date: {ctx.version} (newest release {versions.display(r.version)})')
|
|
return 0
|
|
|
|
if a.cmd == 'update':
|
|
check = jobs.CheckJob(ctx).run_inline()
|
|
if check.result != 'ok' or not jobs.update_available(ctx, check.release):
|
|
print(check.error or f'up to date: {ctx.version}')
|
|
return 0 if check.result == 'ok' else 1
|
|
job = jobs.UpdateDownloadJob(ctx, check.release)
|
|
code = follow(job)
|
|
if code or job.after != 'restart':
|
|
return code
|
|
new = ctx.layout.apps / versions.display(check.release.version)
|
|
cli = new / 'launcher' / 'cli.py'
|
|
print(f'finishing in {versions.display(check.release.version)}', flush=True)
|
|
return subprocess.call([ctx.layout.python_for_builds(), str(cli), '--root', str(ctx.layout.root),
|
|
'continue-update', ctx.version])
|
|
|
|
if a.cmd == 'continue-update':
|
|
gd = s.get('game_dir')
|
|
return follow(jobs.ContinueUpdateJob(ctx, gd if build.installed(gd) else '', a.old,
|
|
s.get('dump')))
|
|
|
|
if a.cmd == 'tools':
|
|
job = jobs.RebuildJob(ctx, '')
|
|
job.steps = job.steps[:1]
|
|
job.run = lambda: job.ensure_tools('tools_update')
|
|
return follow(job)
|
|
|
|
if a.cmd == 'install':
|
|
s['dump'], s['game_dir'] = os.path.abspath(a.dump), os.path.abspath(a.game)
|
|
s.save()
|
|
return follow(jobs.InstallJob(ctx, s['dump'], s['game_dir']))
|
|
|
|
if a.cmd == 'rebuild':
|
|
gd = os.path.abspath(a.game) if a.game else s.get('game_dir')
|
|
if not gd:
|
|
print('error: no game folder (give --game)', file=sys.stderr)
|
|
return 1
|
|
return follow(jobs.RebuildJob(ctx, gd, s.get('dump')))
|
|
|
|
if a.cmd == 'fonts':
|
|
got = gamefiles.extract_fonts(ctx.layout, a.game or s.get('game_dir'), a.dump or s.get('dump'))
|
|
for role, (path, fam) in got.items():
|
|
print(f'{role}: {fam} ({path})')
|
|
return 0 if got else 1
|
|
return 2
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|