ref: f5a6c18b89d71ea3cad792f2f2f2af49a1505172
parent: 89a76e887d9f40ecb8008fe503fa972567f38cb1
author: Bryan Bishop <kanzure@gmail.com>
date: Mon Mar 5 19:15:35 EST 2012
python tooling
--- /dev/null
+++ b/extras/crystal.py
@@ -1,0 +1,1301 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#author: Bryan Bishop <kanzure@gmail.com>
+#date: 2012-03-04
+#utilities to help disassemble pokémon crystal
+import sys
+from copy import copy
+
+#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"
+
+#this is straight out of ../textpre.py because i'm lazy
+#see jap_chars for overrides if you are in japanese mode?
+chars = {+ 0x50: "@",
+ 0x54: "#",
+ 0x75: "…",
+
+ 0x79: "┌",
+ 0x7A: "─",
+ 0x7B: "┐",
+ 0x7C: "│",
+ 0x7D: "└",
+ 0x7E: "┘",
+
+ 0x74: "№",
+
+ 0x7F: " ",
+ 0x80: "A",
+ 0x81: "B",
+ 0x82: "C",
+ 0x83: "D",
+ 0x84: "E",
+ 0x85: "F",
+ 0x86: "G",
+ 0x87: "H",
+ 0x88: "I",
+ 0x89: "J",
+ 0x8A: "K",
+ 0x8B: "L",
+ 0x8C: "M",
+ 0x8D: "N",
+ 0x8E: "O",
+ 0x8F: "P",
+ 0x90: "Q",
+ 0x91: "R",
+ 0x92: "S",
+ 0x93: "T",
+ 0x94: "U",
+ 0x95: "V",
+ 0x96: "W",
+ 0x97: "X",
+ 0x98: "Y",
+ 0x99: "Z",
+ 0x9A: "(",+ 0x9B: ")",
+ 0x9C: ":",
+ 0x9D: ";",
+ 0x9E: "[",
+ 0x9F: "]",
+ 0xA0: "a",
+ 0xA1: "b",
+ 0xA2: "c",
+ 0xA3: "d",
+ 0xA4: "e",
+ 0xA5: "f",
+ 0xA6: "g",
+ 0xA7: "h",
+ 0xA8: "i",
+ 0xA9: "j",
+ 0xAA: "k",
+ 0xAB: "l",
+ 0xAC: "m",
+ 0xAD: "n",
+ 0xAE: "o",
+ 0xAF: "p",
+ 0xB0: "q",
+ 0xB1: "r",
+ 0xB2: "s",
+ 0xB3: "t",
+ 0xB4: "u",
+ 0xB5: "v",
+ 0xB6: "w",
+ 0xB7: "x",
+ 0xB8: "y",
+ 0xB9: "z",
+ 0xC0: "Ä",
+ 0xC1: "Ö",
+ 0xC2: "Ü",
+ 0xC3: "ä",
+ 0xC4: "ö",
+ 0xC5: "ü",
+ 0xD0: "'d",
+ 0xD1: "'l",
+ 0xD2: "'m",
+ 0xD3: "'r",
+ 0xD4: "'s",
+ 0xD5: "'t",
+ 0xD6: "'v",
+ 0xE0: "'",
+ 0xE3: "-",
+ 0xE6: "?",
+ 0xE7: "!",
+ 0xE8: ".",
+ 0xE9: "&",
+ 0xEA: "é",
+ 0xEB: "→",
+ 0xEF: "♂",
+ 0xF0: "¥",
+ 0xF1: "×",
+ 0xF3: "/",
+ 0xF4: ",",
+ 0xF5: "♀",
+ 0xF6: "0",
+ 0xF7: "1",
+ 0xF8: "2",
+ 0xF9: "3",
+ 0xFA: "4",
+ 0xFB: "5",
+ 0xFC: "6",
+ 0xFD: "7",
+ 0xFE: "8",
+ 0xFF: "9",
+}
+
+#override whatever defaults for japanese symbols
+jap_chars = copy(chars)
+jap_chars.update({+ 0x05: "ガ",
+ 0x06: "ギ",
+ 0x07: "グ",
+ 0x08: "ゲ",
+ 0x09: "ゴ",
+ 0x0A: "ザ",
+ 0x0B: "ジ",
+ 0x0C: "ズ",
+ 0x0D: "ゼ",
+ 0x0E: "ゾ",
+ 0x0F: "ダ",
+ 0x10: "ヂ",
+ 0x11: "ヅ",
+ 0x12: "デ",
+ 0x13: "ド",
+ 0x19: "バ",
+ 0x1A: "ビ",
+ 0x1B: "ブ",
+ 0x1C: "ボ",
+ 0x26: "が",
+ 0x27: "ぎ",
+ 0x28: "ぐ",
+ 0x29: "げ",
+ 0x2A: "ご",
+ 0x2B: "ざ",
+ 0x2C: "じ",
+ 0x2D: "ず",
+ 0x2E: "ぜ",
+ 0x2F: "ぞ",
+ 0x30: "だ",
+ 0x31: "ぢ",
+ 0x32: "づ",
+ 0x33: "で",
+ 0x34: "ど",
+ 0x3A: "ば",
+ 0x3B: "び",
+ 0x3C: "ぶ",
+ 0x3D: "べ",
+ 0x3E: "ぼ",
+ 0x40: "パ",
+ 0x41: "ピ",
+ 0x42: "プ",
+ 0x43: "ポ",
+ 0x44: "ぱ",
+ 0x45: "ぴ",
+ 0x46: "ぷ",
+ 0x47: "ぺ",
+ 0x48: "ぽ",
+ 0x80: "ア",
+ 0x81: "イ",
+ 0x82: "ウ",
+ 0x83: "エ",
+ 0x84: "ォ",
+ 0x85: "カ",
+ 0x86: "キ",
+ 0x87: "ク",
+ 0x88: "ケ",
+ 0x89: "コ",
+ 0x8A: "サ",
+ 0x8B: "シ",
+ 0x8C: "ス",
+ 0x8D: "セ",
+ 0x8E: "ソ",
+ 0x8F: "タ",
+ 0x90: "チ",
+ 0x91: "ツ",
+ 0x92: "テ",
+ 0x93: "ト",
+ 0x94: "ナ",
+ 0x95: "ニ",
+ 0x96: "ヌ",
+ 0x97: "ネ",
+ 0x98: "ノ",
+ 0x99: "ハ",
+ 0x9A: "ヒ",
+ 0x9B: "フ",
+ 0x9C: "ホ",
+ 0x9D: "マ",
+ 0x9E: "ミ",
+ 0x9F: "ム",
+ 0xA0: "メ",
+ 0xA1: "モ",
+ 0xA2: "ヤ",
+ 0xA3: "ユ",
+ 0xA4: "ヨ",
+ 0xA5: "ラ",
+ 0xA6: "ル",
+ 0xA7: "レ",
+ 0xA8: "ロ",
+ 0xA9: "ワ",
+ 0xAA: "ヲ",
+ 0xAB: "ン",
+ 0xAC: "ッ",
+ 0xAD: "ャ",
+ 0xAE: "ュ",
+ 0xAF: "ョ",
+ 0xB0: "ィ",
+ 0xB1: "あ",
+ 0xB2: "い",
+ 0xB3: "う",
+ 0xB4: "え",
+ 0xB5: "お",
+ 0xB6: "か",
+ 0xB7: "き",
+ 0xB8: "く",
+ 0xB9: "け",
+ 0xBA: "こ",
+ 0xBB: "さ",
+ 0xBC: "し",
+ 0xBD: "す",
+ 0xBE: "せ",
+ 0xBF: "そ",
+ 0xC0: "た",
+ 0xC1: "ち",
+ 0xC2: "つ",
+ 0xC3: "て",
+ 0xC4: "と",
+ 0xC5: "な",
+ 0xC6: "に",
+ 0xC7: "ぬ",
+ 0xC8: "ね",
+ 0xC9: "の",
+ 0xCA: "は",
+ 0xCB: "ひ",
+ 0xCC: "ふ",
+ 0xCD: "へ",
+ 0xCE: "ほ",
+ 0xCF: "ま",
+ 0xD0: "み",
+ 0xD1: "む",
+ 0xD2: "め",
+ 0xD3: "も",
+ 0xD4: "や",
+ 0xD5: "ゆ",
+ 0xD6: "よ",
+ 0xD7: "ら",
+ 0xD8: "り",
+ 0xD9: "る",
+ 0xDA: "れ",
+ 0xDB: "ろ",
+ 0xDC: "わ",
+ 0xDD: "を",
+ 0xDE: "ん",
+ 0xDF: "っ",
+ 0xE0: "ゃ",
+ 0xE1: "ゅ",
+ 0xE2: "ょ",
+ 0xE3: "ー",
+})
+
+#some of the japanese characters can probably fit into the english table
+#without overriding any of the other mappings.
+for key, value in jap_chars.items():
+ if key not in chars.keys():
+ chars[key] = value
+
+def map_name_cleaner(input):
+ """generate a valid asm label for a given map name"""
+ return input.replace(":", "").\+ replace("(", "").\+ replace(")", "").\+ replace("'", "").\+ replace("/", "").\+ replace(".", "").\+ replace("Pokémon Center", "PokeCenter").\+ replace(" ", "")+
+class RomStr(str):
+ """simple wrapper to prevent a giant rom from being shown on screen"""
+ def __repr__(self):
+ return "RomStr(too long)"
+
+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 load_rom(filename="../baserom.gbc"):
+ """loads bytes into memory"""
+ global rom
+ file_handler = open(filename, "r")
+ rom = RomStr(file_handler.read())
+ file_handler.close()
+ return rom
+
+def rom_interval(offset, length, strings=True):
+ """returns hex values for the rom starting at offset until offset+length"""
+ global rom
+ returnable = []
+ for byte in rom[offset:offset+length]:
+ if strings:
+ returnable.append(hex(ord(byte)))
+ else:
+ returnable.append(ord(byte))
+ return returnable
+
+def rom_until(offset, byte, strings=True):
+ """returns hex values from rom starting at offset until the given byte"""
+ global rom
+ return rom_interval(offset, rom.find(chr(byte), offset) - offset, strings=strings)
+
+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
+ 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
+
+def calculate_bank(address):
+ """you are too lazy to divide on your own?"""
+ if type(address) == str:
+ address = int(address, 16)
+ return int(address) / 0x4000
+
+def calculate_pointer(short_pointer, bank):
+ """calculates the full address given a 4-byte pointer and bank byte"""
+ short_pointer = int(short_pointer)
+ bank = int(bank)
+ pointer = short_pointer - 0x4000 + (bank * 0x4000)
+ return pointer
+
+def parse_script_at(address):
+ """parses a script-engine script"""
+ return {}+
+def parse_warp_bytes(some_bytes):
+ """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
+def parse_xy_trigger_bytes(some_bytes, bank=None):
+ """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)
+ script = parse_script_at(script_address)
+
+ 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
+def parse_signpost_bytes(some_bytes, bank=None):
+ """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)][??]
+ """
+ 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)
+ 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
+ if bank:
+ script_address = calculate_pointer(script_pointer, bank)
+ script = parse_script_at(script)
+ signposts.append({+ "y": y,
+ "x": x,
+ "func": func,
+ "script_ptr": script_pointer,
+ "script_pointer": {"1": script_ptr_byte1, "2": script_ptr_byte2},+ "script_address": script_address,
+ "script": script,
+ })
+ return signposts
+def parse_people_event_bytes(some_bytes, address=None): #max of 14 people per map?
+ """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)
+
+ #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?
+ script_address = None
+ script = None
+ if bank:
+ script_address = calculate_pointer(script_pointer, bank)
+ script = parse_script_at(script_address)
+
+ #take the script pointer
+
+ #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)
+
+ people_events.append({+ "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},
+ "script_address": script_address,
+ "script": script, #parsed script.. hah!
+ #"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
+ })
+ return people_events
+
+class MapEventElement():
+ def __init__(self, *args, **kwargs):
+ if len(args) == 1:
+ if isinstance(args[0], list):
+ if len(args[0]) != self.__class__.standard_size:
+ raise "input has the wrong size"
+ #convert all of the list elements to integers
+ ints = []
+ for byte in args[0]:
+ ints.append(int(byte, 16))
+ #parse using the class default method
+ events = self.__class__.parse_func(ints)
+ for key, value in events[0]:
+ setattr(self, key, value)
+ else:
+ raise "dunno how to handle this positional input"
+ elif len(kwargs.keys()) != 0:
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+ else:
+ raise "dunno how to handle given input"
+class Warp(MapEventElement):
+ standard_size = warp_byte_size
+ parse_func = parse_warp_bytes
+class Trigger(MapEventElement):
+ standard_size = trigger_byte_size
+ parse_func = parse_xy_trigger_bytes
+class Signpost(MapEventElement):
+ standard_size = signpost_byte_size
+ parse_func = parse_signpost_bytes
+class PeopleEvent(MapEventElement):
+ standard_size = people_event_byte_size
+ parse_func = parse_people_event_bytes
+
+def parse_map_header_at(address):
+ """parses an arbitrary map header at some address"""
+ bytes = rom_interval(address, map_header_byte_size, strings=False)
+ 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] #pokégear 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,
+ }
+ map_header.update(parse_second_map_header_at(second_map_header_address))
+ map_header.update(parse_map_event_header_at(map_header["event_address"]))
+ #maybe this next one should be under the "scripts" key?
+ map_header.update(parse_map_script_header_at(map_header["script_address"]))
+ return map_header
+
+def parse_second_map_header_at(address):
+ """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,
+ }
+
+def parse_map_event_header_at(address):
+ """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": 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": parse_xy_trigger_bytes(triggers, bank=bank)})+
+ #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": parse_signpost_bytes(signposts, bank=bank)})+
+ #people events
+ people_event_count = ord(rom[after_signposts])
+ people_event_byte_count = people_event_byte_size * people_event_count
+ people_events = rom_interval(after_signposts+1, people_event_byte_count)
+ returnable.update({"people_event_count": people_event_count, "people_events": parse_people_event_bytes(people_events, address=after_signposts+1)})+
+ return returnable
+
+def parse_map_script_header_at(address):
+ """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.
+
+ 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
+ """
+ #[[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):
+ byte1 = trigger_pointer[0]
+ byte2 = trigger_pointer[1]
+ ptr = byte1 + (byte2 << 8)
+ trigger_address = calculate_pointer(ptr, calculate_bank(address))
+ trigger_script = parse_script_at(trigger_address)
+ 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):
+ 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_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 parse_all_map_headers():
+ """calls parse_map_header_at for each map in each map group"""
+ global map_names
+ if not map_names[1].has_key("offset"):+ raise "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():
+ map_header_offset = offset + ((map_id - 1) * map_header_byte_size)
+ print "map_group is: " + str(group_id) + " map_id is: " + str(map_id)
+ parsed_map = parse_map_header_at(map_header_offset)
+ map_names[group_id][map_id].update(parsed_map)
+ map_names[group_id][map_id]["header_offset"] = map_header_offset
+
+#map names with no labels will be generated at the end of the structure
+map_names = {+ 1: {+ 0x1: {"name": "Olivine Pokémon Center 1F",+ "label": "OlivinePokeCenter1F"},
+ 0x2: {"name": "Olivine Gym"},+ 0x3: {"name": "Olivine Voltorb House"},+ 0x4: {"name": "Olivine House Beta"},+ 0x5: {"name": "Olivine Punishment Speech House"},+ 0x6: {"name": "Olivine Good Rod House"},+ 0x7: {"name": "Olivine Cafe"},+ 0x8: {"name": "Olivine Mart"},+ 0x9: {"name": "Route 38 Ecruteak Gate"},+ 0xA: {"name": "Route 39 Barn"},+ 0xB: {"name": "Route 39 Farmhouse"},+ 0xC: {"name": "Route 38"},+ 0xD: {"name": "Route 39"},+ 0xE: {"name": "Olivine City"},+ },
+ 2: {+ 0x1: {"name": "Mahogany Red Gyarados Speech House"},+ 0x2: {"name": "Mahogany Gym"},+ 0x3: {"name": "Mahogany Pokémon Center 1F",+ "label": "MahoganyPokeCenter1F"},
+ 0x4: {"name": "Route 42 Ecruteak Gate"},+ 0x5: {"name": "Route 42"},+ 0x6: {"name": "Route 43"},+ 0x7: {"name": "Mahogany Town"},+ },
+ 3: {+ 0x1: {"name": "Sprout Tower 1F"},+ 0x2: {"name": "Sprout Tower 2F"},+ 0x3: {"name": "Sprout Tower 3F"},+ 0x4: {"name": "Tin Tower 1F"},+ 0x5: {"name": "Tin Tower 2F"},+ 0x6: {"name": "Tin Tower 3F"},+ 0x7: {"name": "Tin Tower 4F"},+ 0x8: {"name": "Tin Tower 5F"},+ 0x9: {"name": "Tin Tower 6F"},+ 0xA: {"name": "Tin Tower 7F"},+ 0xB: {"name": "Tin Tower 8F"},+ 0xC: {"name": "Tin Tower 9F"},+ 0xD: {"name": "Bured Tower 1F"},+ 0xE: {"name": "Burned Tower B1F"},+ 0xF: {"name": "National Park"},+ 0x10: {"name": "National Park Bug Contest"},+ 0x11: {"name": "Radio Tower 1F"},+ 0x12: {"name": "Radio Tower 2F"},+ 0x13: {"name": "Radio Tower 3F"},+ 0x14: {"name": "Radio Tower 4F"},+ 0x15: {"name": "Radio Tower 5F"},+ 0x16: {"name": "Ruins of Alph Outside"},+ 0x17: {"name": "Ruins of Alph Ho-oh Chamber"},+ 0x18: {"name": "Ruins of Alph Kabuto Chamber"},+ 0x19: {"name": "Ruins of Alph Omanyte Chamber"},+ 0x1A: {"name": "Ruins of Alph Aerodactyl Chamber"},+ 0x1B: {"name": "Ruins of Alph Inner Chamber"},+ 0x1C: {"name": "Ruins of Alph Research Center"},+ 0x1D: {"name": "Ruins of Alph Ho-oh Item Room"},+ 0x1E: {"name": "Ruins of Alph Kabuto Item Room"},+ 0x1F: {"name": "Ruins of Alph Omanyte Item Room"},+ 0x20: {"name": "Ruins of Alph Aerodactyl Item Room"},+ 0x21: {"name": "Ruins of Alph Ho-Oh Word Room"},+ 0x22: {"name": "Ruins of Alph Kabuto Word Room"},+ 0x23: {"name": "Ruins of Alph Omanyte Word Room"},+ 0x24: {"name": "Ruins of Alph Aerodactyl Word Room"},+ 0x25: {"name": "Union Cave 1F"},+ 0x26: {"name": "Union Cave B1F"},+ 0x27: {"name": "Union Cave B2F"},+ 0x28: {"name": "Slowpoke Well B1F"},+ 0x29: {"name": "Slowpoke Well B2F"},+ 0x2A: {"name": "Olivine Lighthouse 1F"},+ 0x2B: {"name": "Olivine Lighthouse 2F"},+ 0x2C: {"name": "Olivine Lighthouse 3F"},+ 0x2D: {"name": "Olivine Lighthouse 4F"},+ 0x2E: {"name": "Olivine Lighthouse 5F"},+ 0x2F: {"name": "Olivine Lighthouse 6F"},+ 0x30: {"name": "Mahogany Mart 1F"},+ 0x31: {"name": "Team Rocket Base B1F"},+ 0x32: {"name": "Team Rocket Base B2F"},+ 0x33: {"name": "Team Rocket Base B3F"},+ 0x34: {"name": "Ilex Forest"},+ 0x35: {"name": "Warehouse Entrance"},+ 0x36: {"name": "Underground Path Switch Room Entrances"},+ 0x37: {"name": "Goldenrod Dept Store B1F"},+ 0x38: {"name": "Underground Warehouse"},+ 0x39: {"name": "Mount Mortar 1F Outside"},+ 0x3A: {"name": "Mount Mortar 1F Inside"},+ 0x3B: {"name": "Mount Mortar 2F Inside"},+ 0x3C: {"name": "Mount Mortar B1F"},+ 0x3D: {"name": "Ice Path 1F"},+ 0x3E: {"name": "Ice Path B1F"},+ 0x3F: {"name": "Ice Path B2F Mahogany Side"},+ 0x40: {"name": "Ice Path B2F Blackthorn Side"},+ 0x41: {"name": "Ice Path B3F"},+ 0x42: {"name": "Whirl Island NW"},+ 0x43: {"name": "Whirl Island NE"},+ 0x44: {"name": "Whirl Island SW"},+ 0x45: {"name": "Whirl Island Cave"},+ 0x46: {"name": "Whirl Island SE"},+ 0x47: {"name": "Whirl Island B1F"},+ 0x48: {"name": "Whirl Island B2F"},+ 0x49: {"name": "Whirl Island Lugia Chamber"},+ 0x4A: {"name": "Silver Cave Room 1"},+ 0x4B: {"name": "Silver Cave Room 2"},+ 0x4C: {"name": "Silver Cave Room 3"},+ 0x4D: {"name": "Silver Cave Item Rooms"},+ 0x4E: {"name": "Dark Cave Violet Entrance"},+ 0x4F: {"name": "Dark Cave Blackthorn Entrance"},+ 0x50: {"name": "Dragon's Den 1F"},+ 0x51: {"name": "Dragon's Den B1F"},+ 0x52: {"name": "Dragon Shrine"},+ 0x53: {"name": "Tohjo Falls"},+ 0x54: {"name": "Diglett's Cave"},+ 0x55: {"name": "Mount Moon"},+ 0x56: {"name": "Underground"},+ 0x57: {"name": "Rock Tunnel 1F"},+ 0x58: {"name": "Rock Tunnel B1F"},+ 0x59: {"name": "Safari Zone Fuchsia Gate Beta"},+ 0x5A: {"name": "Safari Zone Beta"},+ 0x5B: {"name": "Victory Road"},+ },
+ 4: {+ 0x1: {"name": "Ecruteak House"}, #passage to Tin Tower+ 0x2: {"name": "Wise Trio's Room"},+ 0x3: {"name": "Ecruteak Pokémon Center 1F",+ "label": "EcruteakPokeCenter1F"},
+ 0x4: {"name": "Ecruteak Lugia Speech House"},+ 0x5: {"name": "Dance Theatre"},+ 0x6: {"name": "Ecruteak Mart"},+ 0x7: {"name": "Ecruteak Gym"},+ 0x8: {"name": "Ecruteak Itemfinder House"},+ 0x9: {"name": "Ecruteak City"},+ },
+ 5: {+ 0x1: {"name": "Blackthorn Gym 1F"},+ 0x2: {"name": "Blackthorn Gym 2F"},+ 0x3: {"name": "Blackthorn Dragon Speech House"},+ 0x4: {"name": "Blackthorn Dodrio Trade House"},+ 0x5: {"name": "Blackthorn Mart"},+ 0x6: {"name": "Blackthorn Pokémon Center 1F",+ "label": "BlackthornPokeCenter1F"},
+ 0x7: {"name": "Move Deleter's House"},+ 0x8: {"name": "Route 45"},+ 0x9: {"name": "Route 46"},+ 0xA: {"name": "Blackthorn City"},+ },
+ 6: {+ 0x1: {"name": "Cinnabar Pokémon Center 1F",+ "label": "CinnabarPokeCenter1F"},
+ 0x2: {"name": "Cinnabar Pokémon Center 2F Beta",+ "label": "CinnabarPokeCenter2FBeta"},
+ 0x3: {"name": "Route 19 - Fuchsia Gate"},+ 0x4: {"name": "Seafoam Gym"},+ 0x5: {"name": "Route 19"},+ 0x6: {"name": "Route 20"},+ 0x7: {"name": "Route 21"},+ 0x8: {"name": "Cinnabar Island"},+ },
+ 7: {+ 0x1: {"name": "Cerulean Gym Badge Speech House"},+ 0x2: {"name": "Cerulean Police Station"},+ 0x3: {"name": "Cerulean Trade Speech House"},+ 0x4: {"name": "Cerulean Pokémon Center 1F",+ "label": "CeruleanPokeCenter1F"},
+ 0x5: {"name": "Cerulean Pokémon Center 2F Beta",+ "label": "CeruleanPokeCenter2FBeta"},
+ 0x6: {"name": "Cerulean Gym"},+ 0x7: {"name": "Cerulean Mart"},+ 0x8: {"name": "Route 10 Pokémon Center 1F",+ "label": "Route10PokeCenter1F"},
+ 0x9: {"name": "Route 10 Pokémon Center 2F Beta",+ "label": "Route10PokeCenter2FBeta"},
+ 0xA: {"name": "Power Plant"},+ 0xB: {"name": "Bill's House"},+ 0xC: {"name": "Route 4"},+ 0xD: {"name": "Route 9"},+ 0xE: {"name": "Route 10"},+ 0xF: {"name": "Route 24"},+ 0x10: {"name": "Route 25"},+ 0x11: {"name": "Cerulean City"},+ },
+ 8: {+ 0x1: {"name": "Azalea Pokémon Center 1F",+ "label": "AzaleaPokeCenter1F"},
+ 0x2: {"name": "Charcoal Kiln"},+ 0x3: {"name": "Azalea Mart"},+ 0x4: {"name": "Kurt's House"},+ 0x5: {"name": "Azalea Gym"},+ 0x6: {"name": "Route 33"},+ 0x7: {"name": "Azalea Town"},+ },
+ 9: {+ 0x1: {"name": "Lake of Rage Hidden Power House"},+ 0x2: {"name": "Lake of Rage Magikarp House"},+ 0x3: {"name": "Route 43 Mahogany Gate"},+ 0x4: {"name": "Route 43 Gate"},+ 0x5: {"name": "Route 43"},+ 0x6: {"name": "Lake of Rage"},+ },
+ 10: {+ 0x1: {"name": "Route 32"},+ 0x2: {"name": "Route 35"},+ 0x3: {"name": "Route 36"},+ 0x4: {"name": "Route 37"},+ 0x5: {"name": "Violet City"},+ 0x6: {"name": "Violet Mart"},+ 0x7: {"name": "Violet Gym"},+ 0x8: {"name": "Earl's Pokémon Academy",+ "label": "EarlsPokemonAcademy"},
+ 0x9: {"name": "Violet Nickname Speech House"},+ 0xA: {"name": "Violet Pokémon Center 1F",+ "label": "VioletPokeCenter1F"},
+ 0xB: {"name": "Violet Onix Trade House"},+ 0xC: {"name": "Route 32 Ruins of Alph Gate"},+ 0xD: {"name": "Route 32 Pokémon Center 1F",+ "label": "Route32PokeCenter1F"},
+ 0xE: {"name": "Route 35 Goldenrod gate"},+ 0xF: {"name": "Route 35 National Park gate"},+ 0x10: {"name": "Route 36 Ruins of Alph gate"},+ 0x11: {"name": "Route 36 National Park gate"},+ },
+ 11: {+ 0x1: {"name": "Route 34"},+ 0x2: {"name": "Goldenrod City"},+ 0x3: {"name": "Goldenrod Gym"},+ 0x4: {"name": "Goldenrod Bike Shop"},+ 0x5: {"name": "Goldenrod Happiness Rater"},+ 0x6: {"name": "Goldenrod Bill's House"},+ 0x7: {"name": "Goldenrod Magnet Train Station"},+ 0x8: {"name": "Goldenrod Flower Shop"},+ 0x9: {"name": "Goldenrod PP Speech House"},+ 0xA: {"name": "Goldenrod Name Rater's House"},+ 0xB: {"name": "Goldenrod Dept Store 1F"},+ 0xC: {"name": "Goldenrod Dept Store 2F"},+ 0xD: {"name": "Goldenrod Dept Store 3F"},+ 0xE: {"name": "Goldenrod Dept Store 4F"},+ 0xF: {"name": "Goldenrod Dept Store 5F"},+ 0x10: {"name": "Goldenrod Dept Store 6F"},+ 0x11: {"name": "Goldenrod Dept Store Elevator"},+ 0x12: {"name": "Goldenrod Dept Store Roof"},+ 0x13: {"name": "Goldenrod Game Corner"},+ 0x14: {"name": "Goldenrod Pokémon Center 1F",+ "label": "GoldenrodPokeCenter1F"},
+ 0x15: {"name": "Goldenrod PokéCom Center 2F Mobile",+ "label": "GoldenrodPokeComCenter2FMobile"},
+ 0x16: {"name": "Ilex Forest Azalea Gate"},+ 0x17: {"name": "Route 34 Ilex Forest Gate"},+ 0x18: {"name": "Day Care"},+ },
+ 12: {+ 0x1: {"name": "Route 6"},+ 0x2: {"name": "Route 11"},+ 0x3: {"name": "Vermilion City"},+ 0x4: {"name": "Vermilion House Fishing Speech House"},+ 0x5: {"name": "Vermilion Pokémon Center 1F",+ "label": "VermilionPokeCenter1F"},
+ 0x6: {"name": "Vermilion Pokémon Center 2F Beta",+ "label": "VermilionPokeCenter2FBeta"},
+ 0x7: {"name": "Pokémon Fan Club"},+ 0x8: {"name": "Vermilion Magnet Train Speech House"},+ 0x9: {"name": "Vermilion Mart"},+ 0xA: {"name": "Vermilion House Diglett's Cave Speech House"},+ 0xB: {"name": "Vermilion Gym"},+ 0xC: {"name": "Route 6 Saffron Gate"},+ 0xD: {"name": "Route 6 Underground Entrance"},+ },
+ 13: {+ 0x1: {"name": "Route 1"},+ 0x2: {"name": "Pallet Town"},+ 0x3: {"name": "Red's House 1F"},+ 0x4: {"name": "Red's House 2F"},+ 0x5: {"name": "Blue's House"},+ 0x6: {"name": "Oak's Lab"},+ },
+ 14: {+ 0x1: {"name": "Route 3"},+ 0x2: {"name": "Pewter City"},+ 0x3: {"name": "Pewter Nidoran Speech House"},+ 0x4: {"name": "Pewter Gym"},+ 0x5: {"name": "Pewter Mart"},+ 0x6: {"name": "Pewter Pokémon Center 1F",+ "label": "PewterPokeCenter1F"},
+ 0x7: {"name": "Pewter Pokémon Center 2F Beta",+ "label": "PewterPokeCEnter2FBeta"},
+ 0x8: {"name": "Pewter Snooze Speech House"},+ },
+ 15: {+ 0x1: {"name": "Olivine Port"},+ 0x2: {"name": "Vermilion Port"},+ 0x3: {"name": "Fast Ship 1F"},+ 0x4: {"name": "Fast Ship Cabins NNW, NNE, NE",+ "label": "FastShipCabins_NNW_NNE_NE"},
+ 0x5: {"name": "Fast Ship Cabins SW, SSW, NW",+ "label": "FastShipCabins_SW_SSW_NW"},
+ 0x6: {"name": "Fast Ship Cabins SE, SSE, Captain's Cabin",+ "label": "FastShipCabins_SE_SSE_CaptainsCabin"},
+ 0x7: {"name": "Fast Ship B1F"},+ 0x8: {"name": "Olivine Port Passage"},+ 0x9: {"name": "Vermilion Port Passage"},+ 0xA: {"name": "Mount Moon Square"},+ 0xB: {"name": "Mount Moon Gift Shop"},+ 0xC: {"name": "Tin Tower Roof"},+ },
+ 16: {+ 0x1: {"name": "Route 23"},+ 0x2: {"name": "Indigo Plateau Pokémon Center 1F",+ "label": "IndigoPlateauPokeCenter1F"},
+ 0x3: {"name": "Will's Room"},+ 0x4: {"name": "Koga's Room"},+ 0x5: {"name": "Bruno's Room"},+ 0x6: {"name": "Karen's Room"},+ 0x7: {"name": "Lance's Room"},+ 0x8: {"name": "Hall of Fame",+ "label": "HallOfFame"},
+ },
+ 17: {+ 0x1: {"name": "Route 13"},+ 0x2: {"name": "Route 14"},+ 0x3: {"name": "Route 15"},+ 0x4: {"name": "Route 18"},+ 0x5: {"name": "Fuchsia City"},+ 0x6: {"name": "Fuchsia Mart"},+ 0x7: {"name": "Safari Zone Main Office"},+ 0x8: {"name": "Fuchsia Gym"},+ 0x9: {"name": "Fuchsia Bill Speech House"},+ 0xA: {"name": "Fuchsia Pokémon Center 1F",+ "label": "FuchsiaPokeCenter1F"},
+ 0xB: {"name": "Fuchsia Pokémon Center 2F Beta",+ "label": "FuchsiaPokeCenter2FBeta"},
+ 0xC: {"name": "Safari Zone Warden's Home"},+ 0xD: {"name": "Route 15 Fuchsia Gate"},+ },
+ 18: {+ 0x1: {"name": "Route 8"},+ 0x2: {"name": "Route 12"},+ 0x3: {"name": "Route 10"},+ 0x4: {"name": "Lavender Town"},+ 0x5: {"name": "Lavender Pokémon Center 1F",+ "label": "LavenderPokeCenter1F"},
+ 0x6: {"name": "Lavender Pokémon Center 2F Beta",+ "label": "LavenderPokeCenter2FBeta"},
+ 0x7: {"name": "Mr. Fuji's House"},+ 0x8: {"name": "Lavender Town Speech House"},+ 0x9: {"name": "Lavender Name Rater"},+ 0xA: {"name": "Lavender Mart"},+ 0xB: {"name": "Soul House"},+ 0xC: {"name": "Lav Radio Tower 1F"},+ 0xD: {"name": "Route 8 Saffron Gate"},+ 0xE: {"name": "Route 12 Super Rod House"},+ },
+ 19: {+ 0x1: {"name": "Route 28"},+ 0x2: {"name": "Silver Cave Outside"},+ 0x3: {"name": "Silver Cave Pokémon Center 1F",+ "label": "SilverCavePokeCenter1F"},
+ 0x4: {"name": "Route 28 Famous Speech House"},+ },
+ 20: {+ 0x1: {"name": "Pokémon Center 2F",+ "label": "PokeCenter2F"},
+ 0x2: {"name": "Trade Center"},+ 0x3: {"name": "Colosseum"},+ 0x4: {"name": "Time Capsule"},+ 0x5: {"name": "Mobile Trade Room Mobile"},+ 0x6: {"name": "Mobile Battle Room"},+ },
+ 21: {+ 0x1: {"name": "Route 7"},+ 0x2: {"name": "Route 16"},+ 0x3: {"name": "Route 17"},+ 0x4: {"name": "Celadon City"},+ 0x5: {"name": "Celadon Dept Store 1F"},+ 0x6: {"name": "Celadon Dept Store 2F"},+ 0x7: {"name": "Celadon Dept Store 3F"},+ 0x8: {"name": "Celadon Dept Store 4F"},+ 0x9: {"name": "Celadon Dept Store 5F"},+ 0xA: {"name": "Celadon Dept Store 6F"},+ 0xB: {"name": "Celadon Dept Store Elevator"},+ 0xC: {"name": "Celadon Mansion 1F"},+ 0xD: {"name": "Celadon Mansion 2F"},+ 0xE: {"name": "Celadon Mansion 3F"},+ 0xF: {"name": "Celadon Mansion Roof"},+ 0x10: {"name": "Celadon Mansion Roof House"},+ 0x11: {"name": "Celadon Pokémon Center 1F",+ "label": "CeladonPokeCenter1F"},
+ 0x12: {"name": "Celadon Pokémon Center 2F Beta",+ "label": "CeladonPokeCenter2FBeta"},
+ 0x13: {"name": "Celadon Game Corner"},+ 0x14: {"name": "Celadon Game Corner Prize Room"},+ 0x15: {"name": "Celadon Gym"},+ 0x16: {"name": "Celadon Cafe"},+ 0x17: {"name": "Route 16 Fuchsia Speech House"},+ 0x18: {"name": "Route 16 Gate"},+ 0x19: {"name": "Route 7 Saffron Gate"},+ 0x1A: {"name": "Route 17 18 Gate"},+ },
+ 22: {+ 0x1: {"name": "Route 40"},+ 0x2: {"name": "Route 41"},+ 0x3: {"name": "Cianwood City"},+ 0x4: {"name": "Mania's House"},+ 0x5: {"name": "Cianwood Gym"},+ 0x6: {"name": "Cianwood Pokémon Center 1F",+ "label": "CianwoodPokeCenter1F"},
+ 0x7: {"name": "Cianwood Pharmacy"},+ 0x8: {"name": "Cianwood City Photo Studio"},+ 0x9: {"name": "Cianwood Lugia Speech House"},+ 0xA: {"name": "Poke Seer's House"},+ 0xB: {"name": "Battle Tower 1F"},+ 0xC: {"name": "Battle Tower Battle Room"},+ 0xD: {"name": "Battle Tower Elevator"},+ 0xE: {"name": "Battle Tower Hallway"},+ 0xF: {"name": "Route 40 Battle Tower Gate"},+ 0x10: {"name": "Battle Tower Outside"},+ },
+ 23: {+ 0x1: {"name": "Route 2"},+ 0x2: {"name": "Route 22"},+ 0x3: {"name": "Viridian City"},+ 0x4: {"name": "Viridian Gym"},+ 0x5: {"name": "Viridian Nickname Speech House"},+ 0x6: {"name": "Trainer House 1F"},+ 0x7: {"name": "Trainer House B1F"},+ 0x8: {"name": "Viridian Mart"},+ 0x9: {"name": "Viridian Pokémon Center 1F",+ "label": "ViridianPokeCenter1F"},
+ 0xA: {"name": "Viridian Pokémon Center 2F Beta",+ "label": "ViridianPokeCenter2FBeta"},
+ 0xB: {"name": "Route 2 Nugget Speech House"},+ 0xC: {"name": "Route 2 Gate"},+ 0xD: {"name": "Victory Road Gate"},+ },
+ 24: {+ 0x1: {"name": "Route 26"},+ 0x2: {"name": "Route 27"},+ 0x3: {"name": "Route 29"},+ 0x4: {"name": "New Bark Town"},+ 0x5: {"name": "Elm's Lab"},+ 0x6: {"name": "Kris's House 1F"},+ 0x7: {"name": "Kris's House 2F"},+ 0x8: {"name": "Kris's Neighbor's House"},+ 0x9: {"name": "Elm's House"},+ 0xA: {"name": "Route 26 Heal Speech House"},+ 0xB: {"name": "Route 26 Day of Week Siblings House"},+ 0xC: {"name": "Route 27 Sandstorm House"},+ 0xD: {"name": "Route 29 46 Gate"},+ },
+ 25: {+ 0x1: {"name": "Route 5"},+ 0x2: {"name": "Saffron City"},+ 0x3: {"name": "Fighting Dojo"},+ 0x4: {"name": "Saffron Gym"},+ 0x5: {"name": "Saffron Mart"},+ 0x6: {"name": "Saffron Pokémon Center 1F",+ "label": "SaffronPokeCenter1F"},
+ 0x7: {"name": "Saffron Pokémon Center 2F Beta",+ "label": "SaffronPokeCenter2FBeta"},
+ 0x8: {"name": "Mr. Psychic's House"},+ 0x9: {"name": "Saffron Train Station"},+ 0xA: {"name": "Silph Co. 1F"},+ 0xB: {"name": "Copycat's House 1F"},+ 0xC: {"name": "Copycat's House 2F"},+ 0xD: {"name": "Route 5 Underground Entrance"},+ 0xE: {"name": "Route 5 Saffron City Gate"},+ 0xF: {"name": "Route 5 Cleanse Tag Speech House"},+ },
+ 26: {+ 0x1: {"name": "Route 30"},+ 0x2: {"name": "Route 31"},+ 0x3: {"name": "Cherrygrove City"},+ 0x4: {"name": "Cherrygrove Mart"},+ 0x5: {"name": "Cherrygrove Pokémon Center 1F",+ "label": "CherrygrovePokeCenter1F"},
+ 0x6: {"name": "Cherrygrove Gym Speech House"},+ 0x7: {"name": "Guide Gent's House"},+ 0x8: {"name": "Cherrygrove Evolution Speech House"},+ 0x9: {"name": "Route 30 Berry Speech House"},+ 0xA: {"name": "Mr. Pokémon's House"},+ 0xB: {"name": "Route 31 Violet Gate"},+ },
+}
+#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
+#read the rom and figure out the offsets for maps
+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()
+
+if __name__ == "__main__":
+ load_rom()
+ load_map_group_offsets()
--
⑨