Relax version gates to accept V8 story files
V8 uses the same format as V5 (object model, opcodes, stack) with two differences: packed address scaling (×8 instead of ×4) and max file size (512KB instead of 256KB). zmemory: add V8 size validation and packed_address case zobjectparser: accept version 8 alongside 4-5 in all checks zstackmanager: allow V8 stack initialization V6-7 remain unsupported (different packed address format with offsets).
This commit is contained in:
parent
e0573f4229
commit
11d939a70f
4 changed files with 90 additions and 19 deletions
|
|
@ -172,6 +172,9 @@ class ZMemory:
|
|||
elif 4 <= self.version <= 5:
|
||||
if self._total_size > 262144:
|
||||
raise ZMemoryBadStoryfileSize
|
||||
elif self.version == 8:
|
||||
if self._total_size > 524288:
|
||||
raise ZMemoryBadStoryfileSize
|
||||
else:
|
||||
raise ZMemoryUnsupportedVersion
|
||||
|
||||
|
|
@ -253,6 +256,10 @@ class ZMemory:
|
|||
if address < 0 or address > (self._total_size // 4):
|
||||
raise ZMemoryOutOfBounds
|
||||
return address * 4
|
||||
elif self.version == 8:
|
||||
if address < 0 or address > (self._total_size // 8):
|
||||
raise ZMemoryOutOfBounds
|
||||
return address * 8
|
||||
else:
|
||||
raise ZMemoryUnsupportedVersion
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class ZObjectParser:
|
|||
|
||||
if 1 <= self._memory.version <= 3:
|
||||
self._objecttree_addr = self._propdefaults_addr + 62
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
self._objecttree_addr = self._propdefaults_addr + 126
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -90,7 +90,7 @@ class ZObjectParser:
|
|||
if not (1 <= objectnum <= 255):
|
||||
raise ZObjectIllegalObjectNumber
|
||||
result = self._objecttree_addr + (9 * (objectnum - 1))
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
if not (1 <= objectnum <= 65535):
|
||||
log(f"error: there is no object {objectnum}")
|
||||
raise ZObjectIllegalObjectNumber
|
||||
|
|
@ -111,7 +111,7 @@ class ZObjectParser:
|
|||
addr += 4 # skip past attributes
|
||||
result = self._memory[addr : addr + 3]
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
addr += 6 # skip past attributes
|
||||
result = [
|
||||
self._memory.read_word(addr),
|
||||
|
|
@ -135,7 +135,7 @@ class ZObjectParser:
|
|||
# skip past attributes and relatives
|
||||
if 1 <= self._memory.version <= 3:
|
||||
addr += 7
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
addr += 12
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -150,7 +150,7 @@ class ZObjectParser:
|
|||
if 1 <= self._memory.version <= 3:
|
||||
if not (1 <= propnum <= 31):
|
||||
raise ZObjectIllegalPropertyNumber
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
if not (1 <= propnum <= 63):
|
||||
raise ZObjectIllegalPropertyNumber
|
||||
else:
|
||||
|
|
@ -171,7 +171,7 @@ class ZObjectParser:
|
|||
raise ZObjectIllegalAttributeNumber
|
||||
bf = BitField(self._memory[object_addr + (attrnum // 8)])
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
if not (0 <= attrnum <= 47):
|
||||
raise ZObjectIllegalAttributeNumber
|
||||
bf = BitField(self._memory[object_addr + (attrnum // 8)])
|
||||
|
|
@ -192,7 +192,7 @@ class ZObjectParser:
|
|||
byte_offset = attrnum // 8
|
||||
bf = BitField(self._memory[object_addr + byte_offset])
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
if not (0 <= attrnum <= 47):
|
||||
raise ZObjectIllegalAttributeNumber
|
||||
byte_offset = attrnum // 8
|
||||
|
|
@ -215,7 +215,7 @@ class ZObjectParser:
|
|||
byte_offset = attrnum // 8
|
||||
bf = BitField(self._memory[object_addr + byte_offset])
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
if not (0 <= attrnum <= 47):
|
||||
raise ZObjectIllegalAttributeNumber
|
||||
byte_offset = attrnum // 8
|
||||
|
|
@ -233,7 +233,7 @@ class ZObjectParser:
|
|||
|
||||
if 1 <= self._memory.version <= 3:
|
||||
max = 32
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
max = 48
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -269,7 +269,7 @@ class ZObjectParser:
|
|||
addr = self._get_object_addr(objectnum)
|
||||
if 1 <= self._memory.version <= 3:
|
||||
self._memory[addr + 4] = new_parent_num
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
self._memory.write_word(addr + 6, new_parent_num)
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -280,7 +280,7 @@ class ZObjectParser:
|
|||
addr = self._get_object_addr(objectnum)
|
||||
if 1 <= self._memory.version <= 3:
|
||||
self._memory[addr + 6] = new_child_num
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
self._memory.write_word(addr + 10, new_child_num)
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -291,7 +291,7 @@ class ZObjectParser:
|
|||
addr = self._get_object_addr(objectnum)
|
||||
if 1 <= self._memory.version <= 3:
|
||||
self._memory[addr + 5] = new_sibling_num
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
self._memory.write_word(addr + 8, new_sibling_num)
|
||||
else:
|
||||
raise ZObjectIllegalVersion
|
||||
|
|
@ -402,7 +402,7 @@ class ZObjectParser:
|
|||
return (addr, size)
|
||||
addr += size
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
while self._memory[addr] != 0:
|
||||
bf = BitField(self._memory[addr])
|
||||
addr += 1
|
||||
|
|
@ -448,7 +448,7 @@ class ZObjectParser:
|
|||
proplist[pnum] = (addr, size)
|
||||
addr += size
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
while self._memory[addr] != 0:
|
||||
bf = BitField(self._memory[addr])
|
||||
addr += 1
|
||||
|
|
@ -516,7 +516,7 @@ class ZObjectParser:
|
|||
if 1 <= self._memory.version <= 3:
|
||||
bf = BitField(self._memory[addr])
|
||||
return bf[0:5]
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
bf = BitField(self._memory[addr])
|
||||
return bf[0:6]
|
||||
else:
|
||||
|
|
@ -555,7 +555,7 @@ class ZObjectParser:
|
|||
size = bf[5:8] + 1
|
||||
return size
|
||||
|
||||
elif 4 <= self._memory.version <= 5:
|
||||
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||
bf = BitField(self._memory[size_addr])
|
||||
if bf[7]:
|
||||
# Two-byte header: size is in bits 0-5 of this byte
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ class ZRoutine:
|
|||
self.start_addr += 1
|
||||
|
||||
# Initialize the local vars in the ZRoutine's dictionary. This is
|
||||
# only needed on machines v1 through v4. In v5 machines, all local
|
||||
# variables are preinitialized to zero.
|
||||
# only needed on machines v1 through v4. In v5 and v8 machines, all
|
||||
# local variables are preinitialized to zero.
|
||||
self.local_vars = [0 for _ in range(15)]
|
||||
if 1 <= zmem.version <= 4:
|
||||
for i in range(num_local_vars):
|
||||
self.local_vars[i] = zmem.read_word(self.start_addr)
|
||||
self.start_addr += 2
|
||||
elif zmem.version != 5:
|
||||
elif zmem.version not in (5, 8):
|
||||
raise ZStackUnsupportedVersion
|
||||
|
||||
# Place call arguments into local vars, if available
|
||||
|
|
|
|||
64
tests/test_zmemory_v8.py
Normal file
64
tests/test_zmemory_v8.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""Tests for V8 z-machine version support."""
|
||||
|
||||
import pytest
|
||||
|
||||
from mudlib.zmachine.zmemory import ZMemory, ZMemoryUnsupportedVersion
|
||||
|
||||
|
||||
def make_minimal_story(version: int, size: int) -> bytes:
|
||||
"""Create a minimal z-machine story file of the specified version and size."""
|
||||
story = bytearray(size)
|
||||
story[0] = version # version byte
|
||||
# Set static memory base (0x0E) to a reasonable value
|
||||
story[0x0E] = 0x04
|
||||
story[0x0F] = 0x00
|
||||
# Set high memory base (0x04) to end of file
|
||||
story[0x04] = (size >> 8) & 0xFF
|
||||
story[0x05] = size & 0xFF
|
||||
# Set global variables base (0x0C)
|
||||
story[0x0C] = 0x03
|
||||
story[0x0D] = 0x00
|
||||
return bytes(story)
|
||||
|
||||
|
||||
def test_v3_accepts_128kb_story():
|
||||
"""V3 stories can be up to 128KB (131072 bytes)."""
|
||||
story = make_minimal_story(3, 131072)
|
||||
mem = ZMemory(story)
|
||||
assert mem.version == 3
|
||||
|
||||
|
||||
def test_v5_accepts_256kb_story():
|
||||
"""V5 stories can be up to 256KB (262144 bytes)."""
|
||||
story = make_minimal_story(5, 262144)
|
||||
mem = ZMemory(story)
|
||||
assert mem.version == 5
|
||||
|
||||
|
||||
def test_v8_accepts_512kb_story():
|
||||
"""V8 stories can be up to 512KB (524288 bytes)."""
|
||||
story = make_minimal_story(8, 524288)
|
||||
mem = ZMemory(story)
|
||||
assert mem.version == 8
|
||||
|
||||
|
||||
def test_v8_packed_address_scaling():
|
||||
"""V8 uses ×8 scaling for packed addresses (not ×4 like V5)."""
|
||||
story = make_minimal_story(8, 10000)
|
||||
mem = ZMemory(story)
|
||||
# V8 packed address 100 should map to byte address 800
|
||||
assert mem.packed_address(100) == 800
|
||||
|
||||
|
||||
def test_v6_unsupported():
|
||||
"""V6 should still be unsupported (different packed address format)."""
|
||||
story = make_minimal_story(6, 10000)
|
||||
with pytest.raises(ZMemoryUnsupportedVersion):
|
||||
ZMemory(story)
|
||||
|
||||
|
||||
def test_v7_unsupported():
|
||||
"""V7 should still be unsupported (different packed address format)."""
|
||||
story = make_minimal_story(7, 10000)
|
||||
with pytest.raises(ZMemoryUnsupportedVersion):
|
||||
ZMemory(story)
|
||||
Loading…
Reference in a new issue