|
|
|
|
@@ -9,6 +9,7 @@ from ackit import banks
|
|
|
|
|
from ackit.game import Game
|
|
|
|
|
from ackit.inject import SizeLimitError
|
|
|
|
|
from ackit.upk import Package
|
|
|
|
|
from ackit.wem import fill, unfill
|
|
|
|
|
from ackit.workspace import Workspace, ConflictError, MODIFIED, ORIGINAL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -65,12 +66,13 @@ def test_bank_apply_every_copy_and_revert_byte_identical(synth_game, tmp_path):
|
|
|
|
|
files = [os.path.join(cooked, f) for f in ("Synth_A.upk", "Synth_B.upk")]
|
|
|
|
|
before = {f: _sha(f) for f in files}
|
|
|
|
|
s = ws.catalog.by_key["BANK:%08X" % synth.FOLEY[0]]
|
|
|
|
|
new = synth.wem(0.2, 1500)
|
|
|
|
|
new = synth.fitting_wem(ws, s, 0.2, 1500)
|
|
|
|
|
assert len(new) < ws.limit(s)
|
|
|
|
|
ws.apply([(s, new, {"source": "test"})])
|
|
|
|
|
assert ws.state(s) == MODIFIED
|
|
|
|
|
for l in ws.locations(s):
|
|
|
|
|
assert _copy(ws, l) == new + bytes(l.size - len(new))
|
|
|
|
|
assert ws.state(s) == MODIFIED and ws.current_bytes(s) == new
|
|
|
|
|
for l in ws.locations(s): # every copy filled exactly: no zero padding after the WEM
|
|
|
|
|
assert _copy(ws, l) == fill(new, l.size) and len(_copy(ws, l)) == l.size
|
|
|
|
|
assert unfill(_copy(ws, l)) == new
|
|
|
|
|
other = ws.catalog.by_key["BANK:%08X" % synth.FOLEY[1]]
|
|
|
|
|
assert all(_copy(ws, l) == originals[other.target] for l in ws.locations(other))
|
|
|
|
|
assert all(_sha(f) != before[f] for f in files)
|
|
|
|
|
@@ -79,13 +81,49 @@ def test_bank_apply_every_copy_and_revert_byte_identical(synth_game, tmp_path):
|
|
|
|
|
assert ws.state(s) == ORIGINAL and {f: _sha(f) for f in files} == before
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_fill_slot_exactly():
|
|
|
|
|
"""The game plays an in-memory WEM followed by zero padding as silence: a smaller replacement is grown to
|
|
|
|
|
its slot with a label list chunk (wem.fill), which the game's own WEMs use too."""
|
|
|
|
|
from ackit import audio
|
|
|
|
|
from ackit.wem import FILL_MIN, Wem, fillable
|
|
|
|
|
w = synth.wem(0.2, 1500)
|
|
|
|
|
for size in (len(w), len(w) + FILL_MIN, len(w) + 1000):
|
|
|
|
|
f = fill(w, size)
|
|
|
|
|
assert len(f) == size and 8 + int.from_bytes(f[4:8], "little") == size # RIFF covers the slot
|
|
|
|
|
tags = [b"fmt ", b"data"] if size == len(w) else [b"fmt ", b"LIST", b"data"]
|
|
|
|
|
assert unfill(f) == w and [c.tag for c in Wem.parse(f).chunks] == tags
|
|
|
|
|
assert (audio.decode_wem(f)[0] == audio.decode_wem(w)[0]).all()
|
|
|
|
|
assert not fillable(len(w), len(w) + 3) and not fillable(len(w), len(w) + FILL_MIN - 2)
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
fill(w, len(w) + 1)
|
|
|
|
|
f = fill(w, len(w) + 40)
|
|
|
|
|
a = f.index(b"adtllabl")
|
|
|
|
|
game_like = f[:a + 12] + (1).to_bytes(4, "little") + b"END\0" + f[a + 20:] # a real marker label: kept
|
|
|
|
|
assert len(game_like) == len(f) and unfill(game_like) == game_like
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_bank_apply_refuses_unfillable_and_prepare_fits(synth_game, tmp_path):
|
|
|
|
|
from ackit.game import GameError
|
|
|
|
|
root, _ = synth_game
|
|
|
|
|
ws = _ws(root, tmp_path)
|
|
|
|
|
s = ws.catalog.by_key["BANK:%08X" % synth.FOOT[1]]
|
|
|
|
|
bad = next(w for w in (synth.wem(0.1 * (1 - 0.01 * k), 2000) for k in range(40)) if not ws.fits(s, w)
|
|
|
|
|
and len(w) <= ws.limit(s))
|
|
|
|
|
with pytest.raises(GameError):
|
|
|
|
|
ws.apply([(s, bad, {})])
|
|
|
|
|
assert ws.store.entries == {}
|
|
|
|
|
x, sr = synth.tone(0.1, 24000, 2000, 1)
|
|
|
|
|
wem, _ = ws.prepare(s, x, sr)
|
|
|
|
|
assert ws.fits(s, wem)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_loose_bank_in_place(synth_game, tmp_path):
|
|
|
|
|
root, originals = synth_game
|
|
|
|
|
ws = _ws(root, tmp_path)
|
|
|
|
|
s = ws.catalog.by_key["BANK:%08X" % synth.FOOT[1]]
|
|
|
|
|
path = ws.files(s)[0]
|
|
|
|
|
before, size = _sha(path), os.path.getsize(path)
|
|
|
|
|
ws.apply([(s, synth.wem(0.1, 2000), {})])
|
|
|
|
|
ws.apply([(s, synth.fitting_wem(ws, s, 0.1, 2000), {})])
|
|
|
|
|
assert os.path.getsize(path) == size and _sha(path) != before
|
|
|
|
|
ws.revert_all()
|
|
|
|
|
assert _sha(path) == before
|
|
|
|
|
@@ -98,11 +136,11 @@ def test_partial_revert_keeps_other_changes(synth_game, tmp_path):
|
|
|
|
|
before = _sha(path)
|
|
|
|
|
a = ws.catalog.by_key["BANK:%08X" % synth.FOLEY[0]]
|
|
|
|
|
b = ws.catalog.by_key["BANK:%08X" % synth.GUI[0]]
|
|
|
|
|
na, nb = synth.wem(0.2, 1500), synth.wem(0.2, 1700)
|
|
|
|
|
na, nb = synth.fitting_wem(ws, a, 0.2, 1500), synth.fitting_wem(ws, b, 0.2, 1700)
|
|
|
|
|
ws.apply([(a, na, {}), (b, nb, {})])
|
|
|
|
|
ws.revert([a])
|
|
|
|
|
assert ws.current_bytes(a) == originals[a.target]
|
|
|
|
|
assert ws.current_bytes(b).startswith(nb) and ws.state(b) == MODIFIED
|
|
|
|
|
assert ws.current_bytes(b) == nb and ws.state(b) == MODIFIED
|
|
|
|
|
ws.revert([b])
|
|
|
|
|
assert _sha(path) == before
|
|
|
|
|
|
|
|
|
|
@@ -136,15 +174,15 @@ def test_bank_file_changed_underneath(synth_game, tmp_path):
|
|
|
|
|
root, originals = synth_game
|
|
|
|
|
ws = _ws(root, tmp_path)
|
|
|
|
|
s = ws.catalog.by_key["BANK:%08X" % synth.GUI[0]]
|
|
|
|
|
ws.apply([(s, synth.wem(0.2, 1500), {})])
|
|
|
|
|
ws.apply([(s, synth.fitting_wem(ws, s, 0.2, 1500), {})])
|
|
|
|
|
path = ws.files(s)[0]
|
|
|
|
|
# another tool rewrites the package (different bytes elsewhere in it)
|
|
|
|
|
pk = Package(path)
|
|
|
|
|
other = pk.patched([(pk.header_size + 10, b"OTHER-TOOL")])
|
|
|
|
|
open(path, "wb").write(other)
|
|
|
|
|
with pytest.raises(ConflictError):
|
|
|
|
|
ws.apply([(s, synth.wem(0.2, 1600), {})])
|
|
|
|
|
ws.apply([(s, synth.wem(0.2, 1600), {})], force=True)
|
|
|
|
|
ws.apply([(s, synth.fitting_wem(ws, s, 0.2, 1600), {})])
|
|
|
|
|
ws.apply([(s, synth.fitting_wem(ws, s, 0.2, 1600), {})], force=True)
|
|
|
|
|
ws.revert([s])
|
|
|
|
|
assert _sha(path) == _sha(ws.store.full_copy(ws.game.rel(path)))
|
|
|
|
|
|
|
|
|
|
|