Example #1
0
  def get_branch_offset(self):
    """For branching opcodes, examine address pointed to by PC, and
    return two values: first, either True or False (indicating whether
    to branch if true or branch if false), and second, the address to
    jump to.  Increment the PC as necessary."""

    bf = BitField(self._get_pc())
    branch_if_true = bool(bf[7])
    if bf[6]:
      branch_offset = bf[0:6]
    else:
      # We need to do a little magic here. The branch offset is
      # written as a signed 14-bit number, with signed meaning '-n' is
      # written as '65536-n'. Or in this case, as we have 14 bits,
      # '16384-n'.
      #
      # So, if the MSB (ie. bit 13) is set, we have a negative
      # number. We take the value, and substract 16384 to get the
      # actual offset as a negative integer.
      #
      # If the MSB is not set, we just extract the value and return it.
      #
      # Can you spell "Weird" ?
      branch_offset = self._get_pc() + (bf[0:5] << 8)
      if bf[5]:
        branch_offset -= 8192

    log('Branch if %s to offset %+d' % (branch_if_true, branch_offset))
    return branch_if_true, branch_offset
Example #2
0
    def get_branch_offset(self):
        """For branching opcodes, examine address pointed to by PC, and
    return two values: first, either True or False (indicating whether
    to branch if true or branch if false), and second, the address to
    jump to.  Increment the PC as necessary."""

        bf = BitField(self._get_pc())
        branch_if_true = bool(bf[7])
        if bf[6]:
            branch_offset = bf[0:6]
        else:
            # We need to do a little magic here. The branch offset is
            # written as a signed 14-bit number, with signed meaning '-n' is
            # written as '65536-n'. Or in this case, as we have 14 bits,
            # '16384-n'.
            #
            # So, if the MSB (ie. bit 13) is set, we have a negative
            # number. We take the value, and substract 16384 to get the
            # actual offset as a negative integer.
            #
            # If the MSB is not set, we just extract the value and return it.
            #
            # Can you spell "Weird" ?
            branch_offset = self._get_pc() + (bf[0:5] << 8)
            if bf[5]:
                branch_offset -= 8192

        log('Branch if %s to offset %+d' % (branch_if_true, branch_offset))
        return branch_if_true, branch_offset
Example #3
0
  def _generate_cmem_chunk(self):
    """Return a compressed chunk of data representing the compressed
    image of the zmachine's main memory."""

    ### TODO:  debug this when ready
    return "0"

    # XOR the original game image with the current one
    diffarray = list(self._zmachine._pristine_mem)
    for index in range(len(self._zmachine._pristine_mem._total_size)):
      diffarray[index] = self._zmachine._pristine_mem[index] \
                         ^ self._zmachine._mem[index]
    log("XOR array is %s" % diffarray)

    # Run-length encode the resulting list of 0's and 1's.
    result = []
    zerocounter = 0;
    for index in range(len(diffarray)):
      if diffarray[index] == 0:
        zerocounter += 1
        continue;
      else:
        if zerocounter > 0:
          result.append(0)
          result.append(zerocounter)
          zerocounter = 0
        result.append(diffarray[index])
    return result
Example #4
0
    def _generate_cmem_chunk(self):
        """Return a compressed chunk of data representing the compressed
    image of the zmachine's main memory."""

        ### TODO:  debug this when ready
        return "0"

        # XOR the original game image with the current one
        diffarray = list(self._zmachine._pristine_mem)
        for index in range(len(self._zmachine._pristine_mem._total_size)):
            diffarray[index] = self._zmachine._pristine_mem[index] \
                               ^ self._zmachine._mem[index]
        log("XOR array is %s" % diffarray)

        # Run-length encode the resulting list of 0's and 1's.
        result = []
        zerocounter = 0
        for index in range(len(diffarray)):
            if diffarray[index] == 0:
                zerocounter += 1
                continue
            else:
                if zerocounter > 0:
                    result.append(0)
                    result.append(zerocounter)
                    zerocounter = 0
                result.append(diffarray[index])
        return result
Example #5
0
    def get_next_instruction(self):
        """Decode the opcode & operands currently pointed to by the
    program counter, and appropriately increment the program counter
    afterwards. A decoded operation is returned to the caller in the form:

       [opcode-class, opcode-number, [operand, operand, operand, ...]]

    If the opcode has no operands, the operand list is present but empty."""

        opcode = self._get_pc()

        log("Decode opcode %x" % opcode)

        # Determine the opcode type, and hand off further parsing.
        if self._memory.version == 5 and opcode == 0xBE:
            # Extended opcode
            return self._parse_opcode_extended()

        opcode = BitField(opcode)
        if opcode[7] == 0:
            # Long opcode
            return self._parse_opcode_long(opcode)
        elif opcode[6] == 0:
            # Short opcode
            return self._parse_opcode_short(opcode)
        else:
            # Variable opcode
            return self._parse_opcode_variable(opcode)
Example #6
0
  def get_next_instruction(self):
    """Decode the opcode & operands currently pointed to by the
    program counter, and appropriately increment the program counter
    afterwards. A decoded operation is returned to the caller in the form:

       [opcode-class, opcode-number, [operand, operand, operand, ...]]

    If the opcode has no operands, the operand list is present but empty."""

    opcode = self._get_pc()

    log("Decode opcode %x" % opcode)

    # Determine the opcode type, and hand off further parsing.
    if self._memory.version == 5 and opcode == 0xBE:
      # Extended opcode
      return self._parse_opcode_extended()

    opcode = BitField(opcode)
    if opcode[7] == 0:
      # Long opcode
      return self._parse_opcode_long(opcode)
    elif opcode[6] == 0:
      # Short opcode
      return self._parse_opcode_short(opcode)
    else:
      # Variable opcode
      return self._parse_opcode_variable(opcode)
Example #7
0
 def _parse_opcode_long(self, opcode):
   """Parse an opcode of the long form."""
   # Long opcodes are always 2OP. The types of the two operands are
   # encoded in bits 5 and 6 of the opcode.
   log("Opcode is long")
   LONG_OPERAND_TYPES = [SMALL_CONSTANT, VARIABLE]
   operands = [self._parse_operand(LONG_OPERAND_TYPES[opcode[6]]),
               self._parse_operand(LONG_OPERAND_TYPES[opcode[5]])]
   return (OPCODE_2OP, opcode[0:5], operands)
Example #8
0
 def _parse_opcode_long(self, opcode):
     """Parse an opcode of the long form."""
     # Long opcodes are always 2OP. The types of the two operands are
     # encoded in bits 5 and 6 of the opcode.
     log("Opcode is long")
     LONG_OPERAND_TYPES = [SMALL_CONSTANT, VARIABLE]
     operands = [
         self._parse_operand(LONG_OPERAND_TYPES[opcode[6]]),
         self._parse_operand(LONG_OPERAND_TYPES[opcode[5]])
     ]
     return (OPCODE_2OP, opcode[0:5], operands)
Example #9
0
 def write_global(self, varnum, value):
     """Write 16-bit VALUE to global variable VARNUM.  Incoming VARNUM
 must be between 0x10 and 0xFF."""
     if not (0x10 <= varnum <= 0xFF):
         raise ZMemoryOutOfBounds
     if not (0x00 <= value <= 0xFFFF):
         raise ZMemoryIllegalWrite(address)
     log("Write %d to global variable %d" % (value, varnum))
     actual_address = self._global_variable_start + ((varnum - 0x10) * 2)
     bf = bitfield.BitField(value)
     self._memory[actual_address] = bf[8:15]
     self._memory[actual_address + 1] = bf[0:7]
Example #10
0
  def _parse_intd(self, data):
    """Parse a chunk of type IntD, which is interpreter-dependent info."""

    log("  Begin parsing of interpreter-dependent metadata")
    bytes = [ord(x) for x in data]

    os_id = bytes[0:3]
    flags = bytes[4]
    contents_id = bytes[5]
    reserved = bytes[6:8]
    interpreter_id = bytes[8:12]
    private_data = bytes[12:]
Example #11
0
 def write_global(self, varnum, value):
   """Write 16-bit VALUE to global variable VARNUM.  Incoming VARNUM
   must be between 0x10 and 0xFF."""
   if not (0x10 <= varnum <= 0xFF):
     raise ZMemoryOutOfBounds
   if not (0x00 <= value <= 0xFFFF):
     raise ZMemoryIllegalWrite(address)
   log("Write %d to global variable %d" % (value, varnum))
   actual_address = self._global_variable_start + ((varnum - 0x10) * 2)
   bf = bitfield.BitField(value)
   self._memory[actual_address] = bf[8:15]
   self._memory[actual_address + 1] = bf[0:7]
Example #12
0
    def _parse_intd(self, data):
        """Parse a chunk of type IntD, which is interpreter-dependent info."""

        log("  Begin parsing of interpreter-dependent metadata")
        bytes = [ord(x) for x in data]

        os_id = bytes[0:3]
        flags = bytes[4]
        contents_id = bytes[5]
        reserved = bytes[6:8]
        interpreter_id = bytes[8:12]
        private_data = bytes[12:]
Example #13
0
    def _branch(self, test_result):
        """Retrieve the branch information, and set the instruction
        pointer according to the type of branch and the test_result."""
        branch_cond, branch_offset = self._opdecoder.get_branch_offset()

        if test_result == branch_cond:
            if branch_offset == 0 or branch_offset == 1:
                log("Return from routine with %d" % branch_offset)
                addr = self._stackmanager.finish_routine(branch_offset)
                self._opdecoder.program_counter = addr
            else:
                log("Jump to offset %+d" % branch_offset)
                self._opdecoder.program_counter += (branch_offset - 2)
Example #14
0
File: zcpu.py Project: swilcox/zvm
    def _branch(self, test_result):
        """Retrieve the branch information, and set the instruction
        pointer according to the type of branch and the test_result."""
        branch_cond, branch_offset = self._opdecoder.get_branch_offset()

        if test_result == branch_cond:
            if branch_offset == 0 or branch_offset == 1:
                log("Return from routine with %d" % branch_offset)
                addr = self._stackmanager.finish_routine(branch_offset)
                self._opdecoder.program_counter = addr
            else:
                log("Jump to offset %+d" % branch_offset)
                self._opdecoder.program_counter += (branch_offset - 2)
Example #15
0
    def pretty_print(self):
        "Display a ZRoutine nicely, for debugging purposes."

        log("ZRoutine:        start address: %d" % self.start_addr)
        log("ZRoutine: return value address: %d" % self.return_addr)
        log("ZRoutine:      program counter: %d" % self.program_counter)
        log("ZRoutine:      local variables: %d" % self.local_vars)
Example #16
0
  def pretty_print(self):
    "Display a ZRoutine nicely, for debugging purposes."

    log("ZRoutine:        start address: %d" % self.start_addr)
    log("ZRoutine: return value address: %d" % self.return_addr)
    log("ZRoutine:      program counter: %d" % self.program_counter)
    log("ZRoutine:      local variables: %d" % self.local_vars)
Example #17
0
    def __init__(self, initial_string):
        """Construct class based on a string that represents an initial
    'snapshot' of main memory."""
        if initial_string is None:
            raise ZMemoryBadInitialization

        # Copy string into a _memory sequence that represents main memory.
        self._total_size = len(initial_string)
        self._memory = [ord(x) for x in initial_string]

        # Figure out the different sections of memory
        self._static_start = self.read_word(0x0e)
        self._static_end = min(0x0ffff, self._total_size)
        self._dynamic_start = 0
        self._dynamic_end = self._static_start - 1
        self._high_start = self.read_word(0x04)
        self._high_end = self._total_size
        self._global_variable_start = self.read_word(0x0c)

        # Dynamic + static must not exceed 64k
        dynamic_plus_static = ((self._dynamic_end - self._dynamic_start) +
                               (self._static_end - self._static_start))
        if dynamic_plus_static > 65534:
            raise ZMemoryBadMemoryLayout

        # What z-machine version is this story file?
        self.version = self._memory[0]

        # Validate game size
        if 1 <= self.version <= 3:
            if self._total_size > 131072:
                raise ZMemoryBadStoryfileSize
        elif 4 <= self.version <= 5:
            if self._total_size > 262144:
                raise ZMemoryBadStoryfileSize
        else:
            raise ZMemoryUnsupportedVersion

        log("Memory system initialized, map follows")
        log("  Dynamic memory: %x - %x" %
            (self._dynamic_start, self._dynamic_end))
        log("  Static memory: %x - %x" %
            (self._static_start, self._static_end))
        log("  High memory: %x - %x" % (self._high_start, self._high_end))
        log("  Global variable start: %x" % self._global_variable_start)
