shithub: pokecrystal

Download patch

ref: 69f541f541e714a7faf8c114df3b54e2e7f51dcc
parent: 550ca496d606c8c85745e31e7246da411f7989a8
author: Bryan Bishop <[email protected]>
date: Tue Mar 13 20:27:44 EDT 2012

keep track of which scripts have been parsed

--- a/extras/crystal.py
+++ b/extras/crystal.py
@@ -6,6 +6,10 @@
 import sys
 from copy import copy
 
+#for IntervalMap
+from bisect import bisect_left, bisect_right
+from itertools import izip
+
 #table of pointers to map groups
 #each map group contains some number of map headers
 map_group_pointer_table = 0x94000
@@ -296,6 +300,109 @@
     if key not in chars.keys():
         chars[key] = value
 
+class IntervalMap(object):
+    """
+    This class maps a set of intervals to a set of values.
+
+    >>> i = IntervalMap()
+    >>> i[0:5] = "hello world"
+    >>> i[6:10] = "hello cruel world"
+    >>> print i[4]
+    "hello world"
+    """
+    def __init__(self):
+        """initializes an empty IntervalMap"""
+        self._bounds = []
+        self._items = []
+        self._upperitem = None
+    def __setitem__(self, _slice, _value):
+        """sets an interval mapping"""
+        assert isinstance(_slice, slice), 'The key must be a slice object'
+    
+        if _slice.start is None:
+            start_point = -1
+        else:
+            start_point = bisect_left(self._bounds, _slice.start)
+        
+        if _slice.stop is None:
+            end_point = -1
+        else:
+            end_point = bisect_left(self._bounds, _slice.stop)
+        
+        if start_point>=0:
+            if start_point < len(self._bounds) and self._bounds[start_point]<_slice.start:
+                start_point += 1 
+
+            if end_point>=0:        
+                self._bounds[start_point:end_point] = [_slice.start, _slice.stop]
+                if start_point < len(self._items):
+                    self._items[start_point:end_point] = [self._items[start_point], _value]
+                else:
+                    self._items[start_point:end_point] = [self._upperitem, _value]
+            else:
+                self._bounds[start_point:] = [_slice.start]
+                if start_point < len(self._items):
+                    self._items[start_point:] = [self._items[start_point], _value]
+                else:
+                    self._items[start_point:] = [self._upperitem]
+                self._upperitem = _value
+        else:
+            if end_point>=0:
+                self._bounds[:end_point] = [_slice.stop]
+                self._items[:end_point] = [_value]
+            else:
+                self._bounds[:] = []
+                self._items[:] = []
+                self._upperitem = _value
+    def __getitem__(self,_point):
+        """gets a value from the mapping"""
+        assert not isinstance(_point, slice), 'The key cannot be a slice object'  
+            
+        index = bisect_right(self._bounds, _point)
+        if index < len(self._bounds):
+            return self._items[index]
+        else:
+            return self._upperitem
+    def items(self):
+        """returns an iterator with each item being
+        ((low_bound, high_bound), value)
+        these items are returned in order"""
+        previous_bound = None
+        for (b, v) in izip(self._bounds, self._items):
+            if v is not None:
+                yield (previous_bound, b), v
+            previous_bound = b
+        if self._upperitem is not None:
+            yield (previous_bound, None), self._upperitem
+    def values(self):
+        """returns an iterator with each item being a stored value
+        the items are returned in order"""
+        for v in self._items:
+            if v is not None:
+                yield v
+        if self._upperitem is not None:
+            yield self._upperitem
+    def __repr__(self):
+        s = []
+        for b,v in self.items():
+            if v is not None:
+                s.append('[%r, %r] => %r'%(
+                    b[0],
+                    b[1],
+                    v
+                ))
+        return '{'+', '.join(s)+'}'
+
+#keys are intervals "500..555" of byte addresses for each script
+#last byte is not inclusive
+#this is how to make sure scripts are not recalculated
+script_parse_table = IntervalMap()
+
+def is_script_already_parsed_at(address):
+    """looks up whether or not a script is parsed at a certain address"""
+    if script_parse_table[address] == None: return False
+    return True
+
 def map_name_cleaner(input):
     """generate a valid asm label for a given map name"""
     return input.replace(":", "").\
@@ -412,7 +519,7 @@
         long_info = "\n".join(new_lines)
     return long_info
 
-def command_debug_information(command_byte=None, map_group=None, map_id=None, address=None, info=None, long_info=None, pksv_name=None):
+def command_debug_information(command_byte=None, map_group=None, map_id=None, address=0, info=None, long_info=None, pksv_name=None):
     info1 = "parsing command byte " + hex(command_byte) + " for map " + \
           str(map_group) + "." + str(map_id) + " at " + hex(address)
     info1 += "    pksv: " + str(pksv_name)
@@ -1095,18 +1202,30 @@
         for address in addresses:
             print "    " + hex(address)
 
-def parse_script_engine_script_at(address, map_group=None, map_id=None):
+def parse_script_engine_script_at(address, map_group=None, map_id=None, force=False):
     """parses a script-engine script"""
     global rom
     if rom == None:
         load_rom()
-
+    #check if work is being repeated
+    if is_script_already_parsed_at(address) and not force:
+        return script_parse_table[address]
+    original_start_address = address
+    #this next line stops the same script from being re-parsed multiple times
+    #for instance.. maybe there's a script jump, then a jump back
+    #the original script should only be parsed once
+    script_parse_table[original_start_address:original_start_address+1] = "incomplete"
+    #set up some variables
     commands = {}
     offset = address
     end = False
+    #main loop.. parse each command byte
     while not end:
+        #reset variables so we don't contaminate this command
         info, long_info, size = None, None, 0
+        #read the current command byte
         command_byte = ord(rom[offset])
+        #setup the current command representation
         command = {"type": command_byte, "start_address": offset}
 
         #size is the total size including the command byte
@@ -1113,6 +1232,7 @@
         #last_byte_address is offset+size-1
         start_address = offset
 
+        #start checking against possible command bytes
         if   command_byte == 0x00: #Pointer code [2b+ret]
             pksv_name = "2call"
             info = "pointer code"
@@ -2781,6 +2901,8 @@
         offset += 1
         #add the command into the command list please
         commands[len(commands.keys())] = command
+
+    script_parse_table[original_start_address : offset-1] = commands    
     return commands
 
 def parse_warp_bytes(some_bytes):