Process V3 save branch on restore to advance PC past branch data
The Quetzal spec stores the PC pointing at the save instruction's branch data. On restore, this branch must be processed as "save succeeded" to advance the PC to the actual next instruction. Without this, the branch bytes were decoded as an opcode, corrupting execution. Detect the save opcode (0xB5) immediately before the restored PC to distinguish in-game saves from out-of-band saves (which don't need branch processing). Also improve error diagnostics: pop_stack now raises ZStackPopError with frame context, and the instruction trace dumps on all exceptions.
This commit is contained in:
parent
8526e48247
commit
c52e59c5d4
3 changed files with 17 additions and 2 deletions
|
|
@ -53,6 +53,17 @@ class EmbeddedIFSession:
|
||||||
save_data = self.save_path.read_bytes()
|
save_data = self.save_path.read_bytes()
|
||||||
parser = QuetzalParser(self._zmachine)
|
parser = QuetzalParser(self._zmachine)
|
||||||
parser.load_from_bytes(save_data)
|
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
|
||||||
|
):
|
||||||
|
self._zmachine._cpu._branch(True)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Restore failed: {e}")
|
logger.debug(f"Restore failed: {e}")
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,7 @@ class ZCpu:
|
||||||
except (ZCpuQuit, ZCpuRestart):
|
except (ZCpuQuit, ZCpuRestart):
|
||||||
# Normal control flow - don't dump trace
|
# Normal control flow - don't dump trace
|
||||||
raise
|
raise
|
||||||
except ZCpuError:
|
except Exception:
|
||||||
# All other ZCpu errors - dump trace for debugging
|
|
||||||
self._dump_trace()
|
self._dump_trace()
|
||||||
raise
|
raise
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,11 @@ class ZStackManager:
|
||||||
"Remove and return value from the top of the data stack."
|
"Remove and return value from the top of the data stack."
|
||||||
|
|
||||||
current_routine = self._call_stack[-1]
|
current_routine = self._call_stack[-1]
|
||||||
|
if not current_routine.stack:
|
||||||
|
frame_idx = len(self._call_stack) - 1
|
||||||
|
ra = getattr(current_routine, "return_addr", "N/A")
|
||||||
|
pc = getattr(current_routine, "program_counter", 0)
|
||||||
|
raise ZStackPopError(f"frame {frame_idx}, return_addr={ra}, pc=0x{pc:06x}")
|
||||||
return current_routine.stack.pop()
|
return current_routine.stack.pop()
|
||||||
|
|
||||||
def get_stack_frame_index(self):
|
def get_stack_frame_index(self):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue