From 28490230cf68f8045fc63a8c7d3de19c7c1d3bcd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 3 Aug 2013 16:10:52 -0500 Subject: make a basic python module --- bin/dump_sections | 14 + chars.py | 279 -- comparator.py | 267 - crystal.py | 7629 ----------------------------- disassemble_map_scripts.py | 151 - dump_sections | 14 - dump_sections.py | 129 - gbz80disasm.py | 945 ---- gfx.py | 1671 ------- graph.py | 169 - interval_map.py | 103 - item_constants.py | 240 - labels.py | 171 - map_names.py | 484 -- move_constants.py | 255 - old_parse_scripts.py | 1885 ------- overworldripper.py | 18 - parse_consecutive_strings.py | 25 - pksv.py | 314 -- pointers.py | 24 - pokemon_constants.py | 255 - pokemontools/__init__.py | 1 + pokemontools/chars.py | 279 ++ pokemontools/comparator.py | 267 + pokemontools/crystal.py | 7629 +++++++++++++++++++++++++++++ pokemontools/disassemble_map_scripts.py | 151 + pokemontools/dump_sections.py | 129 + pokemontools/gbz80disasm.py | 945 ++++ pokemontools/gfx.py | 1671 +++++++ pokemontools/graph.py | 169 + pokemontools/interval_map.py | 103 + pokemontools/item_constants.py | 240 + pokemontools/labels.py | 171 + pokemontools/map_names.py | 484 ++ pokemontools/move_constants.py | 255 + pokemontools/old_parse_scripts.py | 1885 +++++++ pokemontools/overworldripper.py | 18 + pokemontools/parse_consecutive_strings.py | 25 + pokemontools/pksv.py | 314 ++ pokemontools/pointers.py | 24 + pokemontools/pokemon_constants.py | 255 + pokemontools/romstr.py | 219 + pokemontools/sym.py | 54 + pokemontools/trainers.py | 108 + pokemontools/type_constants.py | 21 + pokemontools/wram.py | 125 + romstr.py | 219 - setup.py | 44 + sym.py | 54 - test_dump_sections.py | 74 - tests.py | 1014 ---- tests/test_dump_sections.py | 74 + tests/tests.py | 1014 ++++ trainers.py | 108 - type_constants.py | 21 - wram.py | 125 - 56 files changed, 16688 insertions(+), 16643 deletions(-) create mode 100755 bin/dump_sections delete mode 100644 chars.py delete mode 100644 comparator.py delete mode 100644 crystal.py delete mode 100644 disassemble_map_scripts.py delete mode 100755 dump_sections delete mode 100755 dump_sections.py delete mode 100644 gbz80disasm.py delete mode 100644 gfx.py delete mode 100644 graph.py delete mode 100644 interval_map.py delete mode 100644 item_constants.py delete mode 100644 labels.py delete mode 100644 map_names.py delete mode 100644 move_constants.py delete mode 100644 old_parse_scripts.py delete mode 100644 overworldripper.py delete mode 100644 parse_consecutive_strings.py delete mode 100644 pksv.py delete mode 100644 pointers.py delete mode 100644 pokemon_constants.py create mode 100644 pokemontools/__init__.py create mode 100644 pokemontools/chars.py create mode 100644 pokemontools/comparator.py create mode 100644 pokemontools/crystal.py create mode 100644 pokemontools/disassemble_map_scripts.py create mode 100755 pokemontools/dump_sections.py create mode 100644 pokemontools/gbz80disasm.py create mode 100644 pokemontools/gfx.py create mode 100644 pokemontools/graph.py create mode 100644 pokemontools/interval_map.py create mode 100644 pokemontools/item_constants.py create mode 100644 pokemontools/labels.py create mode 100644 pokemontools/map_names.py create mode 100644 pokemontools/move_constants.py create mode 100644 pokemontools/old_parse_scripts.py create mode 100644 pokemontools/overworldripper.py create mode 100644 pokemontools/parse_consecutive_strings.py create mode 100644 pokemontools/pksv.py create mode 100644 pokemontools/pointers.py create mode 100644 pokemontools/pokemon_constants.py create mode 100644 pokemontools/romstr.py create mode 100644 pokemontools/sym.py create mode 100644 pokemontools/trainers.py create mode 100644 pokemontools/type_constants.py create mode 100644 pokemontools/wram.py delete mode 100644 romstr.py create mode 100644 setup.py delete mode 100644 sym.py delete mode 100644 test_dump_sections.py delete mode 100644 tests.py create mode 100644 tests/test_dump_sections.py create mode 100644 tests/tests.py delete mode 100644 trainers.py delete mode 100644 type_constants.py delete mode 100644 wram.py diff --git a/bin/dump_sections b/bin/dump_sections new file mode 100755 index 0000000..362318f --- /dev/null +++ b/bin/dump_sections @@ -0,0 +1,14 @@ +#!/bin/bash +# This wraps dump_sections.py so that other python scripts can import the +# functions. If dump_sections.py was instead called dump_sections, then other +# python source code would be unable to use the functions via import +# statements. + +# figure out the path to this script +cwd="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# construct the path to dump_sections.py +secpath=$cwd/dump_sections.py + +# run dump_sections.py +$secpath $1 diff --git a/chars.py b/chars.py deleted file mode 100644 index fc69fc5..0000000 --- a/chars.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- -from copy import copy - -# this is straight out of ../preprocessor.py because i'm lazy -# (also, it's flipped) -# 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: "→", - 0xEC: "▷", - 0xED: "▶", - 0xEE: "▼", - 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 diff --git a/comparator.py b/comparator.py deleted file mode 100644 index 6cc440c..0000000 --- a/comparator.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Find shared functions between red/crystal. -""" - -from crystal import ( - get_label_from_line, - get_address_from_line_comment, - AsmSection, - direct_load_rom, - direct_load_asm, -) - -from romstr import ( - RomStr, - AsmList, -) - -def load_rom(path): - """ - Load a ROM file into an abbreviated RomStr object. - """ - return direct_load_rom(filename=path) - -def load_asm(path): - """ - Load source ASM into an abbreviated AsmList object. - """ - return direct_load_asm(filename=path) - -def findall_iter(sub, string): - # url: http://stackoverflow.com/a/3874760/687783 - - def next_index(length): - index = 0 - length - while True: - index = string.find(sub, index + length) - yield index - - return iter(next_index(len(sub)).next, -1) - -class Address(int): - """ - A simple int wrapper to take 0xFFFF and $FFFF addresses. - """ - - def __new__(cls, x=None, *args, **kwargs): - if type(x) == str: - if "$" in x: - x = x.replace("$", "0x") - - if "0x" in str: - instance = int.__new__(cls, int(x, base=16), *args, **kwargs) - else: - msg = "Address.__new__ doesn't know how to parse this string" - raise Exception, msg - else: - instance = int.__new__(cls, x, *args, **kwargs) - - return instance - -found_blobs = [] - -class BinaryBlob(object): - """ - Store a label, line number, and addresses of a function from Pokémon Red. - - These details can be used to determine whether or not the function was - copied into Pokémon Crystal. - """ - - start_address = None - end_address = None - label = None - line_number = None - bytes = None - bank = None - debug = False - locations = None - - def __init__(self, start_address=None, end_address=None, label=None, \ - debug=None, line_number=None): - if not isinstance(start_address, Address): - start_address = Address(start_address) - if not isinstance(end_address, Address): - end_address = Address(end_address) - - assert label != None, "label can't be none" - assert isinstance(label, str), "label must be a string" - assert line_number != None, "line_number must be provided" - - self.start_address = start_address - self.end_address = end_address - self.label = label - self.line_number = line_number - self.bytes = [] - self.locations = [] - self.bank = start_address / 0x4000 - - if debug != None: - self.debug = debug - - self.parse_from_red() - # self.find_in_crystal() - self.find_by_first_bytes() - - def __repr__(self): - """ - A beautiful poem. - """ - - r = "BinaryBlob(" - r += "label=\""+self.label+"\", " - r += "start_address="+hex(self.start_address)+", " - r += "size="+str(self.end_address - self.start_address)+", " - locnum = len(self.locations) - if locnum == 1: - r += "located="+hex(self.locations[0]) - elif locnum <= 5: - r += "located="+str([hex(x) for x in self.locations]) - else: - r += "located="+str(locnum) - r += ")" - - return r - - def __str__(self): - return self.__repr__() - - def parse_from_red(self): - """ - Read bytes from Pokémon Red and stores them. - """ - - self.bytes = redrom[self.start_address : self.end_address + 1] - - def pretty_bytes(self): - """ - Returns a better looking range of bytes. - """ - - bytes = redrom.interval(self.start_address, \ - self.end_address - self.start_address, \ - strings=False, debug=True) - - return bytes - - def find_in_crystal(self): - """ - Check whether or not the bytes appear in Pokémon Crystal. - """ - - finditer = findall_iter(self.bytes, cryrom) - self.locations = [match for match in finditer] - - if len(self.locations) > 0: - found_blobs.append(self) - - if self.debug: - print self.label + ": found " + str(len(self.locations)) + " matches." - - def find_by_first_bytes(self): - """ - Find this blob in Crystal based on the first n bytes. - """ - - # how many bytes to match - first_n = 3 - - # no match - if len(self.bytes) <= first_n: - return - - finditer = findall_iter(self.bytes[0:first_n], cryrom) - self.locations = [match for match in finditer] - - # filter out locations that suck - self.locations = [i for i in self.locations if abs(self.start_address - i) <= 0x8000] - - if len(self.locations) > 0: - found_blobs.append(self) - - if self.debug: - print self.label + ": found " + str(len(self.locations)) + " matches." - -pokecrystal_rom_path = "../baserom.gbc" -pokecrystal_src_path = "../main.asm" -pokered_rom_path = "../pokered-baserom.gbc" -pokered_src_path = "../pokered-main.asm" - -cryrom = load_rom(pokecrystal_rom_path) -crysrc = load_asm(pokecrystal_src_path) -redrom = load_rom(pokered_rom_path) -redsrc = load_asm(pokered_src_path) - -def scan_red_asm(bank_stop=3, debug=True): - """ - Scan the ASM from Pokémon Red. Finds labels and objects. Does things. - - Uses get_label_from_line and get_address_from_line_comment. - """ - - # whether or not to show the lines from redsrc - show_lines = False - - line_number = 0 - current_bank = 0 - - current_label = None - latest_label = "ignore me" - current_start_address = None - latest_start_address = 0 - latest_line = "" - - for line in redsrc: - if debug and show_lines: - print "processing a line from red: " + line - - if line[0:7] == "SECTION": - thing = AsmSection(line) - current_bank = thing.bank_id - - if debug: - print "scan_red_asm: switching to bank " + str(current_bank) - - elif line[0:6] != "INCBIN": - if ":" in line and not ";XXX:" in line and not " ; XXX:" in line: - current_label = get_label_from_line(line) - current_start_address = get_address_from_line_comment(line, \ - bank=current_bank) - - if current_label != None and current_start_address != None and latest_start_address != None \ - and current_start_address != 0 and current_start_address != latest_start_address \ - and (current_start_address - latest_start_address) > 1: - if latest_label != None: - if latest_label not in ["Char52", "PokeCenterSignText", "DefaultNamesPlayer", "Unnamed_6a12"]: - blob = BinaryBlob(label=latest_label, \ - start_address=latest_start_address, \ - end_address=current_start_address, \ - line_number=line_number) - - if debug: - print "Created a new blob: " + str(blob) + " from line: " + str(latest_line) - - latest_label = current_label - latest_start_address = current_start_address - latest_line = line - - line_number += 1 - - if current_bank == bank_stop: - if debug: - print "scan_red_asm: stopping because current_bank >= " + \ - str(bank_stop) + " (bank_stop)" - - break - -scan_red_asm(bank_stop=3) - -print "================================" - -for blob in found_blobs: - print blob - -print "Found " + str(len(found_blobs)) + " possibly copied functions." - -print [hex(x) for x in found_blobs[10].locations] 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. - - <0x50> + <0xFF> - - Data type <0x00>: Pokémon Data is . Used by most trainers. - Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is . 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: - """ - <0x50> + <0xFF> - - Data type <0x00>: Pokémon Data is . Used by most trainers. - Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is . 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 . 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 - . 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 . 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 - . - 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 memoryotherpointer is 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 it would be a good idea to rename otherpointer strippointer or striploc - # 10:42 since thats more accurate - # 11:05 Above: C803h + xoffset - # 11:05 Below: C803h + (m.height + 3) * (m.width + 6) + xoffset - # 11:05 Left: C800h + (m.width + 6) * (yoffset + 3) - # 11:05 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 Above: C803h + xoffset - # 11:05 Below: C803h + (m.height + 3) * (m.width + 6) + xoffset - # 11:05 Left: C800h + (m.width + 6) * (yoffset + 3) - # 11:05 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 diff --git a/disassemble_map_scripts.py b/disassemble_map_scripts.py deleted file mode 100644 index 21df569..0000000 --- a/disassemble_map_scripts.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- encoding: utf-8 -*- -""" -Dump out asm for scripting things in bank $25. This script will modify main.asm -and insert all scripting commands. -""" - -import crystal -from gbz80disasm import output_bank_opcodes - -rom = crystal.load_rom() -roml = [ord(x) for x in rom] - -script_command_table_address = 0x96cb1 -script_command_count = 170 - -# a list of addresses for each script command -command_pointers = [crystal.calculate_pointer_from_bytes_at(script_command_table_address + (id * 2), bank=0x25) for id in range(0, 170)] - -# a list of hex addresses for each script command in bank $25 -command_pointers_hex = ["$%.2x" % (x % 0x4000 + 0x4000) for x in command_pointers] - -commands = {} - -# force data into a more usable form -for command in crystal.command_classes: - name = "Script_" + command.macro_name - id = command.id - params = {} - - for (id2, param_type) in command.param_types.items(): - param = { - "name": param_type["name"], - "type": param_type["class"].__name__, - } - params[id2] = param - - if id <= 0xa9: - commands[id] = { - "name": name, - "params": params, - "address": command_pointers[id], - } - -avoid = [ - 0x974b0, - 0x974be, - 0x9754b, - 0x97556, - 0x97562, - 0x9756e, - 0x97540, - - 0x96f8e, # verbosegiveitem2 -] - -class DisassembledScriptCommand(): - """ - Just a temporary object to store information about a script command's asm. - This is used by some of the infrastructure in crystal.py to automatically - insert asm into main.asm, rather than having someone do it manually. - """ - dependencies = None - - def __init__(self, label=None, id=None, address=None, params=None): - self.id = id - self.label = crystal.Label(name=label, address=address, object=self) - self.address = address - self.params = params - - max_byte_count = 0x4000 - - # Some of these scripts need to be truncated before insertion, because - # output_bank_opcodes doesn't know anything about stopping if some of - # the local labels are not resolved yet. - - # Script_if_equal - if address == 0x97540: - max_byte_count = 86 - - # disassemble and laso get the last address - (asm, last_address, last_hl_address, last_a_address, used_3d97) = output_bank_opcodes(address, max_byte_count=max_byte_count, stop_at=command_pointers, include_last_address=False) - - # remove indentation - asm = asm.replace("\n\t", "\n") - if asm[0] == "\t": - asm = asm[1:] - - # remove the last two newlines - while asm[-1] == "\n": - asm = asm[:-1] - - self.asm = asm - self.last_address = last_address - - # make sure this gets dumped into main.asm - #if crystal.script_parse_table[self.address] == None and crystal.script_parse_table[self.last_address] == None: - crystal.script_parse_table[self.address : self.last_address] = self - #else: - # print ".. hm, something is already at " + hex(self.address) + " for " + self.label.name - - def to_asm(self): - #output += self.label + ": ; " + hex(self.address) + "\n" - output = "; script command " + hex(self.id) + "\n" - if len(self.params) > 0: - output += "; parameters:\n" - for (id2, param) in self.params.items(): - output += "; " + param["name"] + " (" + param["type"] + ")\n" - output += "\n" - output += self.asm - return output - - def get_dependencies(*args, **kwargs): - return [] - -# make instances of DisassembledScriptCommand -for (id, command) in commands.items(): - name = command["name"] - params = command["params"] - address = command["address"] - - script_asm = DisassembledScriptCommand(label=name, id=id, address=address, params=params) - #print script_asm.to_asm() - #print crystal.to_asm(script_asm, use_asm_rules=True) - -class ScriptCommandTable(): - address = script_command_table_address - last_address = script_command_table_address + (2 * 170) - dependencies = None - - def __init__(self): - self.label = crystal.Label(name="ScriptCommandTable", address=self.address, object=self) - - # make sure this gets dumped into main.asm - crystal.script_parse_table[self.address : self.last_address] = self - - def get_dependencies(*args, **kwargs): - return [] - - def to_asm(self): - output = "" - for (id, command) in commands.items(): - output += "dw " + command["name"] + "; " + hex(command["address"]) + "\n" - if output[-1] == "\n": - output = output[:-1] - return output -script_command_table = ScriptCommandTable() -#print crystal.to_asm(script_command_table, use_asm_rules=True) - -# automatic asm insertion -asm = crystal.Asm() -asm.insert_and_dump(limit=500) diff --git a/dump_sections b/dump_sections deleted file mode 100755 index 362318f..0000000 --- a/dump_sections +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -# This wraps dump_sections.py so that other python scripts can import the -# functions. If dump_sections.py was instead called dump_sections, then other -# python source code would be unable to use the functions via import -# statements. - -# figure out the path to this script -cwd="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# construct the path to dump_sections.py -secpath=$cwd/dump_sections.py - -# run dump_sections.py -$secpath $1 diff --git a/dump_sections.py b/dump_sections.py deleted file mode 100755 index fef35d8..0000000 --- a/dump_sections.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -""" -Use this tool to dump an asm file for a new source code or disassembly project. - -usage: - - from dump_sections import dump_sections - - output = dump_sections("../../butt.gbc") - - file_handler = open("main.asm", "w") - file_handler.write(output) - file_handler.close() - -You can also use this script from the shell, where it will look for -"baserom.gbc" in the current working path or whatever file path you pass in the -first positional argument. -""" - -import os -import sys -import argparse - -def upper_hex(input): - """ - Converts the input to an uppercase hex string. - """ - if input in [0, "0"]: - return "0" - elif input <= 0xF: - return ("%.x" % (input)).upper() - else: - return ("%.2x" % (input)).upper() - -def format_bank_number(address, bank_size=0x4000): - """ - Returns a str of the hex number of the bank based on the address. - """ - return upper_hex(address / bank_size) - -def calculate_bank_quantity(path, bank_size=0x4000): - """ - Returns the number of 0x4000 banks in the file at path. - """ - return float(os.lstat(path).st_size) / bank_size - -def dump_section(bank_number, separator="\n\n"): - """ - Returns a str of a section header for the asm file. - """ - output = "SECTION \"" - if bank_number in [0, "0"]: - output += "bank0\",HOME" - else: - output += "bank" - output += bank_number - output += "\",DATA,BANK[$" - output += bank_number - output += "]" - output += separator - return output - -def dump_incbin_for_section(address, bank_size=0x4000, baserom="baserom.gbc", separator="\n\n"): - """ - Returns a str for an INCBIN line for an entire section. - """ - output = "INCBIN \"" - output += baserom - output += "\",$" - output += upper_hex(address) - output += ",$" - output += upper_hex(bank_size) - output += separator - return output - -def dump_sections(path, bank_size=0x4000, initial_bank=0, last_bank=None, separator="\n\n"): - """ - Returns a str of assembly source code. The source code delineates each - SECTION and includes bytes from the file specified by baserom. - """ - if not last_bank: - last_bank = calculate_bank_quantity(path, bank_size=bank_size) - - if last_bank < initial_bank: - raise Exception("last_bank must be greater than or equal to initial_bank") - - baserom_name = os.path.basename(path) - - output = "" - - banks = range(initial_bank, last_bank) - - for bank_number in banks: - address = bank_number * bank_size - - # get a formatted hex number of the bank based on the address - formatted_bank_number = format_bank_number(address, bank_size=bank_size) - - # SECTION - output += dump_section(formatted_bank_number, separator=separator) - - # INCBIN a range of bytes from the ROM - output += dump_incbin_for_section(address, bank_size=bank_size, baserom=baserom_name, separator=separator) - - # clean up newlines at the end of the output - if output[-2:] == "\n\n": - output = output[:-2] - output += "\n" - - return output - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("rompath", nargs="?", metavar="rompath", type=str) - args = parser.parse_args() - - # default to "baserom.gbc" in the current working directory - baserom = "baserom.gbc" - - # but let the user override the default - if args.rompath: - baserom = args.rompath - - # generate some asm - output = dump_sections(baserom) - - # dump everything to stdout - sys.stdout.write(output) diff --git a/gbz80disasm.py b/gbz80disasm.py deleted file mode 100644 index 6759a4a..0000000 --- a/gbz80disasm.py +++ /dev/null @@ -1,945 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import sys -from copy import copy, deepcopy -from ctypes import c_int8 -import random -import json -from wram import * - -# New versions of json don't have read anymore. -if not hasattr(json, "read"): - json.read = json.loads - -def load_rom(filename="../baserom.gbc"): - """ - Load the specified rom. - - If no rom is given, load "../baserom.gbc". - """ - global rom - rom = bytearray(open(filename,'rb').read()) - return rom - -spacing = "\t" - -temp_opt_table = [ - [ "ADC A", 0x8f, 0 ], - [ "ADC B", 0x88, 0 ], - [ "ADC C", 0x89, 0 ], - [ "ADC D", 0x8a, 0 ], - [ "ADC E", 0x8b, 0 ], - [ "ADC H", 0x8c, 0 ], - [ "ADC [HL]", 0x8e, 0 ], - [ "ADC L", 0x8d, 0 ], - [ "ADC x", 0xce, 1 ], - [ "ADD A", 0x87, 0 ], - [ "ADD B", 0x80, 0 ], - [ "ADD C", 0x81, 0 ], - [ "ADD D", 0x82, 0 ], - [ "ADD E", 0x83, 0 ], - [ "ADD H", 0x84, 0 ], - [ "ADD [HL]", 0x86, 0 ], - [ "ADD HL, BC", 0x9, 0 ], - [ "ADD HL, DE", 0x19, 0 ], - [ "ADD HL, HL", 0x29, 0 ], - [ "ADD HL, SP", 0x39, 0 ], - [ "ADD L", 0x85, 0 ], - [ "ADD SP, x", 0xe8, 1 ], - [ "ADD x", 0xc6, 1 ], - [ "AND A", 0xa7, 0 ], - [ "AND B", 0xa0, 0 ], - [ "AND C", 0xa1, 0 ], - [ "AND D", 0xa2, 0 ], - [ "AND E", 0xa3, 0 ], - [ "AND H", 0xa4, 0 ], - [ "AND [HL]", 0xa6, 0 ], - [ "AND L", 0xa5, 0 ], - [ "AND x", 0xe6, 1 ], - [ "BIT 0, A", 0x47cb, 3 ], - [ "BIT 0, B", 0x40cb, 3 ], - [ "BIT 0, C", 0x41cb, 3 ], - [ "BIT 0, D", 0x42cb, 3 ], - [ "BIT 0, E", 0x43cb, 3 ], - [ "BIT 0, H", 0x44cb, 3 ], - [ "BIT 0, [HL]", 0x46cb, 3 ], - [ "BIT 0, L", 0x45cb, 3 ], - [ "BIT 1, A", 0x4fcb, 3 ], - [ "BIT 1, B", 0x48cb, 3 ], - [ "BIT 1, C", 0x49cb, 3 ], - [ "BIT 1, D", 0x4acb, 3 ], - [ "BIT 1, E", 0x4bcb, 3 ], - [ "BIT 1, H", 0x4ccb, 3 ], - [ "BIT 1, [HL]", 0x4ecb, 3 ], - [ "BIT 1, L", 0x4dcb, 3 ], - [ "BIT 2, A", 0x57cb, 3 ], - [ "BIT 2, B", 0x50cb, 3 ], - [ "BIT 2, C", 0x51cb, 3 ], - [ "BIT 2, D", 0x52cb, 3 ], - [ "BIT 2, E", 0x53cb, 3 ], - [ "BIT 2, H", 0x54cb, 3 ], - [ "BIT 2, [HL]", 0x56cb, 3 ], - [ "BIT 2, L", 0x55cb, 3 ], - [ "BIT 3, A", 0x5fcb, 3 ], - [ "BIT 3, B", 0x58cb, 3 ], - [ "BIT 3, C", 0x59cb, 3 ], - [ "BIT 3, D", 0x5acb, 3 ], - [ "BIT 3, E", 0x5bcb, 3 ], - [ "BIT 3, H", 0x5ccb, 3 ], - [ "BIT 3, [HL]", 0x5ecb, 3 ], - [ "BIT 3, L", 0x5dcb, 3 ], - [ "BIT 4, A", 0x67cb, 3 ], - [ "BIT 4, B", 0x60cb, 3 ], - [ "BIT 4, C", 0x61cb, 3 ], - [ "BIT 4, D", 0x62cb, 3 ], - [ "BIT 4, E", 0x63cb, 3 ], - [ "BIT 4, H", 0x64cb, 3 ], - [ "BIT 4, [HL]", 0x66cb, 3 ], - [ "BIT 4, L", 0x65cb, 3 ], - [ "BIT 5, A", 0x6fcb, 3 ], - [ "BIT 5, B", 0x68cb, 3 ], - [ "BIT 5, C", 0x69cb, 3 ], - [ "BIT 5, D", 0x6acb, 3 ], - [ "BIT 5, E", 0x6bcb, 3 ], - [ "BIT 5, H", 0x6ccb, 3 ], - [ "BIT 5, [HL]", 0x6ecb, 3 ], - [ "BIT 5, L", 0x6dcb, 3 ], - [ "BIT 6, A", 0x77cb, 3 ], - [ "BIT 6, B", 0x70cb, 3 ], - [ "BIT 6, C", 0x71cb, 3 ], - [ "BIT 6, D", 0x72cb, 3 ], - [ "BIT 6, E", 0x73cb, 3 ], - [ "BIT 6, H", 0x74cb, 3 ], - [ "BIT 6, [HL]", 0x76cb, 3 ], - [ "BIT 6, L", 0x75cb, 3 ], - [ "BIT 7, A", 0x7fcb, 3 ], - [ "BIT 7, B", 0x78cb, 3 ], - [ "BIT 7, C", 0x79cb, 3 ], - [ "BIT 7, D", 0x7acb, 3 ], - [ "BIT 7, E", 0x7bcb, 3 ], - [ "BIT 7, H", 0x7ccb, 3 ], - [ "BIT 7, [HL]", 0x7ecb, 3 ], - [ "BIT 7, L", 0x7dcb, 3 ], - [ "CALL C, ?", 0xdc, 2 ], - [ "CALL NC, ?", 0xd4, 2 ], - [ "CALL NZ, ?", 0xc4, 2 ], - [ "CALL Z, ?", 0xcc, 2 ], - [ "CALL ?", 0xcd, 2 ], - [ "CCF", 0x3f, 0 ], - [ "CP A", 0xbf, 0 ], - [ "CP B", 0xb8, 0 ], - [ "CP C", 0xb9, 0 ], - [ "CP D", 0xba, 0 ], - [ "CP E", 0xbb, 0 ], - [ "CP H", 0xbc, 0 ], - [ "CP [HL]", 0xbe, 0 ], - [ "CPL", 0x2f, 0 ], - [ "CP L", 0xbd, 0 ], - [ "CP x", 0xfe, 1 ], - [ "DAA", 0x27, 0 ], - [ "DEBUG", 0xed, 0 ], - [ "DEC A", 0x3d, 0 ], - [ "DEC B", 0x5, 0 ], - [ "DEC BC", 0xb, 0 ], - [ "DEC C", 0xd, 0 ], - [ "DEC D", 0x15, 0 ], - [ "DEC DE", 0x1b, 0 ], - [ "DEC E", 0x1d, 0 ], - [ "DEC H", 0x25, 0 ], - [ "DEC HL", 0x2b, 0 ], - [ "DEC [HL]", 0x35, 0 ], - [ "DEC L", 0x2d, 0 ], - [ "DEC SP", 0x3b, 0 ], - [ "DI", 0xf3, 0 ], - [ "EI", 0xfb, 0 ], - [ "HALT", 0x76, 0 ], - [ "INC A", 0x3c, 0 ], - [ "INC B", 0x4, 0 ], - [ "INC BC", 0x3, 0 ], - [ "INC C", 0xc, 0 ], - [ "INC D", 0x14, 0 ], - [ "INC DE", 0x13, 0 ], - [ "INC E", 0x1c, 0 ], - [ "INC H", 0x24, 0 ], - [ "INC HL", 0x23, 0 ], - [ "INC [HL]", 0x34, 0 ], - [ "INC L", 0x2c, 0 ], - [ "INC SP", 0x33, 0 ], - [ "JP C, ?", 0xda, 2 ], - [ "JP [HL]", 0xe9, 0 ], - [ "JP NC, ?", 0xd2, 2 ], - [ "JP NZ, ?", 0xc2, 2 ], - [ "JP Z, ?", 0xca, 2 ], - [ "JP ?", 0xc3, 2 ], - [ "JR C, x", 0x38, 1 ], - [ "JR NC, x", 0x30, 1 ], - [ "JR NZ, x", 0x20, 1 ], - [ "JR Z, x", 0x28, 1 ], - [ "JR x", 0x18, 1 ], - [ "LD A, A", 0x7f, 0 ], - [ "LD A, B", 0x78, 0 ], - [ "LD A, C", 0x79, 0 ], - [ "LD A, D", 0x7a, 0 ], - [ "LD A, E", 0x7b, 0 ], - [ "LD A, H", 0x7c, 0 ], - [ "LD A, L", 0x7d, 0 ], - [ "LD A, [$FF00+C]", 0xf2, 0 ], - [ "LD A, [$FF00+x]", 0xf0, 1 ], -# [ "LDH A, [x]", 0xf0, 1 ], # rgbds has trouble with this one? - [ "LD A, [BC]", 0xa, 0 ], - [ "LD A, [DE]", 0x1a, 0 ], -# [ "LD A, [HL+]", 0x2a, 0 ], -# [ "LD A, [HL-]", 0x3a, 0 ], - [ "LD A, [HL]", 0x7e, 0 ], - [ "LD A, [HLD]", 0x3a, 0 ], - [ "LD A, [HLI]", 0x2a, 0 ], - [ "LD A, [?]", 0xfa, 2 ], - [ "LD A, x", 0x3e, 1 ], - [ "LD B, A", 0x47, 0 ], - [ "LD B, B", 0x40, 0 ], - [ "LD B, C", 0x41, 0 ], - [ "LD [BC], A", 0x2, 0 ], - [ "LD B, D", 0x42, 0 ], - [ "LD B, E", 0x43, 0 ], - [ "LD B, H", 0x44, 0 ], - [ "LD B, [HL]", 0x46, 0 ], - [ "LD B, L", 0x45, 0 ], - [ "LD B, x", 0x6, 1 ], - [ "LD C, A", 0x4f, 0 ], - [ "LD C, B", 0x48, 0 ], - [ "LD C, C", 0x49, 0 ], - [ "LD C, D", 0x4a, 0 ], - [ "LD C, E", 0x4b, 0 ], - [ "LD C, H", 0x4c, 0 ], - [ "LD C, [HL]", 0x4e, 0 ], - [ "LD C, L", 0x4d, 0 ], - [ "LD C, x", 0xe, 1 ], - [ "LD D, A", 0x57, 0 ], -# [ "LDD A, [HL]", 0x3a, 0 ], - [ "LD D, B", 0x50, 0 ], - [ "LD D, C", 0x51, 0 ], - [ "LD D, D", 0x52, 0 ], - [ "LD D, E", 0x53, 0 ], - [ "LD [DE], A", 0x12, 0 ], - [ "LD D, H", 0x54, 0 ], - [ "LD D, [HL]", 0x56, 0 ], -# [ "LDD [HL], A", 0x32, 0 ], - [ "LD D, L", 0x55, 0 ], - [ "LD D, x", 0x16, 1 ], - [ "LD E, A", 0x5f, 0 ], - [ "LD E, B", 0x58, 0 ], - [ "LD E, C", 0x59, 0 ], - [ "LD E, D", 0x5a, 0 ], - [ "LD E, E", 0x5b, 0 ], - [ "LD E, H", 0x5c, 0 ], - [ "LD E, [HL]", 0x5e, 0 ], - [ "LD E, L", 0x5d, 0 ], - [ "LD E, x", 0x1e, 1 ], - [ "LD [$FF00+C], A", 0xe2, 0 ], - [ "LD [$FF00+x], A", 0xe0, 1 ], -# [ "LDH [x], A", 0xe0, 1 ], - [ "LD H, A", 0x67, 0 ], - [ "LD H, B", 0x60, 0 ], - [ "LD H, C", 0x61, 0 ], - [ "LD H, D", 0x62, 0 ], - [ "LD H, E", 0x63, 0 ], - [ "LD H, H", 0x64, 0 ], - [ "LD H, [HL]", 0x66, 0 ], - [ "LD H, L", 0x65, 0 ], -# [ "LD [HL+], A", 0x22, 0 ], -# [ "LD [HL-], A", 0x32, 0 ], - [ "LD [HL], A", 0x77, 0 ], - [ "LD [HL], B", 0x70, 0 ], - [ "LD [HL], C", 0x71, 0 ], - [ "LD [HL], D", 0x72, 0 ], - [ "LD [HLD], A", 0x32, 0 ], - [ "LD [HL], E", 0x73, 0 ], - [ "LD [HL], H", 0x74, 0 ], - [ "LD [HLI], A", 0x22, 0 ], - [ "LD [HL], L", 0x75, 0 ], -# [ "LD HL, SP+x", 0xf8, 1 ], # rgbds uses [sp+x] - [ "LD HL, [SP+x]", 0xf8, 1 ], - [ "LD [HL], x", 0x36, 1 ], - [ "LD H, x", 0x26, 1 ], -# [ "LDI A, [HL]", 0x2a, 0 ], -# [ "LDI [HL], A", 0x22, 0 ], - [ "LD L, A", 0x6f, 0 ], - [ "LD L, B", 0x68, 0 ], - [ "LD L, C", 0x69, 0 ], - [ "LD L, D", 0x6a, 0 ], - [ "LD L, E", 0x6b, 0 ], - [ "LD L, H", 0x6c, 0 ], - [ "LD L, [HL]", 0x6e, 0 ], - [ "LD L, L", 0x6d, 0 ], - [ "LD L, x", 0x2e, 1 ], -# [ "LD PC, HL", 0xe9, 0 ], #prefer jp [hl] - [ "LD SP, HL", 0xf9, 0 ], - [ "LD BC, ?", 0x1, 2 ], - [ "LD DE, ?", 0x11, 2 ], - [ "LD HL, ?", 0x21, 2 ], - [ "LD SP, ?", 0x31, 2 ], - [ "LD [?], SP", 0x8, 2 ], - [ "LD [?], A", 0xea, 2 ], - [ "NOP", 0x0, 0 ], - [ "OR A", 0xb7, 0 ], - [ "OR B", 0xb0, 0 ], - [ "OR C", 0xb1, 0 ], - [ "OR D", 0xb2, 0 ], - [ "OR E", 0xb3, 0 ], - [ "OR H", 0xb4, 0 ], - [ "OR [HL]", 0xb6, 0 ], - [ "OR L", 0xb5, 0 ], - [ "OR x", 0xf6, 1 ], - [ "POP AF", 0xf1, 0 ], - [ "POP BC", 0xc1, 0 ], - [ "POP DE", 0xd1, 0 ], - [ "POP HL", 0xe1, 0 ], - [ "PUSH AF", 0xf5, 0 ], - [ "PUSH BC", 0xc5, 0 ], - [ "PUSH DE", 0xd5, 0 ], - [ "PUSH HL", 0xe5, 0 ], - [ "RES 0, A", 0x87cb, 3 ], - [ "RES 0, B", 0x80cb, 3 ], - [ "RES 0, C", 0x81cb, 3 ], - [ "RES 0, D", 0x82cb, 3 ], - [ "RES 0, E", 0x83cb, 3 ], - [ "RES 0, H", 0x84cb, 3 ], - [ "RES 0, [HL]", 0x86cb, 3 ], - [ "RES 0, L", 0x85cb, 3 ], - [ "RES 1, A", 0x8fcb, 3 ], - [ "RES 1, B", 0x88cb, 3 ], - [ "RES 1, C", 0x89cb, 3 ], - [ "RES 1, D", 0x8acb, 3 ], - [ "RES 1, E", 0x8bcb, 3 ], - [ "RES 1, H", 0x8ccb, 3 ], - [ "RES 1, [HL]", 0x8ecb, 3 ], - [ "RES 1, L", 0x8dcb, 3 ], - [ "RES 2, A", 0x97cb, 3 ], - [ "RES 2, B", 0x90cb, 3 ], - [ "RES 2, C", 0x91cb, 3 ], - [ "RES 2, D", 0x92cb, 3 ], - [ "RES 2, E", 0x93cb, 3 ], - [ "RES 2, H", 0x94cb, 3 ], - [ "RES 2, [HL]", 0x96cb, 3 ], - [ "RES 2, L", 0x95cb, 3 ], - [ "RES 3, A", 0x9fcb, 3 ], - [ "RES 3, B", 0x98cb, 3 ], - [ "RES 3, C", 0x99cb, 3 ], - [ "RES 3, D", 0x9acb, 3 ], - [ "RES 3, E", 0x9bcb, 3 ], - [ "RES 3, H", 0x9ccb, 3 ], - [ "RES 3, [HL]", 0x9ecb, 3 ], - [ "RES 3, L", 0x9dcb, 3 ], - [ "RES 4, A", 0xa7cb, 3 ], - [ "RES 4, B", 0xa0cb, 3 ], - [ "RES 4, C", 0xa1cb, 3 ], - [ "RES 4, D", 0xa2cb, 3 ], - [ "RES 4, E", 0xa3cb, 3 ], - [ "RES 4, H", 0xa4cb, 3 ], - [ "RES 4, [HL]", 0xa6cb, 3 ], - [ "RES 4, L", 0xa5cb, 3 ], - [ "RES 5, A", 0xafcb, 3 ], - [ "RES 5, B", 0xa8cb, 3 ], - [ "RES 5, C", 0xa9cb, 3 ], - [ "RES 5, D", 0xaacb, 3 ], - [ "RES 5, E", 0xabcb, 3 ], - [ "RES 5, H", 0xaccb, 3 ], - [ "RES 5, [HL]", 0xaecb, 3 ], - [ "RES 5, L", 0xadcb, 3 ], - [ "RES 6, A", 0xb7cb, 3 ], - [ "RES 6, B", 0xb0cb, 3 ], - [ "RES 6, C", 0xb1cb, 3 ], - [ "RES 6, D", 0xb2cb, 3 ], - [ "RES 6, E", 0xb3cb, 3 ], - [ "RES 6, H", 0xb4cb, 3 ], - [ "RES 6, [HL]", 0xb6cb, 3 ], - [ "RES 6, L", 0xb5cb, 3 ], - [ "RES 7, A", 0xbfcb, 3 ], - [ "RES 7, B", 0xb8cb, 3 ], - [ "RES 7, C", 0xb9cb, 3 ], - [ "RES 7, D", 0xbacb, 3 ], - [ "RES 7, E", 0xbbcb, 3 ], - [ "RES 7, H", 0xbccb, 3 ], - [ "RES 7, [HL]", 0xbecb, 3 ], - [ "RES 7, L", 0xbdcb, 3 ], - [ "RETI", 0xd9, 0 ], - [ "RET C", 0xd8, 0 ], - [ "RET NC", 0xd0, 0 ], - [ "RET NZ", 0xc0, 0 ], - [ "RET Z", 0xc8, 0 ], - [ "RET", 0xc9, 0 ], - [ "RLA", 0x17, 0 ], - [ "RL A", 0x17cb, 3 ], - [ "RL B", 0x10cb, 3 ], - [ "RL C", 0x11cb, 3 ], - [ "RLCA", 0x7, 0 ], - [ "RLC A", 0x7cb, 3 ], - [ "RLC B", 0xcb, 3 ], - [ "RLC C", 0x1cb, 3 ], - [ "RLC D", 0x2cb, 3 ], - [ "RLC E", 0x3cb, 3 ], - [ "RLC H", 0x4cb, 3 ], - [ "RLC [HL]", 0x6cb, 3 ], - [ "RLC L", 0x5cb, 3 ], - [ "RL D", 0x12cb, 3 ], - [ "RL E", 0x13cb, 3 ], - [ "RL H", 0x14cb, 3 ], - [ "RL [HL]", 0x16cb, 3 ], - [ "RL L", 0x15cb, 3 ], - [ "RRA", 0x1f, 0 ], - [ "RR A", 0x1fcb, 3 ], - [ "RR B", 0x18cb, 3 ], - [ "RR C", 0x19cb, 3 ], - [ "RRCA", 0xf, 0 ], - [ "RRC A", 0xfcb, 3 ], - [ "RRC B", 0x8cb, 3 ], - [ "RRC C", 0x9cb, 3 ], - [ "RRC D", 0xacb, 3 ], - [ "RRC E", 0xbcb, 3 ], - [ "RRC H", 0xccb, 3 ], - [ "RRC [HL]", 0xecb, 3 ], - [ "RRC L", 0xdcb, 3 ], - [ "RR D", 0x1acb, 3 ], - [ "RR E", 0x1bcb, 3 ], - [ "RR H", 0x1ccb, 3 ], - [ "RR [HL]", 0x1ecb, 3 ], - [ "RR L", 0x1dcb, 3 ], - [ "RST $0", 0xc7, 0 ], - [ "RST $10", 0xd7, 0 ], - [ "RST $18", 0xdf, 0 ], - [ "RST $20", 0xe7, 0 ], - [ "RST $28", 0xef, 0 ], - [ "RST $30", 0xf7, 0 ], - [ "RST $38", 0xff, 0 ], - [ "RST $8", 0xcf, 0 ], - [ "SBC A", 0x9f, 0 ], - [ "SBC B", 0x98, 0 ], - [ "SBC C", 0x99, 0 ], - [ "SBC D", 0x9a, 0 ], - [ "SBC E", 0x9b, 0 ], - [ "SBC H", 0x9c, 0 ], - [ "SBC [HL]", 0x9e, 0 ], - [ "SBC L", 0x9d, 0 ], - [ "SBC x", 0xde, 1 ], - [ "SCF", 0x37, 0 ], - [ "SET 0, A", 0xc7cb, 3 ], - [ "SET 0, B", 0xc0cb, 3 ], - [ "SET 0, C", 0xc1cb, 3 ], - [ "SET 0, D", 0xc2cb, 3 ], - [ "SET 0, E", 0xc3cb, 3 ], - [ "SET 0, H", 0xc4cb, 3 ], - [ "SET 0, [HL]", 0xc6cb, 3 ], - [ "SET 0, L", 0xc5cb, 3 ], - [ "SET 1, A", 0xcfcb, 3 ], - [ "SET 1, B", 0xc8cb, 3 ], - [ "SET 1, C", 0xc9cb, 3 ], - [ "SET 1, D", 0xcacb, 3 ], - [ "SET 1, E", 0xcbcb, 3 ], - [ "SET 1, H", 0xcccb, 3 ], - [ "SET 1, [HL]", 0xcecb, 3 ], - [ "SET 1, L", 0xcdcb, 3 ], - [ "SET 2, A", 0xd7cb, 3 ], - [ "SET 2, B", 0xd0cb, 3 ], - [ "SET 2, C", 0xd1cb, 3 ], - [ "SET 2, D", 0xd2cb, 3 ], - [ "SET 2, E", 0xd3cb, 3 ], - [ "SET 2, H", 0xd4cb, 3 ], - [ "SET 2, [HL]", 0xd6cb, 3 ], - [ "SET 2, L", 0xd5cb, 3 ], - [ "SET 3, A", 0xdfcb, 3 ], - [ "SET 3, B", 0xd8cb, 3 ], - [ "SET 3, C", 0xd9cb, 3 ], - [ "SET 3, D", 0xdacb, 3 ], - [ "SET 3, E", 0xdbcb, 3 ], - [ "SET 3, H", 0xdccb, 3 ], - [ "SET 3, [HL]", 0xdecb, 3 ], - [ "SET 3, L", 0xddcb, 3 ], - [ "SET 4, A", 0xe7cb, 3 ], - [ "SET 4, B", 0xe0cb, 3 ], - [ "SET 4, C", 0xe1cb, 3 ], - [ "SET 4, D", 0xe2cb, 3 ], - [ "SET 4, E", 0xe3cb, 3 ], - [ "SET 4, H", 0xe4cb, 3 ], - [ "SET 4, [HL]", 0xe6cb, 3 ], - [ "SET 4, L", 0xe5cb, 3 ], - [ "SET 5, A", 0xefcb, 3 ], - [ "SET 5, B", 0xe8cb, 3 ], - [ "SET 5, C", 0xe9cb, 3 ], - [ "SET 5, D", 0xeacb, 3 ], - [ "SET 5, E", 0xebcb, 3 ], - [ "SET 5, H", 0xeccb, 3 ], - [ "SET 5, [HL]", 0xeecb, 3 ], - [ "SET 5, L", 0xedcb, 3 ], - [ "SET 6, A", 0xf7cb, 3 ], - [ "SET 6, B", 0xf0cb, 3 ], - [ "SET 6, C", 0xf1cb, 3 ], - [ "SET 6, D", 0xf2cb, 3 ], - [ "SET 6, E", 0xf3cb, 3 ], - [ "SET 6, H", 0xf4cb, 3 ], - [ "SET 6, [HL]", 0xf6cb, 3 ], - [ "SET 6, L", 0xf5cb, 3 ], - [ "SET 7, A", 0xffcb, 3 ], - [ "SET 7, B", 0xf8cb, 3 ], - [ "SET 7, C", 0xf9cb, 3 ], - [ "SET 7, D", 0xfacb, 3 ], - [ "SET 7, E", 0xfbcb, 3 ], - [ "SET 7, H", 0xfccb, 3 ], - [ "SET 7, [HL]", 0xfecb, 3 ], - [ "SET 7, L", 0xfdcb, 3 ], - [ "SLA A", 0x27cb, 3 ], - [ "SLA B", 0x20cb, 3 ], - [ "SLA C", 0x21cb, 3 ], - [ "SLA D", 0x22cb, 3 ], - [ "SLA E", 0x23cb, 3 ], - [ "SLA H", 0x24cb, 3 ], - [ "SLA [HL]", 0x26cb, 3 ], - [ "SLA L", 0x25cb, 3 ], - [ "SRA A", 0x2fcb, 3 ], - [ "SRA B", 0x28cb, 3 ], - [ "SRA C", 0x29cb, 3 ], - [ "SRA D", 0x2acb, 3 ], - [ "SRA E", 0x2bcb, 3 ], - [ "SRA H", 0x2ccb, 3 ], - [ "SRA [HL]", 0x2ecb, 3 ], - [ "SRA L", 0x2dcb, 3 ], - [ "SRL A", 0x3fcb, 3 ], - [ "SRL B", 0x38cb, 3 ], - [ "SRL C", 0x39cb, 3 ], - [ "SRL D", 0x3acb, 3 ], - [ "SRL E", 0x3bcb, 3 ], - [ "SRL H", 0x3ccb, 3 ], - [ "SRL [HL]", 0x3ecb, 3 ], - [ "SRL L", 0x3dcb, 3 ], - [ "STOP", 0x10, 0 ], - [ "SUB A", 0x97, 0 ], - [ "SUB B", 0x90, 0 ], - [ "SUB C", 0x91, 0 ], - [ "SUB D", 0x92, 0 ], - [ "SUB E", 0x93, 0 ], - [ "SUB H", 0x94, 0 ], - [ "SUB [HL]", 0x96, 0 ], - [ "SUB L", 0x95, 0 ], - [ "SUB x", 0xd6, 1 ], - [ "SWAP A", 0x37cb, 3 ], - [ "SWAP B", 0x30cb, 3 ], - [ "SWAP C", 0x31cb, 3 ], - [ "SWAP D", 0x32cb, 3 ], - [ "SWAP E", 0x33cb, 3 ], - [ "SWAP H", 0x34cb, 3 ], - [ "SWAP [HL]", 0x36cb, 3 ], - [ "SWAP L", 0x35cb, 3 ], - [ "XOR A", 0xaf, 0 ], - [ "XOR B", 0xa8, 0 ], - [ "XOR C", 0xa9, 0 ], - [ "XOR D", 0xaa, 0 ], - [ "XOR E", 0xab, 0 ], - [ "XOR H", 0xac, 0 ], - [ "XOR [HL]", 0xae, 0 ], - [ "XOR L", 0xad, 0 ], - [ "XOR x", 0xee, 1 ], - [ "E", 0x100, -1 ], -] - -# construct a more useful version of opt_table -opt_table = {} -for line in temp_opt_table: - opt_table[line[1]] = [line[0], line[2]] -del line - -end_08_scripts_with = [ -0xc9, #ret -0xd9, #reti -0xe9, #jp hl -#0xc3, #jp -##0x18, #jr -###0xda, 0xe9, 0xd2, 0xc2, 0xca, 0xc3, 0x38, 0x30, 0x20, 0x28, 0x18, 0xd8, 0xd0, 0xc0, 0xc8, 0xc9 -] - -discrete_jumps = [0xda, 0xe9, 0xd2, 0xc2, 0xca, 0xc3] -relative_jumps = [0x38, 0x30, 0x20, 0x28, 0x18, 0xc3, 0xda, 0xc2] -relative_unconditional_jumps = [0xc3, 0x18] - -call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd] - -all_labels = {} -def load_labels(filename="labels.json"): - """ - Load labels from specified file. - - If no filename is given, loads 'labels.json'. - """ - global all_labels - - # don't re-load labels each time - if all_labels != {}: - return - - if os.path.exists(filename): - all_labels = json.read(open(filename, "r").read()) - else: - print "You must run crystal.scan_for_predefined_labels() to create \"labels.json\". Trying..." - import crystal - crystal.scan_for_predefined_labels() - -def find_label(local_address, bank_id=0): - # keep an integer - if type(local_address) == str: - local_address = int(local_address.replace("$", "0x"), 16) - - if local_address < 0x8000: - for label_entry in all_labels: - if get_local_address(label_entry["address"]) == local_address: - if label_entry["bank"] == bank_id or label_entry["bank"] == 0: - return label_entry["label"] - if local_address in wram_labels.keys(): - return wram_labels[local_address][-1] - for constants in [gbhw_constants, hram_constants]: - if local_address in constants.keys() and local_address >= 0xff00: - return constants[local_address] - return None - -def find_address_from_label(label): - for label_entry in all_labels: - if label == label_entry["label"]: - return label_entry["address"] - return None - -def asm_label(address): - """ - Return the ASM label using the address. - """ - # why using a random value when you can use the address? - return '.asm_%x' % address - -def data_label(address): - return '.data_%x' % address - -def get_local_address(address): - bank = address / 0x4000 - return (address & 0x3fff) + 0x4000 * bool(bank) - -def get_global_address(address, bank): - if address < 0x8000: - return (address & 0x3fff) + 0x4000 * bank - return None - - return ".ASM_" + hex(address)[2:] - -def output_bank_opcodes(original_offset, max_byte_count=0x4000, include_last_address=True, stop_at=[], debug=False): - """ - Output bank opcodes. - - fs = current_address - b = bank_byte - in = input_data -- rom - bank_size = byte_count - i = offset - ad = end_address - a, oa = current_byte_number - - stop_at can be used to supply a list of addresses to not disassemble - over. This is useful if you know in advance that there are a lot of - fall-throughs. - """ - - load_labels() - load_rom() - - bank_id = original_offset / 0x4000 - if debug: print "bank id is: " + str(bank_id) - - last_hl_address = None #for when we're scanning the main map script - last_a_address = None - used_3d97 = False - - global rom - offset = original_offset - current_byte_number = 0 #start from the beginning - - #we don't actually have an end address, but we'll just say $4000 - end_address = original_offset + max_byte_count - - byte_labels = {} - data_tables = {} - - first_loop = True - output = "" - keep_reading = True - is_data = False - while offset <= end_address and keep_reading: - current_byte = rom[offset] - maybe_byte = current_byte - - # stop at any address - if not first_loop and offset in stop_at: - keep_reading = False - break - - #first check if this byte already has a label - #if it does, use the label - #if not, generate a new label - if offset in byte_labels.keys(): - line_label = byte_labels[offset]["name"] - byte_labels[offset]["usage"] += 1 - output += "\n" - else: - line_label = asm_label(offset) - byte_labels[offset] = {} - byte_labels[offset]["name"] = line_label - byte_labels[offset]["usage"] = 0 - byte_labels[offset]["definition"] = True - output += line_label + "\n" #" ; " + hex(offset) + "\n" - - #find out if there's a two byte key like this - temp_maybe = maybe_byte - temp_maybe += ( rom[offset+1] << 8) - if not is_data and temp_maybe in opt_table.keys() and rom[offset+1]!=0: - opstr = opt_table[temp_maybe][0].lower() - - if "x" in opstr: - for x in range(0, opstr.count("x")): - insertion = rom[offset + 1] - insertion = "$" + hex(insertion)[2:] - - opstr = opstr[:opstr.find("x")].lower() + insertion + opstr[opstr.find("x")+1:].lower() - - current_byte += 1 - offset += 1 - if "?" in opstr: - for y in range(0, opstr.count("?")): - byte1 = rom[offset + 1] - byte2 = rom[offset + 2] - - number = byte1 - number += byte2 << 8; - - insertion = "$%.4x" % (number) - - opstr = opstr[:opstr.find("?")].lower() + insertion + opstr[opstr.find("?")+1:].lower() - - current_byte_number += 2 - offset += 2 - - output += spacing + opstr #+ " ; " + hex(offset) - output += "\n" - - current_byte_number += 2 - offset += 2 - elif not is_data and maybe_byte in opt_table.keys(): - op_code = opt_table[maybe_byte] - op_code_type = op_code[1] - op_code_byte = maybe_byte - - #type = -1 when it's the E op - #if op_code_type != -1: - if op_code_type == 0 and rom[offset] == op_code_byte: - op_str = op_code[0].lower() - - output += spacing + op_code[0].lower() #+ " ; " + hex(offset) - output += "\n" - - offset += 1 - current_byte_number += 1 - elif op_code_type == 1 and rom[offset] == op_code_byte: - oplen = len(op_code[0]) - opstr = copy(op_code[0]) - xes = op_code[0].count("x") - include_comment = False - for x in range(0, xes): - insertion = rom[offset + 1] - insertion = "$" + hex(insertion)[2:] - - if current_byte == 0x18 or current_byte==0x20 or current_byte in relative_jumps: #jr or jr nz - #generate a label for the byte we're jumping to - target_address = offset + 2 + c_int8(rom[offset + 1]).value - if target_address in byte_labels.keys(): - byte_labels[target_address]["usage"] = 1 + byte_labels[target_address]["usage"] - line_label2 = byte_labels[target_address]["name"] - else: - line_label2 = asm_label(target_address) - byte_labels[target_address] = {} - byte_labels[target_address]["name"] = line_label2 - byte_labels[target_address]["usage"] = 1 - byte_labels[target_address]["definition"] = False - - insertion = line_label2 - if has_outstanding_labels(byte_labels) and all_outstanding_labels_are_reverse(byte_labels, offset): - include_comment = True - elif current_byte == 0x3e: - last_a_address = rom[offset + 1] - - opstr = opstr[:opstr.find("x")].lower() + insertion + opstr[opstr.find("x")+1:].lower() - - # because the $ff00+$ff syntax is silly - if opstr.count("$") > 1 and "+" in opstr: - first_orig = opstr[opstr.find("$"):opstr.find("+")] - first_val = eval(first_orig.replace("$","0x")) - - second_orig = opstr[opstr.find("+$")+1:opstr.find("]")] - second_val = eval(second_orig.replace("$","0x")) - - combined_val = "$%.4x" % (first_val + second_val) - result = find_label(combined_val, bank_id) - if result != None: - combined_val = result - - replacetron = "[%s+%s]" % (first_orig, second_orig) - opstr = opstr.replace(replacetron, "[%s]" % combined_val) - - output += spacing + opstr - if include_comment: - output += " ; " + hex(offset) - if current_byte in relative_jumps: - output += " $" + hex(rom[offset + 1])[2:] - output += "\n" - - current_byte_number += 1 - offset += 1 - insertion = "" - - current_byte_number += 1 - offset += 1 - include_comment = False - elif op_code_type == 2 and rom[offset] == op_code_byte: - oplen = len(op_code[0]) - opstr = copy(op_code[0]) - qes = op_code[0].count("?") - for x in range(0, qes): - byte1 = rom[offset + 1] - byte2 = rom[offset + 2] - - number = byte1 - number += byte2 << 8 - - if current_byte not in call_commands + discrete_jumps + relative_jumps: - pointer = get_global_address(number, bank_id) - if pointer not in data_tables.keys(): - data_tables[pointer] = {} - data_tables[pointer]['usage'] = 0 - else: - data_tables[pointer]['usage'] += 1 - - insertion = "$%.4x" % (number) - result = find_label(insertion, bank_id) - if result != None: - insertion = result - - opstr = opstr[:opstr.find("?")].lower() + insertion + opstr[opstr.find("?")+1:].lower() - output += spacing + opstr #+ " ; " + hex(offset) - output += "\n" - - current_byte_number += 2 - offset += 2 - - current_byte_number += 1 - offset += 1 - - if current_byte == 0x21: - last_hl_address = byte1 + (byte2 << 8) - if current_byte == 0xcd: - if number == 0x3d97: used_3d97 = True - - #duck out if this is jp $24d7 - if current_byte == 0xc3 or current_byte in relative_unconditional_jumps: - if current_byte == 0xc3: - if number == 0x3d97: used_3d97 = True - #if number == 0x24d7: #jp - if not has_outstanding_labels(byte_labels) or all_outstanding_labels_are_reverse(byte_labels, offset): - keep_reading = False - is_data = False - break - else: - is_data = True - else: - #if is_data and keep_reading: - output += spacing + "db $" + hex(rom[offset])[2:] #+ " ; " + hex(offset) - output += "\n" - offset += 1 - current_byte_number += 1 - if offset in byte_labels.keys(): - is_data = False - keep_reading = True - #else the while loop would have spit out the opcode - - #these two are done prior - #offset += 1 - #current_byte_number += 1 - - if not is_data and current_byte in relative_unconditional_jumps + end_08_scripts_with: - #stop reading at a jump, relative jump or return - if not has_outstanding_labels(byte_labels) or all_outstanding_labels_are_reverse(byte_labels, offset): - keep_reading = False - is_data = False #cleanup - break - elif offset not in byte_labels.keys() and offset in data_tables.keys(): - is_data = True - keep_reading = True - else: - is_data = False - keep_reading = True - output += "\n" - elif is_data and offset not in byte_labels.keys(): - is_data = True - keep_reading = True - else: - is_data = False - keep_reading = True - - if offset in data_tables.keys(): - output = output.replace('$%x' % (get_local_address(offset)), data_label(offset)) - output += data_label(offset) + '\n' - is_data = True - keep_reading = True - - first_loop = False - - #clean up unused labels - for label_line in byte_labels.keys(): - address = label_line - label_line = byte_labels[label_line] - if label_line["usage"] == 0: - output = output.replace((label_line["name"] + "\n"), "") - - #tone down excessive spacing - output = output.replace("\n\n\n","\n\n") - - #add the offset of the final location - if include_last_address: - output += "; " + hex(offset) - - return (output, offset, last_hl_address, last_a_address, used_3d97) - -def has_outstanding_labels(byte_labels): - """ - Check whether a label is used once in the asm output. - - If so, then that means it has to be called or specified later. - """ - for label_line in byte_labels.keys(): - real_line = byte_labels[label_line] - if real_line["definition"] == False: return True - return False - -def all_outstanding_labels_are_reverse(byte_labels, offset): - for label_id in byte_labels.keys(): - line = byte_labels[label_id] # label_id is also the address - if line["definition"] == False: - if not label_id < offset: return False - return True - - - -if __name__ == "__main__": - load_labels() - addr = sys.argv[1] - if ":" in addr: - addr = addr.split(":") - addr = int(addr[0], 16)*0x4000+(int(addr[1], 16)%0x4000) - else: - label_addr = find_address_from_label(addr) - if label_addr: - addr = label_addr - else: - addr = int(addr, 16) - print output_bank_opcodes(addr)[0] diff --git a/gfx.py b/gfx.py deleted file mode 100644 index c632ac0..0000000 --- a/gfx.py +++ /dev/null @@ -1,1671 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import sys -import png -from math import sqrt, floor, ceil - -from crystal import load_rom - -from pokemon_constants import pokemon_constants -from trainers import trainer_group_names - - -if __name__ != "__main__": - rom = load_rom() - - -def mkdir_p(path): - """ - Make a directory at a given path. - """ - try: - os.makedirs(path) - except OSError as exc: # Python >2.5 - if exc.errno == errno.EEXIST: - pass - else: raise - - -def hex_dump(input, debug=True): - """ - Display hex dump in rows of 16 bytes. - """ - - dump = '' - output = '' - stream = '' - address = 0x00 - margin = 2 + len(hex(len(input))[2:]) - - # dump - for byte in input: - cool = hex(byte)[2:].zfill(2) - dump += cool + ' ' - if debug: stream += cool - - # convenient for testing quick edits in bgb - if debug: output += stream + '\n' - - # get dump info - bytes_per_line = 16 - chars_per_byte = 3 # '__ ' - chars_per_line = bytes_per_line * chars_per_byte - num_lines = int(ceil(float(len(dump)) / float(chars_per_line))) - - # top - # margin - for char in range(margin): - output += ' ' - - for byte in range(bytes_per_line): - output += hex(byte)[2:].zfill(2) + ' ' - output = output[:-1] # last space - - # print hex - for line in range(num_lines): - # address - output += '\n' + hex(address)[2:].zfill(margin - 2) + ': ' - # contents - start = line * chars_per_line - end = chars_per_line + start - 1 # ignore last space - output += dump[start:end] - address += 0x10 - - return output - - -def get_tiles(image): - """ - Split a 2bpp image into 8x8 tiles. - """ - tiles = [] - tile = [] - bytes_per_tile = 16 - - cur_byte = 0 - for byte in image: - # build tile - tile.append(byte) - cur_byte += 1 - # done building? - if cur_byte >= bytes_per_tile: - # push completed tile - tiles.append(tile) - tile = [] - cur_byte = 0 - return tiles - - -def connect(tiles): - """ - Combine 8x8 tiles into a 2bpp image. - """ - out = [] - for tile in tiles: - for byte in tile: - out.append(byte) - return out - - -def transpose(tiles): - """ - Transpose a tile arrangement along line y=x. - """ - - # horizontal <-> vertical - # 00 01 02 03 04 05 00 06 0c 12 18 1e - # 06 07 08 09 0a 0b 01 07 0d 13 19 1f - # 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20 - # 12 13 14 15 16 17 <-> 03 09 0f 15 1b 21 - # 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22 - # 1e 1f 20 21 22 23 05 0b 11 17 1d 23 - # etc - - flipped = [] - t = 0 # which tile we're on - w = int(sqrt(len(tiles))) # assume square image - for tile in tiles: - flipped.append(tiles[t]) - t += w - # end of row? - if t >= w*w: - # wrap around - t -= w*w - # next row - t += 1 - return flipped - - -def to_file(filename, data): - file = open(filename, 'wb') - for byte in data: - file.write('%c' % byte) - file.close() - - - - -# basic rundown of crystal's compression scheme: - -# a control command consists of -# the command (bits 5-7) -# and the count (bits 0-4) -# followed by additional params - -lz_lit = 0 -# print literal for [count] bytes - -lz_iter = 1 -# print one byte [count] times - -lz_alt = 2 -# print alternating bytes (2 params) for [count] bytes - -lz_zeros = 3 -# print 00 for [count] bytes - -# repeater control commands have a signed parameter used to determine the start point -# wraparound is simulated -# positive values are added to the start address of the decompressed data -# and negative values are subtracted from the current position - -lz_repeat = 4 -# print [count] bytes from decompressed data - -lz_flip = 5 -# print [count] bytes from decompressed data in bit order 01234567 - -lz_reverse = 6 -# print [count] bytes from decompressed data backwards - -lz_hi = 7 -# -used when the count exceeds 5 bits. uses a 10-bit count instead -# -bits 2-4 now contain the control code, bits 0-1 are bits 8-9 of the count -# -the following byte contains bits 0-7 of the count - -lz_end = 0xff -# if 0xff is encountered the decompression ends - -# since frontpics have animation tiles lumped onto them, -# sizes must be grabbed from base stats to know when to stop reading them - -max_length = 1 << 10 # can't go higher than 10 bits -lowmax = 1 << 5 # standard 5-bit param - - -class Compressed: - - """ - Compress 2bpp data. - """ - - def __init__(self, image=None, mode='horiz', size=None): - assert image, 'need something to compress!' - image = list(image) - self.image = image - self.pic = [] - self.animtiles = [] - - # only transpose pic (animtiles were never transposed in decompression) - if size != None: - for byte in range((size*size)*16): - self.pic += image[byte] - for byte in range(((size*size)*16),len(image)): - self.animtiles += image[byte] - else: - self.pic = image - - if mode == 'vert': - self.tiles = get_tiles(self.pic) - self.tiles = transpose(self.tiles) - self.pic = connect(self.tiles) - - self.image = self.pic + self.animtiles - - self.end = len(self.image) - - self.byte = None - self.address = 0 - - self.stream = [] - - self.zeros = [] - self.alts = [] - self.iters = [] - self.repeats = [] - self.flips = [] - self.reverses = [] - self.literals = [] - - self.output = [] - - self.compress() - - - def compress(self): - """ - Incomplete, but outputs working compressed data. - """ - - self.address = 0 - - # todo - #self.scanRepeats() - - while ( self.address < self.end ): - - #if (self.repeats): - # self.doRepeats() - - #if (self.flips): - # self.doFlips() - - #if (self.reverses): - # self.doReverses - - if (self.checkWhitespace()): - self.doLiterals() - self.doWhitespace() - - elif (self.checkIter()): - self.doLiterals() - self.doIter() - - elif (self.checkAlts()): - self.doLiterals() - self.doAlts() - - else: # doesn't fit any pattern -> literal - self.addLiteral() - self.next() - - self.doStream() - - # add any literals we've been sitting on - self.doLiterals() - - # done - self.output.append(lz_end) - - - def getCurByte(self): - if self.address < self.end: - self.byte = ord(self.image[self.address]) - else: self.byte = None - - def next(self): - self.address += 1 - self.getCurByte() - - def addLiteral(self): - self.getCurByte() - self.literals.append(self.byte) - if len(self.literals) > max_length: - raise Exception, "literals exceeded max length and the compressor didn't catch it" - elif len(self.literals) == max_length: - self.doLiterals() - - def doLiterals(self): - if len(self.literals) > lowmax: - self.output.append( (lz_hi << 5) | (lz_lit << 2) | ((len(self.literals) - 1) >> 8) ) - self.output.append( (len(self.literals) - 1) & 0xff ) - elif len(self.literals) > 0: - self.output.append( (lz_lit << 5) | (len(self.literals) - 1) ) - for byte in self.literals: - self.output.append(byte) - self.literals = [] - - def doStream(self): - for byte in self.stream: - self.output.append(byte) - self.stream = [] - - - def scanRepeats(self): - """ - Works, but doesn't do flipped/reversed streams yet. - - This takes up most of the compress time and only saves a few bytes - it might be more feasible to exclude it entirely. - """ - - self.repeats = [] - self.flips = [] - self.reverses = [] - - # make a 5-letter word list of the sequence - letters = 5 # how many bytes it costs to use a repeat over a literal - # any shorter and it's not worth the trouble - num_words = len(self.image) - letters - words = [] - for i in range(self.address,num_words): - word = [] - for j in range(letters): - word.append( ord(self.image[i+j]) ) - words.append((word, i)) - - zeros = [] - for zero in range(letters): - zeros.append( 0 ) - - # check for matches - def get_matches(): - # TODO: - # append to 3 different match lists instead of yielding to one - # - #flipped = [] - #for byte in enumerate(this[0]): - # flipped.append( sum(1<<(7-i) for i in range(8) if (this[0][byte])>>i&1) ) - #reversed = this[0][::-1] - # - for whereabout, this in enumerate(words): - for that in range(whereabout+1,len(words)): - if words[that][0] == this[0]: - if words[that][1] - this[1] >= letters: - # remove zeros - if this[0] != zeros: - yield [this[0], this[1], words[that][1]] - - matches = list(get_matches()) - - # remove more zeros - buffer = [] - for match in matches: - # count consecutive zeros in a word - num_zeros = 0 - highest = 0 - for j in range(letters): - if match[0][j] == 0: - num_zeros += 1 - else: - if highest < num_zeros: highest = num_zeros - num_zeros = 0 - if highest < 4: - # any more than 3 zeros in a row isn't worth it - # (and likely to already be accounted for) - buffer.append(match) - matches = buffer - - # combine overlapping matches - buffer = [] - for this, match in enumerate(matches): - if this < len(matches) - 1: # special case for the last match - if matches[this+1][1] <= (match[1] + len(match[0])): # check overlap - if match[1] + len(match[0]) < match[2]: - # next match now contains this match's bytes too - # this only appends the last byte (assumes overlaps are +1 - match[0].append(matches[this+1][0][-1]) - matches[this+1] = match - elif match[1] + len(match[0]) == match[2]: - # we've run into the thing we matched - buffer.append(match) - # else we've gone past it and we can ignore it - else: # no more overlaps - buffer.append(match) - else: # last match, so there's nothing to check - buffer.append(match) - matches = buffer - - # remove alternating sequences - buffer = [] - for match in matches: - for i in range(6 if letters > 6 else letters): - if match[0][i] != match[0][i&1]: - buffer.append(match) - break - matches = buffer - - self.repeats = matches - - - def doRepeats(self): - """doesn't output the right values yet""" - - unusedrepeats = [] - for repeat in self.repeats: - if self.address >= repeat[2]: - - # how far in we are - length = (len(repeat[0]) - (self.address - repeat[2])) - - # decide which side we're copying from - if (self.address - repeat[1]) <= 0x80: - self.doLiterals() - self.stream.append( (lz_repeat << 5) | length - 1 ) - - # wrong? - self.stream.append( (((self.address - repeat[1])^0xff)+1)&0xff ) - - else: - self.doLiterals() - self.stream.append( (lz_repeat << 5) | length - 1 ) - - # wrong? - self.stream.append(repeat[1]>>8) - self.stream.append(repeat[1]&0xff) - - #print hex(self.address) + ': ' + hex(len(self.output)) + ' ' + hex(length) - self.address += length - - else: unusedrepeats.append(repeat) - - self.repeats = unusedrepeats - - - def checkWhitespace(self): - self.zeros = [] - self.getCurByte() - original_address = self.address - - if ( self.byte == 0 ): - while ( self.byte == 0 ) & ( len(self.zeros) <= max_length ): - self.zeros.append(self.byte) - self.next() - if len(self.zeros) > 1: - return True - self.address = original_address - return False - - def doWhitespace(self): - if (len(self.zeros) + 1) >= lowmax: - self.stream.append( (lz_hi << 5) | (lz_zeros << 2) | ((len(self.zeros) - 1) >> 8) ) - self.stream.append( (len(self.zeros) - 1) & 0xff ) - elif len(self.zeros) > 1: - self.stream.append( lz_zeros << 5 | (len(self.zeros) - 1) ) - else: - raise Exception, "checkWhitespace() should prevent this from happening" - - - def checkAlts(self): - self.alts = [] - self.getCurByte() - original_address = self.address - num_alts = 0 - - # make sure we don't check for alts at the end of the file - if self.address+3 >= self.end: return False - - self.alts.append(self.byte) - self.alts.append(ord(self.image[self.address+1])) - - # are we onto smething? - if ( ord(self.image[self.address+2]) == self.alts[0] ): - cur_alt = 0 - while (ord(self.image[(self.address)+1]) == self.alts[num_alts&1]) & (num_alts <= max_length): - num_alts += 1 - self.next() - # include the last alternated byte - num_alts += 1 - self.address = original_address - if num_alts > lowmax: - return True - elif num_alts > 2: - return True - return False - - def doAlts(self): - original_address = self.address - self.getCurByte() - - #self.alts = [] - #num_alts = 0 - - #self.alts.append(self.byte) - #self.alts.append(ord(self.image[self.address+1])) - - #i = 0 - #while (ord(self.image[self.address+1]) == self.alts[i^1]) & (num_alts <= max_length): - # num_alts += 1 - # i ^=1 - # self.next() - ## include the last alternated byte - #num_alts += 1 - - num_alts = len(self.iters) + 1 - - if num_alts > lowmax: - self.stream.append( (lz_hi << 5) | (lz_alt << 2) | ((num_alts - 1) >> 8) ) - self.stream.append( num_alts & 0xff ) - self.stream.append( self.alts[0] ) - self.stream.append( self.alts[1] ) - elif num_alts > 2: - self.stream.append( (lz_alt << 5) | (num_alts - 1) ) - self.stream.append( self.alts[0] ) - self.stream.append( self.alts[1] ) - else: - raise Exception, "checkAlts() should prevent this from happening" - - self.address = original_address - self.address += num_alts - - - def checkIter(self): - self.iters = [] - self.getCurByte() - iter = self.byte - original_address = self.address - while (self.byte == iter) & (len(self.iters) < max_length): - self.iters.append(self.byte) - self.next() - self.address = original_address - if len(self.iters) > 3: - # 3 or fewer isn't worth the trouble and actually longer - # if part of a larger literal set - return True - - return False - - def doIter(self): - self.getCurByte() - iter = self.byte - original_address = self.address - - self.iters = [] - while (self.byte == iter) & (len(self.iters) < max_length): - self.iters.append(self.byte) - self.next() - - if (len(self.iters) - 1) >= lowmax: - self.stream.append( (lz_hi << 5) | (lz_iter << 2) | ((len(self.iters)-1) >> 8) ) - self.stream.append( (len(self.iters) - 1) & 0xff ) - self.stream.append( iter ) - elif len(self.iters) > 3: - # 3 or fewer isn't worth the trouble and actually longer - # if part of a larger literal set - self.stream.append( (lz_iter << 5) | (len(self.iters) - 1) ) - self.stream.append( iter ) - else: - self.address = original_address - raise Exception, "checkIter() should prevent this from happening" - - - - - -class Decompressed: - """ - Parse compressed 2bpp data. - - parameters: - [compressed 2bpp data] - [tile arrangement] default: 'vert' - [size of pic] default: None - [start] (optional) - - splits output into pic [size] and animation tiles if applicable - data can be fed in from rom if [start] is specified - """ - - def __init__(self, lz=None, mode=None, size=None, start=0): - # todo: play nice with Compressed - - assert lz, 'need something to compress!' - self.lz = lz - - self.byte = None - self.address = 0 - self.start = start - - self.output = [] - - self.decompress() - - debug = False - # print tuple containing start and end address - if debug: print '(' + hex(self.start) + ', ' + hex(self.start + self.address+1) + '),' - - # only transpose pic - self.pic = [] - self.animtiles = [] - - if size != None: - self.tiles = get_tiles(self.output) - self.pic = connect(self.tiles[:(size*size)]) - self.animtiles = connect(self.tiles[(size*size):]) - else: self.pic = self.output - - if mode == 'vert': - self.tiles = get_tiles(self.pic) - self.tiles = transpose(self.tiles) - self.pic = connect(self.tiles) - - self.output = self.pic + self.animtiles - - - def decompress(self): - """ - Replica of crystal's decompression. - """ - - self.output = [] - - while True: - self.getCurByte() - - if (self.byte == lz_end): - break - - self.cmd = (self.byte & 0b11100000) >> 5 - - if self.cmd == lz_hi: # 10-bit param - self.cmd = (self.byte & 0b00011100) >> 2 - self.length = (self.byte & 0b00000011) << 8 - self.next() - self.length += self.byte + 1 - else: # 5-bit param - self.length = (self.byte & 0b00011111) + 1 - - # literals - if self.cmd == lz_lit: - self.doLiteral() - elif self.cmd == lz_iter: - self.doIter() - elif self.cmd == lz_alt: - self.doAlt() - elif self.cmd == lz_zeros: - self.doZeros() - - else: # repeaters - self.next() - if self.byte > 0x7f: # negative - self.displacement = self.byte & 0x7f - self.displacement = len(self.output) - self.displacement - 1 - else: # positive - self.displacement = self.byte * 0x100 - self.next() - self.displacement += self.byte - - if self.cmd == lz_flip: - self.doFlip() - elif self.cmd == lz_reverse: - self.doReverse() - else: # lz_repeat - self.doRepeat() - - self.address += 1 - #self.next() # somewhat of a hack - - - def getCurByte(self): - self.byte = ord(self.lz[self.start+self.address]) - - def next(self): - self.address += 1 - self.getCurByte() - - def doLiteral(self): - """ - Copy 2bpp data directly. - """ - for byte in range(self.length): - self.next() - self.output.append(self.byte) - - def doIter(self): - """ - Write one byte repeatedly. - """ - self.next() - for byte in range(self.length): - self.output.append(self.byte) - - def doAlt(self): - """ - Write alternating bytes. - """ - self.alts = [] - self.next() - self.alts.append(self.byte) - self.next() - self.alts.append(self.byte) - - for byte in range(self.length): - self.output.append(self.alts[byte&1]) - - def doZeros(self): - """ - Write zeros. - """ - for byte in range(self.length): - self.output.append(0x00) - - def doFlip(self): - """ - Repeat flipped bytes from 2bpp output. - - eg 11100100 -> 00100111 - quat 3 2 1 0 -> 0 2 1 3 - """ - for byte in range(self.length): - flipped = sum(1<<(7-i) for i in range(8) if self.output[self.displacement+byte]>>i&1) - self.output.append(flipped) - - def doReverse(self): - """ - Repeat reversed bytes from 2bpp output. - """ - for byte in range(self.length): - self.output.append(self.output[self.displacement-byte]) - - def doRepeat(self): - """ - Repeat bytes from 2bpp output. - """ - for byte in range(self.length): - self.output.append(self.output[self.displacement+byte]) - - - -sizes = [ - 5, 6, 7, 5, 6, 7, 5, 6, 7, 5, 5, 7, 5, 5, 7, 5, - 6, 7, 5, 6, 5, 7, 5, 7, 5, 7, 5, 6, 5, 6, 7, 5, - 6, 7, 5, 6, 6, 7, 5, 6, 5, 7, 5, 6, 7, 5, 7, 5, - 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 6, 7, 5, 6, - 7, 5, 7, 7, 5, 6, 7, 5, 6, 5, 6, 6, 6, 7, 5, 7, - 5, 6, 6, 5, 7, 6, 7, 5, 7, 5, 7, 7, 6, 6, 7, 6, - 7, 5, 7, 5, 5, 7, 7, 5, 6, 7, 6, 7, 6, 7, 7, 7, - 6, 6, 7, 5, 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 7, - 6, 7, 7, 5, 5, 6, 6, 6, 6, 5, 6, 5, 6, 7, 7, 7, - 7, 7, 5, 6, 7, 7, 5, 5, 6, 7, 5, 6, 7, 5, 6, 7, - 6, 6, 5, 7, 6, 6, 5, 7, 7, 6, 6, 5, 5, 5, 5, 7, - 5, 6, 5, 6, 7, 7, 5, 7, 6, 7, 5, 6, 7, 5, 5, 6, - 6, 5, 6, 6, 6, 6, 7, 6, 5, 6, 7, 5, 7, 6, 6, 7, - 6, 6, 5, 7, 5, 6, 6, 5, 7, 5, 6, 5, 6, 6, 5, 6, - 6, 7, 7, 6, 7, 7, 5, 7, 6, 7, 7, 5, 7, 5, 6, 6, - 6, 7, 7, 7, 7, 5, 6, 7, 7, 7, 5, -] - -def make_sizes(): - """ - Front pics have specified sizes. - """ - top = 251 - base_stats = 0x51424 - # print monster sizes - address = base_stats + 0x11 - - output = '' - - for id in range(top): - size = (ord(rom[address])) & 0x0f - if id % 16 == 0: output += '\n\t' - output += str(size) + ', ' - address += 0x20 - - print output - - - -fxs = 0xcfcf6 -num_fx = 40 - -def decompress_fx_by_id(id): - address = fxs + id*4 # len_fxptr - # get size - num_tiles = ord(rom[address]) # # tiles - # get pointer - bank = ord(rom[address+1]) - address = (ord(rom[address+3]) << 8) + ord(rom[address+2]) - address = (bank * 0x4000) + (address & 0x3fff) - # decompress - fx = Decompressed(rom, 'horiz', num_tiles, address) - return fx - -def decompress_fx(): - for id in range(num_fx): - fx = decompress_fx_by_id(id) - filename = '../gfx/fx/' + str(id).zfill(3) + '.2bpp' # ../gfx/fx/039.2bpp - to_file(filename, fx.pic) - - -num_pics = 2 -front = 0 -back = 1 - -monsters = 0x120000 -num_monsters = 251 - -unowns = 0x124000 -num_unowns = 26 -unown_dex = 201 - -def decompress_monster_by_id(id=0, type=front): - # no unowns here - if id + 1 == unown_dex: return None - # get size - if type == front: - size = sizes[id] - else: size = None - # get pointer - address = monsters + (id*2 + type)*3 # bank, address - bank = ord(rom[address]) + 0x36 # crystal - address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) - address = (bank * 0x4000) + (address & 0x3fff) - # decompress - monster = Decompressed(rom, 'vert', size, address) - return monster - -def decompress_monsters(type=front): - for id in range(num_monsters): - # decompress - monster = decompress_monster_by_id(id, type) - if monster != None: # no unowns here - if not type: # front - filename = 'front.2bpp' - folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' - to_file(folder+filename, monster.pic) - filename = 'tiles.2bpp' - folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' - to_file(folder+filename, monster.animtiles) - else: # back - filename = 'back.2bpp' - folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' - to_file(folder+filename, monster.pic) - - -def decompress_unown_by_id(letter, type=front): - # get size - if type == front: - size = sizes[unown_dex-1] - else: size = None - # get pointer - address = unowns + (letter*2 + type)*3 # bank, address - bank = ord(rom[address]) + 0x36 # crystal - address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) - address = (bank * 0x4000) + (address & 0x3fff) - # decompress - unown = Decompressed(rom, 'vert', size, address) - return unown - -def decompress_unowns(type=front): - for letter in range(num_unowns): - # decompress - unown = decompress_unown_by_id(letter, type) - - if not type: # front - filename = 'front.2bpp' - folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/' - to_file(folder+filename, unown.pic) - filename = 'tiles.2bpp' - folder = '../gfx/anim/' - to_file(folder+filename, unown.animtiles) - else: # back - filename = 'back.2bpp' - folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/' - to_file(folder+filename, unown.pic) - - -trainers = 0x128000 -num_trainers = 67 - -def decompress_trainer_by_id(id): - # get pointer - address = trainers + id*3 # bank, address - bank = ord(rom[address]) + 0x36 # crystal - address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) - address = (bank * 0x4000) + (address & 0x3fff) - # decompress - trainer = Decompressed(rom, 'vert', None, address) - return trainer - -def decompress_trainers(): - for id in range(num_trainers): - # decompress - trainer = decompress_trainer_by_id(id) - filename = '../gfx/trainers/' + str(id).zfill(3) + '.2bpp' # ../gfx/trainers/066.2bpp - to_file(filename, trainer.pic) - - -# in order of use (sans repeats) -intro_gfx = [ - ('logo', 0x109407), - ('001', 0xE641D), # tilemap - ('unowns', 0xE5F5D), - ('pulse', 0xE634D), - ('002', 0xE63DD), # tilemap - ('003', 0xE5ECD), # tilemap - ('background', 0xE5C7D), - ('004', 0xE5E6D), # tilemap - ('005', 0xE647D), # tilemap - ('006', 0xE642D), # tilemap - ('pichu_wooper', 0xE592D), - ('suicune_run', 0xE555D), - ('007', 0xE655D), # tilemap - ('008', 0xE649D), # tilemap - ('009', 0xE76AD), # tilemap - ('suicune_jump', 0xE6DED), - ('unown_back', 0xE785D), - ('010', 0xE764D), # tilemap - ('011', 0xE6D0D), # tilemap - ('suicune_close', 0xE681D), - ('012', 0xE6C3D), # tilemap - ('013', 0xE778D), # tilemap - ('suicune_back', 0xE72AD), - ('014', 0xE76BD), # tilemap - ('015', 0xE676D), # tilemap - ('crystal_unowns', 0xE662D), - ('017', 0xE672D), # tilemap -] - -def decompress_intro(): - for name, address in intro_gfx: - filename = '../gfx/intro/' + name + '.2bpp' - gfx = Decompressed( rom, 'horiz', None, address ) - to_file(filename, gfx.output) - - -title_gfx = [ - ('suicune', 0x10EF46), - ('logo', 0x10F326), - ('crystal', 0x10FCEE), -] - -def decompress_title(): - for name, address in title_gfx: - filename = '../gfx/title/' + name + '.2bpp' - gfx = Decompressed( rom, 'horiz', None, address ) - to_file(filename, gfx.output) - -def decompress_tilesets(): - tileset_headers = 0x4d596 - len_tileset = 15 - num_tilesets = 0x25 - for tileset in range(num_tilesets): - ptr = tileset*len_tileset + tileset_headers - address = (ord(rom[ptr])*0x4000) + (((ord(rom[ptr+1]))+ord(rom[ptr+2])*0x100)&0x3fff) - tiles = Decompressed( rom, 'horiz', None, address ) - filename = '../gfx/tilesets/'+str(tileset).zfill(2)+'.2bpp' - to_file( filename, tiles.output ) - #print '(' + hex(address) + ', '+ hex(address+tiles.address+1) + '),' - -misc = [ - ('player', 0x2BA1A, 'vert'), - ('dude', 0x2BBAA, 'vert'), - ('town_map', 0xF8BA0, 'horiz'), - ('pokegear', 0x1DE2E4, 'horiz'), - ('pokegear_sprites', 0x914DD, 'horiz'), -] -def decompress_misc(): - for name, address, mode in misc: - filename = '../gfx/misc/' + name + '.2bpp' - gfx = Decompressed( rom, mode, None, address ) - to_file(filename, gfx.output) - -def decompress_all(debug=False): - """ - Decompress all known compressed data in baserom. - """ - - if debug: print 'fronts' - decompress_monsters(front) - if debug: print 'backs' - decompress_monsters(back) - if debug: print 'unown fronts' - decompress_unowns(front) - if debug: print 'unown backs' - decompress_unowns(back) - - if debug: print 'trainers' - decompress_trainers() - - if debug: print 'fx' - decompress_fx() - - if debug: print 'intro' - decompress_intro() - - if debug: print 'title' - decompress_title() - - if debug: print 'tilesets' - decompress_tilesets() - - if debug: print 'misc' - decompress_misc() - - return - - -def decompress_from_address(address, mode='horiz', filename='de.2bpp', size=None): - """ - Write decompressed data from an address to a 2bpp file. - """ - image = Decompressed(rom, mode, size, address) - to_file(filename, image.pic) - - -def decompress_file(filein, fileout, mode='horiz', size=None): - f = open(filein, 'rb') - image = f.read() - f.close() - - de = Decompressed(image, mode, size) - - to_file(fileout, de.pic) - - -def compress_file(filein, fileout, mode='horiz'): - f = open(filein, 'rb') - image = f.read() - f.close() - - lz = Compressed(image, mode) - - to_file(fileout, lz.output) - - - - -def compress_monster_frontpic(id, fileout): - mode = 'vert' - - fpic = '../gfx/pics/' + str(id).zfill(3) + '/front.2bpp' - fanim = '../gfx/pics/' + str(id).zfill(3) + '/tiles.2bpp' - - pic = open(fpic, 'rb').read() - anim = open(fanim, 'rb').read() - image = pic + anim - - lz = Compressed(image, mode, sizes[id-1]) - - out = '../gfx/pics/' + str(id).zfill(3) + '/front.lz' - - to_file(out, lz.output) - - - -def get_uncompressed_gfx(start, num_tiles, filename): - """ - Grab tiles directly from rom and write to file. - """ - bytes_per_tile = 0x10 - length = num_tiles*bytes_per_tile - end = start + length - rom = load_rom() - image = [] - for address in range(start,end): - image.append(ord(rom[address])) - to_file(filename, image) - - - -def hex_to_rgb(word): - red = word & 0b11111 - word >>= 5 - green = word & 0b11111 - word >>= 5 - blue = word & 0b11111 - return (red, green, blue) - -def grab_palettes(address, length=0x80): - output = '' - for word in range(length/2): - color = ord(rom[address+1])*0x100 + ord(rom[address]) - address += 2 - color = hex_to_rgb(color) - red = str(color[0]).zfill(2) - green = str(color[1]).zfill(2) - blue = str(color[2]).zfill(2) - output += '\tRGB '+red+', '+green+', '+blue - output += '\n' - return output - - - - - - - -def dump_monster_pals(): - rom = load_rom() - - pals = 0xa8d6 - pal_length = 0x4 - for mon in range(251): - - name = pokemon_constants[mon+1].title().replace('_','') - num = str(mon+1).zfill(3) - dir = 'gfx/pics/'+num+'/' - - address = pals + mon*pal_length*2 - - - pal_data = [] - for byte in range(pal_length): - pal_data.append(ord(rom[address])) - address += 1 - - filename = 'normal.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (15 - len(name)) - #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' - - - pal_data = [] - for byte in range(pal_length): - pal_data.append(ord(rom[address])) - address += 1 - - filename = 'shiny.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (10 - len(name)) - #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' - - -def dump_trainer_pals(): - rom = load_rom() - - pals = 0xb0d2 - pal_length = 0x4 - for trainer in range(67): - - name = trainer_group_names[trainer+1]['constant'].title().replace('_','') - num = str(trainer).zfill(3) - dir = 'gfx/trainers/' - - address = pals + trainer*pal_length - - pal_data = [] - for byte in range(pal_length): - pal_data.append(ord(rom[address])) - address += 1 - - filename = num+'.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (12 - len(name)) - print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"' - - - -def flatten(planar): - """ - Flatten planar 2bpp image data into a quaternary pixel map. - """ - strips = [] - for pair in range(len(planar)/2): - bottom = ord(planar[(pair*2) ]) - top = ord(planar[(pair*2)+1]) - strip = [] - for i in range(7,-1,-1): - color = ((bottom >> i) & 1) + (((top >> i-1) if i > 0 else (top << 1-i)) & 2) - strip.append(color) - strips += strip - return strips - - -def to_lines(image, width): - """ - Convert a tiled quaternary pixel map to lines of quaternary pixels. - """ - - tile = 8 * 8 - - # so we know how many strips of 8px we're putting into a line - num_columns = width / 8 - # number of lines - height = len(image) / width - - lines = [] - for cur_line in range(height): - tile_row = int(cur_line / 8) - line = [] - for column in range(num_columns): - anchor = num_columns*tile_row*tile + column*tile + (cur_line%8)*8 - line += image[anchor:anchor+8] - lines.append(line) - return lines - -def dmg2rgb(word): - red = word & 0b11111 - word >>= 5 - green = word & 0b11111 - word >>= 5 - blue = word & 0b11111 - alpha = 255 - return ((red<<3)+0b100, (green<<3)+0b100, (blue<<3)+0b100, alpha) - -def rgb_to_dmg(color): - word = (color['r'] / 8) - word += (color['g'] / 8) << 5 - word += (color['b'] / 8) << 10 - return word - - -def png_pal(filename): - palette = [] - with open(filename, 'rb') as pal_data: - words = pal_data.read() - dmg_pals = [] - for word in range(len(words)/2): - dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) - white = (255,255,255,255) - black = (000,000,000,255) - for word in dmg_pals: palette += [dmg2rgb(word)] - if white not in dmg_pals and len(palette) < 4: palette = [white] + palette - if black not in dmg_pals and len(palette) < 4: palette += [black] - return palette - - -def to_png(filein, fileout=None, pal_file=None, height=None, width=None): - """ - Take a planar 2bpp graphics file and converts it to png. - """ - - if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.png' - - image = open(filein, 'rb').read() - - num_pixels = len(image) * 4 - - if num_pixels == 0: return 'empty image!' - - - # unless the pic is square, at least one dimension should be given - - if width == None and height == None: - width = int(sqrt(num_pixels)) - height = width - - elif height == None: - height = num_pixels / width - - elif width == None: - width = num_pixels / height - - - # but try to see if it can be made rectangular - - if width * height != num_pixels: - - # look for possible combos of width/height that would form a rectangle - matches = [] - - # this is pretty inefficient, and there is probably a simpler way - for width in range(8,256+1,8): # we only want dimensions that fit in tiles - height = num_pixels / width - if height % 8 == 0: - matches.append((width, height)) - - # go for the most square image - width, height = sorted(matches, key=lambda (x,y): x+y)[0] # favors height - - - # if it can't, the only option is a width of 1 tile - - if width * height != num_pixels: - width = 8 - height = num_pixels / width - - - # if this still isn't rectangular, then the image isn't made of tiles - - # for now we'll just spit out a warning - if width * height != num_pixels: - print 'Warning! ' + fileout + ' is ' + width + 'x' + height + '(' + width*height + ' pixels),\n' +\ - 'but ' + filein + ' is ' + num_pixels + ' pixels!' - - - # map it out - - lines = to_lines(flatten(image), width) - - if pal_file == None: - if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): - pal_file = os.path.splitext(fileout)[0]+'.pal' - - if pal_file == None: - palette = None - greyscale = True - bitdepth = 2 - inverse = { 0:3, 1:2, 2:1, 3:0 } - map = [[inverse[pixel] for pixel in line] for line in lines] - - else: # gbc color - palette = png_pal(pal_file) - greyscale = False - bitdepth = 8 - map = [[pixel for pixel in line] for line in lines] - - - w = png.Writer(width, height, palette=palette, compression = 9, greyscale = greyscale, bitdepth = bitdepth) - with open(fileout, 'wb') as file: - w.write(file, map) - - - - -def to_2bpp(filein, fileout=None, palout=None): - """ - Take a png and converts it to planar 2bpp. - """ - - if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.2bpp' - - with open(filein, 'rb') as file: - - r = png.Reader(file) - info = r.asRGBA8() - - width = info[0] - height = info[1] - - rgba = list(info[2]) - greyscale = info[3]['greyscale'] - - - padding = { 'left': 0, - 'right': 0, - 'top': 0, - 'bottom': 0, } - #if width % 8 != 0: - # padding['left'] = int(ceil((width / 8 + 8 - width) / 2)) - # padding['right'] = int(floor((width / 8 + 8 - width) / 2)) - #if height % 8 != 0: - # padding['top'] = int(ceil((height / 8 + 8 - height) / 2)) - # padding['bottom'] = int(floor((height / 8 + 8 - height) / 2)) - - - # turn the flat values into something more workable - - pixel_length = 4 # rgba - image = [] - - # while we're at it, let's size up the palette - - palette = [] - - for line in rgba: - newline = [] - for pixel in range(len(line)/pixel_length): - i = pixel * pixel_length - color = { 'r': line[i ], - 'g': line[i+1], - 'b': line[i+2], - 'a': line[i+3], } - newline += [color] - if color not in palette: palette += [color] - image.append(newline) - - # pad out any small palettes - hues = { - 'white': { 'r': 0xff, 'g': 0xff, 'b': 0xff, 'a': 0xff }, - 'black': { 'r': 0x00, 'g': 0x00, 'b': 0x00, 'a': 0xff }, - 'grey': { 'r': 0x55, 'g': 0x55, 'b': 0x55, 'a': 0xff }, - 'gray': { 'r': 0xaa, 'g': 0xaa, 'b': 0xaa, 'a': 0xff }, - } - while len(palette) < 4: - for hue in hues.values(): - if not any(color is hue for color in palette): - palette += [hue] - if len(palette) >= 4: break - - assert len(palette) <= 4, 'Palette should be 4 colors, is really ' + str(len(palette)) - - # sort by luminance - def luminance(color): - # this is actually in reverse, thanks to dmg/cgb palette ordering - rough = { 'r': 4.7, - 'g': 1.4, - 'b': 13.8, } - return sum(color[key] * -rough[key] for key in rough.keys()) - palette = sorted(palette, key=luminance) - - # spit out a new .pal file - # disable this if it causes problems with paletteless images - if palout == None: - if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): - palout = os.path.splitext(fileout)[0]+'.pal' - if palout != None: - output = [] - for color in palette: - word = rgb_to_dmg(color) - output += [word & 0xff] - output += [word >> 8] - to_file(palout, output) - - # create a new map of quaternary color ids - map = [] - if padding['top']: map += [0] * (width + padding['left'] + padding['right']) * padding['top'] - for line in image: - if padding['left']: map += [0] * padding['left'] - for color in line: - map.append(palette.index(color)) - if padding['right']: map += [0] * padding['right'] - if padding['bottom']: map += [0] * (width + padding['left'] + padding['right']) * padding['bottom'] - - # split it into strips of 8, and make them planar - num_columns = width / 8 - num_rows = height / 8 - tile = 8 * 8 - image = [] - for row in range(num_rows): - for column in range(num_columns): - for strip in range(tile / 8): - anchor = row*num_columns*tile + column*tile/8 + strip*width - line = map[anchor:anchor+8] - bottom = 0 - top = 0 - for bit, quad in enumerate(line): - bottom += (quad & 1) << (7-bit) - top += ((quad & 2) >> 1) << (7-bit) - image.append(bottom) - image.append(top) - - to_file(fileout, image) - - -def png_to_lz(filein): - - name = os.path.splitext(filein)[0] - - to_2bpp(filein) - image = open(name+'.2bpp', 'rb').read() - to_file(name+'.lz', Compressed(image).output) - - - - -def mass_to_png(debug=False): - # greyscale - for root, dirs, files in os.walk('../gfx/'): - for name in files: - if debug: print os.path.splitext(name), os.path.join(root, name) - if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) - -def mass_to_colored_png(debug=False): - # greyscale, unless a palette is detected - for root, dirs, files in os.walk('../gfx/'): - if 'pics' not in root and 'trainers' not in root: - for name in files: - if debug: print os.path.splitext(name), os.path.join(root, name) - if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) - os.utime(os.path.join(root, name), None) - - # only monster and trainer pics for now - for root, dirs, files in os.walk('../gfx/pics/'): - for name in files: - if debug: print os.path.splitext(name), os.path.join(root, name) - if os.path.splitext(name)[1] == '.2bpp': - if 'normal.pal' in files: - to_png(os.path.join(root, name), None, os.path.join(root, 'normal.pal')) - else: - to_png(os.path.join(root, name)) - os.utime(os.path.join(root, name), None) - - for root, dirs, files in os.walk('../gfx/trainers/'): - for name in files: - if debug: print os.path.splitext(name), os.path.join(root, name) - if os.path.splitext(name)[1] == '.2bpp': - to_png(os.path.join(root, name)) - os.utime(os.path.join(root, name), None) - - -def mass_decompress(debug=False): - for root, dirs, files in os.walk('../gfx/'): - for name in files: - if 'lz' in name: - if '/pics' in root: - if 'front' in name: - id = root.split('pics/')[1][:3] - if id != 'egg': - with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert', sizes[int(id)-1]) - else: - with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert', 4) - to_file(os.path.join(root, 'front.2bpp'), de.pic) - to_file(os.path.join(root, 'tiles.2bpp'), de.animtiles) - elif 'back' in name: - with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert') - to_file(os.path.join(root, 'back.2bpp'), de.output) - elif '/trainers' in root or '/fx' in root: - with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert') - to_file(os.path.join(root, os.path.splitext(name)[0]+'.2bpp'), de.output) - else: - with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read()) - to_file(os.path.join(root, os.path.splitext(name)[0]+'.2bpp'), de.output) - os.utime(os.path.join(root, name), None) - -def append_terminator_to_lzs(directory): - # fix lzs that were extracted with a missing terminator - for root, dirs, files in os.walk(directory): - for file in files: - if '.lz' in file: - data = open(root+file,'rb').read() - if data[-1] != chr(0xff): - data += chr(0xff) - new = open(root+file,'wb') - new.write(data) - new.close() - -def lz_to_png_by_file(filename): - """ - Convert a lz file to png. Dump a 2bpp file too. - """ - assert filename[-3:] == ".lz" - lz_data = open(filename, "rb").read() - bpp = Decompressed(lz_data).output - bpp_filename = filename.replace(".lz", ".2bpp") - to_file(bpp_filename, bpp) - to_png(bpp_filename) - -def dump_tileset_pngs(): - """ - Convert .lz format tilesets into .png format tilesets. - - Also, leaves a bunch of wonderful .2bpp files everywhere for your amusement. - """ - for tileset_id in range(37): - tileset_filename = "../gfx/tilesets/" + str(tileset_id).zfill(2) + ".lz" - lz_to_png_by_file(tileset_filename) - -def decompress_frontpic(lz_file): - """ - Convert the pic portion of front.lz to front.2bpp - """ - lz = open(lz_file, 'rb').read() - to_file(Decompressed(lz).pic, os.path.splitext(filein)[0] + '.2bpp') - -def decompress_frontpic_anim(lz_file): - """ - Convert the animation tile portion of front.lz to tiles.2bpp - """ - lz = open(lz_file, 'rb').read() - to_file(Decompressed(lz).animtiles, 'tiles.2bpp') - -def expand_pic_palettes(): - """ - Add white and black to palette files with fewer than 4 colors. - - Pokemon Crystal only defines two colors for a pic palette to - save space, filling in black/white at runtime. - Instead of managing palette files of varying length, black - and white are added to pic palettes and excluded from incbins. - """ - for root, dirs, files in os.walk('../gfx/'): - if 'gfx/pics' in root or 'gfx/trainers' in root: - for name in files: - if os.path.splitext(name)[1] == '.pal': - filename = os.path.join(root, name) - palette = bytearray(open(filename, 'rb').read()) - w = bytearray([0xff, 0x7f]) - b = bytearray([0x00, 0x00]) - if len(palette) == 4: - with open(filename, 'wb') as out: - out.write(w + palette + b) - -if __name__ == "__main__": - debug = False - - argv = [None] * 5 - for i, arg in enumerate(sys.argv): - argv[i] = arg - - if argv[1] == 'dump-pngs': - mass_to_colored_png() - - elif argv[1] == 'mass-decompress': - mass_decompress() - - elif argv[1] == 'front-to-2bpp': - decompress_frontpic(argv[2]) - - elif argv[1] == 'anim-from-front': - decompress_frontpic_anim(argv[2]) - - elif argv[1] == 'lz-to-2bpp': - name = os.path.splitext(argv[3])[0] - lz = open(name+'.lz', 'rb').read() - if argv[2] == '--vert': - to_file(name+'.2bpp', Decompressed(lz, 'vert').output) - else: - to_file(name+'.2bpp', Decompressed(lz).output) - - elif argv[1] == 'lz-to-png': - if argv[2] == '--vert': - name = os.path.splitext(argv[3])[0] - lz = open(name+'.lz', 'rb').read() - to_file(name+'.2bpp', Decompressed(lz, 'vert').output) - pic = open(name+'.2bpp', 'rb').read() - to_file(name+'.png', to_png(pic)) - else: - lz_to_png_by_file(argv[2]) - - elif argv[1] == 'png-to-lz': - # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] - if argv[2] == '--front': - # front.2bpp and tiles.2bpp are combined before compression, - # so we have to pass in the anim file and pic size - name = os.path.splitext(argv[4])[0] - to_2bpp(name+'.png', name+'.2bpp') - pic = open(name+'.2bpp', 'rb').read() - anim = open(argv[3], 'rb').read() - size = int(sqrt(len(pic)/16)) # assume square pic - to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output) - elif argv[2] == '--vert': - name = os.path.splitext(argv[3])[0] - to_2bpp(name+'.png', name+'.2bpp') - pic = open(name+'.2bpp', 'rb').read() - to_file(name+'.lz', Compressed(pic, 'vert').output) - else: - png_to_lz(argv[2]) - - elif argv[1] == 'png-to-2bpp': - to_2bpp(argv[2]) - - elif argv[1] == '2bpp-to-lz': - if argv[2] == '--vert': - filein = argv[3] - fileout = argv[4] - compress_file(filein, fileout, 'vert') - else: - filein = argv[2] - fileout = argv[3] - compress_file(filein, fileout) - - elif argv[1] == '2bpp-to-png': - to_png(argv[2]) diff --git a/graph.py b/graph.py deleted file mode 100644 index 47087e5..0000000 --- a/graph.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- - -import networkx as nx - -from romstr import ( - RomStr, - relative_jumps, - call_commands, - relative_unconditional_jumps, -) - -class RomGraph(nx.DiGraph): - """ - Graphs various functions pointing to each other. - - TODO: Bank switches are nasty. They should be detected. Otherwise, - functions will point to non-functions within the same bank. Another way - to detect bankswitches is retroactively. By disassembling one function - after another within the function banks, it can be roughly assumed that - anything pointing to something else (within the same bank) is really - actually a bankswitch. An even better method to handle bankswitches - would be to just detect those situations in the asm (but I presently - forget how bankswitches are performed in pokecrystal). - """ - - # some areas shouldn't be parsed as asm - exclusions = [] - - # where is the first function located? - start_address = 0x150 - - # and where is a good place to stop? - end_address = 0x4000 * 0x03 # only do the first bank? sure.. - - # where is the rom stored? - rompath = "../baserom.gbc" - - def __init__(self, rom=None, **kwargs): - """ - Loads and parses the ROM into a function graph. - """ - # continue the initialization - nx.DiGraph.__init__(self, **kwargs) - - # load the graph - if rom == None: - self.load_rom() - else: - self.rom = rom - - # start parsing the ROM - self.parse() - - def load_rom(self): - """ - Creates a RomStr from rompath. - """ - file_handler = open(self.rompath, "r") - self.rom = RomStr(file_handler.read()) - file_handler.close() - - def parse(self): - """ - Parses the ROM starting with the first function address. Each - function is disassembled and parsed to find where else it leads to. - """ - functions = {} - - address = self.start_address - - other_addresses = set() - - count = 0 - - while True: - if count > 3000: - break - - if address < self.end_address and (address not in functions.keys()) and address >= 0x150: - # address is okay to parse at, keep going - pass - elif len(other_addresses) > 0: - # parse some other address possibly in a remote bank - address = other_addresses.pop() - else: - # no more addresses detected- exit loop - break - - # parse the asm - func = self.rom.to_asm(address) - - # check if there are any nops (probably not a function) - nops = 0 - for (id, command) in func.asm_commands.items(): - if command.has_key("id") and command["id"] == 0x0: - nops += 1 - - # skip this function - if nops > 1: - address = 0 - continue - - # store this parsed function - functions[address] = func - - # where does this function jump to? - used_addresses = set(func.used_addresses()) - - # add this information to the graph - for used_address in used_addresses: - # only add this remote address if it's not yet parsed - if used_address not in functions.keys(): - other_addresses.update([used_address]) - - # add this other address to the graph - if used_address > 100: - self.add_node(used_address) - - # add this as an edge between the two nodes - self.add_edge(address, used_address) - - # setup the next function to be parsed - address = func.last_address - - count += 1 - - self.functions = functions - - def pretty_printer(self): - """ - Shows some text output describing which nodes point to which other - nodes. - """ - print self.edges() - - def to_d3(self): - """ - Exports to d3.js because we're gangster like that. - """ - import networkx.readwrite.json_graph as json_graph - content = json_graph.dumps(self) - fh = open("crystal/crystal.json", "w") - fh.write(content) - fh.close() - - def to_gephi(self): - """ - Generates a gexf file. - """ - nx.write_gexf(self, "graph.gexf") - -class RedGraph(RomGraph): - """ - Not implemented. Go away. - """ - - rompath = "../pokered-baserom.gbc" - -class CryGraph(RomGraph): - exclusions = [ - [0x000, 0x149], - ] - - rompath = "../baserom.gbc" - -if __name__ == "__main__": - crygraph = CryGraph() - crygraph.pretty_printer() - crygraph.to_gephi() diff --git a/interval_map.py b/interval_map.py deleted file mode 100644 index daf22cd..0000000 --- a/interval_map.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- - -from bisect import bisect_left, bisect_right -from itertools import izip - -class IntervalMap(object): - """ - This class maps a set of intervals to a set of values. - - >>> i = IntervalMap() - >>> i[0:5] = "hello world" - >>> i[6:10] = "hello cruel world" - >>> print i[4] - "hello world" - """ - - def __init__(self): - """initializes an empty IntervalMap""" - self._bounds = [] - self._items = [] - self._upperitem = None - - def __setitem__(self, _slice, _value): - """sets an interval mapping""" - assert isinstance(_slice, slice), 'The key must be a slice object' - - if _slice.start is None: - start_point = -1 - else: - start_point = bisect_left(self._bounds, _slice.start) - - if _slice.stop is None: - end_point = -1 - else: - end_point = bisect_left(self._bounds, _slice.stop) - - if start_point>=0: - if start_point < len(self._bounds) and self._bounds[start_point]<_slice.start: - start_point += 1 - - if end_point>=0: - self._bounds[start_point:end_point] = [_slice.start, _slice.stop] - if start_point < len(self._items): - self._items[start_point:end_point] = [self._items[start_point], _value] - else: - self._items[start_point:end_point] = [self._upperitem, _value] - else: - self._bounds[start_point:] = [_slice.start] - if start_point < len(self._items): - self._items[start_point:] = [self._items[start_point], _value] - else: - self._items[start_point:] = [self._upperitem] - self._upperitem = _value - else: - if end_point>=0: - self._bounds[:end_point] = [_slice.stop] - self._items[:end_point] = [_value] - else: - self._bounds[:] = [] - self._items[:] = [] - self._upperitem = _value - - def __getitem__(self,_point): - """gets a value from the mapping""" - assert not isinstance(_point, slice), 'The key cannot be a slice object' - - index = bisect_right(self._bounds, _point) - if index < len(self._bounds): - return self._items[index] - else: - return self._upperitem - - def items(self): - """returns an iterator with each item being - ((low_bound, high_bound), value) - these items are returned in order""" - previous_bound = None - for (b, v) in izip(self._bounds, self._items): - if v is not None: - yield (previous_bound, b), v - previous_bound = b - if self._upperitem is not None: - yield (previous_bound, None), self._upperitem - - def values(self): - """returns an iterator with each item being a stored value - the items are returned in order""" - for v in self._items: - if v is not None: - yield v - if self._upperitem is not None: - yield self._upperitem - - def __repr__(self): - s = [] - for b,v in self.items(): - if v is not None: - s.append('[%r, %r] => %r'%( - b[0], - b[1], - v - )) - return '{'+', '.join(s)+'}' diff --git a/item_constants.py b/item_constants.py deleted file mode 100644 index 3692c92..0000000 --- a/item_constants.py +++ /dev/null @@ -1,240 +0,0 @@ -# -*- coding: utf-8 -*- - -item_constants = { -1: 'MASTER_BALL', -2: 'ULTRA_BALL', -3: 'BRIGHTPOWDER', -4: 'GREAT_BALL', -5: 'POKE_BALL', -7: 'BICYCLE', -8: 'MOON_STONE', -9: 'ANTIDOTE', -10: 'BURN_HEAL', -11: 'ICE_HEAL', -12: 'AWAKENING', -13: 'PARLYZ_HEAL', -14: 'FULL_RESTORE', -15: 'MAX_POTION', -16: 'HYPER_POTION', -17: 'SUPER_POTION', -18: 'POTION', -19: 'ESCAPE_ROPE', -20: 'REPEL', -21: 'MAX_ELIXER', -22: 'FIRE_STONE', -23: 'THUNDERSTONE', -24: 'WATER_STONE', -26: 'HP_UP', -27: 'PROTEIN', -28: 'IRON', -29: 'CARBOS', -30: 'LUCKY_PUNCH', -31: 'CALCIUM', -32: 'RARE_CANDY', -33: 'X_ACCURACY', -34: 'LEAF_STONE', -35: 'METAL_POWDER', -36: 'NUGGET', -37: 'POKE_DOLL', -38: 'FULL_HEAL', -39: 'REVIVE', -40: 'MAX_REVIVE', -41: 'GUARD_SPEC', -42: 'SUPER_REPEL', -43: 'MAX_REPEL', -44: 'DIRE_HIT', -46: 'FRESH_WATER', -47: 'SODA_POP', -48: 'LEMONADE', -49: 'X_ATTACK', -51: 'X_DEFEND', -52: 'X_SPEED', -53: 'X_SPECIAL', -54: 'COIN_CASE', -55: 'ITEMFINDER', -57: 'EXP_SHARE', -58: 'OLD_ROD', -59: 'GOOD_ROD', -60: 'SILVER_LEAF', -61: 'SUPER_ROD', -62: 'PP_UP', -63: 'ETHER', -64: 'MAX_ETHER', -65: 'ELIXER', -66: 'RED_SCALE', -67: 'SECRETPOTION', -68: 'S_S_TICKET', -69: 'MYSTERY_EGG', -70: 'CLEAR_BELL', -71: 'SILVER_WING', -72: 'MOOMOO_MILK', -73: 'QUICK_CLAW', -74: 'PSNCUREBERRY', -75: 'GOLD_LEAF', -76: 'SOFT_SAND', -77: 'SHARP_BEAK', -78: 'PRZCUREBERRY', -79: 'BURNT_BERRY', -80: 'ICE_BERRY', -81: 'POISON_BARB', -82: "KINGS_ROCK", -83: 'BITTER_BERRY', -84: 'MINT_BERRY', -85: 'RED_APRICORN', -86: 'TINYMUSHROOM', -87: 'BIG_MUSHROOM', -88: 'SILVERPOWDER', -89: 'BLU_APRICORN', -91: 'AMULET_COIN', -92: 'YLW_APRICORN', -93: 'GRN_APRICORN', -94: 'CLEANSE_TAG', -95: 'MYSTIC_WATER', -96: 'TWISTEDSPOON', -97: 'WHT_APRICORN', -98: 'BLACKBELT', -99: 'BLK_APRICORN', -101: 'PNK_APRICORN', -102: 'BLACKGLASSES', -103: 'SLOWPOKETAIL', -104: 'PINK_BOW', -105: 'STICK', -106: 'SMOKE_BALL', -107: 'NEVERMELTICE', -108: 'MAGNET', -109: 'MIRACLEBERRY', -110: 'PEARL', -111: 'BIG_PEARL', -112: 'EVERSTONE', -113: 'SPELL_TAG', -114: 'RAGECANDYBAR', -115: 'GS_BALL', -116: 'BLUE_CARD', -117: 'MIRACLE_SEED', -118: 'THICK_CLUB', -119: 'FOCUS_BAND', -121: 'ENERGYPOWDER', -122: 'ENERGY_ROOT', -123: 'HEAL_POWDER', -124: 'REVIVAL_HERB', -125: 'HARD_STONE', -126: 'LUCKY_EGG', -127: 'CARD_KEY', -128: 'MACHINE_PART', -129: 'EGG_TICKET', -130: 'LOST_ITEM', -131: 'STARDUST', -132: 'STAR_PIECE', -133: 'BASEMENT_KEY', -134: 'PASS', -138: 'CHARCOAL', -139: 'BERRY_JUICE', -140: 'SCOPE_LENS', -143: 'METAL_COAT', -144: 'DRAGON_FANG', -146: 'LEFTOVERS', -150: 'MYSTERYBERRY', -151: 'DRAGON_SCALE', -152: 'BERSERK_GENE', -156: 'SACRED_ASH', -157: 'HEAVY_BALL', -158: 'FLOWER_MAIL', -159: 'LEVEL_BALL', -160: 'LURE_BALL', -161: 'FAST_BALL', -163: 'LIGHT_BALL', -164: 'FRIEND_BALL', -165: 'MOON_BALL', -166: 'LOVE_BALL', -167: 'NORMAL_BOX', -168: 'GORGEOUS_BOX', -169: 'SUN_STONE', -170: 'POLKADOT_BOW', -172: 'UP_GRADE', -173: 'BERRY', -174: 'GOLD_BERRY', -175: 'SQUIRTBOTTLE', -177: 'PARK_BALL', -178: 'RAINBOW_WING', -180: 'BRICK_PIECE', -181: 'SURF_MAIL', -182: 'LITEBLUEMAIL', -183: 'PORTRAITMAIL', -184: 'LOVELY_MAIL', -185: 'EON_MAIL', -186: 'MORPH_MAIL', -187: 'BLUESKY_MAIL', -188: 'MUSIC_MAIL', -189: 'MIRAGE_MAIL', -191: 'TM_01', -192: 'TM_02', -193: 'TM_03', -194: 'TM_04', -196: 'TM_05', -197: 'TM_06', -198: 'TM_07', -199: 'TM_08', -200: 'TM_09', -201: 'TM_10', -202: 'TM_11', -203: 'TM_12', -204: 'TM_13', -205: 'TM_14', -206: 'TM_15', -207: 'TM_16', -208: 'TM_17', -209: 'TM_18', -210: 'TM_19', -211: 'TM_20', -212: 'TM_21', -213: 'TM_22', -214: 'TM_23', -215: 'TM_24', -216: 'TM_25', -217: 'TM_26', -218: 'TM_27', -219: 'TM_28', -221: 'TM_29', -222: 'TM_30', -223: 'TM_31', -224: 'TM_32', -225: 'TM_33', -226: 'TM_34', -227: 'TM_35', -228: 'TM_36', -229: 'TM_37', -230: 'TM_38', -231: 'TM_39', -232: 'TM_40', -233: 'TM_41', -234: 'TM_42', -235: 'TM_43', -236: 'TM_44', -237: 'TM_45', -238: 'TM_46', -239: 'TM_47', -240: 'TM_48', -241: 'TM_49', -242: 'TM_50', -243: 'HM_01', -244: 'HM_02', -245: 'HM_03', -246: 'HM_04', -247: 'HM_05', -248: 'HM_06', -249: 'HM_07', -} - -def find_item_label_by_id(id): - if id in item_constants.keys(): - return item_constants[id] - else: return None - -def generate_item_constants(): - """make a list of items to put in constants.asm""" - output = "" - for (id, item) in item_constants.items(): - val = ("$%.2x"%id).upper() - while len(item)<13: item+= " " - output += item + " EQU " + val + "\n" - return output diff --git a/labels.py b/labels.py deleted file mode 100644 index d553bdc..0000000 --- a/labels.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Various label/line-related functions. -""" - -from pointers import ( - calculate_pointer, - calculate_bank, -) - -def remove_quoted_text(line): - """get rid of content inside quotes - and also removes the quotes from the input string""" - while line.count("\"") % 2 == 0 and line.count("\"") > 0: - first = line.find("\"") - second = line.find("\"", first+1) - line = line[0:first] + line[second+1:] - while line.count("\'") % 2 == 0 and line.count("'") > 0: - first = line.find("\'") - second = line.find("\'", first+1) - line = line[0:first] + line[second+1:] - return line - -def line_has_comment_address(line, returnable={}, bank=None): - """checks that a given line has a comment - with a valid address, and returns the address in the object. - Note: bank is required if you have a 4-letter-or-less address, - because otherwise there is no way to figure out which bank - is curretly being scanned.""" - #first set the bank/offset to nada - returnable["bank"] = None - returnable["offset"] = None - returnable["address"] = None - #only valid characters are 0-9a-fA-F - valid = [str(x) for x in range(10)] + \ - [chr(x) for x in range(ord('a'), ord('f')+1)] + \ - [chr(x) for x in range(ord('A'), ord('F')+1)] - #check if there is a comment in this line - if ";" not in line: - return False - #first throw away anything in quotes - if (line.count("\"") % 2 == 0 and line.count("\"")!=0) \ - or (line.count("\'") % 2 == 0 and line.count("\'")!=0): - line = remove_quoted_text(line) - #check if there is still a comment in this line after quotes removed - if ";" not in line: - return False - #but even if there's a semicolon there must be later text - if line[-1] == ";": - return False - #and just a space doesn't count - if line[-2:] == "; ": - return False - #and multiple whitespace doesn't count either - line = line.rstrip(" ").lstrip(" ") - if line[-1] == ";": - return False - #there must be more content after the semicolon - if len(line)-1 == line.find(";"): - return False - #split it up into the main comment part - comment = line[line.find(";")+1:] - #don't want no leading whitespace - comment = comment.lstrip(" ").rstrip(" ") - #split up multi-token comments into single tokens - token = comment - if " " in comment: - #use the first token in the comment - token = comment.split(" ")[0] - if token in ["0x", "$", "x", ":"]: - return False - offset = None - #process a token with a A:B format - if ":" in token: #3:3F0A, $3:$3F0A, 0x3:0x3F0A, 3:3F0A - #split up the token - bank_piece = token.split(":")[0].lower() - offset_piece = token.split(":")[1].lower() - #filter out blanks/duds - if bank_piece in ["$", "0x", "x"] \ - or offset_piece in ["$", "0x", "x"]: - return False - #they can't have both "$" and "x" - if "$" in bank_piece and "x" in bank_piece: - return False - if "$" in offset_piece and "x" in offset_piece: - return False - #process the bank piece - if "$" in bank_piece: - bank_piece = bank_piece.replace("$", "0x") - #check characters for validity? - for c in bank_piece.replace("x", ""): - if c not in valid: - return False - bank = int(bank_piece, 16) - #process the offset piece - if "$" in offset_piece: - offset_piece = offset_piece.replace("$", "0x") - #check characters for validity? - for c in offset_piece.replace("x", ""): - if c not in valid: - return False - if len(offset_piece) == 0: - return None - offset = int(offset_piece, 16) - #filter out blanks/duds - elif token in ["$", "0x", "x"]: - return False - #can't have both "$" and "x" in the number - elif "$" in token and "x" in token: - return False - elif "x" in token and not "0x" in token: #it should be 0x - return False - elif "$" in token and not "x" in token: - token = token.replace("$", "0x") - offset = int(token, 16) - elif "0x" in token and not "$" in token: - offset = int(token, 16) - else: #might just be "1" at this point - token = token.lower() - #check if there are bad characters - for c in token: - if c not in valid: - return False - offset = int(token, 16) - if offset == None and bank == None: - return False - if bank == None: - bank = calculate_bank(offset) - returnable["bank"] = bank - returnable["offset"] = offset - returnable["address"] = calculate_pointer(offset, bank=bank) - return True - -def get_address_from_line_comment(line, bank=None): - """ - wrapper for line_has_comment_address - """ - returnable = {} - result = line_has_comment_address(line, returnable=returnable, bank=bank) - if not result: - return False - return returnable["address"] - -def line_has_label(line): - """returns True if the line has an asm label""" - if not isinstance(line, str): - raise Exception, "can't check this type of object" - line = line.rstrip(" ").lstrip(" ") - line = remove_quoted_text(line) - if ";" in line: - line = line.split(";")[0] - if 0 <= len(line) <= 1: - return False - if ":" not in line: - return False - if line[0] == ";": - return False - if line[0] == "\"": - return False - if "::" in line: - return False - return True - -def get_label_from_line(line): - """returns the label from the line""" - #check if the line has a label - if not line_has_label(line): - return None - #split up the line - label = line.split(":")[0] - return label diff --git a/map_names.py b/map_names.py deleted file mode 100644 index 00cf452..0000000 --- a/map_names.py +++ /dev/null @@ -1,484 +0,0 @@ -# -*- coding: utf-8 -*- - -# this is modified in crystal.py during run-time -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 44"}, - 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": "Burned 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 North"}, - 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 South"}, - 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"}, - }, -} diff --git a/move_constants.py b/move_constants.py deleted file mode 100644 index a20af85..0000000 --- a/move_constants.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- - -moves = { -0x01: "POUND", -0x02: "KARATE_CHOP", -0x03: "DOUBLESLAP", -0x04: "COMET_PUNCH", -0x05: "MEGA_PUNCH", -0x06: "PAY_DAY", -0x07: "FIRE_PUNCH", -0x08: "ICE_PUNCH", -0x09: "THUNDERPUNCH", -0x0A: "SCRATCH", -0x0B: "VICEGRIP", -0x0C: "GUILLOTINE", -0x0D: "RAZOR_WIND", -0x0E: "SWORDS_DANCE", -0x0F: "CUT", -0x10: "GUST", -0x11: "WING_ATTACK", -0x12: "WHIRLWIND", -0x13: "FLY", -0x14: "BIND", -0x15: "SLAM", -0x16: "VINE_WHIP", -0x17: "STOMP", -0x18: "DOUBLE_KICK", -0x19: "MEGA_KICK", -0x1A: "JUMP_KICK", -0x1B: "ROLLING_KICK", -0x1C: "SAND_ATTACK", -0x1D: "HEADBUTT", -0x1E: "HORN_ATTACK", -0x1F: "FURY_ATTACK", -0x20: "HORN_DRILL", -0x21: "TACKLE", -0x22: "BODY_SLAM", -0x23: "WRAP", -0x24: "TAKE_DOWN", -0x25: "THRASH", -0x26: "DOUBLE_EDGE", -0x27: "TAIL_WHIP", -0x28: "POISON_STING", -0x29: "TWINEEDLE", -0x2A: "PIN_MISSILE", -0x2B: "LEER", -0x2C: "BITE", -0x2D: "GROWL", -0x2E: "ROAR", -0x2F: "SING", -0x30: "SUPERSONIC", -0x31: "SONICBOOM", -0x32: "DISABLE", -0x33: "ACID", -0x34: "EMBER", -0x35: "FLAMETHROWER", -0x36: "MIST", -0x37: "WATER_GUN", -0x38: "HYDRO_PUMP", -0x39: "SURF", -0x3A: "ICE_BEAM", -0x3B: "BLIZZARD", -0x3C: "PSYBEAM", -0x3D: "BUBBLEBEAM", -0x3E: "AURORA_BEAM", -0x3F: "HYPER_BEAM", -0x40: "PECK", -0x41: "DRILL_PECK", -0x42: "SUBMISSION", -0x43: "LOW_KICK", -0x44: "COUNTER", -0x45: "SEISMIC_TOSS", -0x46: "STRENGTH", -0x47: "ABSORB", -0x48: "MEGA_DRAIN", -0x49: "LEECH_SEED", -0x4A: "GROWTH", -0x4B: "RAZOR_LEAF", -0x4C: "SOLARBEAM", -0x4D: "POISONPOWDER", -0x4E: "STUN_SPORE", -0x4F: "SLEEP_POWDER", -0x50: "PETAL_DANCE", -0x51: "STRING_SHOT", -0x52: "DRAGON_RAGE", -0x53: "FIRE_SPIN", -0x54: "THUNDERSHOCK", -0x55: "THUNDERBOLT", -0x56: "THUNDER_WAVE", -0x57: "THUNDER", -0x58: "ROCK_THROW", -0x59: "EARTHQUAKE", -0x5A: "FISSURE", -0x5B: "DIG", -0x5C: "TOXIC", -0x5D: "CONFUSION", -0x5E: "PSYCHIC_M", -0x5F: "HYPNOSIS", -0x60: "MEDITATE", -0x61: "AGILITY", -0x62: "QUICK_ATTACK", -0x63: "RAGE", -0x64: "TELEPORT", -0x65: "NIGHT_SHADE", -0x66: "MIMIC", -0x67: "SCREECH", -0x68: "DOUBLE_TEAM", -0x69: "RECOVER", -0x6A: "HARDEN", -0x6B: "MINIMIZE", -0x6C: "SMOKESCREEN", -0x6D: "CONFUSE_RAY", -0x6E: "WITHDRAW", -0x6F: "DEFENSE_CURL", -0x70: "BARRIER", -0x71: "LIGHT_SCREEN", -0x72: "HAZE", -0x73: "REFLECT", -0x74: "FOCUS_ENERGY", -0x75: "BIDE", -0x76: "METRONOME", -0x77: "MIRROR_MOVE", -0x78: "SELFDESTRUCT", -0x79: "EGG_BOMB", -0x7A: "LICK", -0x7B: "SMOG", -0x7C: "SLUDGE", -0x7D: "BONE_CLUB", -0x7E: "FIRE_BLAST", -0x7F: "WATERFALL", -0x80: "CLAMP", -0x81: "SWIFT", -0x82: "SKULL_BASH", -0x83: "SPIKE_CANNON", -0x84: "CONSTRICT", -0x85: "AMNESIA", -0x86: "KINESIS", -0x87: "SOFTBOILED", -0x88: "HI_JUMP_KICK", -0x89: "GLARE", -0x8A: "DREAM_EATER", -0x8B: "POISON_GAS", -0x8C: "BARRAGE", -0x8D: "LEECH_LIFE", -0x8E: "LOVELY_KISS", -0x8F: "SKY_ATTACK", -0x90: "TRANSFORM", -0x91: "BUBBLE", -0x92: "DIZZY_PUNCH", -0x93: "SPORE", -0x94: "FLASH", -0x95: "PSYWAVE", -0x96: "SPLASH", -0x97: "ACID_ARMOR", -0x98: "CRABHAMMER", -0x99: "EXPLOSION", -0x9A: "FURY_SWIPES", -0x9B: "BONEMERANG", -0x9C: "REST", -0x9D: "ROCK_SLIDE", -0x9E: "HYPER_FANG", -0x9F: "SHARPEN", -0xA0: "CONVERSION", -0xA1: "TRI_ATTACK", -0xA2: "SUPER_FANG", -0xA3: "SLASH", -0xA4: "SUBSTITUTE", -0xA5: "STRUGGLE", -0xA6: "SKETCH", -0xA7: "TRIPLE_KICK", -0xA8: "THIEF", -0xA9: "SPIDER_WEB", -0xAA: "MIND_READER", -0xAB: "NIGHTMARE", -0xAC: "FLAME_WHEEL", -0xAD: "SNORE", -0xAE: "CURSE", -0xAF: "FLAIL", -0xB0: "CONVERSION2", -0xB1: "AEROBLAST", -0xB2: "COTTON_SPORE", -0xB3: "REVERSAL", -0xB4: "SPITE", -0xB5: "POWDER_SNOW", -0xB6: "PROTECT", -0xB7: "MACH_PUNCH", -0xB8: "SCARY_FACE", -0xB9: "FAINT_ATTACK", -0xBA: "SWEET_KISS", -0xBB: "BELLY_DRUM", -0xBC: "SLUDGE_BOMB", -0xBD: "MUD_SLAP", -0xBE: "OCTAZOOKA", -0xBF: "SPIKES", -0xC0: "ZAP_CANNON", -0xC1: "FORESIGHT", -0xC2: "DESTINY_BOND", -0xC3: "PERISH_SONG", -0xC4: "ICY_WIND", -0xC5: "DETECT", -0xC6: "BONE_RUSH", -0xC7: "LOCK_ON", -0xC8: "OUTRAGE", -0xC9: "SANDSTORM", -0xCA: "GIGA_DRAIN", -0xCB: "ENDURE", -0xCC: "CHARM", -0xCD: "ROLLOUT", -0xCE: "FALSE_SWIPE", -0xCF: "SWAGGER", -0xD0: "MILK_DRINK", -0xD1: "SPARK", -0xD2: "FURY_CUTTER", -0xD3: "STEEL_WING", -0xD4: "MEAN_LOOK", -0xD5: "ATTRACT", -0xD6: "SLEEP_TALK", -0xD7: "HEAL_BELL", -0xD8: "RETURN", -0xD9: "PRESENT", -0xDA: "FRUSTRATION", -0xDB: "SAFEGUARD", -0xDC: "PAIN_SPLIT", -0xDD: "SACRED_FIRE", -0xDE: "MAGNITUDE", -0xDF: "DYNAMICPUNCH", -0xE0: "MEGAHORN", -0xE1: "DRAGONBREATH", -0xE2: "BATON_PASS", -0xE3: "ENCORE", -0xE4: "PURSUIT", -0xE5: "RAPID_SPIN", -0xE6: "SWEET_SCENT", -0xE7: "IRON_TAIL", -0xE8: "METAL_CLAW", -0xE9: "VITAL_THROW", -0xEA: "MORNING_SUN", -0xEB: "SYNTHESIS", -0xEC: "MOONLIGHT", -0xED: "HIDDEN_POWER", -0xEE: "CROSS_CHOP", -0xEF: "TWISTER", -0xF0: "RAIN_DANCE", -0xF1: "SUNNY_DAY", -0xF2: "CRUNCH", -0xF3: "MIRROR_COAT", -0xF4: "PSYCH_UP", -0xF5: "EXTREMESPEED", -0xF6: "ANCIENTPOWER", -0xF7: "SHADOW_BALL", -0xF8: "FUTURE_SIGHT", -0xF9: "ROCK_SMASH", -0xFA: "WHIRLPOOL", -0xFB: "BEAT_UP", -} diff --git a/old_parse_scripts.py b/old_parse_scripts.py deleted file mode 100644 index 8ed10ef..0000000 --- a/old_parse_scripts.py +++ /dev/null @@ -1,1885 +0,0 @@ -# -*- coding: utf-8 -*- -""" -This is in a separate file because it screws up highlighting. Doesn't -do anything on its own. - -old_parse is a part of the Script class -""" - -def old_parse(self, *args, **kwargs): - """parses a script-engine script; force=True if you want to re-parse - and get the debug information""" - print "Script.old_parse address="+hex(self.address) - #can't handle more than one argument - if len(args) > 1: - raise Exception, "Script.parse_script doesn't know how to handle positional arguments" - #use the first positional argument as the address - elif len(args) == 1: - self.address = args[0] - if type(self.address) == str: - self.address = int(self.address, 16) - elif type(self.address) != int: - raise Exception, "address param is the wrong type" - #parse any keyword arguments, first make up the defaults - kwargsorig = {"map_group": None, "map_id": None, "force": False, "debug": True, "origin": False} - #let the caller override any defaults - kwargsorig.update(kwargs) - #copy these into kwargs - kwargs = kwargsorig - #set these defaults - map_group = kwargs["map_group"] - map_id = kwargs["map_id"] - force = kwargs["force"] - debug = kwargs["debug"] - origin = kwargs["origin"] - self.map_group = map_group - self.map_id = map_id - - global rom - if rom == None: - direct_load_rom() - - #max number of commands in a 'recursive' script - max_cmds = 150 - - #set the address to be parsed - address = self.address - original_start_address = address - - #don't parse these crazy things (battle tower things, some rival things, etc.) - if address in stop_points: - print "got " + hex(address) + ".. map_group=" + str(map_group) + " map_id=" + str(map_id) - return None - #don't parse anything that looks crazy - if address < 0x4000 and address not in [0x26ef, 0x114, 0x1108]: - print "address is less than 0x4000.. address is: " + hex(address) - sys.exit() - - #check if work is being repeated - if is_script_already_parsed_at(address) and not force: - raise Exception, "this script has already been parsed before, please use that instance" - #use the commands from a previously-parsed Script object - #self.commands = script_parse_table[address].commands - #return True - - #return a previously-created Script object - #return script_parse_table[address] - - #this next line stops the same script from being re-parsed multiple times - #for instance.. maybe there's a script jump, then a jump back - #the original script should only be parsed once - script_parse_table[original_start_address:original_start_address+1] = "incomplete Script" - - #set up some variables - self.commands = {} - commands = self.commands - offset = address - end = False - - #main loop.. parse each command byte - while not end: - #reset variables so we don't contaminate this command - info, long_info, size = None, None, 0 - #read the current command byte - command_byte = ord(rom[offset]) - #setup the current command representation - command = {"type": command_byte, "start_address": offset} - - #size is the total size including the command byte - #last_byte_address is offset+size-1 - start_address = offset - - if (len(commands.keys()) > max_cmds) and origin != False: - print "too many commands in this script? might not be a script (starting at: " +\ - hex(original_start_address) + ").. called from a script at: " + hex(origin) - sys.exit() - - #start checking against possible command bytes - if command_byte == 0x00: #Pointer code [2b+ret] - pksv_name = "2call" - info = "pointer code" - long_info = """ - 2byte pointer points to script; when pointed script ends --> return to old script - [code][2 byte pointer] - """ - size = 3 - start_address = offset - last_byte_address = offset + size - 1 - pointer = calculate_pointer_from_bytes_at(start_address+1) - if pointer == None: - raise Exception, "pointer is None (shouldn't be None pointer on 0x0 script command" - command["pointer"] = pointer - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["script"] = script - elif command_byte == 0x01: #Pointer code [3b+ret] - pksv_name = "3call" - info = "pointer code" - long_info = """ - 3byte pointer points to script; when pointed script ends --> return to old script - [Code][resp. pointer(2byte or 3byte)] - """ - size = 4 - info = "pointer code" - pointer = calculate_pointer_from_bytes_at(start_address+1, bank=True) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x02: #Pointer code [2b+3b+ret] - info = "pointer code" - long_info = """ - 2byte pointer points to 3byte pointer; when pointed script --> return to old script - [Code][resp. pointer(2byte or 3byte)] - """ - size = 3 - pointer = calculate_pointer_from_bytes_at(start_address+1) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x03: #Pointer code [2b] - #XXX what does "new script is part of main script" mean? - info = "pointer code" - long_info = """ - 2byte pointer points to script; new script is part of main script - [Code][resp. pointer(2byte or 3byte)] - """ - size = 3 - pointer = calculate_pointer_from_bytes_at(start_address+1) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - end = True #according to pksv - elif command_byte == 0x04: #Pointer code [3b] - info = "pointer code" - long_info = """ - 3byte pointer points to script; new script is part of main script - [Code][resp. pointer(2byte or 3byte)] - """ - size = 4 - pointer = calculate_pointer_from_bytes_at(start_address+1, bank=True) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - end = True #according to pksv - elif command_byte == 0x05: #Pointer code [2b+3b] - info = "pointer code" - long_info = """ - 2byte pointer points to 3byte pointer; new script is part of main script - [Code][resp. pointer(2byte or 3byte)] - """ - size = 3 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1) - command["target_pointer"] = calculate_pointer_from_bytes_at(command["pointer"], bank=True) - - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(command["target_pointer"])+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(command["target_pointer"], original_start_address, debug=debug) - command["script"] = script - end = True #according to pksv - elif command_byte == 0x06: #RAM check [=byte] - info = "RAM check [=byte]" - long_info = """ - When the conditional is true... - .. then go to pointed script, else resume interpreting after the pointer - """ - size = 4 - command["byte"] = ord(rom[start_address+1]) - pointer = calculate_pointer_from_bytes_at(start_address+2) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x07: #RAM check [<>byte] - info = "RAM check [<>byte]" - long_info = """ - When the conditional is true... - .. then go to pointed script, else resume interpreting after the pointer - """ - size = 4 - command["byte"] = ord(rom[start_address+1]) - pointer = calculate_pointer_from_bytes_at(start_address+2) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x08: #RAM check [=0] - info = "RAM check [=0]" - long_info = """ - When the conditional is true... - .. then go to pointed script, else resume interpreting after the pointer - """ - size = 3 - pointer = calculate_pointer_from_bytes_at(start_address+1) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x09: #RAM check [<>0] - info = "RAM check [<>0]" - long_info = """ - When the conditional is true... - .. then go to pointed script, else resume interpreting after the pointer - """ - size = 3 - pointer = calculate_pointer_from_bytes_at(start_address+1) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x0A: #RAM check [byte] - info = "RAM check [>byte]" - long_info = """ - When the conditional is true... - .. then go to pointed script, else resume interpreting after the pointer - """ - size = 4 - command["byte"] = ord(rom[start_address+1]) - pointer = calculate_pointer_from_bytes_at(start_address+2) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) - command["pointer"] = pointer - command["script"] = script - elif command_byte == 0x0C: #0C codes [xxyy] - info = "call predefined script then end" - long_info = """ - Calls predefined scripts. After this code the script ends. - [0C][xxyy] - """ - size = 3 - end = True - byte1 = ord(rom[offset+1]) - byte2 = ord(rom[offset+2]) - number = byte1 + (byte2 << 8) - #0000 to 000AD ... XXX how should these be handled? - command["predefined_script_number"] = number - elif command_byte == 0x0D: #0D codes [xxyy] - info = "call some predefined script" - long_info = """ - Calls predefined scripts. Exactly like $0C except the script does not end. - [0D][xxyy] - """ - size = 3 - byte1 = ord(rom[offset+1]) - byte2 = ord(rom[offset+2]) - number = byte1 + (byte2 << 8) - #0000 to 000AD ... XXX how should these be handled? - command["predefined_script_number"] = number - elif command_byte == 0x0E: #ASM code1 [3b] - info = "ASM code1 [3b]" - long_info = """ - Calls a predefined routine by interpreting the ASM the pointer points to. - [0E][3byte pointer] - """ - size = 4 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) - #XXX should we dissassemble the asm at the target location? - elif command_byte == 0x0F: #0F codes [xxyy] - info = "call some predefined script [0F][yyxx]" - long_info = """ - Calls predefined scripts. - [0F][yyxx] - NOTE: For (some) dialogues the font needs to be loaded with the Text box&font code. - """ - size = 3 - byte1 = ord(rom[offset+1]) - byte2 = ord(rom[offset+2]) - number = byte1 + (byte2 << 8) - command["predefined_script_number"] = number - elif command_byte == 0x10: #ASM code2 [2b] - info = "ASM code2 [2b to 3b to asm]" - long_info = """ - Call an ASM script via a 2byte pointer pointing to a 3byte pointer. - [10][2byte pointer pointing to 3byte pointer pointing to ASM script] - """ - size = 3 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - #XXX should i include the 3-byte pointer at the target location? - #XXX should we dissassemble the asm at the target location? - elif command_byte == 0x11: #Trigger event check1 [xxyy] - info = "Trigger event check1 [xx][yy]" - long_info = """ - Check the current number of the trigger event on map (map group/map id). - [11][map group][map number] - """ - size = 3 - command["map_group"] = ord(rom[start_address+1]) - command["map_id"] = ord(rom[start_address+2]) - elif command_byte == 0x12: #Activate trigger event from afar [xxyyzz] - info = "Activate trigger event from afar [xx][yy][zz]" - long_info = """ - Changes trigger event number on map (map bank/map no) to xx. - xx = trigger event number that should be activated - [12][MapBank][MapNo][xx] - """ - size = 4 - command["map_group"] = ord(rom[start_address+1]) - command["map_id"] = ord(rom[start_address+2]) - command["trigger_number"] = ord(rom[start_address+3]) - elif command_byte == 0x13: #Trigger event check - info = "Trigger event check" - long_info = """ - Checks the number of the trigger events on the current map. - [13] - """ - size = 1 - elif command_byte == 0x14: #De-/activate trigger event [xx] - info = "De-/activate trigger event [xx]" - long_info = """ - Changes trigger event number on current map to xx. - xx = trigger event number that should be activated - [14][xx] - deactivate? Just activate a different trigger event number. There's a limit of 1 active trigger. - """ - size = 2 - command["trigger_number"] = ord(rom[start_address+1]) - elif command_byte == 0x15: #Load variable into RAM [xx] - info = "Load variable into RAM [xx]" - long_info = "[15][xx]" - size = 2 - command["variable"] = ord(rom[start_address+1]) - elif command_byte == 0x16: #Add variables [xx] - info = "Add variables [xx]" - long_info = """ - Adds xx and the variable in RAM. - #[16][xx] - """ - size = 2 - command["variable"] = ord(rom[start_address+1]) - elif command_byte == 0x17: #Random number [xx] - info = "Random number [xx]" - long_info = """ - #Reads xx and creates a random number between 00 and xx -1. - #According to this xx can be all but 00. Random number = [00; xx) - #The nearer the random number is to xx, the rarer it occurs. - #Random number gets written to RAM. - """ - size = 2 - command["rarest"] = ord(rom[start_address+1]) - elif command_byte == 0x18: #Version check - info = "G/S version check" - long_info = """ - #Check if version is gold or silver. Gives feedback. - #00 = Gold - #01 = Silver - #[18] - """ - size = 1 - elif command_byte == 0x19: #Copy variable code1 [xxyy] - info = "Copy from RAM address to script RAM variable [xxyy]" - long_info = """ - #Writes variable from ram address to RAM. - #[19][2-byte RAM address] - """ - size = 3 - #XXX maybe a pointer is a bad idea? - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x1A: #Copy variable code2 [xxyy] - info = "Write variable from script RAM variable to actual RAM address [xxyy]" - long_info = """ - #Writes variable from RAM to actual RAM address. - #[1A][2-byte RAM address] - """ - size = 3 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x1B: #Load variable [xxyyzz] - info = "Load variable [xxyy][zz]" - long_info = """ - #Writes zz to ram address. - #[1B][2-byte RAM address][zz] - """ - size = 4 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["value"] = ord(rom[start_address+3]) - elif command_byte == 0x1C: #Check codes [xx] - #XXX no idea what's going on in this one :( - info = "Check pre-ID-mapped RAM location [xx]" - long_info = """ - #Checks special game-technical values and writes then into RAM. - #[1C][following part][Ram check (when <> 08/09 see „numbers“ in list of following parts)] - #following part (and then hex values) - #01 = PKMN count in party - # 00 - 06 - #02 = ??? - #03 = Battle type of wild PKMN - #04 = ??? - #05 = PokéDex caught - # 00 - FA - #06 = PokéDex seen - # 00 - FA - #07 = Badge count - # 00 - 10 - #08 = Movement - # 00 = walk - # 01 = bike - # 02 = slipping - # 04 = surfer - # 08 = surfing pikachu - #09 = HIRO direction - # 00 (d) - # 01 (u) - # 02 (l) - # 03 (r) - #0A = Time in hours - # 00 - 18 - #0B = Day - # 00 (Mo) - 06 (Su) - #0C = Map bank of current map - #0D = Map no of current map - #0E = Num. of diff. unowns seen - # 00 - 1A - #0F = Action byte of map - #10 = Amount of free spaces in pkmn box - # 00 - 14 - #11 = Minutes until end bug contest - # 00 - 14 - #12 = X position of HIRO - #13 = Y position of HIRO - #14 = phone call number - """ - size = 2 #i think? - command["following_part"] = ord(rom[start_address+1]) - elif command_byte == 0x1D: #Input code1 [xx] - info = "Write to pre-ID-mapped RAM location [xx]" - long_info = """ - #Writes variable from RAM to special game-technical value offsets. - #[1D][following part] - #where [following part] is the same as 0x1C - """ - size = 2 - command["following_part"] = ord(rom[start_address+1]) - elif command_byte == 0x1E: #Input code2 [xxyy] - info = "Write byte value to pre-ID-mapped RAM location [aa][xx]" - long_info = """ - #Writes variable xx to special game-technical value offsets. - #[1E][following part][xx] - #where [following part] is the same as 0x1C - """ - size = 3 - command["following_part"] = ord(rom[start_address+1]) - command["value"] = ord(rom[start_address+2]) - elif command_byte == 0x1F: #Give item code [xxyy] - info = "Give item by id and quantity [xx][yy]" - long_info = """ - #Gives item (item no) amount times. - #feedback: - # 00 = bag full - # 01 = OK - #[1F][item no][amount] - """ - size = 3 - command["item_id"] = ord(rom[start_address+1]) - command["quantity"] = ord(rom[start_address+2]) - elif command_byte == 0x20: #Take item code [xxyy] - info = "Take item by id and quantity [xx][yy]" - long_info = """ - #Gives item (item no) amount times - #feedback: - # 00 = not enough items - #[20][item no][amount] - """ - size = 3 - command["item_id"] = ord(rom[start_address+1]) - command["quantity"] = ord(rom[start_address+2]) - elif command_byte == 0x21: #Check for item code [xx] - info = "Check if player has item [xx]" - long_info = """ - #Checks if item is possessed. - #feedback: - # 00 = does not have item - # 01 = has item - #[21][item no] - """ - size = 2 - command["item_id"] = ord(rom[start_address+1]) - elif command_byte == 0x22: #Give money code [xxyyzzaa] - info = "Give money to HIRO/account [xxyyzzaa]" - long_info = """ - #Gives zzyyxx money to HIRO/account. - #zzyyxx = amount of money (000000 - 0F423F) - #[22][00-HIRO/01-account][xxyyzz] - """ - size = 5 - bytes = rom_interval(start_address, size, strings=False) - command["account"] = bytes[1] - command["amount"] = bytes[2:] - #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" - elif command_byte == 0x23: #Take money code [xxyyzzaa] - info = "Take money from HIRO/account [xxyyzzaa]" - long_info = """ - #Takes zzyyxx money from HIRO/account. - #zzyyxx = amount of money (000000 - 0F423F) - #[23][00-HIRO/01-account][xxyyzz] - """ - size = 5 - bytes = rom_interval(start_address, size, strings=False) - command["account"] = bytes[1] - command["amount"] = bytes[2:] - #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" - elif command_byte == 0x24: #Check for money code [xxyyzzaa] - info = "Check if HIRO/account has enough money [xxyyzzaa]" - long_info = """ - #Checks if HIRO/account has got zzyyxx money. - #feedback: - # 00 = enough money - # 01 = exact amount - # 02 = less money - #zzyyxx = amount of money (000000 - 0F423F) - #[24][00-HIRO/01-account][xxyyzz] - """ - size = 5 - bytes = rom_interval(start_address, size, strings=False) - command["account"] = bytes[1] - command["quantity"] = bytes[2:] - #XXX how is quantity formatted? - #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" - elif command_byte == 0x25: #Give coins code [xxyy] - info = "Give coins to HIRO [xxyy]" - long_info = """ - #Gives coins to HIRO. - #yyxx = amount of coins (0000 - 270F) - #[25][xxyy] - """ - size = 3 - bytes = rom_interval(start_address, size, strings=False) - command["quantity"] = bytes[1] + (bytes[2] << 8) - elif command_byte == 0x26: #Take coins code [xxyy] - info = "Take coins from HIRO [xxyy]" - long_info = """ - #Takes coins away from HIRO. - #yyxx = amount of coins (0000 - 270F) - #[26][xxyy] - """ - size = 3 - bytes = rom_interval(start_address, size, strings=False) - command["quantity"] = bytes[1] + (bytes[2] << 8) - elif command_byte == 0x27: #Check for coins code [xxyy] - info = "Check if HIRO has enough coins [xxyy]" - long_info = """ - #Checks if HIRO has enough coins. - #feedback: - # 00 = has enough coins - # 01 = has exact amount - # 02 = does not have enough - #yyxx = amount of coins necessary (0000 - 270F) - #[27][xxyy] - """ - size = 3 - bytes = rom_interval(start_address, size, strings=False) - command["quantity"] = bytes[1] + (bytes[2] << 8) - elif command_byte == 0x28: #Give cell phone number [xx] - info = "Give cell phone number [xx]" - long_info = """ - #Gives number to HIRO. - #feedback: - # 00 = number was added successfully - # 01 = Number already added, or no memory - #xx = number of person - #[28][xx] - #01 = mother - #02 = bike store - #03 = bll - #04 = elm - """ - size = 2 - command["number"] = ord(rom[start_address+1]) - elif command_byte == 0x29: #Take cell phone number [xx] - info = "Delete cell phone number [xx]" - long_info = """ - #Deletes a number from the list. - #feedback: - # 00 = number deleted successfully - # 01 = number wasn't in list - #xx = number of person - #[29][xx] - """ - size = 2 - command["number"] = ord(rom[start_address+1]) - elif command_byte == 0x2A: #Check for cell phone number [xx] - info = "Check for cell phone number [xx]" - long_info = """ - #Checks if a number is in the list. - #feedback: - # 00 = number is in list - # 01 = number not in list - #xx = number to look for - #[2A][xx] - """ - size = 2 - command["number"] = ord(rom[start_address+1]) - elif command_byte == 0x2B: #Check time of day [xx] - info = "Check time of day [xx]" - long_info = """ - #Checks the time of day. - #feedback: - # 00 = time of day is the same - # 01 = time of day is not the same - #[2B][time of day (01morn-04night)] - """ - size = 2 - command["time_of_day"] = ord(rom[start_address+1]) - elif command_byte == 0x2C: #Check for PKMN [xx] - info = "Check for pokemon [xx]" - long_info = """ - #Checks if there is a certain PKMN in team. - #feedback: - # 00 = in team - # 01 = not in team - #xx = pkmn id - #[2C][xx] - """ - size = 2 - command["pokemon_id"] = ord(rom[start_address+1]) - elif command_byte == 0x2D: #Give PKMN [xxyyzzaa(+2b +2b)] - info = "Give pokemon [pokemon][level][item][trainer2b][...]" - long_info = """ - #Gives a PKMN if there's space - #feedback: - # trainer id - #[2D][PKMN][PKMNlvl][PKMNitem][TRAINER] - #trainer: - # 00 = HIRO - # 01 = after the main code there are 4 bytes added - # [2byte pointer to trainer's name (max.0x0A figures + 0x50)][2byte pointer to nickname (max.0x0A figures + 0x50)] - """ - size = 5 - bytes = rom_interval(start_address, size, strings=False) - command["pokemon_id"] = bytes[1] - command["pokemon_level"] = bytes[2] - command["held_item_id"] = bytes[3] - command["trainer"] = bytes[4] - if command["trainer"] == 0x01: - size += 4 - bytes = rom_interval(start_address, size, strings=False) - command["trainer_name_pointer"] = calculate_pointer_from_bytes_at(start_address+5, bank=False) - command["pokemon_nickname_pointer"] = calculate_pointer_from_bytes_at(start_address+7, bank=False) - elif command_byte == 0x2E: #Give EGG [xxyy] - info = "Give egg [xx][yy]" - long_info = """ - #Gives egg if there's space. - #feedback: - # 00 = OK - # 02 = transaction not complete - #[2E][PKMN][PKMNlvl] - """ - size = 3 - command["pokemon_id"] = ord(rom[start_address+1]) - command["pokemon_level"] = ord(rom[start_address+2]) - elif command_byte == 0x2F: #Attach item code [2B] - info = "Attach item to last pokemon in list [xxyy]" - long_info = """ - #Gives last PKMN in list an item and letter text if applicable. Replaces existing items. - #[2F][2byte pointer to item no + 0x20 bytes letter text] - """ - size = 3 - command["item_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - #XXX are those 20 bytes supposed to happen here? or at the pointer's destination? - elif command_byte == 0x30: #Check letter code [2b] - info = "Check letter against known letter [xxyy]" - long_info = """ - #Opens a PKMN list. Selected PKMN must have the right letter + the right contents. If OK, then PKMN is taken away - #feedback: - # 00 = wrong letter - # 01 = OK - # 02 = Cancelled - # 03 = Chosen PKMN has no letter - # 04 = Chosen PKMN is the only one in the list. - #[30][2byte pointer to letter item no + 0x20 bytes letter text] - """ - size = 3 - command["item_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x31: #BitTable1 check [xxyy] - info = "Check some bit on bit table 1 [xxyy]" - long_info = """ - #Checks whether a bit of BitTable1 has the value 0 or 1. - #feedback: - # 00 = value 0 (off) - # 01 = value 1 (on) - #[31][2-byte bit number] - """ - #XXX what format is the 2-byte number in? - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - #raise NotImplementedError, "what format is the 2-byte number in?" - elif command_byte == 0x32: #BitTable1 reset [xxyy] - info = "Reset (to 0) a bit on bit table 1 [xxyy]" - long_info = """ - #Sets a bit of BitTable1 to value 0. - #[32][Bit no (2byte)] - """ - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - elif command_byte == 0x33: #BitTable1 set [xxyy] - info = "Set (to 1) a bit on bit table 1 [xxyy]" - long_info = """ - #Sets a bit of BitTable1 to value 1. - #[33][Bit-No (2byte)] - """ - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - elif command_byte == 0x34: #BitTable2 check [xxyy] - info = "Check some bit on bit table 2 [xxyy]" - long_info = """ - #Checks whether a bit of BitTable2 has the value 0 or 1. - #feedback: - # 00 = value 0 (off) - # 01 = value 1 (on) - #[34][Bit no (2byte)] - """ - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - elif command_byte == 0x35: #BitTable2 reset [xxyy] - info = "Reset (to 0) a bit on bit table 2 [xxyy]" - long_info = """ - #Sets a bit of BitTable2 to value 0. - #[35][Bit no (2byte)] - """ - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - elif command_byte == 0x36: #BitTable2 set [xxyy] - info = "Set (to 1) a bit on bit table 2 [xxyy]" - long_info = """ - #Sets a bit of BitTable2 to value 1. - #[36][Bit no (2byte)] - """ - size = 3 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["bit_number_bytes"] = bytes - elif command_byte == 0x37: #Deactivate PKMN battles - info = "Turn off wild pokemon battles" - long_info = """ - #This code turns all wild PKMN battles off. - #[37] - """ - size = 1 - elif command_byte == 0x38: #Activate PKMN battles - info = "Turn no wild pokemon battles" - long_info = "This code turns all wild PKMN battles on." - size = 1 - elif command_byte == 0x39: #X/Y comparison [xxyy] - info = "BUGGY x/y comparison [xxyy]" - long_info = """ - #This code is buggy (Bug fix: 0x3021 --> C6) and can't used as - #described without fix. This code compares the X and Y coordinates of - #HIRO with the ones in a table (max. 20h XY pairs) on the current map. - #It sets or resets the 4 bytes D17C to D17F accordingly to this table, - #1 bit for every table entry. To be useful, this code can only be used - #in a command queue, because with every regular move of HIRO the bits - #are reset again. This code is an alternative to the trigger events and - #can be used via the command queue code. - #See Write command queue, Additional documentation: 3:4661 with c= index - #in table (start=00), hl=D171, b=01, d=00. - """ - size = 3 - command["table_pointer"] = rom_interval(start_address+1, size-1, strings=False) - elif command_byte == 0x3A: #Warp modifier [xxyyzz] - info = "Set target for 0xFF warps [warp id][map group][map id]" - long_info = """ - #Changes warp data for all warps of the current map that have a 0xFF for warp-to data. - #[3A][Nee warp-to][New map bank][New map no] - """ - size = 4 - bytes = rom_interval(start_address+1, size-1, strings=False) - command["nee_warp_to"] = bytes[0] - command["map_group"] = bytes[1] - command["map_id"] = bytes[2] - elif command_byte == 0x3B: #Blackout modifier [xxyy] - info = "Blackout warp modifier [map group][map id]" - long_info = """ - #Changes the map HIRO arrives at, after having a blackout. - #There needs to be flying data for that map. - #[3B][Map bank][Map no] - """ - size = 3 - command["map_group"] = ord(rom[start_address+1]) - command["map_id"] = ord(rom[start_address+2]) - elif command_byte == 0x3C: #Warp code [xxyyzzaa] - info = "Warp to [map group][map id][x][y]" - long_info = """ - #Warps to another map. - #If all data is 00s, then the current map is reloaded with - #the current X and Y coordinates. Old script is not finished - #without a [90]. - #[3C][Map bank][Map no][X][Y] - """ - size = 5 - command["map_group"] = ord(rom[start_address+1]) - command["map_id"] = ord(rom[start_address+2]) - command["x"] = ord(rom[start_address+3]) - command["y"] = ord(rom[start_address+4]) - elif command_byte == 0x3D: #Account code [xxyy] - info = "Read money amount [xx][yy]" - long_info = """ - #Reads amount of money in accounts of HIRO and mother and writes - #it to MEMORY1, 2 or 3 for later use in text. - #[3D][00 = HIRO| <> 00 = Mother][00-02 MEMORY] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText01 - """ - size = 3 - command["account_id"] = ord(rom[start_address+1]) - command["memory_id"] = ord(rom[start_address+2]) - elif command_byte == 0x3E: #Coin case code [xx] - info = "Read coins amount [xx]" - long_info = """ - #Reads amount of coins in coin case and writes it to MEMORY 1, 2, - #or 3 for later use in text. - #[3E][00-02 MEMORY] - """ - size = 2 - command["memory_id"] = ord(rom[start_address+1]) - elif command_byte == 0x3F: #Display RAM [xx] - info = "Copy script RAM value into memX [xx]" - long_info = """ - #Reads RAM value and writes it to MEMORY1, 2 or 3 for later use in text. - #[3F][00-02 MEMORY] - """ - size = 2 - command["memory_id"] = ord(rom[start_address+1]) - elif command_byte == 0x40: #Display pokémon name [xxyy] - info = "Copy pokemon name (by id) to memX [id][xx]" - long_info = """ - #Writes pokémon name to MEMORY1, 2 or 3 for later use in text. - #[40][PKMN no][00-02 MEMORY] - """ - size = 3 - command["map_id"] = ord(rom[start_address+1]) - command["memory_id"] = ord(rom[start_address+2]) - elif command_byte == 0x41: #Display item name [xxyy] - info = "Copy item name (by id) to memX [id][xx]" - long_info = """ - #Writes item name to MEMORY1, 2 or 3 for later use in text. - #[41][Item no][00-02 MEMORY] - """ - size = 3 - command["item_id"] = ord(rom[start_address+1]) - command["memory_id"] = ord(rom[start_address+2]) - elif command_byte == 0x42: #Display location name [xx] - info = "Copy map name to memX [xx]" - long_info = """ - #Writes current location's name to MEMORY1, 2 or 3 for later use in text. - #[42][00-02 MEMORY] - """ - size = 2 - command["memory_id"] = ord(rom[start_address+1]) - elif command_byte == 0x43: #Display trainer name [xxyyzz] - info = "Copy trainer name (by id&group) to memZ [xx][yy][zz]" - long_info = """ - #Writes trainer name to MEMORY1, 2 or 3 for later use in text. - #[43][Trainer number][Trainer group][00-02 MEMORY] - """ - size = 4 - command["trainer_id"] = ord(rom[start_address+1]) - command["trainer_group"] = ord(rom[start_address+2]) - command["memory_id"] = ord(rom[start_address+3]) - elif command_byte == 0x44: #Display strings [2b + xx] - info = "Copy text (by pointer) to memX [aabb][xx]" - long_info = """ - #Writes string to MEMORY1, 2 or 3 for later use in text. - #[44][2byte pointer to string (max. 0x0C figures + 0x50)][00-02 MEMORY] - #See 0C codes: 0C2900, 0C2A00, 0C1B00, 0C2200, Usage of variable strings in text. - """ - size = 4 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["memory_id"] = ord(rom[start_address+3]) - command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) - elif command_byte == 0x45: #Stow away item code - info = "Show HIRO put the ITEMNAME in the ITEMPOCKET text box" - long_info = """ - #Text box: "HIRO put the ITEMNAME in the ITEMPOCKET." - #The item number has to have been loaded beforehand - #(e.g. by Give item code). - """ - size = 1 - elif command_byte == 0x46: #Full item pocket code - info = "Show ITEMPOCKET is full textbox" - long_info = """ - #Text box: "ITEMPOCKET is full..." The item number has to have - #been loaded beforehand (e.g. by Give item code). - """ - size = 1 - elif command_byte == 0x47: #Text box&font code - info = "Loads the font into the ram and opens a text box." - size = 1 - elif command_byte == 0x48: #Refresh code [xx] - info = "Screen refresh [xx]" - long_info = """ - #Executes a complete screen refresh. - #[48][xx] - #xx is a dummy byte - """ - size = 2 - command["dummy"] = ord(rom[start_address+1]) - elif command_byte == 0x49: #Load moving sprites - info = "Load moving sprites into memory" - long_info = "Loads moving sprites for person events into ram." - size = 1 - elif command_byte == 0x4A: #Load byte to C1CE [xx] - info = "Load specific byte to $C1CE [xx]" - long_info = """ - #Loads a byte to C1CE. Seems to have no function in the game. - #[4A][Byte] - """ - size = 2 - command["byte"] = ord(rom[start_address+1]) - elif command_byte == 0x4B: #Display text [3b] - info = "Display text by pointer [bb][xxyy]" - long_info = """ - #Opens a text box and writes text. Doesn't load font. - #[4B][Text bank][2byte text pointer] - """ - size = 4 - command["text_group"] = ord(rom[start_address+1]) - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) - command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) - elif command_byte == 0x4C: #Display text [2b] - info = "Display text by pointer [xxyy]" - long_info = """ - #Opens a text box and writes text. Doesn't load font. - #[4C][2byte text pointer] - """ - size = 3 - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) - elif command_byte == 0x4D: #Repeat text [xxyy] - info = "Repeat text [FF][FF]" - long_info = """ - #Opens a text box and writes the text written latest resp. whose address was put statically to D175-D177. - #Doesn't load font. - #[4D][FF][FF] - #Without FF for both bytes, no operation occurs - """ - size = 3 - command["bytes"] = rom_interval(start_address+1, 2, strings=False) - elif command_byte == 0x4E: #YES/No box - info = "YES/No box" - long_info = """ - #Displays a YES/NO box at X0F/Y07 - #feedback: - # 00 = no - # 01 = yes - """ - size = 1 - elif command_byte == 0x4F: #Menu data code [2b] - info = "Load menu data by pointer [xxyy]" - long_info = """ - #Loads data for menus - #[4F][2byte pointer to menu data] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA4F - """ - size = 3 - command["menu_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x50: #Write backup code - info = "Write screen backup" - long_info = "Writes backup of parts of the screen the box was overlapping." - size = 1 - elif command_byte == 0x51: #Text1 code [2b] - info = "Display text (by pointer), turn to HIRO, end [xxyy]" - long_info = """ - #Displays a text and lets person turn to HIRO. - #Afterwards there is no other script interpreted. - #Corresponds to 6A + 47 + 4C + 53 + 49 + 90 - #[51][2byte textpointer] - """ - size = 3 - end = True - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_id, map_id=map_id, debug=debug) - elif command_byte == 0x53: #Text2 code [2b] - info = "Display text (by pointer) and end [xxyy]" - long_info = """ - #Displays a text. Afterwards there is no other script interpreted. - #Corresponds to 47 + 4C + 53 + 49 + 90 - #[52][2byte textpointer] - """ - size = 3 - end = True - command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_id, map_id=map_id, debug=debug) - elif command_byte == 0x54: #Close text box code - info = "Close text box" - long_info = "Closes a text box which was opened by 47 resp. 4B/4C/4D." - size = 1 - elif command_byte == 0x55: #Keep text box open code - info = "Keep text box open" - long_info = "Keeps a text box open which was opened by 47 resp. 4B/4C/4D." - size = 1 - elif command_byte == 0x56: #Pokémon picture code [xx] - info = "Display a pokemon picture in a box by pokemon id [xx]" - long_info = """ - #Opens a box and puts a Pokémon picture into it. - #[55][xx] - #xx: - # <>00 : Pokémon no - # =00 : Pokémon no gets read from RAM - """ - size = 2 - command["byte"] = ord(rom[start_address+1]) - elif command_byte == 0x57: #Pokémon picture YES/NO code - info = "?? Display a pokemon picture and a yes/no box" - long_info = """ - #Displays a YES/NO box at X08/Y05. - #feedback: - # 00 = no chosen - # 01 = yes chosen - """ - size = 1 - elif command_byte == 0x58: #Menu interpreter 1 - info = "Menu interpreter 1 (see menu loader)" - long_info = """ - #Interprets menu data loaded by 4F. - #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA57 - """ - size = 1 - elif command_byte == 0x59: #Menu interpreter 2 - info = "Menu interpreter 2 (see menu loader)" - long_info = """ - #Interprets menu data loaded by 4F. - #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke57 - #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA58 - """ - size = 1 - elif command_byte == 0x5A: #Load Pikachu data - info = "Load pikachu data" - long_info = "Loads 0x19 (Pikachu) to PokéRAM and level 5 to LevelRAM." - size = 1 - elif command_byte == 0x5B: #Delete FightRAM/reset person check - info = "? Disable fleeing from battle" - long_info = """ - #Deletes the value in BattleRAM. - #Turns off the check if the battle was started by entering - #a trainer's area of view. - """ - size = 1 - elif command_byte == 0x5C: #Load trainer data1 - info = "Load trainer from RAM" - long_info = """ - #Loads trainer data when HIRO is in a trainer's range of sight. - #Trainer group is read from CF2E and written to - #TrRAM1, the trainer number is read from CF2F and written to - #TrRAM2. 81 is written to BattleRAM. - """ - size = 1 - elif command_byte == 0x5D: #Load Pokémon data [xxyy] - info = "Loads pokemon by id and level for BattleRAM [xx][yy]" - long_info = """ - #Loads Pokémon data. Writes 80 to BattleRAM. - #[5C][Poke no][Level] - """ - size = 3 - command["pokemon_id"] = ord(rom[start_address+1]) - command["pokemon_level"] = ord(rom[start_address+2]) - elif command_byte == 0x5E: #Load trainer data2 [xxyy] - info = "Load trainer by group/id for BattleRAM [xx][yy]" - long_info = """ - #Loads trainer data. Trainer group --> TrRAM1, - #trainer number --> TrRAM2. Writes 81 to BattleRAM. - #[5D][Trainer group][Trainer no] - """ - size = 3 - command["trainer_group"] = ord(rom[start_address+1]) - command["trainer_id"] = ord(rom[start_address+2]) - elif command_byte == 0x5F: #Start battle - info = "Start pre-configured battle" - long_info = """ - #Starts trainer or Pokémon battle. BattleRAM: 80 = Poké battle; 81 = Trainer battle. - #feedback: - # 00 = win - # 01 = lose - """ - size = 1 - elif command_byte == 0x60: #Return to In game engine after battle - info = "Return to in-game engine after battle" - long_info = "Returns to ingame engine and evaluates battle. When lost then return to last Pokémon Center etc." - size = 1 - elif command_byte == 0x61: #Learn how to catch PKMN [xx] - info = "Pokemon catching tutorial [xx]" - long_info = """ - #Starts a learn-how-to-catch battle with a Pokémon, whose data needs to be loaded beforehand - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke5C - #Player has to have at least 1 Pokémon for it to work. - #Items that are statically used: 1xPotion, 5xPoké ball. - #[60][xx] - #xx: Between 01 and 03. If <> 03 then HIRO sprite instead of dude sprite and kills - #itself when using the item system. - """ - size = 2 - command["byte"] = ord(rom[start_address+1]) - elif command_byte == 0x62: #Trainer text code - info = "Set trainer text by id [xx]" - long_info = """ - #Interprets the data of a in the event structure defined trainer. - #[61][xx] - #Xx decides which text to use. - #xx: Between 00 and 03. - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau - """ - size = 2 - command["byte"] = ord(rom[start_address+1]) - elif command_byte == 0x63: #Trainer status code [xx] - info = "? Check trainer status [xx]" - long_info = """ - #Checks/changes the status of a in the event structure defined trainer. - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau - #[62][xx] - #xx is: - # 00 = deactivate - # 01 = activate - # 02 = check - """ - size = 2 - command["byte"] = ord(rom[start_address+1]) - elif command_byte == 0x64: #Pointer Win/Lose [2b + 2b] - info = "Set win/lose pointers for battle [xxyy][xxyy]" - long_info = """ - #Writes the win/lose pointer of a battle into the ram. - #[63][2byte pointer to text Win][2byte pointer to text Loss*] - #* When pointer = 0000 then "Blackout" instead of return to gameplay. - """ - size = 5 - #sometimes win/lost can be a pointer to 0000 or None? - command["won_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - command["lost_pointer"] = calculate_pointer_from_bytes_at(start_address+3, bank=False) - if command["won_pointer"] == None: - command["won_pointer"] = 0 - else: - command["text_won"] = parse_text_engine_script_at(command["won_pointer"], map_group=map_id, map_id=map_id, debug=debug) - if command["lost_pointer"] == None: - command["lost_pointer"] = 0 - else: - command["text_lost"] = parse_text_engine_script_at(command["lost_pointer"], map_group=map_id, map_id=map_id, debug=debug) - elif command_byte == 0x65: #Script talk-after - #XXX this is a really poor description of whatever this is - info = "? Load the trainer talk-after script" - long_info = """ - #Interprets which script is going to be run, when a in the event-structure-defined - #trainer is talked to again. - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau - #[64] - """ - size = 1 - elif command_byte == 0x66: #Script talk-after-cancel - info = "Disable/cancel trainer after-battle text" - long_info = """ - #Cancels the talk-after script of the in the event-structure-defined - #trainer when talk-after script is executed just after the battle. - #[65] - """ - size = 1 - elif command_byte == 0x67: #Script talk-after-check - #XXX also bad explanation/name... - info = "? Check if trainer talk-after script is executed just after battle or not" - long_info = """ - #Checks if the talk-after script of the event structure defined trainer - #is executed just after the battle or at a later point in time. - #feedback: - # 00 = no - # 01 = yes - #[66] - """ - size = 1 - elif command_byte == 0x68: #Set talked-to person [xx] - info = "Set last talked-to person [xx]" - long_info = """ - #Sets the number of the last person talked to. - #[67][person] - """ - size = 2 - command["person_id"] = ord(rom[start_address+1]) - elif command_byte == 0x69: #Moving code [xx + 2b] - info = "Move person (by id) with moving data (by pointer) [id][xxyy]" - long_info = """ - #Moves the person using moving data. - #[68][Person][2byte pointer to moving data] - #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzB68bis69 - """ - size = 4 - command["person_id"] = ord(rom[start_address+1]) - command["moving_data_pointer"] = calculate_pointer_from_bytes_at(start_address+2, bank=False) - elif command_byte == 0x6A: #Moving code for talked-to person [2b] - info = "Move talked-to person with moving data (by pointer) [xxyy]" - long_info = """ - #Moves talked-to person using moving data. - #[69][2byte pointer to moving data] - """ - size = 3 - command["moving_data_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x6B: #Talk-to facing code - info = "Move talked-to person's facing direction to HIRO" - long_info = """ - #Turns the heads of the talked-to persons to HIRO. - #[6A] - """ - size = 1 - elif command_byte == 0x6C: #Facing of people code [xxyy] - info = "Move facing direction of person1 to look at person2 [2][1]" - long_info = """ - #Turns the head of person1 to another person2. - #[6B][Person2][Person1] - #Person2 = If number is greater than 0xFD, then use number of talked-to person. - #Person1 = If number equals 0xFE, then take number of talked-to person. - """ - size = 3 - command["person2_id"] = ord(rom[start_address+1]) - command["person1_id"] = ord(rom[start_address+2]) - elif command_byte == 0x6D: #Variable sprites [xxyy] - info = "Store value in variable sprite RAM location x by id Y [xx][yy]" - long_info = """ - #Writes a number to the variable sprite RAM from D555 to D564 (see Compendium on the sprite system). - #[6C][xx][Sprite no] - #xx: Number between 0x00 and 0x0F - """ - size = 3 - command["number"] = ord(rom[start_address+1]) - command["sprite_id"] = ord(rom[start_address+2]) - elif command_byte == 0x6E: #Hide person [xx] - info = "Hide person by id [xx]" - long_info = """ - #Hides a person. - #[6D][person id] - """ - size = 2 - command["person_id"] = ord(rom[start_address+1]) - elif command_byte == 0x6F: #Show person [xx] - info = "Show person by id [xx]" - long_info = """ - #Shows a hidden person again. - #[6E][person id] - """ - size = 2 - command["person_id"] = ord(rom[start_address+1]) - elif command_byte == 0x70: #Following code1 [xxyy] - info = "Following code1 [leader id][follower id]" - long_info = """ - #A person1 follows another person2. The person1 that follows - #just repeats the movement of person2, even if the persons are - #not directly next to each other. - #[6F][Leader Person2][Follower Person1] - """ - size = 3 - command["leader_person_id"] = ord(rom[start_address+1]) - command["follower_person_id"] = ord(rom[start_address+2]) - elif command_byte == 0x71: #Stop following code - info = "Stop all follow code" - long_info = "Ends all current follow codes." - size = 1 - elif command_byte == 0x72: #Move person [xxyyzz] - info = "Move person by id to xy [id][xx][yy]" - long_info = """ - #Sets the X/Y values of a person anew. - #The person doesn't get shown immediately. Use hide&show. - #[71][Person][X][Y] - """ - size = 4 - command["person_id"] = ord(rom[start_address+1]) - command["x"] = ord(rom[start_address+2]) - command["y"] = ord(rom[start_address+3]) - elif command_byte == 0x73: #Write person location [xx] (lock person location?) - info = "Lock person's location by id [id]" - long_info = """ - #Writes the current X/Y values of a person into the ram. - #The person is going to stand at its current location even when - #it's out of HIRO's sight and is not going to return to its old - #location until the next map load. - #[72][person] - """ - size = 2 - command["person_id"] = ord(rom[start_address+1]) - elif command_byte == 0x74: #Load emoticons [xx] - info = "Load emoticon bubble [xx]" - long_info = """ - #Loads the emoticon bubble depending on the given bubble number. - #[73][bubble number] - #xx: If xx = FF then take number from RAM. - # 00 = Exclamation mark - # 01 = Question mark - # 02 = Happy - # 03 = Sad - # 04 = Heart - # 05 = Flash - # 06 = Snoring - # 07 = Fish - """ - size = 2 - command["bubble_number"] = ord(rom[start_address+1]) - elif command_byte == 0x75: #Display emoticon [xxyyzz] - info = "Display emoticon by bubble id and person id and time [xx][yy][zz]" - long_info = """ - #Displays the bubble above a persons head for the given time period. - #Attention: Bubbles get loaded into ram! - #[74][Bubble][Person][Time] - #for bubble ids see 0x73 - """ - size = 4 - command["bubble_number"] = ord(rom[start_address+1]) - command["person_id"] = ord(rom[start_address+2]) - command["time"] = ord(rom[start_address+3]) - elif command_byte == 0x76: #Change facing [xxyy] - info = "Set facing direction of person [person][facing]" - long_info = """ - #Changes the facing direction of a person. - #[75][person][facing] - """ - size = 3 - command["person_id"] = ord(rom[start_address+1]) - command["facing"] = ord(rom[start_address+2]) - elif command_byte == 0x77: #Following code2 [xxyy] - info = "Following code2 [leader id][follower id]" - long_info = """ - #A person1 follows a person2. The following person1 automatically clings to person2. - #Person1 just follows person2, but doesn't make the exact same movements at person2. - #[76][Leader Person2][Follower Person1] - """ - size = 3 - command["leader_person_id"] = ord(rom[start_address+1]) - command["follower_person_id"] = ord(rom[start_address+2]) - elif command_byte == 0x78: #Earth quake [xx] - info = "Earthquake [xx]" - long_info = """ - #The screen shakes. xx gives time as well as displacement of the screen. - #[77][xx] - """ - size = 2 - command["shake_byte"] = ord(rom[start_address+1]) - elif command_byte == 0x79: #Exchange map [3b] - info = "Draw map data over current map [bank][pointer]" - long_info = """ - #This code draws another whole map as wide and high as the - #current map over the current map. - #The 3byte pointer points to the new map. - #[78][3byte pointer to new map data] - """ - size = 4 - command["map_data_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) - elif command_byte == 0x7A: #Change block code [xxyyzz] - info = "Change block to block id on map [xx][yy][id]" - long_info = """ - #Changes a block on the current map by giving the new block - #number and its X/Y values measured in half-blocks. - #[79][X][Y][Block] - """ - size = 4 - command["x"] = ord(rom[start_address+1]) - command["y"] = ord(rom[start_address+2]) - command["block"] = ord(rom[start_address+3]) - elif command_byte == 0x7B: #Reload map code - info = "Reload/redisplay map" - long_info = """ - #Reloads and re-displays the map completely. - #Loads tileset and all map data anew. Screen gets light. - #[7A] - """ - size = 1 - elif command_byte == 0x7C: #Reload map part code - info = "Reload/redisplay map portion occupied by HIRO" - long_info = """ - #Reloads and re-displays the part of the map HIRO is on, - #without reloading any other map data or the tileset. - #[7B] - """ - size = 1 - elif command_byte == 0x7D: #Write command queue - info = "Write command queue [xxyy]" - long_info = """ - #Writes a command queue to the next free slot in ram. - #Max 4 command queues à 5 bytes. This code is buggy (bug fix: 25:7C74 --> 12). - #[7C][2byte pointer to 5byte command queue] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok25_7CC9 - """ - size = 3 - command["command_queue_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x7E: #Delete command queue - info = "Delete command queue" - long_info = """ - #Deletes a command queue and frees a slot in ram. - #[7D][First command of the resp. queue] - """ - #XXX wtf? - size = 2 - command["first_command"] = ord(rom[start_address+1]) - elif command_byte == 0x7F: #Song code1 [xxyy] - info = "Play music by number [xxyy]" - long_info = """ - #Immediately plays the music. - #[7E][Music no (2byte)] - #Music no: See the music archive that should accompany - #this document Thanks to Filb. He dumped all the songs - #via gameboy player and gave them to me. - """ - size = 3 - #XXX what is the format of this music data? - command["music_number"] = rom_interval(start_address+1, size-1, strings=False) - elif command_byte == 0x80: #Song code2 - info = "Song code2" - long_info = """ - #Plays the music of the trainer group in TrRAM1. - #Takes music numbers from list at 3A:5027. - #[7F] - """ - size = 1 - elif command_byte == 0x81: #Music fade-out code [xxyy][zz] - info = "Music fade-out then play next [xxyy][time]" - long_info = """ - #The current music is faded out and the new music is played afterwards. - #[80][Music no (2byte)][Time to fade out (00-7F)] - """ - size = 4 - command["music_number"] = rom_interval(start_address+1, 2, strings=False) - command["fade_time"] = ord(rom[start_address+3]) - elif command_byte == 0x82: #Play map music code - info = "Play map's music" - long_info = """ - #Starts playing the original map music. - #Includes special check for surfer and bug contest song. - #[81] - """ - size = 1 - elif command_byte == 0x83: #Map reload music code - info = "Reload map music" - long_info = """ - #After a map reload no music is played. - #[82] - """ - size = 1 - elif command_byte == 0x84: #Cry code [xx00] - info = "Play cry by id or RAM [cry][00]" - long_info = """ - #Plays the Pokémon's cry. - #[83][Cry no][00] - #If the cry no = 00 then the number is taken from RAM. - """ - size = 3 - command["cry_number"] = ord(rom[start_address+1]) - command["other_byte"] = ord(rom[start_address+2]) - elif command_byte == 0x85: #Sound code [xxyy] - info = "Play sound by sound number [xxyy]" - long_info = """ - #Plays the sound. - #[84][Sound no (2byte)] - #Sound no: See the music archive that should accompany this document - #Thanks to philb for this matter. He helped me to record a big - #part of these sounds. - """ - size = 3 - command["sound_number"] = rom_interval(start_address+1, 2, strings=False) - elif command_byte == 0x86: #Key-down code - info = "Wait for key-down" - long_info = """ - #Waits for the Player to press a button. - #[85] - """ - size = 1 - elif command_byte == 0x87: #Warp sound - info = "Warp sound" - long_info = """ - #Evaluates which sound is played when HIRO enters a Warp field. - #Usage via script ingame is rather not useful. - #[86] - """ - size = 1 - elif command_byte == 0x88: #Special sound - info = "Special sound if TM was last checked" - long_info = """ - #When last given/checked Item was a TM then it plays sound 0x9B. If not, then 0x01. - #[87] - """ - size = 1 - elif command_byte == 0x89: #Engine remote control [2b] - info = "Engine remote control [bb][xxyy]" - long_info = """ - #This code controls the engine via "data stream". - #[88][3byte pointer to control structure] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA88 - """ - size = 4 - command["data_stream_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) - elif command_byte == 0x8A: #Load map anew [xx] - info = "Load map with specific loading process [xx]" - long_info = """ - #The number decides which map loading process is used. - #The number must be 0xF0 + process number to work correctly. - #[89][Number] - #see map loading process: - # http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok5_5550 - """ - size = 2 - command["number"] = ord(rom[start_address+1]) - elif command_byte == 0x8B: #Waiting code [xx] - info = "Wait code" - long_info = """ - #This code lets the game wait for 2 * xx time intervals. - #[8A][xx] - #xx: Numbers from 0x01 to 0xFF. - #If 0x00 is chosen then the time can be manipulated by previously loading a number to RAM2. - """ - size = 2 - command["time"] = ord(rom[start_address+1]) - elif command_byte == 0x8C: #Deactivate static facing [xx] - info = "Deactive static facing after time [xx]" - long_info = """ - #Deactivates static facings on all persons on the screen after a time xx. - #[8B][xx] - """ - size = 2 - command["time"] = ord(rom[start_address+1]) - elif command_byte == 0x8D: #Priority jump1 [2b] - info = "Priority jump to script by pointer [xxyy]" - long_info = """ - #The pointer acts like code 00, but with this higher - #functions like the bike etc. are not paid attention to, - #while the script is running. - #[8C][2byte pointer to script] - """ - size = 3 - script_pointer = calculate_pointer_from_bytes_at(start_address+1, bank=False) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(script_pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(script_pointer, original_start_address, debug=debug) - command["script_pointer"] = script_pointer - command["script"] = script - end = True #according to pksv - elif command_byte == 0x8E: #Warp check - info = "Reactive all engine checks if player is warping" - long_info = """ - #If HIRO is entering or leaving a warp then this code reactivates all the engine-checks. - #[8D] - """ - size = 1 - elif command_byte == 0x8F: #Priority jump2 [2b] - info = "Priority jump to script by pointer (after 1st cycle) [xxyy]" - long_info = """ - #The pointer acts like code 03, but with this code all - #higher functions wait for a cycle before the script gets interpreted. - #[8E][2byte pointer to script] - """ - size = 3 - script_pointer = calculate_pointer_from_bytes_at(start_address+1, bank=False) - if debug: - print "in script starting at "+hex(original_start_address)+\ - " about to parse script at "+hex(script_pointer)+\ - " called by "+info+" byte="+hex(command_byte) - script = rec_parse_script_engine_script_at(script_pointer, original_start_address, debug=debug) - command["script_pointer"] = script_pointer - command["script"] = script - end = True #according to pksv - elif command_byte == 0x90: #Return code1 - info = "Return code 1" - long_info = """ - #Ends the current script and loads the backup offset for "linked" - #scripts if applicable. The sophisticated functions are not affected - #and run like before the code. This code is mostly used for scripts - #called by the 2nd part of the script header, because else malfunctions - #occur. - #[8F] - """ - size = 1 - end = True - elif command_byte == 0x91: #Return code2 - info = "Return code 2" - long_info = """ - #Ends the current script and loads the backup offset for "linked" - #scripts if applicable. The sophisticated functions get reset if - #no backup offset was loaded. This code is used to end most scripts. - #[90] - """ - size = 1 - end = True - elif command_byte == 0x92: #Return code3 - info = "Return code 3" - long_info = """ - #Reloads the map completely like the code 0x7A - #and else acts completely like Return code2 - #[91] - #see reload map code - # http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke7A - #see 0x90 - """ - size = 1 - #XXX does this end the script?? "else acts like 0x90" - # else? what's the "if"? - end = True - elif command_byte == 0x93: #Reset sophisticated functions - info = "Reset sophisticated functions" - long_info = """ - #Resets all sophisticated functions to 0. - #[92] - """ - size = 1 - elif command_byte == 0x94: #Mart menu [xxyyzz] - info = "Mart menu [dialog no][mart no 2b]" - long_info = """ - #Displays a whole mart menu, however, doesn't load font to ram. - #[93][Dialog no][Mart no (2byte)] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#AwBsp93 - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzB93 - """ - size = 4 - command["dialog_number"] = ord(rom[start_address+1]) - command["mart_number"] = rom_interval(start_address+2, 2, strings=False) - elif command_byte == 0x95: #Elevator menu [2b] - info = "Display elevator menu by pointer [xxyy]" - long_info = """ - #Displays a whole elevator menu, but it doesn't load font to ram. - #Only works with warps with warp-to = 0xFF. - #[94][2byte pointer to floor list] - """ - size = 3 - command["floor_list_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x96: #Trade menu [xx] - info = "Display trade menu by trade id [xx]" - long_info = """ - #Displays a whole trade menu, but it doesn't load font to ram. - #[95][trade no] - #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDokTausch - """ - size = 2 - command["trade_number"] = ord(rom[start_address+1]) - elif command_byte == 0x97: #Give cell phone number with YES/NO [xx] - info = "Give cell phone number by id with YES/NO [id]" - long_info = """ - #Gives a telephone number but asks for decision beforehand. - #feedback: - # 00 = ok chosen - # 01 = Cell phone number already registered/Memory full - # 02 = no chosen - #[96][Cell phone number] - """ - size = 2 - #maybe this next param should be called "phone_number" - command["number"] = ord(rom[start_address+1]) - elif command_byte == 0x98: #Call code [2b] - info = "Call code pointing to name of caller [xxyy]" - long_info = """ - #Displays the upper cell phone box and displays a freely selectable name. - #[97][2byte pointer to name of caller] - """ - size = 3 - command["caller_name_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) - elif command_byte == 0x99: #Hang-up code - info = "Hang-up phone" - long_info = """ - #Simulates the hanging-up. - #[98] - """ - size = 1 - elif command_byte == 0x9A: #Decoration code [xx] - info = "Set monologue decoration [xx]" - long_info = """ - #Displays monologues according to the selected ornament. - #[99][xx] - #xx values: - # 00 = Map/Poster - # 01 = Ornament left - # 02 = Ornament right - # 03 = Huge doll - # 04 = Console - """ - size = 2 - command["ornament"] = ord(rom[start_address+1]) - elif command_byte == 0x9B: #Berry tree code [xx] - info = "Berry tree by tree id [xx]" - long_info = """ - #Creates a typical berry tree monologue. - #There is a maximum of 32 berry trees in the game. - #After this code the script ends. - #[9A][Fruit tree number] - #Fruit tree number + 11:4091 is the offset where the item no of the berry is defined. - """ - size = 2 - end = True - command["tree_id"] = ord(rom[start_address+1]) - elif command_byte == 0x9C: #Cell phone call code [xx00] - #XXX confirm this? - info = "Cell phone call [2-byte call id]" #was originally: [call id][00] - long_info = """ - #Initiates with the next step on a outer world map (permission byte) a phone call. - #[9B][Call no] and maybe [00] ??? - #call no: - # 01 = PokéRus - # 02 = Pokémon stolen - # 03 = Egg examined/ Assistant in Viola City - # 04 = Team Rocket on the radio - # 05 = PROF. ELM has got something for HIRO - # 06 = Bike shop gives bike away - # 07 = Mother is unhappy that HIRO didn't talk to her before leaving - # 08 = PROF. ELM has got something for HIRO a second time - """ - size = 3 - command["call_id"] = ord(rom[start_address+1]) - command["id"] = rom_interval(start_address+2, 2, strings=False) - elif command_byte == 0x9D: #Check cell phone call code - info = "Check if/which a phone call is active" - long_info = """ - #Checks if a phone call is "in the line". - #feedback: - # 00 = no - # <>00 = call number - #[9C] - """ - size = 1 - elif command_byte == 0x9E: #Commented give item code [xxyy] - info = "Give item by id and quantity with 'put in pocket' text [id][qty]" - long_info = """ - #The same as 0x1F but this code comments where - #HIRO puts what item in a short monologue. - #[9D][Item][Amount] - """ - size = 3 - command["item_id"] = ord(rom[start_address+1]) - command["quantity"] = ord(rom[start_address+2]) - elif command_byte == 0x9F: #Commented ive item code? - info = "Give item by id and quantity with 'put in pocket' text [id][qty]" - long_info = """ - #The same as 0x1F but this code comments where - #HIRO puts what item in a short monologue. - #[9D][Item][Amount] - """ - size = 3 - command["item_id"] = ord(rom[start_address+1]) - command["quantity"] = ord(rom[start_address+2]) - elif command_byte == 0xA0: #Load special wild PKMN data [xxyy] - info = "Load wild pokemon data for a remote map [map group][map id]" - long_info = """ - #Activates the checks in the special tables for the wild pokémon data. - #[9E][map group][map id] - #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok3E_66ED - """ - size = 3 - command["map_group"] = ord(rom[start_address+1]) - command["map_id"] = ord(rom[start_address+2]) - elif command_byte == 0xA1: #Hall of Fame code - info = "Hall of Fame" - long_info = """ - #Saves and enters HIRO's complete Team in the Hall of Fame. - #Shows the credits and restarts the game with HIRO located in New Bark Town. - #[9F] - """ - size = 1 - elif command_byte == 0xA2: #Credits code - info = "Credits" - long_info = """ - #Shows the credits and HIRO is located on the Silver mountain plateau. - #[A0] - """ - size = 1 - elif command_byte == 0xA3: #Facing warp - info = "Warp-to and set facing direction [Facing (00-03)][Map bank][Map no][X][Y]" - long_info = """ - #Acts like code 0x3C but defines the desired facing of HIRO. - #[A1][Facing (00-03)][Map bank][Map no][X][Y] - """ - size = 6 - command["facing"] = ord(rom[start_address+1]) - command["map_group"] = ord(rom[start_address+2]) - command["map_id"] = ord(rom[start_address+3]) - command["x"] = ord(rom[start_address+4]) - command["y"] = ord(rom[start_address+5]) - elif command_byte == 0xA4: #MEMORY code [2b + Bank + xx] - info = "Set memX to a string by a pointer [aabb][bank][xx]" - long_info = """ - #MEMORY1, 2 or 3 can directly be filled with a string from - #a different rom bank. - #[A2][2byte pointer][Bank][00-02 MEMORY] - """ - size = 5 - command["string_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank="reversed") - command["string_pointer_bank"] = ord(rom[start_address+3]) - command["memory_id"] = ord(rom[start_address+4]) - elif command_byte == 0xA5: #Display any location name [xx] - info = "Copy the name of a location (by id) to TEMPMEMORY1" - long_info = """ - #By the location number the name of that location is written to TEMPMEMORY1. - #[A3][Location no] - """ - size = 2 - command["location_number"] = ord(rom[start_address+1]) - else: - size = 1 - #end = True - #raise NotImplementedError, "command byte is " + hex(command_byte) + " at " + hex(offset) + " on map " + str(map_group) + "." + str(map_id) - print "dunno what this command is: " + hex(command_byte) - long_info = clean_up_long_info(long_info) - - if command_byte in pksv_crystal.keys(): - pksv_name = pksv_crystal[command_byte] - else: - pksv_name = None - if command_byte in pksv_no_names.keys(): - pksv_no_names[command_byte].append(address) - else: - pksv_no_names[command_byte] = [address] - - if debug: - print command_debug_information(command_byte=command_byte, map_group=map_group, map_id=map_id, address=offset, info=info, long_info=long_info, pksv_name=pksv_name) - - #store the size of the command - command["size"] = size - #the end address is just offset + size - 1 (because size includes command byte) - offset += size - 1 - #the end address is the last byte belonging to this command - command["last_byte_address"] = offset - #we also add the size of the command byte to get to the next command - offset += 1 - #add the command into the command list please - commands[len(commands.keys())] = command - - self.commands = commands - script_parse_table[original_start_address : offset] = self - return True diff --git a/overworldripper.py b/overworldripper.py deleted file mode 100644 index 654f287..0000000 --- a/overworldripper.py +++ /dev/null @@ -1,18 +0,0 @@ -import gfx - -def rip_sprites_from_bank(bank, offset=0): - """ - Rips sprites from specified bank. - - Sprites are 4x4. - """ - file_handler = open("../gfx/overworld/bank" + str(hex(bank))[2:] + ".asm", "w") - for sprite in range(0 + offset, 256 + offset): - filename = "../gfx/overworld/" + str(sprite).zfill(3) + ".2bpp" - gfx.get_uncompressed_gfx((bank * 0x4000 + ((sprite - offset) * 4 * 0x10)), 4, filename) - gfx.to_png(filename) - file_handler.write("INCBIN \"" + filename[3:] + "\"\n") - file_handler.close() - -rip_sprites_from_bank(0x30) -rip_sprites_from_bank(0x31, offset=256) diff --git a/parse_consecutive_strings.py b/parse_consecutive_strings.py deleted file mode 100644 index 4c864ba..0000000 --- a/parse_consecutive_strings.py +++ /dev/null @@ -1,25 +0,0 @@ -import sys - -import crystal - -rom = crystal.load_rom() - -addr = int(sys.argv[1], 16) -count = int(sys.argv[2]) if len(sys.argv) > 2 else 256 -label_prefix = sys.argv[3] if len(sys.argv) > 3 else "UnknownString" - -ex = None - -for i in range(count): - try: - string = crystal.PokedexText(addr) - asm = string.to_asm() - except Exception as ex: - break - print label_prefix+str(i)+": ; "+hex(addr) - print "\t"+asm - print - addr = string.last_address - -print "; "+hex(addr) -if ex: raise ex diff --git a/pksv.py b/pksv.py deleted file mode 100644 index 2f02ec0..0000000 --- a/pksv.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- - -pksv_gs = { - 0x00: "2call", - 0x01: "3call", - 0x02: "2ptcall", - 0x03: "2jump", - 0x04: "3jump", - 0x05: "2ptjump", - 0x06: "if equal", - 0x07: "if not equal", - 0x08: "if false", - 0x09: "if true", - 0x0A: "if less than", - 0x0B: "if greater than", - 0x0C: "jumpstd", - 0x0D: "callstd", - 0x0E: "3callasm", - 0x0F: "special", - 0x10: "2ptcallasm", - 0x11: "checkmaptriggers", - 0x12: "domaptrigger", - 0x13: "checktriggers", - 0x14: "dotrigger", - 0x15: "writebyte", - 0x16: "addvar", - 0x17: "random", - 0x19: "copybytetovar", - 0x1A: "copyvartobyte", - 0x1B: "loadvar", - 0x1C: "checkcode", - 0x1E: "writecode", - 0x1F: "giveitem", - 0x20: "takeitem", - 0x21: "checkitem", - 0x22: "givemoney", - 0x23: "takemoney", - 0x24: "checkmoney", - 0x25: "givecoins", - 0x26: "takecoins", - 0x27: "checkcoins", - 0x2B: "checktime", - 0x2C: "checkpoke", - 0x2D: "givepoke", - 0x2E: "giveegg", - 0x2F: "givepokeitem", - 0x31: "checkbit1", - 0x32: "clearbit1", - 0x33: "setbit1", - 0x34: "checkbit2", - 0x35: "clearbit2", - 0x36: "setbit2", - 0x37: "wildoff", - 0x38: "wildon", - 0x39: "xycompare", - 0x3A: "warpmod", - 0x3B: "blackoutmod", - 0x3C: "warp", - 0x41: "itemtotext", - 0x43: "trainertotext", - 0x44: "stringtotext", - 0x45: "itemnotify", - 0x46: "pocketisfull", - 0x47: "loadfont", - 0x48: "refreshscreen", - 0x49: "loadmovesprites", - 0x4B: "3writetext", - 0x4C: "2writetext", - 0x4E: "yesorno", - 0x4F: "loadmenudata", - 0x50: "writebackup", - 0x51: "jumptextfaceplayer", - 0x52: "jumptext", - 0x53: "closetext", - 0x54: "keeptextopen", - 0x55: "pokepic", - 0x56: "pokepicyesorno", - 0x57: "interpretmenu", - 0x58: "interpretmenu2", - 0x5C: "loadpokedata", - 0x5D: "loadtrainer", - 0x5E: "startbattle", - 0x5F: "returnafterbattle", - 0x60: "catchtutorial", - 0x63: "winlosstext", - 0x65: "talkaftercancel", - 0x67: "setlasttalked", - 0x68: "applymovement", - 0x6A: "faceplayer", - 0x6B: "faceperson", - 0x6C: "variablesprite", - 0x6D: "disappear", - 0x6E: "appear", - 0x6F: "follow", - 0x70: "stopfollow", - 0x71: "moveperson", - 0x74: "showemote", - 0x75: "spriteface", - 0x76: "follownotexact", - 0x77: "earthquake", - 0x79: "changeblock", - 0x7A: "reloadmap", - 0x7B: "reloadmappart", - 0x7C: "writecmdqueue", - 0x7D: "delcmdqueue", - 0x7E: "playmusic", - 0x7F: "playrammusic", - 0x80: "musicfadeout", - 0x81: "playmapmusic", - 0x82: "reloadmapmusic", - 0x83: "cry", - 0x84: "playsound", - 0x85: "waitbutton", - 0x86: "warpsound", - 0x87: "specialsound", - 0x88: "passtoengine", - 0x89: "newloadmap", - 0x8A: "pause", - 0x8B: "deactivatefacing", - 0x8C: "priorityjump", - 0x8D: "warpcheck", - 0x8E: "ptpriorityjump", - 0x8F: "return", - 0x90: "end", - 0x91: "reloadandreturn", - 0x92: "resetfuncs", - 0x93: "pokemart", - 0x94: "elevator", - 0x95: "trade", - 0x96: "askforphonenumber", - 0x97: "phonecall", - 0x98: "hangup", - 0x99: "describedecoration", - 0x9A: "fruittree", - 0x9C: "checkphonecall", - 0x9D: "verbosegiveitem", - 0x9E: "loadwilddata", - 0x9F: "halloffame", - 0xA0: "credits", - 0xA1: "warpfacing", - 0xA2: "storetext", - 0xA3: "displaylocation", -} - -# see http://www.pokecommunity.com/showpost.php?p=4347261 -# NOTE: this has some updates that need to be back-ported to gold -pksv_crystal = { - 0x00: "2call", - 0x01: "3call", - 0x02: "2ptcall", - 0x03: "2jump", - 0x04: "3jump", - 0x05: "2ptjump", - 0x06: "if equal", - 0x07: "if not equal", - 0x08: "if false", - 0x09: "if true", - 0x0A: "if less than", - 0x0B: "if greater than", - 0x0C: "jumpstd", - 0x0D: "callstd", - 0x0E: "3callasm", - 0x0F: "special", - 0x10: "2ptcallasm", - 0x11: "checkmaptriggers", - 0x12: "domaptrigger", - 0x13: "checktriggers", - 0x14: "dotrigger", - 0x15: "writebyte", - 0x16: "addvar", - 0x17: "random", - 0x19: "copybytetovar", - 0x1A: "copyvartobyte", - 0x1B: "loadvar", - 0x1C: "checkcode", - 0x1D: "writevarcode", - 0x1E: "writecode", - 0x1F: "giveitem", - 0x20: "takeitem", - 0x21: "checkitem", - 0x22: "givemoney", - 0x23: "takemoney", - 0x24: "checkmoney", - 0x25: "givecoins", - 0x26: "takecoins", - 0x27: "checkcoins", - 0x28: "addcellnum", - 0x29: "delcellnum", - 0x2B: "checktime", - 0x2C: "checkpoke", - 0x2D: "givepoke", - 0x2E: "giveegg", - 0x2F: "givepokeitem", - 0x31: "checkbit1", - 0x32: "clearbit1", - 0x33: "setbit1", - 0x34: "checkbit2", - 0x35: "clearbit2", - 0x36: "setbit2", - 0x37: "wildoff", - 0x38: "wildon", - 0x39: "xycompare", - 0x3A: "warpmod", - 0x3B: "blackoutmod", - 0x3C: "warp", - 0x41: "itemtotext", - 0x43: "trainertotext", - 0x44: "stringtotext", - 0x45: "itemnotify", - 0x46: "pocketisfull", - 0x47: "loadfont", - 0x48: "refreshscreen", - 0x49: "loadmovesprites", - 0x4B: "3writetext", - 0x4C: "2writetext", - 0x4E: "yesorno", - 0x4F: "loadmenudata", - 0x50: "writebackup", - 0x51: "jumptextfaceplayer", - 0x53: "jumptext", - 0x54: "closetext", - 0x55: "keeptextopen", - 0x56: "pokepic", - 0x57: "pokepicyesorno", - 0x58: "interpretmenu", - 0x59: "interpretmenu2", - 0x5D: "loadpokedata", - 0x5E: "loadtrainer", - 0x5F: "startbattle", - 0x60: "returnafterbattle", - 0x61: "catchtutorial", - 0x64: "winlosstext", - 0x66: "talkaftercancel", - 0x68: "setlasttalked", - 0x69: "applymovement", - 0x6B: "faceplayer", - 0x6C: "faceperson", - 0x6D: "variablesprite", - 0x6E: "disappear", - 0x6F: "appear", - 0x70: "follow", - 0x71: "stopfollow", - 0x72: "moveperson", - 0x75: "showemote", - 0x76: "spriteface", - 0x77: "follownotexact", - 0x78: "earthquake", - 0x7A: "changeblock", - 0x7B: "reloadmap", - 0x7C: "reloadmappart", - 0x7D: "writecmdqueue", - 0x7E: "delcmdqueue", - 0x7F: "playmusic", - 0x80: "playrammusic", - 0x81: "musicfadeout", - 0x82: "playmapmusic", - 0x83: "reloadmapmusic", - 0x84: "cry", - 0x85: "playsound", - 0x86: "waitbutton", - 0x87: "warpsound", - 0x88: "specialsound", - 0x89: "passtoengine", - 0x8A: "newloadmap", - 0x8B: "pause", - 0x8C: "deactivatefacing", - 0x8D: "priorityjump", - 0x8E: "warpcheck", - 0x8F: "ptpriorityjump", - 0x90: "return", - 0x91: "end", - 0x92: "reloadandreturn", - 0x93: "resetfuncs", - 0x94: "pokemart", - 0x95: "elevator", - 0x96: "trade", - 0x97: "askforphonenumber", - 0x98: "phonecall", - 0x99: "hangup", - 0x9A: "describedecoration", - 0x9B: "fruittree", - 0x9C: "specialphonecall", - 0x9D: "checkphonecall", - 0x9E: "verbosegiveitem", - 0x9F: "verbosegiveitem2", - 0xA0: "loadwilddata", - 0xA1: "halloffame", - 0xA2: "credits", - 0xA3: "warpfacing", - 0xA4: "storetext", - 0xA5: "displaylocation", - 0xB2: "unknown0xb2", -} - -#these cause the script to end; used in create_command_classes -pksv_crystal_more_enders = [0x03, 0x04, 0x05, 0x0C, 0x51, 0x52, - 0x53, 0x8D, 0x8F, 0x90, 0x91, 0x92, - 0x9B, - 0xB2, #maybe? - 0xCC, #maybe? - 0x9A, # describedecoration - ] - -# these have no pksv names as of pksv 2.1.1 -pksv_crystal_unknowns = [ - 0x9F, - 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, - 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, - 0xCC, 0xCD, - 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, - 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, - 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, - 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, -] diff --git a/pointers.py b/pointers.py deleted file mode 100644 index 161a53e..0000000 --- a/pointers.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Various functions related to pointer and address math. Mostly to avoid -depedency loops. -""" - -def calculate_bank(address): - """you are too lazy to divide on your own?""" - if type(address) == str: - address = int(address, 16) - #if 0x4000 <= address <= 0x7FFF: - # raise Exception, "bank 1 does not exist" - return int(address) / 0x4000 - -def calculate_pointer(short_pointer, bank=None): - """calculates the full address given a 4-byte pointer and bank byte""" - short_pointer = int(short_pointer) - if 0x4000 <= short_pointer <= 0x7fff: - short_pointer -= 0x4000 - bank = int(bank) - else: - bank = 0 - pointer = short_pointer + (bank * 0x4000) - return pointer diff --git a/pokemon_constants.py b/pokemon_constants.py deleted file mode 100644 index 221a31c..0000000 --- a/pokemon_constants.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- - -pokemon_constants = { -1: "BULBASAUR", -2: "IVYSAUR", -3: "VENUSAUR", -4: "CHARMANDER", -5: "CHARMELEON", -6: "CHARIZARD", -7: "SQUIRTLE", -8: "WARTORTLE", -9: "BLASTOISE", -10: "CATERPIE", -11: "METAPOD", -12: "BUTTERFREE", -13: "WEEDLE", -14: "KAKUNA", -15: "BEEDRILL", -16: "PIDGEY", -17: "PIDGEOTTO", -18: "PIDGEOT", -19: "RATTATA", -20: "RATICATE", -21: "SPEAROW", -22: "FEAROW", -23: "EKANS", -24: "ARBOK", -25: "PIKACHU", -26: "RAICHU", -27: "SANDSHREW", -28: "SANDSLASH", -29: "NIDORAN_F", -30: "NIDORINA", -31: "NIDOQUEEN", -32: "NIDORAN_M", -33: "NIDORINO", -34: "NIDOKING", -35: "CLEFAIRY", -36: "CLEFABLE", -37: "VULPIX", -38: "NINETALES", -39: "JIGGLYPUFF", -40: "WIGGLYTUFF", -41: "ZUBAT", -42: "GOLBAT", -43: "ODDISH", -44: "GLOOM", -45: "VILEPLUME", -46: "PARAS", -47: "PARASECT", -48: "VENONAT", -49: "VENOMOTH", -50: "DIGLETT", -51: "DUGTRIO", -52: "MEOWTH", -53: "PERSIAN", -54: "PSYDUCK", -55: "GOLDUCK", -56: "MANKEY", -57: "PRIMEAPE", -58: "GROWLITHE", -59: "ARCANINE", -60: "POLIWAG", -61: "POLIWHIRL", -62: "POLIWRATH", -63: "ABRA", -64: "KADABRA", -65: "ALAKAZAM", -66: "MACHOP", -67: "MACHOKE", -68: "MACHAMP", -69: "BELLSPROUT", -70: "WEEPINBELL", -71: "VICTREEBEL", -72: "TENTACOOL", -73: "TENTACRUEL", -74: "GEODUDE", -75: "GRAVELER", -76: "GOLEM", -77: "PONYTA", -78: "RAPIDASH", -79: "SLOWPOKE", -80: "SLOWBRO", -81: "MAGNEMITE", -82: "MAGNETON", -83: "FARFETCH_D", -84: "DODUO", -85: "DODRIO", -86: "SEEL", -87: "DEWGONG", -88: "GRIMER", -89: "MUK", -90: "SHELLDER", -91: "CLOYSTER", -92: "GASTLY", -93: "HAUNTER", -94: "GENGAR", -95: "ONIX", -96: "DROWZEE", -97: "HYPNO", -98: "KRABBY", -99: "KINGLER", -100: "VOLTORB", -101: "ELECTRODE", -102: "EXEGGCUTE", -103: "EXEGGUTOR", -104: "CUBONE", -105: "MAROWAK", -106: "HITMONLEE", -107: "HITMONCHAN", -108: "LICKITUNG", -109: "KOFFING", -110: "WEEZING", -111: "RHYHORN", -112: "RHYDON", -113: "CHANSEY", -114: "TANGELA", -115: "KANGASKHAN", -116: "HORSEA", -117: "SEADRA", -118: "GOLDEEN", -119: "SEAKING", -120: "STARYU", -121: "STARMIE", -122: "MR__MIME", -123: "SCYTHER", -124: "JYNX", -125: "ELECTABUZZ", -126: "MAGMAR", -127: "PINSIR", -128: "TAUROS", -129: "MAGIKARP", -130: "GYARADOS", -131: "LAPRAS", -132: "DITTO", -133: "EEVEE", -134: "VAPOREON", -135: "JOLTEON", -136: "FLAREON", -137: "PORYGON", -138: "OMANYTE", -139: "OMASTAR", -140: "KABUTO", -141: "KABUTOPS", -142: "AERODACTYL", -143: "SNORLAX", -144: "ARTICUNO", -145: "ZAPDOS", -146: "MOLTRES", -147: "DRATINI", -148: "DRAGONAIR", -149: "DRAGONITE", -150: "MEWTWO", -151: "MEW", -152: "CHIKORITA", -153: "BAYLEEF", -154: "MEGANIUM", -155: "CYNDAQUIL", -156: "QUILAVA", -157: "TYPHLOSION", -158: "TOTODILE", -159: "CROCONAW", -160: "FERALIGATR", -161: "SENTRET", -162: "FURRET", -163: "HOOTHOOT", -164: "NOCTOWL", -165: "LEDYBA", -166: "LEDIAN", -167: "SPINARAK", -168: "ARIADOS", -169: "CROBAT", -170: "CHINCHOU", -171: "LANTURN", -172: "PICHU", -173: "CLEFFA", -174: "IGGLYBUFF", -175: "TOGEPI", -176: "TOGETIC", -177: "NATU", -178: "XATU", -179: "MAREEP", -180: "FLAAFFY", -181: "AMPHAROS", -182: "BELLOSSOM", -183: "MARILL", -184: "AZUMARILL", -185: "SUDOWOODO", -186: "POLITOED", -187: "HOPPIP", -188: "SKIPLOOM", -189: "JUMPLUFF", -190: "AIPOM", -191: "SUNKERN", -192: "SUNFLORA", -193: "YANMA", -194: "WOOPER", -195: "QUAGSIRE", -196: "ESPEON", -197: "UMBREON", -198: "MURKROW", -199: "SLOWKING", -200: "MISDREAVUS", -201: "UNOWN", -202: "WOBBUFFET", -203: "GIRAFARIG", -204: "PINECO", -205: "FORRETRESS", -206: "DUNSPARCE", -207: "GLIGAR", -208: "STEELIX", -209: "SNUBBULL", -210: "GRANBULL", -211: "QWILFISH", -212: "SCIZOR", -213: "SHUCKLE", -214: "HERACROSS", -215: "SNEASEL", -216: "TEDDIURSA", -217: "URSARING", -218: "SLUGMA", -219: "MAGCARGO", -220: "SWINUB", -221: "PILOSWINE", -222: "CORSOLA", -223: "REMORAID", -224: "OCTILLERY", -225: "DELIBIRD", -226: "MANTINE", -227: "SKARMORY", -228: "HOUNDOUR", -229: "HOUNDOOM", -230: "KINGDRA", -231: "PHANPY", -232: "DONPHAN", -233: "PORYGON2", -234: "STANTLER", -235: "SMEARGLE", -236: "TYROGUE", -237: "HITMONTOP", -238: "SMOOCHUM", -239: "ELEKID", -240: "MAGBY", -241: "MILTANK", -242: "BLISSEY", -243: "RAIKOU", -244: "ENTEI", -245: "SUICUNE", -246: "LARVITAR", -247: "PUPITAR", -248: "TYRANITAR", -249: "LUGIA", -250: "HO_OH", -251: "CELEBI", -} diff --git a/pokemontools/__init__.py b/pokemontools/__init__.py new file mode 100644 index 0000000..833709b --- /dev/null +++ b/pokemontools/__init__.py @@ -0,0 +1 @@ +import crystal diff --git a/pokemontools/chars.py b/pokemontools/chars.py new file mode 100644 index 0000000..fc69fc5 --- /dev/null +++ b/pokemontools/chars.py @@ -0,0 +1,279 @@ +# -*- coding: utf-8 -*- +from copy import copy + +# this is straight out of ../preprocessor.py because i'm lazy +# (also, it's flipped) +# 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: "→", + 0xEC: "▷", + 0xED: "▶", + 0xEE: "▼", + 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 diff --git a/pokemontools/comparator.py b/pokemontools/comparator.py new file mode 100644 index 0000000..6cc440c --- /dev/null +++ b/pokemontools/comparator.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +""" +Find shared functions between red/crystal. +""" + +from crystal import ( + get_label_from_line, + get_address_from_line_comment, + AsmSection, + direct_load_rom, + direct_load_asm, +) + +from romstr import ( + RomStr, + AsmList, +) + +def load_rom(path): + """ + Load a ROM file into an abbreviated RomStr object. + """ + return direct_load_rom(filename=path) + +def load_asm(path): + """ + Load source ASM into an abbreviated AsmList object. + """ + return direct_load_asm(filename=path) + +def findall_iter(sub, string): + # url: http://stackoverflow.com/a/3874760/687783 + + def next_index(length): + index = 0 - length + while True: + index = string.find(sub, index + length) + yield index + + return iter(next_index(len(sub)).next, -1) + +class Address(int): + """ + A simple int wrapper to take 0xFFFF and $FFFF addresses. + """ + + def __new__(cls, x=None, *args, **kwargs): + if type(x) == str: + if "$" in x: + x = x.replace("$", "0x") + + if "0x" in str: + instance = int.__new__(cls, int(x, base=16), *args, **kwargs) + else: + msg = "Address.__new__ doesn't know how to parse this string" + raise Exception, msg + else: + instance = int.__new__(cls, x, *args, **kwargs) + + return instance + +found_blobs = [] + +class BinaryBlob(object): + """ + Store a label, line number, and addresses of a function from Pokémon Red. + + These details can be used to determine whether or not the function was + copied into Pokémon Crystal. + """ + + start_address = None + end_address = None + label = None + line_number = None + bytes = None + bank = None + debug = False + locations = None + + def __init__(self, start_address=None, end_address=None, label=None, \ + debug=None, line_number=None): + if not isinstance(start_address, Address): + start_address = Address(start_address) + if not isinstance(end_address, Address): + end_address = Address(end_address) + + assert label != None, "label can't be none" + assert isinstance(label, str), "label must be a string" + assert line_number != None, "line_number must be provided" + + self.start_address = start_address + self.end_address = end_address + self.label = label + self.line_number = line_number + self.bytes = [] + self.locations = [] + self.bank = start_address / 0x4000 + + if debug != None: + self.debug = debug + + self.parse_from_red() + # self.find_in_crystal() + self.find_by_first_bytes() + + def __repr__(self): + """ + A beautiful poem. + """ + + r = "BinaryBlob(" + r += "label=\""+self.label+"\", " + r += "start_address="+hex(self.start_address)+", " + r += "size="+str(self.end_address - self.start_address)+", " + locnum = len(self.locations) + if locnum == 1: + r += "located="+hex(self.locations[0]) + elif locnum <= 5: + r += "located="+str([hex(x) for x in self.locations]) + else: + r += "located="+str(locnum) + r += ")" + + return r + + def __str__(self): + return self.__repr__() + + def parse_from_red(self): + """ + Read bytes from Pokémon Red and stores them. + """ + + self.bytes = redrom[self.start_address : self.end_address + 1] + + def pretty_bytes(self): + """ + Returns a better looking range of bytes. + """ + + bytes = redrom.interval(self.start_address, \ + self.end_address - self.start_address, \ + strings=False, debug=True) + + return bytes + + def find_in_crystal(self): + """ + Check whether or not the bytes appear in Pokémon Crystal. + """ + + finditer = findall_iter(self.bytes, cryrom) + self.locations = [match for match in finditer] + + if len(self.locations) > 0: + found_blobs.append(self) + + if self.debug: + print self.label + ": found " + str(len(self.locations)) + " matches." + + def find_by_first_bytes(self): + """ + Find this blob in Crystal based on the first n bytes. + """ + + # how many bytes to match + first_n = 3 + + # no match + if len(self.bytes) <= first_n: + return + + finditer = findall_iter(self.bytes[0:first_n], cryrom) + self.locations = [match for match in finditer] + + # filter out locations that suck + self.locations = [i for i in self.locations if abs(self.start_address - i) <= 0x8000] + + if len(self.locations) > 0: + found_blobs.append(self) + + if self.debug: + print self.label + ": found " + str(len(self.locations)) + " matches." + +pokecrystal_rom_path = "../baserom.gbc" +pokecrystal_src_path = "../main.asm" +pokered_rom_path = "../pokered-baserom.gbc" +pokered_src_path = "../pokered-main.asm" + +cryrom = load_rom(pokecrystal_rom_path) +crysrc = load_asm(pokecrystal_src_path) +redrom = load_rom(pokered_rom_path) +redsrc = load_asm(pokered_src_path) + +def scan_red_asm(bank_stop=3, debug=True): + """ + Scan the ASM from Pokémon Red. Finds labels and objects. Does things. + + Uses get_label_from_line and get_address_from_line_comment. + """ + + # whether or not to show the lines from redsrc + show_lines = False + + line_number = 0 + current_bank = 0 + + current_label = None + latest_label = "ignore me" + current_start_address = None + latest_start_address = 0 + latest_line = "" + + for line in redsrc: + if debug and show_lines: + print "processing a line from red: " + line + + if line[0:7] == "SECTION": + thing = AsmSection(line) + current_bank = thing.bank_id + + if debug: + print "scan_red_asm: switching to bank " + str(current_bank) + + elif line[0:6] != "INCBIN": + if ":" in line and not ";XXX:" in line and not " ; XXX:" in line: + current_label = get_label_from_line(line) + current_start_address = get_address_from_line_comment(line, \ + bank=current_bank) + + if current_label != None and current_start_address != None and latest_start_address != None \ + and current_start_address != 0 and current_start_address != latest_start_address \ + and (current_start_address - latest_start_address) > 1: + if latest_label != None: + if latest_label not in ["Char52", "PokeCenterSignText", "DefaultNamesPlayer", "Unnamed_6a12"]: + blob = BinaryBlob(label=latest_label, \ + start_address=latest_start_address, \ + end_address=current_start_address, \ + line_number=line_number) + + if debug: + print "Created a new blob: " + str(blob) + " from line: " + str(latest_line) + + latest_label = current_label + latest_start_address = current_start_address + latest_line = line + + line_number += 1 + + if current_bank == bank_stop: + if debug: + print "scan_red_asm: stopping because current_bank >= " + \ + str(bank_stop) + " (bank_stop)" + + break + +scan_red_asm(bank_stop=3) + +print "================================" + +for blob in found_blobs: + print blob + +print "Found " + str(len(found_blobs)) + " possibly copied functions." + +print [hex(x) for x in found_blobs[10].locations] diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py new file mode 100644 index 0000000..9ee6ec0 --- /dev/null +++ b/pokemontools/crystal.py @@ -0,0 +1,7629 @@ +# -*- 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. + + <0x50> + <0xFF> + + Data type <0x00>: Pokémon Data is . Used by most trainers. + Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. + Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. + Data type <0x03>: Pokémon Data is . 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: + """ + <0x50> + <0xFF> + + Data type <0x00>: Pokémon Data is . Used by most trainers. + Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. + Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. + Data type <0x03>: Pokémon Data is . 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 . 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 + . 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 . 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 + . + 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 memoryotherpointer is 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 it would be a good idea to rename otherpointer strippointer or striploc + # 10:42 since thats more accurate + # 11:05 Above: C803h + xoffset + # 11:05 Below: C803h + (m.height + 3) * (m.width + 6) + xoffset + # 11:05 Left: C800h + (m.width + 6) * (yoffset + 3) + # 11:05 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 Above: C803h + xoffset + # 11:05 Below: C803h + (m.height + 3) * (m.width + 6) + xoffset + # 11:05 Left: C800h + (m.width + 6) * (yoffset + 3) + # 11:05 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 diff --git a/pokemontools/disassemble_map_scripts.py b/pokemontools/disassemble_map_scripts.py new file mode 100644 index 0000000..21df569 --- /dev/null +++ b/pokemontools/disassemble_map_scripts.py @@ -0,0 +1,151 @@ +# -*- encoding: utf-8 -*- +""" +Dump out asm for scripting things in bank $25. This script will modify main.asm +and insert all scripting commands. +""" + +import crystal +from gbz80disasm import output_bank_opcodes + +rom = crystal.load_rom() +roml = [ord(x) for x in rom] + +script_command_table_address = 0x96cb1 +script_command_count = 170 + +# a list of addresses for each script command +command_pointers = [crystal.calculate_pointer_from_bytes_at(script_command_table_address + (id * 2), bank=0x25) for id in range(0, 170)] + +# a list of hex addresses for each script command in bank $25 +command_pointers_hex = ["$%.2x" % (x % 0x4000 + 0x4000) for x in command_pointers] + +commands = {} + +# force data into a more usable form +for command in crystal.command_classes: + name = "Script_" + command.macro_name + id = command.id + params = {} + + for (id2, param_type) in command.param_types.items(): + param = { + "name": param_type["name"], + "type": param_type["class"].__name__, + } + params[id2] = param + + if id <= 0xa9: + commands[id] = { + "name": name, + "params": params, + "address": command_pointers[id], + } + +avoid = [ + 0x974b0, + 0x974be, + 0x9754b, + 0x97556, + 0x97562, + 0x9756e, + 0x97540, + + 0x96f8e, # verbosegiveitem2 +] + +class DisassembledScriptCommand(): + """ + Just a temporary object to store information about a script command's asm. + This is used by some of the infrastructure in crystal.py to automatically + insert asm into main.asm, rather than having someone do it manually. + """ + dependencies = None + + def __init__(self, label=None, id=None, address=None, params=None): + self.id = id + self.label = crystal.Label(name=label, address=address, object=self) + self.address = address + self.params = params + + max_byte_count = 0x4000 + + # Some of these scripts need to be truncated before insertion, because + # output_bank_opcodes doesn't know anything about stopping if some of + # the local labels are not resolved yet. + + # Script_if_equal + if address == 0x97540: + max_byte_count = 86 + + # disassemble and laso get the last address + (asm, last_address, last_hl_address, last_a_address, used_3d97) = output_bank_opcodes(address, max_byte_count=max_byte_count, stop_at=command_pointers, include_last_address=False) + + # remove indentation + asm = asm.replace("\n\t", "\n") + if asm[0] == "\t": + asm = asm[1:] + + # remove the last two newlines + while asm[-1] == "\n": + asm = asm[:-1] + + self.asm = asm + self.last_address = last_address + + # make sure this gets dumped into main.asm + #if crystal.script_parse_table[self.address] == None and crystal.script_parse_table[self.last_address] == None: + crystal.script_parse_table[self.address : self.last_address] = self + #else: + # print ".. hm, something is already at " + hex(self.address) + " for " + self.label.name + + def to_asm(self): + #output += self.label + ": ; " + hex(self.address) + "\n" + output = "; script command " + hex(self.id) + "\n" + if len(self.params) > 0: + output += "; parameters:\n" + for (id2, param) in self.params.items(): + output += "; " + param["name"] + " (" + param["type"] + ")\n" + output += "\n" + output += self.asm + return output + + def get_dependencies(*args, **kwargs): + return [] + +# make instances of DisassembledScriptCommand +for (id, command) in commands.items(): + name = command["name"] + params = command["params"] + address = command["address"] + + script_asm = DisassembledScriptCommand(label=name, id=id, address=address, params=params) + #print script_asm.to_asm() + #print crystal.to_asm(script_asm, use_asm_rules=True) + +class ScriptCommandTable(): + address = script_command_table_address + last_address = script_command_table_address + (2 * 170) + dependencies = None + + def __init__(self): + self.label = crystal.Label(name="ScriptCommandTable", address=self.address, object=self) + + # make sure this gets dumped into main.asm + crystal.script_parse_table[self.address : self.last_address] = self + + def get_dependencies(*args, **kwargs): + return [] + + def to_asm(self): + output = "" + for (id, command) in commands.items(): + output += "dw " + command["name"] + "; " + hex(command["address"]) + "\n" + if output[-1] == "\n": + output = output[:-1] + return output +script_command_table = ScriptCommandTable() +#print crystal.to_asm(script_command_table, use_asm_rules=True) + +# automatic asm insertion +asm = crystal.Asm() +asm.insert_and_dump(limit=500) diff --git a/pokemontools/dump_sections.py b/pokemontools/dump_sections.py new file mode 100755 index 0000000..fef35d8 --- /dev/null +++ b/pokemontools/dump_sections.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" +Use this tool to dump an asm file for a new source code or disassembly project. + +usage: + + from dump_sections import dump_sections + + output = dump_sections("../../butt.gbc") + + file_handler = open("main.asm", "w") + file_handler.write(output) + file_handler.close() + +You can also use this script from the shell, where it will look for +"baserom.gbc" in the current working path or whatever file path you pass in the +first positional argument. +""" + +import os +import sys +import argparse + +def upper_hex(input): + """ + Converts the input to an uppercase hex string. + """ + if input in [0, "0"]: + return "0" + elif input <= 0xF: + return ("%.x" % (input)).upper() + else: + return ("%.2x" % (input)).upper() + +def format_bank_number(address, bank_size=0x4000): + """ + Returns a str of the hex number of the bank based on the address. + """ + return upper_hex(address / bank_size) + +def calculate_bank_quantity(path, bank_size=0x4000): + """ + Returns the number of 0x4000 banks in the file at path. + """ + return float(os.lstat(path).st_size) / bank_size + +def dump_section(bank_number, separator="\n\n"): + """ + Returns a str of a section header for the asm file. + """ + output = "SECTION \"" + if bank_number in [0, "0"]: + output += "bank0\",HOME" + else: + output += "bank" + output += bank_number + output += "\",DATA,BANK[$" + output += bank_number + output += "]" + output += separator + return output + +def dump_incbin_for_section(address, bank_size=0x4000, baserom="baserom.gbc", separator="\n\n"): + """ + Returns a str for an INCBIN line for an entire section. + """ + output = "INCBIN \"" + output += baserom + output += "\",$" + output += upper_hex(address) + output += ",$" + output += upper_hex(bank_size) + output += separator + return output + +def dump_sections(path, bank_size=0x4000, initial_bank=0, last_bank=None, separator="\n\n"): + """ + Returns a str of assembly source code. The source code delineates each + SECTION and includes bytes from the file specified by baserom. + """ + if not last_bank: + last_bank = calculate_bank_quantity(path, bank_size=bank_size) + + if last_bank < initial_bank: + raise Exception("last_bank must be greater than or equal to initial_bank") + + baserom_name = os.path.basename(path) + + output = "" + + banks = range(initial_bank, last_bank) + + for bank_number in banks: + address = bank_number * bank_size + + # get a formatted hex number of the bank based on the address + formatted_bank_number = format_bank_number(address, bank_size=bank_size) + + # SECTION + output += dump_section(formatted_bank_number, separator=separator) + + # INCBIN a range of bytes from the ROM + output += dump_incbin_for_section(address, bank_size=bank_size, baserom=baserom_name, separator=separator) + + # clean up newlines at the end of the output + if output[-2:] == "\n\n": + output = output[:-2] + output += "\n" + + return output + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("rompath", nargs="?", metavar="rompath", type=str) + args = parser.parse_args() + + # default to "baserom.gbc" in the current working directory + baserom = "baserom.gbc" + + # but let the user override the default + if args.rompath: + baserom = args.rompath + + # generate some asm + output = dump_sections(baserom) + + # dump everything to stdout + sys.stdout.write(output) diff --git a/pokemontools/gbz80disasm.py b/pokemontools/gbz80disasm.py new file mode 100644 index 0000000..6759a4a --- /dev/null +++ b/pokemontools/gbz80disasm.py @@ -0,0 +1,945 @@ +# -*- coding: utf-8 -*- + +import os +import sys +from copy import copy, deepcopy +from ctypes import c_int8 +import random +import json +from wram import * + +# New versions of json don't have read anymore. +if not hasattr(json, "read"): + json.read = json.loads + +def load_rom(filename="../baserom.gbc"): + """ + Load the specified rom. + + If no rom is given, load "../baserom.gbc". + """ + global rom + rom = bytearray(open(filename,'rb').read()) + return rom + +spacing = "\t" + +temp_opt_table = [ + [ "ADC A", 0x8f, 0 ], + [ "ADC B", 0x88, 0 ], + [ "ADC C", 0x89, 0 ], + [ "ADC D", 0x8a, 0 ], + [ "ADC E", 0x8b, 0 ], + [ "ADC H", 0x8c, 0 ], + [ "ADC [HL]", 0x8e, 0 ], + [ "ADC L", 0x8d, 0 ], + [ "ADC x", 0xce, 1 ], + [ "ADD A", 0x87, 0 ], + [ "ADD B", 0x80, 0 ], + [ "ADD C", 0x81, 0 ], + [ "ADD D", 0x82, 0 ], + [ "ADD E", 0x83, 0 ], + [ "ADD H", 0x84, 0 ], + [ "ADD [HL]", 0x86, 0 ], + [ "ADD HL, BC", 0x9, 0 ], + [ "ADD HL, DE", 0x19, 0 ], + [ "ADD HL, HL", 0x29, 0 ], + [ "ADD HL, SP", 0x39, 0 ], + [ "ADD L", 0x85, 0 ], + [ "ADD SP, x", 0xe8, 1 ], + [ "ADD x", 0xc6, 1 ], + [ "AND A", 0xa7, 0 ], + [ "AND B", 0xa0, 0 ], + [ "AND C", 0xa1, 0 ], + [ "AND D", 0xa2, 0 ], + [ "AND E", 0xa3, 0 ], + [ "AND H", 0xa4, 0 ], + [ "AND [HL]", 0xa6, 0 ], + [ "AND L", 0xa5, 0 ], + [ "AND x", 0xe6, 1 ], + [ "BIT 0, A", 0x47cb, 3 ], + [ "BIT 0, B", 0x40cb, 3 ], + [ "BIT 0, C", 0x41cb, 3 ], + [ "BIT 0, D", 0x42cb, 3 ], + [ "BIT 0, E", 0x43cb, 3 ], + [ "BIT 0, H", 0x44cb, 3 ], + [ "BIT 0, [HL]", 0x46cb, 3 ], + [ "BIT 0, L", 0x45cb, 3 ], + [ "BIT 1, A", 0x4fcb, 3 ], + [ "BIT 1, B", 0x48cb, 3 ], + [ "BIT 1, C", 0x49cb, 3 ], + [ "BIT 1, D", 0x4acb, 3 ], + [ "BIT 1, E", 0x4bcb, 3 ], + [ "BIT 1, H", 0x4ccb, 3 ], + [ "BIT 1, [HL]", 0x4ecb, 3 ], + [ "BIT 1, L", 0x4dcb, 3 ], + [ "BIT 2, A", 0x57cb, 3 ], + [ "BIT 2, B", 0x50cb, 3 ], + [ "BIT 2, C", 0x51cb, 3 ], + [ "BIT 2, D", 0x52cb, 3 ], + [ "BIT 2, E", 0x53cb, 3 ], + [ "BIT 2, H", 0x54cb, 3 ], + [ "BIT 2, [HL]", 0x56cb, 3 ], + [ "BIT 2, L", 0x55cb, 3 ], + [ "BIT 3, A", 0x5fcb, 3 ], + [ "BIT 3, B", 0x58cb, 3 ], + [ "BIT 3, C", 0x59cb, 3 ], + [ "BIT 3, D", 0x5acb, 3 ], + [ "BIT 3, E", 0x5bcb, 3 ], + [ "BIT 3, H", 0x5ccb, 3 ], + [ "BIT 3, [HL]", 0x5ecb, 3 ], + [ "BIT 3, L", 0x5dcb, 3 ], + [ "BIT 4, A", 0x67cb, 3 ], + [ "BIT 4, B", 0x60cb, 3 ], + [ "BIT 4, C", 0x61cb, 3 ], + [ "BIT 4, D", 0x62cb, 3 ], + [ "BIT 4, E", 0x63cb, 3 ], + [ "BIT 4, H", 0x64cb, 3 ], + [ "BIT 4, [HL]", 0x66cb, 3 ], + [ "BIT 4, L", 0x65cb, 3 ], + [ "BIT 5, A", 0x6fcb, 3 ], + [ "BIT 5, B", 0x68cb, 3 ], + [ "BIT 5, C", 0x69cb, 3 ], + [ "BIT 5, D", 0x6acb, 3 ], + [ "BIT 5, E", 0x6bcb, 3 ], + [ "BIT 5, H", 0x6ccb, 3 ], + [ "BIT 5, [HL]", 0x6ecb, 3 ], + [ "BIT 5, L", 0x6dcb, 3 ], + [ "BIT 6, A", 0x77cb, 3 ], + [ "BIT 6, B", 0x70cb, 3 ], + [ "BIT 6, C", 0x71cb, 3 ], + [ "BIT 6, D", 0x72cb, 3 ], + [ "BIT 6, E", 0x73cb, 3 ], + [ "BIT 6, H", 0x74cb, 3 ], + [ "BIT 6, [HL]", 0x76cb, 3 ], + [ "BIT 6, L", 0x75cb, 3 ], + [ "BIT 7, A", 0x7fcb, 3 ], + [ "BIT 7, B", 0x78cb, 3 ], + [ "BIT 7, C", 0x79cb, 3 ], + [ "BIT 7, D", 0x7acb, 3 ], + [ "BIT 7, E", 0x7bcb, 3 ], + [ "BIT 7, H", 0x7ccb, 3 ], + [ "BIT 7, [HL]", 0x7ecb, 3 ], + [ "BIT 7, L", 0x7dcb, 3 ], + [ "CALL C, ?", 0xdc, 2 ], + [ "CALL NC, ?", 0xd4, 2 ], + [ "CALL NZ, ?", 0xc4, 2 ], + [ "CALL Z, ?", 0xcc, 2 ], + [ "CALL ?", 0xcd, 2 ], + [ "CCF", 0x3f, 0 ], + [ "CP A", 0xbf, 0 ], + [ "CP B", 0xb8, 0 ], + [ "CP C", 0xb9, 0 ], + [ "CP D", 0xba, 0 ], + [ "CP E", 0xbb, 0 ], + [ "CP H", 0xbc, 0 ], + [ "CP [HL]", 0xbe, 0 ], + [ "CPL", 0x2f, 0 ], + [ "CP L", 0xbd, 0 ], + [ "CP x", 0xfe, 1 ], + [ "DAA", 0x27, 0 ], + [ "DEBUG", 0xed, 0 ], + [ "DEC A", 0x3d, 0 ], + [ "DEC B", 0x5, 0 ], + [ "DEC BC", 0xb, 0 ], + [ "DEC C", 0xd, 0 ], + [ "DEC D", 0x15, 0 ], + [ "DEC DE", 0x1b, 0 ], + [ "DEC E", 0x1d, 0 ], + [ "DEC H", 0x25, 0 ], + [ "DEC HL", 0x2b, 0 ], + [ "DEC [HL]", 0x35, 0 ], + [ "DEC L", 0x2d, 0 ], + [ "DEC SP", 0x3b, 0 ], + [ "DI", 0xf3, 0 ], + [ "EI", 0xfb, 0 ], + [ "HALT", 0x76, 0 ], + [ "INC A", 0x3c, 0 ], + [ "INC B", 0x4, 0 ], + [ "INC BC", 0x3, 0 ], + [ "INC C", 0xc, 0 ], + [ "INC D", 0x14, 0 ], + [ "INC DE", 0x13, 0 ], + [ "INC E", 0x1c, 0 ], + [ "INC H", 0x24, 0 ], + [ "INC HL", 0x23, 0 ], + [ "INC [HL]", 0x34, 0 ], + [ "INC L", 0x2c, 0 ], + [ "INC SP", 0x33, 0 ], + [ "JP C, ?", 0xda, 2 ], + [ "JP [HL]", 0xe9, 0 ], + [ "JP NC, ?", 0xd2, 2 ], + [ "JP NZ, ?", 0xc2, 2 ], + [ "JP Z, ?", 0xca, 2 ], + [ "JP ?", 0xc3, 2 ], + [ "JR C, x", 0x38, 1 ], + [ "JR NC, x", 0x30, 1 ], + [ "JR NZ, x", 0x20, 1 ], + [ "JR Z, x", 0x28, 1 ], + [ "JR x", 0x18, 1 ], + [ "LD A, A", 0x7f, 0 ], + [ "LD A, B", 0x78, 0 ], + [ "LD A, C", 0x79, 0 ], + [ "LD A, D", 0x7a, 0 ], + [ "LD A, E", 0x7b, 0 ], + [ "LD A, H", 0x7c, 0 ], + [ "LD A, L", 0x7d, 0 ], + [ "LD A, [$FF00+C]", 0xf2, 0 ], + [ "LD A, [$FF00+x]", 0xf0, 1 ], +# [ "LDH A, [x]", 0xf0, 1 ], # rgbds has trouble with this one? + [ "LD A, [BC]", 0xa, 0 ], + [ "LD A, [DE]", 0x1a, 0 ], +# [ "LD A, [HL+]", 0x2a, 0 ], +# [ "LD A, [HL-]", 0x3a, 0 ], + [ "LD A, [HL]", 0x7e, 0 ], + [ "LD A, [HLD]", 0x3a, 0 ], + [ "LD A, [HLI]", 0x2a, 0 ], + [ "LD A, [?]", 0xfa, 2 ], + [ "LD A, x", 0x3e, 1 ], + [ "LD B, A", 0x47, 0 ], + [ "LD B, B", 0x40, 0 ], + [ "LD B, C", 0x41, 0 ], + [ "LD [BC], A", 0x2, 0 ], + [ "LD B, D", 0x42, 0 ], + [ "LD B, E", 0x43, 0 ], + [ "LD B, H", 0x44, 0 ], + [ "LD B, [HL]", 0x46, 0 ], + [ "LD B, L", 0x45, 0 ], + [ "LD B, x", 0x6, 1 ], + [ "LD C, A", 0x4f, 0 ], + [ "LD C, B", 0x48, 0 ], + [ "LD C, C", 0x49, 0 ], + [ "LD C, D", 0x4a, 0 ], + [ "LD C, E", 0x4b, 0 ], + [ "LD C, H", 0x4c, 0 ], + [ "LD C, [HL]", 0x4e, 0 ], + [ "LD C, L", 0x4d, 0 ], + [ "LD C, x", 0xe, 1 ], + [ "LD D, A", 0x57, 0 ], +# [ "LDD A, [HL]", 0x3a, 0 ], + [ "LD D, B", 0x50, 0 ], + [ "LD D, C", 0x51, 0 ], + [ "LD D, D", 0x52, 0 ], + [ "LD D, E", 0x53, 0 ], + [ "LD [DE], A", 0x12, 0 ], + [ "LD D, H", 0x54, 0 ], + [ "LD D, [HL]", 0x56, 0 ], +# [ "LDD [HL], A", 0x32, 0 ], + [ "LD D, L", 0x55, 0 ], + [ "LD D, x", 0x16, 1 ], + [ "LD E, A", 0x5f, 0 ], + [ "LD E, B", 0x58, 0 ], + [ "LD E, C", 0x59, 0 ], + [ "LD E, D", 0x5a, 0 ], + [ "LD E, E", 0x5b, 0 ], + [ "LD E, H", 0x5c, 0 ], + [ "LD E, [HL]", 0x5e, 0 ], + [ "LD E, L", 0x5d, 0 ], + [ "LD E, x", 0x1e, 1 ], + [ "LD [$FF00+C], A", 0xe2, 0 ], + [ "LD [$FF00+x], A", 0xe0, 1 ], +# [ "LDH [x], A", 0xe0, 1 ], + [ "LD H, A", 0x67, 0 ], + [ "LD H, B", 0x60, 0 ], + [ "LD H, C", 0x61, 0 ], + [ "LD H, D", 0x62, 0 ], + [ "LD H, E", 0x63, 0 ], + [ "LD H, H", 0x64, 0 ], + [ "LD H, [HL]", 0x66, 0 ], + [ "LD H, L", 0x65, 0 ], +# [ "LD [HL+], A", 0x22, 0 ], +# [ "LD [HL-], A", 0x32, 0 ], + [ "LD [HL], A", 0x77, 0 ], + [ "LD [HL], B", 0x70, 0 ], + [ "LD [HL], C", 0x71, 0 ], + [ "LD [HL], D", 0x72, 0 ], + [ "LD [HLD], A", 0x32, 0 ], + [ "LD [HL], E", 0x73, 0 ], + [ "LD [HL], H", 0x74, 0 ], + [ "LD [HLI], A", 0x22, 0 ], + [ "LD [HL], L", 0x75, 0 ], +# [ "LD HL, SP+x", 0xf8, 1 ], # rgbds uses [sp+x] + [ "LD HL, [SP+x]", 0xf8, 1 ], + [ "LD [HL], x", 0x36, 1 ], + [ "LD H, x", 0x26, 1 ], +# [ "LDI A, [HL]", 0x2a, 0 ], +# [ "LDI [HL], A", 0x22, 0 ], + [ "LD L, A", 0x6f, 0 ], + [ "LD L, B", 0x68, 0 ], + [ "LD L, C", 0x69, 0 ], + [ "LD L, D", 0x6a, 0 ], + [ "LD L, E", 0x6b, 0 ], + [ "LD L, H", 0x6c, 0 ], + [ "LD L, [HL]", 0x6e, 0 ], + [ "LD L, L", 0x6d, 0 ], + [ "LD L, x", 0x2e, 1 ], +# [ "LD PC, HL", 0xe9, 0 ], #prefer jp [hl] + [ "LD SP, HL", 0xf9, 0 ], + [ "LD BC, ?", 0x1, 2 ], + [ "LD DE, ?", 0x11, 2 ], + [ "LD HL, ?", 0x21, 2 ], + [ "LD SP, ?", 0x31, 2 ], + [ "LD [?], SP", 0x8, 2 ], + [ "LD [?], A", 0xea, 2 ], + [ "NOP", 0x0, 0 ], + [ "OR A", 0xb7, 0 ], + [ "OR B", 0xb0, 0 ], + [ "OR C", 0xb1, 0 ], + [ "OR D", 0xb2, 0 ], + [ "OR E", 0xb3, 0 ], + [ "OR H", 0xb4, 0 ], + [ "OR [HL]", 0xb6, 0 ], + [ "OR L", 0xb5, 0 ], + [ "OR x", 0xf6, 1 ], + [ "POP AF", 0xf1, 0 ], + [ "POP BC", 0xc1, 0 ], + [ "POP DE", 0xd1, 0 ], + [ "POP HL", 0xe1, 0 ], + [ "PUSH AF", 0xf5, 0 ], + [ "PUSH BC", 0xc5, 0 ], + [ "PUSH DE", 0xd5, 0 ], + [ "PUSH HL", 0xe5, 0 ], + [ "RES 0, A", 0x87cb, 3 ], + [ "RES 0, B", 0x80cb, 3 ], + [ "RES 0, C", 0x81cb, 3 ], + [ "RES 0, D", 0x82cb, 3 ], + [ "RES 0, E", 0x83cb, 3 ], + [ "RES 0, H", 0x84cb, 3 ], + [ "RES 0, [HL]", 0x86cb, 3 ], + [ "RES 0, L", 0x85cb, 3 ], + [ "RES 1, A", 0x8fcb, 3 ], + [ "RES 1, B", 0x88cb, 3 ], + [ "RES 1, C", 0x89cb, 3 ], + [ "RES 1, D", 0x8acb, 3 ], + [ "RES 1, E", 0x8bcb, 3 ], + [ "RES 1, H", 0x8ccb, 3 ], + [ "RES 1, [HL]", 0x8ecb, 3 ], + [ "RES 1, L", 0x8dcb, 3 ], + [ "RES 2, A", 0x97cb, 3 ], + [ "RES 2, B", 0x90cb, 3 ], + [ "RES 2, C", 0x91cb, 3 ], + [ "RES 2, D", 0x92cb, 3 ], + [ "RES 2, E", 0x93cb, 3 ], + [ "RES 2, H", 0x94cb, 3 ], + [ "RES 2, [HL]", 0x96cb, 3 ], + [ "RES 2, L", 0x95cb, 3 ], + [ "RES 3, A", 0x9fcb, 3 ], + [ "RES 3, B", 0x98cb, 3 ], + [ "RES 3, C", 0x99cb, 3 ], + [ "RES 3, D", 0x9acb, 3 ], + [ "RES 3, E", 0x9bcb, 3 ], + [ "RES 3, H", 0x9ccb, 3 ], + [ "RES 3, [HL]", 0x9ecb, 3 ], + [ "RES 3, L", 0x9dcb, 3 ], + [ "RES 4, A", 0xa7cb, 3 ], + [ "RES 4, B", 0xa0cb, 3 ], + [ "RES 4, C", 0xa1cb, 3 ], + [ "RES 4, D", 0xa2cb, 3 ], + [ "RES 4, E", 0xa3cb, 3 ], + [ "RES 4, H", 0xa4cb, 3 ], + [ "RES 4, [HL]", 0xa6cb, 3 ], + [ "RES 4, L", 0xa5cb, 3 ], + [ "RES 5, A", 0xafcb, 3 ], + [ "RES 5, B", 0xa8cb, 3 ], + [ "RES 5, C", 0xa9cb, 3 ], + [ "RES 5, D", 0xaacb, 3 ], + [ "RES 5, E", 0xabcb, 3 ], + [ "RES 5, H", 0xaccb, 3 ], + [ "RES 5, [HL]", 0xaecb, 3 ], + [ "RES 5, L", 0xadcb, 3 ], + [ "RES 6, A", 0xb7cb, 3 ], + [ "RES 6, B", 0xb0cb, 3 ], + [ "RES 6, C", 0xb1cb, 3 ], + [ "RES 6, D", 0xb2cb, 3 ], + [ "RES 6, E", 0xb3cb, 3 ], + [ "RES 6, H", 0xb4cb, 3 ], + [ "RES 6, [HL]", 0xb6cb, 3 ], + [ "RES 6, L", 0xb5cb, 3 ], + [ "RES 7, A", 0xbfcb, 3 ], + [ "RES 7, B", 0xb8cb, 3 ], + [ "RES 7, C", 0xb9cb, 3 ], + [ "RES 7, D", 0xbacb, 3 ], + [ "RES 7, E", 0xbbcb, 3 ], + [ "RES 7, H", 0xbccb, 3 ], + [ "RES 7, [HL]", 0xbecb, 3 ], + [ "RES 7, L", 0xbdcb, 3 ], + [ "RETI", 0xd9, 0 ], + [ "RET C", 0xd8, 0 ], + [ "RET NC", 0xd0, 0 ], + [ "RET NZ", 0xc0, 0 ], + [ "RET Z", 0xc8, 0 ], + [ "RET", 0xc9, 0 ], + [ "RLA", 0x17, 0 ], + [ "RL A", 0x17cb, 3 ], + [ "RL B", 0x10cb, 3 ], + [ "RL C", 0x11cb, 3 ], + [ "RLCA", 0x7, 0 ], + [ "RLC A", 0x7cb, 3 ], + [ "RLC B", 0xcb, 3 ], + [ "RLC C", 0x1cb, 3 ], + [ "RLC D", 0x2cb, 3 ], + [ "RLC E", 0x3cb, 3 ], + [ "RLC H", 0x4cb, 3 ], + [ "RLC [HL]", 0x6cb, 3 ], + [ "RLC L", 0x5cb, 3 ], + [ "RL D", 0x12cb, 3 ], + [ "RL E", 0x13cb, 3 ], + [ "RL H", 0x14cb, 3 ], + [ "RL [HL]", 0x16cb, 3 ], + [ "RL L", 0x15cb, 3 ], + [ "RRA", 0x1f, 0 ], + [ "RR A", 0x1fcb, 3 ], + [ "RR B", 0x18cb, 3 ], + [ "RR C", 0x19cb, 3 ], + [ "RRCA", 0xf, 0 ], + [ "RRC A", 0xfcb, 3 ], + [ "RRC B", 0x8cb, 3 ], + [ "RRC C", 0x9cb, 3 ], + [ "RRC D", 0xacb, 3 ], + [ "RRC E", 0xbcb, 3 ], + [ "RRC H", 0xccb, 3 ], + [ "RRC [HL]", 0xecb, 3 ], + [ "RRC L", 0xdcb, 3 ], + [ "RR D", 0x1acb, 3 ], + [ "RR E", 0x1bcb, 3 ], + [ "RR H", 0x1ccb, 3 ], + [ "RR [HL]", 0x1ecb, 3 ], + [ "RR L", 0x1dcb, 3 ], + [ "RST $0", 0xc7, 0 ], + [ "RST $10", 0xd7, 0 ], + [ "RST $18", 0xdf, 0 ], + [ "RST $20", 0xe7, 0 ], + [ "RST $28", 0xef, 0 ], + [ "RST $30", 0xf7, 0 ], + [ "RST $38", 0xff, 0 ], + [ "RST $8", 0xcf, 0 ], + [ "SBC A", 0x9f, 0 ], + [ "SBC B", 0x98, 0 ], + [ "SBC C", 0x99, 0 ], + [ "SBC D", 0x9a, 0 ], + [ "SBC E", 0x9b, 0 ], + [ "SBC H", 0x9c, 0 ], + [ "SBC [HL]", 0x9e, 0 ], + [ "SBC L", 0x9d, 0 ], + [ "SBC x", 0xde, 1 ], + [ "SCF", 0x37, 0 ], + [ "SET 0, A", 0xc7cb, 3 ], + [ "SET 0, B", 0xc0cb, 3 ], + [ "SET 0, C", 0xc1cb, 3 ], + [ "SET 0, D", 0xc2cb, 3 ], + [ "SET 0, E", 0xc3cb, 3 ], + [ "SET 0, H", 0xc4cb, 3 ], + [ "SET 0, [HL]", 0xc6cb, 3 ], + [ "SET 0, L", 0xc5cb, 3 ], + [ "SET 1, A", 0xcfcb, 3 ], + [ "SET 1, B", 0xc8cb, 3 ], + [ "SET 1, C", 0xc9cb, 3 ], + [ "SET 1, D", 0xcacb, 3 ], + [ "SET 1, E", 0xcbcb, 3 ], + [ "SET 1, H", 0xcccb, 3 ], + [ "SET 1, [HL]", 0xcecb, 3 ], + [ "SET 1, L", 0xcdcb, 3 ], + [ "SET 2, A", 0xd7cb, 3 ], + [ "SET 2, B", 0xd0cb, 3 ], + [ "SET 2, C", 0xd1cb, 3 ], + [ "SET 2, D", 0xd2cb, 3 ], + [ "SET 2, E", 0xd3cb, 3 ], + [ "SET 2, H", 0xd4cb, 3 ], + [ "SET 2, [HL]", 0xd6cb, 3 ], + [ "SET 2, L", 0xd5cb, 3 ], + [ "SET 3, A", 0xdfcb, 3 ], + [ "SET 3, B", 0xd8cb, 3 ], + [ "SET 3, C", 0xd9cb, 3 ], + [ "SET 3, D", 0xdacb, 3 ], + [ "SET 3, E", 0xdbcb, 3 ], + [ "SET 3, H", 0xdccb, 3 ], + [ "SET 3, [HL]", 0xdecb, 3 ], + [ "SET 3, L", 0xddcb, 3 ], + [ "SET 4, A", 0xe7cb, 3 ], + [ "SET 4, B", 0xe0cb, 3 ], + [ "SET 4, C", 0xe1cb, 3 ], + [ "SET 4, D", 0xe2cb, 3 ], + [ "SET 4, E", 0xe3cb, 3 ], + [ "SET 4, H", 0xe4cb, 3 ], + [ "SET 4, [HL]", 0xe6cb, 3 ], + [ "SET 4, L", 0xe5cb, 3 ], + [ "SET 5, A", 0xefcb, 3 ], + [ "SET 5, B", 0xe8cb, 3 ], + [ "SET 5, C", 0xe9cb, 3 ], + [ "SET 5, D", 0xeacb, 3 ], + [ "SET 5, E", 0xebcb, 3 ], + [ "SET 5, H", 0xeccb, 3 ], + [ "SET 5, [HL]", 0xeecb, 3 ], + [ "SET 5, L", 0xedcb, 3 ], + [ "SET 6, A", 0xf7cb, 3 ], + [ "SET 6, B", 0xf0cb, 3 ], + [ "SET 6, C", 0xf1cb, 3 ], + [ "SET 6, D", 0xf2cb, 3 ], + [ "SET 6, E", 0xf3cb, 3 ], + [ "SET 6, H", 0xf4cb, 3 ], + [ "SET 6, [HL]", 0xf6cb, 3 ], + [ "SET 6, L", 0xf5cb, 3 ], + [ "SET 7, A", 0xffcb, 3 ], + [ "SET 7, B", 0xf8cb, 3 ], + [ "SET 7, C", 0xf9cb, 3 ], + [ "SET 7, D", 0xfacb, 3 ], + [ "SET 7, E", 0xfbcb, 3 ], + [ "SET 7, H", 0xfccb, 3 ], + [ "SET 7, [HL]", 0xfecb, 3 ], + [ "SET 7, L", 0xfdcb, 3 ], + [ "SLA A", 0x27cb, 3 ], + [ "SLA B", 0x20cb, 3 ], + [ "SLA C", 0x21cb, 3 ], + [ "SLA D", 0x22cb, 3 ], + [ "SLA E", 0x23cb, 3 ], + [ "SLA H", 0x24cb, 3 ], + [ "SLA [HL]", 0x26cb, 3 ], + [ "SLA L", 0x25cb, 3 ], + [ "SRA A", 0x2fcb, 3 ], + [ "SRA B", 0x28cb, 3 ], + [ "SRA C", 0x29cb, 3 ], + [ "SRA D", 0x2acb, 3 ], + [ "SRA E", 0x2bcb, 3 ], + [ "SRA H", 0x2ccb, 3 ], + [ "SRA [HL]", 0x2ecb, 3 ], + [ "SRA L", 0x2dcb, 3 ], + [ "SRL A", 0x3fcb, 3 ], + [ "SRL B", 0x38cb, 3 ], + [ "SRL C", 0x39cb, 3 ], + [ "SRL D", 0x3acb, 3 ], + [ "SRL E", 0x3bcb, 3 ], + [ "SRL H", 0x3ccb, 3 ], + [ "SRL [HL]", 0x3ecb, 3 ], + [ "SRL L", 0x3dcb, 3 ], + [ "STOP", 0x10, 0 ], + [ "SUB A", 0x97, 0 ], + [ "SUB B", 0x90, 0 ], + [ "SUB C", 0x91, 0 ], + [ "SUB D", 0x92, 0 ], + [ "SUB E", 0x93, 0 ], + [ "SUB H", 0x94, 0 ], + [ "SUB [HL]", 0x96, 0 ], + [ "SUB L", 0x95, 0 ], + [ "SUB x", 0xd6, 1 ], + [ "SWAP A", 0x37cb, 3 ], + [ "SWAP B", 0x30cb, 3 ], + [ "SWAP C", 0x31cb, 3 ], + [ "SWAP D", 0x32cb, 3 ], + [ "SWAP E", 0x33cb, 3 ], + [ "SWAP H", 0x34cb, 3 ], + [ "SWAP [HL]", 0x36cb, 3 ], + [ "SWAP L", 0x35cb, 3 ], + [ "XOR A", 0xaf, 0 ], + [ "XOR B", 0xa8, 0 ], + [ "XOR C", 0xa9, 0 ], + [ "XOR D", 0xaa, 0 ], + [ "XOR E", 0xab, 0 ], + [ "XOR H", 0xac, 0 ], + [ "XOR [HL]", 0xae, 0 ], + [ "XOR L", 0xad, 0 ], + [ "XOR x", 0xee, 1 ], + [ "E", 0x100, -1 ], +] + +# construct a more useful version of opt_table +opt_table = {} +for line in temp_opt_table: + opt_table[line[1]] = [line[0], line[2]] +del line + +end_08_scripts_with = [ +0xc9, #ret +0xd9, #reti +0xe9, #jp hl +#0xc3, #jp +##0x18, #jr +###0xda, 0xe9, 0xd2, 0xc2, 0xca, 0xc3, 0x38, 0x30, 0x20, 0x28, 0x18, 0xd8, 0xd0, 0xc0, 0xc8, 0xc9 +] + +discrete_jumps = [0xda, 0xe9, 0xd2, 0xc2, 0xca, 0xc3] +relative_jumps = [0x38, 0x30, 0x20, 0x28, 0x18, 0xc3, 0xda, 0xc2] +relative_unconditional_jumps = [0xc3, 0x18] + +call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd] + +all_labels = {} +def load_labels(filename="labels.json"): + """ + Load labels from specified file. + + If no filename is given, loads 'labels.json'. + """ + global all_labels + + # don't re-load labels each time + if all_labels != {}: + return + + if os.path.exists(filename): + all_labels = json.read(open(filename, "r").read()) + else: + print "You must run crystal.scan_for_predefined_labels() to create \"labels.json\". Trying..." + import crystal + crystal.scan_for_predefined_labels() + +def find_label(local_address, bank_id=0): + # keep an integer + if type(local_address) == str: + local_address = int(local_address.replace("$", "0x"), 16) + + if local_address < 0x8000: + for label_entry in all_labels: + if get_local_address(label_entry["address"]) == local_address: + if label_entry["bank"] == bank_id or label_entry["bank"] == 0: + return label_entry["label"] + if local_address in wram_labels.keys(): + return wram_labels[local_address][-1] + for constants in [gbhw_constants, hram_constants]: + if local_address in constants.keys() and local_address >= 0xff00: + return constants[local_address] + return None + +def find_address_from_label(label): + for label_entry in all_labels: + if label == label_entry["label"]: + return label_entry["address"] + return None + +def asm_label(address): + """ + Return the ASM label using the address. + """ + # why using a random value when you can use the address? + return '.asm_%x' % address + +def data_label(address): + return '.data_%x' % address + +def get_local_address(address): + bank = address / 0x4000 + return (address & 0x3fff) + 0x4000 * bool(bank) + +def get_global_address(address, bank): + if address < 0x8000: + return (address & 0x3fff) + 0x4000 * bank + return None + + return ".ASM_" + hex(address)[2:] + +def output_bank_opcodes(original_offset, max_byte_count=0x4000, include_last_address=True, stop_at=[], debug=False): + """ + Output bank opcodes. + + fs = current_address + b = bank_byte + in = input_data -- rom + bank_size = byte_count + i = offset + ad = end_address + a, oa = current_byte_number + + stop_at can be used to supply a list of addresses to not disassemble + over. This is useful if you know in advance that there are a lot of + fall-throughs. + """ + + load_labels() + load_rom() + + bank_id = original_offset / 0x4000 + if debug: print "bank id is: " + str(bank_id) + + last_hl_address = None #for when we're scanning the main map script + last_a_address = None + used_3d97 = False + + global rom + offset = original_offset + current_byte_number = 0 #start from the beginning + + #we don't actually have an end address, but we'll just say $4000 + end_address = original_offset + max_byte_count + + byte_labels = {} + data_tables = {} + + first_loop = True + output = "" + keep_reading = True + is_data = False + while offset <= end_address and keep_reading: + current_byte = rom[offset] + maybe_byte = current_byte + + # stop at any address + if not first_loop and offset in stop_at: + keep_reading = False + break + + #first check if this byte already has a label + #if it does, use the label + #if not, generate a new label + if offset in byte_labels.keys(): + line_label = byte_labels[offset]["name"] + byte_labels[offset]["usage"] += 1 + output += "\n" + else: + line_label = asm_label(offset) + byte_labels[offset] = {} + byte_labels[offset]["name"] = line_label + byte_labels[offset]["usage"] = 0 + byte_labels[offset]["definition"] = True + output += line_label + "\n" #" ; " + hex(offset) + "\n" + + #find out if there's a two byte key like this + temp_maybe = maybe_byte + temp_maybe += ( rom[offset+1] << 8) + if not is_data and temp_maybe in opt_table.keys() and rom[offset+1]!=0: + opstr = opt_table[temp_maybe][0].lower() + + if "x" in opstr: + for x in range(0, opstr.count("x")): + insertion = rom[offset + 1] + insertion = "$" + hex(insertion)[2:] + + opstr = opstr[:opstr.find("x")].lower() + insertion + opstr[opstr.find("x")+1:].lower() + + current_byte += 1 + offset += 1 + if "?" in opstr: + for y in range(0, opstr.count("?")): + byte1 = rom[offset + 1] + byte2 = rom[offset + 2] + + number = byte1 + number += byte2 << 8; + + insertion = "$%.4x" % (number) + + opstr = opstr[:opstr.find("?")].lower() + insertion + opstr[opstr.find("?")+1:].lower() + + current_byte_number += 2 + offset += 2 + + output += spacing + opstr #+ " ; " + hex(offset) + output += "\n" + + current_byte_number += 2 + offset += 2 + elif not is_data and maybe_byte in opt_table.keys(): + op_code = opt_table[maybe_byte] + op_code_type = op_code[1] + op_code_byte = maybe_byte + + #type = -1 when it's the E op + #if op_code_type != -1: + if op_code_type == 0 and rom[offset] == op_code_byte: + op_str = op_code[0].lower() + + output += spacing + op_code[0].lower() #+ " ; " + hex(offset) + output += "\n" + + offset += 1 + current_byte_number += 1 + elif op_code_type == 1 and rom[offset] == op_code_byte: + oplen = len(op_code[0]) + opstr = copy(op_code[0]) + xes = op_code[0].count("x") + include_comment = False + for x in range(0, xes): + insertion = rom[offset + 1] + insertion = "$" + hex(insertion)[2:] + + if current_byte == 0x18 or current_byte==0x20 or current_byte in relative_jumps: #jr or jr nz + #generate a label for the byte we're jumping to + target_address = offset + 2 + c_int8(rom[offset + 1]).value + if target_address in byte_labels.keys(): + byte_labels[target_address]["usage"] = 1 + byte_labels[target_address]["usage"] + line_label2 = byte_labels[target_address]["name"] + else: + line_label2 = asm_label(target_address) + byte_labels[target_address] = {} + byte_labels[target_address]["name"] = line_label2 + byte_labels[target_address]["usage"] = 1 + byte_labels[target_address]["definition"] = False + + insertion = line_label2 + if has_outstanding_labels(byte_labels) and all_outstanding_labels_are_reverse(byte_labels, offset): + include_comment = True + elif current_byte == 0x3e: + last_a_address = rom[offset + 1] + + opstr = opstr[:opstr.find("x")].lower() + insertion + opstr[opstr.find("x")+1:].lower() + + # because the $ff00+$ff syntax is silly + if opstr.count("$") > 1 and "+" in opstr: + first_orig = opstr[opstr.find("$"):opstr.find("+")] + first_val = eval(first_orig.replace("$","0x")) + + second_orig = opstr[opstr.find("+$")+1:opstr.find("]")] + second_val = eval(second_orig.replace("$","0x")) + + combined_val = "$%.4x" % (first_val + second_val) + result = find_label(combined_val, bank_id) + if result != None: + combined_val = result + + replacetron = "[%s+%s]" % (first_orig, second_orig) + opstr = opstr.replace(replacetron, "[%s]" % combined_val) + + output += spacing + opstr + if include_comment: + output += " ; " + hex(offset) + if current_byte in relative_jumps: + output += " $" + hex(rom[offset + 1])[2:] + output += "\n" + + current_byte_number += 1 + offset += 1 + insertion = "" + + current_byte_number += 1 + offset += 1 + include_comment = False + elif op_code_type == 2 and rom[offset] == op_code_byte: + oplen = len(op_code[0]) + opstr = copy(op_code[0]) + qes = op_code[0].count("?") + for x in range(0, qes): + byte1 = rom[offset + 1] + byte2 = rom[offset + 2] + + number = byte1 + number += byte2 << 8 + + if current_byte not in call_commands + discrete_jumps + relative_jumps: + pointer = get_global_address(number, bank_id) + if pointer not in data_tables.keys(): + data_tables[pointer] = {} + data_tables[pointer]['usage'] = 0 + else: + data_tables[pointer]['usage'] += 1 + + insertion = "$%.4x" % (number) + result = find_label(insertion, bank_id) + if result != None: + insertion = result + + opstr = opstr[:opstr.find("?")].lower() + insertion + opstr[opstr.find("?")+1:].lower() + output += spacing + opstr #+ " ; " + hex(offset) + output += "\n" + + current_byte_number += 2 + offset += 2 + + current_byte_number += 1 + offset += 1 + + if current_byte == 0x21: + last_hl_address = byte1 + (byte2 << 8) + if current_byte == 0xcd: + if number == 0x3d97: used_3d97 = True + + #duck out if this is jp $24d7 + if current_byte == 0xc3 or current_byte in relative_unconditional_jumps: + if current_byte == 0xc3: + if number == 0x3d97: used_3d97 = True + #if number == 0x24d7: #jp + if not has_outstanding_labels(byte_labels) or all_outstanding_labels_are_reverse(byte_labels, offset): + keep_reading = False + is_data = False + break + else: + is_data = True + else: + #if is_data and keep_reading: + output += spacing + "db $" + hex(rom[offset])[2:] #+ " ; " + hex(offset) + output += "\n" + offset += 1 + current_byte_number += 1 + if offset in byte_labels.keys(): + is_data = False + keep_reading = True + #else the while loop would have spit out the opcode + + #these two are done prior + #offset += 1 + #current_byte_number += 1 + + if not is_data and current_byte in relative_unconditional_jumps + end_08_scripts_with: + #stop reading at a jump, relative jump or return + if not has_outstanding_labels(byte_labels) or all_outstanding_labels_are_reverse(byte_labels, offset): + keep_reading = False + is_data = False #cleanup + break + elif offset not in byte_labels.keys() and offset in data_tables.keys(): + is_data = True + keep_reading = True + else: + is_data = False + keep_reading = True + output += "\n" + elif is_data and offset not in byte_labels.keys(): + is_data = True + keep_reading = True + else: + is_data = False + keep_reading = True + + if offset in data_tables.keys(): + output = output.replace('$%x' % (get_local_address(offset)), data_label(offset)) + output += data_label(offset) + '\n' + is_data = True + keep_reading = True + + first_loop = False + + #clean up unused labels + for label_line in byte_labels.keys(): + address = label_line + label_line = byte_labels[label_line] + if label_line["usage"] == 0: + output = output.replace((label_line["name"] + "\n"), "") + + #tone down excessive spacing + output = output.replace("\n\n\n","\n\n") + + #add the offset of the final location + if include_last_address: + output += "; " + hex(offset) + + return (output, offset, last_hl_address, last_a_address, used_3d97) + +def has_outstanding_labels(byte_labels): + """ + Check whether a label is used once in the asm output. + + If so, then that means it has to be called or specified later. + """ + for label_line in byte_labels.keys(): + real_line = byte_labels[label_line] + if real_line["definition"] == False: return True + return False + +def all_outstanding_labels_are_reverse(byte_labels, offset): + for label_id in byte_labels.keys(): + line = byte_labels[label_id] # label_id is also the address + if line["definition"] == False: + if not label_id < offset: return False + return True + + + +if __name__ == "__main__": + load_labels() + addr = sys.argv[1] + if ":" in addr: + addr = addr.split(":") + addr = int(addr[0], 16)*0x4000+(int(addr[1], 16)%0x4000) + else: + label_addr = find_address_from_label(addr) + if label_addr: + addr = label_addr + else: + addr = int(addr, 16) + print output_bank_opcodes(addr)[0] diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py new file mode 100644 index 0000000..c632ac0 --- /dev/null +++ b/pokemontools/gfx.py @@ -0,0 +1,1671 @@ +# -*- coding: utf-8 -*- + +import os +import sys +import png +from math import sqrt, floor, ceil + +from crystal import load_rom + +from pokemon_constants import pokemon_constants +from trainers import trainer_group_names + + +if __name__ != "__main__": + rom = load_rom() + + +def mkdir_p(path): + """ + Make a directory at a given path. + """ + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST: + pass + else: raise + + +def hex_dump(input, debug=True): + """ + Display hex dump in rows of 16 bytes. + """ + + dump = '' + output = '' + stream = '' + address = 0x00 + margin = 2 + len(hex(len(input))[2:]) + + # dump + for byte in input: + cool = hex(byte)[2:].zfill(2) + dump += cool + ' ' + if debug: stream += cool + + # convenient for testing quick edits in bgb + if debug: output += stream + '\n' + + # get dump info + bytes_per_line = 16 + chars_per_byte = 3 # '__ ' + chars_per_line = bytes_per_line * chars_per_byte + num_lines = int(ceil(float(len(dump)) / float(chars_per_line))) + + # top + # margin + for char in range(margin): + output += ' ' + + for byte in range(bytes_per_line): + output += hex(byte)[2:].zfill(2) + ' ' + output = output[:-1] # last space + + # print hex + for line in range(num_lines): + # address + output += '\n' + hex(address)[2:].zfill(margin - 2) + ': ' + # contents + start = line * chars_per_line + end = chars_per_line + start - 1 # ignore last space + output += dump[start:end] + address += 0x10 + + return output + + +def get_tiles(image): + """ + Split a 2bpp image into 8x8 tiles. + """ + tiles = [] + tile = [] + bytes_per_tile = 16 + + cur_byte = 0 + for byte in image: + # build tile + tile.append(byte) + cur_byte += 1 + # done building? + if cur_byte >= bytes_per_tile: + # push completed tile + tiles.append(tile) + tile = [] + cur_byte = 0 + return tiles + + +def connect(tiles): + """ + Combine 8x8 tiles into a 2bpp image. + """ + out = [] + for tile in tiles: + for byte in tile: + out.append(byte) + return out + + +def transpose(tiles): + """ + Transpose a tile arrangement along line y=x. + """ + + # horizontal <-> vertical + # 00 01 02 03 04 05 00 06 0c 12 18 1e + # 06 07 08 09 0a 0b 01 07 0d 13 19 1f + # 0c 0d 0e 0f 10 11 <-> 02 08 0e 14 1a 20 + # 12 13 14 15 16 17 <-> 03 09 0f 15 1b 21 + # 18 19 1a 1b 1c 1d 04 0a 10 16 1c 22 + # 1e 1f 20 21 22 23 05 0b 11 17 1d 23 + # etc + + flipped = [] + t = 0 # which tile we're on + w = int(sqrt(len(tiles))) # assume square image + for tile in tiles: + flipped.append(tiles[t]) + t += w + # end of row? + if t >= w*w: + # wrap around + t -= w*w + # next row + t += 1 + return flipped + + +def to_file(filename, data): + file = open(filename, 'wb') + for byte in data: + file.write('%c' % byte) + file.close() + + + + +# basic rundown of crystal's compression scheme: + +# a control command consists of +# the command (bits 5-7) +# and the count (bits 0-4) +# followed by additional params + +lz_lit = 0 +# print literal for [count] bytes + +lz_iter = 1 +# print one byte [count] times + +lz_alt = 2 +# print alternating bytes (2 params) for [count] bytes + +lz_zeros = 3 +# print 00 for [count] bytes + +# repeater control commands have a signed parameter used to determine the start point +# wraparound is simulated +# positive values are added to the start address of the decompressed data +# and negative values are subtracted from the current position + +lz_repeat = 4 +# print [count] bytes from decompressed data + +lz_flip = 5 +# print [count] bytes from decompressed data in bit order 01234567 + +lz_reverse = 6 +# print [count] bytes from decompressed data backwards + +lz_hi = 7 +# -used when the count exceeds 5 bits. uses a 10-bit count instead +# -bits 2-4 now contain the control code, bits 0-1 are bits 8-9 of the count +# -the following byte contains bits 0-7 of the count + +lz_end = 0xff +# if 0xff is encountered the decompression ends + +# since frontpics have animation tiles lumped onto them, +# sizes must be grabbed from base stats to know when to stop reading them + +max_length = 1 << 10 # can't go higher than 10 bits +lowmax = 1 << 5 # standard 5-bit param + + +class Compressed: + + """ + Compress 2bpp data. + """ + + def __init__(self, image=None, mode='horiz', size=None): + assert image, 'need something to compress!' + image = list(image) + self.image = image + self.pic = [] + self.animtiles = [] + + # only transpose pic (animtiles were never transposed in decompression) + if size != None: + for byte in range((size*size)*16): + self.pic += image[byte] + for byte in range(((size*size)*16),len(image)): + self.animtiles += image[byte] + else: + self.pic = image + + if mode == 'vert': + self.tiles = get_tiles(self.pic) + self.tiles = transpose(self.tiles) + self.pic = connect(self.tiles) + + self.image = self.pic + self.animtiles + + self.end = len(self.image) + + self.byte = None + self.address = 0 + + self.stream = [] + + self.zeros = [] + self.alts = [] + self.iters = [] + self.repeats = [] + self.flips = [] + self.reverses = [] + self.literals = [] + + self.output = [] + + self.compress() + + + def compress(self): + """ + Incomplete, but outputs working compressed data. + """ + + self.address = 0 + + # todo + #self.scanRepeats() + + while ( self.address < self.end ): + + #if (self.repeats): + # self.doRepeats() + + #if (self.flips): + # self.doFlips() + + #if (self.reverses): + # self.doReverses + + if (self.checkWhitespace()): + self.doLiterals() + self.doWhitespace() + + elif (self.checkIter()): + self.doLiterals() + self.doIter() + + elif (self.checkAlts()): + self.doLiterals() + self.doAlts() + + else: # doesn't fit any pattern -> literal + self.addLiteral() + self.next() + + self.doStream() + + # add any literals we've been sitting on + self.doLiterals() + + # done + self.output.append(lz_end) + + + def getCurByte(self): + if self.address < self.end: + self.byte = ord(self.image[self.address]) + else: self.byte = None + + def next(self): + self.address += 1 + self.getCurByte() + + def addLiteral(self): + self.getCurByte() + self.literals.append(self.byte) + if len(self.literals) > max_length: + raise Exception, "literals exceeded max length and the compressor didn't catch it" + elif len(self.literals) == max_length: + self.doLiterals() + + def doLiterals(self): + if len(self.literals) > lowmax: + self.output.append( (lz_hi << 5) | (lz_lit << 2) | ((len(self.literals) - 1) >> 8) ) + self.output.append( (len(self.literals) - 1) & 0xff ) + elif len(self.literals) > 0: + self.output.append( (lz_lit << 5) | (len(self.literals) - 1) ) + for byte in self.literals: + self.output.append(byte) + self.literals = [] + + def doStream(self): + for byte in self.stream: + self.output.append(byte) + self.stream = [] + + + def scanRepeats(self): + """ + Works, but doesn't do flipped/reversed streams yet. + + This takes up most of the compress time and only saves a few bytes + it might be more feasible to exclude it entirely. + """ + + self.repeats = [] + self.flips = [] + self.reverses = [] + + # make a 5-letter word list of the sequence + letters = 5 # how many bytes it costs to use a repeat over a literal + # any shorter and it's not worth the trouble + num_words = len(self.image) - letters + words = [] + for i in range(self.address,num_words): + word = [] + for j in range(letters): + word.append( ord(self.image[i+j]) ) + words.append((word, i)) + + zeros = [] + for zero in range(letters): + zeros.append( 0 ) + + # check for matches + def get_matches(): + # TODO: + # append to 3 different match lists instead of yielding to one + # + #flipped = [] + #for byte in enumerate(this[0]): + # flipped.append( sum(1<<(7-i) for i in range(8) if (this[0][byte])>>i&1) ) + #reversed = this[0][::-1] + # + for whereabout, this in enumerate(words): + for that in range(whereabout+1,len(words)): + if words[that][0] == this[0]: + if words[that][1] - this[1] >= letters: + # remove zeros + if this[0] != zeros: + yield [this[0], this[1], words[that][1]] + + matches = list(get_matches()) + + # remove more zeros + buffer = [] + for match in matches: + # count consecutive zeros in a word + num_zeros = 0 + highest = 0 + for j in range(letters): + if match[0][j] == 0: + num_zeros += 1 + else: + if highest < num_zeros: highest = num_zeros + num_zeros = 0 + if highest < 4: + # any more than 3 zeros in a row isn't worth it + # (and likely to already be accounted for) + buffer.append(match) + matches = buffer + + # combine overlapping matches + buffer = [] + for this, match in enumerate(matches): + if this < len(matches) - 1: # special case for the last match + if matches[this+1][1] <= (match[1] + len(match[0])): # check overlap + if match[1] + len(match[0]) < match[2]: + # next match now contains this match's bytes too + # this only appends the last byte (assumes overlaps are +1 + match[0].append(matches[this+1][0][-1]) + matches[this+1] = match + elif match[1] + len(match[0]) == match[2]: + # we've run into the thing we matched + buffer.append(match) + # else we've gone past it and we can ignore it + else: # no more overlaps + buffer.append(match) + else: # last match, so there's nothing to check + buffer.append(match) + matches = buffer + + # remove alternating sequences + buffer = [] + for match in matches: + for i in range(6 if letters > 6 else letters): + if match[0][i] != match[0][i&1]: + buffer.append(match) + break + matches = buffer + + self.repeats = matches + + + def doRepeats(self): + """doesn't output the right values yet""" + + unusedrepeats = [] + for repeat in self.repeats: + if self.address >= repeat[2]: + + # how far in we are + length = (len(repeat[0]) - (self.address - repeat[2])) + + # decide which side we're copying from + if (self.address - repeat[1]) <= 0x80: + self.doLiterals() + self.stream.append( (lz_repeat << 5) | length - 1 ) + + # wrong? + self.stream.append( (((self.address - repeat[1])^0xff)+1)&0xff ) + + else: + self.doLiterals() + self.stream.append( (lz_repeat << 5) | length - 1 ) + + # wrong? + self.stream.append(repeat[1]>>8) + self.stream.append(repeat[1]&0xff) + + #print hex(self.address) + ': ' + hex(len(self.output)) + ' ' + hex(length) + self.address += length + + else: unusedrepeats.append(repeat) + + self.repeats = unusedrepeats + + + def checkWhitespace(self): + self.zeros = [] + self.getCurByte() + original_address = self.address + + if ( self.byte == 0 ): + while ( self.byte == 0 ) & ( len(self.zeros) <= max_length ): + self.zeros.append(self.byte) + self.next() + if len(self.zeros) > 1: + return True + self.address = original_address + return False + + def doWhitespace(self): + if (len(self.zeros) + 1) >= lowmax: + self.stream.append( (lz_hi << 5) | (lz_zeros << 2) | ((len(self.zeros) - 1) >> 8) ) + self.stream.append( (len(self.zeros) - 1) & 0xff ) + elif len(self.zeros) > 1: + self.stream.append( lz_zeros << 5 | (len(self.zeros) - 1) ) + else: + raise Exception, "checkWhitespace() should prevent this from happening" + + + def checkAlts(self): + self.alts = [] + self.getCurByte() + original_address = self.address + num_alts = 0 + + # make sure we don't check for alts at the end of the file + if self.address+3 >= self.end: return False + + self.alts.append(self.byte) + self.alts.append(ord(self.image[self.address+1])) + + # are we onto smething? + if ( ord(self.image[self.address+2]) == self.alts[0] ): + cur_alt = 0 + while (ord(self.image[(self.address)+1]) == self.alts[num_alts&1]) & (num_alts <= max_length): + num_alts += 1 + self.next() + # include the last alternated byte + num_alts += 1 + self.address = original_address + if num_alts > lowmax: + return True + elif num_alts > 2: + return True + return False + + def doAlts(self): + original_address = self.address + self.getCurByte() + + #self.alts = [] + #num_alts = 0 + + #self.alts.append(self.byte) + #self.alts.append(ord(self.image[self.address+1])) + + #i = 0 + #while (ord(self.image[self.address+1]) == self.alts[i^1]) & (num_alts <= max_length): + # num_alts += 1 + # i ^=1 + # self.next() + ## include the last alternated byte + #num_alts += 1 + + num_alts = len(self.iters) + 1 + + if num_alts > lowmax: + self.stream.append( (lz_hi << 5) | (lz_alt << 2) | ((num_alts - 1) >> 8) ) + self.stream.append( num_alts & 0xff ) + self.stream.append( self.alts[0] ) + self.stream.append( self.alts[1] ) + elif num_alts > 2: + self.stream.append( (lz_alt << 5) | (num_alts - 1) ) + self.stream.append( self.alts[0] ) + self.stream.append( self.alts[1] ) + else: + raise Exception, "checkAlts() should prevent this from happening" + + self.address = original_address + self.address += num_alts + + + def checkIter(self): + self.iters = [] + self.getCurByte() + iter = self.byte + original_address = self.address + while (self.byte == iter) & (len(self.iters) < max_length): + self.iters.append(self.byte) + self.next() + self.address = original_address + if len(self.iters) > 3: + # 3 or fewer isn't worth the trouble and actually longer + # if part of a larger literal set + return True + + return False + + def doIter(self): + self.getCurByte() + iter = self.byte + original_address = self.address + + self.iters = [] + while (self.byte == iter) & (len(self.iters) < max_length): + self.iters.append(self.byte) + self.next() + + if (len(self.iters) - 1) >= lowmax: + self.stream.append( (lz_hi << 5) | (lz_iter << 2) | ((len(self.iters)-1) >> 8) ) + self.stream.append( (len(self.iters) - 1) & 0xff ) + self.stream.append( iter ) + elif len(self.iters) > 3: + # 3 or fewer isn't worth the trouble and actually longer + # if part of a larger literal set + self.stream.append( (lz_iter << 5) | (len(self.iters) - 1) ) + self.stream.append( iter ) + else: + self.address = original_address + raise Exception, "checkIter() should prevent this from happening" + + + + + +class Decompressed: + """ + Parse compressed 2bpp data. + + parameters: + [compressed 2bpp data] + [tile arrangement] default: 'vert' + [size of pic] default: None + [start] (optional) + + splits output into pic [size] and animation tiles if applicable + data can be fed in from rom if [start] is specified + """ + + def __init__(self, lz=None, mode=None, size=None, start=0): + # todo: play nice with Compressed + + assert lz, 'need something to compress!' + self.lz = lz + + self.byte = None + self.address = 0 + self.start = start + + self.output = [] + + self.decompress() + + debug = False + # print tuple containing start and end address + if debug: print '(' + hex(self.start) + ', ' + hex(self.start + self.address+1) + '),' + + # only transpose pic + self.pic = [] + self.animtiles = [] + + if size != None: + self.tiles = get_tiles(self.output) + self.pic = connect(self.tiles[:(size*size)]) + self.animtiles = connect(self.tiles[(size*size):]) + else: self.pic = self.output + + if mode == 'vert': + self.tiles = get_tiles(self.pic) + self.tiles = transpose(self.tiles) + self.pic = connect(self.tiles) + + self.output = self.pic + self.animtiles + + + def decompress(self): + """ + Replica of crystal's decompression. + """ + + self.output = [] + + while True: + self.getCurByte() + + if (self.byte == lz_end): + break + + self.cmd = (self.byte & 0b11100000) >> 5 + + if self.cmd == lz_hi: # 10-bit param + self.cmd = (self.byte & 0b00011100) >> 2 + self.length = (self.byte & 0b00000011) << 8 + self.next() + self.length += self.byte + 1 + else: # 5-bit param + self.length = (self.byte & 0b00011111) + 1 + + # literals + if self.cmd == lz_lit: + self.doLiteral() + elif self.cmd == lz_iter: + self.doIter() + elif self.cmd == lz_alt: + self.doAlt() + elif self.cmd == lz_zeros: + self.doZeros() + + else: # repeaters + self.next() + if self.byte > 0x7f: # negative + self.displacement = self.byte & 0x7f + self.displacement = len(self.output) - self.displacement - 1 + else: # positive + self.displacement = self.byte * 0x100 + self.next() + self.displacement += self.byte + + if self.cmd == lz_flip: + self.doFlip() + elif self.cmd == lz_reverse: + self.doReverse() + else: # lz_repeat + self.doRepeat() + + self.address += 1 + #self.next() # somewhat of a hack + + + def getCurByte(self): + self.byte = ord(self.lz[self.start+self.address]) + + def next(self): + self.address += 1 + self.getCurByte() + + def doLiteral(self): + """ + Copy 2bpp data directly. + """ + for byte in range(self.length): + self.next() + self.output.append(self.byte) + + def doIter(self): + """ + Write one byte repeatedly. + """ + self.next() + for byte in range(self.length): + self.output.append(self.byte) + + def doAlt(self): + """ + Write alternating bytes. + """ + self.alts = [] + self.next() + self.alts.append(self.byte) + self.next() + self.alts.append(self.byte) + + for byte in range(self.length): + self.output.append(self.alts[byte&1]) + + def doZeros(self): + """ + Write zeros. + """ + for byte in range(self.length): + self.output.append(0x00) + + def doFlip(self): + """ + Repeat flipped bytes from 2bpp output. + + eg 11100100 -> 00100111 + quat 3 2 1 0 -> 0 2 1 3 + """ + for byte in range(self.length): + flipped = sum(1<<(7-i) for i in range(8) if self.output[self.displacement+byte]>>i&1) + self.output.append(flipped) + + def doReverse(self): + """ + Repeat reversed bytes from 2bpp output. + """ + for byte in range(self.length): + self.output.append(self.output[self.displacement-byte]) + + def doRepeat(self): + """ + Repeat bytes from 2bpp output. + """ + for byte in range(self.length): + self.output.append(self.output[self.displacement+byte]) + + + +sizes = [ + 5, 6, 7, 5, 6, 7, 5, 6, 7, 5, 5, 7, 5, 5, 7, 5, + 6, 7, 5, 6, 5, 7, 5, 7, 5, 7, 5, 6, 5, 6, 7, 5, + 6, 7, 5, 6, 6, 7, 5, 6, 5, 7, 5, 6, 7, 5, 7, 5, + 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 6, 7, 5, 6, + 7, 5, 7, 7, 5, 6, 7, 5, 6, 5, 6, 6, 6, 7, 5, 7, + 5, 6, 6, 5, 7, 6, 7, 5, 7, 5, 7, 7, 6, 6, 7, 6, + 7, 5, 7, 5, 5, 7, 7, 5, 6, 7, 6, 7, 6, 7, 7, 7, + 6, 6, 7, 5, 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 7, + 6, 7, 7, 5, 5, 6, 6, 6, 6, 5, 6, 5, 6, 7, 7, 7, + 7, 7, 5, 6, 7, 7, 5, 5, 6, 7, 5, 6, 7, 5, 6, 7, + 6, 6, 5, 7, 6, 6, 5, 7, 7, 6, 6, 5, 5, 5, 5, 7, + 5, 6, 5, 6, 7, 7, 5, 7, 6, 7, 5, 6, 7, 5, 5, 6, + 6, 5, 6, 6, 6, 6, 7, 6, 5, 6, 7, 5, 7, 6, 6, 7, + 6, 6, 5, 7, 5, 6, 6, 5, 7, 5, 6, 5, 6, 6, 5, 6, + 6, 7, 7, 6, 7, 7, 5, 7, 6, 7, 7, 5, 7, 5, 6, 6, + 6, 7, 7, 7, 7, 5, 6, 7, 7, 7, 5, +] + +def make_sizes(): + """ + Front pics have specified sizes. + """ + top = 251 + base_stats = 0x51424 + # print monster sizes + address = base_stats + 0x11 + + output = '' + + for id in range(top): + size = (ord(rom[address])) & 0x0f + if id % 16 == 0: output += '\n\t' + output += str(size) + ', ' + address += 0x20 + + print output + + + +fxs = 0xcfcf6 +num_fx = 40 + +def decompress_fx_by_id(id): + address = fxs + id*4 # len_fxptr + # get size + num_tiles = ord(rom[address]) # # tiles + # get pointer + bank = ord(rom[address+1]) + address = (ord(rom[address+3]) << 8) + ord(rom[address+2]) + address = (bank * 0x4000) + (address & 0x3fff) + # decompress + fx = Decompressed(rom, 'horiz', num_tiles, address) + return fx + +def decompress_fx(): + for id in range(num_fx): + fx = decompress_fx_by_id(id) + filename = '../gfx/fx/' + str(id).zfill(3) + '.2bpp' # ../gfx/fx/039.2bpp + to_file(filename, fx.pic) + + +num_pics = 2 +front = 0 +back = 1 + +monsters = 0x120000 +num_monsters = 251 + +unowns = 0x124000 +num_unowns = 26 +unown_dex = 201 + +def decompress_monster_by_id(id=0, type=front): + # no unowns here + if id + 1 == unown_dex: return None + # get size + if type == front: + size = sizes[id] + else: size = None + # get pointer + address = monsters + (id*2 + type)*3 # bank, address + bank = ord(rom[address]) + 0x36 # crystal + address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) + address = (bank * 0x4000) + (address & 0x3fff) + # decompress + monster = Decompressed(rom, 'vert', size, address) + return monster + +def decompress_monsters(type=front): + for id in range(num_monsters): + # decompress + monster = decompress_monster_by_id(id, type) + if monster != None: # no unowns here + if not type: # front + filename = 'front.2bpp' + folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' + to_file(folder+filename, monster.pic) + filename = 'tiles.2bpp' + folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' + to_file(folder+filename, monster.animtiles) + else: # back + filename = 'back.2bpp' + folder = '../gfx/pics/' + str(id+1).zfill(3) + '/' + to_file(folder+filename, monster.pic) + + +def decompress_unown_by_id(letter, type=front): + # get size + if type == front: + size = sizes[unown_dex-1] + else: size = None + # get pointer + address = unowns + (letter*2 + type)*3 # bank, address + bank = ord(rom[address]) + 0x36 # crystal + address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) + address = (bank * 0x4000) + (address & 0x3fff) + # decompress + unown = Decompressed(rom, 'vert', size, address) + return unown + +def decompress_unowns(type=front): + for letter in range(num_unowns): + # decompress + unown = decompress_unown_by_id(letter, type) + + if not type: # front + filename = 'front.2bpp' + folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/' + to_file(folder+filename, unown.pic) + filename = 'tiles.2bpp' + folder = '../gfx/anim/' + to_file(folder+filename, unown.animtiles) + else: # back + filename = 'back.2bpp' + folder = '../gfx/pics/' + str(unown_dex).zfill(3) + chr(ord('a') + letter) + '/' + to_file(folder+filename, unown.pic) + + +trainers = 0x128000 +num_trainers = 67 + +def decompress_trainer_by_id(id): + # get pointer + address = trainers + id*3 # bank, address + bank = ord(rom[address]) + 0x36 # crystal + address = (ord(rom[address+2]) << 8) + ord(rom[address+1]) + address = (bank * 0x4000) + (address & 0x3fff) + # decompress + trainer = Decompressed(rom, 'vert', None, address) + return trainer + +def decompress_trainers(): + for id in range(num_trainers): + # decompress + trainer = decompress_trainer_by_id(id) + filename = '../gfx/trainers/' + str(id).zfill(3) + '.2bpp' # ../gfx/trainers/066.2bpp + to_file(filename, trainer.pic) + + +# in order of use (sans repeats) +intro_gfx = [ + ('logo', 0x109407), + ('001', 0xE641D), # tilemap + ('unowns', 0xE5F5D), + ('pulse', 0xE634D), + ('002', 0xE63DD), # tilemap + ('003', 0xE5ECD), # tilemap + ('background', 0xE5C7D), + ('004', 0xE5E6D), # tilemap + ('005', 0xE647D), # tilemap + ('006', 0xE642D), # tilemap + ('pichu_wooper', 0xE592D), + ('suicune_run', 0xE555D), + ('007', 0xE655D), # tilemap + ('008', 0xE649D), # tilemap + ('009', 0xE76AD), # tilemap + ('suicune_jump', 0xE6DED), + ('unown_back', 0xE785D), + ('010', 0xE764D), # tilemap + ('011', 0xE6D0D), # tilemap + ('suicune_close', 0xE681D), + ('012', 0xE6C3D), # tilemap + ('013', 0xE778D), # tilemap + ('suicune_back', 0xE72AD), + ('014', 0xE76BD), # tilemap + ('015', 0xE676D), # tilemap + ('crystal_unowns', 0xE662D), + ('017', 0xE672D), # tilemap +] + +def decompress_intro(): + for name, address in intro_gfx: + filename = '../gfx/intro/' + name + '.2bpp' + gfx = Decompressed( rom, 'horiz', None, address ) + to_file(filename, gfx.output) + + +title_gfx = [ + ('suicune', 0x10EF46), + ('logo', 0x10F326), + ('crystal', 0x10FCEE), +] + +def decompress_title(): + for name, address in title_gfx: + filename = '../gfx/title/' + name + '.2bpp' + gfx = Decompressed( rom, 'horiz', None, address ) + to_file(filename, gfx.output) + +def decompress_tilesets(): + tileset_headers = 0x4d596 + len_tileset = 15 + num_tilesets = 0x25 + for tileset in range(num_tilesets): + ptr = tileset*len_tileset + tileset_headers + address = (ord(rom[ptr])*0x4000) + (((ord(rom[ptr+1]))+ord(rom[ptr+2])*0x100)&0x3fff) + tiles = Decompressed( rom, 'horiz', None, address ) + filename = '../gfx/tilesets/'+str(tileset).zfill(2)+'.2bpp' + to_file( filename, tiles.output ) + #print '(' + hex(address) + ', '+ hex(address+tiles.address+1) + '),' + +misc = [ + ('player', 0x2BA1A, 'vert'), + ('dude', 0x2BBAA, 'vert'), + ('town_map', 0xF8BA0, 'horiz'), + ('pokegear', 0x1DE2E4, 'horiz'), + ('pokegear_sprites', 0x914DD, 'horiz'), +] +def decompress_misc(): + for name, address, mode in misc: + filename = '../gfx/misc/' + name + '.2bpp' + gfx = Decompressed( rom, mode, None, address ) + to_file(filename, gfx.output) + +def decompress_all(debug=False): + """ + Decompress all known compressed data in baserom. + """ + + if debug: print 'fronts' + decompress_monsters(front) + if debug: print 'backs' + decompress_monsters(back) + if debug: print 'unown fronts' + decompress_unowns(front) + if debug: print 'unown backs' + decompress_unowns(back) + + if debug: print 'trainers' + decompress_trainers() + + if debug: print 'fx' + decompress_fx() + + if debug: print 'intro' + decompress_intro() + + if debug: print 'title' + decompress_title() + + if debug: print 'tilesets' + decompress_tilesets() + + if debug: print 'misc' + decompress_misc() + + return + + +def decompress_from_address(address, mode='horiz', filename='de.2bpp', size=None): + """ + Write decompressed data from an address to a 2bpp file. + """ + image = Decompressed(rom, mode, size, address) + to_file(filename, image.pic) + + +def decompress_file(filein, fileout, mode='horiz', size=None): + f = open(filein, 'rb') + image = f.read() + f.close() + + de = Decompressed(image, mode, size) + + to_file(fileout, de.pic) + + +def compress_file(filein, fileout, mode='horiz'): + f = open(filein, 'rb') + image = f.read() + f.close() + + lz = Compressed(image, mode) + + to_file(fileout, lz.output) + + + + +def compress_monster_frontpic(id, fileout): + mode = 'vert' + + fpic = '../gfx/pics/' + str(id).zfill(3) + '/front.2bpp' + fanim = '../gfx/pics/' + str(id).zfill(3) + '/tiles.2bpp' + + pic = open(fpic, 'rb').read() + anim = open(fanim, 'rb').read() + image = pic + anim + + lz = Compressed(image, mode, sizes[id-1]) + + out = '../gfx/pics/' + str(id).zfill(3) + '/front.lz' + + to_file(out, lz.output) + + + +def get_uncompressed_gfx(start, num_tiles, filename): + """ + Grab tiles directly from rom and write to file. + """ + bytes_per_tile = 0x10 + length = num_tiles*bytes_per_tile + end = start + length + rom = load_rom() + image = [] + for address in range(start,end): + image.append(ord(rom[address])) + to_file(filename, image) + + + +def hex_to_rgb(word): + red = word & 0b11111 + word >>= 5 + green = word & 0b11111 + word >>= 5 + blue = word & 0b11111 + return (red, green, blue) + +def grab_palettes(address, length=0x80): + output = '' + for word in range(length/2): + color = ord(rom[address+1])*0x100 + ord(rom[address]) + address += 2 + color = hex_to_rgb(color) + red = str(color[0]).zfill(2) + green = str(color[1]).zfill(2) + blue = str(color[2]).zfill(2) + output += '\tRGB '+red+', '+green+', '+blue + output += '\n' + return output + + + + + + + +def dump_monster_pals(): + rom = load_rom() + + pals = 0xa8d6 + pal_length = 0x4 + for mon in range(251): + + name = pokemon_constants[mon+1].title().replace('_','') + num = str(mon+1).zfill(3) + dir = 'gfx/pics/'+num+'/' + + address = pals + mon*pal_length*2 + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(ord(rom[address])) + address += 1 + + filename = 'normal.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (15 - len(name)) + #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(ord(rom[address])) + address += 1 + + filename = 'shiny.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (10 - len(name)) + #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' + + +def dump_trainer_pals(): + rom = load_rom() + + pals = 0xb0d2 + pal_length = 0x4 + for trainer in range(67): + + name = trainer_group_names[trainer+1]['constant'].title().replace('_','') + num = str(trainer).zfill(3) + dir = 'gfx/trainers/' + + address = pals + trainer*pal_length + + pal_data = [] + for byte in range(pal_length): + pal_data.append(ord(rom[address])) + address += 1 + + filename = num+'.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (12 - len(name)) + print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"' + + + +def flatten(planar): + """ + Flatten planar 2bpp image data into a quaternary pixel map. + """ + strips = [] + for pair in range(len(planar)/2): + bottom = ord(planar[(pair*2) ]) + top = ord(planar[(pair*2)+1]) + strip = [] + for i in range(7,-1,-1): + color = ((bottom >> i) & 1) + (((top >> i-1) if i > 0 else (top << 1-i)) & 2) + strip.append(color) + strips += strip + return strips + + +def to_lines(image, width): + """ + Convert a tiled quaternary pixel map to lines of quaternary pixels. + """ + + tile = 8 * 8 + + # so we know how many strips of 8px we're putting into a line + num_columns = width / 8 + # number of lines + height = len(image) / width + + lines = [] + for cur_line in range(height): + tile_row = int(cur_line / 8) + line = [] + for column in range(num_columns): + anchor = num_columns*tile_row*tile + column*tile + (cur_line%8)*8 + line += image[anchor:anchor+8] + lines.append(line) + return lines + +def dmg2rgb(word): + red = word & 0b11111 + word >>= 5 + green = word & 0b11111 + word >>= 5 + blue = word & 0b11111 + alpha = 255 + return ((red<<3)+0b100, (green<<3)+0b100, (blue<<3)+0b100, alpha) + +def rgb_to_dmg(color): + word = (color['r'] / 8) + word += (color['g'] / 8) << 5 + word += (color['b'] / 8) << 10 + return word + + +def png_pal(filename): + palette = [] + with open(filename, 'rb') as pal_data: + words = pal_data.read() + dmg_pals = [] + for word in range(len(words)/2): + dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) + white = (255,255,255,255) + black = (000,000,000,255) + for word in dmg_pals: palette += [dmg2rgb(word)] + if white not in dmg_pals and len(palette) < 4: palette = [white] + palette + if black not in dmg_pals and len(palette) < 4: palette += [black] + return palette + + +def to_png(filein, fileout=None, pal_file=None, height=None, width=None): + """ + Take a planar 2bpp graphics file and converts it to png. + """ + + if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.png' + + image = open(filein, 'rb').read() + + num_pixels = len(image) * 4 + + if num_pixels == 0: return 'empty image!' + + + # unless the pic is square, at least one dimension should be given + + if width == None and height == None: + width = int(sqrt(num_pixels)) + height = width + + elif height == None: + height = num_pixels / width + + elif width == None: + width = num_pixels / height + + + # but try to see if it can be made rectangular + + if width * height != num_pixels: + + # look for possible combos of width/height that would form a rectangle + matches = [] + + # this is pretty inefficient, and there is probably a simpler way + for width in range(8,256+1,8): # we only want dimensions that fit in tiles + height = num_pixels / width + if height % 8 == 0: + matches.append((width, height)) + + # go for the most square image + width, height = sorted(matches, key=lambda (x,y): x+y)[0] # favors height + + + # if it can't, the only option is a width of 1 tile + + if width * height != num_pixels: + width = 8 + height = num_pixels / width + + + # if this still isn't rectangular, then the image isn't made of tiles + + # for now we'll just spit out a warning + if width * height != num_pixels: + print 'Warning! ' + fileout + ' is ' + width + 'x' + height + '(' + width*height + ' pixels),\n' +\ + 'but ' + filein + ' is ' + num_pixels + ' pixels!' + + + # map it out + + lines = to_lines(flatten(image), width) + + if pal_file == None: + if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): + pal_file = os.path.splitext(fileout)[0]+'.pal' + + if pal_file == None: + palette = None + greyscale = True + bitdepth = 2 + inverse = { 0:3, 1:2, 2:1, 3:0 } + map = [[inverse[pixel] for pixel in line] for line in lines] + + else: # gbc color + palette = png_pal(pal_file) + greyscale = False + bitdepth = 8 + map = [[pixel for pixel in line] for line in lines] + + + w = png.Writer(width, height, palette=palette, compression = 9, greyscale = greyscale, bitdepth = bitdepth) + with open(fileout, 'wb') as file: + w.write(file, map) + + + + +def to_2bpp(filein, fileout=None, palout=None): + """ + Take a png and converts it to planar 2bpp. + """ + + if fileout == None: fileout = '.'.join(filein.split('.')[:-1]) + '.2bpp' + + with open(filein, 'rb') as file: + + r = png.Reader(file) + info = r.asRGBA8() + + width = info[0] + height = info[1] + + rgba = list(info[2]) + greyscale = info[3]['greyscale'] + + + padding = { 'left': 0, + 'right': 0, + 'top': 0, + 'bottom': 0, } + #if width % 8 != 0: + # padding['left'] = int(ceil((width / 8 + 8 - width) / 2)) + # padding['right'] = int(floor((width / 8 + 8 - width) / 2)) + #if height % 8 != 0: + # padding['top'] = int(ceil((height / 8 + 8 - height) / 2)) + # padding['bottom'] = int(floor((height / 8 + 8 - height) / 2)) + + + # turn the flat values into something more workable + + pixel_length = 4 # rgba + image = [] + + # while we're at it, let's size up the palette + + palette = [] + + for line in rgba: + newline = [] + for pixel in range(len(line)/pixel_length): + i = pixel * pixel_length + color = { 'r': line[i ], + 'g': line[i+1], + 'b': line[i+2], + 'a': line[i+3], } + newline += [color] + if color not in palette: palette += [color] + image.append(newline) + + # pad out any small palettes + hues = { + 'white': { 'r': 0xff, 'g': 0xff, 'b': 0xff, 'a': 0xff }, + 'black': { 'r': 0x00, 'g': 0x00, 'b': 0x00, 'a': 0xff }, + 'grey': { 'r': 0x55, 'g': 0x55, 'b': 0x55, 'a': 0xff }, + 'gray': { 'r': 0xaa, 'g': 0xaa, 'b': 0xaa, 'a': 0xff }, + } + while len(palette) < 4: + for hue in hues.values(): + if not any(color is hue for color in palette): + palette += [hue] + if len(palette) >= 4: break + + assert len(palette) <= 4, 'Palette should be 4 colors, is really ' + str(len(palette)) + + # sort by luminance + def luminance(color): + # this is actually in reverse, thanks to dmg/cgb palette ordering + rough = { 'r': 4.7, + 'g': 1.4, + 'b': 13.8, } + return sum(color[key] * -rough[key] for key in rough.keys()) + palette = sorted(palette, key=luminance) + + # spit out a new .pal file + # disable this if it causes problems with paletteless images + if palout == None: + if os.path.exists(os.path.splitext(fileout)[0]+'.pal'): + palout = os.path.splitext(fileout)[0]+'.pal' + if palout != None: + output = [] + for color in palette: + word = rgb_to_dmg(color) + output += [word & 0xff] + output += [word >> 8] + to_file(palout, output) + + # create a new map of quaternary color ids + map = [] + if padding['top']: map += [0] * (width + padding['left'] + padding['right']) * padding['top'] + for line in image: + if padding['left']: map += [0] * padding['left'] + for color in line: + map.append(palette.index(color)) + if padding['right']: map += [0] * padding['right'] + if padding['bottom']: map += [0] * (width + padding['left'] + padding['right']) * padding['bottom'] + + # split it into strips of 8, and make them planar + num_columns = width / 8 + num_rows = height / 8 + tile = 8 * 8 + image = [] + for row in range(num_rows): + for column in range(num_columns): + for strip in range(tile / 8): + anchor = row*num_columns*tile + column*tile/8 + strip*width + line = map[anchor:anchor+8] + bottom = 0 + top = 0 + for bit, quad in enumerate(line): + bottom += (quad & 1) << (7-bit) + top += ((quad & 2) >> 1) << (7-bit) + image.append(bottom) + image.append(top) + + to_file(fileout, image) + + +def png_to_lz(filein): + + name = os.path.splitext(filein)[0] + + to_2bpp(filein) + image = open(name+'.2bpp', 'rb').read() + to_file(name+'.lz', Compressed(image).output) + + + + +def mass_to_png(debug=False): + # greyscale + for root, dirs, files in os.walk('../gfx/'): + for name in files: + if debug: print os.path.splitext(name), os.path.join(root, name) + if os.path.splitext(name)[1] == '.2bpp': + to_png(os.path.join(root, name)) + +def mass_to_colored_png(debug=False): + # greyscale, unless a palette is detected + for root, dirs, files in os.walk('../gfx/'): + if 'pics' not in root and 'trainers' not in root: + for name in files: + if debug: print os.path.splitext(name), os.path.join(root, name) + if os.path.splitext(name)[1] == '.2bpp': + to_png(os.path.join(root, name)) + os.utime(os.path.join(root, name), None) + + # only monster and trainer pics for now + for root, dirs, files in os.walk('../gfx/pics/'): + for name in files: + if debug: print os.path.splitext(name), os.path.join(root, name) + if os.path.splitext(name)[1] == '.2bpp': + if 'normal.pal' in files: + to_png(os.path.join(root, name), None, os.path.join(root, 'normal.pal')) + else: + to_png(os.path.join(root, name)) + os.utime(os.path.join(root, name), None) + + for root, dirs, files in os.walk('../gfx/trainers/'): + for name in files: + if debug: print os.path.splitext(name), os.path.join(root, name) + if os.path.splitext(name)[1] == '.2bpp': + to_png(os.path.join(root, name)) + os.utime(os.path.join(root, name), None) + + +def mass_decompress(debug=False): + for root, dirs, files in os.walk('../gfx/'): + for name in files: + if 'lz' in name: + if '/pics' in root: + if 'front' in name: + id = root.split('pics/')[1][:3] + if id != 'egg': + with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert', sizes[int(id)-1]) + else: + with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert', 4) + to_file(os.path.join(root, 'front.2bpp'), de.pic) + to_file(os.path.join(root, 'tiles.2bpp'), de.animtiles) + elif 'back' in name: + with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert') + to_file(os.path.join(root, 'back.2bpp'), de.output) + elif '/trainers' in root or '/fx' in root: + with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read(), 'vert') + to_file(os.path.join(root, os.path.splitext(name)[0]+'.2bpp'), de.output) + else: + with open(os.path.join(root, name), 'rb') as lz: de = Decompressed(lz.read()) + to_file(os.path.join(root, os.path.splitext(name)[0]+'.2bpp'), de.output) + os.utime(os.path.join(root, name), None) + +def append_terminator_to_lzs(directory): + # fix lzs that were extracted with a missing terminator + for root, dirs, files in os.walk(directory): + for file in files: + if '.lz' in file: + data = open(root+file,'rb').read() + if data[-1] != chr(0xff): + data += chr(0xff) + new = open(root+file,'wb') + new.write(data) + new.close() + +def lz_to_png_by_file(filename): + """ + Convert a lz file to png. Dump a 2bpp file too. + """ + assert filename[-3:] == ".lz" + lz_data = open(filename, "rb").read() + bpp = Decompressed(lz_data).output + bpp_filename = filename.replace(".lz", ".2bpp") + to_file(bpp_filename, bpp) + to_png(bpp_filename) + +def dump_tileset_pngs(): + """ + Convert .lz format tilesets into .png format tilesets. + + Also, leaves a bunch of wonderful .2bpp files everywhere for your amusement. + """ + for tileset_id in range(37): + tileset_filename = "../gfx/tilesets/" + str(tileset_id).zfill(2) + ".lz" + lz_to_png_by_file(tileset_filename) + +def decompress_frontpic(lz_file): + """ + Convert the pic portion of front.lz to front.2bpp + """ + lz = open(lz_file, 'rb').read() + to_file(Decompressed(lz).pic, os.path.splitext(filein)[0] + '.2bpp') + +def decompress_frontpic_anim(lz_file): + """ + Convert the animation tile portion of front.lz to tiles.2bpp + """ + lz = open(lz_file, 'rb').read() + to_file(Decompressed(lz).animtiles, 'tiles.2bpp') + +def expand_pic_palettes(): + """ + Add white and black to palette files with fewer than 4 colors. + + Pokemon Crystal only defines two colors for a pic palette to + save space, filling in black/white at runtime. + Instead of managing palette files of varying length, black + and white are added to pic palettes and excluded from incbins. + """ + for root, dirs, files in os.walk('../gfx/'): + if 'gfx/pics' in root or 'gfx/trainers' in root: + for name in files: + if os.path.splitext(name)[1] == '.pal': + filename = os.path.join(root, name) + palette = bytearray(open(filename, 'rb').read()) + w = bytearray([0xff, 0x7f]) + b = bytearray([0x00, 0x00]) + if len(palette) == 4: + with open(filename, 'wb') as out: + out.write(w + palette + b) + +if __name__ == "__main__": + debug = False + + argv = [None] * 5 + for i, arg in enumerate(sys.argv): + argv[i] = arg + + if argv[1] == 'dump-pngs': + mass_to_colored_png() + + elif argv[1] == 'mass-decompress': + mass_decompress() + + elif argv[1] == 'front-to-2bpp': + decompress_frontpic(argv[2]) + + elif argv[1] == 'anim-from-front': + decompress_frontpic_anim(argv[2]) + + elif argv[1] == 'lz-to-2bpp': + name = os.path.splitext(argv[3])[0] + lz = open(name+'.lz', 'rb').read() + if argv[2] == '--vert': + to_file(name+'.2bpp', Decompressed(lz, 'vert').output) + else: + to_file(name+'.2bpp', Decompressed(lz).output) + + elif argv[1] == 'lz-to-png': + if argv[2] == '--vert': + name = os.path.splitext(argv[3])[0] + lz = open(name+'.lz', 'rb').read() + to_file(name+'.2bpp', Decompressed(lz, 'vert').output) + pic = open(name+'.2bpp', 'rb').read() + to_file(name+'.png', to_png(pic)) + else: + lz_to_png_by_file(argv[2]) + + elif argv[1] == 'png-to-lz': + # python gfx.py png-to-lz [--front anim(2bpp) | --vert] [png] + if argv[2] == '--front': + # front.2bpp and tiles.2bpp are combined before compression, + # so we have to pass in the anim file and pic size + name = os.path.splitext(argv[4])[0] + to_2bpp(name+'.png', name+'.2bpp') + pic = open(name+'.2bpp', 'rb').read() + anim = open(argv[3], 'rb').read() + size = int(sqrt(len(pic)/16)) # assume square pic + to_file(name+'.lz', Compressed(pic + anim, 'vert', size).output) + elif argv[2] == '--vert': + name = os.path.splitext(argv[3])[0] + to_2bpp(name+'.png', name+'.2bpp') + pic = open(name+'.2bpp', 'rb').read() + to_file(name+'.lz', Compressed(pic, 'vert').output) + else: + png_to_lz(argv[2]) + + elif argv[1] == 'png-to-2bpp': + to_2bpp(argv[2]) + + elif argv[1] == '2bpp-to-lz': + if argv[2] == '--vert': + filein = argv[3] + fileout = argv[4] + compress_file(filein, fileout, 'vert') + else: + filein = argv[2] + fileout = argv[3] + compress_file(filein, fileout) + + elif argv[1] == '2bpp-to-png': + to_png(argv[2]) diff --git a/pokemontools/graph.py b/pokemontools/graph.py new file mode 100644 index 0000000..47087e5 --- /dev/null +++ b/pokemontools/graph.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +import networkx as nx + +from romstr import ( + RomStr, + relative_jumps, + call_commands, + relative_unconditional_jumps, +) + +class RomGraph(nx.DiGraph): + """ + Graphs various functions pointing to each other. + + TODO: Bank switches are nasty. They should be detected. Otherwise, + functions will point to non-functions within the same bank. Another way + to detect bankswitches is retroactively. By disassembling one function + after another within the function banks, it can be roughly assumed that + anything pointing to something else (within the same bank) is really + actually a bankswitch. An even better method to handle bankswitches + would be to just detect those situations in the asm (but I presently + forget how bankswitches are performed in pokecrystal). + """ + + # some areas shouldn't be parsed as asm + exclusions = [] + + # where is the first function located? + start_address = 0x150 + + # and where is a good place to stop? + end_address = 0x4000 * 0x03 # only do the first bank? sure.. + + # where is the rom stored? + rompath = "../baserom.gbc" + + def __init__(self, rom=None, **kwargs): + """ + Loads and parses the ROM into a function graph. + """ + # continue the initialization + nx.DiGraph.__init__(self, **kwargs) + + # load the graph + if rom == None: + self.load_rom() + else: + self.rom = rom + + # start parsing the ROM + self.parse() + + def load_rom(self): + """ + Creates a RomStr from rompath. + """ + file_handler = open(self.rompath, "r") + self.rom = RomStr(file_handler.read()) + file_handler.close() + + def parse(self): + """ + Parses the ROM starting with the first function address. Each + function is disassembled and parsed to find where else it leads to. + """ + functions = {} + + address = self.start_address + + other_addresses = set() + + count = 0 + + while True: + if count > 3000: + break + + if address < self.end_address and (address not in functions.keys()) and address >= 0x150: + # address is okay to parse at, keep going + pass + elif len(other_addresses) > 0: + # parse some other address possibly in a remote bank + address = other_addresses.pop() + else: + # no more addresses detected- exit loop + break + + # parse the asm + func = self.rom.to_asm(address) + + # check if there are any nops (probably not a function) + nops = 0 + for (id, command) in func.asm_commands.items(): + if command.has_key("id") and command["id"] == 0x0: + nops += 1 + + # skip this function + if nops > 1: + address = 0 + continue + + # store this parsed function + functions[address] = func + + # where does this function jump to? + used_addresses = set(func.used_addresses()) + + # add this information to the graph + for used_address in used_addresses: + # only add this remote address if it's not yet parsed + if used_address not in functions.keys(): + other_addresses.update([used_address]) + + # add this other address to the graph + if used_address > 100: + self.add_node(used_address) + + # add this as an edge between the two nodes + self.add_edge(address, used_address) + + # setup the next function to be parsed + address = func.last_address + + count += 1 + + self.functions = functions + + def pretty_printer(self): + """ + Shows some text output describing which nodes point to which other + nodes. + """ + print self.edges() + + def to_d3(self): + """ + Exports to d3.js because we're gangster like that. + """ + import networkx.readwrite.json_graph as json_graph + content = json_graph.dumps(self) + fh = open("crystal/crystal.json", "w") + fh.write(content) + fh.close() + + def to_gephi(self): + """ + Generates a gexf file. + """ + nx.write_gexf(self, "graph.gexf") + +class RedGraph(RomGraph): + """ + Not implemented. Go away. + """ + + rompath = "../pokered-baserom.gbc" + +class CryGraph(RomGraph): + exclusions = [ + [0x000, 0x149], + ] + + rompath = "../baserom.gbc" + +if __name__ == "__main__": + crygraph = CryGraph() + crygraph.pretty_printer() + crygraph.to_gephi() diff --git a/pokemontools/interval_map.py b/pokemontools/interval_map.py new file mode 100644 index 0000000..daf22cd --- /dev/null +++ b/pokemontools/interval_map.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +from bisect import bisect_left, bisect_right +from itertools import izip + +class IntervalMap(object): + """ + This class maps a set of intervals to a set of values. + + >>> i = IntervalMap() + >>> i[0:5] = "hello world" + >>> i[6:10] = "hello cruel world" + >>> print i[4] + "hello world" + """ + + def __init__(self): + """initializes an empty IntervalMap""" + self._bounds = [] + self._items = [] + self._upperitem = None + + def __setitem__(self, _slice, _value): + """sets an interval mapping""" + assert isinstance(_slice, slice), 'The key must be a slice object' + + if _slice.start is None: + start_point = -1 + else: + start_point = bisect_left(self._bounds, _slice.start) + + if _slice.stop is None: + end_point = -1 + else: + end_point = bisect_left(self._bounds, _slice.stop) + + if start_point>=0: + if start_point < len(self._bounds) and self._bounds[start_point]<_slice.start: + start_point += 1 + + if end_point>=0: + self._bounds[start_point:end_point] = [_slice.start, _slice.stop] + if start_point < len(self._items): + self._items[start_point:end_point] = [self._items[start_point], _value] + else: + self._items[start_point:end_point] = [self._upperitem, _value] + else: + self._bounds[start_point:] = [_slice.start] + if start_point < len(self._items): + self._items[start_point:] = [self._items[start_point], _value] + else: + self._items[start_point:] = [self._upperitem] + self._upperitem = _value + else: + if end_point>=0: + self._bounds[:end_point] = [_slice.stop] + self._items[:end_point] = [_value] + else: + self._bounds[:] = [] + self._items[:] = [] + self._upperitem = _value + + def __getitem__(self,_point): + """gets a value from the mapping""" + assert not isinstance(_point, slice), 'The key cannot be a slice object' + + index = bisect_right(self._bounds, _point) + if index < len(self._bounds): + return self._items[index] + else: + return self._upperitem + + def items(self): + """returns an iterator with each item being + ((low_bound, high_bound), value) + these items are returned in order""" + previous_bound = None + for (b, v) in izip(self._bounds, self._items): + if v is not None: + yield (previous_bound, b), v + previous_bound = b + if self._upperitem is not None: + yield (previous_bound, None), self._upperitem + + def values(self): + """returns an iterator with each item being a stored value + the items are returned in order""" + for v in self._items: + if v is not None: + yield v + if self._upperitem is not None: + yield self._upperitem + + def __repr__(self): + s = [] + for b,v in self.items(): + if v is not None: + s.append('[%r, %r] => %r'%( + b[0], + b[1], + v + )) + return '{'+', '.join(s)+'}' diff --git a/pokemontools/item_constants.py b/pokemontools/item_constants.py new file mode 100644 index 0000000..3692c92 --- /dev/null +++ b/pokemontools/item_constants.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- + +item_constants = { +1: 'MASTER_BALL', +2: 'ULTRA_BALL', +3: 'BRIGHTPOWDER', +4: 'GREAT_BALL', +5: 'POKE_BALL', +7: 'BICYCLE', +8: 'MOON_STONE', +9: 'ANTIDOTE', +10: 'BURN_HEAL', +11: 'ICE_HEAL', +12: 'AWAKENING', +13: 'PARLYZ_HEAL', +14: 'FULL_RESTORE', +15: 'MAX_POTION', +16: 'HYPER_POTION', +17: 'SUPER_POTION', +18: 'POTION', +19: 'ESCAPE_ROPE', +20: 'REPEL', +21: 'MAX_ELIXER', +22: 'FIRE_STONE', +23: 'THUNDERSTONE', +24: 'WATER_STONE', +26: 'HP_UP', +27: 'PROTEIN', +28: 'IRON', +29: 'CARBOS', +30: 'LUCKY_PUNCH', +31: 'CALCIUM', +32: 'RARE_CANDY', +33: 'X_ACCURACY', +34: 'LEAF_STONE', +35: 'METAL_POWDER', +36: 'NUGGET', +37: 'POKE_DOLL', +38: 'FULL_HEAL', +39: 'REVIVE', +40: 'MAX_REVIVE', +41: 'GUARD_SPEC', +42: 'SUPER_REPEL', +43: 'MAX_REPEL', +44: 'DIRE_HIT', +46: 'FRESH_WATER', +47: 'SODA_POP', +48: 'LEMONADE', +49: 'X_ATTACK', +51: 'X_DEFEND', +52: 'X_SPEED', +53: 'X_SPECIAL', +54: 'COIN_CASE', +55: 'ITEMFINDER', +57: 'EXP_SHARE', +58: 'OLD_ROD', +59: 'GOOD_ROD', +60: 'SILVER_LEAF', +61: 'SUPER_ROD', +62: 'PP_UP', +63: 'ETHER', +64: 'MAX_ETHER', +65: 'ELIXER', +66: 'RED_SCALE', +67: 'SECRETPOTION', +68: 'S_S_TICKET', +69: 'MYSTERY_EGG', +70: 'CLEAR_BELL', +71: 'SILVER_WING', +72: 'MOOMOO_MILK', +73: 'QUICK_CLAW', +74: 'PSNCUREBERRY', +75: 'GOLD_LEAF', +76: 'SOFT_SAND', +77: 'SHARP_BEAK', +78: 'PRZCUREBERRY', +79: 'BURNT_BERRY', +80: 'ICE_BERRY', +81: 'POISON_BARB', +82: "KINGS_ROCK", +83: 'BITTER_BERRY', +84: 'MINT_BERRY', +85: 'RED_APRICORN', +86: 'TINYMUSHROOM', +87: 'BIG_MUSHROOM', +88: 'SILVERPOWDER', +89: 'BLU_APRICORN', +91: 'AMULET_COIN', +92: 'YLW_APRICORN', +93: 'GRN_APRICORN', +94: 'CLEANSE_TAG', +95: 'MYSTIC_WATER', +96: 'TWISTEDSPOON', +97: 'WHT_APRICORN', +98: 'BLACKBELT', +99: 'BLK_APRICORN', +101: 'PNK_APRICORN', +102: 'BLACKGLASSES', +103: 'SLOWPOKETAIL', +104: 'PINK_BOW', +105: 'STICK', +106: 'SMOKE_BALL', +107: 'NEVERMELTICE', +108: 'MAGNET', +109: 'MIRACLEBERRY', +110: 'PEARL', +111: 'BIG_PEARL', +112: 'EVERSTONE', +113: 'SPELL_TAG', +114: 'RAGECANDYBAR', +115: 'GS_BALL', +116: 'BLUE_CARD', +117: 'MIRACLE_SEED', +118: 'THICK_CLUB', +119: 'FOCUS_BAND', +121: 'ENERGYPOWDER', +122: 'ENERGY_ROOT', +123: 'HEAL_POWDER', +124: 'REVIVAL_HERB', +125: 'HARD_STONE', +126: 'LUCKY_EGG', +127: 'CARD_KEY', +128: 'MACHINE_PART', +129: 'EGG_TICKET', +130: 'LOST_ITEM', +131: 'STARDUST', +132: 'STAR_PIECE', +133: 'BASEMENT_KEY', +134: 'PASS', +138: 'CHARCOAL', +139: 'BERRY_JUICE', +140: 'SCOPE_LENS', +143: 'METAL_COAT', +144: 'DRAGON_FANG', +146: 'LEFTOVERS', +150: 'MYSTERYBERRY', +151: 'DRAGON_SCALE', +152: 'BERSERK_GENE', +156: 'SACRED_ASH', +157: 'HEAVY_BALL', +158: 'FLOWER_MAIL', +159: 'LEVEL_BALL', +160: 'LURE_BALL', +161: 'FAST_BALL', +163: 'LIGHT_BALL', +164: 'FRIEND_BALL', +165: 'MOON_BALL', +166: 'LOVE_BALL', +167: 'NORMAL_BOX', +168: 'GORGEOUS_BOX', +169: 'SUN_STONE', +170: 'POLKADOT_BOW', +172: 'UP_GRADE', +173: 'BERRY', +174: 'GOLD_BERRY', +175: 'SQUIRTBOTTLE', +177: 'PARK_BALL', +178: 'RAINBOW_WING', +180: 'BRICK_PIECE', +181: 'SURF_MAIL', +182: 'LITEBLUEMAIL', +183: 'PORTRAITMAIL', +184: 'LOVELY_MAIL', +185: 'EON_MAIL', +186: 'MORPH_MAIL', +187: 'BLUESKY_MAIL', +188: 'MUSIC_MAIL', +189: 'MIRAGE_MAIL', +191: 'TM_01', +192: 'TM_02', +193: 'TM_03', +194: 'TM_04', +196: 'TM_05', +197: 'TM_06', +198: 'TM_07', +199: 'TM_08', +200: 'TM_09', +201: 'TM_10', +202: 'TM_11', +203: 'TM_12', +204: 'TM_13', +205: 'TM_14', +206: 'TM_15', +207: 'TM_16', +208: 'TM_17', +209: 'TM_18', +210: 'TM_19', +211: 'TM_20', +212: 'TM_21', +213: 'TM_22', +214: 'TM_23', +215: 'TM_24', +216: 'TM_25', +217: 'TM_26', +218: 'TM_27', +219: 'TM_28', +221: 'TM_29', +222: 'TM_30', +223: 'TM_31', +224: 'TM_32', +225: 'TM_33', +226: 'TM_34', +227: 'TM_35', +228: 'TM_36', +229: 'TM_37', +230: 'TM_38', +231: 'TM_39', +232: 'TM_40', +233: 'TM_41', +234: 'TM_42', +235: 'TM_43', +236: 'TM_44', +237: 'TM_45', +238: 'TM_46', +239: 'TM_47', +240: 'TM_48', +241: 'TM_49', +242: 'TM_50', +243: 'HM_01', +244: 'HM_02', +245: 'HM_03', +246: 'HM_04', +247: 'HM_05', +248: 'HM_06', +249: 'HM_07', +} + +def find_item_label_by_id(id): + if id in item_constants.keys(): + return item_constants[id] + else: return None + +def generate_item_constants(): + """make a list of items to put in constants.asm""" + output = "" + for (id, item) in item_constants.items(): + val = ("$%.2x"%id).upper() + while len(item)<13: item+= " " + output += item + " EQU " + val + "\n" + return output diff --git a/pokemontools/labels.py b/pokemontools/labels.py new file mode 100644 index 0000000..d553bdc --- /dev/null +++ b/pokemontools/labels.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +""" +Various label/line-related functions. +""" + +from pointers import ( + calculate_pointer, + calculate_bank, +) + +def remove_quoted_text(line): + """get rid of content inside quotes + and also removes the quotes from the input string""" + while line.count("\"") % 2 == 0 and line.count("\"") > 0: + first = line.find("\"") + second = line.find("\"", first+1) + line = line[0:first] + line[second+1:] + while line.count("\'") % 2 == 0 and line.count("'") > 0: + first = line.find("\'") + second = line.find("\'", first+1) + line = line[0:first] + line[second+1:] + return line + +def line_has_comment_address(line, returnable={}, bank=None): + """checks that a given line has a comment + with a valid address, and returns the address in the object. + Note: bank is required if you have a 4-letter-or-less address, + because otherwise there is no way to figure out which bank + is curretly being scanned.""" + #first set the bank/offset to nada + returnable["bank"] = None + returnable["offset"] = None + returnable["address"] = None + #only valid characters are 0-9a-fA-F + valid = [str(x) for x in range(10)] + \ + [chr(x) for x in range(ord('a'), ord('f')+1)] + \ + [chr(x) for x in range(ord('A'), ord('F')+1)] + #check if there is a comment in this line + if ";" not in line: + return False + #first throw away anything in quotes + if (line.count("\"") % 2 == 0 and line.count("\"")!=0) \ + or (line.count("\'") % 2 == 0 and line.count("\'")!=0): + line = remove_quoted_text(line) + #check if there is still a comment in this line after quotes removed + if ";" not in line: + return False + #but even if there's a semicolon there must be later text + if line[-1] == ";": + return False + #and just a space doesn't count + if line[-2:] == "; ": + return False + #and multiple whitespace doesn't count either + line = line.rstrip(" ").lstrip(" ") + if line[-1] == ";": + return False + #there must be more content after the semicolon + if len(line)-1 == line.find(";"): + return False + #split it up into the main comment part + comment = line[line.find(";")+1:] + #don't want no leading whitespace + comment = comment.lstrip(" ").rstrip(" ") + #split up multi-token comments into single tokens + token = comment + if " " in comment: + #use the first token in the comment + token = comment.split(" ")[0] + if token in ["0x", "$", "x", ":"]: + return False + offset = None + #process a token with a A:B format + if ":" in token: #3:3F0A, $3:$3F0A, 0x3:0x3F0A, 3:3F0A + #split up the token + bank_piece = token.split(":")[0].lower() + offset_piece = token.split(":")[1].lower() + #filter out blanks/duds + if bank_piece in ["$", "0x", "x"] \ + or offset_piece in ["$", "0x", "x"]: + return False + #they can't have both "$" and "x" + if "$" in bank_piece and "x" in bank_piece: + return False + if "$" in offset_piece and "x" in offset_piece: + return False + #process the bank piece + if "$" in bank_piece: + bank_piece = bank_piece.replace("$", "0x") + #check characters for validity? + for c in bank_piece.replace("x", ""): + if c not in valid: + return False + bank = int(bank_piece, 16) + #process the offset piece + if "$" in offset_piece: + offset_piece = offset_piece.replace("$", "0x") + #check characters for validity? + for c in offset_piece.replace("x", ""): + if c not in valid: + return False + if len(offset_piece) == 0: + return None + offset = int(offset_piece, 16) + #filter out blanks/duds + elif token in ["$", "0x", "x"]: + return False + #can't have both "$" and "x" in the number + elif "$" in token and "x" in token: + return False + elif "x" in token and not "0x" in token: #it should be 0x + return False + elif "$" in token and not "x" in token: + token = token.replace("$", "0x") + offset = int(token, 16) + elif "0x" in token and not "$" in token: + offset = int(token, 16) + else: #might just be "1" at this point + token = token.lower() + #check if there are bad characters + for c in token: + if c not in valid: + return False + offset = int(token, 16) + if offset == None and bank == None: + return False + if bank == None: + bank = calculate_bank(offset) + returnable["bank"] = bank + returnable["offset"] = offset + returnable["address"] = calculate_pointer(offset, bank=bank) + return True + +def get_address_from_line_comment(line, bank=None): + """ + wrapper for line_has_comment_address + """ + returnable = {} + result = line_has_comment_address(line, returnable=returnable, bank=bank) + if not result: + return False + return returnable["address"] + +def line_has_label(line): + """returns True if the line has an asm label""" + if not isinstance(line, str): + raise Exception, "can't check this type of object" + line = line.rstrip(" ").lstrip(" ") + line = remove_quoted_text(line) + if ";" in line: + line = line.split(";")[0] + if 0 <= len(line) <= 1: + return False + if ":" not in line: + return False + if line[0] == ";": + return False + if line[0] == "\"": + return False + if "::" in line: + return False + return True + +def get_label_from_line(line): + """returns the label from the line""" + #check if the line has a label + if not line_has_label(line): + return None + #split up the line + label = line.split(":")[0] + return label diff --git a/pokemontools/map_names.py b/pokemontools/map_names.py new file mode 100644 index 0000000..00cf452 --- /dev/null +++ b/pokemontools/map_names.py @@ -0,0 +1,484 @@ +# -*- coding: utf-8 -*- + +# this is modified in crystal.py during run-time +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 44"}, + 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": "Burned 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 North"}, + 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 South"}, + 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"}, + }, +} diff --git a/pokemontools/move_constants.py b/pokemontools/move_constants.py new file mode 100644 index 0000000..a20af85 --- /dev/null +++ b/pokemontools/move_constants.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- + +moves = { +0x01: "POUND", +0x02: "KARATE_CHOP", +0x03: "DOUBLESLAP", +0x04: "COMET_PUNCH", +0x05: "MEGA_PUNCH", +0x06: "PAY_DAY", +0x07: "FIRE_PUNCH", +0x08: "ICE_PUNCH", +0x09: "THUNDERPUNCH", +0x0A: "SCRATCH", +0x0B: "VICEGRIP", +0x0C: "GUILLOTINE", +0x0D: "RAZOR_WIND", +0x0E: "SWORDS_DANCE", +0x0F: "CUT", +0x10: "GUST", +0x11: "WING_ATTACK", +0x12: "WHIRLWIND", +0x13: "FLY", +0x14: "BIND", +0x15: "SLAM", +0x16: "VINE_WHIP", +0x17: "STOMP", +0x18: "DOUBLE_KICK", +0x19: "MEGA_KICK", +0x1A: "JUMP_KICK", +0x1B: "ROLLING_KICK", +0x1C: "SAND_ATTACK", +0x1D: "HEADBUTT", +0x1E: "HORN_ATTACK", +0x1F: "FURY_ATTACK", +0x20: "HORN_DRILL", +0x21: "TACKLE", +0x22: "BODY_SLAM", +0x23: "WRAP", +0x24: "TAKE_DOWN", +0x25: "THRASH", +0x26: "DOUBLE_EDGE", +0x27: "TAIL_WHIP", +0x28: "POISON_STING", +0x29: "TWINEEDLE", +0x2A: "PIN_MISSILE", +0x2B: "LEER", +0x2C: "BITE", +0x2D: "GROWL", +0x2E: "ROAR", +0x2F: "SING", +0x30: "SUPERSONIC", +0x31: "SONICBOOM", +0x32: "DISABLE", +0x33: "ACID", +0x34: "EMBER", +0x35: "FLAMETHROWER", +0x36: "MIST", +0x37: "WATER_GUN", +0x38: "HYDRO_PUMP", +0x39: "SURF", +0x3A: "ICE_BEAM", +0x3B: "BLIZZARD", +0x3C: "PSYBEAM", +0x3D: "BUBBLEBEAM", +0x3E: "AURORA_BEAM", +0x3F: "HYPER_BEAM", +0x40: "PECK", +0x41: "DRILL_PECK", +0x42: "SUBMISSION", +0x43: "LOW_KICK", +0x44: "COUNTER", +0x45: "SEISMIC_TOSS", +0x46: "STRENGTH", +0x47: "ABSORB", +0x48: "MEGA_DRAIN", +0x49: "LEECH_SEED", +0x4A: "GROWTH", +0x4B: "RAZOR_LEAF", +0x4C: "SOLARBEAM", +0x4D: "POISONPOWDER", +0x4E: "STUN_SPORE", +0x4F: "SLEEP_POWDER", +0x50: "PETAL_DANCE", +0x51: "STRING_SHOT", +0x52: "DRAGON_RAGE", +0x53: "FIRE_SPIN", +0x54: "THUNDERSHOCK", +0x55: "THUNDERBOLT", +0x56: "THUNDER_WAVE", +0x57: "THUNDER", +0x58: "ROCK_THROW", +0x59: "EARTHQUAKE", +0x5A: "FISSURE", +0x5B: "DIG", +0x5C: "TOXIC", +0x5D: "CONFUSION", +0x5E: "PSYCHIC_M", +0x5F: "HYPNOSIS", +0x60: "MEDITATE", +0x61: "AGILITY", +0x62: "QUICK_ATTACK", +0x63: "RAGE", +0x64: "TELEPORT", +0x65: "NIGHT_SHADE", +0x66: "MIMIC", +0x67: "SCREECH", +0x68: "DOUBLE_TEAM", +0x69: "RECOVER", +0x6A: "HARDEN", +0x6B: "MINIMIZE", +0x6C: "SMOKESCREEN", +0x6D: "CONFUSE_RAY", +0x6E: "WITHDRAW", +0x6F: "DEFENSE_CURL", +0x70: "BARRIER", +0x71: "LIGHT_SCREEN", +0x72: "HAZE", +0x73: "REFLECT", +0x74: "FOCUS_ENERGY", +0x75: "BIDE", +0x76: "METRONOME", +0x77: "MIRROR_MOVE", +0x78: "SELFDESTRUCT", +0x79: "EGG_BOMB", +0x7A: "LICK", +0x7B: "SMOG", +0x7C: "SLUDGE", +0x7D: "BONE_CLUB", +0x7E: "FIRE_BLAST", +0x7F: "WATERFALL", +0x80: "CLAMP", +0x81: "SWIFT", +0x82: "SKULL_BASH", +0x83: "SPIKE_CANNON", +0x84: "CONSTRICT", +0x85: "AMNESIA", +0x86: "KINESIS", +0x87: "SOFTBOILED", +0x88: "HI_JUMP_KICK", +0x89: "GLARE", +0x8A: "DREAM_EATER", +0x8B: "POISON_GAS", +0x8C: "BARRAGE", +0x8D: "LEECH_LIFE", +0x8E: "LOVELY_KISS", +0x8F: "SKY_ATTACK", +0x90: "TRANSFORM", +0x91: "BUBBLE", +0x92: "DIZZY_PUNCH", +0x93: "SPORE", +0x94: "FLASH", +0x95: "PSYWAVE", +0x96: "SPLASH", +0x97: "ACID_ARMOR", +0x98: "CRABHAMMER", +0x99: "EXPLOSION", +0x9A: "FURY_SWIPES", +0x9B: "BONEMERANG", +0x9C: "REST", +0x9D: "ROCK_SLIDE", +0x9E: "HYPER_FANG", +0x9F: "SHARPEN", +0xA0: "CONVERSION", +0xA1: "TRI_ATTACK", +0xA2: "SUPER_FANG", +0xA3: "SLASH", +0xA4: "SUBSTITUTE", +0xA5: "STRUGGLE", +0xA6: "SKETCH", +0xA7: "TRIPLE_KICK", +0xA8: "THIEF", +0xA9: "SPIDER_WEB", +0xAA: "MIND_READER", +0xAB: "NIGHTMARE", +0xAC: "FLAME_WHEEL", +0xAD: "SNORE", +0xAE: "CURSE", +0xAF: "FLAIL", +0xB0: "CONVERSION2", +0xB1: "AEROBLAST", +0xB2: "COTTON_SPORE", +0xB3: "REVERSAL", +0xB4: "SPITE", +0xB5: "POWDER_SNOW", +0xB6: "PROTECT", +0xB7: "MACH_PUNCH", +0xB8: "SCARY_FACE", +0xB9: "FAINT_ATTACK", +0xBA: "SWEET_KISS", +0xBB: "BELLY_DRUM", +0xBC: "SLUDGE_BOMB", +0xBD: "MUD_SLAP", +0xBE: "OCTAZOOKA", +0xBF: "SPIKES", +0xC0: "ZAP_CANNON", +0xC1: "FORESIGHT", +0xC2: "DESTINY_BOND", +0xC3: "PERISH_SONG", +0xC4: "ICY_WIND", +0xC5: "DETECT", +0xC6: "BONE_RUSH", +0xC7: "LOCK_ON", +0xC8: "OUTRAGE", +0xC9: "SANDSTORM", +0xCA: "GIGA_DRAIN", +0xCB: "ENDURE", +0xCC: "CHARM", +0xCD: "ROLLOUT", +0xCE: "FALSE_SWIPE", +0xCF: "SWAGGER", +0xD0: "MILK_DRINK", +0xD1: "SPARK", +0xD2: "FURY_CUTTER", +0xD3: "STEEL_WING", +0xD4: "MEAN_LOOK", +0xD5: "ATTRACT", +0xD6: "SLEEP_TALK", +0xD7: "HEAL_BELL", +0xD8: "RETURN", +0xD9: "PRESENT", +0xDA: "FRUSTRATION", +0xDB: "SAFEGUARD", +0xDC: "PAIN_SPLIT", +0xDD: "SACRED_FIRE", +0xDE: "MAGNITUDE", +0xDF: "DYNAMICPUNCH", +0xE0: "MEGAHORN", +0xE1: "DRAGONBREATH", +0xE2: "BATON_PASS", +0xE3: "ENCORE", +0xE4: "PURSUIT", +0xE5: "RAPID_SPIN", +0xE6: "SWEET_SCENT", +0xE7: "IRON_TAIL", +0xE8: "METAL_CLAW", +0xE9: "VITAL_THROW", +0xEA: "MORNING_SUN", +0xEB: "SYNTHESIS", +0xEC: "MOONLIGHT", +0xED: "HIDDEN_POWER", +0xEE: "CROSS_CHOP", +0xEF: "TWISTER", +0xF0: "RAIN_DANCE", +0xF1: "SUNNY_DAY", +0xF2: "CRUNCH", +0xF3: "MIRROR_COAT", +0xF4: "PSYCH_UP", +0xF5: "EXTREMESPEED", +0xF6: "ANCIENTPOWER", +0xF7: "SHADOW_BALL", +0xF8: "FUTURE_SIGHT", +0xF9: "ROCK_SMASH", +0xFA: "WHIRLPOOL", +0xFB: "BEAT_UP", +} diff --git a/pokemontools/old_parse_scripts.py b/pokemontools/old_parse_scripts.py new file mode 100644 index 0000000..8ed10ef --- /dev/null +++ b/pokemontools/old_parse_scripts.py @@ -0,0 +1,1885 @@ +# -*- coding: utf-8 -*- +""" +This is in a separate file because it screws up highlighting. Doesn't +do anything on its own. + +old_parse is a part of the Script class +""" + +def old_parse(self, *args, **kwargs): + """parses a script-engine script; force=True if you want to re-parse + and get the debug information""" + print "Script.old_parse address="+hex(self.address) + #can't handle more than one argument + if len(args) > 1: + raise Exception, "Script.parse_script doesn't know how to handle positional arguments" + #use the first positional argument as the address + elif len(args) == 1: + self.address = args[0] + if type(self.address) == str: + self.address = int(self.address, 16) + elif type(self.address) != int: + raise Exception, "address param is the wrong type" + #parse any keyword arguments, first make up the defaults + kwargsorig = {"map_group": None, "map_id": None, "force": False, "debug": True, "origin": False} + #let the caller override any defaults + kwargsorig.update(kwargs) + #copy these into kwargs + kwargs = kwargsorig + #set these defaults + map_group = kwargs["map_group"] + map_id = kwargs["map_id"] + force = kwargs["force"] + debug = kwargs["debug"] + origin = kwargs["origin"] + self.map_group = map_group + self.map_id = map_id + + global rom + if rom == None: + direct_load_rom() + + #max number of commands in a 'recursive' script + max_cmds = 150 + + #set the address to be parsed + address = self.address + original_start_address = address + + #don't parse these crazy things (battle tower things, some rival things, etc.) + if address in stop_points: + print "got " + hex(address) + ".. map_group=" + str(map_group) + " map_id=" + str(map_id) + return None + #don't parse anything that looks crazy + if address < 0x4000 and address not in [0x26ef, 0x114, 0x1108]: + print "address is less than 0x4000.. address is: " + hex(address) + sys.exit() + + #check if work is being repeated + if is_script_already_parsed_at(address) and not force: + raise Exception, "this script has already been parsed before, please use that instance" + #use the commands from a previously-parsed Script object + #self.commands = script_parse_table[address].commands + #return True + + #return a previously-created Script object + #return script_parse_table[address] + + #this next line stops the same script from being re-parsed multiple times + #for instance.. maybe there's a script jump, then a jump back + #the original script should only be parsed once + script_parse_table[original_start_address:original_start_address+1] = "incomplete Script" + + #set up some variables + self.commands = {} + commands = self.commands + offset = address + end = False + + #main loop.. parse each command byte + while not end: + #reset variables so we don't contaminate this command + info, long_info, size = None, None, 0 + #read the current command byte + command_byte = ord(rom[offset]) + #setup the current command representation + command = {"type": command_byte, "start_address": offset} + + #size is the total size including the command byte + #last_byte_address is offset+size-1 + start_address = offset + + if (len(commands.keys()) > max_cmds) and origin != False: + print "too many commands in this script? might not be a script (starting at: " +\ + hex(original_start_address) + ").. called from a script at: " + hex(origin) + sys.exit() + + #start checking against possible command bytes + if command_byte == 0x00: #Pointer code [2b+ret] + pksv_name = "2call" + info = "pointer code" + long_info = """ + 2byte pointer points to script; when pointed script ends --> return to old script + [code][2 byte pointer] + """ + size = 3 + start_address = offset + last_byte_address = offset + size - 1 + pointer = calculate_pointer_from_bytes_at(start_address+1) + if pointer == None: + raise Exception, "pointer is None (shouldn't be None pointer on 0x0 script command" + command["pointer"] = pointer + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["script"] = script + elif command_byte == 0x01: #Pointer code [3b+ret] + pksv_name = "3call" + info = "pointer code" + long_info = """ + 3byte pointer points to script; when pointed script ends --> return to old script + [Code][resp. pointer(2byte or 3byte)] + """ + size = 4 + info = "pointer code" + pointer = calculate_pointer_from_bytes_at(start_address+1, bank=True) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x02: #Pointer code [2b+3b+ret] + info = "pointer code" + long_info = """ + 2byte pointer points to 3byte pointer; when pointed script --> return to old script + [Code][resp. pointer(2byte or 3byte)] + """ + size = 3 + pointer = calculate_pointer_from_bytes_at(start_address+1) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x03: #Pointer code [2b] + #XXX what does "new script is part of main script" mean? + info = "pointer code" + long_info = """ + 2byte pointer points to script; new script is part of main script + [Code][resp. pointer(2byte or 3byte)] + """ + size = 3 + pointer = calculate_pointer_from_bytes_at(start_address+1) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + end = True #according to pksv + elif command_byte == 0x04: #Pointer code [3b] + info = "pointer code" + long_info = """ + 3byte pointer points to script; new script is part of main script + [Code][resp. pointer(2byte or 3byte)] + """ + size = 4 + pointer = calculate_pointer_from_bytes_at(start_address+1, bank=True) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + end = True #according to pksv + elif command_byte == 0x05: #Pointer code [2b+3b] + info = "pointer code" + long_info = """ + 2byte pointer points to 3byte pointer; new script is part of main script + [Code][resp. pointer(2byte or 3byte)] + """ + size = 3 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1) + command["target_pointer"] = calculate_pointer_from_bytes_at(command["pointer"], bank=True) + + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(command["target_pointer"])+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(command["target_pointer"], original_start_address, debug=debug) + command["script"] = script + end = True #according to pksv + elif command_byte == 0x06: #RAM check [=byte] + info = "RAM check [=byte]" + long_info = """ + When the conditional is true... + .. then go to pointed script, else resume interpreting after the pointer + """ + size = 4 + command["byte"] = ord(rom[start_address+1]) + pointer = calculate_pointer_from_bytes_at(start_address+2) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x07: #RAM check [<>byte] + info = "RAM check [<>byte]" + long_info = """ + When the conditional is true... + .. then go to pointed script, else resume interpreting after the pointer + """ + size = 4 + command["byte"] = ord(rom[start_address+1]) + pointer = calculate_pointer_from_bytes_at(start_address+2) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x08: #RAM check [=0] + info = "RAM check [=0]" + long_info = """ + When the conditional is true... + .. then go to pointed script, else resume interpreting after the pointer + """ + size = 3 + pointer = calculate_pointer_from_bytes_at(start_address+1) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x09: #RAM check [<>0] + info = "RAM check [<>0]" + long_info = """ + When the conditional is true... + .. then go to pointed script, else resume interpreting after the pointer + """ + size = 3 + pointer = calculate_pointer_from_bytes_at(start_address+1) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x0A: #RAM check [byte] + info = "RAM check [>byte]" + long_info = """ + When the conditional is true... + .. then go to pointed script, else resume interpreting after the pointer + """ + size = 4 + command["byte"] = ord(rom[start_address+1]) + pointer = calculate_pointer_from_bytes_at(start_address+2) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(pointer, original_start_address, debug=debug) + command["pointer"] = pointer + command["script"] = script + elif command_byte == 0x0C: #0C codes [xxyy] + info = "call predefined script then end" + long_info = """ + Calls predefined scripts. After this code the script ends. + [0C][xxyy] + """ + size = 3 + end = True + byte1 = ord(rom[offset+1]) + byte2 = ord(rom[offset+2]) + number = byte1 + (byte2 << 8) + #0000 to 000AD ... XXX how should these be handled? + command["predefined_script_number"] = number + elif command_byte == 0x0D: #0D codes [xxyy] + info = "call some predefined script" + long_info = """ + Calls predefined scripts. Exactly like $0C except the script does not end. + [0D][xxyy] + """ + size = 3 + byte1 = ord(rom[offset+1]) + byte2 = ord(rom[offset+2]) + number = byte1 + (byte2 << 8) + #0000 to 000AD ... XXX how should these be handled? + command["predefined_script_number"] = number + elif command_byte == 0x0E: #ASM code1 [3b] + info = "ASM code1 [3b]" + long_info = """ + Calls a predefined routine by interpreting the ASM the pointer points to. + [0E][3byte pointer] + """ + size = 4 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) + #XXX should we dissassemble the asm at the target location? + elif command_byte == 0x0F: #0F codes [xxyy] + info = "call some predefined script [0F][yyxx]" + long_info = """ + Calls predefined scripts. + [0F][yyxx] + NOTE: For (some) dialogues the font needs to be loaded with the Text box&font code. + """ + size = 3 + byte1 = ord(rom[offset+1]) + byte2 = ord(rom[offset+2]) + number = byte1 + (byte2 << 8) + command["predefined_script_number"] = number + elif command_byte == 0x10: #ASM code2 [2b] + info = "ASM code2 [2b to 3b to asm]" + long_info = """ + Call an ASM script via a 2byte pointer pointing to a 3byte pointer. + [10][2byte pointer pointing to 3byte pointer pointing to ASM script] + """ + size = 3 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + #XXX should i include the 3-byte pointer at the target location? + #XXX should we dissassemble the asm at the target location? + elif command_byte == 0x11: #Trigger event check1 [xxyy] + info = "Trigger event check1 [xx][yy]" + long_info = """ + Check the current number of the trigger event on map (map group/map id). + [11][map group][map number] + """ + size = 3 + command["map_group"] = ord(rom[start_address+1]) + command["map_id"] = ord(rom[start_address+2]) + elif command_byte == 0x12: #Activate trigger event from afar [xxyyzz] + info = "Activate trigger event from afar [xx][yy][zz]" + long_info = """ + Changes trigger event number on map (map bank/map no) to xx. + xx = trigger event number that should be activated + [12][MapBank][MapNo][xx] + """ + size = 4 + command["map_group"] = ord(rom[start_address+1]) + command["map_id"] = ord(rom[start_address+2]) + command["trigger_number"] = ord(rom[start_address+3]) + elif command_byte == 0x13: #Trigger event check + info = "Trigger event check" + long_info = """ + Checks the number of the trigger events on the current map. + [13] + """ + size = 1 + elif command_byte == 0x14: #De-/activate trigger event [xx] + info = "De-/activate trigger event [xx]" + long_info = """ + Changes trigger event number on current map to xx. + xx = trigger event number that should be activated + [14][xx] + deactivate? Just activate a different trigger event number. There's a limit of 1 active trigger. + """ + size = 2 + command["trigger_number"] = ord(rom[start_address+1]) + elif command_byte == 0x15: #Load variable into RAM [xx] + info = "Load variable into RAM [xx]" + long_info = "[15][xx]" + size = 2 + command["variable"] = ord(rom[start_address+1]) + elif command_byte == 0x16: #Add variables [xx] + info = "Add variables [xx]" + long_info = """ + Adds xx and the variable in RAM. + #[16][xx] + """ + size = 2 + command["variable"] = ord(rom[start_address+1]) + elif command_byte == 0x17: #Random number [xx] + info = "Random number [xx]" + long_info = """ + #Reads xx and creates a random number between 00 and xx -1. + #According to this xx can be all but 00. Random number = [00; xx) + #The nearer the random number is to xx, the rarer it occurs. + #Random number gets written to RAM. + """ + size = 2 + command["rarest"] = ord(rom[start_address+1]) + elif command_byte == 0x18: #Version check + info = "G/S version check" + long_info = """ + #Check if version is gold or silver. Gives feedback. + #00 = Gold + #01 = Silver + #[18] + """ + size = 1 + elif command_byte == 0x19: #Copy variable code1 [xxyy] + info = "Copy from RAM address to script RAM variable [xxyy]" + long_info = """ + #Writes variable from ram address to RAM. + #[19][2-byte RAM address] + """ + size = 3 + #XXX maybe a pointer is a bad idea? + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x1A: #Copy variable code2 [xxyy] + info = "Write variable from script RAM variable to actual RAM address [xxyy]" + long_info = """ + #Writes variable from RAM to actual RAM address. + #[1A][2-byte RAM address] + """ + size = 3 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x1B: #Load variable [xxyyzz] + info = "Load variable [xxyy][zz]" + long_info = """ + #Writes zz to ram address. + #[1B][2-byte RAM address][zz] + """ + size = 4 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["value"] = ord(rom[start_address+3]) + elif command_byte == 0x1C: #Check codes [xx] + #XXX no idea what's going on in this one :( + info = "Check pre-ID-mapped RAM location [xx]" + long_info = """ + #Checks special game-technical values and writes then into RAM. + #[1C][following part][Ram check (when <> 08/09 see „numbers“ in list of following parts)] + #following part (and then hex values) + #01 = PKMN count in party + # 00 - 06 + #02 = ??? + #03 = Battle type of wild PKMN + #04 = ??? + #05 = PokéDex caught + # 00 - FA + #06 = PokéDex seen + # 00 - FA + #07 = Badge count + # 00 - 10 + #08 = Movement + # 00 = walk + # 01 = bike + # 02 = slipping + # 04 = surfer + # 08 = surfing pikachu + #09 = HIRO direction + # 00 (d) + # 01 (u) + # 02 (l) + # 03 (r) + #0A = Time in hours + # 00 - 18 + #0B = Day + # 00 (Mo) - 06 (Su) + #0C = Map bank of current map + #0D = Map no of current map + #0E = Num. of diff. unowns seen + # 00 - 1A + #0F = Action byte of map + #10 = Amount of free spaces in pkmn box + # 00 - 14 + #11 = Minutes until end bug contest + # 00 - 14 + #12 = X position of HIRO + #13 = Y position of HIRO + #14 = phone call number + """ + size = 2 #i think? + command["following_part"] = ord(rom[start_address+1]) + elif command_byte == 0x1D: #Input code1 [xx] + info = "Write to pre-ID-mapped RAM location [xx]" + long_info = """ + #Writes variable from RAM to special game-technical value offsets. + #[1D][following part] + #where [following part] is the same as 0x1C + """ + size = 2 + command["following_part"] = ord(rom[start_address+1]) + elif command_byte == 0x1E: #Input code2 [xxyy] + info = "Write byte value to pre-ID-mapped RAM location [aa][xx]" + long_info = """ + #Writes variable xx to special game-technical value offsets. + #[1E][following part][xx] + #where [following part] is the same as 0x1C + """ + size = 3 + command["following_part"] = ord(rom[start_address+1]) + command["value"] = ord(rom[start_address+2]) + elif command_byte == 0x1F: #Give item code [xxyy] + info = "Give item by id and quantity [xx][yy]" + long_info = """ + #Gives item (item no) amount times. + #feedback: + # 00 = bag full + # 01 = OK + #[1F][item no][amount] + """ + size = 3 + command["item_id"] = ord(rom[start_address+1]) + command["quantity"] = ord(rom[start_address+2]) + elif command_byte == 0x20: #Take item code [xxyy] + info = "Take item by id and quantity [xx][yy]" + long_info = """ + #Gives item (item no) amount times + #feedback: + # 00 = not enough items + #[20][item no][amount] + """ + size = 3 + command["item_id"] = ord(rom[start_address+1]) + command["quantity"] = ord(rom[start_address+2]) + elif command_byte == 0x21: #Check for item code [xx] + info = "Check if player has item [xx]" + long_info = """ + #Checks if item is possessed. + #feedback: + # 00 = does not have item + # 01 = has item + #[21][item no] + """ + size = 2 + command["item_id"] = ord(rom[start_address+1]) + elif command_byte == 0x22: #Give money code [xxyyzzaa] + info = "Give money to HIRO/account [xxyyzzaa]" + long_info = """ + #Gives zzyyxx money to HIRO/account. + #zzyyxx = amount of money (000000 - 0F423F) + #[22][00-HIRO/01-account][xxyyzz] + """ + size = 5 + bytes = rom_interval(start_address, size, strings=False) + command["account"] = bytes[1] + command["amount"] = bytes[2:] + #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" + elif command_byte == 0x23: #Take money code [xxyyzzaa] + info = "Take money from HIRO/account [xxyyzzaa]" + long_info = """ + #Takes zzyyxx money from HIRO/account. + #zzyyxx = amount of money (000000 - 0F423F) + #[23][00-HIRO/01-account][xxyyzz] + """ + size = 5 + bytes = rom_interval(start_address, size, strings=False) + command["account"] = bytes[1] + command["amount"] = bytes[2:] + #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" + elif command_byte == 0x24: #Check for money code [xxyyzzaa] + info = "Check if HIRO/account has enough money [xxyyzzaa]" + long_info = """ + #Checks if HIRO/account has got zzyyxx money. + #feedback: + # 00 = enough money + # 01 = exact amount + # 02 = less money + #zzyyxx = amount of money (000000 - 0F423F) + #[24][00-HIRO/01-account][xxyyzz] + """ + size = 5 + bytes = rom_interval(start_address, size, strings=False) + command["account"] = bytes[1] + command["quantity"] = bytes[2:] + #XXX how is quantity formatted? + #raise NotImplementedError, "don't know if zzyyxx is a decimal or hex value" + elif command_byte == 0x25: #Give coins code [xxyy] + info = "Give coins to HIRO [xxyy]" + long_info = """ + #Gives coins to HIRO. + #yyxx = amount of coins (0000 - 270F) + #[25][xxyy] + """ + size = 3 + bytes = rom_interval(start_address, size, strings=False) + command["quantity"] = bytes[1] + (bytes[2] << 8) + elif command_byte == 0x26: #Take coins code [xxyy] + info = "Take coins from HIRO [xxyy]" + long_info = """ + #Takes coins away from HIRO. + #yyxx = amount of coins (0000 - 270F) + #[26][xxyy] + """ + size = 3 + bytes = rom_interval(start_address, size, strings=False) + command["quantity"] = bytes[1] + (bytes[2] << 8) + elif command_byte == 0x27: #Check for coins code [xxyy] + info = "Check if HIRO has enough coins [xxyy]" + long_info = """ + #Checks if HIRO has enough coins. + #feedback: + # 00 = has enough coins + # 01 = has exact amount + # 02 = does not have enough + #yyxx = amount of coins necessary (0000 - 270F) + #[27][xxyy] + """ + size = 3 + bytes = rom_interval(start_address, size, strings=False) + command["quantity"] = bytes[1] + (bytes[2] << 8) + elif command_byte == 0x28: #Give cell phone number [xx] + info = "Give cell phone number [xx]" + long_info = """ + #Gives number to HIRO. + #feedback: + # 00 = number was added successfully + # 01 = Number already added, or no memory + #xx = number of person + #[28][xx] + #01 = mother + #02 = bike store + #03 = bll + #04 = elm + """ + size = 2 + command["number"] = ord(rom[start_address+1]) + elif command_byte == 0x29: #Take cell phone number [xx] + info = "Delete cell phone number [xx]" + long_info = """ + #Deletes a number from the list. + #feedback: + # 00 = number deleted successfully + # 01 = number wasn't in list + #xx = number of person + #[29][xx] + """ + size = 2 + command["number"] = ord(rom[start_address+1]) + elif command_byte == 0x2A: #Check for cell phone number [xx] + info = "Check for cell phone number [xx]" + long_info = """ + #Checks if a number is in the list. + #feedback: + # 00 = number is in list + # 01 = number not in list + #xx = number to look for + #[2A][xx] + """ + size = 2 + command["number"] = ord(rom[start_address+1]) + elif command_byte == 0x2B: #Check time of day [xx] + info = "Check time of day [xx]" + long_info = """ + #Checks the time of day. + #feedback: + # 00 = time of day is the same + # 01 = time of day is not the same + #[2B][time of day (01morn-04night)] + """ + size = 2 + command["time_of_day"] = ord(rom[start_address+1]) + elif command_byte == 0x2C: #Check for PKMN [xx] + info = "Check for pokemon [xx]" + long_info = """ + #Checks if there is a certain PKMN in team. + #feedback: + # 00 = in team + # 01 = not in team + #xx = pkmn id + #[2C][xx] + """ + size = 2 + command["pokemon_id"] = ord(rom[start_address+1]) + elif command_byte == 0x2D: #Give PKMN [xxyyzzaa(+2b +2b)] + info = "Give pokemon [pokemon][level][item][trainer2b][...]" + long_info = """ + #Gives a PKMN if there's space + #feedback: + # trainer id + #[2D][PKMN][PKMNlvl][PKMNitem][TRAINER] + #trainer: + # 00 = HIRO + # 01 = after the main code there are 4 bytes added + # [2byte pointer to trainer's name (max.0x0A figures + 0x50)][2byte pointer to nickname (max.0x0A figures + 0x50)] + """ + size = 5 + bytes = rom_interval(start_address, size, strings=False) + command["pokemon_id"] = bytes[1] + command["pokemon_level"] = bytes[2] + command["held_item_id"] = bytes[3] + command["trainer"] = bytes[4] + if command["trainer"] == 0x01: + size += 4 + bytes = rom_interval(start_address, size, strings=False) + command["trainer_name_pointer"] = calculate_pointer_from_bytes_at(start_address+5, bank=False) + command["pokemon_nickname_pointer"] = calculate_pointer_from_bytes_at(start_address+7, bank=False) + elif command_byte == 0x2E: #Give EGG [xxyy] + info = "Give egg [xx][yy]" + long_info = """ + #Gives egg if there's space. + #feedback: + # 00 = OK + # 02 = transaction not complete + #[2E][PKMN][PKMNlvl] + """ + size = 3 + command["pokemon_id"] = ord(rom[start_address+1]) + command["pokemon_level"] = ord(rom[start_address+2]) + elif command_byte == 0x2F: #Attach item code [2B] + info = "Attach item to last pokemon in list [xxyy]" + long_info = """ + #Gives last PKMN in list an item and letter text if applicable. Replaces existing items. + #[2F][2byte pointer to item no + 0x20 bytes letter text] + """ + size = 3 + command["item_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + #XXX are those 20 bytes supposed to happen here? or at the pointer's destination? + elif command_byte == 0x30: #Check letter code [2b] + info = "Check letter against known letter [xxyy]" + long_info = """ + #Opens a PKMN list. Selected PKMN must have the right letter + the right contents. If OK, then PKMN is taken away + #feedback: + # 00 = wrong letter + # 01 = OK + # 02 = Cancelled + # 03 = Chosen PKMN has no letter + # 04 = Chosen PKMN is the only one in the list. + #[30][2byte pointer to letter item no + 0x20 bytes letter text] + """ + size = 3 + command["item_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x31: #BitTable1 check [xxyy] + info = "Check some bit on bit table 1 [xxyy]" + long_info = """ + #Checks whether a bit of BitTable1 has the value 0 or 1. + #feedback: + # 00 = value 0 (off) + # 01 = value 1 (on) + #[31][2-byte bit number] + """ + #XXX what format is the 2-byte number in? + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + #raise NotImplementedError, "what format is the 2-byte number in?" + elif command_byte == 0x32: #BitTable1 reset [xxyy] + info = "Reset (to 0) a bit on bit table 1 [xxyy]" + long_info = """ + #Sets a bit of BitTable1 to value 0. + #[32][Bit no (2byte)] + """ + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + elif command_byte == 0x33: #BitTable1 set [xxyy] + info = "Set (to 1) a bit on bit table 1 [xxyy]" + long_info = """ + #Sets a bit of BitTable1 to value 1. + #[33][Bit-No (2byte)] + """ + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + elif command_byte == 0x34: #BitTable2 check [xxyy] + info = "Check some bit on bit table 2 [xxyy]" + long_info = """ + #Checks whether a bit of BitTable2 has the value 0 or 1. + #feedback: + # 00 = value 0 (off) + # 01 = value 1 (on) + #[34][Bit no (2byte)] + """ + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + elif command_byte == 0x35: #BitTable2 reset [xxyy] + info = "Reset (to 0) a bit on bit table 2 [xxyy]" + long_info = """ + #Sets a bit of BitTable2 to value 0. + #[35][Bit no (2byte)] + """ + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + elif command_byte == 0x36: #BitTable2 set [xxyy] + info = "Set (to 1) a bit on bit table 2 [xxyy]" + long_info = """ + #Sets a bit of BitTable2 to value 1. + #[36][Bit no (2byte)] + """ + size = 3 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["bit_number_bytes"] = bytes + elif command_byte == 0x37: #Deactivate PKMN battles + info = "Turn off wild pokemon battles" + long_info = """ + #This code turns all wild PKMN battles off. + #[37] + """ + size = 1 + elif command_byte == 0x38: #Activate PKMN battles + info = "Turn no wild pokemon battles" + long_info = "This code turns all wild PKMN battles on." + size = 1 + elif command_byte == 0x39: #X/Y comparison [xxyy] + info = "BUGGY x/y comparison [xxyy]" + long_info = """ + #This code is buggy (Bug fix: 0x3021 --> C6) and can't used as + #described without fix. This code compares the X and Y coordinates of + #HIRO with the ones in a table (max. 20h XY pairs) on the current map. + #It sets or resets the 4 bytes D17C to D17F accordingly to this table, + #1 bit for every table entry. To be useful, this code can only be used + #in a command queue, because with every regular move of HIRO the bits + #are reset again. This code is an alternative to the trigger events and + #can be used via the command queue code. + #See Write command queue, Additional documentation: 3:4661 with c= index + #in table (start=00), hl=D171, b=01, d=00. + """ + size = 3 + command["table_pointer"] = rom_interval(start_address+1, size-1, strings=False) + elif command_byte == 0x3A: #Warp modifier [xxyyzz] + info = "Set target for 0xFF warps [warp id][map group][map id]" + long_info = """ + #Changes warp data for all warps of the current map that have a 0xFF for warp-to data. + #[3A][Nee warp-to][New map bank][New map no] + """ + size = 4 + bytes = rom_interval(start_address+1, size-1, strings=False) + command["nee_warp_to"] = bytes[0] + command["map_group"] = bytes[1] + command["map_id"] = bytes[2] + elif command_byte == 0x3B: #Blackout modifier [xxyy] + info = "Blackout warp modifier [map group][map id]" + long_info = """ + #Changes the map HIRO arrives at, after having a blackout. + #There needs to be flying data for that map. + #[3B][Map bank][Map no] + """ + size = 3 + command["map_group"] = ord(rom[start_address+1]) + command["map_id"] = ord(rom[start_address+2]) + elif command_byte == 0x3C: #Warp code [xxyyzzaa] + info = "Warp to [map group][map id][x][y]" + long_info = """ + #Warps to another map. + #If all data is 00s, then the current map is reloaded with + #the current X and Y coordinates. Old script is not finished + #without a [90]. + #[3C][Map bank][Map no][X][Y] + """ + size = 5 + command["map_group"] = ord(rom[start_address+1]) + command["map_id"] = ord(rom[start_address+2]) + command["x"] = ord(rom[start_address+3]) + command["y"] = ord(rom[start_address+4]) + elif command_byte == 0x3D: #Account code [xxyy] + info = "Read money amount [xx][yy]" + long_info = """ + #Reads amount of money in accounts of HIRO and mother and writes + #it to MEMORY1, 2 or 3 for later use in text. + #[3D][00 = HIRO| <> 00 = Mother][00-02 MEMORY] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#InText01 + """ + size = 3 + command["account_id"] = ord(rom[start_address+1]) + command["memory_id"] = ord(rom[start_address+2]) + elif command_byte == 0x3E: #Coin case code [xx] + info = "Read coins amount [xx]" + long_info = """ + #Reads amount of coins in coin case and writes it to MEMORY 1, 2, + #or 3 for later use in text. + #[3E][00-02 MEMORY] + """ + size = 2 + command["memory_id"] = ord(rom[start_address+1]) + elif command_byte == 0x3F: #Display RAM [xx] + info = "Copy script RAM value into memX [xx]" + long_info = """ + #Reads RAM value and writes it to MEMORY1, 2 or 3 for later use in text. + #[3F][00-02 MEMORY] + """ + size = 2 + command["memory_id"] = ord(rom[start_address+1]) + elif command_byte == 0x40: #Display pokémon name [xxyy] + info = "Copy pokemon name (by id) to memX [id][xx]" + long_info = """ + #Writes pokémon name to MEMORY1, 2 or 3 for later use in text. + #[40][PKMN no][00-02 MEMORY] + """ + size = 3 + command["map_id"] = ord(rom[start_address+1]) + command["memory_id"] = ord(rom[start_address+2]) + elif command_byte == 0x41: #Display item name [xxyy] + info = "Copy item name (by id) to memX [id][xx]" + long_info = """ + #Writes item name to MEMORY1, 2 or 3 for later use in text. + #[41][Item no][00-02 MEMORY] + """ + size = 3 + command["item_id"] = ord(rom[start_address+1]) + command["memory_id"] = ord(rom[start_address+2]) + elif command_byte == 0x42: #Display location name [xx] + info = "Copy map name to memX [xx]" + long_info = """ + #Writes current location's name to MEMORY1, 2 or 3 for later use in text. + #[42][00-02 MEMORY] + """ + size = 2 + command["memory_id"] = ord(rom[start_address+1]) + elif command_byte == 0x43: #Display trainer name [xxyyzz] + info = "Copy trainer name (by id&group) to memZ [xx][yy][zz]" + long_info = """ + #Writes trainer name to MEMORY1, 2 or 3 for later use in text. + #[43][Trainer number][Trainer group][00-02 MEMORY] + """ + size = 4 + command["trainer_id"] = ord(rom[start_address+1]) + command["trainer_group"] = ord(rom[start_address+2]) + command["memory_id"] = ord(rom[start_address+3]) + elif command_byte == 0x44: #Display strings [2b + xx] + info = "Copy text (by pointer) to memX [aabb][xx]" + long_info = """ + #Writes string to MEMORY1, 2 or 3 for later use in text. + #[44][2byte pointer to string (max. 0x0C figures + 0x50)][00-02 MEMORY] + #See 0C codes: 0C2900, 0C2A00, 0C1B00, 0C2200, Usage of variable strings in text. + """ + size = 4 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["memory_id"] = ord(rom[start_address+3]) + command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) + elif command_byte == 0x45: #Stow away item code + info = "Show HIRO put the ITEMNAME in the ITEMPOCKET text box" + long_info = """ + #Text box: "HIRO put the ITEMNAME in the ITEMPOCKET." + #The item number has to have been loaded beforehand + #(e.g. by Give item code). + """ + size = 1 + elif command_byte == 0x46: #Full item pocket code + info = "Show ITEMPOCKET is full textbox" + long_info = """ + #Text box: "ITEMPOCKET is full..." The item number has to have + #been loaded beforehand (e.g. by Give item code). + """ + size = 1 + elif command_byte == 0x47: #Text box&font code + info = "Loads the font into the ram and opens a text box." + size = 1 + elif command_byte == 0x48: #Refresh code [xx] + info = "Screen refresh [xx]" + long_info = """ + #Executes a complete screen refresh. + #[48][xx] + #xx is a dummy byte + """ + size = 2 + command["dummy"] = ord(rom[start_address+1]) + elif command_byte == 0x49: #Load moving sprites + info = "Load moving sprites into memory" + long_info = "Loads moving sprites for person events into ram." + size = 1 + elif command_byte == 0x4A: #Load byte to C1CE [xx] + info = "Load specific byte to $C1CE [xx]" + long_info = """ + #Loads a byte to C1CE. Seems to have no function in the game. + #[4A][Byte] + """ + size = 2 + command["byte"] = ord(rom[start_address+1]) + elif command_byte == 0x4B: #Display text [3b] + info = "Display text by pointer [bb][xxyy]" + long_info = """ + #Opens a text box and writes text. Doesn't load font. + #[4B][Text bank][2byte text pointer] + """ + size = 4 + command["text_group"] = ord(rom[start_address+1]) + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) + command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) + elif command_byte == 0x4C: #Display text [2b] + info = "Display text by pointer [xxyy]" + long_info = """ + #Opens a text box and writes text. Doesn't load font. + #[4C][2byte text pointer] + """ + size = 3 + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_group, map_id=map_id, debug=debug) + elif command_byte == 0x4D: #Repeat text [xxyy] + info = "Repeat text [FF][FF]" + long_info = """ + #Opens a text box and writes the text written latest resp. whose address was put statically to D175-D177. + #Doesn't load font. + #[4D][FF][FF] + #Without FF for both bytes, no operation occurs + """ + size = 3 + command["bytes"] = rom_interval(start_address+1, 2, strings=False) + elif command_byte == 0x4E: #YES/No box + info = "YES/No box" + long_info = """ + #Displays a YES/NO box at X0F/Y07 + #feedback: + # 00 = no + # 01 = yes + """ + size = 1 + elif command_byte == 0x4F: #Menu data code [2b] + info = "Load menu data by pointer [xxyy]" + long_info = """ + #Loads data for menus + #[4F][2byte pointer to menu data] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA4F + """ + size = 3 + command["menu_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x50: #Write backup code + info = "Write screen backup" + long_info = "Writes backup of parts of the screen the box was overlapping." + size = 1 + elif command_byte == 0x51: #Text1 code [2b] + info = "Display text (by pointer), turn to HIRO, end [xxyy]" + long_info = """ + #Displays a text and lets person turn to HIRO. + #Afterwards there is no other script interpreted. + #Corresponds to 6A + 47 + 4C + 53 + 49 + 90 + #[51][2byte textpointer] + """ + size = 3 + end = True + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_id, map_id=map_id, debug=debug) + elif command_byte == 0x53: #Text2 code [2b] + info = "Display text (by pointer) and end [xxyy]" + long_info = """ + #Displays a text. Afterwards there is no other script interpreted. + #Corresponds to 47 + 4C + 53 + 49 + 90 + #[52][2byte textpointer] + """ + size = 3 + end = True + command["pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["text"] = parse_text_engine_script_at(command["pointer"], map_group=map_id, map_id=map_id, debug=debug) + elif command_byte == 0x54: #Close text box code + info = "Close text box" + long_info = "Closes a text box which was opened by 47 resp. 4B/4C/4D." + size = 1 + elif command_byte == 0x55: #Keep text box open code + info = "Keep text box open" + long_info = "Keeps a text box open which was opened by 47 resp. 4B/4C/4D." + size = 1 + elif command_byte == 0x56: #Pokémon picture code [xx] + info = "Display a pokemon picture in a box by pokemon id [xx]" + long_info = """ + #Opens a box and puts a Pokémon picture into it. + #[55][xx] + #xx: + # <>00 : Pokémon no + # =00 : Pokémon no gets read from RAM + """ + size = 2 + command["byte"] = ord(rom[start_address+1]) + elif command_byte == 0x57: #Pokémon picture YES/NO code + info = "?? Display a pokemon picture and a yes/no box" + long_info = """ + #Displays a YES/NO box at X08/Y05. + #feedback: + # 00 = no chosen + # 01 = yes chosen + """ + size = 1 + elif command_byte == 0x58: #Menu interpreter 1 + info = "Menu interpreter 1 (see menu loader)" + long_info = """ + #Interprets menu data loaded by 4F. + #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA57 + """ + size = 1 + elif command_byte == 0x59: #Menu interpreter 2 + info = "Menu interpreter 2 (see menu loader)" + long_info = """ + #Interprets menu data loaded by 4F. + #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke57 + #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA58 + """ + size = 1 + elif command_byte == 0x5A: #Load Pikachu data + info = "Load pikachu data" + long_info = "Loads 0x19 (Pikachu) to PokéRAM and level 5 to LevelRAM." + size = 1 + elif command_byte == 0x5B: #Delete FightRAM/reset person check + info = "? Disable fleeing from battle" + long_info = """ + #Deletes the value in BattleRAM. + #Turns off the check if the battle was started by entering + #a trainer's area of view. + """ + size = 1 + elif command_byte == 0x5C: #Load trainer data1 + info = "Load trainer from RAM" + long_info = """ + #Loads trainer data when HIRO is in a trainer's range of sight. + #Trainer group is read from CF2E and written to + #TrRAM1, the trainer number is read from CF2F and written to + #TrRAM2. 81 is written to BattleRAM. + """ + size = 1 + elif command_byte == 0x5D: #Load Pokémon data [xxyy] + info = "Loads pokemon by id and level for BattleRAM [xx][yy]" + long_info = """ + #Loads Pokémon data. Writes 80 to BattleRAM. + #[5C][Poke no][Level] + """ + size = 3 + command["pokemon_id"] = ord(rom[start_address+1]) + command["pokemon_level"] = ord(rom[start_address+2]) + elif command_byte == 0x5E: #Load trainer data2 [xxyy] + info = "Load trainer by group/id for BattleRAM [xx][yy]" + long_info = """ + #Loads trainer data. Trainer group --> TrRAM1, + #trainer number --> TrRAM2. Writes 81 to BattleRAM. + #[5D][Trainer group][Trainer no] + """ + size = 3 + command["trainer_group"] = ord(rom[start_address+1]) + command["trainer_id"] = ord(rom[start_address+2]) + elif command_byte == 0x5F: #Start battle + info = "Start pre-configured battle" + long_info = """ + #Starts trainer or Pokémon battle. BattleRAM: 80 = Poké battle; 81 = Trainer battle. + #feedback: + # 00 = win + # 01 = lose + """ + size = 1 + elif command_byte == 0x60: #Return to In game engine after battle + info = "Return to in-game engine after battle" + long_info = "Returns to ingame engine and evaluates battle. When lost then return to last Pokémon Center etc." + size = 1 + elif command_byte == 0x61: #Learn how to catch PKMN [xx] + info = "Pokemon catching tutorial [xx]" + long_info = """ + #Starts a learn-how-to-catch battle with a Pokémon, whose data needs to be loaded beforehand + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke5C + #Player has to have at least 1 Pokémon for it to work. + #Items that are statically used: 1xPotion, 5xPoké ball. + #[60][xx] + #xx: Between 01 and 03. If <> 03 then HIRO sprite instead of dude sprite and kills + #itself when using the item system. + """ + size = 2 + command["byte"] = ord(rom[start_address+1]) + elif command_byte == 0x62: #Trainer text code + info = "Set trainer text by id [xx]" + long_info = """ + #Interprets the data of a in the event structure defined trainer. + #[61][xx] + #Xx decides which text to use. + #xx: Between 00 and 03. + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau + """ + size = 2 + command["byte"] = ord(rom[start_address+1]) + elif command_byte == 0x63: #Trainer status code [xx] + info = "? Check trainer status [xx]" + long_info = """ + #Checks/changes the status of a in the event structure defined trainer. + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau + #[62][xx] + #xx is: + # 00 = deactivate + # 01 = activate + # 02 = check + """ + size = 2 + command["byte"] = ord(rom[start_address+1]) + elif command_byte == 0x64: #Pointer Win/Lose [2b + 2b] + info = "Set win/lose pointers for battle [xxyy][xxyy]" + long_info = """ + #Writes the win/lose pointer of a battle into the ram. + #[63][2byte pointer to text Win][2byte pointer to text Loss*] + #* When pointer = 0000 then "Blackout" instead of return to gameplay. + """ + size = 5 + #sometimes win/lost can be a pointer to 0000 or None? + command["won_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + command["lost_pointer"] = calculate_pointer_from_bytes_at(start_address+3, bank=False) + if command["won_pointer"] == None: + command["won_pointer"] = 0 + else: + command["text_won"] = parse_text_engine_script_at(command["won_pointer"], map_group=map_id, map_id=map_id, debug=debug) + if command["lost_pointer"] == None: + command["lost_pointer"] = 0 + else: + command["text_lost"] = parse_text_engine_script_at(command["lost_pointer"], map_group=map_id, map_id=map_id, debug=debug) + elif command_byte == 0x65: #Script talk-after + #XXX this is a really poor description of whatever this is + info = "? Load the trainer talk-after script" + long_info = """ + #Interprets which script is going to be run, when a in the event-structure-defined + #trainer is talked to again. + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#Eventaufbau + #[64] + """ + size = 1 + elif command_byte == 0x66: #Script talk-after-cancel + info = "Disable/cancel trainer after-battle text" + long_info = """ + #Cancels the talk-after script of the in the event-structure-defined + #trainer when talk-after script is executed just after the battle. + #[65] + """ + size = 1 + elif command_byte == 0x67: #Script talk-after-check + #XXX also bad explanation/name... + info = "? Check if trainer talk-after script is executed just after battle or not" + long_info = """ + #Checks if the talk-after script of the event structure defined trainer + #is executed just after the battle or at a later point in time. + #feedback: + # 00 = no + # 01 = yes + #[66] + """ + size = 1 + elif command_byte == 0x68: #Set talked-to person [xx] + info = "Set last talked-to person [xx]" + long_info = """ + #Sets the number of the last person talked to. + #[67][person] + """ + size = 2 + command["person_id"] = ord(rom[start_address+1]) + elif command_byte == 0x69: #Moving code [xx + 2b] + info = "Move person (by id) with moving data (by pointer) [id][xxyy]" + long_info = """ + #Moves the person using moving data. + #[68][Person][2byte pointer to moving data] + #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzB68bis69 + """ + size = 4 + command["person_id"] = ord(rom[start_address+1]) + command["moving_data_pointer"] = calculate_pointer_from_bytes_at(start_address+2, bank=False) + elif command_byte == 0x6A: #Moving code for talked-to person [2b] + info = "Move talked-to person with moving data (by pointer) [xxyy]" + long_info = """ + #Moves talked-to person using moving data. + #[69][2byte pointer to moving data] + """ + size = 3 + command["moving_data_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x6B: #Talk-to facing code + info = "Move talked-to person's facing direction to HIRO" + long_info = """ + #Turns the heads of the talked-to persons to HIRO. + #[6A] + """ + size = 1 + elif command_byte == 0x6C: #Facing of people code [xxyy] + info = "Move facing direction of person1 to look at person2 [2][1]" + long_info = """ + #Turns the head of person1 to another person2. + #[6B][Person2][Person1] + #Person2 = If number is greater than 0xFD, then use number of talked-to person. + #Person1 = If number equals 0xFE, then take number of talked-to person. + """ + size = 3 + command["person2_id"] = ord(rom[start_address+1]) + command["person1_id"] = ord(rom[start_address+2]) + elif command_byte == 0x6D: #Variable sprites [xxyy] + info = "Store value in variable sprite RAM location x by id Y [xx][yy]" + long_info = """ + #Writes a number to the variable sprite RAM from D555 to D564 (see Compendium on the sprite system). + #[6C][xx][Sprite no] + #xx: Number between 0x00 and 0x0F + """ + size = 3 + command["number"] = ord(rom[start_address+1]) + command["sprite_id"] = ord(rom[start_address+2]) + elif command_byte == 0x6E: #Hide person [xx] + info = "Hide person by id [xx]" + long_info = """ + #Hides a person. + #[6D][person id] + """ + size = 2 + command["person_id"] = ord(rom[start_address+1]) + elif command_byte == 0x6F: #Show person [xx] + info = "Show person by id [xx]" + long_info = """ + #Shows a hidden person again. + #[6E][person id] + """ + size = 2 + command["person_id"] = ord(rom[start_address+1]) + elif command_byte == 0x70: #Following code1 [xxyy] + info = "Following code1 [leader id][follower id]" + long_info = """ + #A person1 follows another person2. The person1 that follows + #just repeats the movement of person2, even if the persons are + #not directly next to each other. + #[6F][Leader Person2][Follower Person1] + """ + size = 3 + command["leader_person_id"] = ord(rom[start_address+1]) + command["follower_person_id"] = ord(rom[start_address+2]) + elif command_byte == 0x71: #Stop following code + info = "Stop all follow code" + long_info = "Ends all current follow codes." + size = 1 + elif command_byte == 0x72: #Move person [xxyyzz] + info = "Move person by id to xy [id][xx][yy]" + long_info = """ + #Sets the X/Y values of a person anew. + #The person doesn't get shown immediately. Use hide&show. + #[71][Person][X][Y] + """ + size = 4 + command["person_id"] = ord(rom[start_address+1]) + command["x"] = ord(rom[start_address+2]) + command["y"] = ord(rom[start_address+3]) + elif command_byte == 0x73: #Write person location [xx] (lock person location?) + info = "Lock person's location by id [id]" + long_info = """ + #Writes the current X/Y values of a person into the ram. + #The person is going to stand at its current location even when + #it's out of HIRO's sight and is not going to return to its old + #location until the next map load. + #[72][person] + """ + size = 2 + command["person_id"] = ord(rom[start_address+1]) + elif command_byte == 0x74: #Load emoticons [xx] + info = "Load emoticon bubble [xx]" + long_info = """ + #Loads the emoticon bubble depending on the given bubble number. + #[73][bubble number] + #xx: If xx = FF then take number from RAM. + # 00 = Exclamation mark + # 01 = Question mark + # 02 = Happy + # 03 = Sad + # 04 = Heart + # 05 = Flash + # 06 = Snoring + # 07 = Fish + """ + size = 2 + command["bubble_number"] = ord(rom[start_address+1]) + elif command_byte == 0x75: #Display emoticon [xxyyzz] + info = "Display emoticon by bubble id and person id and time [xx][yy][zz]" + long_info = """ + #Displays the bubble above a persons head for the given time period. + #Attention: Bubbles get loaded into ram! + #[74][Bubble][Person][Time] + #for bubble ids see 0x73 + """ + size = 4 + command["bubble_number"] = ord(rom[start_address+1]) + command["person_id"] = ord(rom[start_address+2]) + command["time"] = ord(rom[start_address+3]) + elif command_byte == 0x76: #Change facing [xxyy] + info = "Set facing direction of person [person][facing]" + long_info = """ + #Changes the facing direction of a person. + #[75][person][facing] + """ + size = 3 + command["person_id"] = ord(rom[start_address+1]) + command["facing"] = ord(rom[start_address+2]) + elif command_byte == 0x77: #Following code2 [xxyy] + info = "Following code2 [leader id][follower id]" + long_info = """ + #A person1 follows a person2. The following person1 automatically clings to person2. + #Person1 just follows person2, but doesn't make the exact same movements at person2. + #[76][Leader Person2][Follower Person1] + """ + size = 3 + command["leader_person_id"] = ord(rom[start_address+1]) + command["follower_person_id"] = ord(rom[start_address+2]) + elif command_byte == 0x78: #Earth quake [xx] + info = "Earthquake [xx]" + long_info = """ + #The screen shakes. xx gives time as well as displacement of the screen. + #[77][xx] + """ + size = 2 + command["shake_byte"] = ord(rom[start_address+1]) + elif command_byte == 0x79: #Exchange map [3b] + info = "Draw map data over current map [bank][pointer]" + long_info = """ + #This code draws another whole map as wide and high as the + #current map over the current map. + #The 3byte pointer points to the new map. + #[78][3byte pointer to new map data] + """ + size = 4 + command["map_data_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) + elif command_byte == 0x7A: #Change block code [xxyyzz] + info = "Change block to block id on map [xx][yy][id]" + long_info = """ + #Changes a block on the current map by giving the new block + #number and its X/Y values measured in half-blocks. + #[79][X][Y][Block] + """ + size = 4 + command["x"] = ord(rom[start_address+1]) + command["y"] = ord(rom[start_address+2]) + command["block"] = ord(rom[start_address+3]) + elif command_byte == 0x7B: #Reload map code + info = "Reload/redisplay map" + long_info = """ + #Reloads and re-displays the map completely. + #Loads tileset and all map data anew. Screen gets light. + #[7A] + """ + size = 1 + elif command_byte == 0x7C: #Reload map part code + info = "Reload/redisplay map portion occupied by HIRO" + long_info = """ + #Reloads and re-displays the part of the map HIRO is on, + #without reloading any other map data or the tileset. + #[7B] + """ + size = 1 + elif command_byte == 0x7D: #Write command queue + info = "Write command queue [xxyy]" + long_info = """ + #Writes a command queue to the next free slot in ram. + #Max 4 command queues à 5 bytes. This code is buggy (bug fix: 25:7C74 --> 12). + #[7C][2byte pointer to 5byte command queue] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok25_7CC9 + """ + size = 3 + command["command_queue_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x7E: #Delete command queue + info = "Delete command queue" + long_info = """ + #Deletes a command queue and frees a slot in ram. + #[7D][First command of the resp. queue] + """ + #XXX wtf? + size = 2 + command["first_command"] = ord(rom[start_address+1]) + elif command_byte == 0x7F: #Song code1 [xxyy] + info = "Play music by number [xxyy]" + long_info = """ + #Immediately plays the music. + #[7E][Music no (2byte)] + #Music no: See the music archive that should accompany + #this document Thanks to Filb. He dumped all the songs + #via gameboy player and gave them to me. + """ + size = 3 + #XXX what is the format of this music data? + command["music_number"] = rom_interval(start_address+1, size-1, strings=False) + elif command_byte == 0x80: #Song code2 + info = "Song code2" + long_info = """ + #Plays the music of the trainer group in TrRAM1. + #Takes music numbers from list at 3A:5027. + #[7F] + """ + size = 1 + elif command_byte == 0x81: #Music fade-out code [xxyy][zz] + info = "Music fade-out then play next [xxyy][time]" + long_info = """ + #The current music is faded out and the new music is played afterwards. + #[80][Music no (2byte)][Time to fade out (00-7F)] + """ + size = 4 + command["music_number"] = rom_interval(start_address+1, 2, strings=False) + command["fade_time"] = ord(rom[start_address+3]) + elif command_byte == 0x82: #Play map music code + info = "Play map's music" + long_info = """ + #Starts playing the original map music. + #Includes special check for surfer and bug contest song. + #[81] + """ + size = 1 + elif command_byte == 0x83: #Map reload music code + info = "Reload map music" + long_info = """ + #After a map reload no music is played. + #[82] + """ + size = 1 + elif command_byte == 0x84: #Cry code [xx00] + info = "Play cry by id or RAM [cry][00]" + long_info = """ + #Plays the Pokémon's cry. + #[83][Cry no][00] + #If the cry no = 00 then the number is taken from RAM. + """ + size = 3 + command["cry_number"] = ord(rom[start_address+1]) + command["other_byte"] = ord(rom[start_address+2]) + elif command_byte == 0x85: #Sound code [xxyy] + info = "Play sound by sound number [xxyy]" + long_info = """ + #Plays the sound. + #[84][Sound no (2byte)] + #Sound no: See the music archive that should accompany this document + #Thanks to philb for this matter. He helped me to record a big + #part of these sounds. + """ + size = 3 + command["sound_number"] = rom_interval(start_address+1, 2, strings=False) + elif command_byte == 0x86: #Key-down code + info = "Wait for key-down" + long_info = """ + #Waits for the Player to press a button. + #[85] + """ + size = 1 + elif command_byte == 0x87: #Warp sound + info = "Warp sound" + long_info = """ + #Evaluates which sound is played when HIRO enters a Warp field. + #Usage via script ingame is rather not useful. + #[86] + """ + size = 1 + elif command_byte == 0x88: #Special sound + info = "Special sound if TM was last checked" + long_info = """ + #When last given/checked Item was a TM then it plays sound 0x9B. If not, then 0x01. + #[87] + """ + size = 1 + elif command_byte == 0x89: #Engine remote control [2b] + info = "Engine remote control [bb][xxyy]" + long_info = """ + #This code controls the engine via "data stream". + #[88][3byte pointer to control structure] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDatA88 + """ + size = 4 + command["data_stream_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=True) + elif command_byte == 0x8A: #Load map anew [xx] + info = "Load map with specific loading process [xx]" + long_info = """ + #The number decides which map loading process is used. + #The number must be 0xF0 + process number to work correctly. + #[89][Number] + #see map loading process: + # http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok5_5550 + """ + size = 2 + command["number"] = ord(rom[start_address+1]) + elif command_byte == 0x8B: #Waiting code [xx] + info = "Wait code" + long_info = """ + #This code lets the game wait for 2 * xx time intervals. + #[8A][xx] + #xx: Numbers from 0x01 to 0xFF. + #If 0x00 is chosen then the time can be manipulated by previously loading a number to RAM2. + """ + size = 2 + command["time"] = ord(rom[start_address+1]) + elif command_byte == 0x8C: #Deactivate static facing [xx] + info = "Deactive static facing after time [xx]" + long_info = """ + #Deactivates static facings on all persons on the screen after a time xx. + #[8B][xx] + """ + size = 2 + command["time"] = ord(rom[start_address+1]) + elif command_byte == 0x8D: #Priority jump1 [2b] + info = "Priority jump to script by pointer [xxyy]" + long_info = """ + #The pointer acts like code 00, but with this higher + #functions like the bike etc. are not paid attention to, + #while the script is running. + #[8C][2byte pointer to script] + """ + size = 3 + script_pointer = calculate_pointer_from_bytes_at(start_address+1, bank=False) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(script_pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(script_pointer, original_start_address, debug=debug) + command["script_pointer"] = script_pointer + command["script"] = script + end = True #according to pksv + elif command_byte == 0x8E: #Warp check + info = "Reactive all engine checks if player is warping" + long_info = """ + #If HIRO is entering or leaving a warp then this code reactivates all the engine-checks. + #[8D] + """ + size = 1 + elif command_byte == 0x8F: #Priority jump2 [2b] + info = "Priority jump to script by pointer (after 1st cycle) [xxyy]" + long_info = """ + #The pointer acts like code 03, but with this code all + #higher functions wait for a cycle before the script gets interpreted. + #[8E][2byte pointer to script] + """ + size = 3 + script_pointer = calculate_pointer_from_bytes_at(start_address+1, bank=False) + if debug: + print "in script starting at "+hex(original_start_address)+\ + " about to parse script at "+hex(script_pointer)+\ + " called by "+info+" byte="+hex(command_byte) + script = rec_parse_script_engine_script_at(script_pointer, original_start_address, debug=debug) + command["script_pointer"] = script_pointer + command["script"] = script + end = True #according to pksv + elif command_byte == 0x90: #Return code1 + info = "Return code 1" + long_info = """ + #Ends the current script and loads the backup offset for "linked" + #scripts if applicable. The sophisticated functions are not affected + #and run like before the code. This code is mostly used for scripts + #called by the 2nd part of the script header, because else malfunctions + #occur. + #[8F] + """ + size = 1 + end = True + elif command_byte == 0x91: #Return code2 + info = "Return code 2" + long_info = """ + #Ends the current script and loads the backup offset for "linked" + #scripts if applicable. The sophisticated functions get reset if + #no backup offset was loaded. This code is used to end most scripts. + #[90] + """ + size = 1 + end = True + elif command_byte == 0x92: #Return code3 + info = "Return code 3" + long_info = """ + #Reloads the map completely like the code 0x7A + #and else acts completely like Return code2 + #[91] + #see reload map code + # http://hax.iimarck.us/files/scriptingcodes_eng.htm#Marke7A + #see 0x90 + """ + size = 1 + #XXX does this end the script?? "else acts like 0x90" + # else? what's the "if"? + end = True + elif command_byte == 0x93: #Reset sophisticated functions + info = "Reset sophisticated functions" + long_info = """ + #Resets all sophisticated functions to 0. + #[92] + """ + size = 1 + elif command_byte == 0x94: #Mart menu [xxyyzz] + info = "Mart menu [dialog no][mart no 2b]" + long_info = """ + #Displays a whole mart menu, however, doesn't load font to ram. + #[93][Dialog no][Mart no (2byte)] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#AwBsp93 + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzB93 + """ + size = 4 + command["dialog_number"] = ord(rom[start_address+1]) + command["mart_number"] = rom_interval(start_address+2, 2, strings=False) + elif command_byte == 0x95: #Elevator menu [2b] + info = "Display elevator menu by pointer [xxyy]" + long_info = """ + #Displays a whole elevator menu, but it doesn't load font to ram. + #Only works with warps with warp-to = 0xFF. + #[94][2byte pointer to floor list] + """ + size = 3 + command["floor_list_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x96: #Trade menu [xx] + info = "Display trade menu by trade id [xx]" + long_info = """ + #Displays a whole trade menu, but it doesn't load font to ram. + #[95][trade no] + #see http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDokTausch + """ + size = 2 + command["trade_number"] = ord(rom[start_address+1]) + elif command_byte == 0x97: #Give cell phone number with YES/NO [xx] + info = "Give cell phone number by id with YES/NO [id]" + long_info = """ + #Gives a telephone number but asks for decision beforehand. + #feedback: + # 00 = ok chosen + # 01 = Cell phone number already registered/Memory full + # 02 = no chosen + #[96][Cell phone number] + """ + size = 2 + #maybe this next param should be called "phone_number" + command["number"] = ord(rom[start_address+1]) + elif command_byte == 0x98: #Call code [2b] + info = "Call code pointing to name of caller [xxyy]" + long_info = """ + #Displays the upper cell phone box and displays a freely selectable name. + #[97][2byte pointer to name of caller] + """ + size = 3 + command["caller_name_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank=False) + elif command_byte == 0x99: #Hang-up code + info = "Hang-up phone" + long_info = """ + #Simulates the hanging-up. + #[98] + """ + size = 1 + elif command_byte == 0x9A: #Decoration code [xx] + info = "Set monologue decoration [xx]" + long_info = """ + #Displays monologues according to the selected ornament. + #[99][xx] + #xx values: + # 00 = Map/Poster + # 01 = Ornament left + # 02 = Ornament right + # 03 = Huge doll + # 04 = Console + """ + size = 2 + command["ornament"] = ord(rom[start_address+1]) + elif command_byte == 0x9B: #Berry tree code [xx] + info = "Berry tree by tree id [xx]" + long_info = """ + #Creates a typical berry tree monologue. + #There is a maximum of 32 berry trees in the game. + #After this code the script ends. + #[9A][Fruit tree number] + #Fruit tree number + 11:4091 is the offset where the item no of the berry is defined. + """ + size = 2 + end = True + command["tree_id"] = ord(rom[start_address+1]) + elif command_byte == 0x9C: #Cell phone call code [xx00] + #XXX confirm this? + info = "Cell phone call [2-byte call id]" #was originally: [call id][00] + long_info = """ + #Initiates with the next step on a outer world map (permission byte) a phone call. + #[9B][Call no] and maybe [00] ??? + #call no: + # 01 = PokéRus + # 02 = Pokémon stolen + # 03 = Egg examined/ Assistant in Viola City + # 04 = Team Rocket on the radio + # 05 = PROF. ELM has got something for HIRO + # 06 = Bike shop gives bike away + # 07 = Mother is unhappy that HIRO didn't talk to her before leaving + # 08 = PROF. ELM has got something for HIRO a second time + """ + size = 3 + command["call_id"] = ord(rom[start_address+1]) + command["id"] = rom_interval(start_address+2, 2, strings=False) + elif command_byte == 0x9D: #Check cell phone call code + info = "Check if/which a phone call is active" + long_info = """ + #Checks if a phone call is "in the line". + #feedback: + # 00 = no + # <>00 = call number + #[9C] + """ + size = 1 + elif command_byte == 0x9E: #Commented give item code [xxyy] + info = "Give item by id and quantity with 'put in pocket' text [id][qty]" + long_info = """ + #The same as 0x1F but this code comments where + #HIRO puts what item in a short monologue. + #[9D][Item][Amount] + """ + size = 3 + command["item_id"] = ord(rom[start_address+1]) + command["quantity"] = ord(rom[start_address+2]) + elif command_byte == 0x9F: #Commented ive item code? + info = "Give item by id and quantity with 'put in pocket' text [id][qty]" + long_info = """ + #The same as 0x1F but this code comments where + #HIRO puts what item in a short monologue. + #[9D][Item][Amount] + """ + size = 3 + command["item_id"] = ord(rom[start_address+1]) + command["quantity"] = ord(rom[start_address+2]) + elif command_byte == 0xA0: #Load special wild PKMN data [xxyy] + info = "Load wild pokemon data for a remote map [map group][map id]" + long_info = """ + #Activates the checks in the special tables for the wild pokémon data. + #[9E][map group][map id] + #see also http://hax.iimarck.us/files/scriptingcodes_eng.htm#ZusatzDok3E_66ED + """ + size = 3 + command["map_group"] = ord(rom[start_address+1]) + command["map_id"] = ord(rom[start_address+2]) + elif command_byte == 0xA1: #Hall of Fame code + info = "Hall of Fame" + long_info = """ + #Saves and enters HIRO's complete Team in the Hall of Fame. + #Shows the credits and restarts the game with HIRO located in New Bark Town. + #[9F] + """ + size = 1 + elif command_byte == 0xA2: #Credits code + info = "Credits" + long_info = """ + #Shows the credits and HIRO is located on the Silver mountain plateau. + #[A0] + """ + size = 1 + elif command_byte == 0xA3: #Facing warp + info = "Warp-to and set facing direction [Facing (00-03)][Map bank][Map no][X][Y]" + long_info = """ + #Acts like code 0x3C but defines the desired facing of HIRO. + #[A1][Facing (00-03)][Map bank][Map no][X][Y] + """ + size = 6 + command["facing"] = ord(rom[start_address+1]) + command["map_group"] = ord(rom[start_address+2]) + command["map_id"] = ord(rom[start_address+3]) + command["x"] = ord(rom[start_address+4]) + command["y"] = ord(rom[start_address+5]) + elif command_byte == 0xA4: #MEMORY code [2b + Bank + xx] + info = "Set memX to a string by a pointer [aabb][bank][xx]" + long_info = """ + #MEMORY1, 2 or 3 can directly be filled with a string from + #a different rom bank. + #[A2][2byte pointer][Bank][00-02 MEMORY] + """ + size = 5 + command["string_pointer"] = calculate_pointer_from_bytes_at(start_address+1, bank="reversed") + command["string_pointer_bank"] = ord(rom[start_address+3]) + command["memory_id"] = ord(rom[start_address+4]) + elif command_byte == 0xA5: #Display any location name [xx] + info = "Copy the name of a location (by id) to TEMPMEMORY1" + long_info = """ + #By the location number the name of that location is written to TEMPMEMORY1. + #[A3][Location no] + """ + size = 2 + command["location_number"] = ord(rom[start_address+1]) + else: + size = 1 + #end = True + #raise NotImplementedError, "command byte is " + hex(command_byte) + " at " + hex(offset) + " on map " + str(map_group) + "." + str(map_id) + print "dunno what this command is: " + hex(command_byte) + long_info = clean_up_long_info(long_info) + + if command_byte in pksv_crystal.keys(): + pksv_name = pksv_crystal[command_byte] + else: + pksv_name = None + if command_byte in pksv_no_names.keys(): + pksv_no_names[command_byte].append(address) + else: + pksv_no_names[command_byte] = [address] + + if debug: + print command_debug_information(command_byte=command_byte, map_group=map_group, map_id=map_id, address=offset, info=info, long_info=long_info, pksv_name=pksv_name) + + #store the size of the command + command["size"] = size + #the end address is just offset + size - 1 (because size includes command byte) + offset += size - 1 + #the end address is the last byte belonging to this command + command["last_byte_address"] = offset + #we also add the size of the command byte to get to the next command + offset += 1 + #add the command into the command list please + commands[len(commands.keys())] = command + + self.commands = commands + script_parse_table[original_start_address : offset] = self + return True diff --git a/pokemontools/overworldripper.py b/pokemontools/overworldripper.py new file mode 100644 index 0000000..654f287 --- /dev/null +++ b/pokemontools/overworldripper.py @@ -0,0 +1,18 @@ +import gfx + +def rip_sprites_from_bank(bank, offset=0): + """ + Rips sprites from specified bank. + + Sprites are 4x4. + """ + file_handler = open("../gfx/overworld/bank" + str(hex(bank))[2:] + ".asm", "w") + for sprite in range(0 + offset, 256 + offset): + filename = "../gfx/overworld/" + str(sprite).zfill(3) + ".2bpp" + gfx.get_uncompressed_gfx((bank * 0x4000 + ((sprite - offset) * 4 * 0x10)), 4, filename) + gfx.to_png(filename) + file_handler.write("INCBIN \"" + filename[3:] + "\"\n") + file_handler.close() + +rip_sprites_from_bank(0x30) +rip_sprites_from_bank(0x31, offset=256) diff --git a/pokemontools/parse_consecutive_strings.py b/pokemontools/parse_consecutive_strings.py new file mode 100644 index 0000000..4c864ba --- /dev/null +++ b/pokemontools/parse_consecutive_strings.py @@ -0,0 +1,25 @@ +import sys + +import crystal + +rom = crystal.load_rom() + +addr = int(sys.argv[1], 16) +count = int(sys.argv[2]) if len(sys.argv) > 2 else 256 +label_prefix = sys.argv[3] if len(sys.argv) > 3 else "UnknownString" + +ex = None + +for i in range(count): + try: + string = crystal.PokedexText(addr) + asm = string.to_asm() + except Exception as ex: + break + print label_prefix+str(i)+": ; "+hex(addr) + print "\t"+asm + print + addr = string.last_address + +print "; "+hex(addr) +if ex: raise ex diff --git a/pokemontools/pksv.py b/pokemontools/pksv.py new file mode 100644 index 0000000..2f02ec0 --- /dev/null +++ b/pokemontools/pksv.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +pksv_gs = { + 0x00: "2call", + 0x01: "3call", + 0x02: "2ptcall", + 0x03: "2jump", + 0x04: "3jump", + 0x05: "2ptjump", + 0x06: "if equal", + 0x07: "if not equal", + 0x08: "if false", + 0x09: "if true", + 0x0A: "if less than", + 0x0B: "if greater than", + 0x0C: "jumpstd", + 0x0D: "callstd", + 0x0E: "3callasm", + 0x0F: "special", + 0x10: "2ptcallasm", + 0x11: "checkmaptriggers", + 0x12: "domaptrigger", + 0x13: "checktriggers", + 0x14: "dotrigger", + 0x15: "writebyte", + 0x16: "addvar", + 0x17: "random", + 0x19: "copybytetovar", + 0x1A: "copyvartobyte", + 0x1B: "loadvar", + 0x1C: "checkcode", + 0x1E: "writecode", + 0x1F: "giveitem", + 0x20: "takeitem", + 0x21: "checkitem", + 0x22: "givemoney", + 0x23: "takemoney", + 0x24: "checkmoney", + 0x25: "givecoins", + 0x26: "takecoins", + 0x27: "checkcoins", + 0x2B: "checktime", + 0x2C: "checkpoke", + 0x2D: "givepoke", + 0x2E: "giveegg", + 0x2F: "givepokeitem", + 0x31: "checkbit1", + 0x32: "clearbit1", + 0x33: "setbit1", + 0x34: "checkbit2", + 0x35: "clearbit2", + 0x36: "setbit2", + 0x37: "wildoff", + 0x38: "wildon", + 0x39: "xycompare", + 0x3A: "warpmod", + 0x3B: "blackoutmod", + 0x3C: "warp", + 0x41: "itemtotext", + 0x43: "trainertotext", + 0x44: "stringtotext", + 0x45: "itemnotify", + 0x46: "pocketisfull", + 0x47: "loadfont", + 0x48: "refreshscreen", + 0x49: "loadmovesprites", + 0x4B: "3writetext", + 0x4C: "2writetext", + 0x4E: "yesorno", + 0x4F: "loadmenudata", + 0x50: "writebackup", + 0x51: "jumptextfaceplayer", + 0x52: "jumptext", + 0x53: "closetext", + 0x54: "keeptextopen", + 0x55: "pokepic", + 0x56: "pokepicyesorno", + 0x57: "interpretmenu", + 0x58: "interpretmenu2", + 0x5C: "loadpokedata", + 0x5D: "loadtrainer", + 0x5E: "startbattle", + 0x5F: "returnafterbattle", + 0x60: "catchtutorial", + 0x63: "winlosstext", + 0x65: "talkaftercancel", + 0x67: "setlasttalked", + 0x68: "applymovement", + 0x6A: "faceplayer", + 0x6B: "faceperson", + 0x6C: "variablesprite", + 0x6D: "disappear", + 0x6E: "appear", + 0x6F: "follow", + 0x70: "stopfollow", + 0x71: "moveperson", + 0x74: "showemote", + 0x75: "spriteface", + 0x76: "follownotexact", + 0x77: "earthquake", + 0x79: "changeblock", + 0x7A: "reloadmap", + 0x7B: "reloadmappart", + 0x7C: "writecmdqueue", + 0x7D: "delcmdqueue", + 0x7E: "playmusic", + 0x7F: "playrammusic", + 0x80: "musicfadeout", + 0x81: "playmapmusic", + 0x82: "reloadmapmusic", + 0x83: "cry", + 0x84: "playsound", + 0x85: "waitbutton", + 0x86: "warpsound", + 0x87: "specialsound", + 0x88: "passtoengine", + 0x89: "newloadmap", + 0x8A: "pause", + 0x8B: "deactivatefacing", + 0x8C: "priorityjump", + 0x8D: "warpcheck", + 0x8E: "ptpriorityjump", + 0x8F: "return", + 0x90: "end", + 0x91: "reloadandreturn", + 0x92: "resetfuncs", + 0x93: "pokemart", + 0x94: "elevator", + 0x95: "trade", + 0x96: "askforphonenumber", + 0x97: "phonecall", + 0x98: "hangup", + 0x99: "describedecoration", + 0x9A: "fruittree", + 0x9C: "checkphonecall", + 0x9D: "verbosegiveitem", + 0x9E: "loadwilddata", + 0x9F: "halloffame", + 0xA0: "credits", + 0xA1: "warpfacing", + 0xA2: "storetext", + 0xA3: "displaylocation", +} + +# see http://www.pokecommunity.com/showpost.php?p=4347261 +# NOTE: this has some updates that need to be back-ported to gold +pksv_crystal = { + 0x00: "2call", + 0x01: "3call", + 0x02: "2ptcall", + 0x03: "2jump", + 0x04: "3jump", + 0x05: "2ptjump", + 0x06: "if equal", + 0x07: "if not equal", + 0x08: "if false", + 0x09: "if true", + 0x0A: "if less than", + 0x0B: "if greater than", + 0x0C: "jumpstd", + 0x0D: "callstd", + 0x0E: "3callasm", + 0x0F: "special", + 0x10: "2ptcallasm", + 0x11: "checkmaptriggers", + 0x12: "domaptrigger", + 0x13: "checktriggers", + 0x14: "dotrigger", + 0x15: "writebyte", + 0x16: "addvar", + 0x17: "random", + 0x19: "copybytetovar", + 0x1A: "copyvartobyte", + 0x1B: "loadvar", + 0x1C: "checkcode", + 0x1D: "writevarcode", + 0x1E: "writecode", + 0x1F: "giveitem", + 0x20: "takeitem", + 0x21: "checkitem", + 0x22: "givemoney", + 0x23: "takemoney", + 0x24: "checkmoney", + 0x25: "givecoins", + 0x26: "takecoins", + 0x27: "checkcoins", + 0x28: "addcellnum", + 0x29: "delcellnum", + 0x2B: "checktime", + 0x2C: "checkpoke", + 0x2D: "givepoke", + 0x2E: "giveegg", + 0x2F: "givepokeitem", + 0x31: "checkbit1", + 0x32: "clearbit1", + 0x33: "setbit1", + 0x34: "checkbit2", + 0x35: "clearbit2", + 0x36: "setbit2", + 0x37: "wildoff", + 0x38: "wildon", + 0x39: "xycompare", + 0x3A: "warpmod", + 0x3B: "blackoutmod", + 0x3C: "warp", + 0x41: "itemtotext", + 0x43: "trainertotext", + 0x44: "stringtotext", + 0x45: "itemnotify", + 0x46: "pocketisfull", + 0x47: "loadfont", + 0x48: "refreshscreen", + 0x49: "loadmovesprites", + 0x4B: "3writetext", + 0x4C: "2writetext", + 0x4E: "yesorno", + 0x4F: "loadmenudata", + 0x50: "writebackup", + 0x51: "jumptextfaceplayer", + 0x53: "jumptext", + 0x54: "closetext", + 0x55: "keeptextopen", + 0x56: "pokepic", + 0x57: "pokepicyesorno", + 0x58: "interpretmenu", + 0x59: "interpretmenu2", + 0x5D: "loadpokedata", + 0x5E: "loadtrainer", + 0x5F: "startbattle", + 0x60: "returnafterbattle", + 0x61: "catchtutorial", + 0x64: "winlosstext", + 0x66: "talkaftercancel", + 0x68: "setlasttalked", + 0x69: "applymovement", + 0x6B: "faceplayer", + 0x6C: "faceperson", + 0x6D: "variablesprite", + 0x6E: "disappear", + 0x6F: "appear", + 0x70: "follow", + 0x71: "stopfollow", + 0x72: "moveperson", + 0x75: "showemote", + 0x76: "spriteface", + 0x77: "follownotexact", + 0x78: "earthquake", + 0x7A: "changeblock", + 0x7B: "reloadmap", + 0x7C: "reloadmappart", + 0x7D: "writecmdqueue", + 0x7E: "delcmdqueue", + 0x7F: "playmusic", + 0x80: "playrammusic", + 0x81: "musicfadeout", + 0x82: "playmapmusic", + 0x83: "reloadmapmusic", + 0x84: "cry", + 0x85: "playsound", + 0x86: "waitbutton", + 0x87: "warpsound", + 0x88: "specialsound", + 0x89: "passtoengine", + 0x8A: "newloadmap", + 0x8B: "pause", + 0x8C: "deactivatefacing", + 0x8D: "priorityjump", + 0x8E: "warpcheck", + 0x8F: "ptpriorityjump", + 0x90: "return", + 0x91: "end", + 0x92: "reloadandreturn", + 0x93: "resetfuncs", + 0x94: "pokemart", + 0x95: "elevator", + 0x96: "trade", + 0x97: "askforphonenumber", + 0x98: "phonecall", + 0x99: "hangup", + 0x9A: "describedecoration", + 0x9B: "fruittree", + 0x9C: "specialphonecall", + 0x9D: "checkphonecall", + 0x9E: "verbosegiveitem", + 0x9F: "verbosegiveitem2", + 0xA0: "loadwilddata", + 0xA1: "halloffame", + 0xA2: "credits", + 0xA3: "warpfacing", + 0xA4: "storetext", + 0xA5: "displaylocation", + 0xB2: "unknown0xb2", +} + +#these cause the script to end; used in create_command_classes +pksv_crystal_more_enders = [0x03, 0x04, 0x05, 0x0C, 0x51, 0x52, + 0x53, 0x8D, 0x8F, 0x90, 0x91, 0x92, + 0x9B, + 0xB2, #maybe? + 0xCC, #maybe? + 0x9A, # describedecoration + ] + +# these have no pksv names as of pksv 2.1.1 +pksv_crystal_unknowns = [ + 0x9F, + 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, + 0xCC, 0xCD, + 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, + 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, +] diff --git a/pokemontools/pointers.py b/pokemontools/pointers.py new file mode 100644 index 0000000..161a53e --- /dev/null +++ b/pokemontools/pointers.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +""" +Various functions related to pointer and address math. Mostly to avoid +depedency loops. +""" + +def calculate_bank(address): + """you are too lazy to divide on your own?""" + if type(address) == str: + address = int(address, 16) + #if 0x4000 <= address <= 0x7FFF: + # raise Exception, "bank 1 does not exist" + return int(address) / 0x4000 + +def calculate_pointer(short_pointer, bank=None): + """calculates the full address given a 4-byte pointer and bank byte""" + short_pointer = int(short_pointer) + if 0x4000 <= short_pointer <= 0x7fff: + short_pointer -= 0x4000 + bank = int(bank) + else: + bank = 0 + pointer = short_pointer + (bank * 0x4000) + return pointer diff --git a/pokemontools/pokemon_constants.py b/pokemontools/pokemon_constants.py new file mode 100644 index 0000000..221a31c --- /dev/null +++ b/pokemontools/pokemon_constants.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- + +pokemon_constants = { +1: "BULBASAUR", +2: "IVYSAUR", +3: "VENUSAUR", +4: "CHARMANDER", +5: "CHARMELEON", +6: "CHARIZARD", +7: "SQUIRTLE", +8: "WARTORTLE", +9: "BLASTOISE", +10: "CATERPIE", +11: "METAPOD", +12: "BUTTERFREE", +13: "WEEDLE", +14: "KAKUNA", +15: "BEEDRILL", +16: "PIDGEY", +17: "PIDGEOTTO", +18: "PIDGEOT", +19: "RATTATA", +20: "RATICATE", +21: "SPEAROW", +22: "FEAROW", +23: "EKANS", +24: "ARBOK", +25: "PIKACHU", +26: "RAICHU", +27: "SANDSHREW", +28: "SANDSLASH", +29: "NIDORAN_F", +30: "NIDORINA", +31: "NIDOQUEEN", +32: "NIDORAN_M", +33: "NIDORINO", +34: "NIDOKING", +35: "CLEFAIRY", +36: "CLEFABLE", +37: "VULPIX", +38: "NINETALES", +39: "JIGGLYPUFF", +40: "WIGGLYTUFF", +41: "ZUBAT", +42: "GOLBAT", +43: "ODDISH", +44: "GLOOM", +45: "VILEPLUME", +46: "PARAS", +47: "PARASECT", +48: "VENONAT", +49: "VENOMOTH", +50: "DIGLETT", +51: "DUGTRIO", +52: "MEOWTH", +53: "PERSIAN", +54: "PSYDUCK", +55: "GOLDUCK", +56: "MANKEY", +57: "PRIMEAPE", +58: "GROWLITHE", +59: "ARCANINE", +60: "POLIWAG", +61: "POLIWHIRL", +62: "POLIWRATH", +63: "ABRA", +64: "KADABRA", +65: "ALAKAZAM", +66: "MACHOP", +67: "MACHOKE", +68: "MACHAMP", +69: "BELLSPROUT", +70: "WEEPINBELL", +71: "VICTREEBEL", +72: "TENTACOOL", +73: "TENTACRUEL", +74: "GEODUDE", +75: "GRAVELER", +76: "GOLEM", +77: "PONYTA", +78: "RAPIDASH", +79: "SLOWPOKE", +80: "SLOWBRO", +81: "MAGNEMITE", +82: "MAGNETON", +83: "FARFETCH_D", +84: "DODUO", +85: "DODRIO", +86: "SEEL", +87: "DEWGONG", +88: "GRIMER", +89: "MUK", +90: "SHELLDER", +91: "CLOYSTER", +92: "GASTLY", +93: "HAUNTER", +94: "GENGAR", +95: "ONIX", +96: "DROWZEE", +97: "HYPNO", +98: "KRABBY", +99: "KINGLER", +100: "VOLTORB", +101: "ELECTRODE", +102: "EXEGGCUTE", +103: "EXEGGUTOR", +104: "CUBONE", +105: "MAROWAK", +106: "HITMONLEE", +107: "HITMONCHAN", +108: "LICKITUNG", +109: "KOFFING", +110: "WEEZING", +111: "RHYHORN", +112: "RHYDON", +113: "CHANSEY", +114: "TANGELA", +115: "KANGASKHAN", +116: "HORSEA", +117: "SEADRA", +118: "GOLDEEN", +119: "SEAKING", +120: "STARYU", +121: "STARMIE", +122: "MR__MIME", +123: "SCYTHER", +124: "JYNX", +125: "ELECTABUZZ", +126: "MAGMAR", +127: "PINSIR", +128: "TAUROS", +129: "MAGIKARP", +130: "GYARADOS", +131: "LAPRAS", +132: "DITTO", +133: "EEVEE", +134: "VAPOREON", +135: "JOLTEON", +136: "FLAREON", +137: "PORYGON", +138: "OMANYTE", +139: "OMASTAR", +140: "KABUTO", +141: "KABUTOPS", +142: "AERODACTYL", +143: "SNORLAX", +144: "ARTICUNO", +145: "ZAPDOS", +146: "MOLTRES", +147: "DRATINI", +148: "DRAGONAIR", +149: "DRAGONITE", +150: "MEWTWO", +151: "MEW", +152: "CHIKORITA", +153: "BAYLEEF", +154: "MEGANIUM", +155: "CYNDAQUIL", +156: "QUILAVA", +157: "TYPHLOSION", +158: "TOTODILE", +159: "CROCONAW", +160: "FERALIGATR", +161: "SENTRET", +162: "FURRET", +163: "HOOTHOOT", +164: "NOCTOWL", +165: "LEDYBA", +166: "LEDIAN", +167: "SPINARAK", +168: "ARIADOS", +169: "CROBAT", +170: "CHINCHOU", +171: "LANTURN", +172: "PICHU", +173: "CLEFFA", +174: "IGGLYBUFF", +175: "TOGEPI", +176: "TOGETIC", +177: "NATU", +178: "XATU", +179: "MAREEP", +180: "FLAAFFY", +181: "AMPHAROS", +182: "BELLOSSOM", +183: "MARILL", +184: "AZUMARILL", +185: "SUDOWOODO", +186: "POLITOED", +187: "HOPPIP", +188: "SKIPLOOM", +189: "JUMPLUFF", +190: "AIPOM", +191: "SUNKERN", +192: "SUNFLORA", +193: "YANMA", +194: "WOOPER", +195: "QUAGSIRE", +196: "ESPEON", +197: "UMBREON", +198: "MURKROW", +199: "SLOWKING", +200: "MISDREAVUS", +201: "UNOWN", +202: "WOBBUFFET", +203: "GIRAFARIG", +204: "PINECO", +205: "FORRETRESS", +206: "DUNSPARCE", +207: "GLIGAR", +208: "STEELIX", +209: "SNUBBULL", +210: "GRANBULL", +211: "QWILFISH", +212: "SCIZOR", +213: "SHUCKLE", +214: "HERACROSS", +215: "SNEASEL", +216: "TEDDIURSA", +217: "URSARING", +218: "SLUGMA", +219: "MAGCARGO", +220: "SWINUB", +221: "PILOSWINE", +222: "CORSOLA", +223: "REMORAID", +224: "OCTILLERY", +225: "DELIBIRD", +226: "MANTINE", +227: "SKARMORY", +228: "HOUNDOUR", +229: "HOUNDOOM", +230: "KINGDRA", +231: "PHANPY", +232: "DONPHAN", +233: "PORYGON2", +234: "STANTLER", +235: "SMEARGLE", +236: "TYROGUE", +237: "HITMONTOP", +238: "SMOOCHUM", +239: "ELEKID", +240: "MAGBY", +241: "MILTANK", +242: "BLISSEY", +243: "RAIKOU", +244: "ENTEI", +245: "SUICUNE", +246: "LARVITAR", +247: "PUPITAR", +248: "TYRANITAR", +249: "LUGIA", +250: "HO_OH", +251: "CELEBI", +} diff --git a/pokemontools/romstr.py b/pokemontools/romstr.py new file mode 100644 index 0000000..69a4f2a --- /dev/null +++ b/pokemontools/romstr.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- + +import sys +import os +import time +import datetime +from ctypes import c_int8 +from copy import copy +import json + +# New versions of json don't have read anymore. +if not hasattr(json, "read"): + json.read = json.loads + +from labels import ( + get_label_from_line, + get_address_from_line_comment, +) + +relative_jumps = [0x38, 0x30, 0x20, 0x28, 0x18, 0xc3, 0xda, 0xc2, 0x32] +relative_unconditional_jumps = [0xc3, 0x18] +call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd] +end_08_scripts_with = [ + 0xe9, # jp hl + 0xc9, # ret +] # possibly also: + # 0xc3, # jp + # 0xc18, # jr + # 0xda, 0xe9, 0xd2, 0xc2, 0xca, 0x38, 0x30, 0x20, 0x28, 0x18, 0xd8, + # 0xd0, 0xc0, 0xc8, 0xc9 + +spacing = "\t" + +class RomStr(str): + """ + Simple wrapper to prevent a giant rom from being shown on screen. + """ + + def __init__(self, *args, **kwargs): + if "labels" in kwargs.keys() and kwargs["labels"] == True: + self.load_labels() + str.__init__(self) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "RomStr(too long)" + + @classmethod + def load(cls, filename=None, crystal=True, red=False): + """ + Load a ROM into a RomStr. + """ + if crystal and not red and not filename: + file_handler = open("../baserom.gbc", "r") + elif red and not crystal and not filename: + file_handler = open("../pokered-baserom.gbc", "r") + elif filename not in ["", None]: + file_handler = open(filename, "rb") + else: + raise Exception("not sure which rom to load?") + bytes = file_handler.read() + file_handler.close() + return RomStr(bytes) + + def load_labels(self, filename="labels.json"): + """ + Loads labels from labels.json. + + (Or parses the source code file and + generates new labels.) + """ + filename = os.path.join(os.path.dirname(__file__), filename) + + # blank out the hash + self.labels = {} + + # check if the labels file exists + file_existence = os.path.exists(filename) + + generate_labels = False + + # determine if the labels file needs to be regenerated + if file_existence: + modified = os.path.getmtime(filename) + modified = datetime.datetime.fromtimestamp(modified) + current = datetime.datetime.fromtimestamp(time.time()) + + is_old = (current - modified) > datetime.timedelta(days=3) + + if is_old: + generate_labels = True + else: + generate_labels = True + + # scan the asm source code for labels + if generate_labels: + asm = open(os.path.join(os.path.dirname(__file__), "../main.asm"), "r").read().split("\n") + + for line in asm: + label = get_label_from_line(line) + + if label: + address = get_address_from_line_comment(line) + + self.labels[address] = label + + content = json.dumps(self.labels) + file_handler = open(filename, "w") + file_handler.write(content) + file_handler.close() + + # load the labels from the file + self.labels = json.read(open(filename, "r").read()) + + def get_address_for(self, label): + """ + Return the address of a label. + + This is slow and could be improved dramatically. + """ + label = str(label) + for address in self.labels.keys(): + if self.labels[address] == label: + return address + return None + + def length(self): + """ + len(self) + """ + return len(self) + + def len(self): + """ + len(self) + """ + return self.length() + + def interval(self, offset, length, strings=True, debug=True): + """ + Return hex values for the rom starting at offset until offset+length. + """ + returnable = [] + for byte in self[offset:offset+length]: + if strings: + returnable.append(hex(ord(byte))) + else: + returnable.append(ord(byte)) + return returnable + + def until(self, offset, byte, strings=True, debug=False): + """ + Return hex values from rom starting at offset until the given byte. + """ + return self.interval(offset, self.find(chr(byte), offset) - offset, strings=strings) + + def to_asm(self, address, end_address=None, size=None, max_size=0x4000, debug=None): + """ + Disassemble ASM at some address. + + This will stop disassembling when either the end_address or size is + met. Also, there's a maximum size that will be parsed, so that large + patches of data aren't parsed as code. + """ + if type(address) in [str, unicode] and "0x" in address: + address = int(address, 16) + + start_address = address + + if start_address == None: + raise Exception, "address must be given" + + if debug == None: + if not hasattr(self, "debug"): + debug = False + else: + debug = self.debug + + # this is probably a terrible idea.. why am i doing this? + if size != None and max_size < size: + raise Exception, "max_size must be greater than or equal to size" + elif end_address != None and (end_address - start_address) > max_size: + raise Exception, "end_address is out of bounds" + elif end_address != None and size != None: + if (end_address - start_address) >= size: + size = end_address - start_address + else: + end_address = start_address + size + elif end_address == None and size != None: + end_address = start_address + size + elif end_address != None and size == None: + size = end_address - start_address + + raise NotImplementedError("DisAsm was removed and never worked; hook up another disassembler please.") + #return DisAsm(start_address=start_address, end_address=end_address, size=size, max_size=max_size, debug=debug, rom=self) + +class AsmList(list): + """ + Simple wrapper to prevent all asm lines from being shown on screen. + """ + + def length(self): + """ + len(self) + """ + return len(self) + + def __repr__(self): + """ + Simplifies this object so that the output doesn't overflow stdout. + """ + return "AsmList(too long)" + +if __name__ == "__main__": + cryrom = RomStr(open("../pokecrystal.gbc", "r").read()); + asm = cryrom.to_asm(sys.argv[1]) + print asm diff --git a/pokemontools/sym.py b/pokemontools/sym.py new file mode 100644 index 0000000..f78b9af --- /dev/null +++ b/pokemontools/sym.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +import json + +# from crystal import load_rom +# from gbz80disasm import load_labels + + +def make_sym_from_json(filename = '../pokecrystal.sym', j = 'labels.json'): + # todo: delete and remake labels.json at runtime + with open(filename, 'w') as sym: + for label in json.load(open(j)): + sym.write('{0:x}:{1:x} {2}\n'.format(label['bank'], label['address']%0x4000 + (0x4000 if label['bank'] else 0), label['label'])) + + +def make_sym_from_mapfile(filename = '../pokecrystal.sym', mapfile = '../mapfile.txt'): + # todo: sort label definitions by address + + output = '' + # get label definitions + with open(mapfile,'r') as map: + lines = map.readlines() + for line in lines: + # bank # + if 'Bank #' in line: + cur_bank = int(line.lstrip('Bank #').strip(':\n').strip(' (HOME)')) + + # label definition + elif '=' in line: + thing = line.split('=') + spacing = ' ' * 11 # arbitrary + addr = int(thing[0].lstrip(spacing)[1:5],16) + + # rgbds doesn't support wram banks yet, + # so this hack is applied instead + if addr > 0xbfff: # 0xc000+ (wram only) + cur_bank = 0 + if addr > 0xcfff: # 0xd000+ (wram only) + cur_bank = 1 + + # convert to sym format (bank:addr label) + label = thing[1].strip('\n') + output += hex(cur_bank)[2:] + ':' + hex(addr)[2:] + ' ' + label + '\n' + + # dump contents to symfile + with open(filename, 'w') as sym: + sym.write(output) + + +if __name__ == "__main__": + # default behavior: generate sym file from rgbds mapfile + try: make_sym_from_mapfile() + # if no mapfile exists, generate from labels.json + except: make_sym_from_json() diff --git a/pokemontools/trainers.py b/pokemontools/trainers.py new file mode 100644 index 0000000..cf17b98 --- /dev/null +++ b/pokemontools/trainers.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# url: http://hax.iimarck.us/topic/8/ + +# for fixing trainer_group_names +import re + +trainer_group_pointer_table_address = 0x39999 +trainer_group_pointer_table_address_gs = 0x3993E + +# Any trainer that appears more than once should have an id after each +# trainer name. + +# "uses_numeric_trainer_ids" means never use a name for the trainer_id +trainer_group_names = { +0x01: {"name": "Falkner", "uses_numeric_trainer_ids": True, "constant": "FALKNER"}, +0x02: {"name": "Whitney", "uses_numeric_trainer_ids": True, "constant": "WHITNEY"}, +0x03: {"name": "Bugsy", "uses_numeric_trainer_ids": True, "constant": "BUGSY"}, +0x04: {"name": "Morty", "uses_numeric_trainer_ids": True, "constant": "MORTY"}, +0x05: {"name": "Pryce", "uses_numeric_trainer_ids": True, "constant": "PRYCE"}, +0x06: {"name": "Jasmine", "uses_numeric_trainer_ids": True, "constant": "JASMINE"}, +0x07: {"name": "Chuck", "uses_numeric_trainer_ids": True, "constant": "CHUCK"}, +0x08: {"name": "Clair", "uses_numeric_trainer_ids": True, "constant": "CLAIR"}, +0x09: {"name": "Rival1", "constant": "RIVAL1"}, +#PokemonProf group is empty :/ +0x0A: {"name": "Pokémon Prof.", "constant": "POKEMON_PROF"}, +0x0B: {"name": "Elite Four Will", "uses_numeric_trainer_ids": True, "constant": "WILL"}, +0x0C: {"name": "PKMN [Cal]", "constant": "CAL"}, +0x0D: {"name": "Elite Four Bruno", "uses_numeric_trainer_ids": True, "constant": "BRUNO"}, +0x0E: {"name": "Elite Four Karen", "uses_numeric_trainer_ids": True, "constant": "KAREN"}, +0x0F: {"name": "Elite Four Koga", "uses_numeric_trainer_ids": True, "constant": "KOGA"}, +0x10: {"name": "Champion", "constant": "CHAMPION"}, +0x11: {"name": "Brock", "uses_numeric_trainer_ids": True, "constant": "BROCK"}, +0x12: {"name": "Misty", "uses_numeric_trainer_ids": True, "constant": "MISTY"}, +0x13: {"name": "Lt.Surge", "uses_numeric_trainer_ids": True, "constant": "LT_SURGE"}, +0x14: {"name": "Scientist", "constant": "SCIENTIST"}, +0x15: {"name": "Erika", "uses_numeric_trainer_ids": True, "constant": "ERIKA"}, +0x16: {"name": "Youngster", "constant": "YOUNGSTER"}, +0x17: {"name": "Schoolboy", "constant": "SCHOOLBOY"}, +0x18: {"name": "Bird Keeper", "constant": "BIRD_KEEPER"}, +0x19: {"name": "Lass", "constant": "LASS"}, +0x1A: {"name": "Janine", "uses_numeric_trainer_ids": True, "constant": "JANINE"}, +0x1B: {"name": "CooltrainerM", "constant": "COOLTRAINERM"}, +0x1C: {"name": "CooltrainerF", "constant": "COOLTRAINERF"}, +0x1D: {"name": "Beauty", "constant": "BEAUTY"}, +0x1E: {"name": "Pokémaniac", "constant": "POKEMANIAC"}, +0x1F: {"name": "GruntM", "uses_numeric_trainer_ids": True, "constant": "GRUNTM"}, +0x20: {"name": "Gentleman", "constant": "GENTLEMAN"}, +0x21: {"name": "Skier", "constant": "SKIER"}, +0x22: {"name": "Teacher", "constant": "TEACHER"}, +0x23: {"name": "Sabrina", "uses_numeric_trainer_ids": True, "constant": "SABRINA"}, +0x24: {"name": "Bug Catcher", "constant": "BUG_CATCHER"}, +0x25: {"name": "Fisher", "constant": "FISHER"}, +0x26: {"name": "SwimmerM", "constant": "SWIMMERM"}, +0x27: {"name": "SwimmerF", "constant": "SWIMMERF"}, +0x28: {"name": "Sailor", "constant": "SAILOR"}, +0x29: {"name": "Super Nerd", "constant": "SUPER_NERD"}, +0x2A: {"name": "Rival2", "uses_numeric_trainer_ids": True, "constant": "RIVAL2"}, +0x2B: {"name": "Guitarist", "constant": "GUITARIST"}, +0x2C: {"name": "Hiker", "constant": "HIKER"}, +0x2D: {"name": "Biker", "constant": "BIKER"}, +0x2E: {"name": "Blaine", "uses_numeric_trainer_ids": True, "constant": "BLAINE"}, +0x2F: {"name": "Burglar", "constant": "BURGLAR"}, +0x30: {"name": "Firebreather", "constant": "FIREBREATHER"}, +0x31: {"name": "Juggler", "constant": "JUGGLER"}, +0x32: {"name": "Blackbelt_T", "constant": "BLACKBELT_T"}, +0x33: {"name": "ExecutiveM", "uses_numeric_trainer_ids": True, "constant": "EXECUTIVEM"}, +0x34: {"name": "Psychic_T", "constant": "PSYCHIC_T"}, +0x35: {"name": "Picnicker", "constant": "PICNICKER"}, +0x36: {"name": "Camper", "constant": "CAMPER"}, +0x37: {"name": "ExecutiveF", "uses_numeric_trainer_ids": True, "constant": "EXECUTIVEF"}, +0x38: {"name": "Sage", "constant": "SAGE"}, +0x39: {"name": "Medium", "constant": "MEDIUM"}, +0x3A: {"name": "Boarder", "constant": "BOARDER"}, +0x3B: {"name": "PokéfanM", "constant": "POKEFANM"}, +0x3C: {"name": "Kimono Girl", "constant": "KIMONO_GIRL"}, +0x3D: {"name": "Twins", "constant": "TWINS"}, +0x3E: {"name": "PokéfanF", "constant": "POKEFANF"}, +0x3F: {"name": "Red", "uses_numeric_trainer_ids": True, "constant": "RED"}, +0x40: {"name": "Blue", "uses_numeric_trainer_ids": True, "constant": "BLUE"}, +0x41: {"name": "Officer", "constant": "OFFICER"}, +0x42: {"name": "GruntF", "uses_numeric_trainer_ids": True, "constant": "GRUNTF"}, +0x43: {"name": "Mysticalman [Eusine]", "constant": "MYSTICALMAN"}, # crystal only +} + +def remove_parentheticals_from_trainer_group_names(): + """ + Clean up the trainer group names. + """ + i = 0 + for (key, value) in trainer_group_names.items(): + # remove the brackets and inner contents from each name + newvalue = re.sub(r'\[[^\)]*\]', '', value["name"]).strip() + + # clean up some characters + newvalue = newvalue.replace("♀", "F")\ + .replace("♂", "M")\ + .replace(".", "")\ + .replace(" ", "")\ + .replace("é", "e") + + # and calculate the address of the first byte of this pointer + trainer_group_names[key]["name"] = newvalue + trainer_group_names[key]["pointer_address"] = trainer_group_pointer_table_address + (i * 2) + i += 1 + return trainer_group_names + +# remove [Blue] from each trainer group name +remove_parentheticals_from_trainer_group_names() diff --git a/pokemontools/type_constants.py b/pokemontools/type_constants.py new file mode 100644 index 0000000..da89b0b --- /dev/null +++ b/pokemontools/type_constants.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +type_constants = { + "NORMAL": 0x00, + "FIGHTING": 0x01, + "FLYING": 0x02, + "POISON": 0x03, + "GROUND": 0x04, + "ROCK": 0x05, + "BUG": 0x07, + "GHOST": 0x08, + "STEEL": 0x09, + "CURSE_T": 0x13, + "FIRE": 0x14, + "WATER": 0x15, + "GRASS": 0x16, + "ELECTRIC": 0x17, + "PSYCHIC": 0x18, + "ICE": 0x19, + "DRAGON": 0x1A, + "DARK": 0x1B, +} diff --git a/pokemontools/wram.py b/pokemontools/wram.py new file mode 100644 index 0000000..5352fb4 --- /dev/null +++ b/pokemontools/wram.py @@ -0,0 +1,125 @@ +# coding: utf-8 +""" +RGBDS BSS section and constant parsing. +""" + +import os +path = os.path.dirname(os.path.abspath(__file__)) + +def read_bss_sections(bss): + sections = [] + section = { + "labels": [], + } + address = None + if type(bss) is not list: bss = bss.split('\n') + for line in bss: + line = line.lstrip() + if 'SECTION' in line: + if section: sections.append(section) # last section + + address = eval(line[line.find('[')+1:line.find(']')].replace('$','0x')) + section = { + 'name': line.split('"')[1], + #'type': line.split(',')[1].split('[')[0].strip(), + 'start': address, + 'labels': [], + } + + elif ':' in line: + # rgbds allows labels without :, but prefer convention + label = line[:line.find(':')] + if ';' not in label: + section['labels'] += [{ + 'label': label, + 'address': address, + 'length': 0, + }] + + elif line[:3] == 'ds ': + length = eval(line[3:line.find(';')].replace('$','0x')) + address += length + # adjacent labels use the same space + for label in section['labels'][::-1]: + if label['length'] == 0: + label['length'] = length + else: + break + + elif 'EQU' in line: + # some space is defined using constants + name, value = line.split('EQU') + name, value = name.strip(), value.strip().replace('$','0x').replace('%','0b') + globals()[name] = eval(value) + + sections.append(section) + return sections + +def read_wram_sections(): + """ + Opens the wram file and calls read_bss_sections. + """ + wram_content = None + wram_file_path = os.path.join(os.path.dirname(path), 'wram.asm') + try: + wram_file_handler = open(wram_file_path, 'r') + except IOError as exception: + wram_content = [""] + else: + wram_content = wram_file_handler.readlines() + wram_sections = read_bss_sections(wram_content) + return wram_sections + +wram_sections = read_wram_sections() + +def make_wram_labels(): + wram_labels = {} + for section in wram_sections: + for label in section['labels']: + if label['address'] not in wram_labels.keys(): + wram_labels[label['address']] = [] + wram_labels[label['address']] += [label['label']] + return wram_labels + +wram_labels = make_wram_labels() + +def constants_to_dict(constants): + return dict((eval(constant[constant.find('EQU')+3:constant.find(';')].replace('$','0x')), constant[:constant.find('EQU')].strip()) for constant in constants) + +def scrape_constants(text): + if type(text) is not list: + text = text.split('\n') + return constants_to_dict([line for line in text if 'EQU' in line[:line.find(';')]]) + +def read_constants(filepath): + """ + Load lines from a file and call scrape_constants. + """ + try: + file_handler = open(filepath, "r") + except IOError as exception: + lines = [""] + else: + lines = file_handler.readlines() + constants = scrape_constants(lines) + return constants + +def read_hram_constants(): + """ + Load constants from hram.asm. + """ + hram_path = os.path.join(os.path.dirname(path), 'hram.asm') + return read_constants(hram_path) + +# TODO: get rid of this global +hram_constants = read_hram_constants() + +def read_gbhw_constants(): + """ + Load constants from gbhw.asm. + """ + gbhw_path = os.path.join(os.path.dirname(path), 'gbhw.asm') + return read_constants(gbhw_path) + +# TODO: get rid of this global +gbhw_constants = read_gbhw_constants() diff --git a/romstr.py b/romstr.py deleted file mode 100644 index 69a4f2a..0000000 --- a/romstr.py +++ /dev/null @@ -1,219 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import os -import time -import datetime -from ctypes import c_int8 -from copy import copy -import json - -# New versions of json don't have read anymore. -if not hasattr(json, "read"): - json.read = json.loads - -from labels import ( - get_label_from_line, - get_address_from_line_comment, -) - -relative_jumps = [0x38, 0x30, 0x20, 0x28, 0x18, 0xc3, 0xda, 0xc2, 0x32] -relative_unconditional_jumps = [0xc3, 0x18] -call_commands = [0xdc, 0xd4, 0xc4, 0xcc, 0xcd] -end_08_scripts_with = [ - 0xe9, # jp hl - 0xc9, # ret -] # possibly also: - # 0xc3, # jp - # 0xc18, # jr - # 0xda, 0xe9, 0xd2, 0xc2, 0xca, 0x38, 0x30, 0x20, 0x28, 0x18, 0xd8, - # 0xd0, 0xc0, 0xc8, 0xc9 - -spacing = "\t" - -class RomStr(str): - """ - Simple wrapper to prevent a giant rom from being shown on screen. - """ - - def __init__(self, *args, **kwargs): - if "labels" in kwargs.keys() and kwargs["labels"] == True: - self.load_labels() - str.__init__(self) - - def __repr__(self): - """ - Simplifies this object so that the output doesn't overflow stdout. - """ - return "RomStr(too long)" - - @classmethod - def load(cls, filename=None, crystal=True, red=False): - """ - Load a ROM into a RomStr. - """ - if crystal and not red and not filename: - file_handler = open("../baserom.gbc", "r") - elif red and not crystal and not filename: - file_handler = open("../pokered-baserom.gbc", "r") - elif filename not in ["", None]: - file_handler = open(filename, "rb") - else: - raise Exception("not sure which rom to load?") - bytes = file_handler.read() - file_handler.close() - return RomStr(bytes) - - def load_labels(self, filename="labels.json"): - """ - Loads labels from labels.json. - - (Or parses the source code file and - generates new labels.) - """ - filename = os.path.join(os.path.dirname(__file__), filename) - - # blank out the hash - self.labels = {} - - # check if the labels file exists - file_existence = os.path.exists(filename) - - generate_labels = False - - # determine if the labels file needs to be regenerated - if file_existence: - modified = os.path.getmtime(filename) - modified = datetime.datetime.fromtimestamp(modified) - current = datetime.datetime.fromtimestamp(time.time()) - - is_old = (current - modified) > datetime.timedelta(days=3) - - if is_old: - generate_labels = True - else: - generate_labels = True - - # scan the asm source code for labels - if generate_labels: - asm = open(os.path.join(os.path.dirname(__file__), "../main.asm"), "r").read().split("\n") - - for line in asm: - label = get_label_from_line(line) - - if label: - address = get_address_from_line_comment(line) - - self.labels[address] = label - - content = json.dumps(self.labels) - file_handler = open(filename, "w") - file_handler.write(content) - file_handler.close() - - # load the labels from the file - self.labels = json.read(open(filename, "r").read()) - - def get_address_for(self, label): - """ - Return the address of a label. - - This is slow and could be improved dramatically. - """ - label = str(label) - for address in self.labels.keys(): - if self.labels[address] == label: - return address - return None - - def length(self): - """ - len(self) - """ - return len(self) - - def len(self): - """ - len(self) - """ - return self.length() - - def interval(self, offset, length, strings=True, debug=True): - """ - Return hex values for the rom starting at offset until offset+length. - """ - returnable = [] - for byte in self[offset:offset+length]: - if strings: - returnable.append(hex(ord(byte))) - else: - returnable.append(ord(byte)) - return returnable - - def until(self, offset, byte, strings=True, debug=False): - """ - Return hex values from rom starting at offset until the given byte. - """ - return self.interval(offset, self.find(chr(byte), offset) - offset, strings=strings) - - def to_asm(self, address, end_address=None, size=None, max_size=0x4000, debug=None): - """ - Disassemble ASM at some address. - - This will stop disassembling when either the end_address or size is - met. Also, there's a maximum size that will be parsed, so that large - patches of data aren't parsed as code. - """ - if type(address) in [str, unicode] and "0x" in address: - address = int(address, 16) - - start_address = address - - if start_address == None: - raise Exception, "address must be given" - - if debug == None: - if not hasattr(self, "debug"): - debug = False - else: - debug = self.debug - - # this is probably a terrible idea.. why am i doing this? - if size != None and max_size < size: - raise Exception, "max_size must be greater than or equal to size" - elif end_address != None and (end_address - start_address) > max_size: - raise Exception, "end_address is out of bounds" - elif end_address != None and size != None: - if (end_address - start_address) >= size: - size = end_address - start_address - else: - end_address = start_address + size - elif end_address == None and size != None: - end_address = start_address + size - elif end_address != None and size == None: - size = end_address - start_address - - raise NotImplementedError("DisAsm was removed and never worked; hook up another disassembler please.") - #return DisAsm(start_address=start_address, end_address=end_address, size=size, max_size=max_size, debug=debug, rom=self) - -class AsmList(list): - """ - Simple wrapper to prevent all asm lines from being shown on screen. - """ - - def length(self): - """ - len(self) - """ - return len(self) - - def __repr__(self): - """ - Simplifies this object so that the output doesn't overflow stdout. - """ - return "AsmList(too long)" - -if __name__ == "__main__": - cryrom = RomStr(open("../pokecrystal.gbc", "r").read()); - asm = cryrom.to_asm(sys.argv[1]) - print asm diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c5f45bb --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +# for uploading to pypi +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + sys.exit() + +# There's some intersection with requirements.txt but pypi can't handle git +# dependencies. +requires = [ + "mock", +] + +setup( + name="pokemontools", + version="1.0", + description="Tools for compiling and disassembling Pokémon Red and Pokémon Crystal.", + long_description=open("README.md", "r").read(), + license="BSD", + author="Bryan Bishop", + author_email="kanzure@gmail.com", + url="https://github.com/kanzure/pokemon-reverse-engineering-tools", + packages=["pokemontools"], + package_dir={"pokemontools": "pokemontools"}, + include_package_data=True, + install_requires=requires, + platforms="any", + classifiers=[ + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + ] +) diff --git a/sym.py b/sym.py deleted file mode 100644 index f78b9af..0000000 --- a/sym.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -import json - -# from crystal import load_rom -# from gbz80disasm import load_labels - - -def make_sym_from_json(filename = '../pokecrystal.sym', j = 'labels.json'): - # todo: delete and remake labels.json at runtime - with open(filename, 'w') as sym: - for label in json.load(open(j)): - sym.write('{0:x}:{1:x} {2}\n'.format(label['bank'], label['address']%0x4000 + (0x4000 if label['bank'] else 0), label['label'])) - - -def make_sym_from_mapfile(filename = '../pokecrystal.sym', mapfile = '../mapfile.txt'): - # todo: sort label definitions by address - - output = '' - # get label definitions - with open(mapfile,'r') as map: - lines = map.readlines() - for line in lines: - # bank # - if 'Bank #' in line: - cur_bank = int(line.lstrip('Bank #').strip(':\n').strip(' (HOME)')) - - # label definition - elif '=' in line: - thing = line.split('=') - spacing = ' ' * 11 # arbitrary - addr = int(thing[0].lstrip(spacing)[1:5],16) - - # rgbds doesn't support wram banks yet, - # so this hack is applied instead - if addr > 0xbfff: # 0xc000+ (wram only) - cur_bank = 0 - if addr > 0xcfff: # 0xd000+ (wram only) - cur_bank = 1 - - # convert to sym format (bank:addr label) - label = thing[1].strip('\n') - output += hex(cur_bank)[2:] + ':' + hex(addr)[2:] + ' ' + label + '\n' - - # dump contents to symfile - with open(filename, 'w') as sym: - sym.write(output) - - -if __name__ == "__main__": - # default behavior: generate sym file from rgbds mapfile - try: make_sym_from_mapfile() - # if no mapfile exists, generate from labels.json - except: make_sym_from_json() diff --git a/test_dump_sections.py b/test_dump_sections.py deleted file mode 100644 index b73b86f..0000000 --- a/test_dump_sections.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- - -try: - import unittest2 as unittest -except ImportError: - import unittest - -# check for things we need in unittest -if not hasattr(unittest.TestCase, 'setUpClass'): - sys.stderr.write("The unittest2 module or Python 2.7 is required to run this script.") - sys.exit(1) - -from dump_sections import ( - upper_hex, - format_bank_number, - calculate_bank_quantity, - dump_section, - dump_incbin_for_section, -) - -class TestDumpSections(unittest.TestCase): - def test_upper_hex(self): - number = 0x52 - self.assertEquals(number, int("0x" + upper_hex(number), 16)) - - number = 0x1 - self.assertEquals(number, int("0x" + upper_hex(number), 16)) - - number = 0x0 - self.assertEquals(number, int("0x" + upper_hex(number), 16)) - - number = 0xAA - self.assertEquals(number, int("0x" + upper_hex(number), 16)) - - number = 0xFFFFAAA0000 - self.assertEquals(number, int("0x" + upper_hex(number), 16)) - - def test_format_bank_number(self): - address = 0x0 - self.assertEquals("0", format_bank_number(address)) - - address = 0x4000 - self.assertEquals("1", format_bank_number(address)) - - address = 0x1FC000 - self.assertEquals("7F", format_bank_number(address)) - - def test_dump_section(self): - self.assertIn("SECTION", dump_section(str(0))) - self.assertIn("HOME", dump_section(str(0))) - self.assertNotIn("HOME", dump_section(str(1))) - self.assertIn("DATA", dump_section(str(2))) - self.assertIn("BANK", dump_section(str(40))) - self.assertNotIn("BANK", dump_section(str(0))) - - def test_dump_incbin_for_section(self): - self.assertIn("INCBIN", dump_incbin_for_section(0)) - - def test_dump_incbin_for_section_separator(self): - separator = "\n\n" - self.assertIn(separator, dump_incbin_for_section(0, separator=separator)) - - separator = "\t\t" # dumb - self.assertIn(separator, dump_incbin_for_section(0, separator=separator)) - - def test_dump_incbin_for_section_default(self): - rom = "baserom.gbc" - self.assertIn(rom, dump_incbin_for_section(0)) - - rom = "baserom" - self.assertIn(rom, dump_incbin_for_section(0x4000)) - -if __name__ == "__main__": - unittest.main() diff --git a/tests.py b/tests.py deleted file mode 100644 index 227071f..0000000 --- a/tests.py +++ /dev/null @@ -1,1014 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -import sys -import inspect -from copy import copy -import hashlib -import random -import json - -from interval_map import IntervalMap -from chars import chars, jap_chars - -from romstr import ( - RomStr, - AsmList, -) - -from item_constants import ( - item_constants, - find_item_label_by_id, - generate_item_constants, -) - -from pointers import ( - calculate_bank, - calculate_pointer, -) - -from pksv import ( - pksv_gs, - pksv_crystal, -) - -from labels import ( - remove_quoted_text, - line_has_comment_address, - line_has_label, - get_label_from_line, -) - -from crystal import ( - rom, - load_rom, - rom_until, - direct_load_rom, - parse_script_engine_script_at, - parse_text_engine_script_at, - parse_text_at2, - find_all_text_pointers_in_script_engine_script, - SingleByteParam, - HexByte, - MultiByteParam, - PointerLabelParam, - ItemLabelByte, - DollarSignByte, - DecimalParam, - rom_interval, - map_names, - Label, - scan_for_predefined_labels, - all_labels, - write_all_labels, - parse_map_header_at, - old_parse_map_header_at, - process_00_subcommands, - parse_all_map_headers, - translate_command_byte, - map_name_cleaner, - load_map_group_offsets, - load_asm, - asm, - is_valid_address, - index, - how_many_until, - grouper, - get_pokemon_constant_by_id, - generate_map_constant_labels, - get_map_constant_label_by_id, - get_id_for_map_constant_label, - calculate_pointer_from_bytes_at, - isolate_incbins, - process_incbins, - get_labels_between, - generate_diff_insert, - find_labels_without_addresses, - rom_text_at, - get_label_for, - split_incbin_line_into_three, - reset_incbins, -) - -# for testing all this crap -try: - import unittest2 as unittest -except ImportError: - import unittest - -# check for things we need in unittest -if not hasattr(unittest.TestCase, 'setUpClass'): - sys.stderr.write("The unittest2 module or Python 2.7 is required to run this script.") - sys.exit(1) - -class TestCram(unittest.TestCase): - "this is where i cram all of my unit tests together" - - @classmethod - def setUpClass(cls): - global rom - cls.rom = direct_load_rom() - rom = cls.rom - - @classmethod - def tearDownClass(cls): - del cls.rom - - def test_generic_useless(self): - "do i know how to write a test?" - self.assertEqual(1, 1) - - def test_map_name_cleaner(self): - name = "hello world" - cleaned_name = map_name_cleaner(name) - self.assertNotEqual(name, cleaned_name) - self.failUnless(" " not in cleaned_name) - name = "Some Random Pokémon Center" - cleaned_name = map_name_cleaner(name) - self.assertNotEqual(name, cleaned_name) - self.failIf(" " in cleaned_name) - self.failIf("é" in cleaned_name) - - def test_grouper(self): - data = range(0, 10) - groups = grouper(data, count=2) - self.assertEquals(len(groups), 5) - data = range(0, 20) - groups = grouper(data, count=2) - self.assertEquals(len(groups), 10) - self.assertNotEqual(data, groups) - self.assertNotEqual(len(data), len(groups)) - - def test_direct_load_rom(self): - rom = self.rom - self.assertEqual(len(rom), 2097152) - self.failUnless(isinstance(rom, RomStr)) - - def test_load_rom(self): - global rom - rom = None - load_rom() - self.failIf(rom == None) - rom = RomStr(None) - load_rom() - self.failIf(rom == RomStr(None)) - - def test_load_asm(self): - asm = load_asm() - joined_lines = "\n".join(asm) - self.failUnless("SECTION" in joined_lines) - self.failUnless("bank" in joined_lines) - self.failUnless(isinstance(asm, AsmList)) - - def test_rom_file_existence(self): - "ROM file must exist" - self.failUnless("baserom.gbc" in os.listdir("../")) - - def test_rom_md5(self): - "ROM file must have the correct md5 sum" - rom = self.rom - correct = "9f2922b235a5eeb78d65594e82ef5dde" - md5 = hashlib.md5() - md5.update(rom) - md5sum = md5.hexdigest() - self.assertEqual(md5sum, correct) - - def test_bizarre_http_presence(self): - rom_segment = self.rom[0x112116:0x112116+8] - self.assertEqual(rom_segment, "HTTP/1.0") - - def test_rom_interval(self): - address = 0x100 - interval = 10 - correct_strings = ['0x0', '0xc3', '0x6e', '0x1', '0xce', - '0xed', '0x66', '0x66', '0xcc', '0xd'] - byte_strings = rom_interval(address, interval, strings=True) - self.assertEqual(byte_strings, correct_strings) - correct_ints = [0, 195, 110, 1, 206, 237, 102, 102, 204, 13] - ints = rom_interval(address, interval, strings=False) - self.assertEqual(ints, correct_ints) - - def test_rom_until(self): - address = 0x1337 - byte = 0x13 - bytes = rom_until(address, byte, strings=True) - self.failUnless(len(bytes) == 3) - self.failUnless(bytes[0] == '0xd5') - bytes = rom_until(address, byte, strings=False) - self.failUnless(len(bytes) == 3) - self.failUnless(bytes[0] == 0xd5) - - def test_how_many_until(self): - how_many = how_many_until(chr(0x13), 0x1337) - self.assertEqual(how_many, 3) - - def test_calculate_bank(self): - self.failUnless(calculate_bank(0x8000) == 2) - self.failUnless(calculate_bank("0x9000") == 2) - self.failUnless(calculate_bank(0) == 0) - for address in [0x4000, 0x5000, 0x6000, 0x7000]: - self.assertRaises(Exception, calculate_bank, address) - - def test_calculate_pointer(self): - # for offset <= 0x4000 - self.assertEqual(calculate_pointer(0x0000), 0x0000) - self.assertEqual(calculate_pointer(0x3FFF), 0x3FFF) - # for 0x4000 <= offset <= 0x7FFFF - self.assertEqual(calculate_pointer(0x430F, bank=5), 0x1430F) - # for offset >= 0x7FFF - self.assertEqual(calculate_pointer(0x8FFF, bank=6), calculate_pointer(0x8FFF, bank=7)) - - def test_calculate_pointer_from_bytes_at(self): - addr1 = calculate_pointer_from_bytes_at(0x100, bank=False) - self.assertEqual(addr1, 0xc300) - addr2 = calculate_pointer_from_bytes_at(0x100, bank=True) - self.assertEqual(addr2, 0x2ec3) - - def test_rom_text_at(self): - self.assertEquals(rom_text_at(0x112116, 8), "HTTP/1.0") - - def test_translate_command_byte(self): - self.failUnless(translate_command_byte(crystal=0x0) == 0x0) - self.failUnless(translate_command_byte(crystal=0x10) == 0x10) - self.failUnless(translate_command_byte(crystal=0x40) == 0x40) - self.failUnless(translate_command_byte(gold=0x0) == 0x0) - self.failUnless(translate_command_byte(gold=0x10) == 0x10) - self.failUnless(translate_command_byte(gold=0x40) == 0x40) - self.assertEqual(translate_command_byte(gold=0x0), translate_command_byte(crystal=0x0)) - self.failUnless(translate_command_byte(gold=0x52) == 0x53) - self.failUnless(translate_command_byte(gold=0x53) == 0x54) - self.failUnless(translate_command_byte(crystal=0x53) == 0x52) - self.failUnless(translate_command_byte(crystal=0x52) == None) - self.assertRaises(Exception, translate_command_byte, None, gold=0xA4) - - def test_pksv_integrity(self): - "does pksv_gs look okay?" - self.assertEqual(pksv_gs[0x00], "2call") - self.assertEqual(pksv_gs[0x2D], "givepoke") - self.assertEqual(pksv_gs[0x85], "waitbutton") - self.assertEqual(pksv_crystal[0x00], "2call") - self.assertEqual(pksv_crystal[0x86], "waitbutton") - self.assertEqual(pksv_crystal[0xA2], "credits") - - def test_chars_integrity(self): - self.assertEqual(chars[0x80], "A") - self.assertEqual(chars[0xA0], "a") - self.assertEqual(chars[0xF0], "¥") - self.assertEqual(jap_chars[0x44], "ぱ") - - def test_map_names_integrity(self): - def map_name(map_group, map_id): return map_names[map_group][map_id]["name"] - self.assertEqual(map_name(2, 7), "Mahogany Town") - self.assertEqual(map_name(3, 0x34), "Ilex Forest") - self.assertEqual(map_name(7, 0x11), "Cerulean City") - - def test_load_map_group_offsets(self): - addresses = load_map_group_offsets() - self.assertEqual(len(addresses), 26, msg="there should be 26 map groups") - addresses = load_map_group_offsets() - self.assertEqual(len(addresses), 26, msg="there should still be 26 map groups") - self.assertIn(0x94034, addresses) - for address in addresses: - self.assertGreaterEqual(address, 0x4000) - self.failIf(0x4000 <= address <= 0x7FFF) - self.failIf(address <= 0x4000) - - def test_index(self): - self.assertTrue(index([1,2,3,4], lambda f: True) == 0) - self.assertTrue(index([1,2,3,4], lambda f: f==3) == 2) - - def test_get_pokemon_constant_by_id(self): - x = get_pokemon_constant_by_id - self.assertEqual(x(1), "BULBASAUR") - self.assertEqual(x(151), "MEW") - self.assertEqual(x(250), "HO_OH") - - def test_find_item_label_by_id(self): - x = find_item_label_by_id - self.assertEqual(x(249), "HM_07") - self.assertEqual(x(173), "BERRY") - self.assertEqual(x(45), None) - - def test_generate_item_constants(self): - x = generate_item_constants - r = x() - self.failUnless("HM_07" in r) - self.failUnless("EQU" in r) - - def test_get_label_for(self): - global all_labels - temp = copy(all_labels) - # this is basd on the format defined in get_labels_between - all_labels = [{"label": "poop", "address": 0x5, - "offset": 0x5, "bank": 0, - "line_number": 2 - }] - self.assertEqual(get_label_for(5), "poop") - all_labels = temp - - def test_generate_map_constant_labels(self): - ids = generate_map_constant_labels() - self.assertEqual(ids[0]["label"], "OLIVINE_POKECENTER_1F") - self.assertEqual(ids[1]["label"], "OLIVINE_GYM") - - def test_get_id_for_map_constant_label(self): - global map_internal_ids - map_internal_ids = generate_map_constant_labels() - self.assertEqual(get_id_for_map_constant_label("OLIVINE_GYM"), 1) - self.assertEqual(get_id_for_map_constant_label("OLIVINE_POKECENTER_1F"), 0) - - def test_get_map_constant_label_by_id(self): - global map_internal_ids - map_internal_ids = generate_map_constant_labels() - self.assertEqual(get_map_constant_label_by_id(0), "OLIVINE_POKECENTER_1F") - self.assertEqual(get_map_constant_label_by_id(1), "OLIVINE_GYM") - - def test_is_valid_address(self): - self.assertTrue(is_valid_address(0)) - self.assertTrue(is_valid_address(1)) - self.assertTrue(is_valid_address(10)) - self.assertTrue(is_valid_address(100)) - self.assertTrue(is_valid_address(1000)) - self.assertTrue(is_valid_address(10000)) - self.assertFalse(is_valid_address(2097153)) - self.assertFalse(is_valid_address(2098000)) - addresses = [random.randrange(0,2097153) for i in range(0, 9+1)] - for address in addresses: - self.assertTrue(is_valid_address(address)) - -class TestIntervalMap(unittest.TestCase): - def test_intervals(self): - i = IntervalMap() - first = "hello world" - second = "testing 123" - i[0:5] = first - i[5:10] = second - self.assertEqual(i[0], first) - self.assertEqual(i[1], first) - self.assertNotEqual(i[5], first) - self.assertEqual(i[6], second) - i[3:10] = second - self.assertEqual(i[3], second) - self.assertNotEqual(i[4], first) - - def test_items(self): - i = IntervalMap() - first = "hello world" - second = "testing 123" - i[0:5] = first - i[5:10] = second - results = list(i.items()) - self.failUnless(len(results) == 2) - self.assertEqual(results[0], ((0, 5), "hello world")) - self.assertEqual(results[1], ((5, 10), "testing 123")) - -class TestRomStr(unittest.TestCase): - """RomStr is a class that should act exactly like str() - except that it never shows the contents of it string - unless explicitly forced""" - sample_text = "hello world!" - sample = None - - def setUp(self): - if self.sample == None: - self.__class__.sample = RomStr(self.sample_text) - - def test_equals(self): - "check if RomStr() == str()" - self.assertEquals(self.sample_text, self.sample) - - def test_not_equal(self): - "check if RomStr('a') != RomStr('b')" - self.assertNotEqual(RomStr('a'), RomStr('b')) - - def test_appending(self): - "check if RomStr()+'a'==str()+'a'" - self.assertEquals(self.sample_text+'a', self.sample+'a') - - def test_conversion(self): - "check if RomStr() -> str() works" - self.assertEquals(str(self.sample), self.sample_text) - - def test_inheritance(self): - self.failUnless(issubclass(RomStr, str)) - - def test_length(self): - self.assertEquals(len(self.sample_text), len(self.sample)) - self.assertEquals(len(self.sample_text), self.sample.length()) - self.assertEquals(len(self.sample), self.sample.length()) - - def test_rom_interval(self): - global rom - load_rom() - address = 0x100 - interval = 10 - correct_strings = ['0x0', '0xc3', '0x6e', '0x1', '0xce', - '0xed', '0x66', '0x66', '0xcc', '0xd'] - byte_strings = rom.interval(address, interval, strings=True) - self.assertEqual(byte_strings, correct_strings) - correct_ints = [0, 195, 110, 1, 206, 237, 102, 102, 204, 13] - ints = rom.interval(address, interval, strings=False) - self.assertEqual(ints, correct_ints) - - def test_rom_until(self): - global rom - load_rom() - address = 0x1337 - byte = 0x13 - bytes = rom.until(address, byte, strings=True) - self.failUnless(len(bytes) == 3) - self.failUnless(bytes[0] == '0xd5') - bytes = rom.until(address, byte, strings=False) - self.failUnless(len(bytes) == 3) - self.failUnless(bytes[0] == 0xd5) - -class TestAsmList(unittest.TestCase): - """AsmList is a class that should act exactly like list() - except that it never shows the contents of its list - unless explicitly forced""" - - def test_equals(self): - base = [1,2,3] - asm = AsmList(base) - self.assertEquals(base, asm) - self.assertEquals(asm, base) - self.assertEquals(base, list(asm)) - - def test_inheritance(self): - self.failUnless(issubclass(AsmList, list)) - - def test_length(self): - base = range(0, 10) - asm = AsmList(base) - self.assertEquals(len(base), len(asm)) - self.assertEquals(len(base), asm.length()) - self.assertEquals(len(base), len(list(asm))) - self.assertEquals(len(asm), asm.length()) - - def test_remove_quoted_text(self): - x = remove_quoted_text - self.assertEqual(x("hello world"), "hello world") - self.assertEqual(x("hello \"world\""), "hello ") - input = 'hello world "testing 123"' - self.assertNotEqual(x(input), input) - input = "hello world 'testing 123'" - self.assertNotEqual(x(input), input) - self.failIf("testing" in x(input)) - - def test_line_has_comment_address(self): - x = line_has_comment_address - self.assertFalse(x("")) - self.assertFalse(x(";")) - self.assertFalse(x(";;;")) - self.assertFalse(x(":;")) - self.assertFalse(x(":;:")) - self.assertFalse(x(";:")) - self.assertFalse(x(" ")) - self.assertFalse(x("".join(" " * 5))) - self.assertFalse(x("".join(" " * 10))) - self.assertFalse(x("hello world")) - self.assertFalse(x("hello_world")) - self.assertFalse(x("hello_world:")) - self.assertFalse(x("hello_world:;")) - self.assertFalse(x("hello_world: ;")) - self.assertFalse(x("hello_world: ; ")) - self.assertFalse(x("hello_world: ;" + "".join(" " * 5))) - self.assertFalse(x("hello_world: ;" + "".join(" " * 10))) - self.assertTrue(x(";1")) - self.assertTrue(x(";F")) - self.assertTrue(x(";$00FF")) - self.assertTrue(x(";0x00FF")) - self.assertTrue(x("; 0x00FF")) - self.assertTrue(x(";$3:$300")) - self.assertTrue(x(";0x3:$300")) - self.assertTrue(x(";$3:0x300")) - self.assertTrue(x(";3:300")) - self.assertTrue(x(";3:FFAA")) - self.assertFalse(x('hello world "how are you today;0x1"')) - self.assertTrue(x('hello world "how are you today:0x1";1')) - returnable = {} - self.assertTrue(x("hello_world: ; 0x4050", returnable=returnable, bank=5)) - self.assertTrue(returnable["address"] == 0x14050) - - def test_line_has_label(self): - x = line_has_label - self.assertTrue(x("hi:")) - self.assertTrue(x("Hello: ")) - self.assertTrue(x("MyLabel: ; test xyz")) - self.assertFalse(x(":")) - self.assertFalse(x(";HelloWorld:")) - self.assertFalse(x("::::")) - self.assertFalse(x(":;:;:;:::")) - - def test_get_label_from_line(self): - x = get_label_from_line - self.assertEqual(x("HelloWorld: "), "HelloWorld") - self.assertEqual(x("HiWorld:"), "HiWorld") - self.assertEqual(x("HiWorld"), None) - - def test_find_labels_without_addresses(self): - global asm - asm = ["hello_world: ; 0x1", "hello_world2: ;"] - labels = find_labels_without_addresses() - self.failUnless(labels[0]["label"] == "hello_world2") - asm = ["hello world: ;1", "hello_world: ;2"] - labels = find_labels_without_addresses() - self.failUnless(len(labels) == 0) - asm = None - - def test_get_labels_between(self): - global asm - x = get_labels_between#(start_line_id, end_line_id, bank) - asm = ["HelloWorld: ;1", - "hi:", - "no label on this line", - ] - labels = x(0, 2, 0x12) - self.assertEqual(len(labels), 1) - self.assertEqual(labels[0]["label"], "HelloWorld") - del asm - - # this test takes a lot of time :( - def xtest_scan_for_predefined_labels(self): - # label keys: line_number, bank, label, offset, address - load_asm() - all_labels = scan_for_predefined_labels() - label_names = [x["label"] for x in all_labels] - self.assertIn("GetFarByte", label_names) - self.assertIn("AddNTimes", label_names) - self.assertIn("CheckShininess", label_names) - - def test_write_all_labels(self): - """dumping json into a file""" - filename = "test_labels.json" - # remove the current file - if os.path.exists(filename): - os.system("rm " + filename) - # make up some labels - labels = [] - # fake label 1 - label = {"line_number": 5, "bank": 0, "label": "SomeLabel", "address": 0x10} - labels.append(label) - # fake label 2 - label = {"line_number": 15, "bank": 2, "label": "SomeOtherLabel", "address": 0x9F0A} - labels.append(label) - # dump to file - write_all_labels(labels, filename=filename) - # open the file and read the contents - file_handler = open(filename, "r") - contents = file_handler.read() - file_handler.close() - # parse into json - obj = json.read(contents) - # begin testing - self.assertEqual(len(obj), len(labels)) - self.assertEqual(len(obj), 2) - self.assertEqual(obj, labels) - - def test_isolate_incbins(self): - global asm - asm = ["123", "456", "789", "abc", "def", "ghi", - 'INCBIN "baserom.gbc",$12DA,$12F8 - $12DA', - "jkl", - 'INCBIN "baserom.gbc",$137A,$13D0 - $137A'] - lines = isolate_incbins() - self.assertIn(asm[6], lines) - self.assertIn(asm[8], lines) - for line in lines: - self.assertIn("baserom", line) - - def test_process_incbins(self): - global incbin_lines, processed_incbins, asm - incbin_lines = ['INCBIN "baserom.gbc",$12DA,$12F8 - $12DA', - 'INCBIN "baserom.gbc",$137A,$13D0 - $137A'] - asm = copy(incbin_lines) - asm.insert(1, "some other random line") - processed_incbins = process_incbins() - self.assertEqual(len(processed_incbins), len(incbin_lines)) - self.assertEqual(processed_incbins[0]["line"], incbin_lines[0]) - self.assertEqual(processed_incbins[2]["line"], incbin_lines[1]) - - def test_reset_incbins(self): - global asm, incbin_lines, processed_incbins - # temporarily override the functions - global load_asm, isolate_incbins, process_incbins - temp1, temp2, temp3 = load_asm, isolate_incbins, process_incbins - def load_asm(): pass - def isolate_incbins(): pass - def process_incbins(): pass - # call reset - reset_incbins() - # check the results - self.assertTrue(asm == [] or asm == None) - self.assertTrue(incbin_lines == []) - self.assertTrue(processed_incbins == {}) - # reset the original functions - load_asm, isolate_incbins, process_incbins = temp1, temp2, temp3 - - def test_find_incbin_to_replace_for(self): - global asm, incbin_lines, processed_incbins - asm = ['first line', 'second line', 'third line', - 'INCBIN "baserom.gbc",$90,$200 - $90', - 'fifth line', 'last line'] - isolate_incbins() - process_incbins() - line_num = find_incbin_to_replace_for(0x100) - # must be the 4th line (the INBIN line) - self.assertEqual(line_num, 3) - - def test_split_incbin_line_into_three(self): - global asm, incbin_lines, processed_incbins - asm = ['first line', 'second line', 'third line', - 'INCBIN "baserom.gbc",$90,$200 - $90', - 'fifth line', 'last line'] - isolate_incbins() - process_incbins() - content = split_incbin_line_into_three(3, 0x100, 10) - # must end up with three INCBINs in output - self.failUnless(content.count("INCBIN") == 3) - - def test_analyze_intervals(self): - global asm, incbin_lines, processed_incbins - asm, incbin_lines, processed_incbins = None, [], {} - asm = ['first line', 'second line', 'third line', - 'INCBIN "baserom.gbc",$90,$200 - $90', - 'fifth line', 'last line', - 'INCBIN "baserom.gbc",$33F,$4000 - $33F'] - isolate_incbins() - process_incbins() - largest = analyze_intervals() - self.assertEqual(largest[0]["line_number"], 6) - self.assertEqual(largest[0]["line"], asm[6]) - self.assertEqual(largest[1]["line_number"], 3) - self.assertEqual(largest[1]["line"], asm[3]) - - def test_generate_diff_insert(self): - global asm - asm = ['first line', 'second line', 'third line', - 'INCBIN "baserom.gbc",$90,$200 - $90', - 'fifth line', 'last line', - 'INCBIN "baserom.gbc",$33F,$4000 - $33F'] - diff = generate_diff_insert(0, "the real first line", debug=False) - self.assertIn("the real first line", diff) - self.assertIn("INCBIN", diff) - self.assertNotIn("No newline at end of file", diff) - self.assertIn("+"+asm[1], diff) - -class TestMapParsing(unittest.TestCase): - def test_parse_all_map_headers(self): - global parse_map_header_at, old_parse_map_header_at, counter - counter = 0 - for k in map_names.keys(): - if "offset" not in map_names[k].keys(): - map_names[k]["offset"] = 0 - temp = parse_map_header_at - temp2 = old_parse_map_header_at - def parse_map_header_at(address, map_group=None, map_id=None, debug=False): - global counter - counter += 1 - return {} - old_parse_map_header_at = parse_map_header_at - parse_all_map_headers(debug=False) - # parse_all_map_headers is currently doing it 2x - # because of the new/old map header parsing routines - self.assertEqual(counter, 388 * 2) - parse_map_header_at = temp - old_parse_map_header_at = temp2 - -class TestTextScript(unittest.TestCase): - """for testing 'in-script' commands, etc.""" - #def test_to_asm(self): - # pass # or raise NotImplementedError, bryan_message - #def test_find_addresses(self): - # pass # or raise NotImplementedError, bryan_message - #def test_parse_text_at(self): - # pass # or raise NotImplementedError, bryan_message - -class TestEncodedText(unittest.TestCase): - """for testing chars-table encoded text chunks""" - - def test_process_00_subcommands(self): - g = process_00_subcommands(0x197186, 0x197186+601, debug=False) - self.assertEqual(len(g), 42) - self.assertEqual(len(g[0]), 13) - self.assertEqual(g[1], [184, 174, 180, 211, 164, 127, 20, 231, 81]) - - def test_parse_text_at2(self): - oakspeech = parse_text_at2(0x197186, 601, debug=False) - self.assertIn("encyclopedia", oakspeech) - self.assertIn("researcher", oakspeech) - self.assertIn("dependable", oakspeech) - - def test_parse_text_engine_script_at(self): - p = parse_text_engine_script_at(0x197185, debug=False) - self.assertEqual(len(p.commands), 2) - self.assertEqual(len(p.commands[0]["lines"]), 41) - - # don't really care about these other two - def test_parse_text_from_bytes(self): pass - def test_parse_text_at(self): pass - -class TestScript(unittest.TestCase): - """for testing parse_script_engine_script_at and script parsing in - general. Script should be a class.""" - #def test_parse_script_engine_script_at(self): - # pass # or raise NotImplementedError, bryan_message - - def test_find_all_text_pointers_in_script_engine_script(self): - address = 0x197637 # 0x197634 - script = parse_script_engine_script_at(address, debug=False) - bank = calculate_bank(address) - r = find_all_text_pointers_in_script_engine_script(script, bank=bank, debug=False) - results = list(r) - self.assertIn(0x197661, results) - -class TestLabel(unittest.TestCase): - def test_label_making(self): - line_number = 2 - address = 0xf0c0 - label_name = "poop" - l = Label(name=label_name, address=address, line_number=line_number) - self.failUnless(hasattr(l, "name")) - self.failUnless(hasattr(l, "address")) - self.failUnless(hasattr(l, "line_number")) - self.failIf(isinstance(l.address, str)) - self.failIf(isinstance(l.line_number, str)) - self.failUnless(isinstance(l.name, str)) - self.assertEqual(l.line_number, line_number) - self.assertEqual(l.name, label_name) - self.assertEqual(l.address, address) - -class TestByteParams(unittest.TestCase): - @classmethod - def setUpClass(cls): - load_rom() - cls.address = 10 - cls.sbp = SingleByteParam(address=cls.address) - - @classmethod - def tearDownClass(cls): - del cls.sbp - - def test__init__(self): - self.assertEqual(self.sbp.size, 1) - self.assertEqual(self.sbp.address, self.address) - - def test_parse(self): - self.sbp.parse() - self.assertEqual(str(self.sbp.byte), str(45)) - - def test_to_asm(self): - self.assertEqual(self.sbp.to_asm(), "$2d") - self.sbp.should_be_decimal = True - self.assertEqual(self.sbp.to_asm(), str(45)) - - # HexByte and DollarSignByte are the same now - def test_HexByte_to_asm(self): - h = HexByte(address=10) - a = h.to_asm() - self.assertEqual(a, "$2d") - - def test_DollarSignByte_to_asm(self): - d = DollarSignByte(address=10) - a = d.to_asm() - self.assertEqual(a, "$2d") - - def test_ItemLabelByte_to_asm(self): - i = ItemLabelByte(address=433) - self.assertEqual(i.byte, 54) - self.assertEqual(i.to_asm(), "COIN_CASE") - self.assertEqual(ItemLabelByte(address=10).to_asm(), "$2d") - - def test_DecimalParam_to_asm(self): - d = DecimalParam(address=10) - x = d.to_asm() - self.assertEqual(x, str(0x2d)) - -class TestMultiByteParam(unittest.TestCase): - def setup_for(self, somecls, byte_size=2, address=443, **kwargs): - self.cls = somecls(address=address, size=byte_size, **kwargs) - self.assertEqual(self.cls.address, address) - self.assertEqual(self.cls.bytes, rom_interval(address, byte_size, strings=False)) - self.assertEqual(self.cls.size, byte_size) - - def test_two_byte_param(self): - self.setup_for(MultiByteParam, byte_size=2) - self.assertEqual(self.cls.to_asm(), "$f0c0") - - def test_three_byte_param(self): - self.setup_for(MultiByteParam, byte_size=3) - - def test_PointerLabelParam_no_bank(self): - self.setup_for(PointerLabelParam, bank=None) - # assuming no label at this location.. - self.assertEqual(self.cls.to_asm(), "$f0c0") - global all_labels - # hm.. maybe all_labels should be using a class? - all_labels = [{"label": "poop", "address": 0xf0c0, - "offset": 0xf0c0, "bank": 0, - "line_number": 2 - }] - self.assertEqual(self.cls.to_asm(), "poop") - -class TestPostParsing: #(unittest.TestCase): - """tests that must be run after parsing all maps""" - @classmethod - def setUpClass(cls): - run_main() - - def test_signpost_counts(self): - self.assertEqual(len(map_names[1][1]["signposts"]), 0) - self.assertEqual(len(map_names[1][2]["signposts"]), 2) - self.assertEqual(len(map_names[10][5]["signposts"]), 7) - - def test_warp_counts(self): - self.assertEqual(map_names[10][5]["warp_count"], 9) - self.assertEqual(map_names[18][5]["warp_count"], 3) - self.assertEqual(map_names[15][1]["warp_count"], 2) - - def test_map_sizes(self): - self.assertEqual(map_names[15][1]["height"], 18) - self.assertEqual(map_names[15][1]["width"], 10) - self.assertEqual(map_names[7][1]["height"], 4) - self.assertEqual(map_names[7][1]["width"], 4) - - def test_map_connection_counts(self): - self.assertEqual(map_names[7][1]["connections"], 0) - self.assertEqual(map_names[10][1]["connections"], 12) - self.assertEqual(map_names[10][2]["connections"], 12) - self.assertEqual(map_names[11][1]["connections"], 9) # or 13? - - def test_second_map_header_address(self): - self.assertEqual(map_names[11][1]["second_map_header_address"], 0x9509c) - self.assertEqual(map_names[1][5]["second_map_header_address"], 0x95bd0) - - def test_event_address(self): - self.assertEqual(map_names[17][5]["event_address"], 0x194d67) - self.assertEqual(map_names[23][3]["event_address"], 0x1a9ec9) - - def test_people_event_counts(self): - self.assertEqual(len(map_names[23][3]["people_events"]), 4) - self.assertEqual(len(map_names[10][3]["people_events"]), 9) - -class TestMetaTesting(unittest.TestCase): - """test whether or not i am finding at least - some of the tests in this file""" - tests = None - - def setUp(self): - if self.tests == None: - self.__class__.tests = assemble_test_cases() - - def test_assemble_test_cases_count(self): - "does assemble_test_cases find some tests?" - self.failUnless(len(self.tests) > 0) - - def test_assemble_test_cases_inclusion(self): - "is this class found by assemble_test_cases?" - # i guess it would have to be for this to be running? - self.failUnless(self.__class__ in self.tests) - - def test_assemble_test_cases_others(self): - "test other inclusions for assemble_test_cases" - self.failUnless(TestRomStr in self.tests) - self.failUnless(TestCram in self.tests) - - def test_check_has_test(self): - self.failUnless(check_has_test("beaver", ["test_beaver"])) - self.failUnless(check_has_test("beaver", ["test_beaver_2"])) - self.failIf(check_has_test("beaver_1", ["test_beaver"])) - - def test_find_untested_methods(self): - untested = find_untested_methods() - # the return type must be an iterable - self.failUnless(hasattr(untested, "__iter__")) - #.. basically, a list - self.failUnless(isinstance(untested, list)) - - def test_find_untested_methods_method(self): - """create a function and see if it is found""" - # setup a function in the global namespace - global some_random_test_method - # define the method - def some_random_test_method(): pass - # first make sure it is in the global scope - members = inspect.getmembers(sys.modules[__name__], inspect.isfunction) - func_names = [functuple[0] for functuple in members] - self.assertIn("some_random_test_method", func_names) - # test whether or not it is found by find_untested_methods - untested = find_untested_methods() - self.assertIn("some_random_test_method", untested) - # remove the test method from the global namespace - del some_random_test_method - - def test_load_tests(self): - loader = unittest.TestLoader() - suite = load_tests(loader, None, None) - suite._tests[0]._testMethodName - membership_test = lambda member: \ - inspect.isclass(member) and issubclass(member, unittest.TestCase) - tests = inspect.getmembers(sys.modules[__name__], membership_test) - classes = [x[1] for x in tests] - for test in suite._tests: - self.assertIn(test.__class__, classes) - - def test_report_untested(self): - untested = find_untested_methods() - output = report_untested() - if len(untested) > 0: - self.assertIn("NOT TESTED", output) - for name in untested: - self.assertIn(name, output) - elif len(untested) == 0: - self.assertNotIn("NOT TESTED", output) - -def assemble_test_cases(): - """finds classes that inherit from unittest.TestCase - because i am too lazy to remember to add them to a - global list of tests for the suite runner""" - classes = [] - clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) - for (name, some_class) in clsmembers: - if issubclass(some_class, unittest.TestCase): - classes.append(some_class) - return classes - -def load_tests(loader, tests, pattern): - suite = unittest.TestSuite() - for test_class in assemble_test_cases(): - tests = loader.loadTestsFromTestCase(test_class) - suite.addTests(tests) - return suite - -def check_has_test(func_name, tested_names): - """checks if there is a test dedicated to this function""" - if "test_"+func_name in tested_names: - return True - for name in tested_names: - if "test_"+func_name in name: - return True - return False - -def find_untested_methods(): - """finds all untested functions in this module - by searching for method names in test case - method names.""" - untested = [] - avoid_funcs = ["main", "run_tests", "run_main", "copy", "deepcopy"] - test_funcs = [] - # get a list of all classes in this module - classes = inspect.getmembers(sys.modules[__name__], inspect.isclass) - # for each class.. - for (name, klass) in classes: - # only look at those that have tests - if issubclass(klass, unittest.TestCase): - # look at this class' methods - funcs = inspect.getmembers(klass, inspect.ismethod) - # for each method.. - for (name2, func) in funcs: - # store the ones that begin with test_ - if "test_" in name2 and name2[0:5] == "test_": - test_funcs.append([name2, func]) - # assemble a list of all test method names (test_x, test_y, ..) - tested_names = [funcz[0] for funcz in test_funcs] - # now get a list of all functions in this module - funcs = inspect.getmembers(sys.modules[__name__], inspect.isfunction) - # for each function.. - for (name, func) in funcs: - # we don't care about some of these - if name in avoid_funcs: continue - # skip functions beginning with _ - if name[0] == "_": continue - # check if this function has a test named after it - has_test = check_has_test(name, tested_names) - if not has_test: - untested.append(name) - return untested - -def report_untested(): - """ - This reports about untested functions in the global namespace. This was - originally in the crystal module, where it would list out the majority of - the functions. Maybe it should be moved back. - """ - untested = find_untested_methods() - output = "NOT TESTED: [" - first = True - for name in untested: - if first: - output += name - first = False - else: output += ", "+name - output += "]\n" - output += "total untested: " + str(len(untested)) - return output - -def run_tests(): # rather than unittest.main() - loader = unittest.TestLoader() - suite = load_tests(loader, None, None) - unittest.TextTestRunner(verbosity=2).run(suite) - print report_untested() - -# run the unit tests when this file is executed directly -if __name__ == "__main__": - run_tests() diff --git a/tests/test_dump_sections.py b/tests/test_dump_sections.py new file mode 100644 index 0000000..b73b86f --- /dev/null +++ b/tests/test_dump_sections.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +try: + import unittest2 as unittest +except ImportError: + import unittest + +# check for things we need in unittest +if not hasattr(unittest.TestCase, 'setUpClass'): + sys.stderr.write("The unittest2 module or Python 2.7 is required to run this script.") + sys.exit(1) + +from dump_sections import ( + upper_hex, + format_bank_number, + calculate_bank_quantity, + dump_section, + dump_incbin_for_section, +) + +class TestDumpSections(unittest.TestCase): + def test_upper_hex(self): + number = 0x52 + self.assertEquals(number, int("0x" + upper_hex(number), 16)) + + number = 0x1 + self.assertEquals(number, int("0x" + upper_hex(number), 16)) + + number = 0x0 + self.assertEquals(number, int("0x" + upper_hex(number), 16)) + + number = 0xAA + self.assertEquals(number, int("0x" + upper_hex(number), 16)) + + number = 0xFFFFAAA0000 + self.assertEquals(number, int("0x" + upper_hex(number), 16)) + + def test_format_bank_number(self): + address = 0x0 + self.assertEquals("0", format_bank_number(address)) + + address = 0x4000 + self.assertEquals("1", format_bank_number(address)) + + address = 0x1FC000 + self.assertEquals("7F", format_bank_number(address)) + + def test_dump_section(self): + self.assertIn("SECTION", dump_section(str(0))) + self.assertIn("HOME", dump_section(str(0))) + self.assertNotIn("HOME", dump_section(str(1))) + self.assertIn("DATA", dump_section(str(2))) + self.assertIn("BANK", dump_section(str(40))) + self.assertNotIn("BANK", dump_section(str(0))) + + def test_dump_incbin_for_section(self): + self.assertIn("INCBIN", dump_incbin_for_section(0)) + + def test_dump_incbin_for_section_separator(self): + separator = "\n\n" + self.assertIn(separator, dump_incbin_for_section(0, separator=separator)) + + separator = "\t\t" # dumb + self.assertIn(separator, dump_incbin_for_section(0, separator=separator)) + + def test_dump_incbin_for_section_default(self): + rom = "baserom.gbc" + self.assertIn(rom, dump_incbin_for_section(0)) + + rom = "baserom" + self.assertIn(rom, dump_incbin_for_section(0x4000)) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..227071f --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,1014 @@ +# -*- coding: utf-8 -*- + +import os +import sys +import inspect +from copy import copy +import hashlib +import random +import json + +from interval_map import IntervalMap +from chars import chars, jap_chars + +from romstr import ( + RomStr, + AsmList, +) + +from item_constants import ( + item_constants, + find_item_label_by_id, + generate_item_constants, +) + +from pointers import ( + calculate_bank, + calculate_pointer, +) + +from pksv import ( + pksv_gs, + pksv_crystal, +) + +from labels import ( + remove_quoted_text, + line_has_comment_address, + line_has_label, + get_label_from_line, +) + +from crystal import ( + rom, + load_rom, + rom_until, + direct_load_rom, + parse_script_engine_script_at, + parse_text_engine_script_at, + parse_text_at2, + find_all_text_pointers_in_script_engine_script, + SingleByteParam, + HexByte, + MultiByteParam, + PointerLabelParam, + ItemLabelByte, + DollarSignByte, + DecimalParam, + rom_interval, + map_names, + Label, + scan_for_predefined_labels, + all_labels, + write_all_labels, + parse_map_header_at, + old_parse_map_header_at, + process_00_subcommands, + parse_all_map_headers, + translate_command_byte, + map_name_cleaner, + load_map_group_offsets, + load_asm, + asm, + is_valid_address, + index, + how_many_until, + grouper, + get_pokemon_constant_by_id, + generate_map_constant_labels, + get_map_constant_label_by_id, + get_id_for_map_constant_label, + calculate_pointer_from_bytes_at, + isolate_incbins, + process_incbins, + get_labels_between, + generate_diff_insert, + find_labels_without_addresses, + rom_text_at, + get_label_for, + split_incbin_line_into_three, + reset_incbins, +) + +# for testing all this crap +try: + import unittest2 as unittest +except ImportError: + import unittest + +# check for things we need in unittest +if not hasattr(unittest.TestCase, 'setUpClass'): + sys.stderr.write("The unittest2 module or Python 2.7 is required to run this script.") + sys.exit(1) + +class TestCram(unittest.TestCase): + "this is where i cram all of my unit tests together" + + @classmethod + def setUpClass(cls): + global rom + cls.rom = direct_load_rom() + rom = cls.rom + + @classmethod + def tearDownClass(cls): + del cls.rom + + def test_generic_useless(self): + "do i know how to write a test?" + self.assertEqual(1, 1) + + def test_map_name_cleaner(self): + name = "hello world" + cleaned_name = map_name_cleaner(name) + self.assertNotEqual(name, cleaned_name) + self.failUnless(" " not in cleaned_name) + name = "Some Random Pokémon Center" + cleaned_name = map_name_cleaner(name) + self.assertNotEqual(name, cleaned_name) + self.failIf(" " in cleaned_name) + self.failIf("é" in cleaned_name) + + def test_grouper(self): + data = range(0, 10) + groups = grouper(data, count=2) + self.assertEquals(len(groups), 5) + data = range(0, 20) + groups = grouper(data, count=2) + self.assertEquals(len(groups), 10) + self.assertNotEqual(data, groups) + self.assertNotEqual(len(data), len(groups)) + + def test_direct_load_rom(self): + rom = self.rom + self.assertEqual(len(rom), 2097152) + self.failUnless(isinstance(rom, RomStr)) + + def test_load_rom(self): + global rom + rom = None + load_rom() + self.failIf(rom == None) + rom = RomStr(None) + load_rom() + self.failIf(rom == RomStr(None)) + + def test_load_asm(self): + asm = load_asm() + joined_lines = "\n".join(asm) + self.failUnless("SECTION" in joined_lines) + self.failUnless("bank" in joined_lines) + self.failUnless(isinstance(asm, AsmList)) + + def test_rom_file_existence(self): + "ROM file must exist" + self.failUnless("baserom.gbc" in os.listdir("../")) + + def test_rom_md5(self): + "ROM file must have the correct md5 sum" + rom = self.rom + correct = "9f2922b235a5eeb78d65594e82ef5dde" + md5 = hashlib.md5() + md5.update(rom) + md5sum = md5.hexdigest() + self.assertEqual(md5sum, correct) + + def test_bizarre_http_presence(self): + rom_segment = self.rom[0x112116:0x112116+8] + self.assertEqual(rom_segment, "HTTP/1.0") + + def test_rom_interval(self): + address = 0x100 + interval = 10 + correct_strings = ['0x0', '0xc3', '0x6e', '0x1', '0xce', + '0xed', '0x66', '0x66', '0xcc', '0xd'] + byte_strings = rom_interval(address, interval, strings=True) + self.assertEqual(byte_strings, correct_strings) + correct_ints = [0, 195, 110, 1, 206, 237, 102, 102, 204, 13] + ints = rom_interval(address, interval, strings=False) + self.assertEqual(ints, correct_ints) + + def test_rom_until(self): + address = 0x1337 + byte = 0x13 + bytes = rom_until(address, byte, strings=True) + self.failUnless(len(bytes) == 3) + self.failUnless(bytes[0] == '0xd5') + bytes = rom_until(address, byte, strings=False) + self.failUnless(len(bytes) == 3) + self.failUnless(bytes[0] == 0xd5) + + def test_how_many_until(self): + how_many = how_many_until(chr(0x13), 0x1337) + self.assertEqual(how_many, 3) + + def test_calculate_bank(self): + self.failUnless(calculate_bank(0x8000) == 2) + self.failUnless(calculate_bank("0x9000") == 2) + self.failUnless(calculate_bank(0) == 0) + for address in [0x4000, 0x5000, 0x6000, 0x7000]: + self.assertRaises(Exception, calculate_bank, address) + + def test_calculate_pointer(self): + # for offset <= 0x4000 + self.assertEqual(calculate_pointer(0x0000), 0x0000) + self.assertEqual(calculate_pointer(0x3FFF), 0x3FFF) + # for 0x4000 <= offset <= 0x7FFFF + self.assertEqual(calculate_pointer(0x430F, bank=5), 0x1430F) + # for offset >= 0x7FFF + self.assertEqual(calculate_pointer(0x8FFF, bank=6), calculate_pointer(0x8FFF, bank=7)) + + def test_calculate_pointer_from_bytes_at(self): + addr1 = calculate_pointer_from_bytes_at(0x100, bank=False) + self.assertEqual(addr1, 0xc300) + addr2 = calculate_pointer_from_bytes_at(0x100, bank=True) + self.assertEqual(addr2, 0x2ec3) + + def test_rom_text_at(self): + self.assertEquals(rom_text_at(0x112116, 8), "HTTP/1.0") + + def test_translate_command_byte(self): + self.failUnless(translate_command_byte(crystal=0x0) == 0x0) + self.failUnless(translate_command_byte(crystal=0x10) == 0x10) + self.failUnless(translate_command_byte(crystal=0x40) == 0x40) + self.failUnless(translate_command_byte(gold=0x0) == 0x0) + self.failUnless(translate_command_byte(gold=0x10) == 0x10) + self.failUnless(translate_command_byte(gold=0x40) == 0x40) + self.assertEqual(translate_command_byte(gold=0x0), translate_command_byte(crystal=0x0)) + self.failUnless(translate_command_byte(gold=0x52) == 0x53) + self.failUnless(translate_command_byte(gold=0x53) == 0x54) + self.failUnless(translate_command_byte(crystal=0x53) == 0x52) + self.failUnless(translate_command_byte(crystal=0x52) == None) + self.assertRaises(Exception, translate_command_byte, None, gold=0xA4) + + def test_pksv_integrity(self): + "does pksv_gs look okay?" + self.assertEqual(pksv_gs[0x00], "2call") + self.assertEqual(pksv_gs[0x2D], "givepoke") + self.assertEqual(pksv_gs[0x85], "waitbutton") + self.assertEqual(pksv_crystal[0x00], "2call") + self.assertEqual(pksv_crystal[0x86], "waitbutton") + self.assertEqual(pksv_crystal[0xA2], "credits") + + def test_chars_integrity(self): + self.assertEqual(chars[0x80], "A") + self.assertEqual(chars[0xA0], "a") + self.assertEqual(chars[0xF0], "¥") + self.assertEqual(jap_chars[0x44], "ぱ") + + def test_map_names_integrity(self): + def map_name(map_group, map_id): return map_names[map_group][map_id]["name"] + self.assertEqual(map_name(2, 7), "Mahogany Town") + self.assertEqual(map_name(3, 0x34), "Ilex Forest") + self.assertEqual(map_name(7, 0x11), "Cerulean City") + + def test_load_map_group_offsets(self): + addresses = load_map_group_offsets() + self.assertEqual(len(addresses), 26, msg="there should be 26 map groups") + addresses = load_map_group_offsets() + self.assertEqual(len(addresses), 26, msg="there should still be 26 map groups") + self.assertIn(0x94034, addresses) + for address in addresses: + self.assertGreaterEqual(address, 0x4000) + self.failIf(0x4000 <= address <= 0x7FFF) + self.failIf(address <= 0x4000) + + def test_index(self): + self.assertTrue(index([1,2,3,4], lambda f: True) == 0) + self.assertTrue(index([1,2,3,4], lambda f: f==3) == 2) + + def test_get_pokemon_constant_by_id(self): + x = get_pokemon_constant_by_id + self.assertEqual(x(1), "BULBASAUR") + self.assertEqual(x(151), "MEW") + self.assertEqual(x(250), "HO_OH") + + def test_find_item_label_by_id(self): + x = find_item_label_by_id + self.assertEqual(x(249), "HM_07") + self.assertEqual(x(173), "BERRY") + self.assertEqual(x(45), None) + + def test_generate_item_constants(self): + x = generate_item_constants + r = x() + self.failUnless("HM_07" in r) + self.failUnless("EQU" in r) + + def test_get_label_for(self): + global all_labels + temp = copy(all_labels) + # this is basd on the format defined in get_labels_between + all_labels = [{"label": "poop", "address": 0x5, + "offset": 0x5, "bank": 0, + "line_number": 2 + }] + self.assertEqual(get_label_for(5), "poop") + all_labels = temp + + def test_generate_map_constant_labels(self): + ids = generate_map_constant_labels() + self.assertEqual(ids[0]["label"], "OLIVINE_POKECENTER_1F") + self.assertEqual(ids[1]["label"], "OLIVINE_GYM") + + def test_get_id_for_map_constant_label(self): + global map_internal_ids + map_internal_ids = generate_map_constant_labels() + self.assertEqual(get_id_for_map_constant_label("OLIVINE_GYM"), 1) + self.assertEqual(get_id_for_map_constant_label("OLIVINE_POKECENTER_1F"), 0) + + def test_get_map_constant_label_by_id(self): + global map_internal_ids + map_internal_ids = generate_map_constant_labels() + self.assertEqual(get_map_constant_label_by_id(0), "OLIVINE_POKECENTER_1F") + self.assertEqual(get_map_constant_label_by_id(1), "OLIVINE_GYM") + + def test_is_valid_address(self): + self.assertTrue(is_valid_address(0)) + self.assertTrue(is_valid_address(1)) + self.assertTrue(is_valid_address(10)) + self.assertTrue(is_valid_address(100)) + self.assertTrue(is_valid_address(1000)) + self.assertTrue(is_valid_address(10000)) + self.assertFalse(is_valid_address(2097153)) + self.assertFalse(is_valid_address(2098000)) + addresses = [random.randrange(0,2097153) for i in range(0, 9+1)] + for address in addresses: + self.assertTrue(is_valid_address(address)) + +class TestIntervalMap(unittest.TestCase): + def test_intervals(self): + i = IntervalMap() + first = "hello world" + second = "testing 123" + i[0:5] = first + i[5:10] = second + self.assertEqual(i[0], first) + self.assertEqual(i[1], first) + self.assertNotEqual(i[5], first) + self.assertEqual(i[6], second) + i[3:10] = second + self.assertEqual(i[3], second) + self.assertNotEqual(i[4], first) + + def test_items(self): + i = IntervalMap() + first = "hello world" + second = "testing 123" + i[0:5] = first + i[5:10] = second + results = list(i.items()) + self.failUnless(len(results) == 2) + self.assertEqual(results[0], ((0, 5), "hello world")) + self.assertEqual(results[1], ((5, 10), "testing 123")) + +class TestRomStr(unittest.TestCase): + """RomStr is a class that should act exactly like str() + except that it never shows the contents of it string + unless explicitly forced""" + sample_text = "hello world!" + sample = None + + def setUp(self): + if self.sample == None: + self.__class__.sample = RomStr(self.sample_text) + + def test_equals(self): + "check if RomStr() == str()" + self.assertEquals(self.sample_text, self.sample) + + def test_not_equal(self): + "check if RomStr('a') != RomStr('b')" + self.assertNotEqual(RomStr('a'), RomStr('b')) + + def test_appending(self): + "check if RomStr()+'a'==str()+'a'" + self.assertEquals(self.sample_text+'a', self.sample+'a') + + def test_conversion(self): + "check if RomStr() -> str() works" + self.assertEquals(str(self.sample), self.sample_text) + + def test_inheritance(self): + self.failUnless(issubclass(RomStr, str)) + + def test_length(self): + self.assertEquals(len(self.sample_text), len(self.sample)) + self.assertEquals(len(self.sample_text), self.sample.length()) + self.assertEquals(len(self.sample), self.sample.length()) + + def test_rom_interval(self): + global rom + load_rom() + address = 0x100 + interval = 10 + correct_strings = ['0x0', '0xc3', '0x6e', '0x1', '0xce', + '0xed', '0x66', '0x66', '0xcc', '0xd'] + byte_strings = rom.interval(address, interval, strings=True) + self.assertEqual(byte_strings, correct_strings) + correct_ints = [0, 195, 110, 1, 206, 237, 102, 102, 204, 13] + ints = rom.interval(address, interval, strings=False) + self.assertEqual(ints, correct_ints) + + def test_rom_until(self): + global rom + load_rom() + address = 0x1337 + byte = 0x13 + bytes = rom.until(address, byte, strings=True) + self.failUnless(len(bytes) == 3) + self.failUnless(bytes[0] == '0xd5') + bytes = rom.until(address, byte, strings=False) + self.failUnless(len(bytes) == 3) + self.failUnless(bytes[0] == 0xd5) + +class TestAsmList(unittest.TestCase): + """AsmList is a class that should act exactly like list() + except that it never shows the contents of its list + unless explicitly forced""" + + def test_equals(self): + base = [1,2,3] + asm = AsmList(base) + self.assertEquals(base, asm) + self.assertEquals(asm, base) + self.assertEquals(base, list(asm)) + + def test_inheritance(self): + self.failUnless(issubclass(AsmList, list)) + + def test_length(self): + base = range(0, 10) + asm = AsmList(base) + self.assertEquals(len(base), len(asm)) + self.assertEquals(len(base), asm.length()) + self.assertEquals(len(base), len(list(asm))) + self.assertEquals(len(asm), asm.length()) + + def test_remove_quoted_text(self): + x = remove_quoted_text + self.assertEqual(x("hello world"), "hello world") + self.assertEqual(x("hello \"world\""), "hello ") + input = 'hello world "testing 123"' + self.assertNotEqual(x(input), input) + input = "hello world 'testing 123'" + self.assertNotEqual(x(input), input) + self.failIf("testing" in x(input)) + + def test_line_has_comment_address(self): + x = line_has_comment_address + self.assertFalse(x("")) + self.assertFalse(x(";")) + self.assertFalse(x(";;;")) + self.assertFalse(x(":;")) + self.assertFalse(x(":;:")) + self.assertFalse(x(";:")) + self.assertFalse(x(" ")) + self.assertFalse(x("".join(" " * 5))) + self.assertFalse(x("".join(" " * 10))) + self.assertFalse(x("hello world")) + self.assertFalse(x("hello_world")) + self.assertFalse(x("hello_world:")) + self.assertFalse(x("hello_world:;")) + self.assertFalse(x("hello_world: ;")) + self.assertFalse(x("hello_world: ; ")) + self.assertFalse(x("hello_world: ;" + "".join(" " * 5))) + self.assertFalse(x("hello_world: ;" + "".join(" " * 10))) + self.assertTrue(x(";1")) + self.assertTrue(x(";F")) + self.assertTrue(x(";$00FF")) + self.assertTrue(x(";0x00FF")) + self.assertTrue(x("; 0x00FF")) + self.assertTrue(x(";$3:$300")) + self.assertTrue(x(";0x3:$300")) + self.assertTrue(x(";$3:0x300")) + self.assertTrue(x(";3:300")) + self.assertTrue(x(";3:FFAA")) + self.assertFalse(x('hello world "how are you today;0x1"')) + self.assertTrue(x('hello world "how are you today:0x1";1')) + returnable = {} + self.assertTrue(x("hello_world: ; 0x4050", returnable=returnable, bank=5)) + self.assertTrue(returnable["address"] == 0x14050) + + def test_line_has_label(self): + x = line_has_label + self.assertTrue(x("hi:")) + self.assertTrue(x("Hello: ")) + self.assertTrue(x("MyLabel: ; test xyz")) + self.assertFalse(x(":")) + self.assertFalse(x(";HelloWorld:")) + self.assertFalse(x("::::")) + self.assertFalse(x(":;:;:;:::")) + + def test_get_label_from_line(self): + x = get_label_from_line + self.assertEqual(x("HelloWorld: "), "HelloWorld") + self.assertEqual(x("HiWorld:"), "HiWorld") + self.assertEqual(x("HiWorld"), None) + + def test_find_labels_without_addresses(self): + global asm + asm = ["hello_world: ; 0x1", "hello_world2: ;"] + labels = find_labels_without_addresses() + self.failUnless(labels[0]["label"] == "hello_world2") + asm = ["hello world: ;1", "hello_world: ;2"] + labels = find_labels_without_addresses() + self.failUnless(len(labels) == 0) + asm = None + + def test_get_labels_between(self): + global asm + x = get_labels_between#(start_line_id, end_line_id, bank) + asm = ["HelloWorld: ;1", + "hi:", + "no label on this line", + ] + labels = x(0, 2, 0x12) + self.assertEqual(len(labels), 1) + self.assertEqual(labels[0]["label"], "HelloWorld") + del asm + + # this test takes a lot of time :( + def xtest_scan_for_predefined_labels(self): + # label keys: line_number, bank, label, offset, address + load_asm() + all_labels = scan_for_predefined_labels() + label_names = [x["label"] for x in all_labels] + self.assertIn("GetFarByte", label_names) + self.assertIn("AddNTimes", label_names) + self.assertIn("CheckShininess", label_names) + + def test_write_all_labels(self): + """dumping json into a file""" + filename = "test_labels.json" + # remove the current file + if os.path.exists(filename): + os.system("rm " + filename) + # make up some labels + labels = [] + # fake label 1 + label = {"line_number": 5, "bank": 0, "label": "SomeLabel", "address": 0x10} + labels.append(label) + # fake label 2 + label = {"line_number": 15, "bank": 2, "label": "SomeOtherLabel", "address": 0x9F0A} + labels.append(label) + # dump to file + write_all_labels(labels, filename=filename) + # open the file and read the contents + file_handler = open(filename, "r") + contents = file_handler.read() + file_handler.close() + # parse into json + obj = json.read(contents) + # begin testing + self.assertEqual(len(obj), len(labels)) + self.assertEqual(len(obj), 2) + self.assertEqual(obj, labels) + + def test_isolate_incbins(self): + global asm + asm = ["123", "456", "789", "abc", "def", "ghi", + 'INCBIN "baserom.gbc",$12DA,$12F8 - $12DA', + "jkl", + 'INCBIN "baserom.gbc",$137A,$13D0 - $137A'] + lines = isolate_incbins() + self.assertIn(asm[6], lines) + self.assertIn(asm[8], lines) + for line in lines: + self.assertIn("baserom", line) + + def test_process_incbins(self): + global incbin_lines, processed_incbins, asm + incbin_lines = ['INCBIN "baserom.gbc",$12DA,$12F8 - $12DA', + 'INCBIN "baserom.gbc",$137A,$13D0 - $137A'] + asm = copy(incbin_lines) + asm.insert(1, "some other random line") + processed_incbins = process_incbins() + self.assertEqual(len(processed_incbins), len(incbin_lines)) + self.assertEqual(processed_incbins[0]["line"], incbin_lines[0]) + self.assertEqual(processed_incbins[2]["line"], incbin_lines[1]) + + def test_reset_incbins(self): + global asm, incbin_lines, processed_incbins + # temporarily override the functions + global load_asm, isolate_incbins, process_incbins + temp1, temp2, temp3 = load_asm, isolate_incbins, process_incbins + def load_asm(): pass + def isolate_incbins(): pass + def process_incbins(): pass + # call reset + reset_incbins() + # check the results + self.assertTrue(asm == [] or asm == None) + self.assertTrue(incbin_lines == []) + self.assertTrue(processed_incbins == {}) + # reset the original functions + load_asm, isolate_incbins, process_incbins = temp1, temp2, temp3 + + def test_find_incbin_to_replace_for(self): + global asm, incbin_lines, processed_incbins + asm = ['first line', 'second line', 'third line', + 'INCBIN "baserom.gbc",$90,$200 - $90', + 'fifth line', 'last line'] + isolate_incbins() + process_incbins() + line_num = find_incbin_to_replace_for(0x100) + # must be the 4th line (the INBIN line) + self.assertEqual(line_num, 3) + + def test_split_incbin_line_into_three(self): + global asm, incbin_lines, processed_incbins + asm = ['first line', 'second line', 'third line', + 'INCBIN "baserom.gbc",$90,$200 - $90', + 'fifth line', 'last line'] + isolate_incbins() + process_incbins() + content = split_incbin_line_into_three(3, 0x100, 10) + # must end up with three INCBINs in output + self.failUnless(content.count("INCBIN") == 3) + + def test_analyze_intervals(self): + global asm, incbin_lines, processed_incbins + asm, incbin_lines, processed_incbins = None, [], {} + asm = ['first line', 'second line', 'third line', + 'INCBIN "baserom.gbc",$90,$200 - $90', + 'fifth line', 'last line', + 'INCBIN "baserom.gbc",$33F,$4000 - $33F'] + isolate_incbins() + process_incbins() + largest = analyze_intervals() + self.assertEqual(largest[0]["line_number"], 6) + self.assertEqual(largest[0]["line"], asm[6]) + self.assertEqual(largest[1]["line_number"], 3) + self.assertEqual(largest[1]["line"], asm[3]) + + def test_generate_diff_insert(self): + global asm + asm = ['first line', 'second line', 'third line', + 'INCBIN "baserom.gbc",$90,$200 - $90', + 'fifth line', 'last line', + 'INCBIN "baserom.gbc",$33F,$4000 - $33F'] + diff = generate_diff_insert(0, "the real first line", debug=False) + self.assertIn("the real first line", diff) + self.assertIn("INCBIN", diff) + self.assertNotIn("No newline at end of file", diff) + self.assertIn("+"+asm[1], diff) + +class TestMapParsing(unittest.TestCase): + def test_parse_all_map_headers(self): + global parse_map_header_at, old_parse_map_header_at, counter + counter = 0 + for k in map_names.keys(): + if "offset" not in map_names[k].keys(): + map_names[k]["offset"] = 0 + temp = parse_map_header_at + temp2 = old_parse_map_header_at + def parse_map_header_at(address, map_group=None, map_id=None, debug=False): + global counter + counter += 1 + return {} + old_parse_map_header_at = parse_map_header_at + parse_all_map_headers(debug=False) + # parse_all_map_headers is currently doing it 2x + # because of the new/old map header parsing routines + self.assertEqual(counter, 388 * 2) + parse_map_header_at = temp + old_parse_map_header_at = temp2 + +class TestTextScript(unittest.TestCase): + """for testing 'in-script' commands, etc.""" + #def test_to_asm(self): + # pass # or raise NotImplementedError, bryan_message + #def test_find_addresses(self): + # pass # or raise NotImplementedError, bryan_message + #def test_parse_text_at(self): + # pass # or raise NotImplementedError, bryan_message + +class TestEncodedText(unittest.TestCase): + """for testing chars-table encoded text chunks""" + + def test_process_00_subcommands(self): + g = process_00_subcommands(0x197186, 0x197186+601, debug=False) + self.assertEqual(len(g), 42) + self.assertEqual(len(g[0]), 13) + self.assertEqual(g[1], [184, 174, 180, 211, 164, 127, 20, 231, 81]) + + def test_parse_text_at2(self): + oakspeech = parse_text_at2(0x197186, 601, debug=False) + self.assertIn("encyclopedia", oakspeech) + self.assertIn("researcher", oakspeech) + self.assertIn("dependable", oakspeech) + + def test_parse_text_engine_script_at(self): + p = parse_text_engine_script_at(0x197185, debug=False) + self.assertEqual(len(p.commands), 2) + self.assertEqual(len(p.commands[0]["lines"]), 41) + + # don't really care about these other two + def test_parse_text_from_bytes(self): pass + def test_parse_text_at(self): pass + +class TestScript(unittest.TestCase): + """for testing parse_script_engine_script_at and script parsing in + general. Script should be a class.""" + #def test_parse_script_engine_script_at(self): + # pass # or raise NotImplementedError, bryan_message + + def test_find_all_text_pointers_in_script_engine_script(self): + address = 0x197637 # 0x197634 + script = parse_script_engine_script_at(address, debug=False) + bank = calculate_bank(address) + r = find_all_text_pointers_in_script_engine_script(script, bank=bank, debug=False) + results = list(r) + self.assertIn(0x197661, results) + +class TestLabel(unittest.TestCase): + def test_label_making(self): + line_number = 2 + address = 0xf0c0 + label_name = "poop" + l = Label(name=label_name, address=address, line_number=line_number) + self.failUnless(hasattr(l, "name")) + self.failUnless(hasattr(l, "address")) + self.failUnless(hasattr(l, "line_number")) + self.failIf(isinstance(l.address, str)) + self.failIf(isinstance(l.line_number, str)) + self.failUnless(isinstance(l.name, str)) + self.assertEqual(l.line_number, line_number) + self.assertEqual(l.name, label_name) + self.assertEqual(l.address, address) + +class TestByteParams(unittest.TestCase): + @classmethod + def setUpClass(cls): + load_rom() + cls.address = 10 + cls.sbp = SingleByteParam(address=cls.address) + + @classmethod + def tearDownClass(cls): + del cls.sbp + + def test__init__(self): + self.assertEqual(self.sbp.size, 1) + self.assertEqual(self.sbp.address, self.address) + + def test_parse(self): + self.sbp.parse() + self.assertEqual(str(self.sbp.byte), str(45)) + + def test_to_asm(self): + self.assertEqual(self.sbp.to_asm(), "$2d") + self.sbp.should_be_decimal = True + self.assertEqual(self.sbp.to_asm(), str(45)) + + # HexByte and DollarSignByte are the same now + def test_HexByte_to_asm(self): + h = HexByte(address=10) + a = h.to_asm() + self.assertEqual(a, "$2d") + + def test_DollarSignByte_to_asm(self): + d = DollarSignByte(address=10) + a = d.to_asm() + self.assertEqual(a, "$2d") + + def test_ItemLabelByte_to_asm(self): + i = ItemLabelByte(address=433) + self.assertEqual(i.byte, 54) + self.assertEqual(i.to_asm(), "COIN_CASE") + self.assertEqual(ItemLabelByte(address=10).to_asm(), "$2d") + + def test_DecimalParam_to_asm(self): + d = DecimalParam(address=10) + x = d.to_asm() + self.assertEqual(x, str(0x2d)) + +class TestMultiByteParam(unittest.TestCase): + def setup_for(self, somecls, byte_size=2, address=443, **kwargs): + self.cls = somecls(address=address, size=byte_size, **kwargs) + self.assertEqual(self.cls.address, address) + self.assertEqual(self.cls.bytes, rom_interval(address, byte_size, strings=False)) + self.assertEqual(self.cls.size, byte_size) + + def test_two_byte_param(self): + self.setup_for(MultiByteParam, byte_size=2) + self.assertEqual(self.cls.to_asm(), "$f0c0") + + def test_three_byte_param(self): + self.setup_for(MultiByteParam, byte_size=3) + + def test_PointerLabelParam_no_bank(self): + self.setup_for(PointerLabelParam, bank=None) + # assuming no label at this location.. + self.assertEqual(self.cls.to_asm(), "$f0c0") + global all_labels + # hm.. maybe all_labels should be using a class? + all_labels = [{"label": "poop", "address": 0xf0c0, + "offset": 0xf0c0, "bank": 0, + "line_number": 2 + }] + self.assertEqual(self.cls.to_asm(), "poop") + +class TestPostParsing: #(unittest.TestCase): + """tests that must be run after parsing all maps""" + @classmethod + def setUpClass(cls): + run_main() + + def test_signpost_counts(self): + self.assertEqual(len(map_names[1][1]["signposts"]), 0) + self.assertEqual(len(map_names[1][2]["signposts"]), 2) + self.assertEqual(len(map_names[10][5]["signposts"]), 7) + + def test_warp_counts(self): + self.assertEqual(map_names[10][5]["warp_count"], 9) + self.assertEqual(map_names[18][5]["warp_count"], 3) + self.assertEqual(map_names[15][1]["warp_count"], 2) + + def test_map_sizes(self): + self.assertEqual(map_names[15][1]["height"], 18) + self.assertEqual(map_names[15][1]["width"], 10) + self.assertEqual(map_names[7][1]["height"], 4) + self.assertEqual(map_names[7][1]["width"], 4) + + def test_map_connection_counts(self): + self.assertEqual(map_names[7][1]["connections"], 0) + self.assertEqual(map_names[10][1]["connections"], 12) + self.assertEqual(map_names[10][2]["connections"], 12) + self.assertEqual(map_names[11][1]["connections"], 9) # or 13? + + def test_second_map_header_address(self): + self.assertEqual(map_names[11][1]["second_map_header_address"], 0x9509c) + self.assertEqual(map_names[1][5]["second_map_header_address"], 0x95bd0) + + def test_event_address(self): + self.assertEqual(map_names[17][5]["event_address"], 0x194d67) + self.assertEqual(map_names[23][3]["event_address"], 0x1a9ec9) + + def test_people_event_counts(self): + self.assertEqual(len(map_names[23][3]["people_events"]), 4) + self.assertEqual(len(map_names[10][3]["people_events"]), 9) + +class TestMetaTesting(unittest.TestCase): + """test whether or not i am finding at least + some of the tests in this file""" + tests = None + + def setUp(self): + if self.tests == None: + self.__class__.tests = assemble_test_cases() + + def test_assemble_test_cases_count(self): + "does assemble_test_cases find some tests?" + self.failUnless(len(self.tests) > 0) + + def test_assemble_test_cases_inclusion(self): + "is this class found by assemble_test_cases?" + # i guess it would have to be for this to be running? + self.failUnless(self.__class__ in self.tests) + + def test_assemble_test_cases_others(self): + "test other inclusions for assemble_test_cases" + self.failUnless(TestRomStr in self.tests) + self.failUnless(TestCram in self.tests) + + def test_check_has_test(self): + self.failUnless(check_has_test("beaver", ["test_beaver"])) + self.failUnless(check_has_test("beaver", ["test_beaver_2"])) + self.failIf(check_has_test("beaver_1", ["test_beaver"])) + + def test_find_untested_methods(self): + untested = find_untested_methods() + # the return type must be an iterable + self.failUnless(hasattr(untested, "__iter__")) + #.. basically, a list + self.failUnless(isinstance(untested, list)) + + def test_find_untested_methods_method(self): + """create a function and see if it is found""" + # setup a function in the global namespace + global some_random_test_method + # define the method + def some_random_test_method(): pass + # first make sure it is in the global scope + members = inspect.getmembers(sys.modules[__name__], inspect.isfunction) + func_names = [functuple[0] for functuple in members] + self.assertIn("some_random_test_method", func_names) + # test whether or not it is found by find_untested_methods + untested = find_untested_methods() + self.assertIn("some_random_test_method", untested) + # remove the test method from the global namespace + del some_random_test_method + + def test_load_tests(self): + loader = unittest.TestLoader() + suite = load_tests(loader, None, None) + suite._tests[0]._testMethodName + membership_test = lambda member: \ + inspect.isclass(member) and issubclass(member, unittest.TestCase) + tests = inspect.getmembers(sys.modules[__name__], membership_test) + classes = [x[1] for x in tests] + for test in suite._tests: + self.assertIn(test.__class__, classes) + + def test_report_untested(self): + untested = find_untested_methods() + output = report_untested() + if len(untested) > 0: + self.assertIn("NOT TESTED", output) + for name in untested: + self.assertIn(name, output) + elif len(untested) == 0: + self.assertNotIn("NOT TESTED", output) + +def assemble_test_cases(): + """finds classes that inherit from unittest.TestCase + because i am too lazy to remember to add them to a + global list of tests for the suite runner""" + classes = [] + clsmembers = inspect.getmembers(sys.modules[__name__], inspect.isclass) + for (name, some_class) in clsmembers: + if issubclass(some_class, unittest.TestCase): + classes.append(some_class) + return classes + +def load_tests(loader, tests, pattern): + suite = unittest.TestSuite() + for test_class in assemble_test_cases(): + tests = loader.loadTestsFromTestCase(test_class) + suite.addTests(tests) + return suite + +def check_has_test(func_name, tested_names): + """checks if there is a test dedicated to this function""" + if "test_"+func_name in tested_names: + return True + for name in tested_names: + if "test_"+func_name in name: + return True + return False + +def find_untested_methods(): + """finds all untested functions in this module + by searching for method names in test case + method names.""" + untested = [] + avoid_funcs = ["main", "run_tests", "run_main", "copy", "deepcopy"] + test_funcs = [] + # get a list of all classes in this module + classes = inspect.getmembers(sys.modules[__name__], inspect.isclass) + # for each class.. + for (name, klass) in classes: + # only look at those that have tests + if issubclass(klass, unittest.TestCase): + # look at this class' methods + funcs = inspect.getmembers(klass, inspect.ismethod) + # for each method.. + for (name2, func) in funcs: + # store the ones that begin with test_ + if "test_" in name2 and name2[0:5] == "test_": + test_funcs.append([name2, func]) + # assemble a list of all test method names (test_x, test_y, ..) + tested_names = [funcz[0] for funcz in test_funcs] + # now get a list of all functions in this module + funcs = inspect.getmembers(sys.modules[__name__], inspect.isfunction) + # for each function.. + for (name, func) in funcs: + # we don't care about some of these + if name in avoid_funcs: continue + # skip functions beginning with _ + if name[0] == "_": continue + # check if this function has a test named after it + has_test = check_has_test(name, tested_names) + if not has_test: + untested.append(name) + return untested + +def report_untested(): + """ + This reports about untested functions in the global namespace. This was + originally in the crystal module, where it would list out the majority of + the functions. Maybe it should be moved back. + """ + untested = find_untested_methods() + output = "NOT TESTED: [" + first = True + for name in untested: + if first: + output += name + first = False + else: output += ", "+name + output += "]\n" + output += "total untested: " + str(len(untested)) + return output + +def run_tests(): # rather than unittest.main() + loader = unittest.TestLoader() + suite = load_tests(loader, None, None) + unittest.TextTestRunner(verbosity=2).run(suite) + print report_untested() + +# run the unit tests when this file is executed directly +if __name__ == "__main__": + run_tests() diff --git a/trainers.py b/trainers.py deleted file mode 100644 index cf17b98..0000000 --- a/trainers.py +++ /dev/null @@ -1,108 +0,0 @@ -# -*- coding: utf-8 -*- -# url: http://hax.iimarck.us/topic/8/ - -# for fixing trainer_group_names -import re - -trainer_group_pointer_table_address = 0x39999 -trainer_group_pointer_table_address_gs = 0x3993E - -# Any trainer that appears more than once should have an id after each -# trainer name. - -# "uses_numeric_trainer_ids" means never use a name for the trainer_id -trainer_group_names = { -0x01: {"name": "Falkner", "uses_numeric_trainer_ids": True, "constant": "FALKNER"}, -0x02: {"name": "Whitney", "uses_numeric_trainer_ids": True, "constant": "WHITNEY"}, -0x03: {"name": "Bugsy", "uses_numeric_trainer_ids": True, "constant": "BUGSY"}, -0x04: {"name": "Morty", "uses_numeric_trainer_ids": True, "constant": "MORTY"}, -0x05: {"name": "Pryce", "uses_numeric_trainer_ids": True, "constant": "PRYCE"}, -0x06: {"name": "Jasmine", "uses_numeric_trainer_ids": True, "constant": "JASMINE"}, -0x07: {"name": "Chuck", "uses_numeric_trainer_ids": True, "constant": "CHUCK"}, -0x08: {"name": "Clair", "uses_numeric_trainer_ids": True, "constant": "CLAIR"}, -0x09: {"name": "Rival1", "constant": "RIVAL1"}, -#PokemonProf group is empty :/ -0x0A: {"name": "Pokémon Prof.", "constant": "POKEMON_PROF"}, -0x0B: {"name": "Elite Four Will", "uses_numeric_trainer_ids": True, "constant": "WILL"}, -0x0C: {"name": "PKMN [Cal]", "constant": "CAL"}, -0x0D: {"name": "Elite Four Bruno", "uses_numeric_trainer_ids": True, "constant": "BRUNO"}, -0x0E: {"name": "Elite Four Karen", "uses_numeric_trainer_ids": True, "constant": "KAREN"}, -0x0F: {"name": "Elite Four Koga", "uses_numeric_trainer_ids": True, "constant": "KOGA"}, -0x10: {"name": "Champion", "constant": "CHAMPION"}, -0x11: {"name": "Brock", "uses_numeric_trainer_ids": True, "constant": "BROCK"}, -0x12: {"name": "Misty", "uses_numeric_trainer_ids": True, "constant": "MISTY"}, -0x13: {"name": "Lt.Surge", "uses_numeric_trainer_ids": True, "constant": "LT_SURGE"}, -0x14: {"name": "Scientist", "constant": "SCIENTIST"}, -0x15: {"name": "Erika", "uses_numeric_trainer_ids": True, "constant": "ERIKA"}, -0x16: {"name": "Youngster", "constant": "YOUNGSTER"}, -0x17: {"name": "Schoolboy", "constant": "SCHOOLBOY"}, -0x18: {"name": "Bird Keeper", "constant": "BIRD_KEEPER"}, -0x19: {"name": "Lass", "constant": "LASS"}, -0x1A: {"name": "Janine", "uses_numeric_trainer_ids": True, "constant": "JANINE"}, -0x1B: {"name": "CooltrainerM", "constant": "COOLTRAINERM"}, -0x1C: {"name": "CooltrainerF", "constant": "COOLTRAINERF"}, -0x1D: {"name": "Beauty", "constant": "BEAUTY"}, -0x1E: {"name": "Pokémaniac", "constant": "POKEMANIAC"}, -0x1F: {"name": "GruntM", "uses_numeric_trainer_ids": True, "constant": "GRUNTM"}, -0x20: {"name": "Gentleman", "constant": "GENTLEMAN"}, -0x21: {"name": "Skier", "constant": "SKIER"}, -0x22: {"name": "Teacher", "constant": "TEACHER"}, -0x23: {"name": "Sabrina", "uses_numeric_trainer_ids": True, "constant": "SABRINA"}, -0x24: {"name": "Bug Catcher", "constant": "BUG_CATCHER"}, -0x25: {"name": "Fisher", "constant": "FISHER"}, -0x26: {"name": "SwimmerM", "constant": "SWIMMERM"}, -0x27: {"name": "SwimmerF", "constant": "SWIMMERF"}, -0x28: {"name": "Sailor", "constant": "SAILOR"}, -0x29: {"name": "Super Nerd", "constant": "SUPER_NERD"}, -0x2A: {"name": "Rival2", "uses_numeric_trainer_ids": True, "constant": "RIVAL2"}, -0x2B: {"name": "Guitarist", "constant": "GUITARIST"}, -0x2C: {"name": "Hiker", "constant": "HIKER"}, -0x2D: {"name": "Biker", "constant": "BIKER"}, -0x2E: {"name": "Blaine", "uses_numeric_trainer_ids": True, "constant": "BLAINE"}, -0x2F: {"name": "Burglar", "constant": "BURGLAR"}, -0x30: {"name": "Firebreather", "constant": "FIREBREATHER"}, -0x31: {"name": "Juggler", "constant": "JUGGLER"}, -0x32: {"name": "Blackbelt_T", "constant": "BLACKBELT_T"}, -0x33: {"name": "ExecutiveM", "uses_numeric_trainer_ids": True, "constant": "EXECUTIVEM"}, -0x34: {"name": "Psychic_T", "constant": "PSYCHIC_T"}, -0x35: {"name": "Picnicker", "constant": "PICNICKER"}, -0x36: {"name": "Camper", "constant": "CAMPER"}, -0x37: {"name": "ExecutiveF", "uses_numeric_trainer_ids": True, "constant": "EXECUTIVEF"}, -0x38: {"name": "Sage", "constant": "SAGE"}, -0x39: {"name": "Medium", "constant": "MEDIUM"}, -0x3A: {"name": "Boarder", "constant": "BOARDER"}, -0x3B: {"name": "PokéfanM", "constant": "POKEFANM"}, -0x3C: {"name": "Kimono Girl", "constant": "KIMONO_GIRL"}, -0x3D: {"name": "Twins", "constant": "TWINS"}, -0x3E: {"name": "PokéfanF", "constant": "POKEFANF"}, -0x3F: {"name": "Red", "uses_numeric_trainer_ids": True, "constant": "RED"}, -0x40: {"name": "Blue", "uses_numeric_trainer_ids": True, "constant": "BLUE"}, -0x41: {"name": "Officer", "constant": "OFFICER"}, -0x42: {"name": "GruntF", "uses_numeric_trainer_ids": True, "constant": "GRUNTF"}, -0x43: {"name": "Mysticalman [Eusine]", "constant": "MYSTICALMAN"}, # crystal only -} - -def remove_parentheticals_from_trainer_group_names(): - """ - Clean up the trainer group names. - """ - i = 0 - for (key, value) in trainer_group_names.items(): - # remove the brackets and inner contents from each name - newvalue = re.sub(r'\[[^\)]*\]', '', value["name"]).strip() - - # clean up some characters - newvalue = newvalue.replace("♀", "F")\ - .replace("♂", "M")\ - .replace(".", "")\ - .replace(" ", "")\ - .replace("é", "e") - - # and calculate the address of the first byte of this pointer - trainer_group_names[key]["name"] = newvalue - trainer_group_names[key]["pointer_address"] = trainer_group_pointer_table_address + (i * 2) - i += 1 - return trainer_group_names - -# remove [Blue] from each trainer group name -remove_parentheticals_from_trainer_group_names() diff --git a/type_constants.py b/type_constants.py deleted file mode 100644 index da89b0b..0000000 --- a/type_constants.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -type_constants = { - "NORMAL": 0x00, - "FIGHTING": 0x01, - "FLYING": 0x02, - "POISON": 0x03, - "GROUND": 0x04, - "ROCK": 0x05, - "BUG": 0x07, - "GHOST": 0x08, - "STEEL": 0x09, - "CURSE_T": 0x13, - "FIRE": 0x14, - "WATER": 0x15, - "GRASS": 0x16, - "ELECTRIC": 0x17, - "PSYCHIC": 0x18, - "ICE": 0x19, - "DRAGON": 0x1A, - "DARK": 0x1B, -} diff --git a/wram.py b/wram.py deleted file mode 100644 index 5352fb4..0000000 --- a/wram.py +++ /dev/null @@ -1,125 +0,0 @@ -# coding: utf-8 -""" -RGBDS BSS section and constant parsing. -""" - -import os -path = os.path.dirname(os.path.abspath(__file__)) - -def read_bss_sections(bss): - sections = [] - section = { - "labels": [], - } - address = None - if type(bss) is not list: bss = bss.split('\n') - for line in bss: - line = line.lstrip() - if 'SECTION' in line: - if section: sections.append(section) # last section - - address = eval(line[line.find('[')+1:line.find(']')].replace('$','0x')) - section = { - 'name': line.split('"')[1], - #'type': line.split(',')[1].split('[')[0].strip(), - 'start': address, - 'labels': [], - } - - elif ':' in line: - # rgbds allows labels without :, but prefer convention - label = line[:line.find(':')] - if ';' not in label: - section['labels'] += [{ - 'label': label, - 'address': address, - 'length': 0, - }] - - elif line[:3] == 'ds ': - length = eval(line[3:line.find(';')].replace('$','0x')) - address += length - # adjacent labels use the same space - for label in section['labels'][::-1]: - if label['length'] == 0: - label['length'] = length - else: - break - - elif 'EQU' in line: - # some space is defined using constants - name, value = line.split('EQU') - name, value = name.strip(), value.strip().replace('$','0x').replace('%','0b') - globals()[name] = eval(value) - - sections.append(section) - return sections - -def read_wram_sections(): - """ - Opens the wram file and calls read_bss_sections. - """ - wram_content = None - wram_file_path = os.path.join(os.path.dirname(path), 'wram.asm') - try: - wram_file_handler = open(wram_file_path, 'r') - except IOError as exception: - wram_content = [""] - else: - wram_content = wram_file_handler.readlines() - wram_sections = read_bss_sections(wram_content) - return wram_sections - -wram_sections = read_wram_sections() - -def make_wram_labels(): - wram_labels = {} - for section in wram_sections: - for label in section['labels']: - if label['address'] not in wram_labels.keys(): - wram_labels[label['address']] = [] - wram_labels[label['address']] += [label['label']] - return wram_labels - -wram_labels = make_wram_labels() - -def constants_to_dict(constants): - return dict((eval(constant[constant.find('EQU')+3:constant.find(';')].replace('$','0x')), constant[:constant.find('EQU')].strip()) for constant in constants) - -def scrape_constants(text): - if type(text) is not list: - text = text.split('\n') - return constants_to_dict([line for line in text if 'EQU' in line[:line.find(';')]]) - -def read_constants(filepath): - """ - Load lines from a file and call scrape_constants. - """ - try: - file_handler = open(filepath, "r") - except IOError as exception: - lines = [""] - else: - lines = file_handler.readlines() - constants = scrape_constants(lines) - return constants - -def read_hram_constants(): - """ - Load constants from hram.asm. - """ - hram_path = os.path.join(os.path.dirname(path), 'hram.asm') - return read_constants(hram_path) - -# TODO: get rid of this global -hram_constants = read_hram_constants() - -def read_gbhw_constants(): - """ - Load constants from gbhw.asm. - """ - gbhw_path = os.path.join(os.path.dirname(path), 'gbhw.asm') - return read_constants(gbhw_path) - -# TODO: get rid of this global -gbhw_constants = read_gbhw_constants() -- cgit v1.2.3