Version 3.2.2: in-memory replacements fill their slots exactly (padded ones played as silence)
This commit is contained in:
54
ackit/wem.py
54
ackit/wem.py
@@ -101,6 +101,60 @@ class Wem:
|
||||
return b"RIFF" + struct.pack("<I", len(body)) + body
|
||||
|
||||
|
||||
# ---- filling an in-memory slot exactly ----
|
||||
# The game ships no in-memory WEM with bytes after its RIFF inside its slot, and plays a replacement that is
|
||||
# followed by zero padding there as silence (findings 24). A smaller replacement is therefore grown to the
|
||||
# slot's exact size with a label list chunk before `data`, the form the game's own WEMs use for markers:
|
||||
# LIST{'adtl', labl{u32 cue id 0, zero text}}. Even sizes only (the game's chunks before `data` are even).
|
||||
FILL_MIN = 8 + 4 + 8 + 4 + 2
|
||||
|
||||
|
||||
def fillable(length, size):
|
||||
"""True if a WEM of `length` bytes can fill a slot of `size` bytes exactly."""
|
||||
pad = size - length
|
||||
return pad == 0 or (pad >= FILL_MIN and pad % 2 == 0)
|
||||
|
||||
|
||||
def fill(wem: bytes, size: int) -> bytes:
|
||||
"""`wem` grown to exactly `size` bytes (see above). ValueError if `fillable` is False."""
|
||||
if not fillable(len(wem), size):
|
||||
raise ValueError("a %d-byte WEM cannot fill a %d-byte slot exactly" % (len(wem), size))
|
||||
pad = size - len(wem)
|
||||
if not pad:
|
||||
return wem
|
||||
i = _data_chunk(wem)
|
||||
text = pad - 8 - 4 - 8 - 4
|
||||
chunk = (b"LIST" + struct.pack("<I", pad - 8) + b"adtl" + b"labl" + struct.pack("<II", text + 4, 0)
|
||||
+ bytes(text))
|
||||
out = wem[:i] + chunk + wem[i:]
|
||||
return out[:4] + struct.pack("<I", len(out) - 8) + out[8:]
|
||||
|
||||
|
||||
def unfill(wem: bytes) -> bytes:
|
||||
"""The WEM without a filler chunk added by `fill` (a game WEM is returned unchanged)."""
|
||||
i = _data_chunk(wem)
|
||||
if i is None:
|
||||
return wem
|
||||
for j in range(12, i, 2):
|
||||
if wem[j:j + 4] == b"LIST" and wem[j + 8:j + 16] == b"adtllabl":
|
||||
n = 8 + struct.unpack_from("<I", wem, j + 4)[0]
|
||||
if j + n == i and struct.unpack_from("<I", wem, j + 20)[0] == 0 and not wem[j + 24:i].strip(b"\0"):
|
||||
out = wem[:j] + wem[i:]
|
||||
return out[:4] + struct.pack("<I", len(out) - 8) + out[8:]
|
||||
return wem
|
||||
|
||||
|
||||
def _data_chunk(wem: bytes):
|
||||
"""Offset of the `data` chunk header (walking the chunks), or None."""
|
||||
pos = 12
|
||||
while pos + 8 <= len(wem):
|
||||
tag, ln = struct.unpack_from("<4sI", wem, pos)
|
||||
if tag == b"data":
|
||||
return pos
|
||||
pos += 8 + ln
|
||||
return None
|
||||
|
||||
|
||||
SUB_ALIGN = 0x800
|
||||
_NONZERO = re.compile(rb"[^\x00]")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user