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:
|
elif 4 <= self.version <= 5:
|
||||||
if self._total_size > 262144:
|
if self._total_size > 262144:
|
||||||
raise ZMemoryBadStoryfileSize
|
raise ZMemoryBadStoryfileSize
|
||||||
|
elif self.version == 8:
|
||||||
|
if self._total_size > 524288:
|
||||||
|
raise ZMemoryBadStoryfileSize
|
||||||
else:
|
else:
|
||||||
raise ZMemoryUnsupportedVersion
|
raise ZMemoryUnsupportedVersion
|
||||||
|
|
||||||
|
|
@ -253,6 +256,10 @@ class ZMemory:
|
||||||
if address < 0 or address > (self._total_size // 4):
|
if address < 0 or address > (self._total_size // 4):
|
||||||
raise ZMemoryOutOfBounds
|
raise ZMemoryOutOfBounds
|
||||||
return address * 4
|
return address * 4
|
||||||
|
elif self.version == 8:
|
||||||
|
if address < 0 or address > (self._total_size // 8):
|
||||||
|
raise ZMemoryOutOfBounds
|
||||||
|
return address * 8
|
||||||
else:
|
else:
|
||||||
raise ZMemoryUnsupportedVersion
|
raise ZMemoryUnsupportedVersion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class ZObjectParser:
|
||||||
|
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
self._objecttree_addr = self._propdefaults_addr + 62
|
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
|
self._objecttree_addr = self._propdefaults_addr + 126
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -90,7 +90,7 @@ class ZObjectParser:
|
||||||
if not (1 <= objectnum <= 255):
|
if not (1 <= objectnum <= 255):
|
||||||
raise ZObjectIllegalObjectNumber
|
raise ZObjectIllegalObjectNumber
|
||||||
result = self._objecttree_addr + (9 * (objectnum - 1))
|
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):
|
if not (1 <= objectnum <= 65535):
|
||||||
log(f"error: there is no object {objectnum}")
|
log(f"error: there is no object {objectnum}")
|
||||||
raise ZObjectIllegalObjectNumber
|
raise ZObjectIllegalObjectNumber
|
||||||
|
|
@ -111,7 +111,7 @@ class ZObjectParser:
|
||||||
addr += 4 # skip past attributes
|
addr += 4 # skip past attributes
|
||||||
result = self._memory[addr : addr + 3]
|
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
|
addr += 6 # skip past attributes
|
||||||
result = [
|
result = [
|
||||||
self._memory.read_word(addr),
|
self._memory.read_word(addr),
|
||||||
|
|
@ -135,7 +135,7 @@ class ZObjectParser:
|
||||||
# skip past attributes and relatives
|
# skip past attributes and relatives
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
addr += 7
|
addr += 7
|
||||||
elif 4 <= self._memory.version <= 5:
|
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||||
addr += 12
|
addr += 12
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -150,7 +150,7 @@ class ZObjectParser:
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
if not (1 <= propnum <= 31):
|
if not (1 <= propnum <= 31):
|
||||||
raise ZObjectIllegalPropertyNumber
|
raise ZObjectIllegalPropertyNumber
|
||||||
elif 4 <= self._memory.version <= 5:
|
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||||
if not (1 <= propnum <= 63):
|
if not (1 <= propnum <= 63):
|
||||||
raise ZObjectIllegalPropertyNumber
|
raise ZObjectIllegalPropertyNumber
|
||||||
else:
|
else:
|
||||||
|
|
@ -171,7 +171,7 @@ class ZObjectParser:
|
||||||
raise ZObjectIllegalAttributeNumber
|
raise ZObjectIllegalAttributeNumber
|
||||||
bf = BitField(self._memory[object_addr + (attrnum // 8)])
|
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):
|
if not (0 <= attrnum <= 47):
|
||||||
raise ZObjectIllegalAttributeNumber
|
raise ZObjectIllegalAttributeNumber
|
||||||
bf = BitField(self._memory[object_addr + (attrnum // 8)])
|
bf = BitField(self._memory[object_addr + (attrnum // 8)])
|
||||||
|
|
@ -192,7 +192,7 @@ class ZObjectParser:
|
||||||
byte_offset = attrnum // 8
|
byte_offset = attrnum // 8
|
||||||
bf = BitField(self._memory[object_addr + byte_offset])
|
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):
|
if not (0 <= attrnum <= 47):
|
||||||
raise ZObjectIllegalAttributeNumber
|
raise ZObjectIllegalAttributeNumber
|
||||||
byte_offset = attrnum // 8
|
byte_offset = attrnum // 8
|
||||||
|
|
@ -215,7 +215,7 @@ class ZObjectParser:
|
||||||
byte_offset = attrnum // 8
|
byte_offset = attrnum // 8
|
||||||
bf = BitField(self._memory[object_addr + byte_offset])
|
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):
|
if not (0 <= attrnum <= 47):
|
||||||
raise ZObjectIllegalAttributeNumber
|
raise ZObjectIllegalAttributeNumber
|
||||||
byte_offset = attrnum // 8
|
byte_offset = attrnum // 8
|
||||||
|
|
@ -233,7 +233,7 @@ class ZObjectParser:
|
||||||
|
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
max = 32
|
max = 32
|
||||||
elif 4 <= self._memory.version <= 5:
|
elif 4 <= self._memory.version <= 5 or self._memory.version == 8:
|
||||||
max = 48
|
max = 48
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -269,7 +269,7 @@ class ZObjectParser:
|
||||||
addr = self._get_object_addr(objectnum)
|
addr = self._get_object_addr(objectnum)
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
self._memory[addr + 4] = new_parent_num
|
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)
|
self._memory.write_word(addr + 6, new_parent_num)
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -280,7 +280,7 @@ class ZObjectParser:
|
||||||
addr = self._get_object_addr(objectnum)
|
addr = self._get_object_addr(objectnum)
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
self._memory[addr + 6] = new_child_num
|
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)
|
self._memory.write_word(addr + 10, new_child_num)
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -291,7 +291,7 @@ class ZObjectParser:
|
||||||
addr = self._get_object_addr(objectnum)
|
addr = self._get_object_addr(objectnum)
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
self._memory[addr + 5] = new_sibling_num
|
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)
|
self._memory.write_word(addr + 8, new_sibling_num)
|
||||||
else:
|
else:
|
||||||
raise ZObjectIllegalVersion
|
raise ZObjectIllegalVersion
|
||||||
|
|
@ -402,7 +402,7 @@ class ZObjectParser:
|
||||||
return (addr, size)
|
return (addr, size)
|
||||||
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:
|
while self._memory[addr] != 0:
|
||||||
bf = BitField(self._memory[addr])
|
bf = BitField(self._memory[addr])
|
||||||
addr += 1
|
addr += 1
|
||||||
|
|
@ -448,7 +448,7 @@ class ZObjectParser:
|
||||||
proplist[pnum] = (addr, size)
|
proplist[pnum] = (addr, size)
|
||||||
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:
|
while self._memory[addr] != 0:
|
||||||
bf = BitField(self._memory[addr])
|
bf = BitField(self._memory[addr])
|
||||||
addr += 1
|
addr += 1
|
||||||
|
|
@ -516,7 +516,7 @@ class ZObjectParser:
|
||||||
if 1 <= self._memory.version <= 3:
|
if 1 <= self._memory.version <= 3:
|
||||||
bf = BitField(self._memory[addr])
|
bf = BitField(self._memory[addr])
|
||||||
return bf[0:5]
|
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])
|
bf = BitField(self._memory[addr])
|
||||||
return bf[0:6]
|
return bf[0:6]
|
||||||
else:
|
else:
|
||||||
|
|
@ -555,7 +555,7 @@ class ZObjectParser:
|
||||||
size = bf[5:8] + 1
|
size = bf[5:8] + 1
|
||||||
return size
|
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])
|
bf = BitField(self._memory[size_addr])
|
||||||
if bf[7]:
|
if bf[7]:
|
||||||
# Two-byte header: size is in bits 0-5 of this byte
|
# Two-byte header: size is in bits 0-5 of this byte
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,14 @@ class ZRoutine:
|
||||||
self.start_addr += 1
|
self.start_addr += 1
|
||||||
|
|
||||||
# Initialize the local vars in the ZRoutine's dictionary. This is
|
# Initialize the local vars in the ZRoutine's dictionary. This is
|
||||||
# only needed on machines v1 through v4. In v5 machines, all local
|
# only needed on machines v1 through v4. In v5 and v8 machines, all
|
||||||
# variables are preinitialized to zero.
|
# local variables are preinitialized to zero.
|
||||||
self.local_vars = [0 for _ in range(15)]
|
self.local_vars = [0 for _ in range(15)]
|
||||||
if 1 <= zmem.version <= 4:
|
if 1 <= zmem.version <= 4:
|
||||||
for i in range(num_local_vars):
|
for i in range(num_local_vars):
|
||||||
self.local_vars[i] = zmem.read_word(self.start_addr)
|
self.local_vars[i] = zmem.read_word(self.start_addr)
|
||||||
self.start_addr += 2
|
self.start_addr += 2
|
||||||
elif zmem.version != 5:
|
elif zmem.version not in (5, 8):
|
||||||
raise ZStackUnsupportedVersion
|
raise ZStackUnsupportedVersion
|
||||||
|
|
||||||
# Place call arguments into local vars, if available
|
# 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