Example #18
0
    def _get_object_addr(self, objectnum):
        """Return address of object number OBJECTNUM."""

        result = 0
        if 1 <= self._memory.version <= 3:
            if not (1 <= objectnum <= 255):
                raise ZObjectIllegalObjectNumber
            result = self._objecttree_addr + (9 * (objectnum - 1))
        elif 4 <= self._memory.version <= 5:
            if not (1 <= objectnum <= 65535):
                log("error:  there is no object %d" % objectnum)
                raise ZObjectIllegalObjectNumber
            result = self._objecttree_addr + (14 * (objectnum - 1))
        else:
            raise ZObjectIllegalVersion

        log("address of object %d is %d" % (objectnum, result))
        return result
Example #19
0
  def _get_object_addr(self, objectnum):
    """Return address of object number OBJECTNUM."""

    result = 0
    if 1 <= self._memory.version <= 3:
      if not (1 <= objectnum <= 255):
        raise ZObjectIllegalObjectNumber
      result = self._objecttree_addr + (9 * (objectnum - 1))
    elif 4 <= self._memory.version <= 5:
      if not (1 <= objectnum <= 65535):
        log("error:  there is no object %d" % objectnum)
        raise ZObjectIllegalObjectNumber
      result = self._objecttree_addr + (14 * (objectnum - 1))
    else:
      raise ZObjectIllegalVersion

    log("address of object %d is %d" % (objectnum, result))
    return result
Example #20
0
  def __init__(self, initial_string):
    """Construct class based on a string that represents an initial
    'snapshot' of main memory."""
    if initial_string is None:
      raise ZMemoryBadInitialization

    # Copy string into a _memory sequence that represents main memory.
    self._total_size = len(initial_string)
    self._memory = [ord(x) for x in initial_string]

    # Figure out the different sections of memory
    self._static_start = self.read_word(0x0e)
    self._static_end = min(0x0ffff, self._total_size)
    self._dynamic_start = 0
    self._dynamic_end = self._static_start - 1
    self._high_start = self.read_word(0x04)
    self._high_end = self._total_size
    self._global_variable_start = self.read_word(0x0c)

    # Dynamic + static must not exceed 64k
    dynamic_plus_static = ((self._dynamic_end - self._dynamic_start)
                           + (self._static_end - self._static_start))
    if dynamic_plus_static > 65534:
      raise ZMemoryBadMemoryLayout

    # What z-machine version is this story file?
    self.version = self._memory[0]

    # Validate game size
    if 1 <= self.version <= 3:
      if self._total_size > 131072:
        raise ZMemoryBadStoryfileSize
    elif 4 <= self.version <=  5:
      if self._total_size > 262144:
        raise ZMemoryBadStoryfileSize
    else:
      raise ZMemoryUnsupportedVersion

    log("Memory system initialized, map follows")
    log("  Dynamic memory: %x - %x" % (self._dynamic_start, self._dynamic_end))
    log("  Static memory: %x - %x" % (self._static_start, self._static_end))
    log("  High memory: %x - %x" % (self._high_start, self._high_end))
    log("  Global variable start: %x" % self._global_variable_start)
Example #21
0
    def __init__(self,
                 start_addr,
                 return_addr,
                 zmem,
                 args,
                 local_vars=None,
                 stack=None):
        """Initialize a routine object beginning at START_ADDR in ZMEM,
    with initial argument values in list ARGS.  If LOCAL_VARS is None,
    then parse them from START_ADDR."""

        self.start_addr = start_addr
        self.return_addr = return_addr
        self.program_counter = 0  # used when execution interrupted

        if stack is None:
            self.stack = []
        else:
            self.stack = stack[:]

        if local_vars is not None:
            self.local_vars = local_vars[:]
        else:
            num_local_vars = zmem[self.start_addr]
            if not (0 <= num_local_vars <= 15):
                log("num local vars is %d" % num_local_vars)
                raise ZStackError
            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.
            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:
                raise ZStackUnsupportedVersion

        # Place call arguments into local vars, if available
        for i in range(0, len(args)):
            self.local_vars[i] = args[i]
Example #22
0
File: zcpu.py Project: swilcox/zvm
    def op_jump(self, offset):
        """Jump unconditionally to the given branch offset.  This
        opcode does not follow the usual branch decision algorithm,
        and so we do not call the _branch method to dispatch the call."""

        old_pc = self._opdecoder.program_counter

        # The offset to the jump instruction is known to be a 2-byte
        # signed integer. We need to make it signed before applying
        # the offset.
        if (offset >= 2**15):
            offset = -2**16 + offset
        log("Jump unconditionally to relative offset %d" % offset)

        # Apparently reading the 2 bytes of operand *isn't* supposed
        # to increment the PC, thus we need to apply this offset to PC
        # that's still pointing at the 'jump' opcode.  Hence the -2
        # modifier below.
        new_pc = self._opdecoder.program_counter + offset - 2
        self._opdecoder.program_counter = new_pc
        log("PC has changed from from %x to %x" % (old_pc, new_pc))
Example #23
0
  def _get_parent_sibling_child(self, objectnum):
    """Return [parent, sibling, child] object numbers of object OBJECTNUM."""

    addr = self._get_object_addr(objectnum)

    result = 0
    if 1 <= self._memory.version <= 3:
      addr += 4  # skip past attributes
      result = self._memory[addr:addr+3]

    elif 4 <= self._memory.version <= 5:
      addr += 6  # skip past attributes
      result = [self._memory.read_word(addr),
                self._memory.read_word(addr + 2),
                self._memory.read_word(addr + 4)]
    else:
      raise ZObjectIllegalVersion

    log ("parent/sibling/child of object %d is %d, %d, %d" %
         (objectnum, result[0], result[1], result[2]))
    return result
