#!/usr/bin/env python3 """Test the phase-1 clear condition by KILLING squadrons from outside the game. Six flown attempts failed to reach a phase advance, because the autopilot cannot win Stage 02 (script-runtime-probe.md). The condition itself is cheap to test directly: the script polls `unit_state` on ADN110/111/112, so make those records read as gone and watch whether `[ScriptPhase+196]` goes to 1 and `[ScriptMission+40]` steps to 2. Two candidate writes, and the FIRST THING THIS DOES IS CHECK WHICH ONE STICKS: +16 state -- but built-in 69 "normalises" this field when it polls, so it may be a cache that the game rewrites within a frame. +4 live object -- built-ins 69/70/24 all test `[rec+4]` first and take an early exit when it is NULL, so NULLing it is the more faithful "this squadron is gone". A write that reverts is itself a result: it means the field is derived, not authoritative, and the poke route is closed. Usage: poke_squadron.py [--field 16|4] [--value N] [names...] """ import os import struct import sys import time sys.path.insert(0, __file__.rsplit('/', 1)[0]) import gmem import isl import squadron_state as S def poke32(f, va, value): off = gmem.va_to_off(va) f.seek(off) f.write(struct.pack('>I', value & 0xFFFFFFFF)) f.flush() def main(): ssb = isl.load(sys.argv[1]) sym2 = isl.symbols(ssb, 2) field = 16 value = 4 if '--field' in sys.argv: field = int(sys.argv[sys.argv.index('--field') + 1], 0) if '--value' in sys.argv: value = int(sys.argv[sys.argv.index('--value') + 1], 0) names = [a for a in sys.argv[2:] if not a.startswith('--') and not a.isdigit()][1:] \ or ['ADN110', 'ADN111', 'ADN112'] path = gmem.mem_path() size = os.path.getsize(path) idx = {n: i for i, (_t, n) in sym2.items()} with open(path, 'r+b', buffering=0) as f: m, fb = S.find_mission(f, size, ssb) if m is None: print('ScriptMission not located'); return 1 ph = S.u32(f, m + 4) arr = S.u32(f, ph + 324) base = S.u32(f, arr + 4) print('ScriptMission 0x%08X ScriptPhase 0x%08X' % (m, ph)) print('before: phase=%s finished=%s' % (S.u32(f, m + 40), S.u32(f, ph + 196))) recs = {} for n in names: rec = S.u32(f, base + idx[n] * 4) recs[n] = rec print(' %-8s rec=0x%08X +4=0x%08X +16=%s' % ( n, rec, S.u32(f, rec + 4), S.u32(f, rec + 16))) # --- does the write even stick? --- probe = recs[names[0]] was = S.u32(f, probe + field) poke32(f, probe + field, value) imm = S.u32(f, probe + field) time.sleep(2.0) later = S.u32(f, probe + field) print('STICK TEST on %s +%d: was=%s wrote=%s immediately=%s after2s=%s -> %s' % ( names[0], field, was, value, imm, later, 'STICKS' if later == value else 'REVERTED (field is derived)')) if later != value: poke32(f, probe + field, was) return 2 for n in names[1:]: poke32(f, recs[n] + field, value) print('poked all %d' % len(names)) for t in range(0, 60, 5): time.sleep(5) print(' [+%2ds] phase=%s finished=%s states=%s' % ( t + 5, S.u32(f, m + 40), S.u32(f, ph + 196), {n: S.u32(f, recs[n] + 16) for n in names}), flush=True) return 0 if __name__ == '__main__': sys.exit(main())