List and edit in-memory sounds in soundbanks
This commit is contained in:
@@ -119,3 +119,92 @@ def run(ws, key=DEFAULT_TARGET, wem=None, mode="modded", seconds=40.0, threshold
|
||||
report["result"] = "PASS" if ok else "FAIL"
|
||||
json.dump(report, open(logp.replace(".log", ".json"), "w"), indent=1)
|
||||
return report
|
||||
|
||||
|
||||
# --- in-memory sounds (soundbanks inside the packages) ------------------------------------------
|
||||
PROBE_RATE, PROBE_Q = 28000, 4 # a codebook setup the game's own WEMs do not use (E10)
|
||||
|
||||
|
||||
def bank_probes(ws, sounds):
|
||||
"""[(sound, probe WEM)]: the same probe format for every sound, shortened to fit its slot."""
|
||||
from ..encoder import encode
|
||||
x, sr = probe_signal(10.0)
|
||||
out = []
|
||||
for s in sounds:
|
||||
lim = ws.limit(s)
|
||||
secs = min(10.0, max(0.4, s.seconds or 1.0))
|
||||
while secs >= 0.1:
|
||||
wem, _ = encode(x[:int(secs * sr)], sr, PROBE_RATE, 1, PROBE_Q)
|
||||
if len(wem) <= lim:
|
||||
out.append((s, wem))
|
||||
break
|
||||
secs *= 0.7
|
||||
return out
|
||||
|
||||
|
||||
def run_banks(ws, keys=None, seconds=45.0, logdir=None, log=print):
|
||||
"""Replace in-memory sounds (default: every sound stored in the Frontend*/Startup packages) with probes, start the game,
|
||||
then check: no crash, the probe data is in the game's memory (the modified packages were loaded), and
|
||||
after the revert every touched file is byte-identical. Probes decoded (codec setup log) and heard
|
||||
(loopback correlation) are reported too, but no in-memory sound plays on the title screen without input,
|
||||
so they are not required. Returns a report dict (report['result'] PASS/FAIL)."""
|
||||
from .debugger import Run, kill_game, search_memory
|
||||
from .loopback import LoopbackRecorder, detect_chunks
|
||||
from ..wem import Wem
|
||||
if keys:
|
||||
sounds = [ws.catalog.resolve(k) for k in keys]
|
||||
else: # everything stored in the packages loaded for the title screen (rain ambience plays without input)
|
||||
sounds = [s for s in ws.catalog.sounds if s.archive == "BANK" and any(
|
||||
l.file.lower().startswith(("frontend", "startup")) for l in ws.locations(s))]
|
||||
probes = bank_probes(ws, sounds)
|
||||
files = sorted({f for s, _ in probes for f in ws.files(s)})
|
||||
logdir = logdir or os.path.join(os.getcwd(), "logs")
|
||||
os.makedirs(logdir, exist_ok=True)
|
||||
logp = os.path.join(logdir, "ingame_banks_%s.log" % time.strftime("%Y%m%d_%H%M%S"))
|
||||
report = dict(mode="banks", sounds=len(sounds), probes=len(probes), files=len(files))
|
||||
kill_game()
|
||||
before = {f: _sha1_file(f) for f in files}
|
||||
hashes = {"%08x" % Wem.parse(w).vorb["codebook_hash"] for _, w in probes}
|
||||
needles = set()
|
||||
for _, w in probes:
|
||||
if len(w) > 400:
|
||||
needles.add(w[len(w) // 2:len(w) // 2 + 32])
|
||||
report.update(probe_hashes=sorted(hashes), needles=len(needles))
|
||||
ws.apply([(s, w, {"source": "ingame-test"}) for s, w in probes], progress=log)
|
||||
from .debugger import ADDR
|
||||
# also log every setup parse (in-memory sources may not go through the stream setup cache)
|
||||
extra = 'bp %#x ".printf \\"ACKIT_PARSE2 hash=%%08x\\\\n\\", poi(poi(@esp+4)+0x9c); gc"' % ADDR["parse_setup"]
|
||||
runner = Run(ws.game.exe, logp, script_extra=extra)
|
||||
found = set()
|
||||
rec = LoopbackRecorder(seconds + 10)
|
||||
rec.start()
|
||||
try:
|
||||
report.update(runner.start())
|
||||
pid = report["pid"]
|
||||
while time.time() - runner.t0 < seconds and runner.alive():
|
||||
time.sleep(0.5)
|
||||
if re.search(r"^ACKIT_CRASH", runner.log(), re.M):
|
||||
break
|
||||
if runner.alive():
|
||||
found = search_memory(pid, needles)
|
||||
report["alive_at_end"] = runner.alive()
|
||||
finally:
|
||||
runner.stop()
|
||||
rec.stop_flag = True
|
||||
rec.join(5)
|
||||
ws.revert([s for s, _ in probes], progress=log)
|
||||
report["reverted_byte_identical"] = all(_sha1_file(f) == h for f, h in before.items())
|
||||
text = runner.log()
|
||||
report["crash"] = bool(re.search(r"^ACKIT_CRASH", text, re.M))
|
||||
ref = probe_signal(0.35)[0][:, 0]
|
||||
report["capture_error"] = rec.error
|
||||
report["ncc_probe"] = round(detect_chunks(rec.audio(), ref)[0], 3)
|
||||
seen = re.findall(r"ACKIT_(?:SETUP|PARSE2) hash=([0-9a-f]+)", text)
|
||||
report["probe_streams_decoded"] = sum(1 for h in seen if h in hashes)
|
||||
report["setups_logged"] = len(seen)
|
||||
report["probes_in_memory"] = len(found)
|
||||
ok = (not report["crash"] and report["alive_at_end"] and report["probes_in_memory"] > 0
|
||||
and report["reverted_byte_identical"])
|
||||
report["result"] = "PASS" if ok else "FAIL"
|
||||
json.dump(report, open(logp.replace(".log", ".json"), "w"), indent=1)
|
||||
return report
|
||||
|
||||
Reference in New Issue
Block a user