ref: 53b0dd13a84899fadd0e4511ab59e1a255399ba4
parent: d174c8b7c6ba0fd9da2b8145e3f8ec5909917e29
author: Bryan Bishop <[email protected]>
date: Tue May 29 10:50:56 EDT 2012
comparator.py - find shared functions between pokered/pokecrystal
--- /dev/null
+++ b/extras/comparator.py
@@ -1,0 +1,228 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# author: Bryan Bishop <[email protected]>
+# date: 2012-05-29
+# purpose: find shared functions between red/crystal
+
+from crystal import get_label_from_line, \
+ get_address_from_line_comment, \
+ AsmSection
+
+from romstr import RomStr, AsmList
+
+def load_rom(path):
+ """ Loads a ROM file into an abbreviated RomStr object.
+ """
+
+ fh = open(path, "r")
+ x = RomStr(fh.read())
+ fh.close()
+
+ return x
+
+def load_asm(path):
+ """ Loads source ASM into an abbreviated AsmList object.
+ """
+
+ fh = open(path, "r")
+ x = AsmList(fh.read().split("\n"))
+ fh.close()
+
+ return x
+
+def findall_iter(sub, string):
+ # url: http://stackoverflow.com/a/3874760/687783
+
+ def next_index(length):
+ index = 0 - length
+ while True:
+ index = string.find(sub, index + length)
+ yield index
+
+ return iter(next_index(len(sub)).next, -1)
+
+class Address(int):
+ """ A simple int wrapper to take 0xFFFF and $FFFF addresses.
+ """
+
+ def __new__(cls, x=None, *args, **kwargs):
+ if type(x) == str:
+ if "$" in x:
+ x = x.replace("$", "0x")
+
+ if "0x" in str:
+ instance = int.__new__(cls, int(x, base=16), *args, **kwargs)
+ else:
+ msg = "Address.__new__ doesn't know how to parse this string"
+ raise Exception, msg
+ else:
+ instance = int.__new__(cls, x, *args, **kwargs)
+
+ return instance
+
+found_blobs = []
+
+class BinaryBlob(object):
+ """ Stores a label, line number, and addresses of a function from Pokémon
+ Red. These details can be used to determine whether or not the function was
+ copied into Pokémon Crystal.
+ """
+
+ start_address = None
+ end_address = None
+ label = None
+ line_number = None
+ bytes = None
+ bank = None
+ debug = False
+ locations = None
+
+ def __init__(self, start_address=None, end_address=None, label=None, \
+ debug=None, line_number=None):
+ if not isinstance(start_address, Address):
+ start_address = Address(start_address)
+ if not isinstance(end_address, Address):
+ end_address = Address(end_address)
+
+ assert label != None, "label can't be none"
+ assert isinstance(label, str), "label must be a string"
+ assert line_number != None, "line_number must be provided"
+
+ self.start_address = start_address
+ self.end_address = end_address
+ self.label = label
+ self.line_number = line_number
+ self.bytes = []
+ self.locations = []
+ self.bank = start_address / 0x4000
+
+ if debug != None:
+ self.debug = debug
+
+ self.parse_from_red()
+ self.find_in_crystal()
+
+ def __repr__(self):
+ """ A beautiful poem.
+ """
+
+ r = "BinaryBlob("
+ r += "label=\""+self.label+"\", "
+ r += "start_address="+hex(self.start_address)+", "
+ r += "size="+str(self.end_address - self.start_address)+", "
+ r += "located="+str(len(self.locations) > 0)
+ r += ")"
+
+ return r
+
+ def __str__(self):
+ return self.__repr__()
+
+ def parse_from_red(self):
+ """ Reads bytes from Pokémon Red and stores them.
+ """
+
+ self.bytes = redrom[self.start_address : self.end_address + 1]
+
+ def pretty_bytes(self):
+ """ Returns a better looking range of bytes.
+ """
+
+ bytes = redrom.interval(self.start_address, \
+ self.end_address - self.start_address, \
+ strings=False, debug=True)
+
+ return bytes
+
+ def find_in_crystal(self):
+ """ Checks whether or not the bytes appear in Pokémon Crystal.
+ """
+
+ finditer = findall_iter(self.bytes, cryrom)
+ self.locations = [match for match in finditer]
+
+ if len(self.locations) > 0:
+ found_blobs.append(self)
+
+ if self.debug:
+ print self.label + ": found " + str(len(self.locations)) + " matches."
+
+pokecrystal_rom_path = "../baserom.gbc"
+pokecrystal_src_path = "../main.asm"
+pokered_rom_path = "../pokered-baserom.gbc"
+pokered_src_path = "../pokered-main.asm"
+
+cryrom = load_rom(pokecrystal_rom_path)
+crysrc = load_asm(pokecrystal_src_path)
+redrom = load_rom(pokered_rom_path)
+redsrc = load_asm(pokered_src_path)
+
+def scan_red_asm(bank_stop=3, debug=True):
+ """ Scans the ASM from Pokémon Red. Finds labels and objects. Does things.
+
+ Uses get_label_from_line and get_address_from_line_comment.
+ """
+
+ # whether or not to show the lines from redsrc
+ show_lines = False
+
+ line_number = 0
+ current_bank = 0
+
+ current_label = None
+ latest_label = None
+ current_start_address = None
+ latest_start_address = None
+ latest_line = ""
+
+ for line in redsrc:
+ if debug and show_lines:
+ print "processing a line from red: " + line
+
+ if line[0:7] == "SECTION":
+ thing = AsmSection(line)
+ current_bank = thing.bank_id
+
+ if debug:
+ print "scan_red_asm: switching to bank " + str(current_bank)
+
+ elif line[0:6] != "INCBIN":
+ if ":" in line:
+ current_label = get_label_from_line(line)
+ current_start_address = get_address_from_line_comment(line, \
+ bank=current_bank)
+
+ if current_label != None and current_start_address != None and \
+ current_start_address != 0 and current_start_address != latest_start_address:
+ if latest_label != None:
+ if latest_label not in ["Char52", "PokeCenterSignText", "DefaultNamesPlayer", "Unnamed_6a12"]:
+ blob = BinaryBlob(label=latest_label, \
+ start_address=latest_start_address, \
+ end_address=current_start_address, \
+ line_number=line_number)
+
+ if debug:
+ print "Created a new blob: " + str(blob) + " from line: " + str(latest_line)
+
+ latest_label = current_label
+ latest_start_address = current_start_address
+ latest_line = line
+
+ line_number += 1
+
+ if current_bank == bank_stop:
+ if debug:
+ print "scan_red_asm: stopping because current_bank >= " + \
+ str(bank_stop) + " (bank_stop)"
+
+ break
+
+scan_red_asm()
+
+print "================================"
+
+for blob in found_blobs:
+ print blob
+
+print "Found " + str(len(found_blobs)) + " possibly copied functions."
+