Example #24
0
    def op_jump(self, offset):
        """Jump unconditionally to the given branch offset.  This
        opcode does not follow the usual branch decision algorithm,
        and so we do not call the _branch method to dispatch the call."""

        old_pc = self._opdecoder.program_counter

        # The offset to the jump instruction is known to be a 2-byte
        # signed integer. We need to make it signed before applying
        # the offset.
        if (offset >= 2**15):
            offset = - 2**16 + offset
        log("Jump unconditionally to relative offset %d" % offset)

        # Apparently reading the 2 bytes of operand *isn't* supposed
        # to increment the PC, thus we need to apply this offset to PC
        # that's still pointing at the 'jump' opcode.  Hence the -2
        # modifier below.
        new_pc = self._opdecoder.program_counter + offset - 2
        self._opdecoder.program_counter = new_pc
        log("PC has changed from from %x to %x" % (old_pc, new_pc))
Example #25
0
  def write(self, savefile_path):
    """Write the current zmachine state to a new Quetzal-file at
    SAVEFILE_PATH."""

    log("Attempting to write game-state to '%s'" % savefile_path)
    self._file = open(savefile_path, 'w')

    ifhd_chunk = self._generate_ifhd_chunk()
    cmem_chunk = self._generate_cmem_chunk()
    stks_chunk = self._generate_stks_chunk()
    anno_chunk = self._generate_anno_chunk()

    total_chunk_size = len(ifhd_chunk) + len(cmem_chunk) \
                       + len(stks_chunk) + len(anno_chunk)

    # Write main FORM chunk to hold other chunks
    self._file.write("FORM")
    ### TODO: self._file_write(total_chunk_size) -- spread it over 4 bytes
    self._file.write("IFZS")

    # Write nested chunks.
    for chunk in (ifhd_chunk, cmem_chunk, stks_chunk, anno_chunk):
      self._file.write(chunk)
      log("Wrote a chunk.")
    self._file.close()
    log("Done writing game-state to savefile.")
Example #26
0
    def write(self, savefile_path):
        """Write the current zmachine state to a new Quetzal-file at
    SAVEFILE_PATH."""

        log("Attempting to write game-state to '%s'" % savefile_path)
        self._file = open(savefile_path, 'w')

        ifhd_chunk = self._generate_ifhd_chunk()
        cmem_chunk = self._generate_cmem_chunk()
        stks_chunk = self._generate_stks_chunk()
        anno_chunk = self._generate_anno_chunk()

        total_chunk_size = len(ifhd_chunk) + len(cmem_chunk) \
                           + len(stks_chunk) + len(anno_chunk)

        # Write main FORM chunk to hold other chunks
        self._file.write("FORM")
        ### TODO: self._file_write(total_chunk_size) -- spread it over 4 bytes
        self._file.write("IFZS")

        # Write nested chunks.
        for chunk in (ifhd_chunk, cmem_chunk, stks_chunk, anno_chunk):
            self._file.write(chunk)
            log("Wrote a chunk.")
        self._file.close()
        log("Done writing game-state to savefile.")
Example #27
0
    def _parse_ifhd(self, data):
        """Parse a chunk of type IFhd, and check that the quetzal file
    really belongs to the current story (by comparing release number,
    serial number, and checksum.)"""

        # Spec says that this chunk *must* come before memory or stack chunks.
        if self._seen_mem_or_stks:
            raise QuetzalIllegalChunkOrder

        bytes = [ord(x) for x in data]
        if len(bytes) != 13:
            raise QuetzalMalformedChunk

        chunk_release = (ord(data[0]) << 8) + ord(data[1])
        chunk_serial = data[2:8]
        chunk_checksum = (ord(data[8]) << 8) + ord(data[9])
        chunk_pc = (ord(data[10]) << 16) + (ord(data[11]) << 8) + ord(data[12])
        self._zmachine._opdecoder.program_counter = chunk_pc

        log("  Found release number %d" % chunk_release)
        log("  Found serial number %d" % int(chunk_serial))
        log("  Found checksum %d" % chunk_checksum)
        log("  Initial program counter value is %d" % chunk_pc)
        self._last_loaded_metadata["release number"] = chunk_release
        self._last_loaded_metadata["serial number"] = chunk_serial
        self._last_loaded_metadata["checksum"] = chunk_checksum
        self._last_loaded_metadata["program counter"] = chunk_pc

        # Verify the save-file params against the current z-story header
        mem = self._zmachine._mem
        if mem.read_word(2) != chunk_release:
            raise QuetzalMismatchedFile
        serial_bytes = [ord(x) for x in chunk_serial]
        if serial_bytes != mem[0x12:0x18]:
            raise QuetzalMismatchedFile
        mem_checksum = mem.read_word(0x1C)
        if mem_checksum != 0 and (mem_checksum != chunk_checksum):
            raise QuetzalMismatchedFile
        log("  Quetzal file correctly verifies against original story.")
Example #28
0
  def _parse_ifhd(self, data):
    """Parse a chunk of type IFhd, and check that the quetzal file
    really belongs to the current story (by comparing release number,
    serial number, and checksum.)"""

    # Spec says that this chunk *must* come before memory or stack chunks.
    if self._seen_mem_or_stks:
      raise QuetzalIllegalChunkOrder

    bytes = [ord(x) for x in data]
    if len(bytes) != 13:
      raise QuetzalMalformedChunk

    chunk_release = (ord(data[0]) << 8) + ord(data[1])
    chunk_serial = data[2:8]
    chunk_checksum = (ord(data[8]) << 8) + ord(data[9])
    chunk_pc = (ord(data[10]) << 16) + (ord(data[11]) << 8) + ord(data[12])
    self._zmachine._opdecoder.program_counter = chunk_pc

    log("  Found release number %d" % chunk_release)
    log("  Found serial number %d" % int(chunk_serial))
    log("  Found checksum %d" % chunk_checksum)
    log("  Initial program counter value is %d" % chunk_pc)
    self._last_loaded_metadata["release number"] = chunk_release
    self._last_loaded_metadata["serial number"] = chunk_serial
    self._last_loaded_metadata["checksum"] = chunk_checksum
    self._last_loaded_metadata["program counter"] = chunk_pc

    # Verify the save-file params against the current z-story header
    mem = self._zmachine._mem
    if mem.read_word(2) != chunk_release:
      raise QuetzalMismatchedFile
    serial_bytes = [ord(x) for x in chunk_serial]
    if serial_bytes != mem[0x12:0x18]:
      raise QuetzalMismatchedFile
    mem_checksum = mem.read_word(0x1C)
    if mem_checksum != 0 and (mem_checksum != chunk_checksum):
      raise QuetzalMismatchedFile
    log("  Quetzal file correctly verifies against original story.")
