121 lines
4.7 KiB
Python
121 lines
4.7 KiB
Python
"""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) and press Edit;
|
|
2. load a 30 s probe file as replacement and press Apply (EditorPanel.apply in the Edit tab);
|
|
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, and the Edit tab's list and replacements go to a temporary folder (the user's edit list is
|
|
untouched).
|
|
|
|
python tools/e2e_editor.py
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
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
|
|
from app.edit import EditStore
|
|
edits = tempfile.mkdtemp() # not the user's edit list and replacements
|
|
w.edit.store = w.edit.model.store = EditStore(edits)
|
|
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()
|
|
shutil.rmtree(edits, ignore_errors=True)
|
|
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")
|
|
w.edit_selected() # the Edit tab, on this sound
|
|
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)
|
|
w.edit_selected()
|
|
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())
|