"""Real end-to-end test of the EDITOR code path against the installed game (Windows, Steam running, WinDbg). 1. open the editor (offscreen) on the real game, select the intro music (Music_393BC53A); 2. load a 30 s probe file as replacement and press Apply (EditorPanel.apply); 3. start the game under cdb + speaker loopback: the probe must be heard, no crash, setup parsed; 4. press Revert (EditorPanel.revert) and check the WAD + pack index are byte-identical to before; 5. also apply/revert a speech line in place and check byte identity. Uses the user's settings and originals store (an interrupted run shows up in `ackit status`); the settings file is never written. python tools/e2e_editor.py """ import hashlib import os import sys import tempfile ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT) os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") os.environ["ACKIT_NO_AUDIO"] = "1" def sha(p): h = hashlib.sha1() with open(p, "rb") as f: for c in iter(lambda: f.read(1 << 22), b""): h.update(c) return h.hexdigest() def main(): from PySide6.QtWidgets import QApplication from ackit import settings as settings_mod from app.mainwindow import MainWindow from app.workers import Runner app = QApplication([]) # the user's settings (same game folder and originals store as `ackit status`), never saved: the window # stores its settings on load and on close, which would overwrite the user's file with this test's ones w = MainWindow(settings=dict(settings_mod.load(), full_backup=True), runner=Runner(sync=True)) w.save_settings = lambda: None w.confirm = lambda *a: True w.editor.confirm = lambda *a: True errors = [] w.editor.inform = lambda t, m: errors.append("%s: %s" % (t, m)) app.processEvents() ws = w.ws assert ws, "game not loaded" ok = True try: ok = run(w, ws, errors) finally: # never leave a change behind, whatever failed ws.revert_all() ws.texts.revert_all() print("E2E EDITOR %s" % ("PASS" if ok else "FAIL")) w.close() return 0 if ok else 1 def run(w, ws, errors): from ackit import audio from ackit.inject import pack_bnk_path, parse_target from ackit.verify import ingame ok = True with tempfile.TemporaryDirectory() as td: # --- music (stream pack, resized) --- s = ws.sound("Music_393BC53A") wad = ws.wad_path(s) files = [wad, pack_bnk_path(wad, parse_target(s.target)[0])] before = {f: sha(f) for f in files} w.search.setText("Music_393BC53A") w._filter_text() assert w.select_name("Music_393BC53A") x, sr = ingame.probe_signal(30.0) probe = os.path.join(td, "probe.wav") audio.save_wav(probe, x, sr) assert w.editor.load_file(probe) w.editor.apply() assert not errors, errors print("applied through the editor:", ws.state(s)) rep = ingame.run(ws, s.key, mode="installed", seconds=40, log=print) for k in ("crash", "alive_at_end", "setup_ok", "ncc_expected", "ncc_original", "result"): print(" %-14s %s" % (k, rep[k])) ok &= rep["result"] == "PASS" w.editor.revert() same = all(sha(f) == h for f, h in before.items()) print("music reverted through the editor, byte-identical:", same) ok &= same # --- speech line (in place) --- t = ws.sound("Speech_TAPE_JOKER_1#4") # 4th WEM of that entry in this language's WAD twad = ws.wad_path(t) tb = sha(twad) w.clear_filters() assert w.select_name(t.name) y, ysr = audio.decode_wem(ws.original_bytes(t)) line = os.path.join(td, "line.wav") audio.save_wav(line, y[::-1], ysr) # reversed speech, same length assert w.editor.load_file(line) w.editor.apply() assert not errors, errors changed = sha(twad) != tb w.editor.revert() same2 = sha(twad) == tb print("speech line applied (WAD changed: %s), reverted byte-identical: %s" % (changed, same2)) ok &= changed and same2 return ok if __name__ == "__main__": sys.exit(main())