def __init__(self, mem): self._memory = mem self._stringfactory = ZStringFactory(self._memory) self._zsciitranslator = ZsciiTranslator(self._memory) # Load and parse game's 'standard' dictionary from static memory. dict_addr = self._memory.read_word(0x08) self._num_entries, self._entry_length, self._separators, entries_addr = \ self._parse_dict_header(dict_addr) self._dict = self.get_dictionary(dict_addr)
def __init__(self, zmem): self._memory = zmem self._propdefaults_addr = zmem.read_word(0x0a) self._stringfactory = ZStringFactory(self._memory) if 1 <= self._memory.version <= 3: self._objecttree_addr = self._propdefaults_addr + 62 elif 4 <= self._memory.version <= 5: self._objecttree_addr = self._propdefaults_addr + 126 else: raise ZObjectIllegalVersion
def __init__(self, story, ui, debugmode=False): zlogging.set_debug(debugmode) self._pristine_mem = ZMemory(story) # the original memory image self._mem = ZMemory( story) # the memory image which changes during play self._stringfactory = ZStringFactory(self._mem) self._objectparser = ZObjectParser(self._mem) self._stackmanager = ZStackManager(self._mem) self._opdecoder = ZOpDecoder(self._mem, self._stackmanager) self._opdecoder.program_counter = self._mem.read_word(0x06) self._ui = ui self._stream_manager = ZStreamManager(self._mem, self._ui) self._cpu = ZCpu(self._mem, self._opdecoder, self._stackmanager, self._objectparser, self._stringfactory, self._stream_manager, self._ui)
class ZObjectParser(object): def __init__(self, zmem): self._memory = zmem self._propdefaults_addr = zmem.read_word(0x0a) self._stringfactory = ZStringFactory(self._memory) if 1 <= self._memory.version <= 3: self._objecttree_addr = self._propdefaults_addr + 62 elif 4 <= self._memory.version <= 5: self._objecttree_addr = self._propdefaults_addr + 126 else: raise ZObjectIllegalVersion 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 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 def _get_proptable_addr(self, objectnum): """Return address of property table of object OBJECTNUM.""" addr = self._get_object_addr(objectnum) # skip past attributes and relatives if 1 <= self._memory.version <= 3: addr += 7 elif 4 <= self._memory.version <= 5: addr += 12 else: raise ZObjectIllegalVersion return self._memory.read_word(addr) def _get_default_property_addr(self, propnum): """Return address of default value for property PROPNUM.""" addr = self._propdefaults_addr if 1 <= self._memory.version <= 3: if not (1 <= propnum <= 31): raise ZObjectIllegalPropertyNumber elif 4 <= self._memory.version <= 5: if not (1 <= propnum <= 63): raise ZObjectIllegalPropertyNumber else: raise ZObjectIllegalVersion return (addr + (2 * (propnum - 1))) #--------- Public APIs ----------- def get_attribute(self, objectnum, attrnum): """Return value (0 or 1) of attribute number ATTRNUM of object number OBJECTNUM.""" object_addr = self._get_object_addr(objectnum) if 1 <= self._memory.version <= 3: if not (0 <= attrnum <= 31): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) elif 4 <= self._memory.version <= 5: if not (0 <= attrnum <= 47): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) else: raise ZObjectIllegalVersion return bf[7 - (attrnum % 8)] def get_all_attributes(self, objectnum): """Return a list of all attribute numbers that are set on object OBJECTNUM""" if 1 <= self._memory.version <= 3: max = 32 elif 4 <= self._memory.version <= 5: max = 48 else: raise ZObjectIllegalVersion # really inefficient, but who cares? attrs = [] for i in range (0, max): if self.get_attribute(objectnum, i): attrs.append(i) return attrs def get_parent(self, objectnum): """Return object number of parent of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return parent def get_child(self, objectnum): """Return object number of child of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return child def get_sibling(self, objectnum): """Return object number of sibling of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return sibling def set_parent(self, objectnum, new_parent_num): """Make OBJECTNUM's parent pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 6, new_parent_num) else: raise ZObjectIllegalVersion def set_child(self, objectnum, new_child_num): """Make OBJECTNUM's child pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 10, new_child_num) else: raise ZObjectIllegalVersion def set_sibling(self, objectnum, new_sibling_num): """Make OBJECTNUM's sibling pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 8, new_sibling_num) else: raise ZObjectIllegalVersion def insert_object(self, parent_object, new_child): """Prepend object NEW_CHILD to the list of PARENT_OBJECT's children.""" # Remember all the original pointers within the new_child [p, s, c] = self._get_parent_sibling_child(new_child) # First insert new_child intto the parent_object original_child = self.get_child(parent_object) self.set_sibling(new_child, original_child) self.set_parent(new_child, parent_object) self.set_child(parent_object, new_child) if p == 0: # no need to 'remove' new_child, since it wasn't in a tree return # Hunt down and remove the new_child from its old location item = self.get_child(p) if item == 0: # new_object claimed to have parent p, but p has no children!? raise ZObjectMalformedTree elif item == new_child: # done! new_object was head of list self.set_child(p, s) # note that s might be 0, that's fine. else: # walk across list of sibling links prev = item current = self.get_sibling(item) while current != 0: if current == new_child: self.set_sibling(prev, s) # s might be 0, that's fine. break else: # we reached the end of the list, never got a match raise ZObjectMalformedTree def get_shortname(self, objectnum): """Return 'short name' of object number OBJECTNUM as ascii string.""" addr = self._get_proptable_addr(objectnum) return self._stringfactory.get(addr+1) def get_prop(self, objectnum, propnum): """Return either a byte or word value of property PROPNUM of object OBJECTNUM.""" (addr, size) = self.get_prop_addr_len(objectnum, propnum) if size == 1: return self._memory[addr] elif size == 2: return self._memory.read_word(addr) else: raise ZObjectIllegalPropLength def get_prop_addr_len(self, objectnum, propnum): """Return address & length of value for property number PROPNUM of object number OBJECTNUM. If object has no such property, then return the address & length of the 'default' value for the property.""" # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object addr += (2 * self._memory[addr]) pnum = 0 if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 if pnum == propnum: return (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[5:0] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[5:0] else: if bf[6]: size = 2 else: size = 1 if pnum == propnum: return (addr, size) addr += size else: raise ZObjectIllegalVersion # property list ran out, so return default propval instead. default_value_addr = self._get_default_property_addr(propnum) return (default_value_addr, 2) def get_all_properties(self, objectnum): """Return a dictionary of all properties listed in the property table of object OBJECTNUM. (Obviously, this discounts 'default' property values.). The dictionary maps property numbers to (addr, len) propval tuples.""" proplist = {} # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object shortname_length = self._memory[addr] addr += 1 addr += (2*shortname_length) if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 proplist[pnum] = (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[0:6] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[0:6] if size == 0: size = 64 else: if bf[6]: size = 2 else: size = 1 proplist[pnum] = (addr, size) addr += size else: raise ZObjectIllegalVersion return proplist def set_property(self, objectnum, propnum, value): """Set a property on an object.""" proplist = self.get_all_properties(objectnum) if propnum not in proplist: raise ZObjectIllegalPropertyNumber addr, size = proplist[propnum] if size == 1: self._memory[addr] = (value & 0xFF) elif size == 2: self._memory.write_word(addr, value) else: raise ZObjectIllegalPropertySet def describe_object(self, objectnum): """For debugging purposes, pretty-print everything known about object OBJECTNUM.""" print "Object number:", objectnum print " Short name:", self.get_shortname(objectnum) print " Parent:", self.get_parent(objectnum), print " Sibling:", self.get_sibling(objectnum), print " Child:", self.get_child(objectnum) print " Attributes:", self.get_all_attributes(objectnum) print " Properties:" proplist = self.get_all_properties(objectnum) for key in proplist.keys(): (addr, len) = proplist[key] print " [%2d] :" % key, for i in range(0, len): print "%02X" % self._memory[addr+i], print
class ZObjectParser(object): def __init__(self, zmem): self._memory = zmem self._propdefaults_addr = zmem.read_word(0x0a) self._stringfactory = ZStringFactory(self._memory) if 1 <= self._memory.version <= 3: self._objecttree_addr = self._propdefaults_addr + 62 elif 4 <= self._memory.version <= 5: self._objecttree_addr = self._propdefaults_addr + 126 else: raise ZObjectIllegalVersion 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 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 def _get_proptable_addr(self, objectnum): """Return address of property table of object OBJECTNUM.""" addr = self._get_object_addr(objectnum) # skip past attributes and relatives if 1 <= self._memory.version <= 3: addr += 7 elif 4 <= self._memory.version <= 5: addr += 12 else: raise ZObjectIllegalVersion return self._memory.read_word(addr) def _get_default_property_addr(self, propnum): """Return address of default value for property PROPNUM.""" addr = self._propdefaults_addr if 1 <= self._memory.version <= 3: if not (1 <= propnum <= 31): raise ZObjectIllegalPropertyNumber elif 4 <= self._memory.version <= 5: if not (1 <= propnum <= 63): raise ZObjectIllegalPropertyNumber else: raise ZObjectIllegalVersion return (addr + (2 * (propnum - 1))) #--------- Public APIs ----------- def get_attribute(self, objectnum, attrnum): """Return value (0 or 1) of attribute number ATTRNUM of object number OBJECTNUM.""" object_addr = self._get_object_addr(objectnum) if 1 <= self._memory.version <= 3: if not (0 <= attrnum <= 31): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) elif 4 <= self._memory.version <= 5: if not (0 <= attrnum <= 47): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) else: raise ZObjectIllegalVersion return bf[7 - (attrnum % 8)] def get_all_attributes(self, objectnum): """Return a list of all attribute numbers that are set on object OBJECTNUM""" if 1 <= self._memory.version <= 3: max = 32 elif 4 <= self._memory.version <= 5: max = 48 else: raise ZObjectIllegalVersion # really inefficient, but who cares? attrs = [] for i in range(0, max): if self.get_attribute(objectnum, i): attrs.append(i) return attrs def get_parent(self, objectnum): """Return object number of parent of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return parent def get_child(self, objectnum): """Return object number of child of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return child def get_sibling(self, objectnum): """Return object number of sibling of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return sibling def set_parent(self, objectnum, new_parent_num): """Make OBJECTNUM's parent pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 6, new_parent_num) else: raise ZObjectIllegalVersion def set_child(self, objectnum, new_child_num): """Make OBJECTNUM's child pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 10, new_child_num) else: raise ZObjectIllegalVersion def set_sibling(self, objectnum, new_sibling_num): """Make OBJECTNUM's sibling pointer point to NEW_PARENT_NUM.""" 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: self._memory.write_word(addr + 8, new_sibling_num) else: raise ZObjectIllegalVersion def insert_object(self, parent_object, new_child): """Prepend object NEW_CHILD to the list of PARENT_OBJECT's children.""" # Remember all the original pointers within the new_child [p, s, c] = self._get_parent_sibling_child(new_child) # First insert new_child intto the parent_object original_child = self.get_child(parent_object) self.set_sibling(new_child, original_child) self.set_parent(new_child, parent_object) self.set_child(parent_object, new_child) if p == 0: # no need to 'remove' new_child, since it wasn't in a tree return # Hunt down and remove the new_child from its old location item = self.get_child(p) if item == 0: # new_object claimed to have parent p, but p has no children!? raise ZObjectMalformedTree elif item == new_child: # done! new_object was head of list self.set_child(p, s) # note that s might be 0, that's fine. else: # walk across list of sibling links prev = item current = self.get_sibling(item) while current != 0: if current == new_child: self.set_sibling(prev, s) # s might be 0, that's fine. break else: # we reached the end of the list, never got a match raise ZObjectMalformedTree def get_shortname(self, objectnum): """Return 'short name' of object number OBJECTNUM as ascii string.""" addr = self._get_proptable_addr(objectnum) return self._stringfactory.get(addr + 1) def get_prop(self, objectnum, propnum): """Return either a byte or word value of property PROPNUM of object OBJECTNUM.""" (addr, size) = self.get_prop_addr_len(objectnum, propnum) if size == 1: return self._memory[addr] elif size == 2: return self._memory.read_word(addr) else: raise ZObjectIllegalPropLength def get_prop_addr_len(self, objectnum, propnum): """Return address & length of value for property number PROPNUM of object number OBJECTNUM. If object has no such property, then return the address & length of the 'default' value for the property.""" # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object addr += (2 * self._memory[addr]) pnum = 0 if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 if pnum == propnum: return (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[5:0] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[5:0] else: if bf[6]: size = 2 else: size = 1 if pnum == propnum: return (addr, size) addr += size else: raise ZObjectIllegalVersion # property list ran out, so return default propval instead. default_value_addr = self._get_default_property_addr(propnum) return (default_value_addr, 2) def get_all_properties(self, objectnum): """Return a dictionary of all properties listed in the property table of object OBJECTNUM. (Obviously, this discounts 'default' property values.). The dictionary maps property numbers to (addr, len) propval tuples.""" proplist = {} # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object shortname_length = self._memory[addr] addr += 1 addr += (2 * shortname_length) if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 proplist[pnum] = (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[0:6] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[0:6] if size == 0: size = 64 else: if bf[6]: size = 2 else: size = 1 proplist[pnum] = (addr, size) addr += size else: raise ZObjectIllegalVersion return proplist def set_property(self, objectnum, propnum, value): """Set a property on an object.""" proplist = self.get_all_properties(objectnum) if propnum not in proplist: raise ZObjectIllegalPropertyNumber addr, size = proplist[propnum] if size == 1: self._memory[addr] = (value & 0xFF) elif size == 2: self._memory.write_word(addr, value) else: raise ZObjectIllegalPropertySet def describe_object(self, objectnum): """For debugging purposes, pretty-print everything known about object OBJECTNUM.""" print "Object number:", objectnum print " Short name:", self.get_shortname(objectnum) print " Parent:", self.get_parent(objectnum), print " Sibling:", self.get_sibling(objectnum), print " Child:", self.get_child(objectnum) print " Attributes:", self.get_all_attributes(objectnum) print " Properties:" proplist = self.get_all_properties(objectnum) for key in proplist.keys(): (addr, len) = proplist[key] print " [%2d] :" % key, for i in range(0, len): print "%02X" % self._memory[addr + i], print
class ZLexer(object): def __init__(self, mem): self._memory = mem self._stringfactory = ZStringFactory(self._memory) self._zsciitranslator = ZsciiTranslator(self._memory) # Load and parse game's 'standard' dictionary from static memory. dict_addr = self._memory.read_word(0x08) self._num_entries, self._entry_length, self._separators, entries_addr = \ self._parse_dict_header(dict_addr) self._dict = self.get_dictionary(dict_addr) def _parse_dict_header(self, address): """Parse the header of the dictionary at ADDRESS. Return the number of entries, the length of each entry, a list of zscii word separators, and an address of the beginning the entries.""" addr = address num_separators = self._memory[addr] separators = self._memory[(addr + 1):(addr + num_separators)] addr += (1 + num_separators) entry_length = self._memory[addr] addr += 1 num_entries = self._memory.read_word(addr) addr += 2 return num_entries, entry_length, separators, addr def _tokenise_string(self, string, separators): """Split unicode STRING into a list of words, and return the list. Whitespace always counts as a word separator, but so do any unicode characters provided in the list of SEPARATORS. Note, however, that instances of these separators caunt as words themselves.""" # re.findall(r'[,.;]|\w+', 'abc, def') sep_string = "" for sep in separators: sep_string += sep if sep_string == "": regex = r"\w+" else: regex = r"[%s]|\w+" % sep_string return re.findall(regex, string) #--------- Public APIs ----------- def get_dictionary(self, address): """Load a z-machine-format dictionary at ADDRESS -- which maps zstrings to bytestrings -- into a python dictionary which maps unicode strings to the address of the word in the original dictionary. Return the new dictionary.""" dict = {} num_entries, entry_length, separators, addr = \ self._parse_dict_header(address) for i in range(0, num_entries): text_key = self._stringfactory.get(addr) dict[text_key] = addr addr += entry_length return dict def parse_input(self, string, dict_addr=None): """Given a unicode string, parse it into words based on a dictionary. if DICT_ADDR is provided, use the custom dictionary at that address to do the analysis, otherwise default to using the game's 'standard' dictionary. The dictionary plays two roles: first, it specifies separator characters beyond the usual space character. Second, we need to look up each word in the dictionary and return the address. Return a list of lists, each list being of the form [word, byte_address_of_word_in_dictionary (or 0 if not in dictionary)] """ if dict_addr is None: zseparators = self._separators dict = self._dict else: num_entries, entry_length, zseparators, addr = \ self._parse_dict_header(dict_addr) dict = self.get_dictionary(dict_addr) # Our list of word separators are actually zscii codes that must # be converted to unicode before we can use them. separators = [] for code in zseparators: separators.append(self._zsciitranslator.ztou(code)) token_list = self._tokenise_string(string, separators) final_list = [] for word in token_list: if dict.has_key(word): byte_addr = dict[word] else: byte_addr = 0 final_list.append([word, byte_addr]) return final_list
class ZObjectParser(object): def __init__(self, zmem): self._memory = zmem self._propdefaults_addr = zmem.read_word(0x0a) self._stringfactory = ZStringFactory(self._memory) if 1 <= self._memory.version <= 3: self._objecttree_addr = self._propdefaults_addr + 62 elif 4 <= self._memory.version <= 5: self._objecttree_addr = self._propdefaults_addr + 126 else: raise ZObjectIllegalVersion def _get_object_addr(self, objectnum): """Return address of object number OBJECTNUM.""" if 1 <= self._memory.version <= 3: if not (1 <= objectnum <= 255): raise ZObjectIllegalObjectNumber return self._objecttree_addr + (9 * (objectnum - 1)) elif 4 <= self._memory.version <= 5: if not (1 <= objectnum <= 65535): raise ZObjectIllegalObjectNumber return self._objecttree_addr + (14 * (objectnum - 1)) else: raise ZObjectIllegalVersion def _get_parent_sibling_child(self, objectnum): """Return [parent, sibling, child] object numbers of object OBJECTNUM.""" addr = self._get_object_addr(objectnum) if 1 <= self._memory.version <= 3: addr += 4 # skip past attributes return self._memory[addr:addr+3] elif 4 <= self._memory.version <= 5: addr += 6 # skip past attributes return [self._memory.read_word(addr), self._memory.read_word(addr + 2), self._memory.read_word(addr + 4)] else: raise ZObjectIllegalVersion def _get_proptable_addr(self, objectnum): """Return address of property table of object OBJECTNUM.""" addr = self._get_object_addr(objectnum) # skip past attributes and relatives if 1 <= self._memory.version <= 3: addr += 7 elif 4 <= self._memory.version <= 5: addr += 12 else: raise ZObjectIllegalVersion return self._memory.read_word(addr) def _get_default_property_addr(self, propnum): """Return address of default value for property PROPNUM.""" addr = self._propdefaults_addr if 1 <= self._memory.version <= 3: if not (1 <= propnum <= 31): raise ZObjectIllegalPropertyNumber elif 4 <= self._memory.version <= 5: if not (1 <= propnum <= 63): raise ZObjectIllegalPropertyNumber else: raise ZObjectIllegalVersion return (addr + (2 * (propnum - 1))) #--------- Public APIs ----------- def get_attribute(self, objectnum, attrnum): """Return value (0 or 1) of attribute number ATTRNUM of object number OBJECTNUM.""" object_addr = self._get_object_addr(objectnum) if 1 <= self._memory.version <= 3: if not (0 <= attrnum <= 31): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) elif 4 <= self._memory.version <= 5: if not (0 <= attrnum <= 47): raise ZObjectIllegalAttributeNumber bf = BitField(self._memory[object_addr + (attrnum / 8)]) else: raise ZObjectIllegalVersion return bf[7 - (attrnum % 8)] def get_all_attributes(self, objectnum): """Return a list of all attribute numbers that are set on object OBJECTNUM""" if 1 <= self._memory.version <= 3: max = 32 elif 4 <= self._memory.version <= 5: max = 48 else: raise ZObjectIllegalVersion # really inefficient, but who cares? attrs = [] for i in range (0, max): if self.get_attribute(objectnum, i): attrs.append(i) return attrs def get_parent(self, objectnum): """Return object number of parent of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return parent def get_child(self, objectnum): """Return object number of child of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return child def get_sibling(self, objectnum): """Return object number of sibling of object number OBJECTNUM.""" [parent, sibling, child] = self._get_parent_sibling_child(objectnum) return sibling def get_shortname(self, objectnum): """Return 'short name' of object number OBJECTNUM as ascii string.""" addr = self._get_proptable_addr(objectnum) return self._stringfactory.get(addr+1) def get_prop_addr_len(self, objectnum, propnum): """Return address & length of value for property number PROPNUM of object number OBJECTNUM. If object has no such property, then return the address & length of the 'default' value for the property.""" # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object addr += (2 * self._memory[addr]) pnum = 0 if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 if pnum == propnum: return (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[5:0] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[5:0] else: if bf[6]: size = 2 else: size = 1 if pnum == propnum: return (addr, size) addr += size else: raise ZObjectIllegalVersion # property list ran out, so return default propval instead. default_value_addr = self._get_default_property_addr(propnum) return (default_value_addr, 2) def get_all_properties(self, objectnum): """Return a dictionary of all properties listed in the property table of object OBJECTNUM. (Obviously, this discounts 'default' property values.). The dictionary maps property numbers to (addr, len) propval tuples.""" proplist = {} # start at the beginning of the object's proptable addr = self._get_proptable_addr(objectnum) # skip past the shortname of the object shortname_length = self._memory[addr] addr += 1 addr += (2*shortname_length) if 1 <= self._memory.version <= 3: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[4:0] size = bf[7:5] + 1 proplist[pnum] = (addr, size) addr += size elif 4 <= self._memory.version <= 5: while self._memory[addr] != 0: bf = BitField(self._memory[addr]) addr += 1 pnum = bf[0:6] if bf[7]: bf2 = BitField(self._memory[addr]) addr += 1 size = bf2[0:6] if size == 0: size = 64 else: if bf[6]: size = 2 else: size = 1 proplist[pnum] = (addr, size) addr += size else: raise ZObjectIllegalVersion return proplist def set_property(self, objectnum, propnum, value): """Set a property on an object.""" proplist = self.get_all_properties(objectnum) if propnum not in proplist: raise ZObjectIllegalPropertyNumber addr, size = proplist[propnum] if size == 1: self._memory[addr] = (value & 0xFF) elif size == 2: self._memory.write_word(addr, value) else: raise ZObjectIllegalPropertySet def describe_object(self, objectnum): """For debugging purposes, pretty-print everything known about object OBJECTNUM.""" print "Object number:", objectnum print " Short name:", self.get_shortname(objectnum) print " Parent:", self.get_parent(objectnum), print " Sibling:", self.get_sibling(objectnum), print " Child:", self.get_child(objectnum) print " Attributes:", self.get_all_attributes(objectnum) print " Properties:" proplist = self.get_all_properties(objectnum) for key in proplist.keys(): (addr, len) = proplist[key] print " [%2d] :" % key, for i in range(0, len): print "%02X" % self._memory[addr+i], print