diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-08-03 16:10:52 -0500 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-08-03 16:10:52 -0500 |
commit | 28490230cf68f8045fc63a8c7d3de19c7c1d3bcd (patch) | |
tree | c4d1a3acbc7d34b77a890f8e19a8d7253917be8c /pokemontools/romstr.py | |
parent | a14c36eadb75ea3d6fbc4cb3f382d7c9785d9fe9 (diff) |
make a basic python module
Diffstat (limited to 'pokemontools/romstr.py')
-rw-r--r-- | pokemontools/romstr.py | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/pokemontools/romstr.py b/pokemontools/romstr.py new file mode 100644 index 0000000..69a4f2a --- /dev/null +++ b/pokemontools/romstr.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- + +import sys +import os +import time +import datetime +from ctypes import c_int8 +from copy import copy +import json + +# New versions of json don't have read anymore. +if not hasattr(json, "read"): + json.read = json.loads + +from labels import ( + get_label_from_line, + get_address_from_line_comment, +) + +relative_jumps = [0x38, 0x30, 0x20, 0x28, 0x18, 0xc3, 0xda, 0xc2, 0x32] +relative_unconditional_jumps = [0xc3, 0x18] +call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd] +end_08_scripts_with = [ + 0xe9, # jp hl + 0xc9, # ret +] # possibly also: + # 0xc3, # jp + # 0xc18, # jr + # 0xda, 0xe9, 0xd2, 0xc2, 0xca, 0x38, 0x30, 0x20, 0x28, 0x18, 0xd8, + # 0xd0, 0xc0, 0xc8, 0xc9 + +spacing = "\t" + +class RomStr(str): + """ + Simple wrapper to prevent a giant rom from being shown on screen. + """ + + def __init__(self, *args, **kwargs): + if "labels" in kwargs.keys() and kwargs["labels"] == True: + self.load_labels() + str.__init__(self) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "RomStr(too long)" + + @classmethod + def load(cls, filename=None, crystal=True, red=False): + """ + Load a ROM into a RomStr. + """ + if crystal and not red and not filename: + file_handler = open("../baserom.gbc", "r") + elif red and not crystal and not filename: + file_handler = open("../pokered-baserom.gbc", "r") + elif filename not in ["", None]: + file_handler = open(filename, "rb") + else: + raise Exception("not sure which rom to load?") + bytes = file_handler.read() + file_handler.close() + return RomStr(bytes) + + def load_labels(self, filename="labels.json"): + """ + Loads labels from labels.json. + + (Or parses the source code file and + generates new labels.) + """ + filename = os.path.join(os.path.dirname(__file__), filename) + + # blank out the hash + self.labels = {} + + # check if the labels file exists + file_existence = os.path.exists(filename) + + generate_labels = False + + # determine if the labels file needs to be regenerated + if file_existence: + modified = os.path.getmtime(filename) + modified = datetime.datetime.fromtimestamp(modified) + current = datetime.datetime.fromtimestamp(time.time()) + + is_old = (current - modified) > datetime.timedelta(days=3) + + if is_old: + generate_labels = True + else: + generate_labels = True + + # scan the asm source code for labels + if generate_labels: + asm = open(os.path.join(os.path.dirname(__file__), "../main.asm"), "r").read().split("\n") + + for line in asm: + label = get_label_from_line(line) + + if label: + address = get_address_from_line_comment(line) + + self.labels[address] = label + + content = json.dumps(self.labels) + file_handler = open(filename, "w") + file_handler.write(content) + file_handler.close() + + # load the labels from the file + self.labels = json.read(open(filename, "r").read()) + + def get_address_for(self, label): + """ + Return the address of a label. + + This is slow and could be improved dramatically. + """ + label = str(label) + for address in self.labels.keys(): + if self.labels[address] == label: + return address + return None + + def length(self): + """ + len(self) + """ + return len(self) + + def len(self): + """ + len(self) + """ + return self.length() + + def interval(self, offset, length, strings=True, debug=True): + """ + Return hex values for the rom starting at offset until offset+length. + """ + returnable = [] + for byte in self[offset:offset+length]: + if strings: + returnable.append(hex(ord(byte))) + else: + returnable.append(ord(byte)) + return returnable + + def until(self, offset, byte, strings=True, debug=False): + """ + Return hex values from rom starting at offset until the given byte. + """ + return self.interval(offset, self.find(chr(byte), offset) - offset, strings=strings) + + def to_asm(self, address, end_address=None, size=None, max_size=0x4000, debug=None): + """ + Disassemble ASM at some address. + + This will stop disassembling when either the end_address or size is + met. Also, there's a maximum size that will be parsed, so that large + patches of data aren't parsed as code. + """ + if type(address) in [str, unicode] and "0x" in address: + address = int(address, 16) + + start_address = address + + if start_address == None: + raise Exception, "address must be given" + + if debug == None: + if not hasattr(self, "debug"): + debug = False + else: + debug = self.debug + + # this is probably a terrible idea.. why am i doing this? + if size != None and max_size < size: + raise Exception, "max_size must be greater than or equal to size" + elif end_address != None and (end_address - start_address) > max_size: + raise Exception, "end_address is out of bounds" + elif end_address != None and size != None: + if (end_address - start_address) >= size: + size = end_address - start_address + else: + end_address = start_address + size + elif end_address == None and size != None: + end_address = start_address + size + elif end_address != None and size == None: + size = end_address - start_address + + raise NotImplementedError("DisAsm was removed and never worked; hook up another disassembler please.") + #return DisAsm(start_address=start_address, end_address=end_address, size=size, max_size=max_size, debug=debug, rom=self) + +class AsmList(list): + """ + Simple wrapper to prevent all asm lines from being shown on screen. + """ + + def length(self): + """ + len(self) + """ + return len(self) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "AsmList(too long)" + +if __name__ == "__main__": + cryrom = RomStr(open("../pokecrystal.gbc", "r").read()); + asm = cryrom.to_asm(sys.argv[1]) + print asm |