Example #29
0
  def __init__(self, start_addr, return_addr, zmem, args,
               local_vars=None, stack=None):
    """Initialize a routine object beginning at START_ADDR in ZMEM,
    with initial argument values in list ARGS.  If LOCAL_VARS is None,
    then parse them from START_ADDR."""

    self.start_addr = start_addr
    self.return_addr = return_addr
    self.program_counter = 0    # used when execution interrupted

    if stack is None:
      self.stack = []
    else:
      self.stack = stack[:]

    if local_vars is not None:
      self.local_vars = local_vars[:]
    else:
      num_local_vars = zmem[self.start_addr]
      if not (0 <= num_local_vars <= 15):
        log("num local vars is %d" % num_local_vars)
        raise ZStackError
      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.
      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:
        raise ZStackUnsupportedVersion

    # Place call arguments into local vars, if available
    for i in range(0, len(args)):
      self.local_vars[i] = args[i]
Example #30
0
    def _get_parent_sibling_child(self, objectnum):
        """Return [parent, sibling, child] object numbers of object OBJECTNUM."""

        addr = self._get_object_addr(objectnum)

        result = 0
        if 1 <= self._memory.version <= 3:
            addr += 4  # skip past attributes
            result = self._memory[addr:addr + 3]

        elif 4 <= self._memory.version <= 5:
            addr += 6  # skip past attributes
            result = [
                self._memory.read_word(addr),
                self._memory.read_word(addr + 2),
                self._memory.read_word(addr + 4)
            ]
        else:
            raise ZObjectIllegalVersion

        log("parent/sibling/child of object %d is %d, %d, %d" %
            (objectnum, result[0], result[1], result[2]))
        return result
Example #31
0
 def _parse_opcode_short(self, opcode):
     """Parse an opcode of the short form."""
     # Short opcodes can have either 1 operand, or no operand.
     log("Opcode is short")
     operand_type = opcode[4:6]
     operand = self._parse_operand(operand_type)
     if operand is None:  # 0OP variant
         log("Opcode is 0OP variant")
         return (OPCODE_0OP, opcode[0:4], [])
     else:
         log("Opcode is 1OP variant")
         return (OPCODE_1OP, opcode[0:4], [operand])
Example #32
0
 def _parse_opcode_short(self, opcode):
   """Parse an opcode of the short form."""
   # Short opcodes can have either 1 operand, or no operand.
   log("Opcode is short")
   operand_type = opcode[4:6]
   operand = self._parse_operand(operand_type)
   if operand is None: # 0OP variant
     log("Opcode is 0OP variant")
     return (OPCODE_0OP, opcode[0:4], [])
   else:
     log("Opcode is 1OP variant")
     return (OPCODE_1OP, opcode[0:4], [operand])
Example #33
0
    def _parse_opcode_variable(self, opcode):
        """Parse an opcode of the variable form."""
        log("Opcode is variable")
        if opcode[5]:
            log("Variable opcode of VAR kind")
            opcode_type = OPCODE_VAR
        else:
            log("Variable opcode of 2OP kind")
            opcode_type = OPCODE_2OP

        opcode_num = opcode[0:5]

        # Parse the types byte to retrieve the operands.
        operands = self._parse_operands_byte()

        # Special case: opcodes 12 and 26 have a second operands byte.
        if opcode[0:7] == 0xC or opcode[0:7] == 0x1A:
            log("Opcode has second operand byte")
            operands += self._parse_operands_byte()

        return (opcode_type, opcode_num, operands)
Example #34
0
  def _parse_opcode_variable(self, opcode):
    """Parse an opcode of the variable form."""
    log("Opcode is variable")
    if opcode[5]:
      log("Variable opcode of VAR kind")
      opcode_type = OPCODE_VAR
    else:
      log("Variable opcode of actually of 2OP kind")
      opcode_type = OPCODE_2OP

    opcode_num = opcode[0:5]

    # Parse the types byte to retrieve the operands.
    operands = self._parse_operands_byte()

    # Special case: opcodes 12 and 26 have a second operands byte.
    if opcode_num == 0xC or opcode_num == 0x1A:
      log("Opcode has second operand byte")
      operands += self._parse_operands_byte()

    return (opcode_type, opcode_num, operands)
Example #35
0
File: zcpu.py Project: swilcox/zvm
    def op_random(self, n):
        """Generate a random number, or seed the PRNG.

        If the input is positive, generate a uniformly random number
        in the range [1:input]. If the input is negative, seed the
        PRNG with that value. If the input is zero, seed the PRNG with
        the current time.
        """
        result = 0
        if n > 0:
            log("Generate random number in [1:%d]" % n)
            result = random.randint(1, n)
        elif n < 0:
            log("Seed PRNG with %d" % n)
            random.seed(n)
        else:
            log("Seed PRNG with time")
            random.seed(time.time())
        self._write_result(result)
Example #36
0
    def op_random(self, n):
        """Generate a random number, or seed the PRNG.

        If the input is positive, generate a uniformly random number
        in the range [1:input]. If the input is negative, seed the
        PRNG with that value. If the input is zero, seed the PRNG with
        the current time.
        """
        result = 0
        if n > 0:
            log("Generate random number in [1:%d]" % n)
            result = random.randint(1, n)
        elif n < 0:
            log("Seed PRNG with %d" % n)
            random.seed(n)
        else:
            log("Seed PRNG with time")
            random.seed(time.time())
        self._write_result(result)
