Implement V5+ save/restore opcodes and handle in-game saves on restore
- op_save_v5: generates Quetzal save, stores 1 on success / 0 on failure
- op_restore_v5: loads Quetzal save, stores 2 ("restored") via store byte
- _try_restore: detect V5+ in-game saves (0xBE 0x00 before PC) and process
the store byte with result 2, matching the V3 branch-on-restore pattern
This commit is contained in:
parent
602da45ac2
commit
e55294af78
2 changed files with 73 additions and 13 deletions
|
|
@ -46,6 +46,14 @@ class EmbeddedIFSession:
|
|||
|
||||
Must be called before the interpreter thread is launched.
|
||||
Returns True if state was restored successfully.
|
||||
|
||||
Handles two save origins:
|
||||
- In-game save (V3: opcode 0xB5, V5+: EXT 0xBE/0x00): PC points at
|
||||
branch data (V3) or store byte (V5+). Process them so execution
|
||||
resumes at the next instruction.
|
||||
- MUD-level _do_save during sread/aread: PC points past the read
|
||||
instruction. No post-processing needed (phantom output suppressed
|
||||
in start()).
|
||||
"""
|
||||
if not self.save_path.exists():
|
||||
return False
|
||||
|
|
@ -53,17 +61,24 @@ class EmbeddedIFSession:
|
|||
save_data = self.save_path.read_bytes()
|
||||
parser = QuetzalParser(self._zmachine)
|
||||
parser.load_from_bytes(save_data)
|
||||
# In V1-3, the saved PC points to branch data after the save
|
||||
# instruction. Process the branch as "save succeeded" so the
|
||||
# PC advances past it. Detect by checking for save opcode (0xB5)
|
||||
# immediately before the restored PC.
|
||||
|
||||
pc = self._zmachine._opdecoder.program_counter
|
||||
if (
|
||||
self._zmachine._mem.version <= 3
|
||||
and pc > 0
|
||||
and self._zmachine._mem[pc - 1] == 0xB5
|
||||
):
|
||||
mem = self._zmachine._mem
|
||||
|
||||
if mem.version <= 3 and pc > 0 and mem[pc - 1] == 0xB5:
|
||||
# V3 in-game save: PC at branch data after save opcode (0xB5).
|
||||
# Process the branch as "save succeeded".
|
||||
self._zmachine._cpu._branch(True)
|
||||
elif (
|
||||
mem.version >= 5
|
||||
and pc >= 3
|
||||
and mem[pc - 3] == 0xBE
|
||||
and mem[pc - 2] == 0x00
|
||||
):
|
||||
# V5+ in-game save: PC at store byte after EXT save opcode.
|
||||
# Read store byte and write 2 ("restored") to that variable.
|
||||
self._zmachine._cpu._write_result(2)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.debug(f"Restore failed: {e}")
|
||||
|
|
|
|||
|
|
@ -1034,12 +1034,57 @@ class ZCpu:
|
|||
## EXT opcodes (opcodes 256-284)
|
||||
|
||||
def op_save_v5(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
"""Save game state to file (V5+ - stores result).
|
||||
|
||||
Generates Quetzal save data and writes via filesystem.
|
||||
Stores 1 on success, 0 on failure. On restore, the game
|
||||
will see 2 stored in the same variable.
|
||||
"""
|
||||
if self._zmachine is None:
|
||||
self._write_result(0)
|
||||
return
|
||||
|
||||
from .quetzal import QuetzalWriter
|
||||
|
||||
try:
|
||||
writer = QuetzalWriter(self._zmachine)
|
||||
save_data = writer.generate_save_data()
|
||||
success = self._ui.filesystem.save_game(save_data)
|
||||
self._write_result(1 if success else 0)
|
||||
except Exception as e:
|
||||
log(f"Save failed with exception: {e}")
|
||||
self._write_result(0)
|
||||
|
||||
def op_restore_v5(self, *args):
|
||||
"""TODO: Write docstring here."""
|
||||
raise ZCpuNotImplemented
|
||||
"""Restore game state from file (V5+ - stores result).
|
||||
|
||||
Loads Quetzal save data and restores memory/stack/PC.
|
||||
The restored PC points at the store byte of the original save
|
||||
instruction. We read it and write 2 (meaning "restored") to
|
||||
the indicated variable.
|
||||
Stores 0 on failure (in the current, un-restored state).
|
||||
"""
|
||||
if self._zmachine is None:
|
||||
self._write_result(0)
|
||||
return
|
||||
|
||||
from .quetzal import QuetzalParser
|
||||
|
||||
try:
|
||||
save_data = self._ui.filesystem.restore_game()
|
||||
if save_data is None:
|
||||
self._write_result(0)
|
||||
return
|
||||
|
||||
parser = QuetzalParser(self._zmachine)
|
||||
parser.load_from_bytes(save_data)
|
||||
|
||||
# Restored PC points at the store byte of the save instruction.
|
||||
# Read it and write 2 ("restored") to that variable.
|
||||
self._write_result(2)
|
||||
except Exception as e:
|
||||
log(f"Restore failed with exception: {e}")
|
||||
self._write_result(0)
|
||||
|
||||
def op_log_shift(self, number, places):
|
||||
"""Logical shift: positive places = left, negative = right (V5+).
|
||||
|
|
|
|||
Loading…
Reference in a new issue