#!/usr/bin/env python3
"""FLARE-On 13 #9 NeonOutRun - full solver (static; the game is never run).

Usage: python3 -I solve.py            (expects loot/extracted/neon_outrun, see writeup.md)
Needs (../.venv): unicorn, pyelftools, zstandard

1. extract_js.py  : recover the hidden game script the Rust host runs in its embedded V8
                    (XOR(2-byte key) -> LZSS -> zstd -> raw DEFLATE).
   The script builds neon = messages[(lap-1)//2][map[lap-1]] per lap + "@f1are-on.com"
   -> "decode this system to win it@f1are-on.com"  (a decoy / hint).
2. The host replaces String.prototype.replaceAll, Math.min and Array.prototype.sort with
   native callbacks (decrypted names, 0x67e81e / 0x67eb83 / 0x67deb2):
     sort(laps)    : weights w_i = lapY_i * rnd, target T = sum(w_i), acc = 0, bytes = []
     Math.min(best, lapSecs) per lap i: b = trunc(lapSecs); bytes.push(b);
                     acc += w_i * sin(C1/X*(L_i +- b) mod 2pi)
     replaceAll    : if acc == T: copy bytes over the string, s[30] += ';'  ('1' -> 'l')
   With random weights the only general solution is sin(...) == 1 for every lap, which
   pins every lap time b_i (in whole seconds) -> hook_model.py.
"""
import os
import subprocess
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from hook_model import solve  # noqa: E402

BIN = os.path.join(HERE, "loot", "extracted", "neon_outrun")


def main():
    subprocess.run([sys.executable, "-I", os.path.join(HERE, "extract_js.py"), BIN,
                    os.path.join(HERE, "loot", "game.js")], check=True)
    key, laps = solve(BIN)
    times = bytes(b for _, _, _, b, _ in laps)
    assert all(abs(f - 1.0) < 1e-9 for *_, f in laps)
    print("[*] required lap times (s):", list(times))
    s = bytearray(b"decode_this_system_to_win_it@f1are-on.com")
    s[:len(times)] = times
    s[30] += 0x3b
    flag = s.decode()
    print("[+] flag:", flag)
    open(os.path.join(HERE, "flag.txt"), "w").write(flag + "\n")


if __name__ == "__main__":
    main()