Example #37
0
File: zcpu.py Project: swilcox/zvm
    def run(self):
        """The Magic Function that takes little bits and bytes, twirls
        them around, and brings the magic to your screen!"""
        log("Execution started")
        while True:
            current_pc = self._opdecoder.program_counter
            log("Reading next opcode at address %x" % current_pc)
            (opcode_class, opcode_number,
             operands) = self._opdecoder.get_next_instruction()
            implemented, func = self._get_handler(opcode_class, opcode_number)
            log_disasm(current_pc, zopdecoder.OPCODE_STRINGS[opcode_class],
                       opcode_number, func.__name__,
                       ', '.join([str(x) for x in operands]))
            if not implemented:
                log("Unimplemented opcode %s, "
                    "halting execution" % func.__name__)
                break

            # The returned function is unbound, so we must pass
            # self to it ourselves.
            func(self, *operands)
Example #38
0
  def _parse_umem(self, data):
    """Parse a chunk of type Umem.  Suck a raw image of dynamic memory
    and place it into the ZMachine."""

    ### TODO:  test this by either finding an interpreter that ouptuts
    ## this type of chunk, or by having own QuetzalWriter class
    ## (optionally) do it.
    log("  Loading uncompressed dynamic memory image")
    self._seen_mem_or_stks = True

    cmem = self._zmachine._mem
    dynamic_len = (cmem._dynamic_end - cmem.dynamic_start) + 1
    log("  Dynamic memory length is %d" % dynamic_len)
    self._last_loaded_metadata["dynamic memory length"] = dynamic_len

    savegame_mem = [ord(x) for x in data]
    if len(savegame_mem) != dynamic_len:
      raise QuetzalMemoryMismatch

    cmem[cmem._dynamic_start:(cmem._dynamic_end + 1)] = savegame_mem
    log("  Successfully installed new dynamic memory.")
Example #39
0
File: zcpu.py Project: swilcox/zvm
    def _write_result(self, result_value, store_addr=None):
        """Write the given result value to the stack or to a
        local/global variable.  Write result_value to the store_addr
        variable, or if None, extract the destination variable from
        the opcode."""
        if store_addr == None:
            result_addr = self._opdecoder.get_store_address()
        else:
            result_addr = store_addr

        if result_addr != None:
            if result_addr == 0x0:
                log("Push %d to stack" % result_value)
                self._stackmanager.push_stack(result_value)
            elif 0x0 < result_addr < 0x10:
                log("Local variable %d = %d" % (result_addr - 1, result_value))
                self._stackmanager.set_local_variable(result_addr - 1,
                                                      result_value)
            else:
                log("Global variable %d = %d" % (result_addr, result_value))
                self._memory.write_global(result_addr, result_value)
Example #40
0
    def _parse_umem(self, data):
        """Parse a chunk of type Umem.  Suck a raw image of dynamic memory
    and place it into the ZMachine."""

        ### TODO:  test this by either finding an interpreter that ouptuts
        ## this type of chunk, or by having own QuetzalWriter class
        ## (optionally) do it.
        log("  Loading uncompressed dynamic memory image")
        self._seen_mem_or_stks = True

        cmem = self._zmachine._mem
        dynamic_len = (cmem._dynamic_end - cmem.dynamic_start) + 1
        log("  Dynamic memory length is %d" % dynamic_len)
        self._last_loaded_metadata["dynamic memory length"] = dynamic_len

        savegame_mem = [ord(x) for x in data]
        if len(savegame_mem) != dynamic_len:
            raise QuetzalMemoryMismatch

        cmem[cmem._dynamic_start:(cmem._dynamic_end + 1)] = savegame_mem
        log("  Successfully installed new dynamic memory.")
Example #41
0
    def run(self):
        """The Magic Function that takes little bits and bytes, twirls
        them around, and brings the magic to your screen!"""
        log("Execution started")
        while True:
            current_pc = self._opdecoder.program_counter
            log("Reading next opcode at address %x" % current_pc)
            (opcode_class, opcode_number,
             operands) = self._opdecoder.get_next_instruction()
            implemented, func = self._get_handler(opcode_class,
                                                  opcode_number)
            log_disasm(current_pc, zopdecoder.OPCODE_STRINGS[opcode_class],
                       opcode_number, func.__name__,
                       ', '.join([str(x) for x in operands]))
            if not implemented:
                log("Unimplemented opcode %s, "
                    "halting execution" % func.__name__)
                break

            # The returned function is unbound, so we must pass
            # self to it ourselves.
            func(self, *operands)
Example #42
0
    def _write_result(self, result_value, store_addr=None):
        """Write the given result value to the stack or to a
        local/global variable.  Write result_value to the store_addr
        variable, or if None, extract the destination variable from
        the opcode."""
        if store_addr == None:
            result_addr = self._opdecoder.get_store_address()
        else:
            result_addr = store_addr

        if result_addr != None:
            if result_addr == 0x0:
                log("Push %d to stack" % result_value)
                self._stackmanager.push_stack(result_value)
            elif 0x0 < result_addr < 0x10:
                log("Local variable %d = %d" % (
                    result_addr - 1, result_value))
                self._stackmanager.set_local_variable(result_addr - 1,
                                                      result_value)
            else:
                log("Global variable %d = %d" % (result_addr,
                                                 result_value))
                self._memory.write_global(result_addr, result_value)
Example #43
0
 def __init__(self, zmachine):
     log("Creating new instance of QuetzalWriter")
     self._zmachine = zmachine
