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 /crystal.py | |
parent | a14c36eadb75ea3d6fbc4cb3f382d7c9785d9fe9 (diff) |
make a basic python module
Diffstat (limited to 'crystal.py')
-rw-r--r-- | crystal.py | 7629 |
1 files changed, 0 insertions, 7629 deletions
diff --git a/crystal.py b/crystal.py deleted file mode 100644 index 9ee6ec0..0000000 --- a/crystal.py +++ /dev/null @@ -1,7629 +0,0 @@ -# -*- coding: utf-8 -*- -# utilities to help disassemble pokémon crystal -import os -import sys -import inspect -import hashlib -import json -from copy import copy, deepcopy -import subprocess -from new import classobj -import random - -# for capwords -import string - -# for python2.6 -if not hasattr(json, "dumps"): - json.dumps = json.write - -# New versions of json don't have read anymore. -if not hasattr(json, "read"): - json.read = json.loads - -from labels import ( - remove_quoted_text, - line_has_comment_address, - line_has_label, - get_label_from_line, - get_address_from_line_comment -) - -spacing = "\t" - -lousy_dragon_shrine_hack = [0x18d079, 0x18d0a9, 0x18d061, 0x18d091] - -# table of pointers to map groups -# each map group contains some number of map headers -map_group_pointer_table = 0x94000 -map_group_count = 26 -map_group_offsets = [] -map_header_byte_size = 9 -second_map_header_byte_size = 12 - -# event segment sizes -warp_byte_size = 5 -trigger_byte_size = 8 -signpost_byte_size = 5 -people_event_byte_size = 13 - -# a message to show with NotImplementedErrors -bryan_message = "bryan hasn't got to this yet" - -max_texts = 3 -text_count = 0 -texts = [] - -# these appear outside of quotes (see pokered/extras/pretty_map_headers.py) -# this doesn't do anything but is still used in TextScript -constant_abbreviation_bytes = {} - -# Import the characters from its module. -from chars import chars, jap_chars - -from trainers import ( - trainer_group_pointer_table_address, # 0x39999 - trainer_group_pointer_table_address_gs, # 0x3993E - trainer_group_names, -) - -from move_constants import moves - -# for fixing trainer_group_names -import re - -from interval_map import IntervalMap - -from pksv import ( - pksv_gs, - pksv_crystal, - pksv_crystal_unknowns, - pksv_crystal_more_enders -) - -# ---- script_parse_table explanation ---- -# This is an IntervalMap that keeps track of previously parsed scripts, texts -# and other objects. Anything that has a location in the ROM should be mapped -# to an interval (a range of addresses) in this structure. Each object that is -# assigned to an interval should implement attributes or methods like: -# ATTRIBUTE/METHOD EXPLANATION -# label what the heck to call the object -# address where it begins -# to_asm() spit out asm (not including label) -# keys are intervals "500..555" of byte addresses for each script -# last byte is not inclusive(?) really? according to who?? -# 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 script_parse_table_pretty_printer(): - """helpful debugging output""" - for each in script_parse_table.items(): - print each - -def map_name_cleaner(input): - """generate a valid asm label for a given map name""" - return input.replace(":", "").\ - replace("(", "").\ - replace(")", "").\ - replace("'", "").\ - replace("/", "").\ - replace(",", "").\ - replace(".", "").\ - replace("Pokémon Center", "PokeCenter").\ - replace("é", "e").\ - replace("-", "").\ - replace("Hooh", "HoOh").\ - replace("hooh", "HoOh").\ - replace(" ", "") - -from romstr import ( - RomStr, - AsmList, -) - -rom = RomStr(None) - -def direct_load_rom(filename="../baserom.gbc"): - """loads bytes into memory""" - global rom - file_handler = open(filename, "rb") - rom = RomStr(file_handler.read()) - file_handler.close() - return rom - -def load_rom(filename="../baserom.gbc"): - """checks that the loaded rom matches the path - and then loads the rom if necessary.""" - global rom - if rom != RomStr(None) and rom != None: - return rom - if not isinstance(rom, RomStr): - return direct_load_rom(filename=filename) - elif os.lstat(filename).st_size != len(rom): - return direct_load_rom(filename) - -def direct_load_asm(filename="../main.asm"): - """returns asm source code (AsmList) from a file""" - asm = open(filename, "r").read().split("\n") - asm = AsmList(asm) - return asm - -def load_asm(filename="../main.asm"): - """returns asm source code (AsmList) from a file (uses a global)""" - global asm - asm = direct_load_asm(filename=filename) - return asm - -def grouper(some_list, count=2): - """splits a list into sublists - given: [1, 2, 3, 4] - returns: [[1, 2], [3, 4]]""" - return [some_list[i:i+count] for i in range(0, len(some_list), count)] - -def is_valid_address(address): - """is_valid_rom_address""" - if address == None: - return False - if type(address) == str: - address = int(address, 16) - if 0 <= address <= 2097152: - return True - else: - return False - -def rom_interval(offset, length, strings=True, debug=True): - """returns hex values for the rom starting at offset until offset+length""" - global rom - return rom.interval(offset, length, strings=strings, debug=debug) - -def rom_until(offset, byte, strings=True, debug=True): - """returns hex values from rom starting at offset until the given byte""" - global rom - return rom.until(offset, byte, strings=strings, debug=debug) - -def how_many_until(byte, starting): - index = rom.find(byte, starting) - return index - starting - -def load_map_group_offsets(): - """reads the map group table for the list of pointers""" - global map_group_pointer_table, map_group_count, map_group_offsets - global rom - map_group_offsets = [] # otherwise this method can only be used once - data = rom_interval(map_group_pointer_table, map_group_count*2, strings=False) - data = grouper(data) - for pointer_parts in data: - pointer = pointer_parts[0] + (pointer_parts[1] << 8) - offset = pointer - 0x4000 + map_group_pointer_table - map_group_offsets.append(offset) - return map_group_offsets - -from pointers import ( - calculate_bank, - calculate_pointer, -) - -def calculate_pointer_from_bytes_at(address, bank=False): - """calculates a pointer from 2 bytes at a location - or 3-byte pointer [bank][2-byte pointer] if bank=True""" - if bank == True: - bank = ord(rom[address]) - address += 1 - elif bank == False or bank == None: - bank = calculate_bank(address) - elif bank == "reverse" or bank == "reversed": - bank = ord(rom[address+2]) - elif type(bank) == int: - pass - else: - raise Exception("bad bank given to calculate_pointer_from_bytes_at") - byte1 = ord(rom[address]) - byte2 = ord(rom[address+1]) - temp = byte1 + (byte2 << 8) - if temp == 0: - return None - return calculate_pointer(temp, bank) - -def clean_up_long_info(long_info): - """cleans up some data from parse_script_engine_script_at formatting issues""" - long_info = str(long_info) - # get rid of the first newline - if long_info[0] == "\n": - long_info = long_info[1:] - # get rid of the last newline and any leftover space - if long_info.count("\n") > 0: - if long_info[long_info.rindex("\n")+1:].isspace(): - long_info = long_info[:long_info.rindex("\n")] - # remove spaces+hash from the front of each line - new_lines = [] - for line in long_info.split("\n"): - line = line.strip() - if line[0] == "#": - line = line[1:] - new_lines.append(line) - long_info = "\n".join(new_lines) - return long_info - -from pokemon_constants import pokemon_constants - -def get_pokemon_constant_by_id(id): - if id == 0: - return None - else: - return pokemon_constants[id] - -from item_constants import ( - item_constants, - find_item_label_by_id, - generate_item_constants, -) - -def command_debug_information(command_byte=None, map_group=None, map_id=None, address=0, info=None, long_info=None, pksv_name=None): - "used to help debug in parse_script_engine_script_at" - info1 = "parsing command byte " + hex(command_byte) + " for map " + \ - str(map_group) + "." + str(map_id) + " at " + hex(address) - info1 += " pksv: " + str(pksv_name) - #info1 += " info: " + str(info) - #info1 += " long_info: " + long_info - return info1 - -all_texts = [] -class TextScript: - """ - A text is a sequence of bytes (and sometimes commands). It's not the same - thing as a Script. The bytes are translated into characters based on the - lookup table (see chars.py). The in-text commands are for including values - from RAM, playing sound, etc. - - see: http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText - """ - base_label = "UnknownText_" - def __init__(self, address, map_group=None, map_id=None, debug=False, label=None, force=False, show=None): - self.address = address - # $91, $84, $82, $54, $8c - # 0x19768c is a a weird problem? - if address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]: - return None - self.map_group, self.map_id, self.debug = map_group, map_id, debug - self.dependencies = None - self.commands = None - self.force = force - - if is_script_already_parsed_at(address) and not force: - raise Exception("TextScript already parsed at "+hex(address)) - - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - - self.parse() - - def is_valid(self): - return not (self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]) - - # hmm this looks exactly like Script.get_dependencies (which makes sense..) - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]: - return [] - - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - - dependencies = [] - - for command in self.commands: - deps = command.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - dependencies.extend(deps) - - self.dependencies = dependencies - return self.dependencies - - # this is almost an exact copy of Script.parse - # with the exception of using text_command_classes instead of command_classes - def parse(self): - if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]: - return None - - global text_command_classes, script_parse_table - current_address = copy(self.address) - start_address = copy(current_address) - - # don't clutter up my screen - if self.debug: - print "NewTextScript.parse address="+hex(self.address)+" map_group="+str(self.map_group)+" map_id="+str(self.map_id) - - # load up the rom if it hasn't been loaded already - load_rom() - - # in the event that the script parsing fails.. it would be nice to leave evidence - script_parse_table[start_address:start_address+1] = "incomplete NewTextScript.parse" - - # start with a blank script - commands = [] - - # use this to control the while loop - end = False - - # for each command found... - while not end: - # get the current scripting byte - cur_byte = ord(rom[current_address]) - - # reset the command class (last command was probably different) - scripting_command_class = None - - # match the command id byte to a scripting command class like MainText - for class_ in text_command_classes: - if class_[1].id == cur_byte: - scripting_command_class = class_[1] - - if self.address == 0x9c00e and self.debug: - if current_address > 0x9c087: - print "self.commands is: " + str(commands) - print "command 0 address is: " + hex(commands[0].address) + " last_address="+hex(commands[0].last_address) - print "command 1 address is: " + hex(commands[1].address) + " last_address="+hex(commands[1].last_address) - raise Exception("going beyond the bounds for this text script") - - # no matching command found - if scripting_command_class == None: - raise Exception("unable to parse text command $%.2x in the text script at %s at %s" % (cur_byte, hex(start_address), hex(current_address))) - - # create an instance of the command class and let it parse its parameter bytes - cls = scripting_command_class(address=current_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=self.force) - - if self.debug: - print cls.to_asm() - - # store it in this script object - commands.append(cls) - - # certain commands will end the scripting engine - end = cls.end - - # skip past the command's parameter bytes to go to the next command - #current_address += cls.size - current_address = cls.last_address - - # last byte belonging to script is last byte of last command, - # or the last byte of the last command's last parameter - # (actually i think this might be the next byte after??) - self.last_address = current_address - - if self.debug: - print "cls.address is: " + hex(cls.address) - print "cls.size is: " + hex(cls.size) - print "cls.last_address is: " + hex(cls.last_address) - print "self.last_address is: " + hex(self.last_address) - - assert self.last_address == (cls.address + cls.size), "the last address should equal the last command's (address + size)" - assert self.last_address == cls.last_address, "the last address of the TextScript should be the last_address of its last command" - - # just some debugging.. - if self.debug: - last_address = self.last_address - print "TextScript last_address == " + hex(last_address) - #assert last_address != 0x5db06, "TextScript.parse somehow has a text with a last_address of 0x5db06 instead of 0x5db07" - - # store the script in the global table/map thing - script_parse_table[start_address:current_address] = self - all_texts.append(self) - - if self.debug: - asm_output = "\n".join([command.to_asm() for command in commands]) - print "--------------\n"+asm_output - - # store the script - self.commands = commands - - return commands - - def to_asm(self): - if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]: - return None - - asm_output = "\n".join([command.to_asm() for command in self.commands]) - return asm_output - -class OldTextScript: - "a text is a sequence of commands different from a script-engine script" - base_label = "UnknownText_" - def __init__(self, address, map_group=None, map_id=None, debug=True, show=True, force=False, label=None): - self.address = address - self.map_group, self.map_id, self.debug, self.show, self.force = map_group, map_id, debug, show, force - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - self.dependencies = [] - self.parse_text_at(address) - - @staticmethod - def find_addresses(): - """returns a list of text pointers - useful for testing parse_text_engine_script_at - - Note that this list is not exhaustive. There are some texts that - are only pointed to from some script that a current script just - points to. So find_all_text_pointers_in_script_engine_script will - have to recursively follow through each script to find those. - .. it does this now :) - """ - addresses = set() - # for each map group - for map_group in map_names: - # for each map id - for map_id in map_names[map_group]: - # skip the offset key - if map_id == "offset": continue - # dump this into smap - smap = map_names[map_group][map_id] - # signposts - signposts = smap["signposts"] - # for each signpost - for signpost in signposts: - if signpost["func"] in [0, 1, 2, 3, 4]: - # dump this into script - script = signpost["script"] - elif signpost["func"] in [05, 06]: - script = signpost["script"] - else: continue - # skip signposts with no bytes - if len(script) == 0: continue - # find all text pointers in script - texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"]) - # dump these addresses in - addresses.update(texts) - # xy triggers - xy_triggers = smap["xy_triggers"] - # for each xy trigger - for xy_trigger in xy_triggers: - # dump this into script - script = xy_trigger["script"] - # find all text pointers in script - texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"]) - # dump these addresses in - addresses.update(texts) - # trigger scripts - triggers = smap["trigger_scripts"] - # for each trigger - for (i, trigger) in triggers.items(): - # dump this into script - script = trigger["script"] - # find all text pointers in script - texts = find_all_text_pointers_in_script_engine_script(script, calculate_bank(trigger["address"])) - # dump these addresses in - addresses.update(texts) - # callback scripts - callbacks = smap["callback_scripts"] - # for each callback - for (k, callback) in callbacks.items(): - # dump this into script - script = callback["script"] - # find all text pointers in script - texts = find_all_text_pointers_in_script_engine_script(script, calculate_bank(callback["address"])) - # dump these addresses in - addresses.update(texts) - # people-events - events = smap["people_events"] - # for each event - for event in events: - if event["event_type"] == "script": - # dump this into script - script = event["script"] - # find all text pointers in script - texts = find_all_text_pointers_in_script_engine_script(script, smap["event_bank"]) - # dump these addresses in - addresses.update(texts) - if event["event_type"] == "trainer": - trainer_data = event["trainer_data"] - addresses.update([trainer_data["text_when_seen_ptr"]]) - addresses.update([trainer_data["text_when_trainer_beaten_ptr"]]) - trainer_bank = calculate_bank(event["trainer_data_address"]) - script1 = trainer_data["script_talk_again"] - texts1 = find_all_text_pointers_in_script_engine_script(script1, trainer_bank) - addresses.update(texts1) - script2 = trainer_data["script_when_lost"] - texts2 = find_all_text_pointers_in_script_engine_script(script2, trainer_bank) - addresses.update(texts2) - return addresses - - def parse_text_at(self, address): - """parses a text-engine script ("in-text scripts") - http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText - - This is presently very broken. - - see parse_text_at2, parse_text_at, and process_00_subcommands - """ - global rom, text_count, max_texts, texts, script_parse_table - if rom == None: - direct_load_rom() - if address == None: - return "not a script" - map_group, map_id, debug, show, force = self.map_group, self.map_id, self.debug, self.show, self.force - commands = {} - - if is_script_already_parsed_at(address) and not force: - print "text is already parsed at this location: " + hex(address) - raise Exception("text is already parsed, what's going on ?") - return script_parse_table[address] - - total_text_commands = 0 - command_counter = 0 - original_address = address - offset = address - end = False - script_parse_table[original_address:original_address+1] = "incomplete text" - while not end: - address = offset - command = {} - command_byte = ord(rom[address]) - if debug: - print "TextScript.parse_script_at has encountered a command byte " + hex(command_byte) + " at " + hex(address) - end_address = address + 1 - if command_byte == 0: - # read until $57, $50 or $58 - jump57 = how_many_until(chr(0x57), offset) - jump50 = how_many_until(chr(0x50), offset) - jump58 = how_many_until(chr(0x58), offset) - - # whichever command comes first - jump = min([jump57, jump50, jump58]) - - end_address = offset + jump # we want the address before $57 - - lines = process_00_subcommands(offset+1, end_address, debug=debug) - - if show and debug: - text = parse_text_at2(offset+1, end_address-offset+1, debug=debug) - print text - - command = {"type": command_byte, - "start_address": offset, - "end_address": end_address, - "size": jump, - "lines": lines, - } - - offset += jump - elif command_byte == 0x17: - # TX_FAR [pointer][bank] - pointer_byte1 = ord(rom[offset+1]) - pointer_byte2 = ord(rom[offset+2]) - pointer_bank = ord(rom[offset+3]) - - pointer = (pointer_byte1 + (pointer_byte2 << 8)) - pointer = extract_maps.calculate_pointer(pointer, pointer_bank) - - text = TextScript(pointer, map_group=self.map_group, map_id=self.amp_id, debug=self.debug, \ - show=self.debug, force=self.debug, label="Target"+self.label.name) - if text.is_valid(): - self.dependencies.append(text) - - command = {"type": command_byte, - "start_address": offset, - "end_address": offset + 3, # last byte belonging to this command - "pointer": pointer, # parameter - "text": text, - } - - offset += 3 + 1 - elif command_byte == 0x50 or command_byte == 0x57 or command_byte == 0x58: # end text - command = {"type": command_byte, - "start_address": offset, - "end_address": offset, - } - - # this byte simply indicates to end the script - end = True - - # this byte simply indicates to end the script - if command_byte == 0x50 and ord(rom[offset+1]) == 0x50: # $50$50 means end completely - end = True - commands[command_counter+1] = command - - # also save the next byte, before we quit - commands[command_counter+1]["start_address"] += 1 - commands[command_counter+1]["end_address"] += 1 - add_command_byte_to_totals(command_byte) - elif command_byte == 0x50: # only end if we started with $0 - if len(commands.keys()) > 0: - if commands[0]["type"] == 0x0: end = True - elif command_byte == 0x57 or command_byte == 0x58: # end completely - end = True - offset += 1 # go past this 0x50 - elif command_byte == 0x1: - # 01 = text from RAM. [01][2-byte pointer] - size = 3 # total size, including the command byte - pointer_byte1 = ord(rom[offset+1]) - pointer_byte2 = ord(rom[offset+2]) - - command = {"type": command_byte, - "start_address": offset+1, - "end_address": offset+2, # last byte belonging to this command - "pointer": [pointer_byte1, pointer_byte2], # RAM pointer - } - - # view near these bytes - # subsection = rom[offset:offset+size+1] #peak ahead - #for x in subsection: - # print hex(ord(x)) - #print "--" - - offset += 2 + 1 # go to the next byte - - # use this to look at the surrounding bytes - if debug: - print "next command is: " + hex(ord(rom[offset])) + " ... we are at command number: " + str(command_counter) + " near " + hex(offset) + " on map_id=" + str(map_id) - elif command_byte == 0x7: - # 07 = shift texts 1 row above (2nd line becomes 1st line); address for next text = 2nd line. [07] - size = 1 - command = {"type": command_byte, - "start_address": offset, - "end_address": offset, - } - offset += 1 - elif command_byte == 0x3: - # 03 = set new address in RAM for text. [03][2-byte RAM address] - size = 3 - command = {"type": command_byte, "start_address": offset, "end_address": offset+2} - offset += size - elif command_byte == 0x4: # draw box - # 04 = draw box. [04][2-Byte pointer][height Y][width X] - size = 5 # including the command - command = { - "type": command_byte, - "start_address": offset, - "end_address": offset + size, - "pointer_bytes": [ord(rom[offset+1]), ord(rom[offset+2])], - "y": ord(rom[offset+3]), - "x": ord(rom[offset+4]), - } - offset += size + 1 - elif command_byte == 0x5: - # 05 = write text starting at 2nd line of text-box. [05][text][ending command] - # read until $57, $50 or $58 - jump57 = how_many_until(chr(0x57), offset) - jump50 = how_many_until(chr(0x50), offset) - jump58 = how_many_until(chr(0x58), offset) - - # whichever command comes first - jump = min([jump57, jump50, jump58]) - - end_address = offset + jump # we want the address before $57 - - lines = process_00_subcommands(offset+1, end_address, debug=debug) - - if show and debug: - text = parse_text_at2(offset+1, end_address-offset+1, debug=debug) - print text - - command = {"type": command_byte, - "start_address": offset, - "end_address": end_address, - "size": jump, - "lines": lines, - } - offset = end_address + 1 - elif command_byte == 0x6: - # 06 = wait for keypress A or B (put blinking arrow in textbox). [06] - command = {"type": command_byte, "start_address": offset, "end_address": offset} - offset += 1 - elif command_byte == 0x7: - # 07 = shift texts 1 row above (2nd line becomes 1st line); address for next text = 2nd line. [07] - command = {"type": command_byte, "start_address": offset, "end_address": offset} - offset += 1 - elif command_byte == 0x8: - # 08 = asm until whenever - command = {"type": command_byte, "start_address": offset, "end_address": offset} - offset += 1 - end = True - elif command_byte == 0x9: - # 09 = write hex-to-dec number from RAM to textbox [09][2-byte RAM address][byte bbbbcccc] - # bbbb = how many bytes to read (read number is big-endian) - # cccc = how many digits display (decimal) - #(note: max of decimal digits is 7,i.e. max number correctly displayable is 9999999) - ram_address_byte1 = ord(rom[offset+1]) - ram_address_byte2 = ord(rom[offset+2]) - read_byte = ord(rom[offset+3]) - - command = { - "type": command_byte, - "address": [ram_address_byte1, ram_address_byte2], - "read_byte": read_byte, # split this up when we make a macro for this - } - - offset += 4 - else: - #if len(commands) > 0: - # print "Unknown text command " + hex(command_byte) + " at " + hex(offset) + ", script began with " + hex(commands[0]["type"]) - if debug: - print "Unknown text command at " + hex(offset) + " - command: " + hex(ord(rom[offset])) + " on map_id=" + str(map_id) - - # end at the first unknown command - end = True - commands[command_counter] = command - command_counter += 1 - total_text_commands += len(commands) - - text_count += 1 - #if text_count >= max_texts: - # sys.exit() - - self.commands = commands - self.last_address = offset - script_parse_table[original_address:offset] = self - all_texts.append(self) - self.size = self.byte_count = self.last_address - original_address - return commands - - def get_dependencies(self, recompute=False, global_dependencies=set()): - #if recompute: - # raise NotImplementedError(bryan_message) - global_dependencies.update(self.dependencies) - return self.dependencies - - def to_asm(self, label=None): - address = self.address - start_address = address - if label == None: label = self.label.name - # using deepcopy because otherwise additional @s get appended each time - # like to the end of the text for TextScript(0x5cf3a) - commands = deepcopy(self.commands) - # apparently this isn't important anymore? - needs_to_begin_with_0 = True - # start with zero please - byte_count = 0 - # where we store all output - output = "" - had_text_end_byte = False - had_text_end_byte_57_58 = False - had_db_last = False - xspacing = "" - # reset this pretty fast.. - first_line = True - # for each command.. - for this_command in commands.keys(): - if not "lines" in commands[this_command].keys(): - command = commands[this_command] - if not "type" in command.keys(): - print "ERROR in command: " + str(command) - continue # dunno what to do here? - - if command["type"] == 0x1: # TX_RAM - p1 = command["pointer"][0] - p2 = command["pointer"][1] - - # remember to account for big endian -> little endian - output += "\n" + xspacing + "TX_RAM $%.2x%.2x" %(p2, p1) - byte_count += 3 - had_db_last = False - elif command["type"] == 0x17: # TX_FAR - #p1 = command["pointer"][0] - #p2 = command["pointer"][1] - output += "\n" + xspacing + "TX_FAR _" + label + " ; " + hex(command["pointer"]) - byte_count += 4 # $17, bank, address word - had_db_last = False - elif command["type"] == 0x9: # TX_RAM_HEX2DEC - # address, read_byte - output += "\n" + xspacing + "TX_NUM $%.2x%.2x, $%.2x" % (command["address"][1], command["address"][0], command["read_byte"]) - had_db_last = False - byte_count += 4 - elif command["type"] == 0x50 and not had_text_end_byte: - # had_text_end_byte helps us avoid repeating $50s - if had_db_last: - output += ", $50" - else: - output += "\n" + xspacing + "db $50" - byte_count += 1 - had_db_last = True - elif command["type"] in [0x57, 0x58] and not had_text_end_byte_57_58: - if had_db_last: - output += ", $%.2x" % (command["type"]) - else: - output += "\n" + xspacing + "db $%.2x" % (command["type"]) - byte_count += 1 - had_db_last = True - elif command["type"] in [0x57, 0x58] and had_text_end_byte_57_58: - pass # this is ok - elif command["type"] == 0x50 and had_text_end_byte: - pass # this is also ok - elif command["type"] == 0x0b: - if had_db_last: - output += ", $0b" - else: - output += "\n" + xspacing + "db $0B" - byte_count += 1 - had_db_last = True - elif command["type"] == 0x11: - if had_db_last: - output += ", $11" - else: - output += "\n" + xspacing + "db $11" - byte_count += 1 - had_db_last = True - elif command["type"] == 0x6: # wait for keypress - if had_db_last: - output += ", $6" - else: - output += "\n" + xspacing + "db $6" - byte_count += 1 - had_db_last = True - else: - print "ERROR in command: " + hex(command["type"]) - had_db_last = False - - # everything else is for $0s, really - continue - lines = commands[this_command]["lines"] - - # reset this in case we have non-$0s later - had_db_last = False - - # add the ending byte to the last line- always seems $57 - # this should already be in there, but it's not because of a bug in the text parser - lines[len(lines.keys())-1].append(commands[len(commands.keys())-1]["type"]) - - first = True # first byte - for line_id in lines: - line = lines[line_id] - output += xspacing + "db " - if first and needs_to_begin_with_0: - output += "$0, " - first = False - byte_count += 1 - - quotes_open = False - first_byte = True - was_byte = False - for byte in line: - if byte == 0x50: - had_text_end_byte = True # don't repeat it - if byte in [0x58, 0x57]: - had_text_end_byte_57_58 = True - - if byte in chars: - if not quotes_open and not first_byte: # start text - output += ", \"" - quotes_open = True - first_byte = False - if not quotes_open and first_byte: # start text - output += "\"" - quotes_open = True - output += chars[byte] - elif byte in constant_abbreviation_bytes: - if quotes_open: - output += "\"" - quotes_open = False - if not first_byte: - output += ", " - output += constant_abbreviation_bytes[byte] - else: - if quotes_open: - output += "\"" - quotes_open = False - - # if you want the ending byte on the last line - #if not (byte == 0x57 or byte == 0x50 or byte == 0x58): - if not first_byte: - output += ", " - - output += "$" + hex(byte)[2:] - was_byte = True - - # add a comma unless it's the end of the line - #if byte_count+1 != len(line): - # output += ", " - - first_byte = False - byte_count += 1 - # close final quotes - if quotes_open: - output += "\"" - quotes_open = False - - output += "\n" - #include_newline = "\n" - #if len(output)!=0 and output[-1] == "\n": - # include_newline = "" - #output += include_newline + "; " + hex(start_address) + " + " + str(byte_count) + " bytes = " + hex(start_address + byte_count) - if len(output) > 0 and output[-1] == "\n": - output = output[:-1] - self.size = self.byte_count = byte_count - return output - -def parse_text_engine_script_at(address, map_group=None, map_id=None, debug=True, show=True, force=False): - """parses a text-engine script ("in-text scripts") - http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText - see parse_text_at2, parse_text_at, and process_00_subcommands - """ - if is_script_already_parsed_at(address) and not force: - return script_parse_table[address] - return TextScript(address, map_group=map_group, map_id=map_id, debug=debug, show=show, force=force) - -def find_text_addresses(): - """returns a list of text pointers - useful for testing parse_text_engine_script_at""" - return TextScript.find_addresses() - -class EncodedText: - """a sequence of bytes that, when decoded, represent readable text - based on the chars table from preprocessor.py and other places""" - base_label = "UnknownRawText_" - - def __init__(self, address, bank=None, map_group=None, map_id=None, debug=True, label=None): - self.address = address - if bank: - self.bank = bank - else: - self.bank = calculate_bank(address) - self.map_group, self.map_id, self.debug = map_group, map_id, debug - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - self.dependencies = None - self.parse() - script_parse_table[self.address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - - def parse(self): - offset = self.address - - # read until $57, $50 or $58 - jump57 = how_many_until(chr(0x57), offset) - jump50 = how_many_until(chr(0x50), offset) - jump58 = how_many_until(chr(0x58), offset) - - # whichever command comes first - jump = min([jump57, jump50, jump58]) - - end_address = offset + jump # we want the address before $57 - - text = parse_text_at2(offset, end_address-offset, debug=self.debug) - - if jump == jump50: - text += "@" - - self.text = text - - self.last_address = self.end_address = end_address - - def to_asm(self): - return "\""+self.text+"\"" - - @staticmethod - def process_00_subcommands(start_address, end_address, debug=True): - """split this text up into multiple lines - based on subcommands ending each line""" - if debug: - print "process_00_subcommands(" + hex(start_address) + ", " + hex(end_address) + ")" - lines = {} - subsection = rom[start_address:end_address] - - line_count = 0 - current_line = [] - for pbyte in subsection: - byte = ord(pbyte) - current_line.append(byte) - if byte == 0x4f or byte == 0x51 or byte == 0x55: - lines[line_count] = current_line - current_line = [] - line_count += 1 - - # don't forget the last line - lines[line_count] = current_line - line_count += 1 - return lines - - @staticmethod - def from_bytes(bytes, debug=True, japanese=False): - """assembles a string based on bytes looked up in the chars table""" - line = "" - if japanese: charset = jap_chars - else: charset = chars - for byte in bytes: - if type(byte) != int: - byte = ord(byte) - if byte in charset.keys(): - line += charset[byte] - elif debug: - print "byte not known: " + hex(byte) - return line - - @staticmethod - def parse_text_at(address, count=10, debug=True, japanese=False): - """returns a string of text from an address - this does not handle text commands""" - output = "" - commands = process_00_subcommands(address, address+count, debug=debug) - for (line_id, line) in commands.items(): - output += parse_text_from_bytes(line, debug=debug, japanese=japanese) - texts.append([address, output]) - return output - - -def process_00_subcommands(start_address, end_address, debug=True): - """split this text up into multiple lines - based on subcommands ending each line""" - return EncodedText.process_00_subcommands(start_address, end_address, debug=debug) - -def parse_text_from_bytes(bytes, debug=True, japanese=False): - """assembles a string based on bytes looked up in the chars table""" - return EncodedText.from_bytes(bytes, debug=debug, japanese=japanese) - -def parse_text_at(address, count=10, debug=True): - """returns a list of bytes from an address - see parse_text_at2 for pretty printing""" - return parse_text_from_bytes(rom_interval(address, count, strings=False), debug=debug) - -def parse_text_at2(address, count=10, debug=True, japanese=False): - """returns a string of text from an address - this does not handle text commands""" - return EncodedText.parse_text_at(address, count, debug=debug, japanese=japanese) - -def parse_text_at3(address, map_group=None, map_id=None, debug=False): - deh = script_parse_table[address] - if deh: - return deh - else: - text = TextScript(address, map_group=map_group, map_id=map_id, debug=debug) - if text.is_valid(): - return text - else: - return None - -def rom_text_at(address, count=10): - """prints out raw text from the ROM - like for 0x112110""" - return "".join([chr(x) for x in rom_interval(address, count, strings=False)]) - -def get_map_constant_label(map_group=None, map_id=None): - """returns PALLET_TOWN for some map group/id pair""" - if map_group == None: - raise Exception("need map_group") - if map_id == None: - raise Exception("need map_id") - - global map_internal_ids - for (id, each) in map_internal_ids.items(): - if each["map_group"] == map_group and each["map_id"] == map_id: - return each["label"] - return None - -def get_map_constant_label_by_id(global_id): - """returns a map constant label for a particular map id""" - global map_internal_ids - return map_internal_ids[global_id]["label"] - -def get_id_for_map_constant_label(label): - """returns some global id for a given map constant label - PALLET_TOWN = 1, for instance.""" - global map_internal_ids - for (id, each) in map_internal_ids.items(): - if each["label"] == label: - return id - return None - -def generate_map_constant_labels(): - """generates the global for this script - mapping ids to map groups/ids/labels""" - global map_internal_ids - map_internal_ids = {} - i = 0 - for map_group in map_names.keys(): - for map_id in map_names[map_group].keys(): - if map_id == "offset": continue - cmap = map_names[map_group][map_id] - name = cmap["name"] - name = name.replace("Pokémon Center", "PokeCenter").\ - replace(" ", "_").\ - replace("-", "_").\ - replace("é", "e") - constant_label = map_name_cleaner(name).upper() - map_internal_ids[i] = {"label": constant_label, - "map_id": map_id, - "map_group": map_group} - i += 1 - return map_internal_ids - -# see generate_map_constant_labels() later -def generate_map_constants(): - """generates content for constants.asm - this will generate two macros: GROUP and MAP""" - global map_internal_ids - if map_internal_ids == None or map_internal_ids == {}: - generate_map_constant_labels() - globals, groups, maps = "", "", "" - for (id, each) in map_internal_ids.items(): - label = each["label"].replace("-", "_").replace("é", "e").upper() - - groups += "GROUP_"+ label + " EQU $%.2x" % (each["map_group"]) - groups += "\n" - maps += "MAP_"+ label + " EQU $%.2x" % (each["map_id"]) - maps += "\n" - globals += label + " EQU $%.2x" % (id) - globals += "\n" - #for multi-byte constants: - #print each["label"] + " EQUS \"$%.2x,$%.2x\"" % (each["map_group"], each["map_id"]) - print globals - print groups - print maps - -def generate_map_constants_dimensions(): - """ - Generate _WIDTH and _HEIGHT properties. - """ - global map_internal_ids - output = "" - if map_internal_ids == None or map_internal_ids == {}: - generate_map_constant_labels() - for (id, each) in map_internal_ids.items(): - map_group = each["map_group"] - map_id = each["map_id"] - label = each["label"].replace("-", "_").replace("é", "e").upper() - output += label + "_HEIGHT EQU %d\n" % (map_names[map_group][map_id]["header_new"].second_map_header.height.byte) - output += label + "_WIDTH EQU %d\n" % (map_names[map_group][map_id]["header_new"].second_map_header.width.byte) - return output - -def transform_wildmons(asm): - """ - Converts a wildmons section to use map constants. - input: wildmons text. - """ - asmlines = asm.split("\n") - returnlines = [] - for line in asmlines: - if "; " in line and not ("day" in line or "morn" in line or "nite" in line or "0x" in line or "encounter" in line) \ - and line != "" and line.split("; ")[0] != "": - map_group = int(line.split("\tdb ")[1].split(",")[0].replace("$", "0x"), base=16) - map_id = int(line.split("\tdb ")[1].split(",")[1].replace("$", "0x").split("; ")[0], base=16) - label = get_map_constant_label(map_group=map_group, map_id=map_id) - returnlines.append("\tdb GROUP_"+label+", MAP_"+label) #+" ; " + line.split(";")[1]) - else: - returnlines.append(line) - return "\n".join(returnlines) - -def parse_script_asm_at(*args, **kwargs): - # XXX TODO - return None - -def find_all_text_pointers_in_script_engine_script(script, bank=None, debug=False): - """returns a list of text pointers - based on each script-engine script command""" - # TODO: recursively follow any jumps in the script - if script == None: - return [] - addresses = set() - for (k, command) in enumerate(script.commands): - if debug: - print "command is: " + str(command) - if command.id == 0x4B: - addresses.add(command.params[0].parsed_address) - elif command.id == 0x4C: - addresses.add(command.params[0].parsed_address) - elif command.id == 0x51: - addresses.add(command.params[0].parsed_address) - elif command.id == 0x53: - addresses.add(command.params[0].parsed_address) - elif command.id == 0x64: - addresses.add(command.params[0].parsed_address) - addresses.add(command.params[1].parsed_address) - return addresses - -def translate_command_byte(crystal=None, gold=None): - """takes a command byte from either crystal or gold - returns the command byte in the other (non-given) game - - The new commands are values 0x52 and 0x9F. This means: - Crystal's 0x00–0x51 correspond to Gold's 0x00–0x51 - Crystal's 0x53–0x9E correspond to Gold's 0x52–0x9D - Crystal's 0xA0–0xA5 correspond to Gold's 0x9E–0xA3 - - see: http://www.pokecommunity.com/showpost.php?p=4347261 - """ - if crystal != None: # convert to gold - if crystal <= 0x51: return crystal - if crystal == 0x52: return None - if 0x53 <= crystal <= 0x9E: return crystal-1 - if crystal == 0x9F: return None - if 0xA0 <= crystal <= 0xA5: return crystal-2 - if crystal > 0xA5: - raise Exception("dunno yet if crystal has new insertions after crystal:0xA5 (gold:0xA3)") - elif gold != None: # convert to crystal - if gold <= 0x51: return gold - if 0x52 <= gold <= 0x9D: return gold+1 - if 0x9E <= gold <= 0xA3: return gold+2 - if gold > 0xA3: - raise Exception("dunno yet if crystal has new insertions after gold:0xA3 (crystal:0xA5)") - else: - raise Exception("translate_command_byte needs either a crystal or gold command") - -class SingleByteParam(): - """or SingleByte(CommandParam)""" - size = 1 - should_be_decimal = False - byte_type = "db" - - def __init__(self, *args, **kwargs): - for (key, value) in kwargs.items(): - setattr(self, key, value) - # check address - if not hasattr(self, "address"): - raise Exception("an address is a requirement") - elif self.address == None: - raise Exception("address must not be None") - elif not is_valid_address(self.address): - raise Exception("address must be valid") - # check size - if not hasattr(self, "size") or self.size == None: - raise Exception("size is probably 1?") - # parse bytes from ROM - self.parse() - - def parse(self): self.byte = ord(rom[self.address]) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - - def to_asm(self): - if not self.should_be_decimal: - return hex(self.byte).replace("0x", "$") - else: - return str(self.byte) - -class DollarSignByte(SingleByteParam): - def to_asm(self): - return hex(self.byte).replace("0x", "$") -HexByte=DollarSignByte - -class ItemLabelByte(DollarSignByte): - def to_asm(self): - label = find_item_label_by_id(self.byte) - if label: - return label - elif not label: - return DollarSignByte.to_asm(self) - - -class DecimalParam(SingleByteParam): - should_be_decimal = True - - -class MultiByteParam(): - """or MultiByte(CommandParam)""" - size = 2 - should_be_decimal = False - byte_type = "dw" - - def __init__(self, *args, **kwargs): - self.prefix = "$" # default.. feel free to set 0x in kwargs - for (key, value) in kwargs.items(): - setattr(self, key, value) - # check address - if not hasattr(self, "address") or self.address == None: - raise Exception("an address is a requirement") - elif not is_valid_address(self.address): - raise Exception("address must be valid") - # check size - if not hasattr(self, "size") or self.size == None: - raise Exception("don't know how many bytes to read (size)") - self.parse() - - def parse(self): - self.bytes = rom_interval(self.address, self.size, strings=False) - self.parsed_number = self.bytes[0] + (self.bytes[1] << 8) - if hasattr(self, "bank"): - self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - else: - self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=None) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - - # you won't actually use this to_asm because it's too generic - #def to_asm(self): return ", ".join([(self.prefix+"%.2x")%x for x in self.bytes]) - def to_asm(self): - if not self.should_be_decimal: - return self.prefix+"".join([("%.2x")%x for x in reversed(self.bytes)]) - elif self.should_be_decimal: - decimal = int("0x"+"".join([("%.2x")%x for x in reversed(self.bytes)]), 16) - return str(decimal) - - -class PointerLabelParam(MultiByteParam): - # default size is 2 bytes - default_size = 2 - size = 2 - # default is to not parse out a bank - bank = False - force = False - debug = False - - def __init__(self, *args, **kwargs): - self.dependencies = None - # bank can be overriden - if "bank" in kwargs.keys(): - if kwargs["bank"] != False and kwargs["bank"] != None and kwargs["bank"] in [True, "reverse"]: - # not +=1 because child classes set size=3 already - self.size = self.default_size + 1 - self.given_bank = kwargs["bank"] - #if kwargs["bank"] not in [None, False, True, "reverse"]: - # raise Exception("bank cannot be: " + str(kwargs["bank"])) - if self.size > 3: - raise Exception("param size is too large") - # continue instantiation.. self.bank will be set down the road - MultiByteParam.__init__(self, *args, **kwargs) - - def parse(self): - self.parsed_address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - MultiByteParam.parse(self) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - dependencies = [] - if self.parsed_address == self.address: - return dependencies - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - thing = script_parse_table[self.parsed_address] - if thing and thing.address == self.parsed_address and not (thing is self): - #if self.debug: - # print "parsed address is: " + hex(self.parsed_address) + " with label: " + thing.label.name + " of type: " + str(thing.__class__) - dependencies.append(thing) - if not thing in global_dependencies: - global_dependencies.add(thing) - more = thing.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - dependencies.extend(more) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - bank = self.bank - # we pass bank= for whether or not to include a bank byte when reading - #.. it's not related to caddress - caddress = None - if not (hasattr(self, "parsed_address") and self.parsed_address != None): - caddress = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - else: - caddress = self.parsed_address - label = get_label_for(caddress) - pointer_part = label # use the label, if it is found - - # check that the label actually points to the right place - result = script_parse_table[caddress] - if result != None and hasattr(result, "label"): - if result.label.name != label: - label = None - elif result.address != caddress: - label = None - elif result != None: - label = None - - # setup output bytes if the label was not found - if not label: - #pointer_part = (", ".join([(self.prefix+"%.2x")%x for x in reversed(self.bytes[1:])])) - pointer_part = self.prefix+("%.2x"%self.bytes[1])+("%.2x"%self.bytes[0]) - - # bank positioning matters! - if bank == True or bank == "reverse": # bank, pointer - # possibly use BANK(LABEL) if we know the bank - if not label: - bank_part = ((self.prefix+"%.2x")%bank) - else: - if "$" in label: - if 0x4000 <= caddress <= 0x7FFF: - #bank_part = "$%.2x" % (calculate_bank(self.parent.parent.address)) - bank_part = "1" - else: - bank_part = "$%.2x" % (calculate_bank(caddress)) - else: - bank_part = "BANK("+label+")" - # return the asm based on the order the bytes were specified to be in - if bank == "reverse": # pointer, bank - return pointer_part+", "+bank_part - elif bank == True: # bank, pointer - return bank_part+", "+pointer_part - else: - raise Exception("this should never happen") - raise Exception("this should never happen") - # this next one will either return the label or the raw bytes - elif bank == False or bank == None: # pointer - return pointer_part # this could be the same as label - else: - #raise Exception("this should never happen") - return pointer_part # probably in the same bank ? - raise Exception("this should never happen") - -class PointerLabelBeforeBank(PointerLabelParam): - bank = True # bank appears first, see calculate_pointer_from_bytes_at - size = 3 - byte_type = "dw" - -class PointerLabelAfterBank(PointerLabelParam): - bank = "reverse" # bank appears last, see calculate_pointer_from_bytes_at - size = 3 - - -class ScriptPointerLabelParam(PointerLabelParam): pass - - -class ScriptPointerLabelBeforeBank(PointerLabelBeforeBank): pass - - -class ScriptPointerLabelAfterBank(PointerLabelAfterBank): pass - - -def _parse_script_pointer_bytes(self, debug = False): - PointerLabelParam.parse(self) - if debug: print "_parse_script_pointer_bytes - calculating the pointer located at " + hex(self.address) - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - if address != None and address > 0x4000: - if debug: print "_parse_script_pointer_bytes - the pointer is: " + hex(address) - self.script = parse_script_engine_script_at(address, debug=self.debug, force=self.force, map_group=self.map_group, map_id=self.map_id) -ScriptPointerLabelParam.parse = _parse_script_pointer_bytes -ScriptPointerLabelBeforeBank.parse = _parse_script_pointer_bytes -ScriptPointerLabelAfterBank.parse = _parse_script_pointer_bytes - -class PointerLabelToScriptPointer(PointerLabelParam): - def parse(self): - PointerLabelParam.parse(self) - address = calculate_pointer_from_bytes_at(self.parsed_address, bank=self.bank) - address2 = calculate_pointer_from_bytes_at(address, bank=True) - self.script = parse_script_engine_script_at(address2, origin=False, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) - - -class AsmPointerParam(PointerLabelBeforeBank): - def parse(self): - PointerLabelBeforeBank.parse(self) - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) # 3-byte pointer - self.asm = parse_script_asm_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) # might end in some specific way? - - -class PointerToAsmPointerParam(PointerLabelParam): - def parse(self): - PointerLabelParam.parse(self) - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) # 2-byte pointer - address2 = calculate_pointer_from_bytes_at(address, bank="reverse") # maybe not "reverse"? - self.asm = parse_script_asm_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) # might end in some specific way? - - -class RAMAddressParam(MultiByteParam): - def to_asm(self): - address = calculate_pointer_from_bytes_at(self.address, bank=False) - label = get_ram_label(address) - if label: - return label - else: - return "$"+"".join(["%.2x"%x for x in reversed(self.bytes)])+"" - - -class MoneyByteParam(MultiByteParam): - size = 3 - max_value = 0x0F423F - should_be_decimal = True - def parse(self): - MultiByteParam.parse(self) - # in the rom as xxyyzz - self.x = self.bytes[2] - self.y = self.bytes[1] - self.z = self.bytes[0] - def to_asm(self): - return str(self.x + (self.y << 8) + (self.z << 16)) - - # this is used by the preprocessor - @staticmethod - def from_asm(value): - # max is 0F423F - # z = 0x0F ; y = 0x42 ; x = 0x3F - # 999999 = x + (y << 8) + (z << 16) - - value = int(value) - - x = (value & 0x0000FF) - y = (value & 0x00FF00) >> 8 - z = (value & 0xFF0000) >> 16 - - return str(z) + "\ndb "+str(y)+"\ndb "+str(x) - -def read_money(address, dohex=False): - z = ord(rom[address]) - y = ord(rom[address+1]) - x = ord(rom[address+2]) - answer = x + (y << 8) + (z << 16) - if not dohex: - return answer - else: - return hex(answer) - -def write_money(money): - value = money - x = (value & 0x0000FF) - y = (value & 0x00FF00) >> 8 - z = (value & 0xFF0000) >> 16 - return "db "+str(z)+"\ndb "+str(y)+"\ndb "+str(x) - -class CoinByteParam(MultiByteParam): - size = 2 - max_value = 0x270F - should_be_decimal = True - - -class MapGroupParam(SingleByteParam): - def to_asm(self): - map_id = ord(rom[self.address+1]) - map_constant_label = get_map_constant_label(map_id=map_id, map_group=self.byte) # like PALLET_TOWN - if map_constant_label == None: - return str(self.byte) - #else: return "GROUP("+map_constant_label+")" - else: - return "GROUP_"+map_constant_label - - -class MapIdParam(SingleByteParam): - def parse(self): - SingleByteParam.parse(self) - self.map_group = ord(rom[self.address-1]) - - def to_asm(self): - map_group = ord(rom[self.address-1]) - map_constant_label = get_map_constant_label(map_id=self.byte, map_group=map_group) - if map_constant_label == None: - return str(self.byte) - #else: return "MAP("+map_constant_label+")" - else: - return "MAP_"+map_constant_label - - -class MapGroupIdParam(MultiByteParam): - def parse(self): - MultiByteParam.parse(self) - self.map_group = self.bytes[0] - self.map_id = self.bytes[1] - - def to_asm(self): - map_group = self.map_group - map_id = self.map_id - label = get_map_constant_label(map_group=map_group, map_id=map_id) - return label - - -class PokemonParam(SingleByteParam): - def to_asm(self): - pokemon_constant = get_pokemon_constant_by_id(self.byte) - if pokemon_constant: - return pokemon_constant - else: - return str(self.byte) - - -class PointerParamToItemAndLetter(MultiByteParam): - # [2F][2byte pointer to item no + 0x20 bytes letter text] - #raise NotImplementedError(bryan_message) - pass - - -class TrainerIdParam(SingleByteParam): - def to_asm(self): - # find the group id by first finding the param type id - i = 0 - foundit = None - for (k, v) in self.parent.param_types.items(): - if v["class"] == TrainerGroupParam: - foundit = i - break - i += 1 - - if foundit == None: - raise Exception("didn't find a TrainerGroupParam in this command??") - - # now get the trainer group id - trainer_group_id = self.parent.params[foundit].byte - - # check the rule to see whether to use an id or not - if ("uses_numeric_trainer_ids" in trainer_group_names[trainer_group_id].keys()) or \ - (not "trainer_names" in trainer_group_names[trainer_group_id].keys()): - return str(self.byte) - else: - return trainer_group_names[trainer_group_id]["trainer_names"][self.byte-1] - -class TrainerGroupParam(SingleByteParam): - def to_asm(self): - trainer_group_id = self.byte - return trainer_group_names[trainer_group_id]["constant"] - -class MoveParam(SingleByteParam): - def to_asm(self): - if self.byte in moves.keys(): - return moves[self.byte] - else: - # this happens for move=0 (no move) in trainer headers - return str(self.byte) - -class MenuDataPointerParam(PointerLabelParam): - # read menu data at the target site - #raise NotImplementedError(bryan_message) - pass - - -string_to_text_texts = [] -class RawTextPointerLabelParam(PointerLabelParam): - # not sure if these are always to a text script or raw text? - def parse(self): - PointerLabelParam.parse(self) - #bank = calculate_bank(self.address) - address = calculate_pointer_from_bytes_at(self.address, bank=False) - self.calculated_address = address - #self.text = parse_text_at3(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - #self.text = TextScript(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - global_dependencies.add(self.text) - return [self.text] - -class EncodedTextLabelParam(PointerLabelParam): - def parse(self): - PointerLabelParam.parse(self) - - address = calculate_pointer_from_bytes_at(self.address, bank=False) - self.parsed_address = address - self.text = EncodedText(address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - - if isinstance(self.text, EncodedText): - string_to_text_texts.append(self.text) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - global_dependencies.add(self.text) - return [self.text] - -class TextPointerLabelParam(PointerLabelParam): - """this is a pointer to a text script""" - bank = False - text = None - def parse(self): - PointerLabelParam.parse(self) - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - if address != None and address != 0: - self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) - if not self.text: - self.text = script_parse_table[address] - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.text: - global_dependencies.add(self.text) - return [self.text] - else: - return [] - -class TextPointerLabelAfterBankParam(PointerLabelAfterBank): - text = None - def parse(self): - PointerLabelAfterBank.parse(self) - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - if address != None and address != 0: - self.text = parse_text_engine_script_at(address, map_group=self.map_group, map_id=self.map_id, force=self.force, debug=self.debug) - if not self.text: - self.text = script_parse_table[address] - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.text: - global_dependencies.add(self.text) - return [self.text] - else: - return [] - -class MovementPointerLabelParam(PointerLabelParam): - def parse(self): - PointerLabelParam.parse(self) - if is_script_already_parsed_at(self.parsed_address): - self.movement = script_parse_table[self.parsed_address] - else: - self.movement = ApplyMovementData(self.parsed_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if hasattr(self, "movement") and self.movement: - global_dependencies.add(self.movement) - return [self.movement] + self.movement.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - else: - raise Exception("MovementPointerLabelParam hasn't been parsed yet") - -class MapDataPointerParam(PointerLabelParam): - pass - -class Command: - """ - Note: when dumping to asm, anything in script_parse_table that directly - inherits Command should not be .to_asm()'d. - """ - # use this when the "byte id" doesn't matter - # .. for example, a non-script command doesn't use the "byte id" - override_byte_check = False - base_label = "UnseenLabel_" - - def __init__(self, address=None, *pargs, **kwargs): - """params: - address - where the command starts - force - whether or not to force the script to be parsed (default False) - debug - are we in debug mode? default False - map_group - map_id - """ - defaults = {"force": False, "debug": False, "map_group": None, "map_id": None} - if not is_valid_address(address): - raise Exception("address is invalid") - # set up some variables - self.address = address - self.last_address = None - # setup the label based on base_label if available - label = self.base_label + hex(self.address) - self.label = Label(name=label, address=address, object=self) - # params are where this command's byte parameters are stored - self.params = {} - self.dependencies = None - # override default settings - defaults.update(kwargs) - # set everything - for (key, value) in defaults.items(): - setattr(self, key, value) - # but also store these kwargs - self.args = defaults - # start parsing this command's parameter bytes - self.parse() - - def get_dependencies(self, recompute=False, global_dependencies=set()): - dependencies = [] - #if self.dependencies != None and not recompute: - # global_dependencies.update(self.dependencies) - # return self.dependencies - for (key, param) in self.params.items(): - if hasattr(param, "get_dependencies") and param != self: - deps = param.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - if deps != None and not self in deps: - dependencies.extend(deps) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - # start with the rgbasm macro name for this command - output = "" - #if len(self.macro_name) > 0 and self.macro_name[0].isdigit(): - # output += "_" - output += self.macro_name - # return if there are no params - if len(self.param_types.keys()) == 0: - return output - # first one will have no prefixing comma - first = True - # start reading the bytes after the command byte - if not self.override_byte_check: - current_address = self.address+1 - else: - current_address = self.address - #output = self.macro_name + ", ".join([param.to_asm() for (key, param) in self.params.items()]) - # add each param - for (key, param) in self.params.items(): - name = param.name - # the first param shouldn't have ", " prefixed - if first: - output += " " - first = False - # but all other params should - else: output += ", " - # now add the asm-compatible param string - output += param.to_asm() - current_address += param.size - #for param_type in self.param_types: - # name = param_type["name"] - # klass = param_type["klass"] - # # create an instance of this type - # # tell it to begin parsing at this latest byte - # obj = klass(address=current_address) - # # the first param shouldn't have ", " prefixed - # if first: first = False - # # but all other params should - # else: output += ", " - # # now add the asm-compatible param string - # output += obj.to_asm() - # current_address += obj.size - return output - - def parse(self): - # id, size (inclusive), param_types - #param_type = {"name": each[1], "class": each[0]} - if not self.override_byte_check: - current_address = self.address+1 - else: - current_address = self.address - byte = ord(rom[self.address]) - if not self.override_byte_check and (not byte == self.id): - raise Exception("byte ("+hex(byte)+") != self.id ("+hex(self.id)+")") - i = 0 - for (key, param_type) in self.param_types.items(): - name = param_type["name"] - klass = param_type["class"] - # make an instance of this class, like SingleByteParam() - # or ItemLabelByte.. by making an instance, obj.parse() is called - obj = klass(address=current_address, name=name, parent=self, **dict([(k,v) for (k, v) in self.args.items() if k not in ["parent"]])) - # save this for later - self.params[i] = obj - # increment our counters - current_address += obj.size - i += 1 - self.last_address = current_address - return True - - -class GivePoke(Command): - id = 0x2D - macro_name = "givepoke" - size = 4 # minimum - end = False - param_types = { - 0: {"name": "pokemon", "class": PokemonParam}, - 1: {"name": "level", "class": DecimalParam}, - 2: {"name": "item", "class": ItemLabelByte}, - 3: {"name": "trainer", "class": DecimalParam}, - 4: {"name": "trainer_name_pointer", "class": MultiByteParam}, # should probably use TextLabelParam - 5: {"name": "pkmn_nickname", "class": MultiByteParam}, # XXX TextLabelParam ? - } - allowed_lengths = [4, 6] - - def parse(self): - self.params = {} - byte = ord(rom[self.address]) - if not byte == self.id: - raise Exception("this should never happen") - current_address = self.address+1 - i = 0 - self.size = 1 - for (key, param_type) in self.param_types.items(): - # stop executing after the 4th byte unless it == 0x1 - if i == 4: print "self.params[3].byte is: " + str(self.params[3].byte) - if i == 4 and self.params[3].byte != 1: break - name = param_type["name"] - klass = param_type["class"] - # make an instance of this class, like SingleByteParam() - # or ItemLabelByte.. by making an instance, obj.parse() is called - obj = klass(address=current_address, name=name) - # save this for later - self.params[i] = obj - # increment our counters - current_address += obj.size - self.size += obj.size - i += 1 - self.last_address = current_address - return True - -class DataByteWordMacro(Command): - """ - Only used by the preprocessor. - """ - - id = None - macro_name = "dbw" - size = 3 - override_byte_check = True - - param_types = { - 0: {"name": "db value", "class": DecimalParam}, - 1: {"name": "dw value", "class": PointerLabelParam}, - } - - def __init__(self): pass - def parse(self): pass - def to_asm(self): pass - -class MovementCommand(Command): - # the vast majority of movement commands do not end the movement script - end = False - - # this is only used for e.g. macros that don't appear as a byte in the ROM - # don't use the override because all movements are specified with a byte - override_byte_check = False - - # most commands have size=1 but one or two have a single parameter (gasp) - size = 1 - - param_types = {} - params = [] - - # most movement commands won't have any dependencies - # get_dependencies on Command will look at the values of params - # so this doesn't need to be specified by MovementCommand as long as it extends Command - #def get_dependencies(self, recompute=False, global_dependencies=set()): - # return [] - - def parse(self): - if ord(rom[self.address]) < 0x45: - # this is mostly handled in to_asm - pass - else: - Command.parse(self) - - def to_asm(self): - # return "db $%.2x"%(self.byte) - return Command.to_asm(self) - -class MovementDBCommand(Command): - end = False - macro_name = "db" - override_byte_check = True - id = None - byte = None - size = 1 - param_types = { - 0: {"name": "db value", "class": SingleByteParam}, - } - params = [] - - def to_asm(self): - asm = Command.to_asm(self) - return asm + " ; movement" - -# down, up, left, right -movement_command_bases = { - 0x00: "turn_head", - 0x04: "half_step", - 0x08: "slow_step", # small_step? - 0x0C: "step", - 0x10: "big_step", # fast_step? - 0x14: "slow_slide_step", - 0x18: "slide_step", - 0x1C: "fast_slide_step", - 0x20: "turn_away", - 0x24: "turn_in", # towards? - 0x28: "turn_waterfall", # what?? - 0x2C: "slow_jump_step", - 0x30: "jump_step", - 0x34: "fast_jump_step", - - # tauwasser says the pattern stops at $45 but $38 looks more realistic? - 0x3A: "remove_fixed_facing", - 0x3B: "fix_facing", - 0x3D: "hide_person", - 0x3E: "show_person", - 0x45: "accelerate_last", - 0x46: ["step_sleep", ["duration", DecimalParam]], - 0x47: "step_end", - 0x49: "hide_person", - - # do these next two have any params ?? - 0x4C: "teleport_from", - 0x4D: "teleport_to", - - 0x4E: "skyfall", - 0x4F: "step_wait5", - 0x53: "hide_emote", - 0x54: "show_emote", - 0x55: ["step_shake", ["displacement", DecimalParam]], -} - -# create MovementCommands from movement_command_bases -def create_movement_commands(debug=False): - """ - Creates MovementCommands from movement_command_bases. This is just a cheap - trick instead of manually defining all of those classes. - """ - #movement_command_classes = inspect.getmembers(sys.modules[__name__], \ - # lambda obj: inspect.isclass(obj) and \ - # issubclass(obj, MovementCommand) and \ - # not (obj is MovementCommand)) - movement_command_classes2 = [] - for (byte, cmd) in movement_command_bases.items(): - if type(cmd) == str: - cmd = [cmd] - cmd_name = cmd[0].replace(" ", "_") - params = {"id": byte, "size": 1, "end": byte is 0x47, "macro_name": cmd_name} - params["param_types"] = {} - if len(cmd) > 1: - param_types = cmd[1:] - for (i, each) in enumerate(param_types): - thing = {"name": each[0], "class": each[1]} - params["param_types"][i] = thing - if debug: - print "each is: " + str(each) - print "thing[class] is: " + str(thing["class"]) - params["size"] += thing["class"].size - - if byte <= 0x34: - for x in range(0, 4): - - direction = None - if x == 0: - direction = "down" - elif x == 1: - direction = "up" - elif x == 2: - direction = "left" - elif x == 3: - direction = "right" - else: - raise Exception("this should never happen") - - cmd_name = cmd[0].replace(" ", "_") + "_" + direction - klass_name = cmd_name+"Command" - params["id"] = copy(byte) - params["macro_name"] = cmd_name - klass = classobj(copy(klass_name), (MovementCommand,), deepcopy(params)) - globals()[klass_name] = klass - movement_command_classes2.append(klass) - - byte += 1 - del cmd_name - del params - del klass_name - else: - klass_name = cmd_name+"Command" - klass = classobj(klass_name, (MovementCommand,), params) - globals()[klass_name] = klass - movement_command_classes2.append(klass) - # later an individual klass will be instantiated to handle something - return movement_command_classes2 - -movement_command_classes = create_movement_commands() - -all_movements = [] -class ApplyMovementData: - base_label = "MovementData_" - - def __init__(self, address, map_group=None, map_id=None, debug=False, label=None, force=False): - self.address = address - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.force = force - - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - - self.dependencies = [] - self.commands = [] - - self.parse() - - # this is almost an exact copy of Script.parse - # with the exception of using text_command_classes instead of command_classes - def parse(self): - global movement_command_classes, script_parse_table - address = self.address - - # i feel like checking myself - assert is_valid_address(address), "ApplyMovementData.parse must be given a valid address" - - current_address = copy(self.address) - start_address = copy(current_address) - - # don't clutter up my screen - if self.debug: - print "ApplyMovementData.parse address="+hex(self.address)+" map_group="+str(self.map_group)+" map_id="+str(self.map_id) - - # load up the rom if it hasn't been loaded already - load_rom() - - # in the event that the script parsing fails.. it would be nice to leave evidence - script_parse_table[start_address:start_address+1] = "incomplete ApplyMovementData.parse" - - # start with a blank script - commands = [] - - # use this to control the while loop - end = False - - # for each command found... - while not end: - # get the current scripting byte - cur_byte = ord(rom[current_address]) - - # reset the command class (last command was probably different) - scripting_command_class = None - - # match the command id byte to a scripting command class like "step half" - for class_ in movement_command_classes: - # allow lists of ids - if (type(class_.id) == list and cur_byte in class_.id) \ - or class_.id == cur_byte: - scripting_command_class = class_ - - # temporary fix for applymovement scripts - if ord(rom[current_address]) == 0x47: - end = True - - # no matching command found - xyz = None - if scripting_command_class == None: - scripting_command_class = MovementDBCommand - #scripting_command_class = deepcopy(MovementCommand) - #scripting_command_class.id = scripting_command_class.byte = ord(rom[current_address]) - #scripting_command_class.macro_name = "db" - #scripting_command_class.size = 1 - #scripting_command_class.override_byte_check = True - #scripting_command_class.id = None - #scripting_command_class.param_types = {0: {"name": "db value", "class": DecimalParam}} - - xyz = True - - # create an instance of the command class and let it parse its parameter bytes - cls = scripting_command_class(address=current_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=self.force) - - if self.debug: - print cls.to_asm() - - # store it in this script object - commands.append(cls) - - # certain commands will end the movement engine - end = cls.end - - # skip past the command's parameter bytes to go to the next command - current_address += cls.size - - # last byte belonging to script is last byte of last command, - # or the last byte of the last command's last parameter - # (actually i think this might be the next byte after??) - self.last_address = current_address - - # store the script in the global table/map thing - all_movements.append(self) - script_parse_table[start_address:current_address] = self - - if self.debug: - asm_output = "\n".join([command.to_asm() for command in commands]) - print "--------------\n"+asm_output - - # store the script - self.commands = commands - return commands - - def to_asm(self): - asm_output = "\n".join([command.to_asm() for command in self.commands]) - return asm_output - - # TODO: get_dependencies doesn't work if ApplyMovementData uses labels in the future - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - -def print_all_movements(): - for each in all_movements: - print each.to_asm() - print "------------------" - print "done" - -class TextCommand(Command): - # an individual text command will not end it - end = False - - # this is only used for e.g. macros that don't appear as a byte in the ROM - # don't use the override because all text commands are specified with a byte - override_byte_check = False - - # in the case of text/asm commands, size is unknown until after parsing - # some text commands can specify this upfront but not $0 - size = None - - param_types = {} - params = [] - - # most text commands won't have any dependencies - # .. except for that one that points to another location for text - # get_dependencies on Command will look at the values of params - # so this doesn't need to be specified by TextCommand as long as it extends Command - #def get_dependencies(self, recompute=False, global_dependencies=set()): - # return [] - -# this is a regular command in a TextScript for writing text -# but unlike other macros that preprocessor.py handles, -# the preprocessor-parser is custom and MainText is not -# used as a macro by main.asm - however, MainText is -# treated as a macro for the sake of parsing the ROM because -# it is called with $0. This is very similar to how Script -# is parsed and handled. But again, script command macros -# are quite different.. preprocessor.py allows some of them -# to handle how they should be parsed from main.asm, in -# addition to their regular "parse()" method. -class MainText(TextCommand): - "Write text. Structure: [00][Text][0x50 (ends code)]" - id = 0x0 - macro_name = "do_text" - use_zero = True - - def parse(self): - offset = self.address - - # the code below assumes we're jumping past a $0 byte - if self.use_zero == False: - offset = offset - else: - offset = offset + 1 - - # read until $50, $57 or $58 (not sure about $58...) - jump57 = how_many_until(chr(0x57), offset) - jump50 = how_many_until(chr(0x50), offset) - jump58 = how_many_until(chr(0x58), offset) - - # pick whichever one comes first - jump = min([jump57, jump50, jump58]) - - # if $57 appears first then this command is the last in this text script - if jump == jump57 or jump == jump58: - self.end = True - - jump += 1 - - # we want the address after the $57 - # ("last_address" is misnamed everywhere) - end_address = offset + jump - self.last_address = self.end_address = end_address - - # read the text bytes into a structure - # skip the first offset byte because that's the command byte - self.bytes = rom_interval(offset, jump, strings=False) - - # include the original command in the size calculation - self.size = jump - - if self.use_zero: - self.last_address = self.address + jump + 1 - self.size = self.last_address - self.address - - if self.address == 0x9c00e and self.debug: - if self.last_address != 0x9c086: - print "self.address is: " + hex(self.address) - print "jump is: " + str(jump) - print "bytes are: " + str(self.bytes) - print "self.size is: " + str(self.size) - print "self.last_address is: " + hex(self.last_address) - raise Exception("last_address is wrong for 0x9c00e") - - def to_asm(self): - if self.size < 2 or len(self.bytes) < 1: - raise Exception("$0 text command can't end itself with no follow-on bytes") - - if self.use_zero: - output = "db $0" - else: - output = "" - - # db $0, $57 or db $0, $50 or w/e - if self.size == 2 and len(self.bytes) == 1: - output += ", $%.2x" % (self.bytes[0]) - return output - - # whether or not quotes are open - in_quotes = False - - # whether or not to print "db " next - new_line = False - - # whether or not there was a ", " last.. - # this is useful outside of quotes - was_comma = False - - # has a $50 or $57 been passed yet? - end = False - - if not self.use_zero: - new_line = True - was_comma = False - - for byte in self.bytes: - if end: - raise Exception("the text ended due to a $50 or $57 but there are more bytes?") - - if new_line: - if in_quotes: - raise Exception("can't be in_quotes on a newline") - elif was_comma: - raise Exception("last line's last character can't be a comma") - - output += "db " - - # $4f, $51 and $55 can end a line - if byte in [0x4f, 0x51, 0x55]: - assert not new_line, "can't have $4f, $51, $55 as the first character on a newline" - - if in_quotes: - output += "\", $%.2x\n" % (byte) - elif not in_quotes: - if not was_comma: - output += ", " - output += "$%.2x\n" % (byte) - - # reset everything - in_quotes = False - new_line = True - was_comma = False - elif byte == 0x50: - # technically you could have this i guess... db "@" - # but in most situations it will be added to the end of the previous line - #assert not new_line, "can't have $50 or '@' as the first character on a newline in the text at "+hex(self.address) - - if in_quotes: - output += "@\"\n" - new_line = True - elif not in_quotes: - if not was_comma and not new_line: - output += ", " - output += "\"@\"\n" - - # reset everything - in_quotes = False - new_line = True - was_comma = False - end = True - - # self.end should be set in parse or constructor - # so this is very useless here.. but it's a truism i guess - self.end = True - elif byte == 0x57 or byte == 0x58: - # close any quotes - if in_quotes: - output += "\"" - was_comma = False - - if not was_comma and not new_line: - output += ", " - - output += "$%.2x\n" % (byte) - - in_quotes = False - new_line = True - was_comma = False - end = True - - # dunno if $58 should end a text script or not - # also! self.end should be set in parse not in to_asm - # so this is pretty useless overall... - if byte == 0x58: - self.end = True - elif byte in chars.keys(): - # figure out what the character actually is - char = chars[byte] - - # oh wait.. quotes isn't a valid character in the first place :( - if char == "\"": - if in_quotes: - output += "\"" - in_quotes = False - elif not in_quotes: - if new_line: - output += "\"" - elif not new_line: - if not was_comma: - output += ", " - output += "\"" - in_quotes = True - - # the above if statement is probably never called - else: - if not in_quotes: - if not new_line and not was_comma: - output += ", " - output += "\"" - in_quotes = True - - output += char - - new_line = False - was_comma = False - end = False - else: - # raise Exception("unknown byte in text script ($%.2x)" % (byte)) - # just add an unknown byte directly to the text.. what's the worse that can happen? - - if in_quotes: - output += "\", $%.2x" % (byte) - - in_quotes = False - was_comma = False - new_line = False - elif not in_quotes: - if not was_comma and not new_line: - output += ", " - output += "$%.2x" % (byte) - - # reset things - in_quotes = False - new_line = False - was_comma = False - - # this shouldn't happen because of the rom_until calls in the parse method - if not end: - raise Exception("ran out of bytes without the script ending? starts at "+hex(self.address)) - - # last character may or may not be allowed to be a newline? - # Script.to_asm() has command.to_asm()+"\n" - if output[-1] == "\n": - output = output[:-1] - - return output - -class PokedexText(MainText): - use_zero = False - -class WriteTextFromRAM(TextCommand): - """ - Write text from ram. Structure: [01][Ram address (2byte)] - For valid ram addresses see Glossary. This enables use of variable text strings. - """ - id = 0x1 - macro_name = "text_from_ram" - size = 3 - param_types = { - 0: {"name": "pointer", "class": MultiByteParam}, - } -class WriteNumberFromRAM(TextCommand): - """ - 02 = Write number from ram. Structure: [02][Ram address (2byte)][Byte] - - Byte: - - Bit5:Bit6:Bit7 - 1: 1: 1 = PokéDollar| Don’t write zeros - 0: 1: 1 = Don’t write zeros - 0: 0: 1 = Spaces instead of zeros - 0: 0: 0 = Write zeros - 0: 1: 0 = Write zeros - 1: 0: 0 = PokéDollar - 1: 1: 0 = PokéDollar - 1: 0: 1 = Spaces instead of zeros| PokéDollar - - Number of figures = Byte AND 0x1F *2 - No Hex --> Dec Conversio - """ - id = 0x2 - macro_name = "number_from_ram" - size = 4 - param_types = { - 0: {"name": "pointer", "class": PointerLabelParam}, - 1: {"name": "config", "class": HexByte}, - } -class SetWriteRAMLocation(TextCommand): - "Define new ram address to write to. Structure: [03][Ram address (2byte)]" - id = 0x3 - macro_name = "store_at" - size = 3 - param_types = { - 0: {"name": "ram address", "class": PointerLabelParam}, - } -class ShowBoxWithValueAt(TextCommand): - "04 = Write a box. Structure: [04][Ram address (2byte)][Y][X]" - id = 0x4 - macro_name = "text_box" - size = 5 - param_types = { - 0: {"name": "ram address", "class": PointerLabelParam}, - 1: {"name": "y", "class": DecimalParam}, - 2: {"name": "x", "class": DecimalParam}, - } -class Populate2ndLineOfTextBoxWithRAMContents(TextCommand): - "05 = New ram address to write to becomes 2nd line of a text box. Structure: [05]" - id = 0x5 - macro_name = "text_dunno1" - size = 1 -class ShowArrowsAndButtonWait(TextCommand): - "06 = Wait for key down + show arrows. Structure: [06]" - id = 0x6 - macro_name = "text_waitbutton" - size = 1 -class Populate2ndLine(TextCommand): - """ - 07 = New ram address to write to becomes 2nd line of a text box - Textbox + show arrows. Structure: [07] - """ - id = 0x7 - macro_name = "text_dunno2" - size = 1 -class TextInlineAsm(TextCommand): - "08 = After the code an ASM script starts. Structure: [08][Script]" - id = 0x8 - macro_name = "start_asm" - end = True - size = 1 - # TODO: parse the following asm with gbz80disasm -class WriteDecimalNumberFromRAM(TextCommand): - """ - 09 = Write number from rom/ram in decimal. Structure: [09][Ram address/Pointer (2byte)][Byte] - Byte: - - Is split: 1. 4 bits = Number of bytes to load. 0 = 3, 1 = 1, 2 = 2 - 2. 4 bits = Number of figures of displayed number - 0 = Don’t care - 1 = Don’t care - >=2 = Number - """ - id = 0x9 - macro_name = "deciram" - size = 4 - param_types = { - 0: {"name": "pointer?", "class": PointerLabelParam}, - 1: {"name": "config", "class": HexByte}, - } -class InterpretDataStream(TextCommand): - """ - 0A = Interpret Data stream. Structure: [0A] - see: http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke88 - """ - id = 0xA - macro_name = "interpret_data" - size = 1 -class Play0thSound(TextCommand): - "0B = Play sound 0x0000. Structure: [0B]" - id = 0xB - sound_num = 0 - macro_name = "sound0" - size = 1 -class LimitedIntrepretDataStream(TextCommand): - """ - 0C = Interpret Data stream. Structure: [0C][Number of codes to interpret] - For every interpretation there is a“…“ written - """ - id = 0xC - macro_name = "limited_interpret_data" - size = 2 - param_types = { - 0: {"name": "number of codes to interpret", "class": DecimalParam}, - } -class WaitForKeyDownDisplayArrow(ShowArrowsAndButtonWait): - """ - 0D = Wait for key down display arrow. Structure: [0D] - """ - id = 0xD - macro_name = "waitbutton2" - size = 1 -class Play9thSound(Play0thSound): - id = 0xE - sound_num = 9 - macro_name = "sound0x09" - size = 1 -class Play1stSound(Play0thSound): - id = 0xF - sound_num = 1 - macro_name = "sound0x0F" - size = 1 -class Play2ndSound(Play0thSound): - id = 0x10 - sound_num = 2 - macro_name = "sound0x02" - size = 1 -class Play10thSound(Play0thSound): - id = 0x11 - sound_num = 10 - macro_name = "sound0x0A" - size = 1 -class Play45thSound(Play0thSound): - id = 0x12 - sound_num = 0x2D - macro_name = "sound0x2D" - size = 1 -class Play44thSound(Play0thSound): - id = 0x13 - sound_num = 0x2C - macro_name = "sound0x2C" - size = 1 -class DisplayByteFromRAMAt(TextCommand): - """ - 14 = Display MEMORY. Structure: [14][Byte] - - Byte: - - 00 = MEMORY1 - 01 = MEMORY2 - 02 = MEMORY - 04 = TEMPMEMORY2 - 05 = TEMPMEMORY1 - """ - id = 0x14 - macro_name = "show_byte_at" - size = 2 - param_types = { - 1: {"name": "memory byte id", "class": DecimalParam}, - } -class WriteCurrentDay(TextCommand): - "15 = Write current day. Structure: [15]" - id = 0x15 - macro_name = "current_day" - size = 1 -class TextJump(TextCommand): - "16 = 3byte pointer to new text follows. Structure: [16][2byte pointer][bank]" - id = 0x16 - macro_name = "text_jump" - size = 4 - param_types = { - 0: {"name": "text", "class": TextPointerLabelAfterBankParam}, - } -# this is needed because sometimes a script ends with $50 $50 -class TextEndingCommand(TextCommand): - id = 0x50 - macro_name = "db" - override_byte_check = False - size = 1 - end = True - def to_asm(self): - return "db $50" - -text_command_classes = inspect.getmembers(sys.modules[__name__], \ - lambda obj: inspect.isclass(obj) and \ - issubclass(obj, TextCommand) and \ - obj != TextCommand and obj != PokedexText) - -# byte: [name, [param1 name, param1 type], [param2 name, param2 type], ...] -# 0x9E: ["verbosegiveitem", ["item", ItemLabelByte], ["quantity", SingleByteParam]], -pksv_crystal_more = { - 0x00: ["2call", ["pointer", ScriptPointerLabelParam]], - 0x01: ["3call", ["pointer", ScriptPointerLabelBeforeBank]], - 0x02: ["2ptcall", ["pointer", RAMAddressParam]], - 0x03: ["2jump", ["pointer", ScriptPointerLabelParam]], - 0x04: ["3jump", ["pointer", ScriptPointerLabelBeforeBank]], - 0x05: ["2ptjump", ["pointer", RAMAddressParam]], - 0x06: ["if equal", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]], - 0x07: ["if not equal", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]], - 0x08: ["iffalse", ["pointer", ScriptPointerLabelParam]], - 0x09: ["iftrue", ["pointer", ScriptPointerLabelParam]], - 0x0A: ["if less than", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]], - 0x0B: ["if greater than", ["byte", SingleByteParam], ["pointer", ScriptPointerLabelParam]], - 0x0C: ["jumpstd", ["predefined_script", MultiByteParam]], - 0x0D: ["callstd", ["predefined_script", MultiByteParam]], - 0x0E: ["3callasm", ["asm", AsmPointerParam]], - 0x0F: ["special", ["predefined_script", MultiByteParam]], - 0x10: ["2ptcallasm", ["asm", RAMAddressParam]], - # should map_group/map_id be dealt with in some special way in the asm? - 0x11: ["checkmaptriggers", ["map_group", SingleByteParam], ["map_id", SingleByteParam]], - 0x12: ["domaptrigger", ["map_group", MapGroupParam], ["map_id", MapIdParam], ["trigger_id", SingleByteParam]], - 0x13: ["checktriggers"], - 0x14: ["dotrigger", ["trigger_id", SingleByteParam]], - 0x15: ["writebyte", ["value", SingleByteParam]], - 0x16: ["addvar", ["value", SingleByteParam]], - 0x17: ["random", ["input", SingleByteParam]], - 0x18: ["checkver"], - 0x19: ["copybytetovar", ["address", RAMAddressParam]], - 0x1A: ["copyvartobyte", ["address", RAMAddressParam]], - 0x1B: ["loadvar", ["address", RAMAddressParam], ["value", SingleByteParam]], - 0x1C: ["checkcode", ["variable_id", SingleByteParam]], - 0x1D: ["writevarcode", ["variable_id", SingleByteParam]], - 0x1E: ["writecode", ["variable_id", SingleByteParam], ["value", SingleByteParam]], - 0x1F: ["giveitem", ["item", ItemLabelByte], ["quantity", SingleByteParam]], - 0x20: ["takeitem", ["item", ItemLabelByte], ["quantity", DecimalParam]], - 0x21: ["checkitem", ["item", ItemLabelByte]], - 0x22: ["givemoney", ["account", SingleByteParam], ["money", MoneyByteParam]], - 0x23: ["takemoney", ["account", SingleByteParam], ["money", MoneyByteParam]], - 0x24: ["checkmoney", ["account", SingleByteParam], ["money", MoneyByteParam]], - 0x25: ["givecoins", ["coins", CoinByteParam]], - 0x26: ["takecoins", ["coins", CoinByteParam]], - 0x27: ["checkcoins", ["coins", CoinByteParam]], - # 0x28-0x2A not from pksv - 0x28: ["addcellnum", ["person", SingleByteParam]], - 0x29: ["delcellnum", ["person", SingleByteParam]], - 0x2A: ["checkcellnum", ["person", SingleByteParam]], - # back on track... - 0x2B: ["checktime", ["time", SingleByteParam]], - 0x2C: ["checkpoke", ["pkmn", PokemonParam]], -#0x2D: ["givepoke", ], .... see GivePoke class - 0x2E: ["giveegg", ["pkmn", PokemonParam], ["level", DecimalParam]], - 0x2F: ["givepokeitem", ["pointer", PointerParamToItemAndLetter]], - 0x30: ["checkpokeitem", ["pointer", PointerParamToItemAndLetter]], # not pksv - 0x31: ["checkbit1", ["bit_number", MultiByteParam]], - 0x32: ["clearbit1", ["bit_number", MultiByteParam]], - 0x33: ["setbit1", ["bit_number", MultiByteParam]], - 0x34: ["checkbit2", ["bit_number", MultiByteParam]], - 0x35: ["clearbit2", ["bit_number", MultiByteParam]], - 0x36: ["setbit2", ["bit_number", MultiByteParam]], - 0x37: ["wildoff"], - 0x38: ["wildon"], - 0x39: ["xycompare", ["pointer", MultiByteParam]], - 0x3A: ["warpmod", ["warp_id", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam]], - 0x3B: ["blackoutmod", ["map_group", MapGroupParam], ["map_id", MapIdParam]], - 0x3C: ["warp", ["map_group", MapGroupParam], ["map_id", MapIdParam], ["x", SingleByteParam], ["y", SingleByteParam]], - 0x3D: ["readmoney", ["account", SingleByteParam], ["memory", SingleByteParam]], # not pksv - 0x3E: ["readcoins", ["memory", SingleByteParam]], # not pksv - 0x3F: ["RAM2MEM", ["memory", SingleByteParam]], # not pksv - 0x40: ["pokenamemem", ["pokemon", PokemonParam], ["memory", SingleByteParam]], # not pksv - 0x41: ["itemtotext", ["item", ItemLabelByte], ["memory", SingleByteParam]], - 0x42: ["mapnametotext", ["memory", SingleByteParam]], # not pksv - 0x43: ["trainertotext", ["trainer_id", TrainerGroupParam], ["trainer_group", TrainerIdParam], ["memory", SingleByteParam]], - 0x44: ["stringtotext", ["text_pointer", EncodedTextLabelParam], ["memory", SingleByteParam]], - 0x45: ["itemnotify"], - 0x46: ["pocketisfull"], - 0x47: ["loadfont"], - 0x48: ["refreshscreen", ["dummy", SingleByteParam]], - 0x49: ["loadmovesprites"], - 0x4A: ["loadbytec1ce", ["byte", SingleByteParam]], # not pksv - 0x4B: ["3writetext", ["text_pointer", PointerLabelBeforeBank]], - 0x4C: ["2writetext", ["text_pointer", RawTextPointerLabelParam]], - 0x4D: ["repeattext", ["byte", SingleByteParam], ["byte", SingleByteParam]], # not pksv - 0x4E: ["yesorno"], - 0x4F: ["loadmenudata", ["data", MenuDataPointerParam]], - 0x50: ["writebackup"], - 0x51: ["jumptextfaceplayer", ["text_pointer", RawTextPointerLabelParam]], - 0x52: ["3jumptext", ["text_pointer", PointerLabelBeforeBank]], - 0x53: ["jumptext", ["text_pointer", RawTextPointerLabelParam]], - 0x54: ["closetext"], - 0x55: ["keeptextopen"], - 0x56: ["pokepic", ["pokemon", PokemonParam]], - 0x57: ["pokepicyesorno"], - 0x58: ["interpretmenu"], - 0x59: ["interpretmenu2"], -# not pksv - 0x5A: ["loadpikachudata"], - 0x5B: ["battlecheck"], - 0x5C: ["loadtrainerdata"], -# back to pksv.. - 0x5D: ["loadpokedata", ["pokemon", PokemonParam], ["level", DecimalParam]], - 0x5E: ["loadtrainer", ["trainer_group", TrainerGroupParam], ["trainer_id", TrainerIdParam]], - 0x5F: ["startbattle"], - 0x60: ["returnafterbattle"], - 0x61: ["catchtutorial", ["byte", SingleByteParam]], -# not pksv - 0x62: ["trainertext", ["which_text", SingleByteParam]], - 0x63: ["trainerstatus", ["action", SingleByteParam]], -# back to pksv.. - 0x64: ["winlosstext", ["win_text_pointer", TextPointerLabelParam], ["loss_text_pointer", TextPointerLabelParam]], - 0x65: ["scripttalkafter"], # not pksv - 0x66: ["talkaftercancel"], - 0x67: ["talkaftercheck"], - 0x68: ["setlasttalked", ["person", SingleByteParam]], - 0x69: ["applymovement", ["person", SingleByteParam], ["data", MovementPointerLabelParam]], - 0x6A: ["applymovement2", ["data", MovementPointerLabelParam]], # not pksv - 0x6B: ["faceplayer"], - 0x6C: ["faceperson", ["person1", SingleByteParam], ["person2", SingleByteParam]], - 0x6D: ["variablesprite", ["byte", SingleByteParam], ["sprite", SingleByteParam]], - 0x6E: ["disappear", ["person", SingleByteParam]], # hideperson - 0x6F: ["appear", ["person", SingleByteParam]], # showperson - 0x70: ["follow", ["person2", SingleByteParam], ["person1", SingleByteParam]], - 0x71: ["stopfollow"], - 0x72: ["moveperson", ["person", SingleByteParam], ["x", SingleByteParam], ["y", SingleByteParam]], - 0x73: ["writepersonxy", ["person", SingleByteParam]], # not pksv - 0x74: ["loademote", ["bubble", SingleByteParam]], - 0x75: ["showemote", ["bubble", SingleByteParam], ["person", SingleByteParam], ["time", DecimalParam]], - 0x76: ["spriteface", ["person", SingleByteParam], ["facing", SingleByteParam]], - 0x77: ["follownotexact", ["person2", SingleByteParam], ["person1", SingleByteParam]], - 0x78: ["earthquake", ["param", DecimalParam]], - 0x79: ["changemap", ["map_data_pointer", MapDataPointerParam]], - 0x7A: ["changeblock", ["x", SingleByteParam], ["y", SingleByteParam], ["block", SingleByteParam]], - 0x7B: ["reloadmap"], - 0x7C: ["reloadmappart"], - 0x7D: ["writecmdqueue", ["queue_pointer", MultiByteParam]], - 0x7E: ["delcmdqueue", ["byte", SingleByteParam]], - 0x7F: ["playmusic", ["music_pointer", MultiByteParam]], - 0x80: ["playrammusic"], - 0x81: ["musicfadeout", ["music", MultiByteParam], ["fadetime", SingleByteParam]], - 0x82: ["playmapmusic"], - 0x83: ["reloadmapmusic"], - 0x84: ["cry", ["cry_id", MultiByteParam]], # XXX maybe it should use PokemonParam - 0x85: ["playsound", ["sound_pointer", MultiByteParam]], - 0x86: ["waitbutton"], - 0x87: ["warpsound"], - 0x88: ["specialsound"], - 0x89: ["passtoengine", ["data_pointer", PointerLabelBeforeBank]], - 0x8A: ["newloadmap", ["which_method", SingleByteParam]], - 0x8B: ["pause", ["length", DecimalParam]], - 0x8C: ["deactivatefacing", ["time", SingleByteParam]], - 0x8D: ["priorityjump", ["pointer", ScriptPointerLabelParam]], - 0x8E: ["warpcheck"], - 0x8F: ["ptpriorityjump", ["pointer", ScriptPointerLabelParam]], - 0x90: ["return"], - 0x91: ["end"], - 0x92: ["reloadandreturn"], - 0x93: ["resetfuncs"], - 0x94: ["pokemart", ["dialog_id", SingleByteParam], ["mart_id", MultiByteParam]], # maybe it should be a pokemark constant id/label? - 0x95: ["elevator", ["floor_list_pointer", PointerLabelParam]], - 0x96: ["trade", ["trade_id", SingleByteParam]], - 0x97: ["askforphonenumber", ["number", SingleByteParam]], - 0x98: ["phonecall", ["caller_name", RawTextPointerLabelParam]], - 0x99: ["hangup"], - 0x9A: ["describedecoration", ["byte", SingleByteParam]], - 0x9B: ["fruittree", ["tree_id", SingleByteParam]], - 0x9C: ["specialphonecall", ["call_id", MultiByteParam]], - 0x9D: ["checkphonecall"], - 0x9E: ["verbosegiveitem", ["item", ItemLabelByte], ["quantity", DecimalParam]], - 0x9F: ["verbosegiveitem2", ["item", ItemLabelByte], ["var", SingleByteParam]], - 0xA0: ["loadwilddata", ["map_group", MapGroupParam], ["map_id", MapIdParam]], - 0xA1: ["halloffame"], - 0xA2: ["credits"], - 0xA3: ["warpfacing", ["facing", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam], ["x", SingleByteParam], ["y", SingleByteParam]], - 0xA4: ["storetext", ["pointer", PointerLabelBeforeBank], ["memory", SingleByteParam]], - 0xA5: ["displaylocation", ["id", SingleByteParam]], - 0xA6: ["unknown0xa6"], - 0xA7: ["unknown0xa7"], - 0xA8: ["unknown0xa8", ["unknown", SingleByteParam]], - 0xA9: ["unknown0xa9"], - 0xAA: ["unknown0xaa"], -} -def create_command_classes(debug=False): - """creates some classes for each command byte""" - # don't forget to add any manually created script command classes - # .. except for Warp, Signpost and some others that aren't found in scripts - klasses = [GivePoke] - for (byte, cmd) in pksv_crystal_more.items(): - cmd_name = cmd[0].replace(" ", "_") - params = {"id": byte, "size": 1, "end": byte in pksv_crystal_more_enders, "macro_name": cmd_name} - params["param_types"] = {} - if len(cmd) > 1: - param_types = cmd[1:] - for (i, each) in enumerate(param_types): - thing = {"name": each[0], "class": each[1]} - params["param_types"][i] = thing - if debug: - print "each is: " + str(each) - print "thing[class] is: " + str(thing["class"]) - params["size"] += thing["class"].size - klass_name = cmd_name+"Command" - klass = classobj(klass_name, (Command,), params) - globals()[klass_name] = klass - klasses.append(klass) - # later an individual klass will be instantiated to handle something - return klasses -command_classes = create_command_classes() - - - -music_commands_new = { - 0xD0: ["octave8"], - 0xD1: ["octave7"], - 0xD2: ["octave6"], - 0xD3: ["octave5"], - 0xD4: ["octave4"], - 0xD5: ["octave3"], - 0xD6: ["octave2"], - 0xD7: ["octave1"], - 0xD8: ["notetype", ["note_length", SingleByteParam], ["intensity", SingleByteParam]], # only 1 param on ch3 - 0xD9: ["forceoctave", ["octave", SingleByteParam]], - 0xDA: ["tempo", ["tempo", MultiByteParam]], - 0xDB: ["dutycycle", ["duty_cycle", SingleByteParam]], - 0xDC: ["intensity", ["intensity", SingleByteParam]], - 0xDD: ["soundinput", ["input", SingleByteParam]], - 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]], # also updates duty cycle - 0xDF: ["unknownmusic0xdf"], - 0xE0: ["unknownmusic0xe0", ["unknown", SingleByteParam], ["unknown", SingleByteParam]], - 0xE1: ["vibrato", ["delay", SingleByteParam], ["extent", SingleByteParam]], - 0xE2: ["unknownmusic0xe2", ["unknown", SingleByteParam]], - 0xE3: ["togglenoise", ["id", SingleByteParam]], # this can have 0-1 params! - 0xE4: ["panning", ["tracks", SingleByteParam]], - 0xE5: ["volume", ["volume", SingleByteParam]], - 0xE6: ["tone", ["tone", MultiByteParam]], # big endian - 0xE7: ["unknownmusic0xe7", ["unknown", SingleByteParam]], - 0xE8: ["unknownmusic0xe8", ["unknown", SingleByteParam]], - 0xE9: ["globaltempo", ["value", MultiByteParam]], - 0xEA: ["restartchannel", ["address", PointerLabelParam]], - 0xEB: ["newsong", ["id", MultiByteParam]], - 0xEC: ["sfxpriorityon"], - 0xED: ["sfxpriorityoff"], - 0xEE: ["unknownmusic0xee", ["address", PointerLabelParam]], - 0xEF: ["stereopanning", ["tracks", SingleByteParam]], - 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # 0-1 params - 0xF1: ["music0xf1"], # nothing - 0xF2: ["music0xf2"], # nothing - 0xF3: ["music0xf3"], # nothing - 0xF4: ["music0xf4"], # nothing - 0xF5: ["music0xf5"], # nothing - 0xF6: ["music0xf6"], # nothing - 0xF7: ["music0xf7"], # nothing - 0xF8: ["music0xf8"], # nothing - 0xF9: ["unknownmusic0xf9"], - 0xFA: ["setcondition", ["condition", SingleByteParam]], - 0xFB: ["jumpif", ["condition", SingleByteParam], ["address", PointerLabelParam]], - 0xFC: ["jumpchannel", ["address", PointerLabelParam]], - 0xFD: ["loopchannel", ["count", SingleByteParam], ["address", PointerLabelParam]], - 0xFE: ["callchannel", ["address", PointerLabelParam]], - 0xFF: ["endchannel"], -} - -music_command_enders = [0xEA, 0xEB, 0xEE, 0xFC, 0xFF,] -# special case for 0xFD (if loopchannel.count = 0, break) - -def create_music_command_classes(debug=False): - klasses = [] - for (byte, cmd) in music_commands_new.items(): - cmd_name = cmd[0].replace(" ", "_") - params = {"id": byte, "size": 1, "end": byte in music_command_enders, "macro_name": cmd_name} - params["param_types"] = {} - if len(cmd) > 1: - param_types = cmd[1:] - for (i, each) in enumerate(param_types): - thing = {"name": each[0], "class": each[1]} - params["param_types"][i] = thing - if debug: - print "each is: " + str(each) - print "thing[class] is: " + str(thing["class"]) - params["size"] += thing["class"].size - klass_name = cmd_name+"Command" - klass = classobj(klass_name, (Command,), params) - globals()[klass_name] = klass - if klass.macro_name == "notetype": - klass.allowed_lengths = [1, 2] - elif klass.macro_name in ["togglenoise", "sfxtogglenoise"]: - klass.allowed_lengths = [0, 1] - klasses.append(klass) - # later an individual klass will be instantiated to handle something - return klasses -music_classes = create_music_command_classes() - - - -effect_commands = { - 0x1: ['checkturn'], - 0x2: ['checkobedience'], - 0x3: ['usedmovetext'], - 0x4: ['doturn'], - 0x5: ['critical'], - 0x6: ['damagestats'], - 0x7: ['stab'], - 0x8: ['damagevariation'], - 0x9: ['checkhit'], - 0xa: ['effect0x0a'], - 0xb: ['effect0x0b'], - 0xc: ['effect0x0c'], - 0xd: ['resulttext'], - 0xe: ['checkfaint'], - 0xf: ['criticaltext'], - 0x10: ['supereffectivetext'], - 0x11: ['checkdestinybond'], - 0x12: ['buildopponentrage'], - 0x13: ['poisontarget'], - 0x14: ['sleeptarget'], - 0x15: ['draintarget'], - 0x16: ['eatdream'], - 0x17: ['burntarget'], - 0x18: ['freezetarget'], - 0x19: ['paralyzetarget'], - 0x1a: ['selfdestruct'], - 0x1b: ['mirrormove'], - 0x1c: ['statup'], - 0x1d: ['statdown'], - 0x1e: ['payday'], - 0x1f: ['conversion'], - 0x20: ['resetstats'], - 0x21: ['storeenergy'], - 0x22: ['unleashenergy'], - 0x23: ['forceswitch'], - 0x24: ['endloop'], - 0x25: ['flinchtarget'], - 0x26: ['ohko'], - 0x27: ['recoil'], - 0x28: ['mist'], - 0x29: ['focusenergy'], - 0x2a: ['confuse'], - 0x2b: ['confusetarget'], - 0x2c: ['heal'], - 0x2d: ['transform'], - 0x2e: ['screen'], - 0x2f: ['poison'], - 0x30: ['paralyze'], - 0x31: ['substitute'], - 0x32: ['rechargenextturn'], - 0x33: ['mimic'], - 0x34: ['metronome'], - 0x35: ['leechseed'], - 0x36: ['splash'], - 0x37: ['disable'], - 0x38: ['cleartext'], - 0x39: ['charge'], - 0x3a: ['checkcharge'], - 0x3b: ['traptarget'], - 0x3c: ['effect0x3c'], - 0x3d: ['rampage'], - 0x3e: ['checkrampage'], - 0x3f: ['constantdamage'], - 0x40: ['counter'], - 0x41: ['encore'], - 0x42: ['painsplit'], - 0x43: ['snore'], - 0x44: ['conversion2'], - 0x45: ['lockon'], - 0x46: ['sketch'], - 0x47: ['defrostopponent'], - 0x48: ['sleeptalk'], - 0x49: ['destinybond'], - 0x4a: ['spite'], - 0x4b: ['falseswipe'], - 0x4c: ['healbell'], - 0x4d: ['kingsrock'], - 0x4e: ['triplekick'], - 0x4f: ['kickcounter'], - 0x50: ['thief'], - 0x51: ['arenatrap'], - 0x52: ['nightmare'], - 0x53: ['defrost'], - 0x54: ['curse'], - 0x55: ['protect'], - 0x56: ['spikes'], - 0x57: ['foresight'], - 0x58: ['perishsong'], - 0x59: ['startsandstorm'], - 0x5a: ['endure'], - 0x5b: ['checkcurl'], - 0x5c: ['rolloutpower'], - 0x5d: ['effect0x5d'], - 0x5e: ['furycutter'], - 0x5f: ['attract'], - 0x60: ['happinesspower'], - 0x61: ['present'], - 0x62: ['damagecalc'], - 0x63: ['frustrationpower'], - 0x64: ['safeguard'], - 0x65: ['checksafeguard'], - 0x66: ['getmagnitude'], - 0x67: ['batonpass'], - 0x68: ['pursuit'], - 0x69: ['clearhazards'], - 0x6a: ['healmorn'], - 0x6b: ['healday'], - 0x6c: ['healnite'], - 0x6d: ['hiddenpower'], - 0x6e: ['startrain'], - 0x6f: ['startsun'], - 0x70: ['attackup'], - 0x71: ['defenseup'], - 0x72: ['speedup'], - 0x73: ['specialattackup'], - 0x74: ['specialdefenseup'], - 0x75: ['accuracyup'], - 0x76: ['evasionup'], - 0x77: ['attackup2'], - 0x78: ['defenseup2'], - 0x79: ['speedup2'], - 0x7a: ['specialattackup2'], - 0x7b: ['specialdefenseup2'], - 0x7c: ['accuracyup2'], - 0x7d: ['evasionup2'], - 0x7e: ['attackdown'], - 0x7f: ['defensedown'], - 0x80: ['speeddown'], - 0x81: ['specialattackdown'], - 0x82: ['specialdefensedown'], - 0x83: ['accuracydown'], - 0x84: ['evasiondown'], - 0x85: ['attackdown2'], - 0x86: ['defensedown2'], - 0x87: ['speeddown2'], - 0x88: ['specialattackdown2'], - 0x89: ['specialdefensedown2'], - 0x8a: ['accuracydown2'], - 0x8b: ['evasiondown2'], - 0x8c: ['statmessageuser'], - 0x8d: ['statmessagetarget'], - 0x8e: ['statupfailtext'], - 0x8f: ['statdownfailtext'], - 0x90: ['effectchance'], - 0x91: ['effect0x91'], - 0x92: ['effect0x92'], - 0x93: ['switchturn'], - 0x94: ['fakeout'], - 0x95: ['bellydrum'], - 0x96: ['psychup'], - 0x97: ['rage'], - 0x98: ['doubleflyingdamage'], - 0x99: ['doubleundergrounddamage'], - 0x9a: ['mirrorcoat'], - 0x9b: ['checkfuturesight'], - 0x9c: ['futuresight'], - 0x9d: ['doubleminimizedamage'], - 0x9e: ['skipsuncharge'], - 0x9f: ['thunderaccuracy'], - 0xa0: ['teleport'], - 0xa1: ['beatup'], - 0xa2: ['ragedamage'], - 0xa3: ['effect0xa3'], - 0xa4: ['allstatsup'], - 0xa5: ['effect0xa5'], - 0xa6: ['effect0xa6'], - 0xa7: ['effect0xa7'], - 0xa8: ['effect0xa8'], - 0xa9: ['clearmissdamage'], - 0xaa: ['wait'], - 0xab: ['hittarget'], - 0xac: ['tristatuschance'], - 0xad: ['supereffectivelooptext'], - 0xae: ['startloop'], - 0xaf: ['curl'], - 0xfe: ['endturn'], - 0xff: ['endmove'], -} - -effect_command_enders = [0xFF,] - -def create_effect_command_classes(debug=False): - klasses = [] - for (byte, cmd) in effect_commands.items(): - cmd_name = cmd[0].replace(" ", "_") - params = { - "id": byte, - "size": 1, - "end": byte in effect_command_enders, - "macro_name": cmd_name - } - params["param_types"] = {} - if len(cmd) > 1: - param_types = cmd[1:] - for (i, each) in enumerate(param_types): - thing = {"name": each[0], "class": each[1]} - params["param_types"][i] = thing - if debug: - print "each is: " + str(each) - print "thing[class] is: " + str(thing["class"]) - params["size"] += thing["class"].size - klass_name = cmd_name+"Command" - klass = classobj(klass_name, (Command,), params) - globals()[klass_name] = klass - klasses.append(klass) - # later an individual klass will be instantiated to handle something - return klasses - -effect_classes = create_effect_command_classes() - - - -def generate_macros(filename="../script_macros.asm"): - """generates all macros based on commands - this is dumped into script_macros.asm""" - output = "; This file is generated by generate_macros.\n" - for command in command_classes: - output += "\n" - #if command.macro_name[0].isdigit(): - # output += "_" - output += command.macro_name + ": MACRO\n" - output += spacing + "db $%.2x\n"%(command.id) - current_param = 1 - for (index, each) in command.param_types.items(): - if issubclass(each["class"], SingleByteParam): - output += spacing + "db \\" + str(current_param) + "\n" - elif issubclass(each["class"], MultiByteParam): - output += spacing + "dw \\" + str(current_param) + "\n" - current_param += 1 - output += spacing + "ENDM\n" - - fh = open(filename, "w") - fh.write(output) - fh.close() - - return output - -# use this to keep track of commands without pksv names -pksv_no_names = {} -def pretty_print_pksv_no_names(): - """just some nice debugging output - use this to keep track of commands without pksv names - pksv_no_names is created in parse_script_engine_script_at""" - for (command_byte, addresses) in pksv_no_names.items(): - if command_byte in pksv_crystal_unknowns: continue - print hex(command_byte) + " appearing in these scripts: " - for address in addresses: - print " " + hex(address) - -recursive_scripts = set([]) -def rec_parse_script_engine_script_at(address, origin=None, debug=True): - """this is called in parse_script_engine_script_at for recursion - when this works it should be flipped back to using the regular - parser.""" - recursive_scripts.add((address, origin)) - return parse_script_engine_script_at(address, origin=origin, debug=debug) - -def find_broken_recursive_scripts(output=False, debug=True): - """well.. these at least have a chance of maybe being broken?""" - for r in list(recursive_scripts): - script = {} - length = "not counted here" - if is_script_already_parsed_at(r[0]): - script = script_parse_table[r[0]] - length = str(len(script)) - if len(script) > 20 or script == {}: - print "******************* begin" - print "script at " + hex(r[0]) + " from main script " + hex(r[1]) + " with length: " + length - if output: - parse_script_engine_script_at(r[0], force=True, debug=True) - print "==================== end" - - -stop_points = [0x1aafa2, - 0x9f58f, # battle tower - 0x9f62f, # battle tower - ] -class Script: - base_label = "UnknownScript_" - def __init__(self, *args, **kwargs): - self.address = None - self.commands = None - if len(kwargs) == 0 and len(args) == 0: - raise Exception("Script.__init__ must be given some arguments") - # first positional argument is address - if len(args) == 1: - address = args[0] - if type(address) == str: - address = int(address, 16) - elif type(address) != int: - raise Exception("address must be an integer or string") - self.address = address - elif len(args) > 1: - raise Exception("don't know what to do with second (or later) positional arguments") - self.dependencies = None - if "label" in kwargs.keys(): - label = kwargs["label"] - else: - label = None - if not label: - label = self.base_label + hex(self.address) - self.label = Label(name=label, address=address, object=self) - if "map_group" in kwargs.keys(): - self.map_group = kwargs["map_group"] - if "map_id" in kwargs.keys(): - self.map_id = kwargs["map_id"] - if "parent" in kwargs.keys(): - self.parent = kwargs["parent"] - # parse the script at the address - if "use_old_parse" in kwargs.keys() and kwargs["use_old_parse"] == True: - self.old_parse(**kwargs) - else: - self.parse(self.address, **kwargs) - - def pksv_list(self): - """shows a list of pksv names for each command in the script""" - items = [] - if type(self.commands) == dict: - for (id, command) in self.commands.items(): - if command["type"] in pksv_crystal: - items.append(pksv_crystal[command["type"]]) - else: - items.append(hex(command["type"])) - else: - for command in self.commands: - items.append(command.macro_name) - return items - - - def to_pksv(self): - """returns a string of pksv command names""" - pksv = self.pksv_list() - output = "script starting at: "+hex(self.address)+" .. " - first = True - for item in pksv: - item = str(item) - if first: - output += item - first = False - else: - output += ", "+item - return output - - def show_pksv(self): - """prints a list of pksv command names in this script""" - print self.to_pksv() - - def parse(self, start_address, force=False, map_group=None, map_id=None, force_top=True, origin=True, debug=False): - """parses a script using the Command classes - as an alternative to the old method using hard-coded commands - - force_top just means 'force the main script to get parsed, but not any subscripts' - """ - global command_classes, rom, script_parse_table - current_address = start_address - if debug: print "Script.parse address="+hex(self.address) +" map_group="+str(map_group)+" map_id="+str(map_id) - if start_address in stop_points and force == False: - if debug: print "script parsing is stopping at stop_point=" + hex(start_address) + " at map_group="+str(map_group)+" map_id="+str(map_id) - return None - if start_address < 0x4000 and start_address not in [0x26ef, 0x114, 0x1108]: - if debug: print "address is less than 0x4000.. address is: " + hex(start_address) - sys.exit(1) - if is_script_already_parsed_at(start_address) and not force and not force_top: - raise Exception("this script has already been parsed before, please use that instance ("+hex(start_address)+")") - - # load up the rom if it hasn't been loaded already - load_rom() - - # in the event that the script parsing fails.. it would be nice to leave evidence - script_parse_table[start_address:start_address+1] = "incomplete parse_script_with_command_classes" - - # start with a blank script - commands = [] - - # use this to control the while loop - end = False - - # for each command found.. - while not end: - # get the current scripting byte - cur_byte = ord(rom[current_address]) - - # reset the command class (last command was probably different) - scripting_command_class = None - - # match the command id byte to a scripting command class like GivePoke - for class_ in command_classes: - if class_.id == cur_byte: - scripting_command_class = class_ - - # no matching command found (not implemented yet)- just end this script - # NOTE: might be better to raise an exception and end the program? - if scripting_command_class == None: - if debug: print "parsing script; current_address is: " + hex(current_address) - current_address += 1 - asm_output = "\n".join([command.to_asm() for command in commands]) - end = True - continue - # maybe the program should exit with failure instead? - #raise Exception("no command found? id: " + hex(cur_byte) + " at " + hex(current_address) + " asm is:\n" + asm_output) - - # create an instance of the command class and let it parse its parameter bytes - #print "about to parse command(script@"+hex(start_address)+"): " + str(scripting_command_class.macro_name) - cls = scripting_command_class(address=current_address, force=force, map_group=map_group, map_id=map_id, parent=self) - - #if self.debug: - # print cls.to_asm() - - # store it in this script object - commands.append(cls) - - # certain commands will end the scripting engine - end = cls.end - - # skip past the command's parameter bytes to go to the next command - #current_address = cls.last_address + 1 - current_address += cls.size - - # last byte belonging to script is last byte of last command, - # or the last byte of the last command's last parameter - self.last_address = current_address - - # store the script in the global table/map thing - script_parse_table[start_address:current_address] = self - - asm_output = "\n".join([command.to_asm() for command in commands]) - if debug: print "--------------\n"+asm_output - - # store the script - self.commands = commands - - return commands - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - dependencies = [] - for command in self.commands: - deps = command.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - dependencies.extend(deps) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - asm_output = "".join([command.to_asm()+"\n" for command in self.commands]) - if asm_output[-1] == "\n": - asm_output = asm_output[:-1] - return asm_output - - def old_parse(self, *args, **kwargs): - """included from old_parse_scripts""" -from old_parse_scripts import old_parse -Script.old_parse = old_parse - -def parse_script_engine_script_at(address, map_group=None, map_id=None, force=False, debug=True, origin=True): - if is_script_already_parsed_at(address) and not force: - return script_parse_table[address] - return Script(address, map_group=map_group, map_id=map_id, force=force, debug=debug, origin=origin) - -def compare_script_parsing_methods(address): - """ - compares the parsed scripts using the new method and the old method - The new method is Script.parse, the old method is Script.old_parse. - - There are likely to be problems with the new script parser, the one - that uses the command classes to parse bytes. To look for these - problems, you can compare the output of one parsing method to the - output of the other. When there's a difference, there is something - worth correcting. Probably by each command's "macro_name" attribute. - """ - load_rom() - separator = "################ compare_script_parsing_methods" - # first do it the old way - print separator - print "parsing the script at " + hex(address) + " using the old method" - oldscript = Script(address, debug=True, force=True, origin=True, use_old_parse=True) - # and now the old way - print separator - print "parsing the script at " + hex(address) + " using the new method" - newscript = Script(address, debug=True, force=True, origin=True) - # let the comparison begin.. - errors = 0 - print separator + " COMPARISON RESULTS" - if not len(oldscript.commands.keys()) == len(newscript.commands): - print "the two scripts don't have the same number of commands" - errors += 1 - for (id, oldcommand) in oldscript.commands.items(): - newcommand = newscript.commands[id] - oldcommand_pksv_name = pksv_crystal[oldcommand["type"]].replace(" ", "_") - if oldcommand["start_address"] != newcommand.address: - print "the two addresses (command id="+str(id)+") do not match old="+hex(oldcommand["start_address"]) + " new="+hex(newcommand.address) - errors += 1 - if oldcommand_pksv_name != newcommand.macro_name: - print "the two commands (id="+str(id)+") do not have the same name old="+oldcommand_pksv_name+" new="+newcommand.macro_name - errors += 1 - print "total comparison errors: " + str(errors) - return oldscript, newscript - - -class Warp(Command): - """only used outside of scripts""" - size = warp_byte_size - macro_name = "warp_def" - param_types = { - 0: {"name": "y", "class": HexByte}, - 1: {"name": "x", "class": HexByte}, - 2: {"name": "warp_to", "class": DecimalParam}, - 3: {"name": "map_bank", "class": MapGroupParam}, - 4: {"name": "map_id", "class": MapIdParam}, - } - override_byte_check = True - - def __init__(self, *args, **kwargs): - self.id = kwargs["id"] - script_parse_table[kwargs["address"] : kwargs["address"] + self.size] = self - Command.__init__(self, *args, **kwargs) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - -all_warps = [] -def parse_warps(address, warp_count, bank=None, map_group=None, map_id=None, debug=True): - warps = [] - current_address = address - for each in range(warp_count): - warp = Warp(address=current_address, id=each, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - current_address += warp_byte_size - warps.append(warp) - all_warps.extend(warps) - return warps - -def old_parse_warp_bytes(some_bytes, debug=True): - """parse some number of warps from the data""" - assert len(some_bytes) % warp_byte_size == 0, "wrong number of bytes" - warps = [] - for bytes in grouper(some_bytes, count=warp_byte_size): - y = int(bytes[0], 16) - x = int(bytes[1], 16) - warp_to = int(bytes[2], 16) - map_group = int(bytes[3], 16) - map_id = int(bytes[4], 16) - warps.append({ - "y": y, - "x": x, - "warp_to": warp_to, - "map_group": map_group, - "map_id": map_id, - }) - return warps - -class XYTrigger(Command): - size = trigger_byte_size - macro_name = "xy_trigger" - param_types = { - 0: {"name": "number", "class": DecimalParam}, - 1: {"name": "y", "class": HexByte}, - 2: {"name": "x", "class": HexByte}, - 3: {"name": "unknown1", "class": SingleByteParam}, - 4: {"name": "script", "class": ScriptPointerLabelParam}, - 5: {"name": "unknown2", "class": SingleByteParam}, - 6: {"name": "unknown3", "class": SingleByteParam}, - } - override_byte_check = True - - def __init__(self, *args, **kwargs): - self.id = kwargs["id"] - self.dependencies = None - Command.__init__(self, *args, **kwargs) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - dependencies = [] - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - thing = script_parse_table[self.params[4].parsed_address] - if thing and thing != self.params[4]: - dependencies.append(thing) - global_dependencies.add(thing) - self.dependencies = dependencies - return dependencies - -all_xy_triggers = [] -def parse_xy_triggers(address, trigger_count, bank=None, map_group=None, map_id=None, debug=True): - xy_triggers = [] - current_address = address - for each in range(trigger_count): - xy_trigger = XYTrigger(address=current_address, id=each, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - current_address += trigger_byte_size - xy_triggers.append(xy_trigger) - all_xy_triggers.extend(xy_triggers) - return xy_triggers - -def old_parse_xy_trigger_bytes(some_bytes, bank=None, map_group=None, map_id=None, debug=True): - """parse some number of triggers from the data""" - assert len(some_bytes) % trigger_byte_size == 0, "wrong number of bytes" - triggers = [] - for bytes in grouper(some_bytes, count=trigger_byte_size): - trigger_number = int(bytes[0], 16) - y = int(bytes[1], 16) - x = int(bytes[2], 16) - unknown1 = int(bytes[3], 16) # XXX probably 00? - script_ptr_byte1 = int(bytes[4], 16) - script_ptr_byte2 = int(bytes[5], 16) - script_ptr = script_ptr_byte1 + (script_ptr_byte2 << 8) - script_address = None - script = None - if bank: - script_address = calculate_pointer(script_ptr, bank) - print "******* parsing xy trigger byte scripts... x=" + str(x) + " y=" + str(y) - script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id) - - triggers.append({ - "trigger_number": trigger_number, - "y": y, - "x": x, - "unknown1": unknown1, # probably 00 - "script_ptr": script_ptr, - "script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2}, - "script_address": script_address, - "script": script, - }) - return triggers - - -class ItemFragment(Command): - """used by ItemFragmentParam and PeopleEvent - (for items placed on a map)""" - size = 2 - macro_name = "db" - base_label = "ItemFragment_" - override_byte_check = True - param_types = { - 0: {"name": "item", "class": ItemLabelByte}, - 1: {"name": "quantity", "class": DecimalParam}, - } - - def __init__(self, address=None, bank=None, map_group=None, map_id=None, debug=False, label=None): - assert is_valid_address(address), "PeopleEvent must be given a valid address" - self.address = address - self.last_address = address + self.size - self.bank = bank - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.params = {} - self.dependencies = None - self.args = {"debug": debug, "map_group": map_group, "map_id": map_id, "bank": bank} - script_parse_table[self.address : self.last_address] = self - self.parse() - - -class ItemFragmentParam(PointerLabelParam): - """used by PeopleEvent""" - - def parse(self): - PointerLabelParam.parse(self) - - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - self.calculated_address = address - - itemfrag = ItemFragment(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.itemfrag = itemfrag - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - self.dependencies = [self.itemfrag].extend(self.itemfrag.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - global_dependencies.add(self.itemfrag) - return self.dependencies - -trainer_group_maximums = {} -class TrainerFragment(Command): - """used by TrainerFragmentParam and PeopleEvent for trainer data - - Maybe this shouldn't be a Command. The output might sprawl - over multiple lines, and maybe it should be commented in to_asm? - - [Bit no. (2byte)][Trainer group][Trainer] - [2byte pointer to Text when seen] - [2byte pointer to text when trainer beaten] - [2byte pointer to script when lost (0000=Blackout)] - [2byte pointer to script if won/talked to again] - - The bit number tell the game later on if the trainer has been - beaten already (bit = 1) or not (bit = 0). All Bit number of BitTable1. - - 03 = Nothing - 04 = Nothing - 05 = Nothing - 06 = Nothing - """ - size = 12 - macro_name = "trainer_def" - base_label = "Trainer_" - override_byte_check = True - param_types = { - 0: {"name": "bit_number", "class": MultiByteParam}, - 1: {"name": "trainer_group", "class": TrainerGroupParam}, - 2: {"name": "trainer_id", "class": TrainerIdParam}, - 3: {"name": "text_when_seen", "class": TextPointerLabelParam}, - 4: {"name": "text_when_trainer_beaten", "class": TextPointerLabelParam}, - 5: {"name": "script_when_lost", "class": ScriptPointerLabelParam}, - 6: {"name": "script_talk_again", "class": ScriptPointerLabelParam}, - } - - def __init__(self, *args, **kwargs): - address = kwargs["address"] - print "TrainerFragment address=" + hex(address) - self.address = address - self.last_address = self.address + self.size - if not is_valid_address(address) or address in [0x26ef]: - self.include_in_asm = False - return - script_parse_table[self.address : self.last_address] = self - self.dependencies = None - Command.__init__(self, *args, **kwargs) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - deps = [] - if not is_valid_address(self.address): - return deps - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - #deps.append(self.params[3]) - deps.extend(self.params[3].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - #deps.append(self.params[4]) - deps.extend(self.params[4].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - #deps.append(self.params[5]) - deps.extend(self.params[5].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - #deps.append(self.params[6]) - deps.extend(self.params[6].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = deps - return deps - - def parse(self): - Command.parse(self) - - # get the trainer group id - trainer_group = self.params[1].byte - - # get the trainer id - trainer_id = self.params[2].byte - - if not trainer_group in trainer_group_maximums.keys(): - trainer_group_maximums[trainer_group] = set([trainer_id]) - else: - trainer_group_maximums[trainer_group].add(trainer_id) - - # give this object a possibly better label - label = "Trainer" - if ("uses_numeric_trainer_ids" in trainer_group_names[trainer_group].keys()) \ - or ("trainer_names" not in trainer_group_names[trainer_group].keys()): - label += string.capwords(trainer_group_names[trainer_group]["constant"]) - if "trainer_names" in trainer_group_names[trainer_group].keys() \ - and len(trainer_group_names[trainer_group]["trainer_names"]) > 1: - label += str(trainer_id) - else: - label += string.capwords(trainer_group_names[trainer_group]["constant"]) + \ - string.capwords(trainer_group_names[trainer_group]["trainer_names"][trainer_id-1]) - - label = label.replace("Gruntm", "GruntM").replace("Gruntf", "GruntF").replace("Lt_surge", "LtSurge") - - self.label = Label(name=label, address=self.address, object=self) - - # ---- give better labels to the objects created by TrainerFragment ---- - - text_when_seen_text = script_parse_table[self.params[3].parsed_address] - if text_when_seen_text != None: - text_when_seen_label = Label(name=label + "WhenSeenText", address=text_when_seen_text.address, object=text_when_seen_text) - text_when_seen_text.label = text_when_seen_label - - text_when_beaten_text = script_parse_table[self.params[4].parsed_address] - if text_when_beaten_text != None: - text_when_beaten_label = Label(name=label + "WhenBeatenText", address=text_when_beaten_text.address, object=text_when_beaten_text) - text_when_beaten_text.label = text_when_beaten_label - - script_when_lost = script_parse_table[self.params[5].parsed_address] - if script_when_lost != None: - script_when_lost_label = Label(name=label + "WhenLostScript", address=script_when_lost.address, object=script_when_lost) - script_when_lost.label = script_when_lost_label - - script_talk_again = script_parse_table[self.params[6].parsed_address] - if script_talk_again != None: - script_talk_again_label = Label(name=label + "WhenTalkScript", address=script_talk_again.address, object=script_talk_again) - script_talk_again.label = script_talk_again_label - - def to_asm(self): - xspacing = "" - output = "" - output += xspacing + "; bit/flag number\n" - output += xspacing + "dw $%.2x"%(self.params[0].parsed_number) - output += "\n\n"+xspacing+"; trainer group && trainer id\n" - output += xspacing + "db %s, %s" % (self.params[1].to_asm(), self.params[2].to_asm()) - output += "\n\n"+xspacing+"; text when seen\n" - output += xspacing + "dw " + self.params[3].to_asm() - output += "\n\n"+xspacing+"; text when trainer beaten\n" - output += xspacing + "dw " + self.params[4].to_asm() - output += "\n\n"+xspacing+"; script when lost\n" - output += xspacing + "dw " + self.params[5].to_asm() - output += "\n\n"+xspacing+"; script when talk again\n" - output += xspacing + "dw " + self.params[6].to_asm() - return output - -class TrainerFragmentParam(PointerLabelParam): - """used by PeopleEvent to point to trainer data""" - def parse(self): - address = calculate_pointer_from_bytes_at(self.address, bank=self.bank) - self.calculated_address = address - if address == 0x26ef: - self.trainerfrag = None - else: - trainerfrag = TrainerFragment(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.trainerfrag = trainerfrag - PointerLabelParam.parse(self) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - deps = [] - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - if self.trainerfrag: - global_dependencies.add(self.trainerfrag) - deps.append(self.trainerfrag) - deps.extend(self.trainerfrag.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = deps - return deps - -trainer_group_table = None -class TrainerGroupTable: - """ - A list of pointers. - - This should probably be called TrainerGroupPointerTable. - """ - - def __init__(self): - assert 0x43 in trainer_group_maximums.keys(), "TrainerGroupTable should onyl be created after all the trainers have been found" - self.address = trainer_group_pointer_table_address - self.bank = calculate_bank(trainer_group_pointer_table_address) - self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self) - self.size = None - self.last_address = None - self.dependencies = None - self.headers = [] - self.parse() - - script_parse_table[self.address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - global_dependencies.update(self.headers) - if recompute == True and self.dependencies != None and self.dependencies != []: - return self.dependencies - dependencies = copy(self.headers) - for header in self.headers: - dependencies.extend(header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - return dependencies - - def parse(self): - size = 0 - for (key, kvalue) in trainer_group_names.items(): - # calculate the location of this trainer group header from its pointer - pointer_bytes_location = kvalue["pointer_address"] - parsed_address = calculate_pointer_from_bytes_at(pointer_bytes_location, bank=self.bank) - trainer_group_names[key]["parsed_address"] = parsed_address - - # parse the trainer group header at this location - name = kvalue["name"] - trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name) - trainer_group_names[key]["header"] = trainer_group_header - self.headers.append(trainer_group_header) - - # keep track of the size of this pointer table - size += 2 - self.size = size - self.last_address = self.address + self.size - - def to_asm(self): - output = "".join([str("dw "+get_label_for(header.address)+"\n") for header in self.headers]) - return output - -class TrainerGroupHeader: - """ - A trainer group header is a repeating list of individual trainer headers. - - <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF> - - Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers. - Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers. - """ - - def __init__(self, address=None, group_id=None, group_name=None): - assert address!=None, "TrainerGroupHeader requires an address" - assert group_id!=None, "TrainerGroupHeader requires a group_id" - assert group_name!=None, "TrainerGroupHeader requires a group_name" - - self.address = address - self.group_id = group_id - self.group_name = group_name - self.dependencies = None - self.individual_trainer_headers = [] - self.label = Label(name=group_name+"TrainerGroupHeader", address=self.address, object=self) - self.parse() - - script_parse_table[address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - """ - TrainerGroupHeader has no dependencies. - """ - # TODO: possibly include self.individual_trainer_headers - if recompute or self.dependencies == None: - self.dependencies = [] - return self.dependencies - - def parse(self): - """ - how do i know when there's no more data for this header? - do a global analysis of the rom and figure out the max ids - this wont work for rom hacks of course - see find_trainer_ids_from_scripts - """ - size = 0 - current_address = self.address - - if self.group_id not in trainer_group_maximums.keys(): - self.size = 0 - self.last_address = current_address - return - - # create an IndividualTrainerHeader for each id in range(min id, max id + 1) - min_id = min(trainer_group_maximums[self.group_id]) - max_id = max(trainer_group_maximums[self.group_id]) - - if self.group_id == 0x0C: - # CAL appears a third time with third-stage evos (meganium, typhlosion, feraligatr) - max_id += 1 - elif self.group_id == 0x29: - # there's a missing supernerd :( - max_id += 1 - elif self.group_id == 0x2D: - # missing bikers - max_id += 2 - elif self.group_id == 0x31: - # missing jugglers - max_id += 3 - elif self.group_id == 0x32: - # blackbelt wai - max_id += 1 - elif self.group_id == 0x3C: - # kimono girl miki - max_id += 1 - elif self.group_id == 0x3D: - # twins lea & pia - max_id += 1 - - for trainer_id in range(min_id, max_id+1): - trainer_header = TrainerHeader(address=current_address, trainer_group_id=self.group_id, trainer_id=trainer_id, parent=self) - self.individual_trainer_headers.append(trainer_header) - # current_address += trainer_header.size - current_address = trainer_header.last_address - size += trainer_header.size - - self.last_address = current_address - self.size = size - - def to_asm(self): - output = "\n\n".join(["; "+header.make_constant_name()+" ("+str(header.trainer_id)+") at "+hex(header.address)+"\n"+header.to_asm() for header in self.individual_trainer_headers]) - return output - -class TrainerHeader: - """ - <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF> - - Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers. - Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. Used by a few Cooltrainers. - """ - - def __init__(self, address=None, trainer_group_id=None, trainer_id=None, parent=None): - self.parent = parent - self.address = address - self.trainer_group_id = trainer_group_id - self.trainer_id = trainer_id - self.dependencies = [] - self.size = None - self.last_address = None - self.parse() - self.label = Label(name=self.make_name(), address=self.address, object=self) - # this shouldn't be added to script_parse_table because - # TrainerGroupHeader covers its address range - - def make_name(self): - """ - Must occur after parse() is called. - Constructs a name based on self.parent.group_name and self.name. - """ - if self.trainer_group_id in [0x14, 0x16, 0x17, 0x18, 0x19, 0x1B, 0x1C, 0x1D, 0x1E, 0x20, 0x21, 0x22, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2B, 0x2C, 0x2D, 0x2F, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x41]: - return self.parent.group_name.upper() + "_" + self.name[:-1] - else: - return self.parent.group_name + "_" + str(self.trainer_id) - - def make_constant_name(self): - if hasattr(self, "seed_constant_name"): - seed = self.seed_constant_name - else: - seed = self.name - - if "?" in seed: - if seed[-2].isdigit(): - x = 2 - else: - x = 1 - seed = trainer_group_names[self.trainer_group_id]["name"]+"_"+seed[-x:] - elif self.trainer_group_id == 0x1f and "EXECUTIVE" in seed: - seed = "GRUNT_"+seed - elif self.trainer_group_id == 0x2d and "BENNY" in seed.upper(): - seed = "BIKER_BENNY" - elif self.trainer_group_id == 0x24 and "BENNY" in seed.upper(): - seed = "BUG_CATCHER_BENNY" - - return string.capwords(seed).\ - replace("@", "").\ - replace(" & ", "AND").\ - replace(" ", "").\ - replace(".", "_").\ - upper() - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if recompute or self.dependencies == None: - self.dependencies = [] - return self.dependencies - - def parse(self): - address = self.address - - # figure out how many bytes until 0x50 "@" - jump = how_many_until(chr(0x50), address) - - # parse the "@" into the name - self.name = parse_text_at(address, jump+1) - - # where is the next byte? - current_address = address + jump + 1 - - # figure out the pokemon data type - self.data_type = ord(rom[current_address]) - - current_address += 1 - - # figure out which partymon parser to use for this trainer header - party_mon_parser = None - for monparser in trainer_party_mon_parsers: - if monparser.id == self.data_type: - party_mon_parser = monparser - break - - if party_mon_parser == None: - raise Exception("no trainer party mon parser found to parse data type " + hex(self.data_type)) - - self.party_mons = party_mon_parser(address=current_address, group_id=self.trainer_group_id, trainer_id=self.trainer_id, parent=self) - - # let's have everything in trainer_party_mon_parsers handle the last $FF - #self.size = self.party_mons.size + 1 + len(self.name) - self.size = self.party_mons.last_address - self.address - self.last_address = self.party_mons.last_address - - def to_asm(self): - output = "db \""+self.name+"\"\n" - output += "db $%.2x ; data type\n" % (self.data_type) - output += self.party_mons.to_asm() - output += "\n; last_address="+hex(self.last_address)+" size="+str(self.size) - return output - -class TrainerPartyMonParser: - """ - Just a generic trainer party mon parser. - Don't use this directly. Only use the child classes. - """ - id = None - dependencies = None - param_types = None - - # could go either way on this one.. TrainerGroupHeader.parse would need to be changed - # so as to not increase current_address by one after reading "data_type" - override_byte_check = True - - def __init__(self, address=None, group_id=None, trainer_id=None, parent=None): - self.address = address - self.group_id = group_id - self.trainer_id = trainer_id - self.parent = parent - self.args = {} - self.mons = {} - self.parse() - - # pick up the $FF at the end - self.last_address += 1 - - def parse(self): - current_address = self.address - pkmn = 0 - continuer = True - while continuer: - self.mons[pkmn] = {} - i = 0 - for (key, param_type) in self.param_types.items(): - name = param_type["name"] - klass = param_type["class"] - # make an instance of this class, like SingleByteParam() - # or ItemLabelByte.. by making an instance, obj.parse() is called - obj = klass(address=current_address, name=name, parent=self, **dict([(k,v) for (k, v) in self.args.items() if k not in ["parent"]])) - # save this for later - self.mons[pkmn][i] = obj - # increment our counters - current_address += obj.size - i += 1 - pkmn += 1 - if ord(rom[current_address]) == 0xFF: - break - self.last_address = current_address - return True - - def to_asm(self): - output = "" - #output = "; " + ", ".join([param_type["name"] for (key, param_type) in self.param_types.items()]) + "\n" - for mon in self.mons: - output += "db " + ", ".join([param.to_asm() for (name, param) in self.mons[mon].items()]) - output += "\n" - output += "db $ff ; end trainer party mons" - return output - -class TrainerPartyMonParser0(TrainerPartyMonParser): - """ - Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers. - """ - id = 0 - size = 2 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - } -class TrainerPartyMonParser1(TrainerPartyMonParser): - """ - Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> - <Move4>. Used often for Gym Leaders. - """ - id = 1 - size = 6 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "move1", "class": MoveParam}, - 3: {"name": "move2", "class": MoveParam}, - 4: {"name": "move3", "class": MoveParam}, - 5: {"name": "move4", "class": MoveParam}, - } -class TrainerPartyMonParser2(TrainerPartyMonParser): - """ - Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans. - """ - id = 2 - size = 3 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "item", "class": ItemLabelByte}, - } -class TrainerPartyMonParser3(TrainerPartyMonParser): - """ - Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> - <Move2> <Move3> <Move4>. - Used by a few Cooltrainers. - """ - id = 3 - size = 7 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "item", "class": ItemLabelByte}, - 3: {"name": "move1", "class": MoveParam}, - 4: {"name": "move2", "class": MoveParam}, - 5: {"name": "move3", "class": MoveParam}, - 6: {"name": "move4", "class": MoveParam}, - } - -trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3] - -def find_trainer_ids_from_scripts(): - """ - Looks through all scripts to find trainer group numbers and trainer numbers. - - This can be used with trainer_group_maximums to figure out the current number of - trainers in each of the originating trainer groups. - """ - total_unreferenced_trainers = 0 - - # look at each possibly relevant script - for item in script_parse_table.items(): - object = item[1] - if isinstance(object, Script): - check_script_has_trainer_data(object) - - # make a set of each list of trainer ids to avoid dupes - # this will be used later in TrainerGroupTable - for item in trainer_group_maximums.items(): - key = item[0] - value = set(item[1]) - trainer_group_maximums[key] = value - -def report_unreferenced_trainer_ids(): - """ - Reports on the number of unreferenced trainer ids in each group. - - This should be called after find_trainer_ids_from_scripts. - - These are trainer groups with "unused" trainer ids. The - "find_trainer_ids_from_scripts" function analyzes each script in the game, - and each map header in the game (because of code in TrainerFragment), and - finds all references to trainers. But, if there are any trainers that are - referenced in raw ASM, this method does not detect them. Each instance of a - trainer reference is added to a global table called - "trainer_group_maximums". Next, "find_trainer_ids_from_scripts" looks at - the trainer IDs referenced for each group and takes the minimum number and - the maximum number. To find whether or not there are any unused trainers, - it takes the minimum and maximum ids and then sees which intermediate - numbers are missing from the list of "referenced" trainer ids. - """ - for item in trainer_group_maximums.items(): - key = item[0] - value = item[1] - - # i'm curious: are there any missing trainer ids in this group? - min_id = min(value) - max_id = max(value) - expectables = range(min_id, max_id+1) - - unreferenced = set() - - for expectable in expectables: - if not expectable in value: - unreferenced.add(expectable) - - if len(unreferenced) > 0: - total_unreferenced_trainers += len(unreferenced) - output = "trainer group "+hex(key)+" (\""+trainer_group_names[key]["name"]+"\")" - output += " (min="+str(min_id)+", max="+str(max_id)+")" - output += " has "+str(len(unreferenced))+" unreferenced trainer ids" - output += ": " + str(unreferenced) - print output - print "total unreferenced trainers: " + str(total_unreferenced_trainers) - -def check_script_has_trainer_data(script): - """ - see find_trainer_ids_from_scripts - """ - for command in script.commands: - trainer_group = None - trainer_id = None - - if command.id == 0x43: - trainer_group = command.params[0].byte - trainer_id = command.params[1].byte - elif command.id == 0x5E: - trainer_group = command.params[0].byte - trainer_id = command.params[1].byte - - if trainer_group != None and trainer_id != None: - if trainer_group in trainer_group_maximums.keys(): - trainer_group_maximums[trainer_group].add(trainer_id) - else: - trainer_group_maximums[trainer_group] = set([trainer_id]) - -def trainer_name_from_group(group_id, trainer_id=0): - """This doesn't actually work for trainer_id > 0.""" - bank = calculate_bank(0x39999) - ptr_address = 0x39999 + ((group_id - 1)*2) - address = calculate_pointer_from_bytes_at(ptr_address, bank=bank) - text = parse_text_at2(address, how_many_until(chr(0x50), address)) - return text - -def trainer_group_report(): - """ - Reports how many trainer ids are used in each trainer group. - """ - output = "" - total = 0 - for trainer_group_id in trainer_group_maximums.keys(): - group_name = trainer_group_names[trainer_group_id]["name"] - first_name = trainer_name_from_group(trainer_group_id).replace("\n", "") - trainers = len(trainer_group_maximums[trainer_group_id]) - total += trainers - output += "group "+hex(trainer_group_id)+":\n" - output += "\tname: "+group_name+"\n" - output += "\tfirst: "+first_name+"\n" - output += "\ttrainer count:\t"+str(trainers)+"\n\n" - output += "total trainers: " + str(total) - return output - -def make_trainer_group_name_trainer_ids(trainer_group_table, debug=True): - """ - Edits trainer_group_names and sets the trainer names. - For instance, "AMY & MAY" becomes "AMY_AND_MAY1" and "AMY_AND_MAY2" - - This should only be used after TrainerGroupTable.parse has been called. - """ - assert trainer_group_table != None, "TrainerGroupTable must be called before setting the trainer names" - - if debug: - print "starting to make trainer names and give ids to repeated trainer names" - - i = 1 - for header in trainer_group_table.headers: - trainer_names = [] # (name, trainer_header) - dupes = set() - group_id = i - group_name = header.group_name - for trainer_header in header.individual_trainer_headers: - if trainer_header.name in [x[0] for x in trainer_names]: - dupes.add(trainer_header.name) - trainer_names.append([trainer_header.name, trainer_header]) - - # now fix trainers with duplicate names by appending an id - if len(dupes) > 0: - for dupe in dupes: - culprits = [trainer_header for trainer_header in header.individual_trainer_headers if trainer_header.name == dupe] - for (id, culprit) in enumerate(culprits): - culprit.seed_constant_name = culprit.name.replace("@", "") + str(id+1) - culprit.constant_name = culprit.make_constant_name() - - # now add the trainer names to trainer_group_names - trainer_group_names[i]["trainer_names"] = [theader.make_constant_name() for theader in header.individual_trainer_headers] - - i += 1 - - if debug: - print "done improving trainer names" - -def pretty_print_trainer_id_constants(): - """ - Prints out some constants for trainer ids, for "constants.asm". - - make_trainer_group_name_trainer_ids must be called prior to this. - """ - assert trainer_group_table != None, "must make trainer_group_table first" - assert trainer_group_names != None, "must have trainer_group_names available" - assert "trainer_names" in trainer_group_names[1].keys(), "trainer_names must be set in trainer_group_names" - - output = "" - for (key, value) in trainer_group_names.items(): - if "uses_numeric_trainer_ids" in trainer_group_names[key].keys(): - continue - id = key - group = value - header = group["header"] - name = group["name"] - trainer_names = group["trainer_names"] - output += "; " + name + "\n" - for (id, name) in enumerate(trainer_names): - output += name.upper() + " EQU $%.2x"%(id+1) + "\n" - output += "\n" - return output - -class PeopleEvent(Command): - size = people_event_byte_size - macro_name = "person_event" - base_label = "PeopleEvent_" - override_byte_check = True - param_types = { - 0: {"name": "sprite", "class": HexByte}, - 1: {"name": "y from top+4", "class": DecimalParam}, - 2: {"name": "x from top+4", "class": DecimalParam}, - 3: {"name": "facing", "class": HexByte}, - 4: {"name": "movement", "class": HexByte}, - 5: {"name": "clock_hour", "class": DecimalParam}, - 6: {"name": "clock_daytime", "class": DecimalParam}, - 7: {"name": "color_function", "class": HexByte}, - 8: {"name": "sight_range", "class": DecimalParam}, - 9: {"name": "pointer", "class": PointerLabelParam}, # or ScriptPointerLabelParam or ItemLabelParam - 10: {"name": "BitTable1 bit number", "class": MultiByteParam}, - } - - def xto_asm(self): - output = "\n; person-event\n; picture, y, x, facing, movement, clock_hour, clock_daytime, color_function, sight_range\n" - output += "db $%.2x, %d, %d, $%.2x, $%.2x, %d, %d, $%.2x, %d\n" % (self.params[0].byte, self.params[1].byte, self.params[2].byte, self.params[3].byte, self.params[4].byte, self.params[5].byte, self.params[6].byte, self.params[7].byte, self.params[8].byte) - output += "; pointer\ndw %s\n" % (self.params[9].to_asm()) - output += "; BitTable1 bit number\ndw %s" % (self.params[10].to_asm()) - return output - - def __init__(self, address, id, bank=None, map_group=None, map_id=None, debug=False, label=None, force=False): - assert is_valid_address(address), "PeopleEvent must be given a valid address" - self.address = address - self.last_address = address + people_event_byte_size - self.id = id - self.bank = bank - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.force = force - self.params = {} - self.dependencies = None - # PeopleEvent should probably not be in the global script_parse_table - #script_parse_table[self.address : self.last_address] = self - self.parse() - - def parse(self): - address = self.address - bank = self.bank - - color_function_byte = None - lower_bits = None - higher_bits = None - is_regular_script = None - is_give_item = None - is_trainer = None - - self.params = {} - current_address = self.address - i = 0 - self.size = 1 - color_function_byte = None - for (key, param_type) in self.param_types.items(): - if i == 9: - if is_give_item: - name = "item_fragment_pointer" - klass = ItemFragmentParam - elif is_regular_script: - name = "script_pointer" - klass = ScriptPointerLabelParam - elif is_trainer: - name = "trainer" - #klass = MultiByteParam - klass = TrainerFragmentParam - else: - name = "unknown" - klass = MultiByteParam - else: - name = param_type["name"] - klass = param_type["class"] - obj = klass(address=current_address, name=name, debug=self.debug, force=self.force, map_group=self.map_group, map_id=self.map_id, bank=self.bank) - self.params[i] = obj - if i == 7: - color_function_byte = ord(rom[current_address]) - lower_bits = color_function_byte & 0xF - higher_bits = color_function_byte >> 4 - is_regular_script = lower_bits == 00 - is_give_item = lower_bits == 01 - is_trainer = lower_bits == 02 - current_address += obj.size - self.size += obj.size - i += 1 - self.last_address = current_address - self.is_trainer = is_trainer - self.is_give_item = is_give_item - self.is_regular_script = is_regular_script - self.y = self.params[1].byte - self.x = self.params[2].byte - self.facing = self.params[3].byte - self.movement = self.params[4].byte - self.clock_hour = self.params[5].byte - self.clock_daytime = self.params[6].byte - self.color_function = self.params[7].byte - self.sight_range = self.params[8].byte - self.pointer = self.params[9].bytes - self.bit_number = self.params[10].bytes - return True - - -all_people_events = [] -def parse_people_events(address, people_event_count, bank=None, map_group=None, map_id=None, debug=False, force=False): - # people_event_byte_size - people_events = [] - current_address = address - id = 0 - for each in range(people_event_count): - pevent = PeopleEvent(address=current_address, id=id, bank=bank, map_group=map_group, map_id=map_id, debug=debug, force=force) - current_address += people_event_byte_size - people_events.append(pevent) - id += 1 - all_people_events.extend(people_events) - return people_events - -def old_parse_people_event_bytes(some_bytes, address=None, map_group=None, map_id=None, debug=True): - """parse some number of people-events from the data - see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Scripthdr - - For example, map 1.1 (group 1 map 1) has four person-events. - - 37 05 07 06 00 FF FF 00 00 02 40 FF FF - 3B 08 0C 05 01 FF FF 00 00 05 40 FF FF - 3A 07 06 06 00 FF FF A0 00 08 40 FF FF - 29 05 0B 06 00 FF FF 00 00 0B 40 FF FF - """ - assert len(some_bytes) % people_event_byte_size == 0, "wrong number of bytes" - - # address is not actually required for this function to work... - bank = None - if address: - bank = calculate_bank(address) - - people_events = [] - for bytes in grouper(some_bytes, count=people_event_byte_size): - pict = int(bytes[0], 16) - y = int(bytes[1], 16) # y from top + 4 - x = int(bytes[2], 16) # x from left + 4 - face = int(bytes[3], 16) # 0-4 for regular, 6-9 for static facing - move = int(bytes[4], 16) - clock_time_byte1 = int(bytes[5], 16) - clock_time_byte2 = int(bytes[6], 16) - color_function_byte = int(bytes[7], 16) # Color|Function - trainer_sight_range = int(bytes[8], 16) - - lower_bits = color_function_byte & 0xF - #lower_bits_high = lower_bits >> 2 - #lower_bits_low = lower_bits & 3 - higher_bits = color_function_byte >> 4 - #higher_bits_high = higher_bits >> 2 - #higher_bits_low = higher_bits & 3 - - is_regular_script = lower_bits == 00 - # pointer points to script - is_give_item = lower_bits == 01 - # pointer points to [Item no.][Amount] - is_trainer = lower_bits == 02 - # pointer points to trainer header - - # goldmap called these next two bytes "text_block" and "text_bank"? - script_pointer_byte1 = int(bytes[9], 16) - script_pointer_byte2 = int(bytes[10], 16) - script_pointer = script_pointer_byte1 + (script_pointer_byte2 << 8) - # calculate the full address by assuming it's in the current bank - # but what if it's not in the same bank? - extra_portion = {} - if bank: - ptr_address = calculate_pointer(script_pointer, bank) - if is_regular_script: - print "parsing a person-script at x=" + str(x-4) + " y=" + str(y-4) + " address="+hex(ptr_address) - script = parse_script_engine_script_at(ptr_address, map_group=map_group, map_id=map_id) - extra_portion = { - "script_address": ptr_address, - "script": script, - "event_type": "script", - } - if is_give_item: - print "... not parsing give item event... [item id][quantity]" - extra_portion = { - "event_type": "give_item", - "give_item_data_address": ptr_address, - "item_id": ord(rom[ptr_address]), - "item_qty": ord(rom[ptr_address+1]), - } - if is_trainer: - print "parsing a trainer (person-event) at x=" + str(x) + " y=" + str(y) - parsed_trainer = parse_trainer_header_at(ptr_address, map_group=map_group, map_id=map_id) - extra_portion = { - "event_type": "trainer", - "trainer_data_address": ptr_address, - "trainer_data": parsed_trainer, - } - - # XXX not sure what's going on here - # bit no. of bit table 1 (hidden if set) - # note: FFFF for none - when_byte = int(bytes[11], 16) - hide = int(bytes[12], 16) - - bit_number_of_bit_table1_byte2 = int(bytes[11], 16) - bit_number_of_bit_table1_byte1 = int(bytes[12], 16) - bit_number_of_bit_table1 = bit_number_of_bit_table1_byte1 + (bit_number_of_bit_table1_byte2 << 8) - - people_event = { - "pict": pict, - "y": y, # y from top + 4 - "x": x, # x from left + 4 - "face": face, # 0-4 for regular, 6-9 for static facing - "move": move, - "clock_time": {"1": clock_time_byte1, - "2": clock_time_byte2}, # clock/time setting byte 1 - "color_function_byte": color_function_byte, # Color|Function - "trainer_sight_range": trainer_sight_range, # trainer range of sight - "script_pointer": {"1": script_pointer_byte1, - "2": script_pointer_byte2}, - - #"text_block": text_block, # script pointer byte 1 - #"text_bank": text_bank, # script pointer byte 2 - "when_byte": when_byte, # bit no. of bit table 1 (hidden if set) - "hide": hide, # note: FFFF for none - - "is_trainer": is_trainer, - "is_regular_script": is_regular_script, - "is_give_item": is_give_item, - } - people_event.update(extra_portion) - people_events.append(people_event) - return people_events - - -class SignpostRemoteBase: - def __init__(self, address, bank=None, map_group=None, map_id=None, signpost=None, debug=False, label=None): - self.address = address - self.last_address = address + self.size - script_parse_table[self.address : self.last_address] = self - self.bank = bank - self.map_group = map_group - self.map_id = map_id - self.signpost = signpost - self.debug = debug - self.params = [] - if not label: - label = self.base_label + hex(address) - self.label = Label(name=label, address=address, object=self) - self.dependencies = None - self.parse() - - def get_dependencies(self, recompute=False, global_dependencies=set()): - dependencies = [] - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - for p in self.params: - deps = p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - dependencies.extend(deps) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - """very similar to Command.to_asm""" - if len(self.params) == 0: - return "" - #output = ", ".join([p.to_asm() for p in self.params]) - output = "" - for param in self.params: - if issubclass(param.__class__, SingleByteParam): - output += "db " - else: - output += "dw " - output += param.to_asm() + "\n" - return output - - -class SignpostRemoteScriptChunk(SignpostRemoteBase): - """ - a signpost might point to [Bit-Nr. (2byte)][2byte pointer to script] - """ - base_label = "SignpostRemoteScript_" - size = 4 - - def parse(self): - address = self.address - bank = self.bank - - #bit_table_byte1 = ord(rom[address]) - #bit_table_byte2 = ord(rom[address+1]) - bit_table = MultiByteParam(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(bit_table) - - #script_address = calculate_pointer_from_bytes_at(address+2, bank=bank) - #script = parse_script_engine_script_at(script_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - script_param = ScriptPointerLabelParam(address=address+2, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=False) - self.params.append(script_param) - self.script = script_param.script - self.signpost.remote_script = self.script - - #self.bit_table_bytes = [bit_table_byte1, bit_table_byte2] - #self.script_address = script_address - #self.script = script - - -class SignpostRemoteItemChunk(SignpostRemoteBase): - """ - a signpost might point to [Bit-Nr. (2byte)][Item no.] - """ - base_label = "SignpostRemoteItem_" - size = 3 - - def parse(self): - address = self.address - bank = self.bank - - bit_table = MultiByteParam(address=address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(bit_table) - - item = ItemLabelByte(address=address+2) - self.params.append(item) - self.item = item - - -class SignpostRemoteUnknownChunk(SignpostRemoteBase): - """ - a signpost might point to [Bit-Nr. (2byte)][??] - """ - base_label = "SignpostRemoteUnknown_" - size = 3 - - def parse(self): - address = self.address - bank = self.bank - - bit_table = MultiByteParam(address=address, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(bit_table) - - byte = SingleByteParam(address=address+2) - self.params.append(byte) - - -# this could potentially extend Command -# see how class Warp does this -class Signpost(Command): - """parse some number of signposts from the data - - [Y position][X position][Function][Script pointer (2byte)] - - functions: - 00 Sign can be read from all directions - script pointer to: script - 01 Sign can only be read from below - script pointer to: script - 02 Sign can only be read from above - script pointer to: script - 03 Sign can only be read from right - script pointer to: script - 04 Sign can only be read from left - script pointer to: script - 05 If bit of BitTable1 is set then pointer is interpreted - script pointer to: [Bit-Nr. (2byte)][2byte pointer to script] - 06 If bit of BitTable1 is not set then pointer is interpreted - script pointer to: [Bit-Nr. (2byte)][2byte pointer to script] - 07 If bit of BitTable1 is set then item is given - script pointer to: [Bit-Nr. (2byte)][Item no.] - 08 No Action - script pointer to: [Bit-Nr. (2byte)][??] - """ - size = 5 - macro_name = "signpost" - override_byte_check = True - - # preprocessor uses this - param_types = { - 0: {"name": "y", "class": DecimalParam}, - 1: {"name": "x", "class": DecimalParam}, - 2: {"name": "function", "class": HexByte}, - 3: {"name": "pointer", "class": PointerLabelParam}, - } - - def __init__(self, address, id, bank=None, map_group=None, map_id=None, debug=True, label=None): - self.address = address - self.id = id - if label == None: - label = "UnknownSignpost_"+str(map_group)+"Map"+str(map_id)+"_"+hex(address) - self.label = Label(name=label, address=address, object=self) - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.bank = bank - self.last_address = self.address + self.size - self.y, self.x, self.func = None, None, None - # Signpost should probably not be in the globals - #script_parse_table[self.address : self.last_address] = self - self.remotes = [] - self.params = [] - self.dependencies = None - self.parse() - - def parse(self): - """parse just one signpost""" - address = self.address - bank = self.bank - self.last_address = self.address + self.size - bytes = rom_interval(self.address, self.size) #, signpost_byte_size) - - self.y = int(bytes[0], 16) - self.x = int(bytes[1], 16) - self.func = int(bytes[2], 16) - y, x, func = self.y, self.x, self.func - - # y - self.params.append(DecimalParam(address=address, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug)) - # x - self.params.append(DecimalParam(address=address+1, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug)) - # func - self.params.append(HexByte(address=address+2, bank=self.bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug)) - - output = "******* parsing signpost "+str(self.id)+" at: " - output += "x="+str(x)+" y="+str(y)+" on map_group=" - output += str(self.map_group)+" map_id="+str(self.map_id) - - if func in [0, 1, 2, 3, 4]: - # signpost's script pointer points to a script - script_ptr_byte1 = int(bytes[3], 16) - script_ptr_byte2 = int(bytes[4], 16) - script_pointer = script_ptr_byte1 + (script_ptr_byte2 << 8) - - script_address = calculate_pointer(script_pointer, bank) - output += " script@"+hex(script_address) - print output - - param = ScriptPointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug, force=False) - self.params.append(param) - param = script_parse_table[param.parsed_address] - param.label = Label(address=param.address, object=param, name="Map"+map_names[self.map_group][self.map_id]["label"]+"Signpost"+str(self.id)+"Script") - - #self.script_address = script_address - #self.script = script - elif func in [5, 6]: - # signpost's script pointer points to [Bit-Nr. (2byte)][2byte pointer to script] - ptr_byte1 = int(bytes[3], 16) - ptr_byte2 = int(bytes[4], 16) - pointer = ptr_byte1 + (ptr_byte2 << 8) - address = calculate_pointer(pointer, bank) - - bit_table_byte1 = ord(rom[address]) - bit_table_byte2 = ord(rom[address+1]) - script_ptr_byte1 = ord(rom[address+2]) - script_ptr_byte2 = ord(rom[address+3]) - script_address = calculate_pointer_from_bytes_at(address+2, bank=bank) - - output += " remote_chunk@"+hex(address)+" remote_script@"+hex(script_address) - print output - - r1 = SignpostRemoteScriptChunk(address, signpost=self, \ - bank=self.bank, map_group=self.map_group, map_id=self.map_id, \ - debug=self.debug) - self.remotes.append(r1) - - # give a better label to the SignpostRemoteScriptChunk - r1.label = Label(address=r1.address, object=r1, name="Map"+map_names[self.map_group][self.map_id]["label"]+"SignpostPtr"+str(self.id)) - - mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(mb) - - # update the remote script address - param = script_parse_table[script_address] - param.label = Label(address=param.address, object=param, name="Map"+map_names[self.map_group][self.map_id]["label"]+"Signpost"+str(self.id)+"Script") - - elif func == 7: - # signpost's script pointer points to [Bit-Nr. (2byte)][Item no.] - ptr_byte1 = int(bytes[3], 16) - ptr_byte2 = int(bytes[4], 16) - pointer = ptr_byte1 + (ptr_byte2 << 8) - address = calculate_pointer(pointer, bank) - - item_id = ord(rom[address+2]) - output += " item_id="+str(item_id) - print output - - r1 = SignpostRemoteItemChunk(address, signpost=self, \ - bank=self.bank, map_group=self.map_group, map_id=self.map_id, \ - debug=self.debug) - self.remotes.append(r1) - r1.label = Label(address=r1.address, object=r1, name="Map"+map_names[self.map_group][self.map_id]["label"]+"SignpostItem"+str(self.id)) - - mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(mb) - - #bit_table_byte1 = ord(rom[address]) - #bit_table_byte2 = ord(rom[address+1]) - #self.bit_table_bytes = [bit_table_byte1, bit_table_byte2] - #self.item_id = item_id - elif func == 8: - # signpost's script pointer points to [Bit-Nr. (2byte)][??] - ptr_byte1 = int(bytes[3], 16) - ptr_byte2 = int(bytes[4], 16) - pointer = ptr_byte1 + (ptr_byte2 << 8) - address = calculate_pointer(pointer, bank) - - output += " remote unknown chunk at="+hex(address) - print output - - r1 = SignpostRemoteUnknownChunk(address, signpost=self, \ - bank=self.bank, map_group=self.map_group, map_id=self.map_id, \ - debug=self.debug) - self.remotes.append(r1) - - mb = PointerLabelParam(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.params.append(mb) - else: - raise Exception("unknown signpost type byte="+hex(func) + " signpost@"+hex(self.address)) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - dependencies = [] - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - for p in self.params: - dependencies.extend(p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - output = self.macro_name + " " - if self.params == []: - raise Exception("signpost has no params?") - output += ", ".join([p.to_asm() for p in self.params]) - return output - -all_signposts = [] -def parse_signposts(address, signpost_count, bank=None, map_group=None, map_id=None, debug=True): - if bank == None: - raise Exception("signposts need to know their bank") - signposts = [] - current_address = address - id = 0 - for each in range(signpost_count): - signpost = Signpost(current_address, id, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - current_address += signpost_byte_size # i think ?? - signposts.append(signpost) - id += 1 - all_signposts.extend(signposts) - return signposts - -def old_parse_signpost_bytes(some_bytes, bank=None, map_group=None, map_id=None, debug=True): - assert len(some_bytes) % signpost_byte_size == 0, "wrong number of bytes" - signposts = [] - for bytes in grouper(some_bytes, count=signpost_byte_size): - y = int(bytes[0], 16) - x = int(bytes[1], 16) - func = int(bytes[2], 16) - - additional = {} - if func in [0, 1, 2, 3, 4]: - print "******* parsing signpost script.. signpost is at: x=" + str(x) + " y=" + str(y) - script_ptr_byte1 = int(bytes[3], 16) - script_ptr_byte2 = int(bytes[4], 16) - script_pointer = script_ptr_byte1 + (script_ptr_byte2 << 8) - - script_address = None - script = None - - script_address = calculate_pointer(script_pointer, bank) - script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id) - - additional = { - "script_ptr": script_pointer, - "script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2}, - "script_address": script_address, - "script": script, - } - elif func in [5, 6]: - print "******* parsing signpost script.. signpost is at: x=" + str(x) + " y=" + str(y) - ptr_byte1 = int(bytes[3], 16) - ptr_byte2 = int(bytes[4], 16) - pointer = ptr_byte1 + (ptr_byte2 << 8) - address = calculate_pointer(pointer, bank) - bit_table_byte1 = ord(rom[address]) - bit_table_byte2 = ord(rom[address+1]) - script_ptr_byte1 = ord(rom[address+2]) - script_ptr_byte2 = ord(rom[address+3]) - script_address = calculate_pointer_from_bytes_at(address+2, bank=bank) - script = parse_script_engine_script_at(script_address, map_group=map_group, map_id=map_id) - - additional = { - "bit_table_bytes": {"1": bit_table_byte1, "2": bit_table_byte2}, - "script_ptr": script_ptr_byte1 + (script_ptr_byte2 << 8), - "script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2}, - "script_address": script_address, - "script": script, - } - else: - print ".. type 7 or 8 signpost not parsed yet." - - spost = { - "y": y, - "x": x, - "func": func, - } - spost.update(additional) - signposts.append(spost) - return signposts - - -class MapHeader: - base_label = "MapHeader_" - - def __init__(self, address, map_group=None, map_id=None, debug=True, label=None, bank=0x25): - print "creating a MapHeader at "+hex(address)+" map_group="+str(map_group)+" map_id="+str(map_id) - self.address = address - self.map_group = map_group - self.map_id = map_id - self.bank = bank - self.debug = debug - self.dependencies = None - label = self.make_label() - self.label = Label(name=label, address=address, object=self) - self.last_address = address + 9 - script_parse_table[address : self.last_address] = self - self.parse() - - def make_label(self): - return map_names[self.map_group][self.map_id]["label"] + "_MapHeader" - - def parse(self): - address = self.address - print "parsing a MapHeader at " + hex(address) - self.bank = HexByte(address=address) - self.tileset = HexByte(address=address+1) - self.permission = DecimalParam(address=address+2) - self.second_map_header_address = calculate_pointer(ord(rom[address+3])+(ord(rom[address+4])<<8), self.bank.byte) - # TODO: is the bank really supposed to be 0x25 all the time ?? - self.second_map_header = SecondMapHeader(self.second_map_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - all_second_map_headers.append(self.second_map_header) - self.location_on_world_map = HexByte(address=address+5) - self.music = HexByte(address=address+6) - self.time_of_day = DecimalParam(address=address+7) - self.fishing_group = DecimalParam(address=address+8) - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - dependencies = [self.second_map_header] - global_dependencies.add(self.second_map_header) - dependencies.append(self.second_map_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - output = "; bank, tileset, permission\n" - output += "db " + ", ".join(["BANK(" + self.second_map_header.label.name + ")", self.tileset.to_asm(), self.permission.to_asm()]) - output += "\n\n; second map header\n" - output += "dw " + PointerLabelParam(address=self.address+3).to_asm() # TODO: should we include bank=self.bank.byte ?? - output += "\n\n; location on world map, music, time of day, fishing group\n" - output += "db " + ", ".join([self.location_on_world_map.to_asm(), self.music.to_asm(), self.time_of_day.to_asm(), self.fishing_group.to_asm()]) - return output - - -all_map_headers = [] -def parse_map_header_at(address, map_group=None, map_id=None, debug=True): - """parses an arbitrary map header at some address""" - print "parsing a map header at: " + hex(address) - map_header = MapHeader(address, map_group=map_group, map_id=map_id, debug=debug) - all_map_headers.append(map_header) - return map_header - -def old_parse_map_header_at(address, map_group=None, map_id=None, debug=True): - """parses an arbitrary map header at some address""" - print "parsing a map header at: " + hex(address) - bytes = rom_interval(address, map_header_byte_size, strings=False, debug=debug) - bank = bytes[0] - tileset = bytes[1] - permission = bytes[2] - second_map_header_address = calculate_pointer(bytes[3] + (bytes[4] << 8), 0x25) - location_on_world_map = bytes[5] # pokegear world map location - music = bytes[6] - time_of_day = bytes[7] - fishing_group = bytes[8] - - map_header = { - "bank": bank, - "tileset": tileset, - "permission": permission, # map type? - "second_map_header_pointer": {"1": bytes[3], "2": bytes[4]}, - "second_map_header_address": second_map_header_address, - "location_on_world_map": location_on_world_map, # area - "music": music, - "time_of_day": time_of_day, - "fishing": fishing_group, - } - print "second map header address is: " + hex(second_map_header_address) - map_header["second_map_header"] = old_parse_second_map_header_at(second_map_header_address, debug=debug) - event_header_address = map_header["second_map_header"]["event_address"] - script_header_address = map_header["second_map_header"]["script_address"] - # maybe event_header and script_header should be put under map_header["second_map_header"] - map_header["event_header"] = old_parse_map_event_header_at(event_header_address, map_group=map_group, map_id=map_id, debug=debug) - map_header["script_header"] = old_parse_map_script_header_at(script_header_address, map_group=map_group, map_id=map_id, debug=debug) - return map_header - - -def get_direction(connection_byte, connection_id): - """ - Given a connection byte and a connection id, which direction is this - connection? - - example: - The 0th connection of $5 is SOUTH and the 1st connection is - EAST. - """ - connection_options = [0b1000, 0b0100, 0b0010, 0b0001] - results = ["NORTH", "SOUTH", "WEST", "EAST"] - - for option in connection_options: - if (option & connection_byte) == 0: - results[connection_options.index(option)] = "" - - # prune results - while "" in results: - results.remove("") - - return results[connection_id] - -class SecondMapHeader: - base_label = "SecondMapHeader_" - - def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None): - print "creating a SecondMapHeader at " + hex(address) - self.address = address - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.bank = bank - self.dependencies = None - label = self.make_label() - self.label = Label(name=label, address=address, object=self) - - # the minimum number of bytes is 12 - self.last_address = address+12 - self.size = 12 - - script_parse_table[address : self.last_address] = self - self.parse() - - def make_label(self): - return map_names[self.map_group][self.map_id]["label"] + "_SecondMapHeader" - - def parse(self): - address = self.address - bytes = rom_interval(address, second_map_header_byte_size, strings=False) - size = second_map_header_byte_size - - # for later - self.connections = [] - - self.border_block = HexByte(address=address) - self.height = DecimalParam(address=address+1) - self.width = DecimalParam(address=address+2) - - # bank appears first - ###self.blockdata_address = PointerLabelBeforeBank(address+3) - self.blockdata_address = calculate_pointer_from_bytes_at(address+3, bank=True) - xyz = script_parse_table[self.blockdata_address] - if xyz == None: - self.blockdata = MapBlockData(self.blockdata_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug, width=self.width, height=self.height) - else: - self.blockdata = xyz - - # bank appears first - ###self.script_address = PointerLabelBeforeBank(address+6) - self.script_header_address = calculate_pointer_from_bytes_at(address+6, bank=True) - self.script_header = MapScriptHeader(self.script_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - all_map_script_headers.append(self.script_header) - - self.event_bank = ord(rom[address+6]) - self.event_header_address = calculate_pointer_from_bytes_at(address+9, bank=ord(rom[address+6])) - self.event_header = MapEventHeader(self.event_header_address, map_group=self.map_group, map_id=self.map_id, debug=self.debug) - self.connection_byte = DecimalParam(address=address+11) - all_map_event_headers.append(self.event_header) - - self.size = size - - if self.connection_byte == 0: - return True - - current_address = address+12 - - # short alias - cb = self.connection_byte.byte - - # east = 1, west = 2, south = 4, north = 8 (or'd together) - east = ((cb & 0x1) != 0) - west = ((cb & 0x2) != 0) - south = ((cb & 0x4) != 0) - north = ((cb & 0x8) != 0) - directions = [east, west, south, north] - connection_count = directions.count(True) - - for connection in range(0, connection_count): - direction = get_direction(self.connection_byte.byte, connection) - connection = Connection(current_address, direction=direction, map_group=self.map_group, map_id=self.map_id, debug=self.debug, smh=self) - self.connections.append(connection) - - # 12 bytes each? - current_address += connection.size - - self.last_address = current_address - - return True - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - dependencies = [self.script_header, self.event_header, self.blockdata] - global_dependencies.update(dependencies) - dependencies.append(self.script_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - dependencies.append(self.event_header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - self_constant_label = get_map_constant_label(map_group=self.map_group, map_id=self.map_id) - output = "; border block\n" - output += "db " + self.border_block.to_asm() + "\n\n" - output += "; height, width\n" - output += "db " + self_constant_label + "_HEIGHT, " + self_constant_label + "_WIDTH\n\n" - output += "; blockdata (bank-then-pointer)\n" - thing = ScriptPointerLabelBeforeBank(address=self.address+3, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm() - output += "dbw " + thing.split(", ")[0] + ", "+thing.split(", ")[1] + "\n\n" - output += "; script header (bank-then-pointer)\n" - thing = ScriptPointerLabelBeforeBank(address=self.address+6, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm() - output += "dbw " + thing.split(", ")[0] + ", " + thing.split(", ")[1] + "\n\n" - output += "; map event header (bank-then-pointer)\n" - output += "dw " + PointerLabelParam(address=self.address+9, bank=self.event_bank, map_group=self.map_group, map_id=self.map_id, debug=self.debug).to_asm() + "\n\n" - - output += "; connections\n" - dir_results = [] - connection_options = [0b1000, 0b0100, 0b0010, 0b0001] - dirs = ["NORTH", "SOUTH", "WEST", "EAST"] - for (id, each) in enumerate(dirs): - if ((connection_options[id] & self.connection_byte.byte) != 0): - dir_results.append(each) - output += "db " + " | ".join(dir_results) - if len(dir_results) == 0: - output += "0" - - if self.connection_byte.byte == 0 or len(dir_results) == 0: - return output - else: - output += "\n\n" - - connections = "\n\n".join([connection.to_asm() for connection in self.connections]) - output += connections - - return output - -strip_pointer_data = [] -strip_destination_data = [] -connections = [] -wrong_norths = [] -wrong_easts = [] -wrong_souths = [] -wrong_wests = [] - -class Connection: - size = 12 - - def __init__(self, address, direction=None, map_group=None, map_id=None, debug=True, smh=None): - self.address = address - self.direction = direction.lower() - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.smh = smh - self.last_address = address + self.size - connections.append(self) - - self.parse() - - def parse(self): - current_address = self.address - - is_vertical = ((self.direction == "north") or (self.direction == "south")) - is_horizontal = ((self.direction == "east") or (self.direction == "west")) - - connected_map_group_id = ord(rom[current_address]) - self.connected_map_group_id = connected_map_group_id - current_address += 1 - - connected_map_id = ord(rom[current_address]) - self.connected_map_id = connected_map_id - current_address += 1 - - # window (use JohtoMap's calculation, not this) - # up: C701h + height_of_connected_map * (width_of_connected_map + 6) - # left: C706h + 2 * width_of_connected_map - # down/right: C707h + width_of_connected_map - # - # 2 bytes (flipped) - X position of starting point for intermediate - # tiles (scrolls through connected map line-by-line. this way you can - # change Y position also) - # - # According to JohtoMap, the calculation for tile data pointer is: - # int p = otherMap.tileDataLocation; - # int h = (otherMap.width - otherMap.height) - # if (h > 0) - # p += (h * otherMap.height) + (otherMap.height * 3) + (otherMap.height + 3) - # else - # p += (otherMap.height * otherMap.width) - (otherMap.width * 3); - # c.tileDataPointer = gb.Get2BytePointer(p); - # - # tauwasser calls this "connection strip pointer" - tile_data_pointer = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8) - strip_pointer = tile_data_pointer - self.strip_pointer = tile_data_pointer - current_address += 2 - - # 10:19 <comet> memoryotherpointer is <comet> what johtomap calls OMDL (in ram where the tiles start getting pulled from the other map) - memory_other_pointer = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8) - # 10:42 <comet> it would be a good idea to rename otherpointer strippointer or striploc - # 10:42 <comet> since thats more accurate - # 11:05 <comet> Above: C803h + xoffset - # 11:05 <comet> Below: C803h + (m.height + 3) * (m.width + 6) + xoffset - # 11:05 <comet> Left: C800h + (m.width + 6) * (yoffset + 3) - # 11:05 <comet> Right: C7FDh + (m.width + 6) * (yoffset + 4) - # - # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer" - # Points to the upper left block of the connection strip - # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.) - # The connection strip is always 3 Blocks high resp. wide - # (depending on the connection's direction) - strip_destination = memory_other_pointer - self.strip_destination = memory_other_pointer - current_address += 2 - - # length of the connection strip in blocks - connection_strip_length = ord(rom[current_address]) - current_address += 1 - connected_map_width = ord(rom[current_address]) - current_address += 1 - - self.connection_strip_length = connection_strip_length - self.connected_map_width = connected_map_width - - y_position_after_map_change = ord(rom[current_address]) - yoffset = y_position_after_map_change - current_address += 1 - - x_position_after_map_change = ord(rom[current_address]) - xoffset = x_position_after_map_change - current_address += 1 - - # in pokered these were called alignments? same thing? - self.yoffset = y_position_after_map_change - self.xoffset = x_position_after_map_change - - # tauwasser calls this "window" and lin calls this "memoryCurrentPointer" - # Position of the upper left block after entering the Map - # - # tauwasser's formula for windows: - # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6) - # Left: C706h + 2 * Width_of_connected_map - # Below/Right: C707h + Width_of_connected_map - window = ord(rom[current_address]) + (ord(rom[current_address+1]) << 8) - current_address += 2 - - self.window = window - - current_map_height = self.smh.height.byte - current_map_width = self.smh.width.byte - - if "header_new" in map_names[connected_map_group_id][connected_map_id].keys(): - # the below code ensures that there's an equation to handle strip_pointer - - ldirection = self.direction.lower() - connected_map_header = map_names[connected_map_group_id][connected_map_id]["header_new"] - connected_second_map_header = connected_map_header.second_map_header - connected_map_height = connected_second_map_header.height.byte - connected_map_width = connected_second_map_header.width.byte - p = connected_second_map_header.blockdata.address - h = None - method = "default" - - if ldirection == "north": - h = connected_map_width - self.smh.width.byte - if ((p + ((connected_map_height * connected_map_width) - (connected_map_width * 3)))%0x4000)+0x4000 == strip_pointer: - # lin's equation: - # p += (otherMap.height * otherMap.width) - (otherMap.width * 3) - p += (connected_map_height * connected_map_width) - (connected_map_width * 3) - method = "north1" - elif ((p + connected_map_width + xoffset + (16 * connected_map_height) - 16)%0x4000)+0x4000 == strip_pointer: - p += connected_map_width + xoffset + (16 * connected_map_height) - 16 - method = "north2" - elif p != strip_pointer: - # worst case scenario: we don't know how to calculate p, so we'll just set it as a constant - # example: Route10North north to Route9 (strip_pointer=0x7eae, connected map's blockdata=0x7de9) - p = strip_pointer - method = "north3" - else: - # this doesn't seem to ever happen - # or just do nothing (value is already ok) - method = "north4" - elif ldirection == "west": - h = connected_map_height - self.smh.height.byte - if ((p + (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2)%0x4000)+0x4000 == strip_pointer: - # lin's method: - # p += (h * otherMap.width) - (otherMap.width * 3) + (otherMap.width - 3) - p += (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2 - method = "west1" - elif ((p + connected_map_width - 3)%0x4000)+0x4000 == strip_pointer: - print "west h <= 0" - # lin's method: - # p += otherMap.width - 3 - p += connected_map_width - 3 - method = "west2" - elif ((p + xoffset + (current_map_height * 2))%0x4000 + 0x4000) == strip_pointer: - method = "west3" - p += xoffset + (current_map_height * 2) - elif (p%0x4000)+0x4000 != strip_pointer: - # worst case scenario: dunno what to do - method = "west4" - p = strip_pointer - else: - # this doesn't seem to ever happen - # do nothing - method = "west5" - elif ldirection == "south": - print "south.. dunno what to do?" - - if (p%0x4000)+0x4000 == strip_pointer: - # do nothing - method = "south1" - elif ((p + (xoffset - connection_strip_length + self.smh.width.byte) / 2)%0x4000)+0x4000 == strip_pointer: - # comet's method - method = "south2" - p += (xoffset - connection_strip_length + self.smh.width.byte) / 2 - elif ((p + ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1)%0x4000)+0x4000 == strip_pointer: - method = "south3" - p += ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1 - elif ldirection == "east": - if (p%0x4000)+0x4000 == strip_pointer: - # do nothing - method = "east1" - elif ((p + (connected_map_height - connection_strip_length) * connected_map_width)%0x4000)+0x4000 == strip_pointer: - p += (connected_map_height - connection_strip_length) * connected_map_width - method = "east2" - elif ((p + 100 - 4 * connected_map_width)%0x4000) + 0x4000 == strip_pointer: - method = "east3" - p += 100 - 4 * connected_map_width - elif ((p + 2 * (100 - 4 * connected_map_width))%0x4000) + 0x4000 == strip_pointer: - method = "east4" - # the "2" is possibly ( connected_map_height / current_map_height ) - # or current_map_width/yoffset or connected_map_width/yoffset - p += 2 * (100 - 4 * connected_map_width) - - # convert the address to a 2-byte pointer - intermediate_p = p - p = (p % 0x4000) + 0x4000 - - data = { - "strip_pointer": strip_pointer, - "strip_length": connection_strip_length, - "other_blockdata_address": connected_second_map_header.blockdata.address, - "other_blockdata_pointer": (connected_second_map_header.blockdata.address%0x4000)+0x4000, - - "xoffset": xoffset, - "yoffset": yoffset, - - "connected_map_height": connected_map_height, - "connected_map_width": connected_map_width, - "connected_map_group_id": connected_map_group_id, - "connected_map_id": connected_map_id, - "connected_map_label": map_names[connected_map_group_id][connected_map_id]["label"], - - "current_map_width": self.smh.width.byte, - "current_map_height": self.smh.height.byte, - "current_map_label": map_names[self.smh.map_group][self.smh.map_id]["label"], - "current_map_group_id": self.smh.map_group, - "current_map_id": self.smh.map_id, - - "difference": strip_pointer - ((connected_second_map_header.blockdata.address%0x4000)+0x4000), - "direction": ldirection, - "method": method, - } - strip_pointer_data.append(data) - - if p != strip_pointer: - print "method: " + method + " direction: " + ldirection - print "other map blockdata address: " + hex(connected_second_map_header.blockdata.address) - print "h = " + str(h) - print "initial p = " + hex(connected_second_map_header.blockdata.address) - print "intermediate p = " + hex(intermediate_p) - print "final p = " + hex(p) - print "connection length = " + str(connection_strip_length) - print "strip_pointer = " + hex(strip_pointer) - print "other map height = " + str(connected_map_height) - print "other map width = " + str(connected_map_width) - o = "other map group_id="+hex(connected_map_group_id) + " map_id="+hex(connected_map_id)+" "+map_names[connected_map_group_id][connected_map_id]["label"] + " smh="+hex(connected_second_map_header.address) - o += " width="+str(connected_second_map_header.width.byte)+" height="+str(connected_second_map_header.height.byte) - print o - - o = "current map group_id="+hex(self.map_group)+" map_id="+hex(self.map_id)+" "+map_names[self.map_group][self.map_id]["label"]+" smh="+hex(self.smh.address) - o += " width="+str(self.smh.width.byte)+" height="+str(self.smh.height.byte) - print o - - if ldirection == "east": - wrong_easts.append(data) - elif ldirection == "west": - wrong_wests.append(data) - elif ldirection == "south": - wrong_souths.append(data) - elif ldirection == "north": - wrong_norths.append(data) - - # this will only happen if there's a bad formula - raise Exception("tauwasser strip_pointer calculation was wrong? strip_pointer="+hex(strip_pointer) + " p="+hex(p)) - - calculated_destination = None - method = "strip_destination_default" - x_movement_of_the_connection_strip_in_blocks = None - y_movement_of_the_connection_strip_in_blocks = None - - # the below code makes sure there's an equation to calculate strip_destination - # 11:05 <comet> Above: C803h + xoffset - # 11:05 <comet> Below: C803h + (m.height + 3) * (m.width + 6) + xoffset - # 11:05 <comet> Left: C800h + (m.width + 6) * (yoffset + 3) - # 11:05 <comet> Right: C7FDh + (m.width + 6) * (yoffset + 4) - # - # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer" - # Points to the upper left block of the connection strip - # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.) - # The connection strip is always 3 Blocks high resp. wide - # (depending on the connection's direction) - if ldirection == "north": - x_movement_of_the_connection_strip_in_blocks = strip_destination - 0xC703 - print "(north) x_movement_of_the_connection_strip_in_blocks is: " + str(x_movement_of_the_connection_strip_in_blocks) - if x_movement_of_the_connection_strip_in_blocks < 0: - raise Exception("x_movement_of_the_connection_strip_in_blocks is wrong? " + str(x_movement_of_the_connection_strip_in_blocks)) - elif ldirection == "south": - # strip_destination = - # 0xc703 + (current_map_height + 3) * (current_map_width + 6) + x_movement_of_the_connection_strip_in_blocks - x_movement_of_the_connection_strip_in_blocks = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6)) - print "(south) x_movement_of_the_connection_strip_in_blocks is: " + str(x_movement_of_the_connection_strip_in_blocks) - elif ldirection == "east": - # strip_destination = - # 0xc700 + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 3) - y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3 - print "(east) y_movement_of_the_connection_strip_in_blocks is: " + str(y_movement_of_the_connection_strip_in_blocks) - elif ldirection == "west": - # strip_destination = - # 0xc6fd + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 4) - y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc6fd) / (current_map_width + 6) - 4 - print "(west) y_movement_of_the_connection_strip_in_blocks is: " + str(y_movement_of_the_connection_strip_in_blocks) - - # let's also check the window equations - # tauwasser calls this "window" and lin calls this "memoryCurrentPointer" - # Position of the upper left block after entering the Map - # - # tauwasser's formula for windows: - # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6) - # Left: C706h + 2 * Width_of_connected_map - # Below/Right: C707h + Width_of_connected_map - window_worked = False - if ldirection == "north": - # tauwasser's formula: 0xc701 + connected_map_height * (connected_map_width + 6) - window_start = 0xc801 - if window == window_start + (connected_map_height * 6) + (connected_map_height * connected_map_width): - window_worked = True - elif ldirection == "east": - window_start = 0xc807 - if window == (window_start + connected_map_width): - window_worked = True - elif ldirection == "south": - window_start = 0xc807 - if window == (window_start + connected_map_width): - window_worked = True - elif ldirection == "west": - window_start = 0xc807 - if window == (window_start + xoffset): - window_worked = True - - data = { - "window": window, - "window_start": window_start, - "window_diff": window - window_start, - "window_worked": window_worked, - "strip_destination": strip_destination, - "strip_length": connection_strip_length, - "other_blockdata_address": connected_second_map_header.blockdata.address, - "other_blockdata_pointer": (connected_second_map_header.blockdata.address%0x4000)+0x4000, - - "xoffset": xoffset, - "yoffset": yoffset, - - "connected_map_height": connected_map_height, - "connected_map_width": connected_map_width, - "connected_map_group_id": connected_map_group_id, - "connected_map_id": connected_map_id, - "connected_map_label": map_names[connected_map_group_id][connected_map_id]["label"], - - "current_map_width": self.smh.width.byte, - "current_map_height": self.smh.height.byte, - "current_map_label": map_names[self.smh.map_group][self.smh.map_id]["label"], - "current_map_group_id": self.smh.map_group, - "current_map_id": self.smh.map_id, - - "y_movement_of_the_connection_strip_in_blocks": y_movement_of_the_connection_strip_in_blocks, - "x_movement_of_the_connection_strip_in_blocks": x_movement_of_the_connection_strip_in_blocks, - - "direction": ldirection, - "method": method, - } - strip_destination_data.append(data) - - def to_asm(self): - output = "" - ldirection = self.direction.lower() - - connected_map_group_id = self.connected_map_group_id - connected_map_id = self.connected_map_id - - connected_map_header = map_names[connected_map_group_id][connected_map_id]["header_new"] - connected_second_map_header = connected_map_header.second_map_header - connected_map_height = connected_second_map_header.height.byte - connected_map_width = connected_second_map_header.width.byte - - connection_strip_length = self.connection_strip_length - connected_map_width = self.connected_map_width - - current_map_height = self.smh.height.byte - current_map_width = self.smh.width.byte - - map_constant_label = get_map_constant_label(map_group=connected_map_group_id, map_id=connected_map_id) - self_constant_label = get_map_constant_label(map_group=self.smh.map_group, map_id=self.smh.map_id) - if map_constant_label != None: - map_group_label = "GROUP_" + map_constant_label - map_label = "MAP_" + map_constant_label - else: - map_group_label = str(connected_map_group_id) - map_label = str(connected_map_id) - - output += "; " + self.direction.upper() + " to " \ - + map_names[connected_map_group_id][connected_map_id]["name"] \ - + "\n" - - output += "db %s, %s ; connected map (group, id)\n" % (map_group_label, map_label) - - yoffset = self.yoffset - xoffset = self.xoffset - - # According to JohtoMap, the calculation for tile data pointer is: - # int p = otherMap.tileDataLocation; - # int h = (otherMap.width - otherMap.height) - # if (h > 0) - # p += (h * otherMap.height) + (otherMap.height * 3) + (otherMap.height + 3) - # else - # p += (otherMap.height * otherMap.width) - (otherMap.width * 3); - # c.tileDataPointer = gb.Get2BytePointer(p); - strip_pointer = self.strip_pointer - - p = connected_second_map_header.blockdata.address - - output += "dw " - - if ldirection == "north": - h = connected_map_width - self.smh.width.byte - if ((p + ((connected_map_height * connected_map_width) - (connected_map_width * 3)))%0x4000)+0x4000 == strip_pointer: - # lin's equation: - # p += (otherMap.height * otherMap.width) - (otherMap.width * 3) - p += (connected_map_height * connected_map_width) - (connected_map_width * 3) - method = "north1" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + (" + map_constant_label + "_HEIGHT * " + map_constant_label + "_WIDTH) - (" + map_constant_label + "_WIDTH * 3))" - elif ((p + connected_map_width + xoffset + (16 * connected_map_height) - 16)%0x4000)+0x4000 == strip_pointer: - p += connected_map_width + xoffset + (16 * connected_map_height) - 16 - method = "north2" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + map_constant_label + "_WIDTH + " + str(xoffset) + " + (16 * " + map_constant_label + "_HEIGHT) - 16)" - elif p != strip_pointer: - # worst case scenario: we don't know how to calculate p, so we'll just set it as a constant - # example: Route10North north to Route9 (strip_pointer=0x7eae, connected map's blockdata=0x7de9) - p = strip_pointer - method = "north3" - output += "$%.2x" % (p) - else: - # this doesn't seem to ever happen - # or just do nothing (value is already ok) - method = "north4" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")" - elif ldirection == "west": - h = connected_map_height - self.smh.height.byte - h_out = "(" + map_constant_label +"_HEIGHT - " + self_constant_label +"_HEIGHT)" - if ((p + (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2)%0x4000)+0x4000 == strip_pointer: - # lin's method: - # p += (h * otherMap.width) - (otherMap.width * 3) + (otherMap.width - 3) - p += (h * connected_map_width) - (connected_map_width * 3) + (connected_map_width - 1) - 2 - method = "west1" - this_part = "((" + h_out + " * " + map_constant_label + "_WIDTH) - (" + map_constant_label + "_WIDTH * 3) + (" + map_constant_label + "_WIDTH - 1) - 2)" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")" - elif ((p + connected_map_width - 3)%0x4000)+0x4000 == strip_pointer: - print "west h <= 0" - # lin's method: - # p += otherMap.width - 3 - p += connected_map_width - 3 - method = "west2" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + map_constant_label + "_WIDTH - 3)" - elif ((p + xoffset + (current_map_height * 2))%0x4000 + 0x4000) == strip_pointer: - method = "west3" - p += xoffset + (current_map_height * 2) - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + str(xoffset) + " + (" + map_constant_label + "_HEIGHT * 2))" - elif (p%0x4000)+0x4000 != strip_pointer: - # worst case scenario: dunno what to do - method = "west4" - p = strip_pointer - output += "$%.2x" % ((p%0x4000)+0x4000) - else: - # this doesn't seem to ever happen - # do nothing - method = "west5" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")" - elif ldirection == "south": - if (p%0x4000)+0x4000 == strip_pointer: - # do nothing - method = "south1" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")" - elif ((p + (xoffset - connection_strip_length + self.smh.width.byte) / 2)%0x4000)+0x4000 == strip_pointer: - # comet's method - method = "south2" - p += (xoffset - connection_strip_length + self.smh.width.byte) / 2 - this_part = "((" + str(xoffset) + " - " + str(connection_strip_length) + " + " + self_constant_label + "_WIDTH) / 2)" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")" - elif ((p + ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1)%0x4000)+0x4000 == strip_pointer: - method = "south3" - p += ((xoffset - connection_strip_length + self.smh.width.byte) / 2) - 1 - this_part = "(((" + str(xoffset) + " - " + str(connection_strip_length) + " + " + self_constant_label + "_WIDTH) / 2) - 1)" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")" - elif ldirection == "east": - if (p%0x4000)+0x4000 == strip_pointer: - # do nothing - method = "east1" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + ")" - elif ((p + (connected_map_height - connection_strip_length) * connected_map_width)%0x4000)+0x4000 == strip_pointer: - p += (connected_map_height - connection_strip_length) * connected_map_width - method = "east2" - this_part = "((" + map_constant_label + "_HEIGHT - " + str(connection_strip_length) + ") * " + map_constant_label + "_WIDTH)" - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + " + this_part + ")" - elif ((p + 100 - 4 * connected_map_width)%0x4000) + 0x4000 == strip_pointer: - method = "east3" - p += 100 - 4 * connected_map_width - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + 100 - (" + map_constant_label + "_WIDTH * 4))" - elif ((p + 2 * (100 - 4 * connected_map_width))%0x4000) + 0x4000 == strip_pointer: - method = "east4" - # the "2" is possibly ( connected_map_height / current_map_height ) - # or current_map_width/yoffset or connected_map_width/yoffset - p += 2 * (100 - 4 * connected_map_width) - output += "(" + get_label_for(connected_second_map_header.blockdata.address) + " + ((100 - (" + map_constant_label + "_WIDTH * 4)) * 2))" - - output += " ; strip pointer\n" - - # tauwasser calls this "connection strip destination" and lin calls this "memoryOtherPointer" - # Points to the upper left block of the connection strip - # (The bank the Blockdata is in, is loaded out of the Mapheader of the connected Map.) - # The connection strip is always 3 Blocks high resp. wide - # (depending on the connection's direction) - strip_destination = self.strip_destination - - output += "dw " - - # i am not convinced about these calculations - if ldirection == "north": - x_movement_of_the_connection_strip_in_blocks = strip_destination - 0xC703 - xmov = x_movement_of_the_connection_strip_in_blocks - output += "($C703 + " + str(xmov) + ")" - elif ldirection == "south": - # strip_destination = - # 0xc703 + (current_map_height + 3) * (current_map_width + 6) + x_movement_of_the_connection_strip_in_blocks - x_movement_of_the_connection_strip_in_blocks = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6)) - xmov = x_movement_of_the_connection_strip_in_blocks - #output += "($C703 + (((" + self_constant_label + "_HEIGHT + 3) * (" + self_constant_label + "_WIDTH + 6)) + " + str(xmov) + "))" - - # xmov = strip_destination - (0xc703 + (current_map_height + 3) * (current_map_width + 6)) - #difference = 0xC715 + xmov + 6*current_map_height + 3*current_map_width + current_map_width*current_map_height - #difference = 50965 + ymov + 6*current_map_height + 3*current_map_width + current_map_width*current_map_height - - output += "($C703 + " + str(xmov) + " + ((" + self_constant_label + "_HEIGHT + 3) * (" + self_constant_label + "_WIDTH + 6)))" - elif ldirection == "east": - # strip_destination = - # 0xc700 + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 3) - y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3 - ymov = y_movement_of_the_connection_strip_in_blocks - #output += "($C700 + ((" + self_constant_label + "_WIDTH + 6) * (" + str(ymov) + " + 3)) + "+str(ymov)+")" - output += "$%.2x" % (strip_destination) - elif ldirection == "west": - # strip_destination = - # 0xc6fd + (current_map_width + 6) * (y_movement_of_the_connection_strip_in_blocks + 4) - y_movement_of_the_connection_strip_in_blocks = (strip_destination - 0xc700) / (current_map_width + 6) - 3 - ymov = y_movement_of_the_connection_strip_in_blocks - #output += "($C700 + ((" + self_constant_label + "_WIDTH + 6) * (" + str(ymov) + " + 4)) - 4)" - output += "$%.2x" % (strip_destination) - output += " ; strip destination\n" - - output += "db " + str(connection_strip_length,) + ", " + map_constant_label + "_WIDTH ; (connection strip length, connected map width)\n" - - #if ldirection in ["east", "west"]: - # Y_movement_of_connection_strip_in_blocks = - #elif direction in ["north", "south"]: - # X_movement_of_connection_strip_in_blocks = - - # Above: (Height_of_connected_map * 2) - 1 - # Below: 0 - # Left/Right: (Y_movement_of_connection_strip_in_blocks * -2) - yoffset = self.yoffset # y_position_after_map_change - - if ldirection == "south" and yoffset != 0: - raise Exception("tauwasser was wrong about yoffset=0 for south? it's: " + str(yoffset)) - elif ldirection == "north" and yoffset != ((connected_map_height * 2) - 1): - raise Exception("tauwasser was wrong about yoffset for north? it's: " + str(yoffset)) - #elif not ((yoffset % -2) == 0): - # raise Exception("tauwasser was wrong about yoffset for west/east? it's not divisible by -2: " + str(yoffset)) - - # Left: (Width_of_connected_map * 2) - 1 - # Right: 0 - # Above/Below: (X_movement_of_connection_strip_in_blocks * -2) - xoffset = self.xoffset # x_position_after_map_change - - if ldirection == "east" and xoffset != 0: - raise Exception("tauwasser was wrong about xoffset=0 for east? it's: " + str(xoffset)) - elif ldirection == "west" and xoffset != ((connected_map_width * 2) - 1): - raise Exception("tauwasser was wrong about xoffset for west? it's: " + str(xoffset)) - #elif not ((xoffset % -2) == 0): - # raise Exception("tauwasser was wrong about xoffset for north/south? it's not divisible by -2: " + str(xoffset)) - - output += "db " - - if ldirection == "south": - output += "0" - elif ldirection == "north": - output += "((" + map_constant_label + "_HEIGHT * 2) - 1)" - else: - output += str(yoffset) - - output += ", " - - if ldirection == "east": - output += "0" - elif ldirection == "west": - output += "((" + map_constant_label + "_WIDTH * 2) - 1)" - else: - output += str(xoffset) - - output += " ; yoffset, xoffset\n" - - window = self.window - - output += "dw " - - # let's also check the window equations - # tauwasser calls this "window" and lin calls this "memoryCurrentPointer" - # Position of the upper left block after entering the Map - # - # tauwasser's formula for windows: - # Above: C701h + Height_of_connected_map * (Width_of_connected_map + 6) - # Left: C706h + 2 * Width_of_connected_map - # Below/Right: C707h + Width_of_connected_map - window_worked = False - if ldirection == "north": - # tauwasser's formula: 0xc701 + connected_map_height * (connected_map_width + 6) - window_start = 0xc801 - if window == window_start + (connected_map_height * 6) + (connected_map_height * connected_map_width): - window_worked = True - output += "($C801 + ((" + map_constant_label + "_HEIGHT * 6) + (" + map_constant_label + "_HEIGHT * " + map_constant_label + "_WIDTH)))" - elif ldirection == "east": - window_start = 0xc807 - if window == (window_start + connected_map_width): - window_worked = True - output += "($C807 + " + map_constant_label + "_WIDTH)" - elif ldirection == "south": - window_start = 0xc807 - if window == (window_start + connected_map_width): - window_worked = True - output += "($C807 + " + map_constant_label + "_WIDTH)" - elif ldirection == "west": - window_start = 0xc807 - if window == (window_start + xoffset): - window_worked = True - output += "($C807 + " + str(xoffset) + ")" - - output += " ; window" - - return output - -all_second_map_headers = [] -def parse_second_map_header_at(address, map_group=None, map_id=None, debug=True): - """each map has a second map header""" - smh = SecondMapHeader(address, map_group=map_group, map_id=map_id, debug=debug) - all_second_map_headers.append(smh) - return smh - -def old_parse_second_map_header_at(address, map_group=None, map_id=None, debug=True): - """each map has a second map header""" - bytes = rom_interval(address, second_map_header_byte_size, strings=False) - border_block = bytes[0] - height = bytes[1] - width = bytes[2] - blockdata_bank = bytes[3] - blockdata_pointer = bytes[4] + (bytes[5] << 8) - blockdata_address = calculate_pointer(blockdata_pointer, blockdata_bank) - script_bank = bytes[6] - script_pointer = bytes[7] + (bytes[8] << 8) - script_address = calculate_pointer(script_pointer, script_bank) - event_bank = script_bank - event_pointer = bytes[9] + (bytes[10] << 8) - event_address = calculate_pointer(event_pointer, event_bank) - connections = bytes[11] - return { - "border_block": border_block, - "height": height, - "width": width, - "blockdata_bank": blockdata_bank, - "blockdata_pointer": {"1": bytes[4], "2": bytes[5]}, - "blockdata_address": blockdata_address, - "script_bank": script_bank, - "script_pointer": {"1": bytes[7], "2": bytes[8]}, - "script_address": script_address, - "event_bank": event_bank, - "event_pointer": {"1": bytes[9], "2": bytes[10]}, - "event_address": event_address, - "connections": connections, - } - - -class MapBlockData: - base_label = "MapBlockData_" - maps_path = os.path.realpath(os.path.join(os.path.realpath("."), "../maps")) - - def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None, width=None, height=None): - self.address = address - self.map_group = map_group - self.map_id = map_id - self.map_name = map_names[map_group][map_id]["label"] - self.map_path = os.path.join(self.maps_path, self.map_name + ".blk") - self.debug = debug - self.bank = bank - if width and height: - self.width = width - self.height = height - else: - raise Exception("MapBlockData needs to know the width/height of its map") - label = self.make_label() - self.label = Label(name=label, address=address, object=self) - self.last_address = self.address + (self.width.byte * self.height.byte) - script_parse_table[address : self.last_address] = self - self.parse() - - def make_label(self): - return map_names[self.map_group][self.map_id]["label"] + "_BlockData" - - def save_to_file(self): - # check if the file exists already - map_path = self.map_path - if not os.path.exists(self.maps_path): - os.mkdir(self.maps_path) - if not os.path.exists(map_path): - # dump to file - #bytes = rom_interval(self.address, self.width.byte*self.height.byte, strings=True) - bytes = rom[self.address : self.address + self.width.byte*self.height.byte] - file_handler = open(map_path, "w") - file_handler.write(bytes) - file_handler.close() - - def parse(self): - self.save_to_file() - - def to_asm(self): - return "INCBIN \"maps/"+self.map_name+".blk\"" - - -class MapEventHeader: - base_label = "MapEventHeader_" - - def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None): - print "making a MapEventHeader at "+hex(address)+" map_group="+str(map_group)+" map_id="+str(map_id) - self.address = address - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.bank = bank - self.dependencies = None - label = self.make_label() - self.label = Label(name=label, address=address, object=self) - self.parse() - script_parse_table[address : self.last_address] = self - - def make_label(self): - return map_names[self.map_group][self.map_id]["label"] + "_MapEventHeader" - - def parse(self): - map_group, map_id, debug = self.map_group, self.map_id, self.debug - address = self.address - bank = calculate_bank(self.address) # or use self.bank - print "event header address is: " + hex(address) - - filler1 = ord(rom[address]) - filler2 = ord(rom[address+1]) - self.fillers = [filler1, filler2] - - # warps - warp_count = ord(rom[address+2]) - warp_byte_count = warp_byte_size * warp_count - after_warps = address + 3 + warp_byte_count - warps = parse_warps(address+3, warp_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - self.warp_count = warp_count - self.warps = warps - - # triggers (based on xy location) - xy_trigger_count = ord(rom[after_warps]) - trigger_byte_count = trigger_byte_size * xy_trigger_count - xy_triggers = parse_xy_triggers(after_warps+1, xy_trigger_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - after_triggers = after_warps + 1 + trigger_byte_count - self.xy_trigger_count = xy_trigger_count - self.xy_triggers = xy_triggers - - # signposts - signpost_count = ord(rom[after_triggers]) - signpost_byte_count = signpost_byte_size * signpost_count - # signposts = rom_interval(after_triggers+1, signpost_byte_count) - signposts = parse_signposts(after_triggers+1, signpost_count, bank=bank, map_group=map_group, map_id=map_id, debug=debug) - after_signposts = after_triggers + 1 + signpost_byte_count - self.signpost_count = signpost_count - self.signposts = signposts - - # people events - people_event_count = ord(rom[after_signposts]) - people_event_byte_count = people_event_byte_size * people_event_count - # people_events_bytes = rom_interval(after_signposts+1, people_event_byte_count) - # people_events = parse_people_event_bytes(people_events_bytes, address=after_signposts+1, map_group=map_group, map_id=map_id) - people_events = parse_people_events(after_signposts+1, people_event_count, bank=calculate_bank(after_signposts+2), map_group=map_group, map_id=map_id, debug=debug) - self.people_event_count = people_event_count - self.people_events = people_events - - if people_event_count > 0: - self.last_address = people_events[-1].last_address - else: - self.last_address = after_signposts+1 - return True - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - bases = [] - bases += self.people_events - bases += self.signposts - bases += self.xy_triggers - bases += self.warps - - dependencies = [] - for p in bases: - dependencies.extend(p.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - xspacing = "" # was =spacing - output = "; filler\n" - output += "db %d, %d\n\n" % (self.fillers[0], self.fillers[1]) - - output += xspacing + "; warps\n" - output += xspacing + "db %d"%(self.warp_count) - if len(self.warps) > 0: - output += "\n" - output += "\n".join([xspacing+warp.to_asm() for warp in self.warps]) - - output += "\n\n" - output += xspacing + "; xy triggers\n" - output += xspacing + "db %d"%(self.xy_trigger_count) - if len(self.xy_triggers) > 0: - output += "\n" - output += "\n".join([xspacing+xy_trigger.to_asm() for xy_trigger in self.xy_triggers]) - - output += "\n\n" - output += xspacing + "; signposts\n" - output += xspacing + "db %d"%(self.signpost_count) - if len(self.signposts) > 0: - output += "\n" - output += "\n".join([xspacing+signpost.to_asm() for signpost in self.signposts]) - - output += "\n\n" - output += xspacing + "; people-events\n" - output += xspacing + "db %d"%(self.people_event_count) - if len(self.people_events) > 0: - output += "\n" - - for people_event in self.people_events: - output += xspacing - output += people_event.to_asm() - output += "\n" - - if output[-1] == "\n": - output = output[:-1] - return output - -all_map_event_headers = [] -def parse_map_event_header_at(address, map_group=None, map_id=None, debug=True, bank=None): - """parse crystal map event header byte structure thing""" - ev = MapEventHeader(address, map_group=map_group, map_id=map_id, debug=debug, bank=bank) - all_map_event_headers.append(ev) - return ev - -def old_parse_map_event_header_at(address, map_group=None, map_id=None, debug=True): - """parse crystal map event header byte structure thing""" - returnable = {} - - bank = calculate_bank(address) - - print "event header address is: " + hex(address) - filler1 = ord(rom[address]) - filler2 = ord(rom[address+1]) - returnable.update({"1": filler1, "2": filler2}) - - # warps - warp_count = ord(rom[address+2]) - warp_byte_count = warp_byte_size * warp_count - warps = rom_interval(address+3, warp_byte_count) - after_warps = address + 3 + warp_byte_count - returnable.update({"warp_count": warp_count, "warps": old_parse_warp_bytes(warps)}) - - # triggers (based on xy location) - trigger_count = ord(rom[after_warps]) - trigger_byte_count = trigger_byte_size * trigger_count - triggers = rom_interval(after_warps+1, trigger_byte_count) - after_triggers = after_warps + 1 + trigger_byte_count - returnable.update({"xy_trigger_count": trigger_count, "xy_triggers": old_parse_xy_trigger_bytes(triggers, bank=bank, map_group=map_group, map_id=map_id)}) - - # signposts - signpost_count = ord(rom[after_triggers]) - signpost_byte_count = signpost_byte_size * signpost_count - signposts = rom_interval(after_triggers+1, signpost_byte_count) - after_signposts = after_triggers + 1 + signpost_byte_count - returnable.update({"signpost_count": signpost_count, "signposts": old_parse_signpost_bytes(signposts, bank=bank, map_group=map_group, map_id=map_id)}) - - # people events - people_event_count = ord(rom[after_signposts]) - people_event_byte_count = people_event_byte_size * people_event_count - people_events_bytes = rom_interval(after_signposts+1, people_event_byte_count) - people_events = old_parse_people_event_bytes(people_events_bytes, address=after_signposts+1, map_group=map_group, map_id=map_id) - returnable.update({"people_event_count": people_event_count, "people_events": people_events}) - - return returnable - - -class MapScriptHeader: - """parses a script header - - This structure allows the game to have e.g. one-time only events on a map - or first enter events or permanent changes to the map or permanent script - calls. - - This header a combination of a trigger script section and a callback script - section. I don't know if these 'trigger scripts' are the same as the others - referenced in the map event header, so this might need to be renamed very - soon. The scripts in MapEventHeader are called XYTrigger. - - trigger scripts: - [[Number1 of pointers] Number1 * [2byte pointer to script][00][00]] - - callback scripts: - [[Number2 of pointers] Number2 * [hook number][2byte pointer to script]] - - hook byte choices: - 01 - map data has already been loaded to ram, tileset and sprites still missing - map change (3rd step) - loading (2nd step) - map connection (3rd step) - after battle (1st step) - 02 - map data, tileset and sprites are all loaded - map change (5th step) - 03 - neither map data not tilesets nor sprites are loaded - map change (2nd step) - loading (1st step) - map connection (2nd step) - 04 - map data and tileset loaded, sprites still missing - map change (4th step) - loading (3rd step) - sprite reload (1st step) - map connection (4th step) - after battle (2nd step) - 05 - neither map data not tilesets nor sprites are loaded - map change (1st step) - map connection (1st step) - - When certain events occur, the call backs will be called in this order (same info as above): - map change: - 05, 03, 01, 04, 02 - loading: - 03, 01, 04 - sprite reload: - 04 - map connection: - 05, 03, 01, 04 note that #2 is not called (unlike "map change") - after battle: - 01, 04 - """ - base_label = "MapScriptHeader_" - - def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None): - print "creating a MapScriptHeader at " + hex(address) + " map_group="+str(map_group)+" map_id="+str(map_id) - self.address = address - self.map_group = map_group - self.map_id = map_id - self.debug = debug - self.bank = bank - self.dependencies = None - label = self.make_label() - self.label = Label(name=label, address=address, object=self) - self.parse() - script_parse_table[address : self.last_address] = self - - def make_label(self): - return map_names[self.map_group][self.map_id]["label"] + "_MapScriptHeader" - - def parse(self): - address = self.address - map_group = self.map_group - map_id = self.map_id - debug = self.debug - #[[Number1 of pointers] Number1 * [2byte pointer to script][00][00]] - self.trigger_count = ord(rom[address]) - self.triggers = [] - ptr_line_size = 4 - groups = grouper(rom_interval(address+1, self.trigger_count * ptr_line_size, strings=False), count=ptr_line_size) - current_address = address+1 - for (index, trigger_bytes) in enumerate(groups): - print "parsing a map trigger script at "+hex(current_address)+" map_group="+str(map_group)+" map_id="+str(map_id) - script = ScriptPointerLabelParam(address=current_address, map_group=map_group, map_id=map_id, debug=debug) - extra_bytes = MultiByteParam(address=current_address+2, map_group=map_group, map_id=map_id, debug=debug) - self.triggers.append([script, extra_bytes]) - current_address += ptr_line_size - current_address = address + (self.trigger_count * ptr_line_size) + 1 - #[[Number2 of pointers] Number2 * [hook number][2byte pointer to script]] - callback_ptr_line_size = 3 - self.callback_count = DecimalParam(address=current_address) - self.callback_count = self.callback_count.byte - current_address += 1 - self.callbacks = [] - for index in range(self.callback_count): - print "parsing a callback script at "+hex(current_address)+" map_group="+str(map_group)+" map_id="+str(map_id) - hook_byte = HexByte(address=current_address) - callback = ScriptPointerLabelParam(address=current_address+1, map_group=map_group, map_id=map_id, debug=debug) - self.callbacks.append({"hook": hook_byte, "callback": callback}) - current_address += 3 # i think? - self.last_address = current_address - print "done parsing a MapScriptHeader map_group="+str(map_group)+" map_id="+str(map_id) - return True - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if self.dependencies != None and not recompute: - global_dependencies.update(self.dependencies) - return self.dependencies - dependencies = [] - for p in list(self.triggers): - # dependencies.append(p[0]) - dependencies.extend(p[0].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - for callback in self.callbacks: - dependencies.append(callback["callback"]) - global_dependencies.add(callback["callback"]) - dependencies.extend(callback["callback"].get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) - self.dependencies = dependencies - return dependencies - - def to_asm(self): - output = "" - output += "; trigger count\n" - output += "db %d\n"%self.trigger_count - if len(self.triggers) > 0: - output += "\n; triggers\n" - output += "\n".join([str("dw "+p[0].to_asm()+", "+p[1].to_asm()) for p in self.triggers]) - output += "\n" - output += "\n; callback count\n" - output += "db %d"%self.callback_count - if len(self.callbacks) > 0: - output += "\n\n; callbacks\n\n" - output += "\n\n".join(["dbw "+str(p["hook"].byte)+", "+p["callback"].to_asm() for p in self.callbacks]) - return output - -all_map_script_headers = [] -def parse_map_script_header_at(address, map_group=None, map_id=None, debug=True): - evv = MapScriptHeader(address, map_group=map_group, map_id=map_id, debug=debug) - all_map_script_headers.append(evv) - return evv - -def old_parse_map_script_header_at(address, map_group=None, map_id=None, debug=True): - print "starting to parse the map's script header.." - #[[Number1 of pointers] Number1 * [2byte pointer to script][00][00]] - ptr_line_size = 4 #[2byte pointer to script][00][00] - trigger_ptr_cnt = ord(rom[address]) - trigger_pointers = grouper(rom_interval(address+1, trigger_ptr_cnt * ptr_line_size, strings=False), count=ptr_line_size) - triggers = {} - for index, trigger_pointer in enumerate(trigger_pointers): - print "parsing a trigger header..." - byte1 = trigger_pointer[0] - byte2 = trigger_pointer[1] - ptr = byte1 + (byte2 << 8) - trigger_address = calculate_pointer(ptr, calculate_bank(address)) - trigger_script = parse_script_engine_script_at(trigger_address, map_group=map_group, map_id=map_id) - triggers[index] = { - "script": trigger_script, - "address": trigger_address, - "pointer": {"1": byte1, "2": byte2}, - } - - # bump ahead in the byte stream - address += trigger_ptr_cnt * ptr_line_size + 1 - - #[[Number2 of pointers] Number2 * [hook number][2byte pointer to script]] - callback_ptr_line_size = 3 - callback_ptr_cnt = ord(rom[address]) - callback_ptrs = grouper(rom_interval(address+1, callback_ptr_cnt * callback_ptr_line_size, strings=False), count=callback_ptr_line_size) - callback_pointers = {} - callbacks = {} - for index, callback_line in enumerate(callback_ptrs): - print "parsing a callback header..." - hook_byte = callback_line[0] # 1, 2, 3, 4, 5 - callback_byte1 = callback_line[1] - callback_byte2 = callback_line[2] - callback_ptr = callback_byte1 + (callback_byte2 << 8) - callback_address = calculate_pointer(callback_ptr, calculate_bank(address)) - callback_script = parse_script_engine_script_at(callback_address) - callback_pointers[len(callback_pointers.keys())] = [hook_byte, callback_ptr] - callbacks[index] = { - "script": callback_script, - "address": callback_address, - "pointer": {"1": callback_byte1, "2": callback_byte2}, - } - - # XXX do these triggers/callbacks call asm or script engine scripts? - return { - #"trigger_ptr_cnt": trigger_ptr_cnt, - "trigger_pointers": trigger_pointers, - #"callback_ptr_cnt": callback_ptr_cnt, - #"callback_ptr_scripts": callback_ptrs, - "callback_pointers": callback_pointers, - "trigger_scripts": triggers, - "callback_scripts": callbacks, - } - -def old_parse_trainer_header_at(address, map_group=None, map_id=None, debug=True): - bank = calculate_bank(address) - bytes = rom_interval(address, 12, strings=False) - bit_number = bytes[0] + (bytes[1] << 8) - trainer_group = bytes[2] - trainer_id = bytes[3] - text_when_seen_ptr = calculate_pointer_from_bytes_at(address+4, bank=bank) - text_when_seen = parse_text_engine_script_at(text_when_seen_ptr, map_group=map_group, map_id=map_id, debug=debug) - text_when_trainer_beaten_ptr = calculate_pointer_from_bytes_at(address+6, bank=bank) - text_when_trainer_beaten = parse_text_engine_script_at(text_when_trainer_beaten_ptr, map_group=map_group, map_id=map_id, debug=debug) - - if [ord(rom[address+8]), ord(rom[address+9])] == [0, 0]: - script_when_lost_ptr = 0 - script_when_lost = None - else: - print "parsing script-when-lost" - script_when_lost_ptr = calculate_pointer_from_bytes_at(address+8, bank=bank) - script_when_lost = None - silver_avoids = [0xfa53] - if script_when_lost_ptr > 0x4000 and not script_when_lost_ptr in silver_avoids: - script_when_lost = parse_script_engine_script_at(script_when_lost_ptr, map_group=map_group, map_id=map_id, debug=debug) - - print "parsing script-talk-again" # or is this a text? - script_talk_again_ptr = calculate_pointer_from_bytes_at(address+10, bank=bank) - script_talk_again = None - if script_talk_again_ptr > 0x4000: - script_talk_again = parse_script_engine_script_at(script_talk_again_ptr, map_group=map_group, map_id=map_id, debug=debug) - - return { - "bit_number": bit_number, - "trainer_group": trainer_group, - "trainer_id": trainer_id, - "text_when_seen_ptr": text_when_seen_ptr, - "text_when_seen": text_when_seen, - "text_when_trainer_beaten_ptr": text_when_trainer_beaten_ptr, - "text_when_trainer_beaten": text_when_trainer_beaten, - "script_when_lost_ptr": script_when_lost_ptr, - "script_when_lost": script_when_lost, - "script_talk_again_ptr": script_talk_again_ptr, - "script_talk_again": script_talk_again, - } - -def old_parse_people_event_bytes(some_bytes, address=None, map_group=None, map_id=None, debug=True): - """parse some number of people-events from the data - see PeopleEvent - see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Scripthdr - - For example, map 1.1 (group 1 map 1) has four person-events. - - 37 05 07 06 00 FF FF 00 00 02 40 FF FF - 3B 08 0C 05 01 FF FF 00 00 05 40 FF FF - 3A 07 06 06 00 FF FF A0 00 08 40 FF FF - 29 05 0B 06 00 FF FF 00 00 0B 40 FF FF - - max of 14 people per map? - """ - assert len(some_bytes) % people_event_byte_size == 0, "wrong number of bytes" - - # address is not actually required for this function to work... - bank = None - if address: - bank = calculate_bank(address) - - people_events = [] - for bytes in grouper(some_bytes, count=people_event_byte_size): - pict = int(bytes[0], 16) - y = int(bytes[1], 16) # y from top + 4 - x = int(bytes[2], 16) # x from left + 4 - face = int(bytes[3], 16) # 0-4 for regular, 6-9 for static facing - move = int(bytes[4], 16) - clock_time_byte1 = int(bytes[5], 16) - clock_time_byte2 = int(bytes[6], 16) - color_function_byte = int(bytes[7], 16) # Color|Function - trainer_sight_range = int(bytes[8], 16) - - lower_bits = color_function_byte & 0xF - #lower_bits_high = lower_bits >> 2 - #lower_bits_low = lower_bits & 3 - higher_bits = color_function_byte >> 4 - #higher_bits_high = higher_bits >> 2 - #higher_bits_low = higher_bits & 3 - - is_regular_script = lower_bits == 00 - # pointer points to script - is_give_item = lower_bits == 01 - # pointer points to [Item no.][Amount] - is_trainer = lower_bits == 02 - # pointer points to trainer header - - # goldmap called these next two bytes "text_block" and "text_bank"? - script_pointer_byte1 = int(bytes[9], 16) - script_pointer_byte2 = int(bytes[10], 16) - script_pointer = script_pointer_byte1 + (script_pointer_byte2 << 8) - # calculate the full address by assuming it's in the current bank - # but what if it's not in the same bank? - extra_portion = {} - if bank: - ptr_address = calculate_pointer(script_pointer, bank) - if is_regular_script: - print "parsing a person-script at x=" + str(x-4) + " y=" + str(y-4) + " address="+hex(ptr_address) - script = parse_script_engine_script_at(ptr_address, map_group=map_group, map_id=map_id) - extra_portion = { - "script_address": ptr_address, - "script": script, - "event_type": "script", - } - if is_give_item: - print "... not parsing give item event... [item id][quantity]" - extra_portion = { - "event_type": "give_item", - "give_item_data_address": ptr_address, - "item_id": ord(rom[ptr_address]), - "item_qty": ord(rom[ptr_address+1]), - } - if is_trainer: - print "parsing a trainer (person-event) at x=" + str(x) + " y=" + str(y) - parsed_trainer = old_parse_trainer_header_at(ptr_address, map_group=map_group, map_id=map_id) - extra_portion = { - "event_type": "trainer", - "trainer_data_address": ptr_address, - "trainer_data": parsed_trainer, - } - - # XXX not sure what's going on here - # bit no. of bit table 1 (hidden if set) - # note: FFFF for none - when_byte = int(bytes[11], 16) - hide = int(bytes[12], 16) - - bit_number_of_bit_table1_byte2 = int(bytes[11], 16) - bit_number_of_bit_table1_byte1 = int(bytes[12], 16) - bit_number_of_bit_table1 = bit_number_of_bit_table1_byte1 + (bit_number_of_bit_table1_byte2 << 8) - - people_event = { - "pict": pict, - "y": y, # y from top + 4 - "x": x, # x from left + 4 - "face": face, # 0-4 for regular, 6-9 for static facing - "move": move, - "clock_time": {"1": clock_time_byte1, - "2": clock_time_byte2}, # clock/time setting byte 1 - "color_function_byte": color_function_byte, # Color|Function - "trainer_sight_range": trainer_sight_range, # trainer range of sight - "script_pointer": {"1": script_pointer_byte1, - "2": script_pointer_byte2}, - - #"text_block": text_block, # script pointer byte 1 - #"text_bank": text_bank, # script pointer byte 2 - "when_byte": when_byte, # bit no. of bit table 1 (hidden if set) - "hide": hide, # note: FFFF for none - - "is_trainer": is_trainer, - "is_regular_script": is_regular_script, - "is_give_item": is_give_item, - } - people_event.update(extra_portion) - people_events.append(people_event) - return people_events - -def parse_map_header_by_id(*args, **kwargs): - """convenience function to parse a specific map""" - map_group, map_id = None, None - if "map_group" in kwargs.keys(): - map_group = kwargs["map_group"] - if "map_id" in kwargs.keys(): - map_id = kwargs["map_id"] - if (map_group == None and map_id != None) or \ - (map_group != None and map_id == None): - raise Exception("map_group and map_id must both be provided") - elif map_group == None and map_id == None and len(args) == 0: - raise Exception("must be given an argument") - elif len(args) == 1 and type(args[0]) == str: - map_group = int(args[0].split(".")[0]) - map_id = int(args[0].split(".")[1]) - elif map_group == None and map_id == None: - raise Exception("dunno what to do with input") - offset = map_names[map_group]["offset"] - map_header_offset = offset + ((map_id - 1) * map_header_byte_size) - return parse_map_header_at(map_header_offset, map_group=map_group, map_id=map_id) - -def parse_all_map_headers(debug=True): - """calls parse_map_header_at for each map in each map group""" - global map_names - if not map_names[1].has_key("offset"): - raise Exception("dunno what to do - map_names should have groups with pre-calculated offsets by now") - for group_id, group_data in map_names.items(): - offset = group_data["offset"] - # we only care about the maps - #del group_data["offset"] - for map_id, map_data in group_data.items(): - if map_id == "offset": continue # skip the "offset" address for this map group - if debug: print "map_group is: " + str(group_id) + " map_id is: " + str(map_id) - map_header_offset = offset + ((map_id - 1) * map_header_byte_size) - map_names[group_id][map_id]["header_offset"] = map_header_offset - - new_parsed_map = parse_map_header_at(map_header_offset, map_group=group_id, map_id=map_id, debug=debug) - map_names[group_id][map_id]["header_new"] = new_parsed_map - old_parsed_map = old_parse_map_header_at(map_header_offset, map_group=group_id, map_id=map_id, debug=debug) - map_names[group_id][map_id]["header_old"] = old_parsed_map - -class PokedexEntryPointerTable: - """ - A list of pointers. - """ - - def __init__(self): - self.address = 0x44378 - self.target_bank = calculate_bank(0x181695) - self.label = Label(name="PokedexDataPointerTable", address=self.address, object=self) - self.size = None - self.last_address = None - self.dependencies = None - self.entries = [] - self.parse() - - script_parse_table[self.address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - global_dependencies.update(self.entries) - dependencies = [] - [dependencies.extend(entry.get_dependencies(recompute=recompute, global_dependencies=global_dependencies)) for entry in self.entries] - return dependencies - - def parse(self): - size = 0 - lastpointer = 0 - for i in range(251): - # Those are consecutive in GS! - if i == 0x40: - self.target_bank = 0x6e - elif i == 0x80: - self.target_bank = 0x73 - elif i == 0xc0: - self.target_bank = 0x74 - loc = self.address+(i*2) - pointer = calculate_pointer_from_bytes_at(loc, bank=self.target_bank) - #print(hex(pointer)) - #if pointer < lastpointer: - # self.target_bank += 1 - # pointer += 0x4000 - self.entries.append(PokedexEntry(pointer, i+1)) - - size += 2 - self.size = size - self.last_address = self.address + self.size - - def to_asm(self): - output = "".join([str("dw "+get_label_for(entry.address)+"\n") for entry in self.entries]) - return output - -class PokedexEntry: - def __init__(self, address, pokemon_id): - self.address = address - self.dependencies = None - #label = self.make_label() - if pokemon_id in pokemon_constants: - pokename = string.capwords(pokemon_constants[pokemon_id].replace("__", " ").replace("_", " ")).replace(" ", "") - else: - pokename = "Pokemon{0}".format(pokemon_id) - self.label = Label(name=pokename+"PokedexEntry", address=self.address, object=self) - self.parse() - script_parse_table[address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - return [] - - def parse(self): - # eww. - address = self.address - jump = how_many_until(chr(0x50), address) - self.species = parse_text_at(address, jump+1) - address = address + jump + 1 - - self.weight = ord(rom[address ]) + (ord(rom[address+1]) << 8) - self.height = ord(rom[address+2]) + (ord(rom[address+3]) << 8) - address += 4 - - jump = how_many_until(chr(0x50), address) - self.page1 = PokedexText(address) - address = address + jump + 1 - jump = how_many_until(chr(0x50), address) - self.page2 = PokedexText(address) - - self.last_address = address + jump + 1 - #print(self.to_asm()) - return True - - def to_asm(self): - output = """\ - db "{0}" ; species name - dw {1}, {2} ; height, weight - - {3} - {4}""".format(self.species, self.weight, self.height, self.page1.to_asm(), self.page2.to_asm()) - return output - -from map_names import map_names - -# map names with no labels will be generated -# generate labels for each map name -for map_group_id in map_names.keys(): - map_group = map_names[map_group_id] - for map_id in map_group.keys(): - # skip if we maybe already have the 'offset' label set in this map group - if map_id == "offset": continue - # skip if we provided a pre-set value for the map's label - if map_group[map_id].has_key("label"): continue - # convience alias - map_data = map_group[map_id] - # clean up the map name to be an asm label - cleaned_name = map_name_cleaner(map_data["name"]) - # set the value in the original dictionary - map_names[map_group_id][map_id]["label"] = cleaned_name -# generate map constants (like 1=PALLET_TOWN) -generate_map_constant_labels() - -#### asm utilities #### -# these are pulled in from pokered/extras/analyze_incbins.py - -# store each line of source code here -asm = None - -# store each incbin line separately -incbin_lines = [] - -# storage for processed incbin lines -processed_incbins = {} - -def to_asm(some_object, use_asm_rules=False): - """shows an object's asm with a label and an ending comment - showing the next byte address""" - if isinstance(some_object, int): - some_object = script_parse_table[some_object] - # add one to the last_address to show where the next byte is in the file - last_address = some_object.last_address - # create a line like "label: ; 0x10101" - asm = some_object.label.name + ": ; " + hex(some_object.address) + "\n" - # now add the inner/actual asm - #asm += spacing + some_object.to_asm().replace("\n", "\n"+spacing).replace("\n"+spacing+"\n"+spacing, "\n\n"+spacing) - asmr = some_object.to_asm() - asmr = asmr.replace("\n", "\n"+spacing) - asmr = asmr.replace("\n"+spacing+"\n", "\n\n"+spacing) - asmr = asmr.replace("\n\n"+spacing+spacing, "\n\n"+spacing) - asm += spacing + asmr - if use_asm_rules: - asm = asm.replace("\n" + spacing + "; ", "\n; ") - asm = asm.replace("\n" + spacing + ".asm_", "\n.asm_") - # show the address of the next byte below this - asm += "\n; " + hex(last_address) - return asm - -def flattener(x): - "flattens a list of sublists into just one list (generator)" - try: - it = iter(x) - except TypeError: - yield x - else: - for i in it: - for j in flattener(i): - yield j - -def flatten(x): - "flattens a list of sublists into just one list" - return list(flattener(x)) - -def get_dependencies_for(some_object, recompute=False, global_dependencies=set()): - """ - calculates which labels need to be satisfied for an object - to be inserted into the asm and compile successfully. - - You could also choose to not insert labels into the asm, but - then you're losing out on the main value of having asm in the - first place. - """ - try: - if isinstance(some_object, int): - some_object = script_parse_table[some_object] - if some_object.dependencies != None and not recompute: - global_dependencies.update(some_object.dependencies) - else: - some_object.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - return global_dependencies - except RuntimeError, e: - # 1552, 1291, 2075, 1552, 1291... - print "some_object is: " + str(some_object) - print "class type: " + str(some_object.__class__) - print "label name: " + str(some_object.label.name) - print "address: " + str(some_object.address) - print "asm is: \n\n" + to_asm(some_object) - raise e - -def isolate_incbins(): - "find each incbin line" - global incbin_lines, asm - incbin_lines = [] - for line in asm: - if line == "": continue - if line.count(" ") == len(line): continue - - # clean up whitespace at beginning of line - while line[0] == " ": - line = line[1:] - - if line[0:6] == "INCBIN" and "baserom.gbc" in line: - incbin_lines.append(line) - return incbin_lines - -def process_incbins(): - "parse incbin lines into memory" - global asm, incbin_lines, processed_incbins - # load asm if it isn't ready yet - if asm == [] or asm == None: - load_asm() - # get a list of incbins if that hasn't happened yet - if incbin_lines == [] or incbin_lines == None: - isolate_incbins() - # reset the global that this function creates - processed_incbins = {} - # for each incbin.. - for incbin in incbin_lines: - # reset this entry - processed_incbin = {} - # get the line number from the global asm line list - line_number = asm.index(incbin) - # forget about all the leading characters - partial_start = incbin[21:] - start = partial_start.split(",")[0].replace("$", "0x") - start = eval(start) - start_hex = hex(start).replace("0x", "$") - - partial_interval = incbin[21:].split(",")[1] - partial_interval = partial_interval.replace(";", "#") - partial_interval = partial_interval.replace("$", "0x").replace("0xx", "0x") - interval = eval(partial_interval) - interval_hex = hex(interval).replace("0x", "$").replace("x", "") - - end = start + interval - end_hex = hex(end).replace("0x", "$") - - processed_incbin = {"line_number": line_number, - "line": incbin, - "start": start, - "interval": interval, - "end": end, } - # don't add this incbin if the interval is 0 - if interval != 0: - processed_incbins[line_number] = processed_incbin - return processed_incbins - -def reset_incbins(): - "reset asm before inserting another diff" - global asm, incbin_lines, processed_incbins - asm = None - incbin_lines = [] - processed_incbins = {} - load_asm() - isolate_incbins() - process_incbins() - -def find_incbin_to_replace_for(address, debug=False, rom_file="../baserom.gbc"): - """returns a line number for which incbin to edit - if you were to insert bytes into main.asm""" - if type(address) == str: address = int(address, 16) - if not (0 <= address <= os.lstat(rom_file).st_size): - raise IndexError("address is out of bounds") - for incbin_key in processed_incbins.keys(): - incbin = processed_incbins[incbin_key] - start = incbin["start"] - end = incbin["end"] - if debug: - print "start is: " + str(start) - print "end is: " + str(end) - print "address is: " + str(type(address)) - print "checking.... " + hex(start) + " <= " + hex(address) + " <= " + hex(end) - if start <= address <= end: - return incbin_key - return None - -def split_incbin_line_into_three(line, start_address, byte_count, rom_file="../baserom.gbc"): - """ - splits an incbin line into three pieces. - you can replace the middle one with the new content of length bytecount - - start_address: where you want to start inserting bytes - byte_count: how many bytes you will be inserting - """ - if type(start_address) == str: start_address = int(start_address, 16) - if not (0 <= start_address <= os.lstat(rom_file).st_size): - raise IndexError("start_address is out of bounds") - if len(processed_incbins) == 0: - raise Exception("processed_incbins must be populated") - - original_incbin = processed_incbins[line] - start = original_incbin["start"] - end = original_incbin["end"] - - # start, end1, end2 (to be printed as start, end1 - end2) - if start_address - start > 0: - first = (start, start_address, start) - else: - first = (None) # skip this one because we're not including anything - - # this is the one you will replace with whatever content - second = (start_address, byte_count) - - third = (start_address + byte_count, end - (start_address + byte_count)) - - output = "" - - if first: - output += "INCBIN \"baserom.gbc\",$" + hex(first[0])[2:] + ",$" + hex(first[1])[2:] + " - $" + hex(first[2])[2:] + "\n" - output += "INCBIN \"baserom.gbc\",$" + hex(second[0])[2:] + "," + str(byte_count) + "\n" - output += "INCBIN \"baserom.gbc\",$" + hex(third[0])[2:] + ",$" + hex(third[1])[2:] # no newline - return output - -def generate_diff_insert(line_number, newline, debug=False): - """generates a diff between the old main.asm and the new main.asm - note: requires python2.7 i think? b/c of subprocess.check_output""" - global asm - original = "\n".join(line for line in asm) - newfile = deepcopy(asm) - newfile[line_number] = newline # possibly inserting multiple lines - newfile = "\n".join(line for line in newfile) - - # make sure there's a newline at the end of the file - if newfile[-1] != "\n": - newfile += "\n" - - original_filename = "ejroqjfoad.temp" - newfile_filename = "fjiqefo.temp" - - original_fh = open(original_filename, "w") - original_fh.write(original) - original_fh.close() - - newfile_fh = open(newfile_filename, "w") - newfile_fh.write(newfile) - newfile_fh.close() - - try: - from subprocess import CalledProcessError - except ImportError: - CalledProcessError = None - - try: - diffcontent = subprocess.check_output("diff -u ../main.asm " + newfile_filename, shell=True) - except (AttributeError, CalledProcessError): - p = subprocess.Popen(["diff", "-u", "../main.asm", newfile_filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = p.communicate() - diffcontent = out - - os.system("rm " + original_filename) - os.system("rm " + newfile_filename) - - if debug: print diffcontent - return diffcontent - -def apply_diff(diff, try_fixing=True, do_compile=True): - print "... Applying diff." - - # write the diff to a file - fh = open("temp.patch", "w") - fh.write(diff) - fh.close() - - # apply the patch - os.system("cp ../main.asm ../main1.asm") - os.system("patch ../main.asm temp.patch") - - # remove the patch - os.system("rm temp.patch") - - # confirm it's working - if do_compile: - try: - subprocess.check_call("cd ../; make clean; make", shell=True) - return True - except Exception, exc: - if try_fixing: - os.system("mv ../main1.asm ../main.asm") - return False - -class AsmLine: - # TODO: parse label lines - def __init__(self, line, bank=None): - self.line = line - self.bank = bank - def to_asm(self): - return self.line - -class Incbin: - def __init__(self, line, bank=None, debug=False): - self.line = line - self.bank = bank - self.replace_me = False - self.debug = debug - self.parse() - def parse(self): - incbin = self.line - partial_start = incbin[21:] - start = partial_start.split(",")[0].replace("$", "0x") - - if self.debug: - print "Incbin.parse -- line is: " + self.line - print "Incbin.parse -- partial_start is: " + partial_start - print "Incbin.parse -- start is: " + start - try: - start = eval(start) - except Exception, e: - print "start is: " + str(start) - raise Exception("problem with evaluating interval range: " + str(e)) - - start_hex = hex(start).replace("0x", "$") - - partial_interval = incbin[21:].split(",")[1] - partial_interval = partial_interval.replace(";", "#") - partial_interval = partial_interval.replace("$", "0x").replace("0xx", "0x") - interval = eval(partial_interval) - interval_hex = hex(interval).replace("0x", "$").replace("x", "") - - end = start + interval - end_hex = hex(end).replace("0x", "$") - - self.address = start - self.start_address = start - self.end_address = end - self.last_address = end - self.interval = interval - def to_asm(self): - if self.interval > 0: - return self.line - else: - return "" - def split(self, start_address, byte_count): - """splits this incbin into three separate incbins""" - if start_address < self.start_address or start_address > self.end_address: - raise Exception("this incbin doesn't handle this address") - incbins = [] - - if self.debug: - print "splitting an incbin ("+self.line+") into three at "+hex(start_address)+" for "+str(byte_count)+" bytes" - - # start, end1, end2 (to be printed as start, end1 - end2) - if (start_address - self.start_address) > 0: - first = (self.start_address, start_address, self.start_address) - incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x - $%.2x" % (first[0], first[1], first[2]))) - if self.debug: - print " " + incbins[0].line - else: - # skip this one because we're not including anything - first = None - - # this is the one you will replace with whatever content - second = (start_address, byte_count) - incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x" % (start_address, byte_count))) - incbins[-1].replace_me = True - if self.debug: - print " " + incbins[-1].line - - if (self.last_address - (start_address + byte_count)) > 0: - third = (start_address + byte_count, self.last_address - (start_address + byte_count)) - incbins.append(Incbin("INCBIN \"baserom.gbc\",$%.2x,$%.2x" % (third[0], third[1]))) - if self.debug: - print " " + incbins[-1].line - - return incbins - -class AsmSection: - def __init__(self, line): - self.bank_id = None - self.line = line - self.parse() - def parse(self): - line = self.line - - if not "bank" in line: - self.bank_id = -1 - self.address = -1 - self.last_address = None - self.end_address = None - return - - bank_id = int(line.split("\"")[1].split("bank")[1], 16) - self.bank_id = bank_id - start_address = bank_id * 0x4000 - end_address = (bank_id * 0x4000) + 0x4000 - 1 - - self.address = self.start_address = start_address - self.last_address = None - self.end_address = None - # this entity doesn't actually take up this space.. - # although it could be argued that lines should exist under this object - #self.address = self.start_address = start_address - #self.last_address = self.end_address = end_address - def to_asm(self): - return self.line - -new_asm = None -def load_asm2(filename="../main.asm", force=False): - """loads the asm source code into memory""" - global new_asm - if new_asm == None or force: - new_asm = Asm(filename=filename) - return new_asm - -class Asm: - """controls the overall asm output""" - def __init__(self, filename="../main.asm", debug=True): - self.parts = [] - self.labels = [] - self.filename = filename - self.debug = debug - self.load_and_parse() - def load_and_parse(self): - self.parts = [] - asm = open(self.filename, "r").read().split("\n") - asm_list = AsmList(asm) - bank = 0 - for line in asm_list: - if (line[0:6] == "INCBIN" or line[1:6] == "INCBIN") and not any([contaminant+"\"" in line for contaminant in [".2bpp", ".1bpp", ".asm", ".lz"]]): - thing = Incbin(line, bank=bank) - elif line[0:7] == "SECTION": - thing = AsmSection(line) - bank = thing.bank_id - else: - thing = AsmLine(line, bank=bank) - label = get_label_from_line(line) - if label: - laddress = get_address_from_line_comment(line) - thing.label = Label(name=label, address=laddress, object=thing, add_to_globals=False) - self.labels.append(thing.label) - self.parts.append(thing) - def is_label_name_in_file(self, label_name): - for llabel in self.labels: - if llabel.name == label_name: - return llabel - return False - def does_address_have_label(self, address): - """ - Checks if an address has a label. - """ - # either something will directly have the address - # or- it's possibel that no label was given - # or there will be an Incbin that covers the range - for part in self.parts: - if isinstance(part, Incbin) and part.start_address <= address <= part.end_address: - return False - elif hasattr(part, "address") and part.address == address and hasattr(part, "label"): - return part.label - - return None - def insert(self, new_object): - if isinstance(new_object, ScriptPointerLabelParam): - # its' probably being injected in some get_dependencies() somewhere - print "don't know why ScriptPointerLabelParam is getting to this point?" - return - - # first some validation - if not hasattr(new_object, "address"): - print "object needs to have an address property: " + str(new_object) - return - - start_address = new_object.address - - # skip this dragon shrine script calling itself - # what about other scripts that call themselves ? - if start_address in lousy_dragon_shrine_hack: - print "skipping 0x18d079 in dragon shrine for a lousy hack" - return - - if not hasattr(new_object, "label") and hasattr(new_object, "is_valid") and not new_object.is_valid(): - return - - debugmsg = "object is " + new_object.label.name + " type="+str(new_object.__class__)+" new_object="+str(new_object) - debugmsg += " label = " + new_object.label.name - debugmsg += " start_address="+hex(start_address)#+" end_address="+hex(end_address) - - if not hasattr(new_object, "last_address"): - print debugmsg - raise Exception("object needs to have a last_address property") - end_address = new_object.last_address - debugmsg += " last_address="+hex(end_address) - - # check if the object is already inserted - if new_object in self.parts: - print "object was previously inserted ("+str(new_object)+"; " + hex(new_object.address) + ")" - return - # check by label - other_obj = self.is_label_name_in_file(new_object.label.name) - if other_obj: - other_obj = other_obj.object - print "object was previously inserted ("+new_object.label.name+" at "+hex(new_object.address)+") by "+other_obj.label.name+" at "+hex(other_obj.address) - return - # check by address - #if self.does_address_have_label(new_object.address): - # print "object's address is already used ("+str(new_object)+") at "+hex(new_object.address)+" label="+new_object.label.name - # return - - if self.debug: - print debugmsg - del debugmsg - if (end_address < start_address) or ((end_address - start_address) < 0): - if not self.debug: - print "object is new_object="+str(new_object) - print "start_address="+hex(start_address)+" end_address="+hex(end_address) - if hasattr(new_object, "to_asm"): - print to_asm(new_object) - raise Exception("Asm.insert was given an object with a bad address range") - - # 1) find which object needs to be replaced - # or - # 2) find which object goes after it - found = False - for object in list(self.parts): - # skip objects without a defined interval (like a comment line) - if not hasattr(object, "address") or not hasattr(object, "last_address"): - continue - # skip an AsmSection - if isinstance(object, AsmSection): - continue - # replace an incbin with three incbins, replace middle incbin with whatever - elif isinstance(object, Incbin) and (object.address <= start_address < object.last_address): - # split up the incbin into three segments - incbins = object.split(start_address, end_address - start_address) - # figure out which incbin to replace with the new object - if incbins[0].replace_me: - index = 0 - else: # assume incbins[1].replace_me (the middle one) - index = 1 - # replace that index with the new_object - incbins[index] = new_object - # insert these incbins into self.parts - gindex = self.parts.index(object) - self.parts = self.parts[:gindex] + incbins + self.parts[gindex:] - self.parts.remove(object) - found = True - break - elif object.address <= start_address < object.last_address: - print "this is probably a script that is looping back on itself?" - found = True - break - # insert before the current object - elif object.address > end_address: - #insert_before = index of object - index = self.parts.index(object) - self.parts.insert(index, new_object) - found = True - break - if not found: - raise Exception("unable to insert object into Asm") - self.labels.append(new_object.label) - return True - def insert_with_dependencies(self, input): - if type(input) == list: - input_objects = input - else: - input_objects = [input] - - for object0 in input_objects: - global_dependencies = set([object0]) - poopbutt = get_dependencies_for(object0, global_dependencies=global_dependencies, recompute=False) - objects = global_dependencies - objects.update(poopbutt) - new_objects = copy(objects) - for object in objects: - if hasattr(object, "dependencies") and object.dependencies == None: - new_objects.update(object.get_dependencies()) - for object in new_objects: - if isinstance(object, ScriptPointerLabelParam): - continue - #if object in self.parts: - # if self.debug: - # print "already inserted -- object.__class__="+str(object.__class__)+" object is: "+str(object)+\ - # " for object.__class__="+str(object0.__class__)+" object="+str(object0) - # continue - if self.debug: - print " object is: " + str(object) - self.insert(object) - - # just some old debugging - #if object.label.name == "UnknownText_0x60128": - # raise Exception("debugging...") - #elif object.label.name == "UnknownScript_0x60011": - # raise Exception("debugging.. dependencies are: " + str(object.dependencies) + " versus: " + str(object.get_dependencies())) - def insert_single_with_dependencies(self, object): - self.insert_with_dependencies(object) - def insert_multiple_with_dependencies(self, objects): - self.insert_with_dependencies(objects) - def insert_all(self, limit=100): - count = 0 - for each in script_parse_table.items(): - if count == limit: break - object = each[1] - if type(object) == str: continue - self.insert_single_with_dependencies(object) - count += 1 - def insert_and_dump(self, limit=100, filename="output.txt"): - self.insert_all(limit=limit) - self.dump(filename=filename) - def dump(self, filename="output.txt"): - fh = open(filename, "w") - newlines_before_next_obj_requested = 0 - newlines_before_next_obj_given = 0 - - current_requested_newlines_before = 0 - current_requested_newlines_after = 0 - previous_requested_newlines_before = 0 - previous_requested_newlines_after = 0 - - written_newlines = 0 - write_something = False - first = True - last = None - for each in self.parts: - asm = "" - previous_requested_newlines_after = current_requested_newlines_after - current_requested_newlines_before = current_requested_newlines_after - - write_something = True - if (isinstance(each, str) and each == "") or (isinstance(each, AsmLine) and each.line == ""): - current_requested_newlines_before = 0 - if current_requested_newlines_after < 2: - current_requested_newlines_after += 1 - write_something = False - elif (isinstance(each, str) and each != "") or (isinstance(each, AsmLine) and each.line != ""): - if isinstance(each, AsmLine): - asm = each.to_asm() - elif isinstance(each, str): - asm = each - current_requested_newlines_before = 0 - current_requested_newlines_after = 1 - elif isinstance(each, AsmSection) or isinstance(each, Incbin) or hasattr(each, "to_asm"): - if isinstance(each, AsmSection) or isinstance(each, Incbin): - asm = each.to_asm() - else: - asm = to_asm(each) - current_requested_newlines_before = 2 - current_requested_newlines_after = 2 - else: - raise Exception("dunno what to do with("+str(each)+") in Asm.parts") - - if write_something: - if not first: - newlines_before = max([current_requested_newlines_before, previous_requested_newlines_after]) - while written_newlines < newlines_before: - fh.write("\n") - written_newlines += 1 - else: - first = False - fh.write(asm) - written_newlines = 0 - last = each - - # make sure the file ends with a newline - fh.write("\n") - -def list_things_in_bank(bank): - objects = [] - for blah in script_parse_table.items(): - object = blah[1] - if hasattr(object, "address") and calculate_bank(object.address) == bank: - objects.append(object) - return objects - -def list_texts_in_bank(bank): - """ - Narrows down the list of objects that you will be inserting into Asm. - """ - if len(all_texts) == 0: - raise Exception("all_texts is blank.. run_main() will populate it") - - assert bank != None, "list_texts_in_banks must be given a particular bank" - - assert 0 <= bank < 0x80, "bank doesn't exist in the ROM" - - texts = [] - for text in all_texts: - if calculate_bank(text.address) == bank: - texts.append(text) - - return texts - -def list_movements_in_bank(bank): - """ - Narrows down the list of objects to speed up Asm insertion. - """ - if len(all_movements) == 0: - raise Exception("all_movements is blank.. run_main() will populate it") - - assert bank != None, "list_movements_in_bank must be given a particular bank" - assert 0 <= bank < 0x80, "bank doesn't exist in the ROM (out of bounds)" - - movements = [] - for movement in all_movements: - if calculate_bank(movement.address) == bank: - movements.append(movement) - return movements - -def dump_asm_for_texts_in_bank(bank, start=50, end=100): - """ - Simple utility to help with dumping texts into a particular bank. This is - helpful for figuring out which text is breaking that bank. - """ - # load and parse the ROM if necessary - if rom == None or len(rom) <= 4: - load_rom() - run_main() - - # get all texts - # first 100 look okay? - texts = list_texts_in_bank(bank)[start:end] - - # create a new dump - asm = Asm() - - # start the insertion process - asm.insert_multiple_with_dependencies(texts) - - # start dumping - asm.dump() - - print "done dumping texts for bank $%.2x" % (bank) - -def dump_asm_for_movements_in_bank(bank, start=0, end=100): - if rom == None or len(rom) <= 4: - load_rom() - run_main() - - movements = list_movements_in_bank(bank)[start:end] - - asm = Asm() - asm.insert_with_dependencies(movements) - asm.dump() - print "done dumping movements for bank $%.2x" % (bank) - -def dump_things_in_bank(bank, start=50, end=100): - """ - is helpful for figuring out which object is breaking that bank. - """ - # load and parse the ROM if necessary - if rom == None or len(rom) <= 4: - load_rom() - run_main() - - things = list_things_in_bank(bank)[start:end] - - # create a new dump - asm = Asm() - - # start the insertion process - asm.insert_with_dependencies(things) - - # start dumping - asm.dump() - - print "done dumping things for bank $%.2x" % (bank) - -def index(seq, f): - """return the index of the first item in seq - where f(item) == True.""" - return next((i for i in xrange(len(seq)) if f(seq[i])), None) - -def analyze_intervals(): - """find the largest baserom.gbc intervals""" - global asm, processed_incbins - if asm == None: - load_asm() - if processed_incbins == {}: - isolate_incbins() - process_incbins() - results = [] - ordered_keys = sorted(processed_incbins, key=lambda entry: processed_incbins[entry]["interval"]) - ordered_keys.reverse() - for key in ordered_keys: - results.append(processed_incbins[key]) - return results - -all_labels = [] -def write_all_labels(all_labels, filename="labels.json"): - fh = open(filename, "w") - fh.write(json.dumps(all_labels)) - fh.close() - return True - -from wram import wram_labels -def get_ram_label(address): - """returns a label assigned to a particular ram address""" - if address in wram_labels.keys(): - return wram_labels[address][-1] - return None - -def get_label_for(address): - """returns a label assigned to a particular rom address""" - global all_labels - - if address == None: - return None - if type(address) != int: - raise Exception("get_label_for requires an integer address, got: " + str(type(address))) - - # lousy hack to get around recursive scripts in dragon shrine - if address in lousy_dragon_shrine_hack: - return None - - # the old way - for thing in all_labels: - if thing["address"] == address: - return thing["label"] - - # the new way - obj = script_parse_table[address] - if obj: - if hasattr(obj, "label"): - return obj.label.name - else: - return "AlreadyParsedNoDefaultUnknownLabel_" + hex(address) - - #return "NotYetParsed_"+hex(address) - if address > 0x7FFF: - value = 0x4000 + (address % 0x4000) - return "$%.2x"%(value) - else: - return "$%.2x"%(address) - -# all_new_labels is a temporary replacement for all_labels, -# at least until the two approaches are merged in the code base. -all_new_labels = [] - -class Label: - """ - Every object in script_parse_table is given a label. - - This label is simply a way to keep track of what objects have - been previously written to file. - """ - def __init__(self, name=None, address=None, line_number=None, object=None, is_in_file=None, address_is_in_file=None, add_to_globals=True): - assert address != None, "need an address" - assert is_valid_address(address), "address must be valid" - assert object != None, "need an object to relate with" - - self.address = address - self.object = object - - # label might not be in the file yet - self.line_number = line_number - - # -- These were some old attempts to check whether the label - # -- was already in use. They work, but the other method is - # -- better. - # - # check if the label is in the file already - # check if the address of this label is already in use - - self.is_in_file = is_in_file - - self.address_is_in_file = address_is_in_file - - if name == None: - name = object.base_label + "_" + hex(object.address) - - self.name = name - - if add_to_globals: - all_new_labels.append(self) - - def check_is_in_file(self): - """ - This method checks if the label appears in the file based on the - entries to the Asm.parts list. - """ - # assert new_asm != None, "new_asm should be an instance of Asm" - load_asm2() - is_in_file = new_asm.is_label_name_in_file(self.name) - self.is_in_file = is_in_file - return is_in_file - - def check_address_is_in_file(self): - """ - Checks if the address is in use by another label. - """ - load_asm2() - self.address_is_in_file = new_asm.does_address_have_label(self.address) - return self.address_is_in_file - - def old_check_address_is_in_file(self): - """ - Checks whether or not the address of the object is already in the file. - This might happen if the label name is different but the address is the - same. Another scenario is that the label is already used, but at a - different address. - - This method works by looking at the INCBINs. When there is - an INCBIN that covers this address in the file, then there - is no label at this address yet (or there is, but we can - easily add another label in front of the incbin or something), - and when there is no INCBIN that has this address, then we - know that something is already using this address. - """ - if processed_incbins == {}: - process_incbins() - - incbin = find_incbin_to_replace_for(self.address) - - if incbin == None: - return True - else: - return False - - def make_label(self): - """ - Generates a label name based on parents and self.object. - """ - object = self.object - name = object.make_label() - return name - -def find_labels_without_addresses(): - """scans the asm source and finds labels that are unmarked""" - without_addresses = [] - for (line_number, line) in enumerate(asm): - if line_has_label(line): - label = get_label_from_line(line) - if not line_has_comment_address(line): - without_addresses.append({"line_number": line_number, "line": line, "label": label}) - return without_addresses - -label_errors = "" -def get_labels_between(start_line_id, end_line_id, bank): - labels = [] - #label = { - # "line_number": 15, - # "bank": 32, - # "label": "PalletTownText1", - # "offset": 0x5315, - # "address": 0x75315, - #} - if asm == None: - load_asm() - sublines = asm[start_line_id : end_line_id + 1] - for (current_line_offset, line) in enumerate(sublines): - # skip lines without labels - if not line_has_label(line): continue - # reset some variables - line_id = start_line_id + current_line_offset - line_label = get_label_from_line(line) - address = None - offset = None - # setup a place to store return values from line_has_comment_address - returnable = {} - # get the address from the comment - has_comment = line_has_comment_address(line, returnable=returnable, bank=bank) - # skip this line if it has no address in the comment - if not has_comment: continue - # parse data from line_has_comment_address - address = returnable["address"] - bank = returnable["bank"] - offset = returnable["offset"] - # dump all this info into a single structure - label = { - "line_number": line_id, - "bank": bank, - "label": line_label, - "offset": offset, - "address": address, - } - # store this structure - labels.append(label) - return labels - -def scan_for_predefined_labels(debug=False): - """looks through the asm file for labels at specific addresses, - this relies on the label having its address after. ex: - - ViridianCity_h: ; 0x18357 to 0x18384 (45 bytes) (bank=6) (id=1) - PalletTownText1: ; 4F96 0x18f96 - ViridianCityText1: ; 0x19102 - - It would be more productive to use rgbasm to spit out all label - addresses, but faster to write this script. rgbasm would be able - to grab all label addresses better than this script.. - """ - global all_labels - all_labels = [] - bank_intervals = {} - - if asm == None: - load_asm() - - # figure out line numbers for each bank - for bank_id in range(0x7F+1): - abbreviation = ("%.x" % (bank_id)).upper() - abbreviation_next = ("%.x" % (bank_id+1)).upper() - if bank_id == 0: - abbreviation = "0" - abbreviation_next = "1" - - # calculate the start/stop line numbers for this bank - start_line_id = index(asm, lambda line: "\"bank" + abbreviation.lower() + "\"" in line.lower()) - if bank_id != 0x7F: - end_line_id = index(asm, lambda line: "\"bank" + abbreviation_next.lower() + "\"" in line.lower()) - end_line_id += 1 - else: - end_line_id = len(asm) - 1 - - if debug: - output = "bank" + abbreviation + " starts at " - output += str(start_line_id) - output += " to " - output += str(end_line_id) - print output - - # store the start/stop line number for this bank - bank_intervals[bank_id] = {"start": start_line_id, - "end": end_line_id,} - # for each bank.. - for bank_id in bank_intervals.keys(): - # get the start/stop line number - bank_data = bank_intervals[bank_id] - start_line_id = bank_data["start"] - end_line_id = bank_data["end"] - # get all labels between these two lines - labels = get_labels_between(start_line_id, end_line_id, bank_id) - # bank_intervals[bank_id]["labels"] = labels - all_labels.extend(labels) - write_all_labels(all_labels) - return all_labels - -def run_main(): - # read the rom and figure out the offsets for maps - direct_load_rom() - load_map_group_offsets() - - # add the offsets into our map structure, why not (johto maps only) - [map_names[map_group_id+1].update({"offset": offset}) for map_group_id, offset in enumerate(map_group_offsets)] - - # parse map header bytes for each map - parse_all_map_headers() - - # find trainers based on scripts and map headers - # this can only happen after parsing the entire map and map scripts - find_trainer_ids_from_scripts() - - # and parse the main TrainerGroupTable once we know the max number of trainers - #global trainer_group_table - trainer_group_table = TrainerGroupTable() - - # improve duplicate trainer names - make_trainer_group_name_trainer_ids(trainer_group_table) - -# just a helpful alias -main = run_main - -# when you load the module.. parse everything -if __name__ == "crystal": - pass |