Example #44
0
    def load(self, savefile_path):
        """Parse each chunk of the Quetzal file at SAVEFILE_PATH,
    initializing associated zmachine subsystems as needed."""

        self._last_loaded_metadata = {}

        if not os.path.isfile(savefile_path):
            raise QuetzalNoSuchSavefile

        log("Attempting to load saved game from '%s'" % savefile_path)
        self._file = open(savefile_path)

        # The python 'chunk' module is pretty dumb; it doesn't understand
        # the FORM chunk and the way it contains nested chunks.
        # Therefore, we deliberately seek 12 bytes into the file so that
        # we can start sucking out chunks.  This also allows us to
        # validate that the FORM type is "IFZS".
        header = self._file.read(4)
        if header != "FORM":
            raise QuetzalUnrecognizedFileFormat
        bytestring = self._file.read(4)
        self._len = ord(bytestring[0]) << 24
        self._len += (ord(bytestring[1]) << 16)
        self._len += (ord(bytestring[2]) << 8)
        self._len += ord(bytestring[3])
        log("Total length of FORM data is %d" % self._len)
        self._last_loaded_metadata["total length"] = self._len

        type = self._file.read(4)
        if type != "IFZS":
            raise QuetzalUnrecognizedFileFormat

        try:
            while 1:
                c = chunk.Chunk(self._file)
                chunkname = c.getname()
                chunksize = c.getsize()
                data = c.read(chunksize)
                log("** Found chunk ID %s: length %d" % (chunkname, chunksize))
                self._last_loaded_metadata[chunkname] = chunksize

                if chunkname == "IFhd":
                    self._parse_ifhd(data)
                elif chunkname == "CMem":
                    self._parse_cmem(data)
                elif chunkname == "UMem":
                    self._parse_umem(data)
                elif chunkname == "Stks":
                    self._parse_stks(data)
                elif chunkname == "IntD":
                    self._parse_intd(data)
                elif chunkname == "AUTH":
                    self._parse_auth(data)
                elif chunkname == "(c) ":
                    self._parse_copyright(data)
                elif chunkname == "ANNO":
                    self._parse_anno(data)
                else:
                    # spec says to ignore and skip past unrecognized chunks
                    pass

        except EOFError:
            pass

        self._file.close()
        log("Finished parsing Quetzal file.")
Example #45
0
    def _parse_anno(self, data):
        """Parse a chunk of type ANNO.  Display any annotation"""

        log("Annotation: %s" % data)
        self._last_loaded_metadata["annotation"] = data
Example #46
0
    def _parse_copyright(self, data):
        """Parse a chunk of type (c) .  Display the copyright."""

        log("Copyright: (C) %s" % data)
        self._last_loaded_metadata["copyright"] = data
Example #47
0
  def _parse_copyright(self, data):
    """Parse a chunk of type (c) .  Display the copyright."""

    log("Copyright: (C) %s" % data)
    self._last_loaded_metadata["copyright"] = data
Example #48
0
 def __init__(self, zmachine):
     log("Creating new instance of QuetzalParser")
     self._zmachine = zmachine
     self._seen_mem_or_stks = False
     self._last_loaded_metadata = {}  # metadata for tests & debugging
Example #49
0
    def _parse_cmem(self, data):
        """Parse a chunk of type Cmem.  Decompress an image of dynamic
    memory, and place it into the ZMachine."""

        log("  Decompressing dynamic memory image")
        self._seen_mem_or_stks = True

        # Just duplicate the dynamic memory block of the pristine story image,
        # and then make tweaks to it as we decode the runlength-encoding.
        pmem = self._zmachine._pristine_mem
        cmem = self._zmachine._mem
        savegame_mem = list(pmem[pmem._dynamic_start:(pmem._dynamic_end + 1)])
        memlen = len(savegame_mem)
        memcounter = 0
        log("  Dynamic memory length is %d" % memlen)
        self._last_loaded_metadata["memory length"] = memlen

        runlength_bytes = [ord(x) for x in data]
        bytelen = len(runlength_bytes)
        bytecounter = 0

        log("  Decompressing dynamic memory image")
        while bytecounter < bytelen:
            byte = runlength_bytes[bytecounter]
            if byte != 0:
                savegame_mem[memcounter] = byte ^ pmem[memcounter]
                memcounter += 1
                bytecounter += 1
                log("   Set byte %d:%d" %
                    (memcounter, savegame_mem[memcounter]))
            else:
                bytecounter += 1
                num_extra_zeros = runlength_bytes[bytecounter]
                memcounter += (1 + num_extra_zeros)
                bytecounter += 1
                log("   Skipped %d unchanged bytes" % (1 + num_extra_zeros))
            if memcounter >= memlen:
                raise QuetzalMemoryOutOfBounds

        # If memcounter finishes less then memlen, that's totally fine, it
        # just means there are no more diffs to apply.

        cmem[cmem._dynamic_start:(cmem._dynamic_end + 1)] = savegame_mem
        log("  Successfully installed new dynamic memory.")
Example #50
0
  def _parse_operand(self, operand_type):
    """Read and return an operand of the given type.

    This assumes that the operand is in memory, at the address pointed
    by the Program Counter."""
    assert operand_type <= 0x3

    if operand_type == LARGE_CONSTANT:
      log("Operand is large constant")
      operand = self._memory.read_word(self.program_counter)
      self.program_counter += 2
    elif operand_type == SMALL_CONSTANT:
      log("Operand is small constant")
      operand = self._get_pc()
    elif operand_type == VARIABLE:
      variable_number = self._get_pc()
      log("Operand is variable %d" % variable_number)
      if variable_number == 0:
        log("Operand value comes from stack")
        operand = self._stack.pop_stack() # TODO: make sure this is right.
      elif variable_number < 16:
        log("Operand value comes from local variable")
        operand = self._stack.get_local_variable(variable_number - 1)
      else:
        log("Operand value comes from global variable")
        operand = self._memory.read_global(variable_number)
    elif operand_type == ABSENT:
      log("Operand is absent")
      operand = None
    if operand is not None:
      log("Operand value: %d" % operand)

    return operand
Example #51
0
 def split_window(self, height):
   log("TODO: split window here to height %d" % height)
Example #52
0
 def select_window(self, window_num):
   log("TODO: select window %d here" % window_num)
Example #53
0
 def set_cursor_position(self, x, y):
   log("TODO: set cursor position to (%d,%d) here" % (x,y))
Example #54
0
 def __init__(self, zmachine):
   log("Creating new instance of QuetzalWriter")
   self._zmachine = zmachine
Example #55
0
  def load(self, savefile_path):
    """Parse each chunk of the Quetzal file at SAVEFILE_PATH,
    initializing associated zmachine subsystems as needed."""

    self._last_loaded_metadata = {}

    if not os.path.isfile(savefile_path):
      raise QuetzalNoSuchSavefile

    log("Attempting to load saved game from '%s'" % savefile_path)
    self._file = open(savefile_path)

    # The python 'chunk' module is pretty dumb; it doesn't understand
    # the FORM chunk and the way it contains nested chunks.
    # Therefore, we deliberately seek 12 bytes into the file so that
    # we can start sucking out chunks.  This also allows us to
    # validate that the FORM type is "IFZS".
    header = self._file.read(4)
    if header != "FORM":
      raise QuetzalUnrecognizedFileFormat
    bytestring = self._file.read(4)
    self._len = ord(bytestring[0]) << 24
    self._len += (ord(bytestring[1]) << 16)
    self._len += (ord(bytestring[2]) << 8)
    self._len += ord(bytestring[3])
    log("Total length of FORM data is %d" % self._len)
    self._last_loaded_metadata["total length"] = self._len

    type = self._file.read(4)
    if type != "IFZS":
      raise QuetzalUnrecognizedFileFormat

    try:
      while 1:
        c = chunk.Chunk(self._file)
        chunkname = c.getname()
        chunksize = c.getsize()
        data = c.read(chunksize)
        log("** Found chunk ID %s: length %d" % (chunkname, chunksize))
        self._last_loaded_metadata[chunkname] = chunksize

        if chunkname == "IFhd":
          self._parse_ifhd(data)
        elif chunkname == "CMem":
          self._parse_cmem(data)
        elif chunkname == "UMem":
          self._parse_umem(data)
        elif chunkname == "Stks":
          self._parse_stks(data)
        elif chunkname == "IntD":
          self._parse_intd(data)
        elif chunkname == "AUTH":
          self._parse_auth(data)
        elif chunkname == "(c) ":
          self._parse_copyright(data)
        elif chunkname == "ANNO":
          self._parse_anno(data)
        else:
          # spec says to ignore and skip past unrecognized chunks
          pass

    except EOFError:
      pass

    self._file.close()
    log("Finished parsing Quetzal file.")
Example #56
0
    def _parse_stks(self, data):
        """Parse a chunk of type Stks."""

        log("  Begin parsing of stack frames")

        # Our strategy here is simply to create an entirely new
        # ZStackManager object and populate it with a series of ZRoutine
        # stack-frames parses from the quetzal file.  We then attach this
        # new ZStackManager to our z-machine, and allow the old one to be
        # garbage collected.
        stackmanager = zstackmanager.ZStackManager(self._zmachine._mem)

        self._seen_mem_or_stks = True
        bytes = [ord(x) for x in data]
        total_len = len(bytes)
        ptr = 0

        # Read successive stack frames:
        while (ptr < total_len):
            log("  Parsing stack frame...")
            return_pc = (bytes[ptr] << 16) + (
                bytes[ptr + 1] << 8) + bytes[ptr + 3]
            ptr += 3
            flags_bitfield = bitfield.BitField(bytes[ptr])
            ptr += 1
            varnum = bytes[
                ptr]  ### TODO: tells us which variable gets the result
            ptr += 1
            argflag = bytes[ptr]
            ptr += 1
            evalstack_size = (bytes[ptr] << 8) + bytes[ptr + 1]
            ptr += 2

            # read anywhere from 0 to 15 local vars
            local_vars = []
            for i in range(flags_bitfield[0:3]):
                var = (bytes[ptr] << 8) + bytes[ptr + 1]
                ptr += 2
                local_vars.append(var)
            log("    Found %d local vars" % len(local_vars))

            # least recent to most recent stack values:
            stack_values = []
            for i in range(evalstack_size):
                val = (bytes[ptr] << 8) + bytes[ptr + 1]
                ptr += 2
                stack_values.append(val)
            log("    Found %d local stack values" % len(stack_values))

            ### Interesting... the reconstructed stack frames have no 'start
            ### address'.  I guess it doesn't matter, since we only need to
            ### pop back to particular return addresses to resume each
            ### routine.

            ### TODO: I can exactly which of the 7 args is "supplied", but I
            ### don't understand where the args *are*??

            routine = zstackmanager.ZRoutine(0, return_pc, self._zmachine._mem,
                                             [], local_vars, stack_values)
            stackmanager.push_routine(routine)
            log("    Added new frame to stack.")

            if (ptr > total_len):
                raise QuetzalStackFrameOverflow

        self._zmachine._stackmanager = stackmanager
        log("  Successfully installed all stack frames.")
Example #57
0
  def _parse_anno(self, data):
    """Parse a chunk of type ANNO.  Display any annotation"""

    log("Annotation: %s" % data)
    self._last_loaded_metadata["annotation"] = data
Example #58
0
 def __init__(self, zmachine):
   log("Creating new instance of QuetzalParser")
   self._zmachine = zmachine
   self._seen_mem_or_stks = False
   self._last_loaded_metadata = {}  # metadata for tests & debugging
Example #59
0
    def _parse_auth(self, data):
        """Parse a chunk of type AUTH.  Display the author."""

        log("Author of file: %s" % data)
        self._last_loaded_metadata["author"] = data
Example #60
0
    def _parse_operand(self, operand_type):
        """Read and return an operand of the given type.

    This assumes that the operand is in memory, at the address pointed
    by the Program Counter."""
        assert operand_type <= 0x3

        if operand_type == LARGE_CONSTANT:
            log("Operand is large constant")
            operand = self._memory.read_word(self.program_counter)
            self.program_counter += 2
        elif operand_type == SMALL_CONSTANT:
            log("Operand is small constant")
            operand = self._get_pc()
        elif operand_type == VARIABLE:
            variable_number = self._get_pc()
            log("Operand is variable %d" % variable_number)
            if variable_number == 0:
                log("Operand value comes from stack")
                operand = self._stack.pop_stack(
                )  # TODO: make sure this is right.
            elif variable_number < 16:
                log("Operand value comes from local variable")
                operand = self._stack.get_local_variable(variable_number - 1)
            else:
                log("Operand value comes from global variable")
                operand = self._memory.read_global(variable_number)
        elif operand_type == ABSENT:
            log("Operand is absent")
            operand = None
        if operand is not None:
            log("Operand value: %d" % operand)

        return operand