From a4849db2658a14deebb6620fac50e9d8e9f8a0ad Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:34:43 -0500 Subject: rename config.py -> configuration.py This should help cut down on the confusion between the "config" module and the "config" variable that everyone likes to use. The config variable should refer to an instance of Config, whereas before it was being shared as both the name of the module and the name of an instance. "configuration" is always the module. "config" should always be a Config instance. TODO: avoid passing around a Config instance everywhere. --- pokemontools/__init__.py | 2 +- pokemontools/config.py | 54 ------------------------------------------- pokemontools/configuration.py | 54 +++++++++++++++++++++++++++++++++++++++++++ pokemontools/gbz80disasm.py | 4 ++-- 4 files changed, 57 insertions(+), 57 deletions(-) delete mode 100644 pokemontools/config.py create mode 100644 pokemontools/configuration.py diff --git a/pokemontools/__init__.py b/pokemontools/__init__.py index 09331af..293e2f2 100644 --- a/pokemontools/__init__.py +++ b/pokemontools/__init__.py @@ -1,3 +1,3 @@ -import config +import configuration as config import crystal import preprocessor diff --git a/pokemontools/config.py b/pokemontools/config.py deleted file mode 100644 index cbf230c..0000000 --- a/pokemontools/config.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Configuration -""" - -import os - -import exceptions - -class Config(object): - """ - The Config class handles all configuration for pokemontools. Other classes - and functions use a Config object to determine where expected files can be - located. - """ - - def __init__(self, **kwargs): - """ - Store all parameters. - """ - self._config = {} - - for (key, value) in kwargs.items(): - if key not in self.__dict__: - self._config[key] = value - else: - raise exceptions.ConfigException( - "Can't store \"{0}\" in configuration because the key conflicts with an existing property." - .format(key) - ) - - if "path" not in self._config: - self._config["path"] = os.getcwd() - - # vba save states go into ./save-states/ - if "save_state_path" not in self._config: - self._config["save_state_path"] = os.path.join(self._config["path"], "save-states/") - - # assume rom is at ./baserom.gbc - if "rom" not in self._config: - self._config["rom_path"] = os.path.join(self._config["path"], "baserom.gbc") - - def __getattr__(self, key): - """ - Grab the value from the class properties, then check the configuration, - and raise an exception if nothing works. - """ - if key in self.__dict__: - return self.__dict__[key] - elif key in self._config: - return self._config[key] - else: - raise exceptions.ConfigException( - "no config found for \"{0}\"".format(key) - ) diff --git a/pokemontools/configuration.py b/pokemontools/configuration.py new file mode 100644 index 0000000..cbf230c --- /dev/null +++ b/pokemontools/configuration.py @@ -0,0 +1,54 @@ +""" +Configuration +""" + +import os + +import exceptions + +class Config(object): + """ + The Config class handles all configuration for pokemontools. Other classes + and functions use a Config object to determine where expected files can be + located. + """ + + def __init__(self, **kwargs): + """ + Store all parameters. + """ + self._config = {} + + for (key, value) in kwargs.items(): + if key not in self.__dict__: + self._config[key] = value + else: + raise exceptions.ConfigException( + "Can't store \"{0}\" in configuration because the key conflicts with an existing property." + .format(key) + ) + + if "path" not in self._config: + self._config["path"] = os.getcwd() + + # vba save states go into ./save-states/ + if "save_state_path" not in self._config: + self._config["save_state_path"] = os.path.join(self._config["path"], "save-states/") + + # assume rom is at ./baserom.gbc + if "rom" not in self._config: + self._config["rom_path"] = os.path.join(self._config["path"], "baserom.gbc") + + def __getattr__(self, key): + """ + Grab the value from the class properties, then check the configuration, + and raise an exception if nothing works. + """ + if key in self.__dict__: + return self.__dict__[key] + elif key in self._config: + return self._config[key] + else: + raise exceptions.ConfigException( + "no config found for \"{0}\"".format(key) + ) diff --git a/pokemontools/gbz80disasm.py b/pokemontools/gbz80disasm.py index 1fb9d85..a77e433 100644 --- a/pokemontools/gbz80disasm.py +++ b/pokemontools/gbz80disasm.py @@ -10,7 +10,7 @@ from ctypes import c_int8 import random import json -import config +import configuration import crystal import labels import wram @@ -929,7 +929,7 @@ class Disassembler(object): return (output, offset, last_hl_address, last_a_address, used_3d97) if __name__ == "__main__": - conf = config.Config() + conf = configuration.Config() disasm = Disassembler(conf) disasm.initialize() -- cgit v1.2.3 From 7cb38df60b1f3ccb76823c799549e3fbea4ab59f Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:43:24 -0500 Subject: move AsmLine into crystalparts/asmline.py --- pokemontools/crystal.py | 10 ++-------- pokemontools/crystalparts/__init__.py | 0 pokemontools/crystalparts/asmline.py | 8 ++++++++ 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 pokemontools/crystalparts/__init__.py create mode 100644 pokemontools/crystalparts/asmline.py diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 8a12dad..a3d87f0 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -7022,14 +7022,8 @@ def apply_diff(diff, try_fixing=True, do_compile=True): 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 +import crystalparts.asmline +AsmLine = crystalparts.asmline.AsmLine class Incbin: def __init__(self, line, bank=None, debug=False): diff --git a/pokemontools/crystalparts/__init__.py b/pokemontools/crystalparts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pokemontools/crystalparts/asmline.py b/pokemontools/crystalparts/asmline.py new file mode 100644 index 0000000..e62d774 --- /dev/null +++ b/pokemontools/crystalparts/asmline.py @@ -0,0 +1,8 @@ +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 -- cgit v1.2.3 From 2059ea6f9524d68b654ca8cb876db84620f3cd03 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:44:14 -0500 Subject: move "import crystal" in labels.py until needed Otherwise there's a circular import. This circular import is going away once crystal.py is fixed up. --- pokemontools/labels.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/labels.py b/pokemontools/labels.py index 1509ae6..8fefd2c 100644 --- a/pokemontools/labels.py +++ b/pokemontools/labels.py @@ -8,7 +8,6 @@ import json import logging import pointers -import crystal class Labels(object): """ @@ -32,6 +31,7 @@ class Labels(object): "Running crystal.scan_for_predefined_labels to create \"{0}\". Trying.." .format(Labels.filename) ) + import crystal crystal.scan_for_predefined_labels() self.labels = json.read(open(self.path, "r").read()) -- cgit v1.2.3 From c80af2798ee369a775d26a6f0d57853c7a91d918 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:46:21 -0500 Subject: allow (some) json files in the repo This ignores only "labels.json", which so far has been the only problematic json file. This is a file generated by gbz80disasm. Other json files might be necessary in the future, and it's okay to let them in. --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a8b43a7..34a7196 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,8 @@ # swap files for gedit *~ -# no data from extras/ -*.json +# labels.json is auto-generated by gbz80disasm +labels.json # for vim configuration # url: http://www.vim.org/scripts/script.php?script_id=441 -- cgit v1.2.3 From d1343b58b47e1db419bb2bf0d2f8aa863692756d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:50:17 -0500 Subject: remove global from load_asm2 Why is there even a second method in the first place? --- pokemontools/crystal.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index a3d87f0..9f156c5 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -7144,12 +7144,9 @@ class AsmSection: def to_asm(self): return self.line -new_asm = None -def load_asm2(filename="../main.asm", force=False): +def load_asm2(filename="../main.asm"): """loads the asm source code into memory""" - global new_asm - if new_asm == None or force: - new_asm = Asm(filename=filename) + new_asm = Asm(filename=filename) return new_asm class Asm: @@ -7643,7 +7640,7 @@ class Label: entries to the Asm.parts list. """ # assert new_asm != None, "new_asm should be an instance of Asm" - load_asm2() + new_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 @@ -7652,7 +7649,7 @@ class Label: """ Checks if the address is in use by another label. """ - load_asm2() + new_asm = load_asm2() self.address_is_in_file = new_asm.does_address_have_label(self.address) return self.address_is_in_file -- cgit v1.2.3 From 01779ed10d33994a2c14c3639584317a9b4cd129 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:51:48 -0500 Subject: move load_asm2 next to load_asm --- pokemontools/crystal.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 9f156c5..918232a 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -137,6 +137,11 @@ def load_asm(filename="../main.asm"): asm = direct_load_asm(filename=filename) return asm +def load_asm2(filename="../main.asm"): + """loads the asm source code into memory""" + new_asm = Asm(filename=filename) + return new_asm + def is_valid_address(address): """is_valid_rom_address""" if address == None: @@ -7144,11 +7149,6 @@ class AsmSection: def to_asm(self): return self.line -def load_asm2(filename="../main.asm"): - """loads the asm source code into memory""" - new_asm = Asm(filename=filename) - return new_asm - class Asm: """controls the overall asm output""" def __init__(self, filename="../main.asm", debug=True): -- cgit v1.2.3 From 5a4955e9c7dc1887ae440c3d564bd94d79a4ccdd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 00:56:45 -0500 Subject: move is_valid_address into addresses.py --- pokemontools/addresses.py | 14 ++++++++++++++ pokemontools/crystal.py | 14 +++----------- 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 pokemontools/addresses.py diff --git a/pokemontools/addresses.py b/pokemontools/addresses.py new file mode 100644 index 0000000..7b9aba5 --- /dev/null +++ b/pokemontools/addresses.py @@ -0,0 +1,14 @@ +""" +Common methods used against addresses. +""" + +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 diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 918232a..8618185 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -61,6 +61,9 @@ import item_constants import wram import exceptions +import addresses +is_valid_address = addresses.is_valid_address + from map_names import map_names # ---- script_parse_table explanation ---- @@ -142,17 +145,6 @@ def load_asm2(filename="../main.asm"): new_asm = Asm(filename=filename) return new_asm -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 -- cgit v1.2.3 From 1389ed7b020a72fa18eff9dc8e59ef538a2e3429 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:00:05 -0500 Subject: remove a global from how_many_until --- pokemontools/crystal.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 8618185..027ce7e 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -155,7 +155,7 @@ def rom_until(offset, byte, strings=True, debug=True): global rom return rom.until(offset, byte, strings=strings, debug=debug) -def how_many_until(byte, starting): +def how_many_until(byte, starting, rom): index = rom.find(byte, starting) return index - starting @@ -548,9 +548,9 @@ class OldTextScript: 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) + jump57 = how_many_until(chr(0x57), offset, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) # whichever command comes first jump = min([jump57, jump50, jump58]) @@ -676,9 +676,9 @@ class OldTextScript: 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) + jump57 = how_many_until(chr(0x57), offset, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) # whichever command comes first jump = min([jump57, jump50, jump58]) @@ -970,9 +970,9 @@ class EncodedText: 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) + jump57 = how_many_until(chr(0x57), offset, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) # whichever command comes first jump = min([jump57, jump50, jump58]) @@ -2263,9 +2263,9 @@ class MainText(TextCommand): 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) + jump57 = how_many_until(chr(0x57), offset, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) # pick whichever one comes first jump = min([jump57, jump50, jump58]) @@ -4062,7 +4062,7 @@ class TrainerHeader: address = self.address # figure out how many bytes until 0x50 "@" - jump = how_many_until(chr(0x50), address) + jump = how_many_until(chr(0x50), address, rom) # parse the "@" into the name self.name = parse_text_at(address, jump+1) @@ -4303,7 +4303,7 @@ def trainer_name_from_group(group_id, trainer_id=0): bank = pointers.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)) + text = parse_text_at2(address, how_many_until(chr(0x50), address, rom)) return text def trainer_group_report(): @@ -6702,7 +6702,7 @@ class PokedexEntry: def parse(self): # eww. address = self.address - jump = how_many_until(chr(0x50), address) + jump = how_many_until(chr(0x50), address, rom) self.species = parse_text_at(address, jump+1) address = address + jump + 1 @@ -6710,10 +6710,10 @@ class PokedexEntry: self.height = ord(rom[address+2]) + (ord(rom[address+3]) << 8) address += 4 - jump = how_many_until(chr(0x50), address) + jump = how_many_until(chr(0x50), address, rom) self.page1 = PokedexText(address) address = address + jump + 1 - jump = how_many_until(chr(0x50), address) + jump = how_many_until(chr(0x50), address, rom) self.page2 = PokedexText(address) self.last_address = address + jump + 1 -- cgit v1.2.3 From 06dc9462fe822079bdeec5617a1d3eaccb2b26d5 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:05:10 -0500 Subject: split OldTextScript out of crystal.py This creates a new file called old_text_script.py with that huge class. Does this mean that I wrote this a second time when I wrote TextScript ? --- pokemontools/crystal.py | 526 +--------------------------------------- pokemontools/old_text_script.py | 526 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 529 insertions(+), 523 deletions(-) create mode 100644 pokemontools/old_text_script.py diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 027ce7e..f4d2b00 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -64,6 +64,9 @@ import exceptions import addresses is_valid_address = addresses.is_valid_address +import old_text_script +OldTextScript = old_text_script + from map_names import map_names # ---- script_parse_table explanation ---- @@ -407,529 +410,6 @@ class TextScript: 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, pointers.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, pointers.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 = pointers.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: - logging.debug("text is already parsed at this location: {0}".format(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: - logging.debug( - "TextScript.parse_script_at has encountered a command byte {0} at {1}" - .format(hex(command_byte), hex(address)) - ) - end_address = address + 1 - if command_byte == 0: - # read until $57, $50 or $58 - jump57 = how_many_until(chr(0x57), offset, rom) - jump50 = how_many_until(chr(0x50), offset, rom) - jump58 = how_many_until(chr(0x58), offset, rom) - - # 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) - logging.debug("output of parse_text_at2 is {0}".format(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: - logging.debug("next command is {0}".format(hex(ord(rom[offset])))) - logging.debug( - ".. current command number is {counter} near {offset} on map_id={map_id}" - .format( - counter=command_counter, - offset=hex(offset), - map_id=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, rom) - jump50 = how_many_until(chr(0x50), offset, rom) - jump58 = how_many_until(chr(0x58), offset, rom) - - # 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) - logging.debug("parse_text_at2 text is {0}".format(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: - logging.debug( - "Unknown text command at {offset} - command: {command} on map_id={map_id}" - .format( - offset=hex(offset), - command=hex(ord(rom[offset])), - map_id=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()): - 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(): - logging.debug("ERROR in command: {0}".format(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: - logging.debug("ERROR in command: {0}".format(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.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.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 diff --git a/pokemontools/old_text_script.py b/pokemontools/old_text_script.py new file mode 100644 index 0000000..912c027 --- /dev/null +++ b/pokemontools/old_text_script.py @@ -0,0 +1,526 @@ +""" +An old implementation of TextScript that may not be useful anymore. +""" + +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, pointers.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, pointers.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 = pointers.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: + logging.debug("text is already parsed at this location: {0}".format(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: + logging.debug( + "TextScript.parse_script_at has encountered a command byte {0} at {1}" + .format(hex(command_byte), hex(address)) + ) + end_address = address + 1 + if command_byte == 0: + # read until $57, $50 or $58 + jump57 = how_many_until(chr(0x57), offset, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) + + # 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) + logging.debug("output of parse_text_at2 is {0}".format(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: + logging.debug("next command is {0}".format(hex(ord(rom[offset])))) + logging.debug( + ".. current command number is {counter} near {offset} on map_id={map_id}" + .format( + counter=command_counter, + offset=hex(offset), + map_id=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, rom) + jump50 = how_many_until(chr(0x50), offset, rom) + jump58 = how_many_until(chr(0x58), offset, rom) + + # 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) + logging.debug("parse_text_at2 text is {0}".format(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: + logging.debug( + "Unknown text command at {offset} - command: {command} on map_id={map_id}" + .format( + offset=hex(offset), + command=hex(ord(rom[offset])), + map_id=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()): + 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(): + logging.debug("ERROR in command: {0}".format(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: + logging.debug("ERROR in command: {0}".format(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.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.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 -- cgit v1.2.3 From 510bb1c3d95ec7bf061255dce78b97d89e1fcdb8 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:07:18 -0500 Subject: try to fix old_text_script.py with an import There are a few more missing imports to get this to work. On the other hand, it may not be valuable enough to bother fixing. --- pokemontools/old_text_script.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pokemontools/old_text_script.py b/pokemontools/old_text_script.py index 912c027..23e4a67 100644 --- a/pokemontools/old_text_script.py +++ b/pokemontools/old_text_script.py @@ -2,6 +2,8 @@ An old implementation of TextScript that may not be useful anymore. """ +import pokemontools.pointers as pointers + class OldTextScript: "a text is a sequence of commands different from a script-engine script" base_label = "UnknownText_" -- cgit v1.2.3 From fff6a7154cf70512d82bf106687efff7111c6934 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:09:34 -0500 Subject: don't override the object type/variable --- pokemontools/crystal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index f4d2b00..b4d5185 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -7153,8 +7153,8 @@ class Label: """ Generates a label name based on parents and self.object. """ - object = self.object - name = object.make_label() + obj = self.object + name = obj.make_label() return name def find_labels_without_addresses(): -- cgit v1.2.3 From 05c396ddd985bf690590aa920a4f771d5d02d7e1 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:15:01 -0500 Subject: remove an unused import from integration tests --- tests/integration/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/tests.py b/tests/integration/tests.py index 1dffd4e..40933e5 100644 --- a/tests/integration/tests.py +++ b/tests/integration/tests.py @@ -84,7 +84,6 @@ from pokemontools.crystal import ( process_incbins, get_labels_between, generate_diff_insert, - find_labels_without_addresses, rom_text_at, get_label_for, split_incbin_line_into_three, -- cgit v1.2.3 From 463cd6197f0a7be56312eaf6931563f0572de837 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:15:45 -0500 Subject: move find_labels_without_addresses into labels.py --- pokemontools/crystal.py | 10 ---------- pokemontools/labels.py | 10 ++++++++++ tests/tests.py | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index b4d5185..d5a861f 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -7157,16 +7157,6 @@ class Label: name = obj.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 labels.line_has_label(line): - label = labels.get_label_from_line(line) - if not labels.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): foundlabels = [] diff --git a/pokemontools/labels.py b/pokemontools/labels.py index 8fefd2c..96e34b9 100644 --- a/pokemontools/labels.py +++ b/pokemontools/labels.py @@ -197,3 +197,13 @@ def get_label_from_line(line): #split up the line label = line.split(":")[0] return label + +def find_labels_without_addresses(asm): + """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 diff --git a/tests/tests.py b/tests/tests.py index c6c1265..7919a66 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -35,6 +35,7 @@ from pokemontools.labels import ( line_has_comment_address, line_has_label, get_label_from_line, + find_labels_without_addresses, ) from pokemontools.helpers import ( @@ -84,7 +85,6 @@ from pokemontools.crystal import ( process_incbins, get_labels_between, generate_diff_insert, - find_labels_without_addresses, rom_text_at, get_label_for, split_incbin_line_into_three, @@ -389,10 +389,10 @@ class TestAsmList(unittest.TestCase): def test_find_labels_without_addresses(self): global asm asm = ["hello_world: ; 0x1", "hello_world2: ;"] - labels = find_labels_without_addresses() + labels = find_labels_without_addresses(asm) self.failUnless(labels[0]["label"] == "hello_world2") asm = ["hello world: ;1", "hello_world: ;2"] - labels = find_labels_without_addresses() + labels = find_labels_without_addresses(asm) self.failUnless(len(labels) == 0) asm = None -- cgit v1.2.3 From c6f0f7f5c9c6d7a5ec1310043c37f4d0a40b3395 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 01:37:30 -0500 Subject: move old parsers from 'crystal' into crystalparts/ --- pokemontools/crystal.py | 412 ------------------------------ pokemontools/crystalparts/old_parsers.py | 425 +++++++++++++++++++++++++++++++ 2 files changed, 425 insertions(+), 412 deletions(-) create mode 100644 pokemontools/crystalparts/old_parsers.py diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index d5a861f..0222f8e 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -3022,25 +3022,6 @@ def parse_warps(address, warp_count, bank=None, map_group=None, map_id=None, deb 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 helpers.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" @@ -4432,73 +4413,6 @@ def parse_signposts(address, signpost_count, bank=None, map_group=None, map_id=N 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 helpers.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]: - logging.debug( - "parsing signpost script.. signpost is at x={x} y={y}" - .format(x=x, y=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 = pointers.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]: - logging.debug( - "parsing signpost script.. signpost is at x={x} y={y}" - .format(x=x, y=y) - ) - - ptr_byte1 = int(bytes[3], 16) - ptr_byte2 = int(bytes[4], 16) - pointer = ptr_byte1 + (ptr_byte2 << 8) - address = pointers.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: - logging.debug(".. 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_" @@ -4565,40 +4479,6 @@ def parse_map_header_at(address, map_group=None, map_id=None, debug=True): 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""" - logging.debug("parsing a map header at {0}".format(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 = pointers.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, - } - logging.debug("second map header address is {0}".format(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 @@ -5427,39 +5307,6 @@ def parse_second_map_header_at(address, map_group=None, map_id=None, debug=True) 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 = pointers.calculate_pointer(blockdata_pointer, blockdata_bank) - script_bank = bytes[6] - script_pointer = bytes[7] + (bytes[8] << 8) - script_address = pointers.calculate_pointer(script_pointer, script_bank) - event_bank = script_bank - event_pointer = bytes[9] + (bytes[10] << 8) - event_address = pointers.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")) @@ -5645,48 +5492,6 @@ def parse_map_event_header_at(address, map_group=None, map_id=None, debug=True, 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 = pointers.calculate_bank(address) - - logging.debug("event header address is {0}".format(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 @@ -5851,223 +5656,6 @@ def parse_map_script_header_at(address, map_group=None, map_id=None, debug=True) all_map_script_headers.append(evv) return evv -def old_parse_map_script_header_at(address, map_group=None, map_id=None, debug=True): - logging.debug("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 = helpers.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): - logging.debug("parsing a trigger header...") - byte1 = trigger_pointer[0] - byte2 = trigger_pointer[1] - ptr = byte1 + (byte2 << 8) - trigger_address = pointers.calculate_pointer(ptr, pointers.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 = helpers.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): - logging.debug("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 = pointers.calculate_pointer(callback_ptr, pointers.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 = pointers.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: - logging.debug("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) - - logging.debug("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 = pointers.calculate_bank(address) - - people_events = [] - for bytes in helpers.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 = pointers.calculate_pointer(script_pointer, bank) - if is_regular_script: - logging.debug( - "parsing a person-script at x={x} y={y} address={address}" - .format(x=(x-4), y=(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: - logging.debug("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: - logging.debug("parsing a trainer (person-event) at x={x} y={y}".format(x=x, y=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 diff --git a/pokemontools/crystalparts/old_parsers.py b/pokemontools/crystalparts/old_parsers.py new file mode 100644 index 0000000..b23ab5d --- /dev/null +++ b/pokemontools/crystalparts/old_parsers.py @@ -0,0 +1,425 @@ +""" +Some old methods rescued from crystal.py +""" + +import pointers + +map_header_byte_size = ... +rom_interval = ... + +all_map_headers = [] + +def old_parse_map_script_header_at(address, map_group=None, map_id=None, debug=True): + logging.debug("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 = helpers.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): + logging.debug("parsing a trigger header...") + byte1 = trigger_pointer[0] + byte2 = trigger_pointer[1] + ptr = byte1 + (byte2 << 8) + trigger_address = pointers.calculate_pointer(ptr, pointers.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 = helpers.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): + logging.debug("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 = pointers.calculate_pointer(callback_ptr, pointers.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_map_header_at(address, map_group=None, map_id=None, debug=True): + """parses an arbitrary map header at some address""" + logging.debug("parsing a map header at {0}".format(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 = pointers.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, + } + logging.debug("second map header address is {0}".format(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 + +all_second_map_headers = [] + +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 = pointers.calculate_pointer(blockdata_pointer, blockdata_bank) + script_bank = bytes[6] + script_pointer = bytes[7] + (bytes[8] << 8) + script_address = pointers.calculate_pointer(script_pointer, script_bank) + event_bank = script_bank + event_pointer = bytes[9] + (bytes[10] << 8) + event_address = pointers.calculate_pointer(event_pointer, event_bank) + connections = bytes[11] + return { + "border_block": border_block, + "height": height, + "width": width, + "blockdata_bank": blockdata_bank, + "blockdata_pointer": {"1": bytes[4], "2": bytes[5]}, + "blockdata_address": blockdata_address, + "script_bank": script_bank, + "script_pointer": {"1": bytes[7], "2": bytes[8]}, + "script_address": script_address, + "event_bank": event_bank, + "event_pointer": {"1": bytes[9], "2": bytes[10]}, + "event_address": event_address, + "connections": connections, + } + +def 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 helpers.grouper(some_bytes, count=warp_byte_size): + y = int(bytes[0], 16) + x = int(bytes[1], 16) + warp_to = int(bytes[2], 16) + map_group = int(bytes[3], 16) + map_id = int(bytes[4], 16) + warps.append({ + "y": y, + "x": x, + "warp_to": warp_to, + "map_group": map_group, + "map_id": map_id, + }) + return warps + +def 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 helpers.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]: + logging.debug( + "parsing signpost script.. signpost is at x={x} y={y}" + .format(x=x, y=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 = pointers.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]: + logging.debug( + "parsing signpost script.. signpost is at x={x} y={y}" + .format(x=x, y=y) + ) + + ptr_byte1 = int(bytes[3], 16) + ptr_byte2 = int(bytes[4], 16) + pointer = ptr_byte1 + (ptr_byte2 << 8) + address = pointers.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: + logging.debug(".. type 7 or 8 signpost not parsed yet.") + + spost = { + "y": y, + "x": x, + "func": func, + } + spost.update(additional) + signposts.append(spost) + return signposts + +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 = pointers.calculate_bank(address) + + people_events = [] + for bytes in helpers.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 = pointers.calculate_pointer(script_pointer, bank) + if is_regular_script: + logging.debug( + "parsing a person-script at x={x} y={y} address={address}" + .format( + x=(x-4), + y=(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: + logging.debug("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: + logging.debug( + "parsing a trainer (person-event) at x={x} y={y}" + .format(x=x, y=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 + +def old_parse_trainer_header_at(address, map_group=None, map_id=None, debug=True): + bank = pointers.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: + logging.debug("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) + + logging.debug("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_map_event_header_at(address, map_group=None, map_id=None, debug=True): + """parse crystal map event header byte structure thing""" + returnable = {} + + bank = pointers.calculate_bank(address) + + logging.debug("event header address is {0}".format(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 -- cgit v1.2.3 From 0c1e9095d805ce60af68c6d4497ea71e9bc08b5d Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:27:33 -0500 Subject: remove globals from some map group parsers --- pokemontools/crystal.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 0222f8e..4fe9f38 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -148,26 +148,22 @@ def load_asm2(filename="../main.asm"): new_asm = Asm(filename=filename) return new_asm -def rom_interval(offset, length, strings=True, debug=True): +def rom_interval(offset, length, rom=None, 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): +def rom_until(offset, byte, rom=None, 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, rom): index = rom.find(byte, starting) return index - starting -def load_map_group_offsets(): +def load_map_group_offsets(map_group_pointer_table, map_group_count, rom=None): """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 = rom_interval(map_group_pointer_table, map_group_count*2, strings=False, rom=rom) data = helpers.grouper(data) for pointer_parts in data: pointer = pointer_parts[0] + (pointer_parts[1] << 8) @@ -5677,16 +5673,18 @@ def parse_map_header_by_id(*args, **kwargs): 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 +def parse_all_map_headers(map_names, debug=True): + """ + Calls parse_map_header_at for each map in each map group. Updates the + map_names structure. + """ 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(): + 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(): + for (map_id, map_data) in group_data.items(): if map_id == "offset": continue # skip the "offset" address for this map group if debug: logging.debug( @@ -6529,14 +6527,14 @@ def list_movements_in_bank(bank): movements.append(movement) return movements -def dump_asm_for_texts_in_bank(bank, start=50, end=100): +def dump_asm_for_texts_in_bank(bank, start=50, end=100, rom=None): """ 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() + rom = load_rom() run_main() # get all texts @@ -6846,16 +6844,19 @@ def scan_for_predefined_labels(debug=False): 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() +def run_main(rom=None): + if not rom: + # read the rom and figure out the offsets for maps + rom = direct_load_rom() + + # figure out the map offsets + map_group_offsets = load_map_group_offsets(map_group_pointer_table=map_group_pointer_table, map_group_count=map_group_count, rom=rom) # 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() + parse_all_map_headers(map_names) # find trainers based on scripts and map headers # this can only happen after parsing the entire map and map scripts -- cgit v1.2.3 From b3bbd5f9cba203e3729c68c1cce344823f523dcd Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:29:56 -0500 Subject: don't use the old header parsing method --- pokemontools/crystal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 4fe9f38..3cc5ea1 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -5696,8 +5696,6 @@ def parse_all_map_headers(map_names, debug=True): 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: """ -- cgit v1.2.3 From 325a1d63deecc3cd06cc5b60d575011a1bb91735 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:36:47 -0500 Subject: convert all_map_headers from a global --- pokemontools/crystal.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 3cc5ea1..e2e04ca 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -4466,9 +4466,7 @@ class MapHeader: 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): +def parse_map_header_at(address, map_group=None, map_id=None, all_map_headers=None, debug=True): """parses an arbitrary map header at some address""" logging.debug("parsing a map header at {0}".format(hex(address))) map_header = MapHeader(address, map_group=map_group, map_id=map_id, debug=debug) @@ -5655,6 +5653,7 @@ def parse_map_script_header_at(address, map_group=None, map_id=None, debug=True) def parse_map_header_by_id(*args, **kwargs): """convenience function to parse a specific map""" map_group, map_id = None, None + all_map_headers = kwargs["all_map_headers"] if "map_group" in kwargs.keys(): map_group = kwargs["map_group"] if "map_id" in kwargs.keys(): @@ -5671,9 +5670,9 @@ def parse_map_header_by_id(*args, **kwargs): 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) + return parse_map_header_at(map_header_offset, all_map_headers=all_map_headers, map_group=map_group, map_id=map_id) -def parse_all_map_headers(map_names, debug=True): +def parse_all_map_headers(map_names, all_map_headers=None, debug=True): """ Calls parse_map_header_at for each map in each map group. Updates the map_names structure. @@ -5694,7 +5693,7 @@ def parse_all_map_headers(map_names, debug=True): 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) + new_parsed_map = parse_map_header_at(map_header_offset, map_group=group_id, map_id=map_id, all_map_headers=all_map_headers, debug=debug) map_names[group_id][map_id]["header_new"] = new_parsed_map class PokedexEntryPointerTable: @@ -6842,6 +6841,8 @@ def scan_for_predefined_labels(debug=False): write_all_labels(all_labels) return all_labels +all_map_headers = [] + def run_main(rom=None): if not rom: # read the rom and figure out the offsets for maps @@ -6854,7 +6855,7 @@ def run_main(rom=None): [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(map_names) + parse_all_map_headers(map_names, all_map_headers=all_map_headers) # find trainers based on scripts and map headers # this can only happen after parsing the entire map and map scripts -- cgit v1.2.3 From 97da7b7922190f64d5d330d3b6d5cb2470ff9559 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:50:04 -0500 Subject: knock out some trainer group globals --- pokemontools/crystal.py | 88 ++++++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index e2e04ca..c38c510 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -3149,7 +3149,6 @@ class ItemFragmentParam(PointerLabelParam): global_dependencies.add(self.itemfrag) return self.dependencies -trainer_group_maximums = {} class TrainerFragment(Command): """used by TrainerFragmentParam and PeopleEvent for trainer data @@ -3223,10 +3222,10 @@ class TrainerFragment(Command): # 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]) + if not trainer_group in self.trainer_group_maximums.keys(): + self.trainer_group_maximums[trainer_group] = set([trainer_id]) else: - trainer_group_maximums[trainer_group].add(trainer_id) + self.trainer_group_maximums[trainer_group].add(trainer_id) # give this object a possibly better label label = "Trainer" @@ -3315,10 +3314,13 @@ class TrainerGroupTable: This should probably be called TrainerGroupPointerTable. """ - def __init__(self): - assert 0x43 in trainer_group_maximums.keys(), "TrainerGroupTable should only be created after all the trainers have been found" - self.address = trainers.trainer_group_pointer_table_address - self.bank = pointers.calculate_bank(trainers.trainer_group_pointer_table_address) + def __init__(self, trainer_group_maximums=None, trainers=None, script_parse_table=None): + self.trainer_group_maximums = trainer_group_maximums + self.trainers = trainers + self.script_parse_table = script_parse_table + assert 0x43 in self.trainer_group_maximums.keys(), "TrainerGroupTable should only be created after all the trainers have been found" + self.address = self.trainers.trainer_group_pointer_table_address + self.bank = pointers.calculate_bank(self.trainers.trainer_group_pointer_table_address) self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self) self.size = None self.last_address = None @@ -3326,7 +3328,7 @@ class TrainerGroupTable: self.headers = [] self.parse() - script_parse_table[self.address : self.last_address] = self + self.script_parse_table[self.address : self.last_address] = self def get_dependencies(self, recompute=False, global_dependencies=set()): global_dependencies.update(self.headers) @@ -3339,16 +3341,16 @@ class TrainerGroupTable: def parse(self): size = 0 - for (key, kvalue) in trainers.trainer_group_names.items(): + for (key, kvalue) in self.trainers.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) - trainers.trainer_group_names[key]["parsed_address"] = parsed_address + self.trainers.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) - trainers.trainer_group_names[key]["header"] = trainer_group_header + trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name, trainer_group_maximums=self.trainer_group_maximums) + self.trainers.trainer_group_names[key]["header"] = trainer_group_header self.headers.append(trainer_group_header) # keep track of the size of this pointer table @@ -3372,11 +3374,13 @@ class TrainerGroupHeader: Data type <0x03>: Pokémon Data is . Used by a few Cooltrainers. """ - def __init__(self, address=None, group_id=None, group_name=None): + def __init__(self, address=None, group_id=None, group_name=None, trainer_group_maximums=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.trainer_group_maximums = trainer_group_maximums + self.address = address self.group_id = group_id self.group_name = group_name @@ -3406,14 +3410,14 @@ class TrainerGroupHeader: size = 0 current_address = self.address - if self.group_id not in trainer_group_maximums.keys(): + if self.group_id not in self.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]) + min_id = min(self.trainer_group_maximums[self.group_id]) + max_id = max(self.trainer_group_maximums[self.group_id]) if self.group_id == 0x0C: # CAL appears a third time with third-stage evos (meganium, typhlosion, feraligatr) @@ -3670,7 +3674,7 @@ class TrainerPartyMonParser3(TrainerPartyMonParser): trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3] -def find_trainer_ids_from_scripts(): +def find_trainer_ids_from_scripts(script_parse_table=None, trainer_group_maximums=None): """ Looks through all scripts to find trainer group numbers and trainer numbers. @@ -3683,7 +3687,7 @@ def find_trainer_ids_from_scripts(): for item in script_parse_table.items(): object = item[1] if isinstance(object, Script): - check_script_has_trainer_data(object) + check_script_has_trainer_data(object, trainer_group_maximums=trainer_group_maximums) # make a set of each list of trainer ids to avoid dupes # this will be used later in TrainerGroupTable @@ -3692,7 +3696,7 @@ def find_trainer_ids_from_scripts(): value = set(item[1]) trainer_group_maximums[key] = value -def report_unreferenced_trainer_ids(): +def report_unreferenced_trainer_ids(trainer_group_maximums): """ Reports on the number of unreferenced trainer ids in each group. @@ -3734,7 +3738,25 @@ def report_unreferenced_trainer_ids(): logging.info(output) logging.info("total unreferenced trainers: {0}".format(total_unreferenced_trainers)) -def check_script_has_trainer_data(script): +def trainer_group_report(trainer_group_maximums): + """ + 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 = trainers.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 check_script_has_trainer_data(script, trainer_group_maximums=None): """ see find_trainer_ids_from_scripts """ @@ -3763,24 +3785,6 @@ def trainer_name_from_group(group_id, trainer_id=0): text = parse_text_at2(address, how_many_until(chr(0x50), address, rom)) 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 = trainers.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 trainers.trainer_group_names and sets the trainer names. @@ -6843,6 +6847,8 @@ def scan_for_predefined_labels(debug=False): all_map_headers = [] +trainer_group_maximums = {} + def run_main(rom=None): if not rom: # read the rom and figure out the offsets for maps @@ -6859,11 +6865,11 @@ def run_main(rom=None): # 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() + find_trainer_ids_from_scripts(script_parse_table=script_parse_table, trainer_group_maximums=trainer_group_maximums) # and parse the main TrainerGroupTable once we know the max number of trainers #global trainer_group_table - trainer_group_table = TrainerGroupTable() + trainer_group_table = TrainerGroupTable(trainer_group_maximums=trainer_group_maximums, trainers=trainers, script_parse_table=script_parse_table) # improve duplicate trainer names make_trainer_group_name_trainer_ids(trainer_group_table) -- cgit v1.2.3 From 10360d114f1f2cd9c16ec426b188991e4f7b2457 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:51:24 -0500 Subject: one preprocessor macro needs trainer_group_maximum --- pokemontools/crystal.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index c38c510..71f66b6 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -6849,6 +6849,10 @@ all_map_headers = [] trainer_group_maximums = {} +# Some of the commands need a reference to this data. This is a hacky way to +# get around having a global, and it should be fixed eventually. +Command.trainer_group_maximums = trainer_group_maximums + def run_main(rom=None): if not rom: # read the rom and figure out the offsets for maps -- cgit v1.2.3 From 30afb173f5ce0b490dbc59d9b38272377c6741db Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 22:57:45 -0500 Subject: pretty_print_trainer_id_constants to trainers.py --- pokemontools/crystal.py | 25 ------------------------- pokemontools/trainers.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 71f66b6..64d45de 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -3824,31 +3824,6 @@ def make_trainer_group_name_trainer_ids(trainer_group_table, debug=True): if debug: logging.info("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 trainers.trainer_group_names != None, "must have trainers.trainer_group_names available" - assert "trainer_names" in trainers.trainer_group_names[1].keys(), "trainer_names must be set in trainers.trainer_group_names" - - output = "" - for (key, value) in trainers.trainer_group_names.items(): - if "uses_numeric_trainer_ids" in trainers.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" diff --git a/pokemontools/trainers.py b/pokemontools/trainers.py index cf17b98..f636025 100644 --- a/pokemontools/trainers.py +++ b/pokemontools/trainers.py @@ -104,5 +104,30 @@ def remove_parentheticals_from_trainer_group_names(): i += 1 return trainer_group_names +def pretty_print_trainer_id_constants(trainer_group_table, trainers): + """ + 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 trainers.trainer_group_names != None, "must have trainers.trainer_group_names available" + assert "trainer_names" in trainers.trainer_group_names[1].keys(), "trainer_names must be set in trainers.trainer_group_names" + + output = "" + for (key, value) in trainers.trainer_group_names.items(): + if "uses_numeric_trainer_ids" in trainers.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 + # remove [Blue] from each trainer group name remove_parentheticals_from_trainer_group_names() -- cgit v1.2.3 From 6d075ba08c06b91fd7138d09d8bcd0a701f30b3a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 23:00:15 -0500 Subject: run_main -> main --- pokemontools/crystal.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 64d45de..839a917 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -6474,7 +6474,7 @@ 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") + raise Exception("all_texts is blank.. main() will populate it") assert bank != None, "list_texts_in_banks must be given a particular bank" @@ -6492,7 +6492,7 @@ 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") + raise Exception("all_movements is blank.. 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)" @@ -6511,7 +6511,7 @@ def dump_asm_for_texts_in_bank(bank, start=50, end=100, rom=None): # load and parse the ROM if necessary if rom == None or len(rom) <= 4: rom = load_rom() - run_main() + main() # get all texts # first 100 look okay? @@ -6531,7 +6531,7 @@ def dump_asm_for_texts_in_bank(bank, start=50, end=100, rom=None): def dump_asm_for_movements_in_bank(bank, start=0, end=100): if rom == None or len(rom) <= 4: load_rom() - run_main() + main() movements = list_movements_in_bank(bank)[start:end] @@ -6547,7 +6547,7 @@ def dump_things_in_bank(bank, start=50, end=100): # load and parse the ROM if necessary if rom == None or len(rom) <= 4: load_rom() - run_main() + main() things = list_things_in_bank(bank)[start:end] @@ -6828,7 +6828,7 @@ trainer_group_maximums = {} # get around having a global, and it should be fixed eventually. Command.trainer_group_maximums = trainer_group_maximums -def run_main(rom=None): +def main(rom=None): if not rom: # read the rom and figure out the offsets for maps rom = direct_load_rom() @@ -6853,8 +6853,5 @@ def run_main(rom=None): # improve duplicate trainer names make_trainer_group_name_trainer_ids(trainer_group_table) -# just a helpful alias -main = run_main - if __name__ == "crystal": pass -- cgit v1.2.3 From 03a94980d8c1b111a7d0256a806d74ec9e00f332 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 23:27:44 -0500 Subject: pass around all_movements (not a global) --- pokemontools/crystal.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 839a917..02887f1 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -301,7 +301,7 @@ class TextScript: ) # load up the rom if it hasn't been loaded already - load_rom() + rom = 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" @@ -1601,7 +1601,7 @@ class ApplyMovementData: ) # load up the rom if it hasn't been loaded already - load_rom() + rom = 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" @@ -2835,7 +2835,7 @@ class Script: 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() + rom = 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" @@ -2943,7 +2943,7 @@ def compare_script_parsing_methods(address): output of the other. When there's a difference, there is something worth correcting. Probably by each command's "macro_name" attribute. """ - load_rom() + rom = load_rom() separator = "################ compare_script_parsing_methods" # first do it the old way logging.debug(separator) @@ -6487,7 +6487,7 @@ def list_texts_in_bank(bank): return texts -def list_movements_in_bank(bank): +def list_movements_in_bank(bank, all_movements): """ Narrows down the list of objects to speed up Asm insertion. """ @@ -6528,12 +6528,12 @@ def dump_asm_for_texts_in_bank(bank, start=50, end=100, rom=None): logging.info("done dumping texts for bank {banked}".format(banked="$%.2x" % bank)) -def dump_asm_for_movements_in_bank(bank, start=0, end=100): +def dump_asm_for_movements_in_bank(bank, start=0, end=100, all_movements=None): if rom == None or len(rom) <= 4: - load_rom() + rom = load_rom() main() - movements = list_movements_in_bank(bank)[start:end] + movements = list_movements_in_bank(bank, all_movements)[start:end] asm = Asm() asm.insert_with_dependencies(movements) @@ -6546,7 +6546,7 @@ def dump_things_in_bank(bank, start=50, end=100): """ # load and parse the ROM if necessary if rom == None or len(rom) <= 4: - load_rom() + rom = load_rom() main() things = list_things_in_bank(bank)[start:end] -- cgit v1.2.3 From b9955a5e40a2c517d50a9f84ee47d3c12ce23e9a Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 12 Sep 2013 23:33:14 -0500 Subject: deglobalize map_internal_ids Also, don't set global rom in the load_rom related methods. This will nip everything for a while, but roms should be passed around as references instead of globals. --- pokemontools/crystal.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 02887f1..c76d006 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -114,7 +114,6 @@ rom = romstr.RomStr(None) def direct_load_rom(filename="../baserom.gbc"): """loads bytes into memory""" - global rom file_handler = open(filename, "rb") rom = romstr.RomStr(file_handler.read()) file_handler.close() @@ -123,7 +122,6 @@ def direct_load_rom(filename="../baserom.gbc"): 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.RomStr(None) and rom != None: return rom if not isinstance(rom, romstr.RomStr): @@ -139,7 +137,6 @@ def direct_load_asm(filename="../main.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 @@ -285,7 +282,8 @@ class TextScript: if self.address in [0x26ef, 0x26f2, 0x6ee, 0x1071, 0x5ce33, 0x69523, 0x7ee98, 0x72176, 0x7a578, 0x19c09b, 0x19768c]: return None - global text_command_classes, script_parse_table + text_command_classes = self.text_command_classes + script_parse_table = self.script_parse_table current_address = copy(self.address) start_address = copy(current_address) @@ -559,22 +557,20 @@ def rom_text_at(address, count=10): 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): +def get_map_constant_label(map_group=None, map_id=None, map_internal_ids=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): +def get_map_constant_label_by_id(global_id, map_internal_ids): """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): @@ -647,7 +643,7 @@ def generate_map_constants_dimensions(): 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): +def transform_wildmons(asm, map_internal_ids): """ Converts a wildmons section to use map constants. input: wildmons text. @@ -659,7 +655,7 @@ def transform_wildmons(asm): 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) + label = get_map_constant_label(map_group=map_group, map_id=map_id, map_internal_ids=map_internal_ids) returnlines.append("\tdb GROUP_"+label+", MAP_"+label) #+" ; " + line.split(";")[1]) else: returnlines.append(line) @@ -1042,7 +1038,7 @@ class CoinByteParam(MultiByteParam): 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 + map_constant_label = get_map_constant_label(map_id=map_id, map_group=self.byte, map_internal_ids=self.map_internal_ids) # like PALLET_TOWN if map_constant_label == None: return str(self.byte) #else: return "GROUP("+map_constant_label+")" @@ -1057,7 +1053,7 @@ class MapIdParam(SingleByteParam): 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) + map_constant_label = get_map_constant_label(map_id=self.byte, map_group=map_group, map_internal_ids=self.map_internal_ids) if map_constant_label == None: return str(self.byte) #else: return "MAP("+map_constant_label+")" @@ -1074,7 +1070,7 @@ class MapGroupIdParam(MultiByteParam): 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) + label = get_map_constant_label(map_group=map_group, map_id=map_id, map_internal_ids=self.map_internal_ids) return label @@ -4573,7 +4569,7 @@ class SecondMapHeader: return dependencies def to_asm(self): - self_constant_label = get_map_constant_label(map_group=self.map_group, map_id=self.map_id) + self_constant_label = get_map_constant_label(map_group=self.map_group, map_id=self.map_id, map_internal_ids=self.map_internal_ids) output = "; border block\n" output += "db " + self.border_block.to_asm() + "\n\n" output += "; height, width\n" @@ -5016,8 +5012,8 @@ class Connection: 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) + map_constant_label = get_map_constant_label(map_group=connected_map_group_id, map_id=connected_map_id, map_internal_ids=self.map_internal_ids) + self_constant_label = get_map_constant_label(map_group=self.smh.map_group, map_id=self.smh.map_id, map_internal_ids=self.map_internal_ids) if map_constant_label != None: map_group_label = "GROUP_" + map_constant_label map_label = "MAP_" + map_constant_label @@ -6828,6 +6824,9 @@ trainer_group_maximums = {} # get around having a global, and it should be fixed eventually. Command.trainer_group_maximums = trainer_group_maximums +SingleByteParam.map_internal_ids = map_internal_ids +MultiByteParam.map_internal_ids = map_internal_ids + def main(rom=None): if not rom: # read the rom and figure out the offsets for maps -- cgit v1.2.3 From bd3b6081888e0602432c0328e6f44bfc7d0b7f63 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 14 Sep 2013 17:10:59 -0400 Subject: barely-working map editor works with both pokecrystal and pokered version is crystal by default --- pokemontools/map_editor.py | 635 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 635 insertions(+) create mode 100644 pokemontools/map_editor.py diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py new file mode 100644 index 0000000..92e4a63 --- /dev/null +++ b/pokemontools/map_editor.py @@ -0,0 +1,635 @@ +import os + +from Tkinter import * +import ttk +from ttk import Frame, Style +import PIL +from PIL import Image, ImageTk + +import configuration +conf = configuration.Config() + + +version = 'crystal' +#version = 'red' + +if version == 'crystal': + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2d' % x + block_dir = os.path.join(conf.path, 'tilesets/') + block_ext = '_metatiles.bin' + + palettes_on = True + palmap_dir = os.path.join(conf.path, 'tilesets/') + palette_dir = os.path.join(conf.path, 'tilesets/') + + asm_dir = os.path.join(conf.path, 'maps/') + + constants_dir = os.path.join(conf.path, 'constants/') + constants_filename = os.path.join(constants_dir, 'map_constants.asm') + + header_dir = os.path.join(conf.path, 'maps/') + + # todo + display_connections = False + +elif version == 'red': + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2x' % x + block_dir = os.path.join(conf.path, 'gfx/blocksets/') + block_ext = '.bst' + + palettes_on = False + + asm_path = os.path.join(conf.path, 'main.asm') + + constants_filename = os.path.join(conf.path, 'constants.asm') + + header_path = os.path.join(conf.path, 'main.asm') + + # todo + display_connections = False + +else: + raise Exception, 'version must be "crystal" or "red"' + + +def get_constants(): + lines = open(constants_filename, 'r').readlines() + for line in lines: + if ' EQU ' in line: + name, value = [s.strip() for s in line.split(' EQU ')] + globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) +get_constants() + + +class Application(Frame): + def __init__(self, master=None): + Frame.__init__(self, master) + self.grid() + Style().configure("TFrame", background="#444") + self.paint_tile = 1 + self.init_ui() + + def init_ui(self): + self.connections = {} + self.button_frame = Frame(self) + self.button_frame.grid(row=0, column=0, columnspan=2) + self.map_frame = Frame(self) + self.map_frame.grid(row=1, column=0, padx=5, pady=5) + self.picker_frame = Frame(self) + self.picker_frame.grid(row=1, column=1) + + self.new = Button(self.button_frame) + self.new["text"] = "New" + self.new["command"] = self.new_map + self.new.grid(row=0, column=0, padx=2) + + self.open = Button(self.button_frame) + self.open["text"] = "Open" + self.open["command"] = self.open_map + self.open.grid(row=0, column=1, padx=2) + + self.save = Button(self.button_frame) + self.save["text"] = "Save" + self.save["command"] = self.save_map + self.save.grid(row=0, column=2, padx=2) + + self.get_map_list() + self.map_list.grid(row=0, column=3, padx=2) + + + def get_map_list(self): + self.available_maps = sorted(m for m in get_available_maps()) + self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) + if len(self.available_maps): + self.map_list.set(self.available_maps[0]) + + def new_map(self): + self.map_name = None + self.init_map() + self.map.blockdata = [self.paint_tile] * 20 * 20 + self.map.width = 20 + self.map.height = 20 + self.draw_map() + self.init_picker() + + def open_map(self): + self.map_name = self.map_list.get() + self.init_map() + self.draw_map() + self.init_picker() + + def save_map(self): + if hasattr(self, 'map'): + if self.map.blockdata_filename: + with open(self.map.blockdata_filename, 'wb') as save: + save.write(self.map.blockdata) + print 'blockdata saved as %s' % self.map.blockdata_filename + else: + print 'dunno how to save this' + else: + print 'nothing to save' + + def init_map(self): + if hasattr(self, 'map'): + self.map.kill_canvas() + self.map = Map(self.map_frame, self.map_name) + self.init_map_connections() + + def draw_map(self): + self.map.init_canvas(self.map_frame) + self.map.canvas.pack() #.grid(row=1,column=1) + self.map.draw() + self.map.canvas.bind('', self.paint) + self.map.canvas.bind('', self.paint) + + def init_picker(self): + + self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.width = 1 + self.current_tile.height = 1 + self.current_tile.init_canvas() + self.current_tile.draw() + self.current_tile.canvas.grid(row=0, column=4, padx=4) + + if hasattr(self, 'picker'): + self.picker.kill_canvas() + self.picker = Map(self, tileset_id=self.map.tileset_id) + self.picker.blockdata = range(len(self.picker.tileset.blocks)) + self.picker.width = 4 + self.picker.height = len(self.picker.blockdata) / self.picker.width + self.picker.init_canvas(self.picker_frame) + + if hasattr(self.picker_frame, 'vbar'): + self.picker_frame.vbar.destroy() + self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) + self.picker_frame.vbar.pack(side=RIGHT, fill=Y) + self.picker_frame.vbar.config(command=self.picker.canvas.yview) + + + self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) + self.map_frame.update() + self.picker.canvas.config(height=self.map_frame.winfo_height()) + self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) + self.picker.canvas.pack(side=LEFT, expand=True) + + self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) + + self.picker.draw() + self.picker.canvas.bind('', self.pick_block) + + def scroll_picker(self, event): + if event.num == 4: + self.picker.canvas.yview('scroll', -1, 'units') + elif event.num == 5: + self.picker.canvas.yview('scroll', 1, 'units') + + + def pick_block(self, event): + block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) + block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) + i = block_y * self.picker.width + block_x + self.paint_tile = self.picker.blockdata[i] + + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.draw() + + def paint(self, event): + block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) + block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) + i = block_y * self.map.width + block_x + if 0 <= i < len(self.map.blockdata): + self.map.blockdata[i] = self.paint_tile + self.map.draw_block(block_x, block_y) + + def init_map_connections(self): + if not display_connections: + return + for direction in self.map.connections.keys(): + if direction in self.connections.keys(): + if hasattr(self.connections[direction], 'canvas'): + self.connections[direction].kill_canvas() + if self.map.connections[direction] == {}: + self.connections[direction] = {} + continue + self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) + + if direction in ['north', 'south']: + x1 = 0 + y1 = 0 + x2 = x1 + eval(self.map.connections[direction]['strip_length']) + y2 = y1 + 3 + else: # east, west + x1 = 0 + y1 = 0 + x2 = x1 + 3 + y2 = y1 + eval(self.map.connections[direction]['strip_length']) + + self.connections[direction].crop(x1, y1, x2, y2) + self.connections[direction].init_canvas(self.map_frame) + self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].draw() + + +class Map: + def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): + self.parent = parent + + self.name = name + + self.blockdata_filename = blockdata_filename + if not self.blockdata_filename and self.name: + self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') + elif not self.blockdata_filename: + self.blockdata_filename = '' + + asm_filename = '' + if self.name: + if 'asm_dir' in globals().keys(): + asm_filename = os.path.join(asm_dir, self.name + '.asm') + elif 'asm_path' in globals().keys(): + asm_filename = asm_path + + if os.path.exists(asm_filename): + for props in [map_header(self.name), second_map_header(self.name)]: + self.__dict__.update(props) + self.asm = open(asm_filename, 'r').read() + self.events = event_header(self.asm, self.name) + self.scripts = script_header(self.asm, self.name) + + self.tileset_id = eval(self.tileset_id) + + self.width = eval(self.width) + self.height = eval(self.height) + + else: + self.width = width + self.height = height + self.tileset_id = tileset_id + + if self.blockdata_filename: + self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) + else: + self.blockdata = [] + + self.tileset = Tileset(self.tileset_id) + + def init_canvas(self, parent=None): + if parent == None: + parent = self.parent + if not hasattr(self, 'canvas'): + self.canvas_width = self.width * 32 + self.canvas_height = self.height * 32 + self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) + self.canvas.xview_moveto(0) + self.canvas.yview_moveto(0) + + def kill_canvas(self): + if hasattr(self, 'canvas'): + self.canvas.destroy() + + def crop(self, x1, y1, x2, y2): + blockdata = self.blockdata + start = y1 * self.width + x1 + width = x2 - x1 + height = y2 - y1 + self.blockdata = [] + for y in xrange(height): + for x in xrange(width): + self.blockdata += [blockdata[start + y * self.width + x]] + self.blockdata = bytearray(self.blockdata) + self.width = width + self.height = height + + def draw(self): + for i in xrange(len(self.blockdata)): + block_x = i % self.width + block_y = i / self.width + self.draw_block(block_x, block_y) + + def draw_block(self, block_x, block_y): + # the canvas starts at 4, 4 for some reason + # probably something to do with a border + index, indey = 4, 4 + + # Draw one block (4x4 tiles) + block = self.blockdata[block_y * self.width + block_x] + for j, tile in enumerate(self.tileset.blocks[block]): + # Tile gfx are split in half to make vram mapping easier + if tile >= 0x80: + tile -= 0x20 + tile_x = block_x * 32 + (j % 4) * 8 + tile_y = block_y * 32 + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + + +class Tileset: + def __init__(self, tileset_id=0): + self.id = tileset_id + + self.tile_width = 8 + self.tile_height = 8 + self.block_width = 4 + self.block_height = 4 + + self.alpha = 255 + + if palettes_on: + self.get_palettes() + self.get_palette_map() + + self.get_blocks() + self.get_tiles() + + def get_tiles(self): + filename = os.path.join( + gfx_dir, + to_gfx_name(self.id) + '.png' + ) + self.img = Image.open(filename) + self.img.width, self.img.height = self.img.size + self.tiles = [] + cur_tile = 0 + for y in xrange(0, self.img.height, self.tile_height): + for x in xrange(0, self.img.width, self.tile_width): + tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) + + if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): + # Palette maps are padded to make vram mapping easier. + pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 + tile = self.colorize_tile(tile, self.palettes[pal]) + + self.tiles += [ImageTk.PhotoImage(tile)] + cur_tile += 1 + + def colorize_tile(self, tile, palette): + width, height = tile.size + tile = tile.convert("RGB") + px = tile.load() + for y in xrange(height): + for x in xrange(width): + # assume greyscale + which_color = 3 - (px[x, y][0] / 0x55) + r, g, b = [v * 8 for v in palette[which_color]] + px[x, y] = (r, g, b) + return tile + + def get_blocks(self): + filename = os.path.join( + block_dir, + to_gfx_name(self.id) + block_ext + ) + self.blocks = [] + block_length = self.block_width * self.block_height + blocks = bytearray(open(filename, 'rb').read()) + for block in xrange(len(blocks) / (block_length)): + i = block * block_length + self.blocks += [blocks[i : i + block_length]] + + def get_palette_map(self): + filename = os.path.join( + palmap_dir, + str(self.id).zfill(2) + '_palette_map.bin' + ) + self.palette_map = [] + palmap = bytearray(open(filename, 'rb').read()) + for i in xrange(len(palmap)): + self.palette_map += [palmap[i] & 0xf] + self.palette_map += [(palmap[i] >> 4) & 0xf] + + def get_palettes(self): + filename = os.path.join( + palette_dir, + ['morn', 'day', 'nite'][time_of_day] + '.pal' + ) + self.palettes = get_palettes(filename) + + +time_of_day = 1 + + +def get_palettes(filename): + pals = bytearray(open(filename, 'rb').read()) + + num_colors = 4 + color_length = 2 + + palette_length = num_colors * color_length + + num_pals = len(pals) / palette_length + + palettes = [] + for pal in xrange(num_pals): + palettes += [[]] + + for color in xrange(num_colors): + i = pal * palette_length + i += color * color_length + word = pals[i] + pals[i+1] * 0x100 + palettes[pal] += [[ + c & 0x1f for c in [ + word >> 0, + word >> 5, + word >> 10, + ] + ]] + return palettes + + + +def get_available_maps(): + for root, dirs, files in os.walk(map_dir): + for filename in files: + base_name, ext = os.path.splitext(filename) + if ext == '.blk': + yield base_name + + +def map_header(name): + if version == 'crystal': + headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() + label = name + '_MapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'dw', 'db' ] + attributes = [ + 'bank', + 'tileset_id', + 'permission', + 'second_map_header', + 'world_map_location', + 'music', + 'time_of_day', + 'fishing_group', + ] + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + return attrs + + elif version == 'red': + headers = open(header_path, 'r').read() + + # there has to be a better way to do this + lower_label = name + '_h' + i = headers.lower().find(lower_label) + if i == -1: + return {} + label = headers[i:i+len(lower_label)] + + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dw', 'db' ] + attributes = [ + 'tileset_id', + 'height', + 'width', + 'blockdata_label', + 'text_label', + 'script_label', + 'which_connections', + ] + values, l = read_header_macros(header, attributes, macros) + + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + + macros = [ 'dw' ] + attributes = [ + 'object_label', + ] + values, l = read_header_macros(header[l:], attributes, macros) + attrs.update(dict(zip(attributes, values))) + + return attrs + + return {} + +def second_map_header(name): + if version == 'crystal': + headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() + label = name + '_SecondMapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] + attributes = [ + 'border_block', + 'height', + 'width', + 'blockdata_bank', + 'blockdata_label', + 'script_header_bank', + 'script_header_label', + 'map_event_header_label', + 'which_connections', + ] + + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + return attrs + + return {} + +def connections(which_connections, header, l=0): + directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } + macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] + + if version == 'crystal': + attributes = [ + 'map_group', + 'map_no', + ] + + elif version == 'red': + attributes = [ + 'map_id', + ] + + attributes += [ + 'strip_pointer', + 'strip_destination', + 'strip_length', + 'map_width', + 'y_offset', + 'x_offset', + 'window', + ] + for d in directions.keys(): + if d.upper() in which_connections: + values, l = read_header_macros(header, attributes, macros) + header = header[l:] + directions[d] = dict(zip(attributes, values)) + if version == 'crystal': + directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') + elif version == 'red': + directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') + return directions, l + +def read_header_macros(header, attributes, macros): + values = [] + i = 0 + l = 0 + for l, (asm, comment) in enumerate(header): + if asm.strip() != '': + values += macro_values(asm, macros[i]) + i += 1 + if len(values) >= len(attributes): + l += 1 + break + return values, l + + +def event_header(asm, name): + return {} + +def script_header(asm, name): + return {} + + +def macro_values(line, macro): + values = line[line.find(macro) + len(macro):].split(',') + return [v.replace('$','0x').strip() for v in values] + +def db_value(line): + macro = 'db' + return macro_values(line, macro) + +def db_values(line): + macro = 'db' + return macro_values(line, macro) + + +from preprocessor import separate_comment + +def asm_at_label(asm, label): + label_def = label + ':' + start = asm.find(label_def) + len(label_def) + lines = asm[start:].split('\n') + # go until the next label + content = [] + for line in lines: + l, comment = separate_comment(line + '\n') + if ':' in l: + break + content += [[l, comment]] + return content + + +root = Tk() +root.wm_title("MAP EDITOR") +app = Application(master=root) + +try: + app.mainloop() +except KeyboardInterrupt: + pass + +try: + root.destroy() +except TclError: + pass + -- cgit v1.2.3 From 33c8e4147c9a188b677a0995e8f9ce0760a2d60e Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 15 Sep 2013 15:46:26 -0400 Subject: map_editor: red: read tileset gfx filenames from source --- pokemontools/map_editor.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 92e4a63..208cc49 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -348,11 +348,27 @@ class Tileset: self.get_blocks() self.get_tiles() + def get_tileset_gfx_filename(self): + filename = None + + if version == 'red': + tileset_defs = open(os.path.join(conf.path, 'main.asm'), 'r').read() + incbin = asm_at_label(tileset_defs, 'Tset%.2X_GFX' % self.id) + print incbin + filename = read_header_macros(incbin, ['filename'], ['INCBIN'])[0][0].replace('"','').replace('.2bpp','.png') + filename = os.path.join(conf.path, filename) + print filename + + if not filename: + filename = os.path.join( + gfx_dir, + to_gfx_name(self.id) + '.png' + ) + + return filename + def get_tiles(self): - filename = os.path.join( - gfx_dir, - to_gfx_name(self.id) + '.png' - ) + filename = self.get_tileset_gfx_filename() self.img = Image.open(filename) self.img.width, self.img.height = self.img.size self.tiles = [] -- cgit v1.2.3 From 427fbf4d042504d04e9a5373451292302dd91bbe Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 16 Sep 2013 12:36:53 -0500 Subject: fix how pointers are imported --- pokemontools/old_text_script.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/old_text_script.py b/pokemontools/old_text_script.py index 23e4a67..a4cfb8c 100644 --- a/pokemontools/old_text_script.py +++ b/pokemontools/old_text_script.py @@ -2,7 +2,7 @@ An old implementation of TextScript that may not be useful anymore. """ -import pokemontools.pointers as pointers +import pointers class OldTextScript: "a text is a sequence of commands different from a script-engine script" -- cgit v1.2.3 From b67e68c6a16d4ac0b5a23d5d796ad439e727e95c Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Mon, 16 Sep 2013 12:40:37 -0500 Subject: use try/except on globals.asm for preprocessing --- pokemontools/preprocessor.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index 0cb7f48..5fe0851 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -489,12 +489,16 @@ class Preprocessor(object): """ Add any labels not already in globals.asm. """ - globes = open(os.path.join(self.config.path, 'globals.asm'), 'r+') - lines = globes.readlines() - for globe in self.globes: - line = 'GLOBAL ' + globe + '\n' - if line not in lines: - globes.write(line) + # TODO: pokered needs to be fixed + try: + globes = open(os.path.join(self.config.path, 'globals.asm'), 'r+') + lines = globes.readlines() + for globe in self.globes: + line = 'GLOBAL ' + globe + '\n' + if line not in lines: + globes.write(line) + except Exception as exception: + pass # don't care if it's not there... def read_line(self, l): """ -- cgit v1.2.3 From 8caeec66b9004c9bae9a27b0c8008091ba5f39f9 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 21:32:29 -0500 Subject: include all pokemontools/ files in installs --- MANIFEST.in | 1 + pokemontools/crystalparts/old_parsers.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index d750b85..7bd0a47 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include README.md +recursive-include pokemontools * global-exclude *.pyc global-exclude .gitignore diff --git a/pokemontools/crystalparts/old_parsers.py b/pokemontools/crystalparts/old_parsers.py index b23ab5d..e07082d 100644 --- a/pokemontools/crystalparts/old_parsers.py +++ b/pokemontools/crystalparts/old_parsers.py @@ -4,8 +4,7 @@ Some old methods rescued from crystal.py import pointers -map_header_byte_size = ... -rom_interval = ... +map_header_byte_size = 9 all_map_headers = [] -- cgit v1.2.3 From 0da95fe05f9949125fe2b821c3f72a0ac5c826ea Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 21:37:07 -0500 Subject: version bump to: v1.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2c277e..709a7a8 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ requires = [ setup( name="pokemontools", - version="1.3.0", + version="1.4.0", description="Tools for compiling and disassembling Pokémon Red and Pokémon Crystal.", long_description=open("README.md", "r").read(), license="BSD", -- cgit v1.2.3 From 480593e472dd2efbfc4c643e1594ae10aef3dc2c Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 21:39:51 -0500 Subject: also exclude .*.swp files --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 7bd0a47..de35aca 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include README.md recursive-include pokemontools * +global-exclude .*.swp global-exclude *.pyc global-exclude .gitignore global-exclude .DS_Store -- cgit v1.2.3 From 82fb73edc3850e3601f085d84f7bba03fe6d7076 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 21 Sep 2013 21:45:49 -0500 Subject: version bump to: v1.4.1 The previous version was uploaded with .swp files and a large libvba.so that was in my working directory. I regret everything. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 709a7a8..65c9842 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ requires = [ setup( name="pokemontools", - version="1.4.0", + version="1.4.1", description="Tools for compiling and disassembling Pokémon Red and Pokémon Crystal.", long_description=open("README.md", "r").read(), license="BSD", -- cgit v1.2.3 From 5f0bd7a2fefbb9354301a3926b011d055e650c56 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 26 Sep 2013 01:57:29 -0400 Subject: use config instead of configuration this was kind of overzealous --- pokemontools/map_editor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 208cc49..9cd29ea 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -6,12 +6,12 @@ from ttk import Frame, Style import PIL from PIL import Image, ImageTk -import configuration -conf = configuration.Config() +import config +conf = config.Config() -version = 'crystal' -#version = 'red' +#version = 'crystal' +version = 'red' if version == 'crystal': map_dir = os.path.join(conf.path, 'maps/') -- cgit v1.2.3 From 9bea14db798eedcb262d17562a37b1f337fb67eb Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:04:51 -0500 Subject: tabs to spaces (pep8) --- pokemontools/map_editor.py | 1078 ++++++++++++++++++++++---------------------- 1 file changed, 539 insertions(+), 539 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 92e4a63..d30d3d2 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -14,609 +14,609 @@ version = 'crystal' #version = 'red' if version == 'crystal': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2d' % x - block_dir = os.path.join(conf.path, 'tilesets/') - block_ext = '_metatiles.bin' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2d' % x + block_dir = os.path.join(conf.path, 'tilesets/') + block_ext = '_metatiles.bin' - palettes_on = True - palmap_dir = os.path.join(conf.path, 'tilesets/') - palette_dir = os.path.join(conf.path, 'tilesets/') + palettes_on = True + palmap_dir = os.path.join(conf.path, 'tilesets/') + palette_dir = os.path.join(conf.path, 'tilesets/') - asm_dir = os.path.join(conf.path, 'maps/') + asm_dir = os.path.join(conf.path, 'maps/') - constants_dir = os.path.join(conf.path, 'constants/') - constants_filename = os.path.join(constants_dir, 'map_constants.asm') + constants_dir = os.path.join(conf.path, 'constants/') + constants_filename = os.path.join(constants_dir, 'map_constants.asm') - header_dir = os.path.join(conf.path, 'maps/') + header_dir = os.path.join(conf.path, 'maps/') - # todo - display_connections = False + # todo + display_connections = False elif version == 'red': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2x' % x - block_dir = os.path.join(conf.path, 'gfx/blocksets/') - block_ext = '.bst' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2x' % x + block_dir = os.path.join(conf.path, 'gfx/blocksets/') + block_ext = '.bst' - palettes_on = False + palettes_on = False - asm_path = os.path.join(conf.path, 'main.asm') + asm_path = os.path.join(conf.path, 'main.asm') - constants_filename = os.path.join(conf.path, 'constants.asm') + constants_filename = os.path.join(conf.path, 'constants.asm') - header_path = os.path.join(conf.path, 'main.asm') + header_path = os.path.join(conf.path, 'main.asm') - # todo - display_connections = False + # todo + display_connections = False else: - raise Exception, 'version must be "crystal" or "red"' + raise Exception, 'version must be "crystal" or "red"' def get_constants(): - lines = open(constants_filename, 'r').readlines() - for line in lines: - if ' EQU ' in line: - name, value = [s.strip() for s in line.split(' EQU ')] - globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) + lines = open(constants_filename, 'r').readlines() + for line in lines: + if ' EQU ' in line: + name, value = [s.strip() for s in line.split(' EQU ')] + globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) get_constants() class Application(Frame): - def __init__(self, master=None): - Frame.__init__(self, master) - self.grid() - Style().configure("TFrame", background="#444") - self.paint_tile = 1 - self.init_ui() - - def init_ui(self): - self.connections = {} - self.button_frame = Frame(self) - self.button_frame.grid(row=0, column=0, columnspan=2) - self.map_frame = Frame(self) - self.map_frame.grid(row=1, column=0, padx=5, pady=5) - self.picker_frame = Frame(self) - self.picker_frame.grid(row=1, column=1) - - self.new = Button(self.button_frame) - self.new["text"] = "New" - self.new["command"] = self.new_map - self.new.grid(row=0, column=0, padx=2) - - self.open = Button(self.button_frame) - self.open["text"] = "Open" - self.open["command"] = self.open_map - self.open.grid(row=0, column=1, padx=2) - - self.save = Button(self.button_frame) - self.save["text"] = "Save" - self.save["command"] = self.save_map - self.save.grid(row=0, column=2, padx=2) - - self.get_map_list() - self.map_list.grid(row=0, column=3, padx=2) - - - def get_map_list(self): - self.available_maps = sorted(m for m in get_available_maps()) - self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) - if len(self.available_maps): - self.map_list.set(self.available_maps[0]) - - def new_map(self): - self.map_name = None - self.init_map() - self.map.blockdata = [self.paint_tile] * 20 * 20 - self.map.width = 20 - self.map.height = 20 - self.draw_map() - self.init_picker() - - def open_map(self): - self.map_name = self.map_list.get() - self.init_map() - self.draw_map() - self.init_picker() - - def save_map(self): - if hasattr(self, 'map'): - if self.map.blockdata_filename: - with open(self.map.blockdata_filename, 'wb') as save: - save.write(self.map.blockdata) - print 'blockdata saved as %s' % self.map.blockdata_filename - else: - print 'dunno how to save this' - else: - print 'nothing to save' - - def init_map(self): - if hasattr(self, 'map'): - self.map.kill_canvas() - self.map = Map(self.map_frame, self.map_name) - self.init_map_connections() - - def draw_map(self): - self.map.init_canvas(self.map_frame) - self.map.canvas.pack() #.grid(row=1,column=1) - self.map.draw() - self.map.canvas.bind('', self.paint) - self.map.canvas.bind('', self.paint) - - def init_picker(self): - - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.width = 1 - self.current_tile.height = 1 - self.current_tile.init_canvas() - self.current_tile.draw() - self.current_tile.canvas.grid(row=0, column=4, padx=4) - - if hasattr(self, 'picker'): - self.picker.kill_canvas() - self.picker = Map(self, tileset_id=self.map.tileset_id) - self.picker.blockdata = range(len(self.picker.tileset.blocks)) - self.picker.width = 4 - self.picker.height = len(self.picker.blockdata) / self.picker.width - self.picker.init_canvas(self.picker_frame) - - if hasattr(self.picker_frame, 'vbar'): - self.picker_frame.vbar.destroy() - self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) - self.picker_frame.vbar.pack(side=RIGHT, fill=Y) - self.picker_frame.vbar.config(command=self.picker.canvas.yview) - - - self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) - self.map_frame.update() - self.picker.canvas.config(height=self.map_frame.winfo_height()) - self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) - self.picker.canvas.pack(side=LEFT, expand=True) - - self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) - - self.picker.draw() - self.picker.canvas.bind('', self.pick_block) - - def scroll_picker(self, event): - if event.num == 4: - self.picker.canvas.yview('scroll', -1, 'units') - elif event.num == 5: - self.picker.canvas.yview('scroll', 1, 'units') - - - def pick_block(self, event): - block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) - block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) - i = block_y * self.picker.width + block_x - self.paint_tile = self.picker.blockdata[i] - - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.draw() - - def paint(self, event): - block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) - block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) - i = block_y * self.map.width + block_x - if 0 <= i < len(self.map.blockdata): - self.map.blockdata[i] = self.paint_tile - self.map.draw_block(block_x, block_y) - - def init_map_connections(self): - if not display_connections: - return - for direction in self.map.connections.keys(): - if direction in self.connections.keys(): - if hasattr(self.connections[direction], 'canvas'): - self.connections[direction].kill_canvas() - if self.map.connections[direction] == {}: - self.connections[direction] = {} - continue - self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) - - if direction in ['north', 'south']: - x1 = 0 - y1 = 0 - x2 = x1 + eval(self.map.connections[direction]['strip_length']) - y2 = y1 + 3 - else: # east, west - x1 = 0 - y1 = 0 - x2 = x1 + 3 - y2 = y1 + eval(self.map.connections[direction]['strip_length']) - - self.connections[direction].crop(x1, y1, x2, y2) - self.connections[direction].init_canvas(self.map_frame) - self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) - self.connections[direction].draw() + def __init__(self, master=None): + Frame.__init__(self, master) + self.grid() + Style().configure("TFrame", background="#444") + self.paint_tile = 1 + self.init_ui() + + def init_ui(self): + self.connections = {} + self.button_frame = Frame(self) + self.button_frame.grid(row=0, column=0, columnspan=2) + self.map_frame = Frame(self) + self.map_frame.grid(row=1, column=0, padx=5, pady=5) + self.picker_frame = Frame(self) + self.picker_frame.grid(row=1, column=1) + + self.new = Button(self.button_frame) + self.new["text"] = "New" + self.new["command"] = self.new_map + self.new.grid(row=0, column=0, padx=2) + + self.open = Button(self.button_frame) + self.open["text"] = "Open" + self.open["command"] = self.open_map + self.open.grid(row=0, column=1, padx=2) + + self.save = Button(self.button_frame) + self.save["text"] = "Save" + self.save["command"] = self.save_map + self.save.grid(row=0, column=2, padx=2) + + self.get_map_list() + self.map_list.grid(row=0, column=3, padx=2) + + + def get_map_list(self): + self.available_maps = sorted(m for m in get_available_maps()) + self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) + if len(self.available_maps): + self.map_list.set(self.available_maps[0]) + + def new_map(self): + self.map_name = None + self.init_map() + self.map.blockdata = [self.paint_tile] * 20 * 20 + self.map.width = 20 + self.map.height = 20 + self.draw_map() + self.init_picker() + + def open_map(self): + self.map_name = self.map_list.get() + self.init_map() + self.draw_map() + self.init_picker() + + def save_map(self): + if hasattr(self, 'map'): + if self.map.blockdata_filename: + with open(self.map.blockdata_filename, 'wb') as save: + save.write(self.map.blockdata) + print 'blockdata saved as %s' % self.map.blockdata_filename + else: + print 'dunno how to save this' + else: + print 'nothing to save' + + def init_map(self): + if hasattr(self, 'map'): + self.map.kill_canvas() + self.map = Map(self.map_frame, self.map_name) + self.init_map_connections() + + def draw_map(self): + self.map.init_canvas(self.map_frame) + self.map.canvas.pack() #.grid(row=1,column=1) + self.map.draw() + self.map.canvas.bind('', self.paint) + self.map.canvas.bind('', self.paint) + + def init_picker(self): + + self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.width = 1 + self.current_tile.height = 1 + self.current_tile.init_canvas() + self.current_tile.draw() + self.current_tile.canvas.grid(row=0, column=4, padx=4) + + if hasattr(self, 'picker'): + self.picker.kill_canvas() + self.picker = Map(self, tileset_id=self.map.tileset_id) + self.picker.blockdata = range(len(self.picker.tileset.blocks)) + self.picker.width = 4 + self.picker.height = len(self.picker.blockdata) / self.picker.width + self.picker.init_canvas(self.picker_frame) + + if hasattr(self.picker_frame, 'vbar'): + self.picker_frame.vbar.destroy() + self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) + self.picker_frame.vbar.pack(side=RIGHT, fill=Y) + self.picker_frame.vbar.config(command=self.picker.canvas.yview) + + + self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) + self.map_frame.update() + self.picker.canvas.config(height=self.map_frame.winfo_height()) + self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) + self.picker.canvas.pack(side=LEFT, expand=True) + + self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) + + self.picker.draw() + self.picker.canvas.bind('', self.pick_block) + + def scroll_picker(self, event): + if event.num == 4: + self.picker.canvas.yview('scroll', -1, 'units') + elif event.num == 5: + self.picker.canvas.yview('scroll', 1, 'units') + + + def pick_block(self, event): + block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) + block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) + i = block_y * self.picker.width + block_x + self.paint_tile = self.picker.blockdata[i] + + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.draw() + + def paint(self, event): + block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) + block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) + i = block_y * self.map.width + block_x + if 0 <= i < len(self.map.blockdata): + self.map.blockdata[i] = self.paint_tile + self.map.draw_block(block_x, block_y) + + def init_map_connections(self): + if not display_connections: + return + for direction in self.map.connections.keys(): + if direction in self.connections.keys(): + if hasattr(self.connections[direction], 'canvas'): + self.connections[direction].kill_canvas() + if self.map.connections[direction] == {}: + self.connections[direction] = {} + continue + self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) + + if direction in ['north', 'south']: + x1 = 0 + y1 = 0 + x2 = x1 + eval(self.map.connections[direction]['strip_length']) + y2 = y1 + 3 + else: # east, west + x1 = 0 + y1 = 0 + x2 = x1 + 3 + y2 = y1 + eval(self.map.connections[direction]['strip_length']) + + self.connections[direction].crop(x1, y1, x2, y2) + self.connections[direction].init_canvas(self.map_frame) + self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].draw() class Map: - def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): - self.parent = parent - - self.name = name - - self.blockdata_filename = blockdata_filename - if not self.blockdata_filename and self.name: - self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') - elif not self.blockdata_filename: - self.blockdata_filename = '' - - asm_filename = '' - if self.name: - if 'asm_dir' in globals().keys(): - asm_filename = os.path.join(asm_dir, self.name + '.asm') - elif 'asm_path' in globals().keys(): - asm_filename = asm_path - - if os.path.exists(asm_filename): - for props in [map_header(self.name), second_map_header(self.name)]: - self.__dict__.update(props) - self.asm = open(asm_filename, 'r').read() - self.events = event_header(self.asm, self.name) - self.scripts = script_header(self.asm, self.name) - - self.tileset_id = eval(self.tileset_id) - - self.width = eval(self.width) - self.height = eval(self.height) - - else: - self.width = width - self.height = height - self.tileset_id = tileset_id - - if self.blockdata_filename: - self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) - else: - self.blockdata = [] - - self.tileset = Tileset(self.tileset_id) - - def init_canvas(self, parent=None): - if parent == None: - parent = self.parent - if not hasattr(self, 'canvas'): - self.canvas_width = self.width * 32 - self.canvas_height = self.height * 32 - self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) - self.canvas.xview_moveto(0) - self.canvas.yview_moveto(0) - - def kill_canvas(self): - if hasattr(self, 'canvas'): - self.canvas.destroy() - - def crop(self, x1, y1, x2, y2): - blockdata = self.blockdata - start = y1 * self.width + x1 - width = x2 - x1 - height = y2 - y1 - self.blockdata = [] - for y in xrange(height): - for x in xrange(width): - self.blockdata += [blockdata[start + y * self.width + x]] - self.blockdata = bytearray(self.blockdata) - self.width = width - self.height = height - - def draw(self): - for i in xrange(len(self.blockdata)): - block_x = i % self.width - block_y = i / self.width - self.draw_block(block_x, block_y) - - def draw_block(self, block_x, block_y): - # the canvas starts at 4, 4 for some reason - # probably something to do with a border - index, indey = 4, 4 - - # Draw one block (4x4 tiles) - block = self.blockdata[block_y * self.width + block_x] - for j, tile in enumerate(self.tileset.blocks[block]): - # Tile gfx are split in half to make vram mapping easier - if tile >= 0x80: - tile -= 0x20 - tile_x = block_x * 32 + (j % 4) * 8 - tile_y = block_y * 32 + (j / 4) * 8 - self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): + self.parent = parent + + self.name = name + + self.blockdata_filename = blockdata_filename + if not self.blockdata_filename and self.name: + self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') + elif not self.blockdata_filename: + self.blockdata_filename = '' + + asm_filename = '' + if self.name: + if 'asm_dir' in globals().keys(): + asm_filename = os.path.join(asm_dir, self.name + '.asm') + elif 'asm_path' in globals().keys(): + asm_filename = asm_path + + if os.path.exists(asm_filename): + for props in [map_header(self.name), second_map_header(self.name)]: + self.__dict__.update(props) + self.asm = open(asm_filename, 'r').read() + self.events = event_header(self.asm, self.name) + self.scripts = script_header(self.asm, self.name) + + self.tileset_id = eval(self.tileset_id) + + self.width = eval(self.width) + self.height = eval(self.height) + + else: + self.width = width + self.height = height + self.tileset_id = tileset_id + + if self.blockdata_filename: + self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) + else: + self.blockdata = [] + + self.tileset = Tileset(self.tileset_id) + + def init_canvas(self, parent=None): + if parent == None: + parent = self.parent + if not hasattr(self, 'canvas'): + self.canvas_width = self.width * 32 + self.canvas_height = self.height * 32 + self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) + self.canvas.xview_moveto(0) + self.canvas.yview_moveto(0) + + def kill_canvas(self): + if hasattr(self, 'canvas'): + self.canvas.destroy() + + def crop(self, x1, y1, x2, y2): + blockdata = self.blockdata + start = y1 * self.width + x1 + width = x2 - x1 + height = y2 - y1 + self.blockdata = [] + for y in xrange(height): + for x in xrange(width): + self.blockdata += [blockdata[start + y * self.width + x]] + self.blockdata = bytearray(self.blockdata) + self.width = width + self.height = height + + def draw(self): + for i in xrange(len(self.blockdata)): + block_x = i % self.width + block_y = i / self.width + self.draw_block(block_x, block_y) + + def draw_block(self, block_x, block_y): + # the canvas starts at 4, 4 for some reason + # probably something to do with a border + index, indey = 4, 4 + + # Draw one block (4x4 tiles) + block = self.blockdata[block_y * self.width + block_x] + for j, tile in enumerate(self.tileset.blocks[block]): + # Tile gfx are split in half to make vram mapping easier + if tile >= 0x80: + tile -= 0x20 + tile_x = block_x * 32 + (j % 4) * 8 + tile_y = block_y * 32 + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) class Tileset: - def __init__(self, tileset_id=0): - self.id = tileset_id - - self.tile_width = 8 - self.tile_height = 8 - self.block_width = 4 - self.block_height = 4 - - self.alpha = 255 - - if palettes_on: - self.get_palettes() - self.get_palette_map() - - self.get_blocks() - self.get_tiles() - - def get_tiles(self): - filename = os.path.join( - gfx_dir, - to_gfx_name(self.id) + '.png' - ) - self.img = Image.open(filename) - self.img.width, self.img.height = self.img.size - self.tiles = [] - cur_tile = 0 - for y in xrange(0, self.img.height, self.tile_height): - for x in xrange(0, self.img.width, self.tile_width): - tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) - - if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): - # Palette maps are padded to make vram mapping easier. - pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 - tile = self.colorize_tile(tile, self.palettes[pal]) - - self.tiles += [ImageTk.PhotoImage(tile)] - cur_tile += 1 - - def colorize_tile(self, tile, palette): - width, height = tile.size - tile = tile.convert("RGB") - px = tile.load() - for y in xrange(height): - for x in xrange(width): - # assume greyscale - which_color = 3 - (px[x, y][0] / 0x55) - r, g, b = [v * 8 for v in palette[which_color]] - px[x, y] = (r, g, b) - return tile - - def get_blocks(self): - filename = os.path.join( - block_dir, - to_gfx_name(self.id) + block_ext - ) - self.blocks = [] - block_length = self.block_width * self.block_height - blocks = bytearray(open(filename, 'rb').read()) - for block in xrange(len(blocks) / (block_length)): - i = block * block_length - self.blocks += [blocks[i : i + block_length]] - - def get_palette_map(self): - filename = os.path.join( - palmap_dir, - str(self.id).zfill(2) + '_palette_map.bin' - ) - self.palette_map = [] - palmap = bytearray(open(filename, 'rb').read()) - for i in xrange(len(palmap)): - self.palette_map += [palmap[i] & 0xf] - self.palette_map += [(palmap[i] >> 4) & 0xf] - - def get_palettes(self): - filename = os.path.join( - palette_dir, - ['morn', 'day', 'nite'][time_of_day] + '.pal' - ) - self.palettes = get_palettes(filename) + def __init__(self, tileset_id=0): + self.id = tileset_id + + self.tile_width = 8 + self.tile_height = 8 + self.block_width = 4 + self.block_height = 4 + + self.alpha = 255 + + if palettes_on: + self.get_palettes() + self.get_palette_map() + + self.get_blocks() + self.get_tiles() + + def get_tiles(self): + filename = os.path.join( + gfx_dir, + to_gfx_name(self.id) + '.png' + ) + self.img = Image.open(filename) + self.img.width, self.img.height = self.img.size + self.tiles = [] + cur_tile = 0 + for y in xrange(0, self.img.height, self.tile_height): + for x in xrange(0, self.img.width, self.tile_width): + tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) + + if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): + # Palette maps are padded to make vram mapping easier. + pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 + tile = self.colorize_tile(tile, self.palettes[pal]) + + self.tiles += [ImageTk.PhotoImage(tile)] + cur_tile += 1 + + def colorize_tile(self, tile, palette): + width, height = tile.size + tile = tile.convert("RGB") + px = tile.load() + for y in xrange(height): + for x in xrange(width): + # assume greyscale + which_color = 3 - (px[x, y][0] / 0x55) + r, g, b = [v * 8 for v in palette[which_color]] + px[x, y] = (r, g, b) + return tile + + def get_blocks(self): + filename = os.path.join( + block_dir, + to_gfx_name(self.id) + block_ext + ) + self.blocks = [] + block_length = self.block_width * self.block_height + blocks = bytearray(open(filename, 'rb').read()) + for block in xrange(len(blocks) / (block_length)): + i = block * block_length + self.blocks += [blocks[i : i + block_length]] + + def get_palette_map(self): + filename = os.path.join( + palmap_dir, + str(self.id).zfill(2) + '_palette_map.bin' + ) + self.palette_map = [] + palmap = bytearray(open(filename, 'rb').read()) + for i in xrange(len(palmap)): + self.palette_map += [palmap[i] & 0xf] + self.palette_map += [(palmap[i] >> 4) & 0xf] + + def get_palettes(self): + filename = os.path.join( + palette_dir, + ['morn', 'day', 'nite'][time_of_day] + '.pal' + ) + self.palettes = get_palettes(filename) time_of_day = 1 def get_palettes(filename): - pals = bytearray(open(filename, 'rb').read()) + pals = bytearray(open(filename, 'rb').read()) - num_colors = 4 - color_length = 2 + num_colors = 4 + color_length = 2 - palette_length = num_colors * color_length + palette_length = num_colors * color_length - num_pals = len(pals) / palette_length + num_pals = len(pals) / palette_length - palettes = [] - for pal in xrange(num_pals): - palettes += [[]] + palettes = [] + for pal in xrange(num_pals): + palettes += [[]] - for color in xrange(num_colors): - i = pal * palette_length - i += color * color_length - word = pals[i] + pals[i+1] * 0x100 - palettes[pal] += [[ - c & 0x1f for c in [ - word >> 0, - word >> 5, - word >> 10, - ] - ]] - return palettes + for color in xrange(num_colors): + i = pal * palette_length + i += color * color_length + word = pals[i] + pals[i+1] * 0x100 + palettes[pal] += [[ + c & 0x1f for c in [ + word >> 0, + word >> 5, + word >> 10, + ] + ]] + return palettes def get_available_maps(): - for root, dirs, files in os.walk(map_dir): - for filename in files: - base_name, ext = os.path.splitext(filename) - if ext == '.blk': - yield base_name + for root, dirs, files in os.walk(map_dir): + for filename in files: + base_name, ext = os.path.splitext(filename) + if ext == '.blk': + yield base_name def map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() - label = name + '_MapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'dw', 'db' ] - attributes = [ - 'bank', - 'tileset_id', - 'permission', - 'second_map_header', - 'world_map_location', - 'music', - 'time_of_day', - 'fishing_group', - ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - return attrs - - elif version == 'red': - headers = open(header_path, 'r').read() - - # there has to be a better way to do this - lower_label = name + '_h' - i = headers.lower().find(lower_label) - if i == -1: - return {} - label = headers[i:i+len(lower_label)] - - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dw', 'db' ] - attributes = [ - 'tileset_id', - 'height', - 'width', - 'blockdata_label', - 'text_label', - 'script_label', - 'which_connections', - ] - values, l = read_header_macros(header, attributes, macros) - - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - - macros = [ 'dw' ] - attributes = [ - 'object_label', - ] - values, l = read_header_macros(header[l:], attributes, macros) - attrs.update(dict(zip(attributes, values))) - - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() + label = name + '_MapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'dw', 'db' ] + attributes = [ + 'bank', + 'tileset_id', + 'permission', + 'second_map_header', + 'world_map_location', + 'music', + 'time_of_day', + 'fishing_group', + ] + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + return attrs + + elif version == 'red': + headers = open(header_path, 'r').read() + + # there has to be a better way to do this + lower_label = name + '_h' + i = headers.lower().find(lower_label) + if i == -1: + return {} + label = headers[i:i+len(lower_label)] + + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dw', 'db' ] + attributes = [ + 'tileset_id', + 'height', + 'width', + 'blockdata_label', + 'text_label', + 'script_label', + 'which_connections', + ] + values, l = read_header_macros(header, attributes, macros) + + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + + macros = [ 'dw' ] + attributes = [ + 'object_label', + ] + values, l = read_header_macros(header[l:], attributes, macros) + attrs.update(dict(zip(attributes, values))) + + return attrs + + return {} def second_map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() - label = name + '_SecondMapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] - attributes = [ - 'border_block', - 'height', - 'width', - 'blockdata_bank', - 'blockdata_label', - 'script_header_bank', - 'script_header_label', - 'map_event_header_label', - 'which_connections', - ] - - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() + label = name + '_SecondMapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] + attributes = [ + 'border_block', + 'height', + 'width', + 'blockdata_bank', + 'blockdata_label', + 'script_header_bank', + 'script_header_label', + 'map_event_header_label', + 'which_connections', + ] + + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + return attrs + + return {} def connections(which_connections, header, l=0): - directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } - macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] - - if version == 'crystal': - attributes = [ - 'map_group', - 'map_no', - ] - - elif version == 'red': - attributes = [ - 'map_id', - ] - - attributes += [ - 'strip_pointer', - 'strip_destination', - 'strip_length', - 'map_width', - 'y_offset', - 'x_offset', - 'window', - ] - for d in directions.keys(): - if d.upper() in which_connections: - values, l = read_header_macros(header, attributes, macros) - header = header[l:] - directions[d] = dict(zip(attributes, values)) - if version == 'crystal': - directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') - elif version == 'red': - directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') - return directions, l + directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } + macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] + + if version == 'crystal': + attributes = [ + 'map_group', + 'map_no', + ] + + elif version == 'red': + attributes = [ + 'map_id', + ] + + attributes += [ + 'strip_pointer', + 'strip_destination', + 'strip_length', + 'map_width', + 'y_offset', + 'x_offset', + 'window', + ] + for d in directions.keys(): + if d.upper() in which_connections: + values, l = read_header_macros(header, attributes, macros) + header = header[l:] + directions[d] = dict(zip(attributes, values)) + if version == 'crystal': + directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') + elif version == 'red': + directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') + return directions, l def read_header_macros(header, attributes, macros): - values = [] - i = 0 - l = 0 - for l, (asm, comment) in enumerate(header): - if asm.strip() != '': - values += macro_values(asm, macros[i]) - i += 1 - if len(values) >= len(attributes): - l += 1 - break - return values, l + values = [] + i = 0 + l = 0 + for l, (asm, comment) in enumerate(header): + if asm.strip() != '': + values += macro_values(asm, macros[i]) + i += 1 + if len(values) >= len(attributes): + l += 1 + break + return values, l def event_header(asm, name): - return {} + return {} def script_header(asm, name): - return {} + return {} def macro_values(line, macro): - values = line[line.find(macro) + len(macro):].split(',') - return [v.replace('$','0x').strip() for v in values] + values = line[line.find(macro) + len(macro):].split(',') + return [v.replace('$','0x').strip() for v in values] def db_value(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) def db_values(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) from preprocessor import separate_comment def asm_at_label(asm, label): - label_def = label + ':' - start = asm.find(label_def) + len(label_def) - lines = asm[start:].split('\n') - # go until the next label - content = [] - for line in lines: - l, comment = separate_comment(line + '\n') - if ':' in l: - break - content += [[l, comment]] - return content + label_def = label + ':' + start = asm.find(label_def) + len(label_def) + lines = asm[start:].split('\n') + # go until the next label + content = [] + for line in lines: + l, comment = separate_comment(line + '\n') + if ':' in l: + break + content += [[l, comment]] + return content root = Tk() @@ -624,12 +624,12 @@ root.wm_title("MAP EDITOR") app = Application(master=root) try: - app.mainloop() + app.mainloop() except KeyboardInterrupt: - pass + pass try: - root.destroy() + root.destroy() except TclError: - pass + pass -- cgit v1.2.3 From e4b2d2e4d24d09b719712bf3793a655307fa60d8 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:05:23 -0500 Subject: minor formatting fix --- pokemontools/map_editor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index d30d3d2..c6f9f16 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -147,7 +147,6 @@ class Application(Frame): self.map.canvas.bind('', self.paint) def init_picker(self): - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) self.current_tile.blockdata = [self.paint_tile] self.current_tile.width = 1 -- cgit v1.2.3 From 3f28232f0d7ad1a3c6a72c603c5f8318a57fba64 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:06:19 -0500 Subject: put the tk loop into __main__ --- pokemontools/map_editor.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index c6f9f16..11bfb4b 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -617,18 +617,23 @@ def asm_at_label(asm, label): content += [[l, comment]] return content - -root = Tk() -root.wm_title("MAP EDITOR") -app = Application(master=root) - -try: - app.mainloop() -except KeyboardInterrupt: - pass - -try: - root.destroy() -except TclError: - pass - +def main(): + """ + Launches the map editor. + """ + root = Tk() + root.wm_title("MAP EDITOR") + app = Application(master=root) + + try: + app.mainloop() + except KeyboardInterrupt: + pass + + try: + root.destroy() + except TclError: + pass + +if __name__ == "__main__": + main() -- cgit v1.2.3 From 9d1a68cce57a9e4ac4111853c1597a646866a6af Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:09:50 -0500 Subject: refactor a global constant to be set in __init__ --- pokemontools/map_editor.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 11bfb4b..13c3b49 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -31,9 +31,6 @@ if version == 'crystal': header_dir = os.path.join(conf.path, 'maps/') - # todo - display_connections = False - elif version == 'red': map_dir = os.path.join(conf.path, 'maps/') gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') @@ -49,9 +46,6 @@ elif version == 'red': header_path = os.path.join(conf.path, 'main.asm') - # todo - display_connections = False - else: raise Exception, 'version must be "crystal" or "red"' @@ -67,6 +61,8 @@ get_constants() class Application(Frame): def __init__(self, master=None): + self.display_connections = False + Frame.__init__(self, master) self.grid() Style().configure("TFrame", background="#444") @@ -209,7 +205,7 @@ class Application(Frame): self.map.draw_block(block_x, block_y) def init_map_connections(self): - if not display_connections: + if not self.display_connections: return for direction in self.map.connections.keys(): if direction in self.connections.keys(): -- cgit v1.2.3 From cff5ce9bc81c78d8f841570480f1716fb91c1fac Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 01:24:13 -0500 Subject: Revert "tabs to spaces (pep8)" This reverts commit 9bea14db798eedcb262d17562a37b1f337fb67eb. Conflicts: pokemontools/map_editor.py --- pokemontools/map_editor.py | 1064 ++++++++++++++++++++++---------------------- 1 file changed, 532 insertions(+), 532 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 13c3b49..d4b5047 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -14,604 +14,604 @@ version = 'crystal' #version = 'red' if version == 'crystal': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2d' % x - block_dir = os.path.join(conf.path, 'tilesets/') - block_ext = '_metatiles.bin' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2d' % x + block_dir = os.path.join(conf.path, 'tilesets/') + block_ext = '_metatiles.bin' - palettes_on = True - palmap_dir = os.path.join(conf.path, 'tilesets/') - palette_dir = os.path.join(conf.path, 'tilesets/') + palettes_on = True + palmap_dir = os.path.join(conf.path, 'tilesets/') + palette_dir = os.path.join(conf.path, 'tilesets/') - asm_dir = os.path.join(conf.path, 'maps/') + asm_dir = os.path.join(conf.path, 'maps/') - constants_dir = os.path.join(conf.path, 'constants/') - constants_filename = os.path.join(constants_dir, 'map_constants.asm') + constants_dir = os.path.join(conf.path, 'constants/') + constants_filename = os.path.join(constants_dir, 'map_constants.asm') - header_dir = os.path.join(conf.path, 'maps/') + header_dir = os.path.join(conf.path, 'maps/') elif version == 'red': - map_dir = os.path.join(conf.path, 'maps/') - gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') - to_gfx_name = lambda x : '%.2x' % x - block_dir = os.path.join(conf.path, 'gfx/blocksets/') - block_ext = '.bst' + map_dir = os.path.join(conf.path, 'maps/') + gfx_dir = os.path.join(conf.path, 'gfx/tilesets/') + to_gfx_name = lambda x : '%.2x' % x + block_dir = os.path.join(conf.path, 'gfx/blocksets/') + block_ext = '.bst' - palettes_on = False + palettes_on = False - asm_path = os.path.join(conf.path, 'main.asm') + asm_path = os.path.join(conf.path, 'main.asm') - constants_filename = os.path.join(conf.path, 'constants.asm') + constants_filename = os.path.join(conf.path, 'constants.asm') - header_path = os.path.join(conf.path, 'main.asm') + header_path = os.path.join(conf.path, 'main.asm') else: - raise Exception, 'version must be "crystal" or "red"' + raise Exception, 'version must be "crystal" or "red"' def get_constants(): - lines = open(constants_filename, 'r').readlines() - for line in lines: - if ' EQU ' in line: - name, value = [s.strip() for s in line.split(' EQU ')] - globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) + lines = open(constants_filename, 'r').readlines() + for line in lines: + if ' EQU ' in line: + name, value = [s.strip() for s in line.split(' EQU ')] + globals()[name] = eval(value.split(';')[0].replace('$','0x').replace('%','0b')) get_constants() class Application(Frame): - def __init__(self, master=None): - self.display_connections = False - - Frame.__init__(self, master) - self.grid() - Style().configure("TFrame", background="#444") - self.paint_tile = 1 - self.init_ui() - - def init_ui(self): - self.connections = {} - self.button_frame = Frame(self) - self.button_frame.grid(row=0, column=0, columnspan=2) - self.map_frame = Frame(self) - self.map_frame.grid(row=1, column=0, padx=5, pady=5) - self.picker_frame = Frame(self) - self.picker_frame.grid(row=1, column=1) - - self.new = Button(self.button_frame) - self.new["text"] = "New" - self.new["command"] = self.new_map - self.new.grid(row=0, column=0, padx=2) - - self.open = Button(self.button_frame) - self.open["text"] = "Open" - self.open["command"] = self.open_map - self.open.grid(row=0, column=1, padx=2) - - self.save = Button(self.button_frame) - self.save["text"] = "Save" - self.save["command"] = self.save_map - self.save.grid(row=0, column=2, padx=2) - - self.get_map_list() - self.map_list.grid(row=0, column=3, padx=2) - - - def get_map_list(self): - self.available_maps = sorted(m for m in get_available_maps()) - self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) - if len(self.available_maps): - self.map_list.set(self.available_maps[0]) - - def new_map(self): - self.map_name = None - self.init_map() - self.map.blockdata = [self.paint_tile] * 20 * 20 - self.map.width = 20 - self.map.height = 20 - self.draw_map() - self.init_picker() - - def open_map(self): - self.map_name = self.map_list.get() - self.init_map() - self.draw_map() - self.init_picker() - - def save_map(self): - if hasattr(self, 'map'): - if self.map.blockdata_filename: - with open(self.map.blockdata_filename, 'wb') as save: - save.write(self.map.blockdata) - print 'blockdata saved as %s' % self.map.blockdata_filename - else: - print 'dunno how to save this' - else: - print 'nothing to save' - - def init_map(self): - if hasattr(self, 'map'): - self.map.kill_canvas() - self.map = Map(self.map_frame, self.map_name) - self.init_map_connections() - - def draw_map(self): - self.map.init_canvas(self.map_frame) - self.map.canvas.pack() #.grid(row=1,column=1) - self.map.draw() - self.map.canvas.bind('', self.paint) - self.map.canvas.bind('', self.paint) - - def init_picker(self): - self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.width = 1 - self.current_tile.height = 1 - self.current_tile.init_canvas() - self.current_tile.draw() - self.current_tile.canvas.grid(row=0, column=4, padx=4) - - if hasattr(self, 'picker'): - self.picker.kill_canvas() - self.picker = Map(self, tileset_id=self.map.tileset_id) - self.picker.blockdata = range(len(self.picker.tileset.blocks)) - self.picker.width = 4 - self.picker.height = len(self.picker.blockdata) / self.picker.width - self.picker.init_canvas(self.picker_frame) - - if hasattr(self.picker_frame, 'vbar'): - self.picker_frame.vbar.destroy() - self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) - self.picker_frame.vbar.pack(side=RIGHT, fill=Y) - self.picker_frame.vbar.config(command=self.picker.canvas.yview) - - - self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) - self.map_frame.update() - self.picker.canvas.config(height=self.map_frame.winfo_height()) - self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) - self.picker.canvas.pack(side=LEFT, expand=True) - - self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) - self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) - - self.picker.draw() - self.picker.canvas.bind('', self.pick_block) - - def scroll_picker(self, event): - if event.num == 4: - self.picker.canvas.yview('scroll', -1, 'units') - elif event.num == 5: - self.picker.canvas.yview('scroll', 1, 'units') - - - def pick_block(self, event): - block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) - block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) - i = block_y * self.picker.width + block_x - self.paint_tile = self.picker.blockdata[i] - - self.current_tile.blockdata = [self.paint_tile] - self.current_tile.draw() - - def paint(self, event): - block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) - block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) - i = block_y * self.map.width + block_x - if 0 <= i < len(self.map.blockdata): - self.map.blockdata[i] = self.paint_tile - self.map.draw_block(block_x, block_y) - - def init_map_connections(self): - if not self.display_connections: - return - for direction in self.map.connections.keys(): - if direction in self.connections.keys(): - if hasattr(self.connections[direction], 'canvas'): - self.connections[direction].kill_canvas() - if self.map.connections[direction] == {}: - self.connections[direction] = {} - continue - self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) - - if direction in ['north', 'south']: - x1 = 0 - y1 = 0 - x2 = x1 + eval(self.map.connections[direction]['strip_length']) - y2 = y1 + 3 - else: # east, west - x1 = 0 - y1 = 0 - x2 = x1 + 3 - y2 = y1 + eval(self.map.connections[direction]['strip_length']) - - self.connections[direction].crop(x1, y1, x2, y2) - self.connections[direction].init_canvas(self.map_frame) - self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) - self.connections[direction].draw() + def __init__(self, master=None): + self.display_connections = False + Frame.__init__(self, master) + self.grid() + Style().configure("TFrame", background="#444") + self.paint_tile = 1 + self.init_ui() + + def init_ui(self): + self.connections = {} + self.button_frame = Frame(self) + self.button_frame.grid(row=0, column=0, columnspan=2) + self.map_frame = Frame(self) + self.map_frame.grid(row=1, column=0, padx=5, pady=5) + self.picker_frame = Frame(self) + self.picker_frame.grid(row=1, column=1) + + self.new = Button(self.button_frame) + self.new["text"] = "New" + self.new["command"] = self.new_map + self.new.grid(row=0, column=0, padx=2) + + self.open = Button(self.button_frame) + self.open["text"] = "Open" + self.open["command"] = self.open_map + self.open.grid(row=0, column=1, padx=2) + + self.save = Button(self.button_frame) + self.save["text"] = "Save" + self.save["command"] = self.save_map + self.save.grid(row=0, column=2, padx=2) + + self.get_map_list() + self.map_list.grid(row=0, column=3, padx=2) + + + def get_map_list(self): + self.available_maps = sorted(m for m in get_available_maps()) + self.map_list = ttk.Combobox(self.button_frame, height=24, width=24, values=self.available_maps) + if len(self.available_maps): + self.map_list.set(self.available_maps[0]) + + def new_map(self): + self.map_name = None + self.init_map() + self.map.blockdata = [self.paint_tile] * 20 * 20 + self.map.width = 20 + self.map.height = 20 + self.draw_map() + self.init_picker() + + def open_map(self): + self.map_name = self.map_list.get() + self.init_map() + self.draw_map() + self.init_picker() + + def save_map(self): + if hasattr(self, 'map'): + if self.map.blockdata_filename: + with open(self.map.blockdata_filename, 'wb') as save: + save.write(self.map.blockdata) + print 'blockdata saved as %s' % self.map.blockdata_filename + else: + print 'dunno how to save this' + else: + print 'nothing to save' + + def init_map(self): + if hasattr(self, 'map'): + self.map.kill_canvas() + self.map = Map(self.map_frame, self.map_name) + self.init_map_connections() + + def draw_map(self): + self.map.init_canvas(self.map_frame) + self.map.canvas.pack() #.grid(row=1,column=1) + self.map.draw() + self.map.canvas.bind('', self.paint) + self.map.canvas.bind('', self.paint) + + def init_picker(self): + + self.current_tile = Map(self.button_frame, tileset_id=self.map.tileset_id) + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.width = 1 + self.current_tile.height = 1 + self.current_tile.init_canvas() + self.current_tile.draw() + self.current_tile.canvas.grid(row=0, column=4, padx=4) + + if hasattr(self, 'picker'): + self.picker.kill_canvas() + self.picker = Map(self, tileset_id=self.map.tileset_id) + self.picker.blockdata = range(len(self.picker.tileset.blocks)) + self.picker.width = 4 + self.picker.height = len(self.picker.blockdata) / self.picker.width + self.picker.init_canvas(self.picker_frame) + + if hasattr(self.picker_frame, 'vbar'): + self.picker_frame.vbar.destroy() + self.picker_frame.vbar = Scrollbar(self.picker_frame, orient=VERTICAL) + self.picker_frame.vbar.pack(side=RIGHT, fill=Y) + self.picker_frame.vbar.config(command=self.picker.canvas.yview) + + + self.picker.canvas.config(scrollregion=(0,0,self.picker.canvas_width, self.picker.canvas_height)) + self.map_frame.update() + self.picker.canvas.config(height=self.map_frame.winfo_height()) + self.picker.canvas.config(yscrollcommand=self.picker_frame.vbar.set) + self.picker.canvas.pack(side=LEFT, expand=True) + + self.picker.canvas.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker.canvas.bind('<5>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<4>', lambda event : self.scroll_picker(event)) + self.picker_frame.vbar.bind('<5>', lambda event : self.scroll_picker(event)) + + self.picker.draw() + self.picker.canvas.bind('', self.pick_block) + + def scroll_picker(self, event): + if event.num == 4: + self.picker.canvas.yview('scroll', -1, 'units') + elif event.num == 5: + self.picker.canvas.yview('scroll', 1, 'units') + + + def pick_block(self, event): + block_x = int(self.picker.canvas.canvasx(event.x)) / (self.picker.tileset.block_width * self.picker.tileset.tile_width) + block_y = int(self.picker.canvas.canvasy(event.y)) / (self.picker.tileset.block_height * self.picker.tileset.tile_height) + i = block_y * self.picker.width + block_x + self.paint_tile = self.picker.blockdata[i] + + self.current_tile.blockdata = [self.paint_tile] + self.current_tile.draw() + + def paint(self, event): + block_x = event.x / (self.map.tileset.block_width * self.map.tileset.tile_width) + block_y = event.y / (self.map.tileset.block_height * self.map.tileset.tile_height) + i = block_y * self.map.width + block_x + if 0 <= i < len(self.map.blockdata): + self.map.blockdata[i] = self.paint_tile + self.map.draw_block(block_x, block_y) + + def init_map_connections(self): + if not display_connections: + return + for direction in self.map.connections.keys(): + if direction in self.connections.keys(): + if hasattr(self.connections[direction], 'canvas'): + self.connections[direction].kill_canvas() + if self.map.connections[direction] == {}: + self.connections[direction] = {} + continue + self.connections[direction] = Map(self, self.map.connections[direction]['map_name']) + + if direction in ['north', 'south']: + x1 = 0 + y1 = 0 + x2 = x1 + eval(self.map.connections[direction]['strip_length']) + y2 = y1 + 3 + else: # east, west + x1 = 0 + y1 = 0 + x2 = x1 + 3 + y2 = y1 + eval(self.map.connections[direction]['strip_length']) + + self.connections[direction].crop(x1, y1, x2, y2) + self.connections[direction].init_canvas(self.map_frame) + self.connections[direction].canvas.pack(side={'west':LEFT,'east':RIGHT}[direction]) + self.connections[direction].draw() class Map: - def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): - self.parent = parent - - self.name = name - - self.blockdata_filename = blockdata_filename - if not self.blockdata_filename and self.name: - self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') - elif not self.blockdata_filename: - self.blockdata_filename = '' - - asm_filename = '' - if self.name: - if 'asm_dir' in globals().keys(): - asm_filename = os.path.join(asm_dir, self.name + '.asm') - elif 'asm_path' in globals().keys(): - asm_filename = asm_path - - if os.path.exists(asm_filename): - for props in [map_header(self.name), second_map_header(self.name)]: - self.__dict__.update(props) - self.asm = open(asm_filename, 'r').read() - self.events = event_header(self.asm, self.name) - self.scripts = script_header(self.asm, self.name) - - self.tileset_id = eval(self.tileset_id) - - self.width = eval(self.width) - self.height = eval(self.height) - - else: - self.width = width - self.height = height - self.tileset_id = tileset_id - - if self.blockdata_filename: - self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) - else: - self.blockdata = [] - - self.tileset = Tileset(self.tileset_id) - - def init_canvas(self, parent=None): - if parent == None: - parent = self.parent - if not hasattr(self, 'canvas'): - self.canvas_width = self.width * 32 - self.canvas_height = self.height * 32 - self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) - self.canvas.xview_moveto(0) - self.canvas.yview_moveto(0) - - def kill_canvas(self): - if hasattr(self, 'canvas'): - self.canvas.destroy() - - def crop(self, x1, y1, x2, y2): - blockdata = self.blockdata - start = y1 * self.width + x1 - width = x2 - x1 - height = y2 - y1 - self.blockdata = [] - for y in xrange(height): - for x in xrange(width): - self.blockdata += [blockdata[start + y * self.width + x]] - self.blockdata = bytearray(self.blockdata) - self.width = width - self.height = height - - def draw(self): - for i in xrange(len(self.blockdata)): - block_x = i % self.width - block_y = i / self.width - self.draw_block(block_x, block_y) - - def draw_block(self, block_x, block_y): - # the canvas starts at 4, 4 for some reason - # probably something to do with a border - index, indey = 4, 4 - - # Draw one block (4x4 tiles) - block = self.blockdata[block_y * self.width + block_x] - for j, tile in enumerate(self.tileset.blocks[block]): - # Tile gfx are split in half to make vram mapping easier - if tile >= 0x80: - tile -= 0x20 - tile_x = block_x * 32 + (j % 4) * 8 - tile_y = block_y * 32 + (j / 4) * 8 - self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + def __init__(self, parent, name=None, width=20, height=20, tileset_id=2, blockdata_filename=None): + self.parent = parent + + self.name = name + + self.blockdata_filename = blockdata_filename + if not self.blockdata_filename and self.name: + self.blockdata_filename = os.path.join(map_dir, self.name + '.blk') + elif not self.blockdata_filename: + self.blockdata_filename = '' + + asm_filename = '' + if self.name: + if 'asm_dir' in globals().keys(): + asm_filename = os.path.join(asm_dir, self.name + '.asm') + elif 'asm_path' in globals().keys(): + asm_filename = asm_path + + if os.path.exists(asm_filename): + for props in [map_header(self.name), second_map_header(self.name)]: + self.__dict__.update(props) + self.asm = open(asm_filename, 'r').read() + self.events = event_header(self.asm, self.name) + self.scripts = script_header(self.asm, self.name) + + self.tileset_id = eval(self.tileset_id) + + self.width = eval(self.width) + self.height = eval(self.height) + + else: + self.width = width + self.height = height + self.tileset_id = tileset_id + + if self.blockdata_filename: + self.blockdata = bytearray(open(self.blockdata_filename, 'rb').read()) + else: + self.blockdata = [] + + self.tileset = Tileset(self.tileset_id) + + def init_canvas(self, parent=None): + if parent == None: + parent = self.parent + if not hasattr(self, 'canvas'): + self.canvas_width = self.width * 32 + self.canvas_height = self.height * 32 + self.canvas = Canvas(parent, width=self.canvas_width, height=self.canvas_height) + self.canvas.xview_moveto(0) + self.canvas.yview_moveto(0) + + def kill_canvas(self): + if hasattr(self, 'canvas'): + self.canvas.destroy() + + def crop(self, x1, y1, x2, y2): + blockdata = self.blockdata + start = y1 * self.width + x1 + width = x2 - x1 + height = y2 - y1 + self.blockdata = [] + for y in xrange(height): + for x in xrange(width): + self.blockdata += [blockdata[start + y * self.width + x]] + self.blockdata = bytearray(self.blockdata) + self.width = width + self.height = height + + def draw(self): + for i in xrange(len(self.blockdata)): + block_x = i % self.width + block_y = i / self.width + self.draw_block(block_x, block_y) + + def draw_block(self, block_x, block_y): + # the canvas starts at 4, 4 for some reason + # probably something to do with a border + index, indey = 4, 4 + + # Draw one block (4x4 tiles) + block = self.blockdata[block_y * self.width + block_x] + for j, tile in enumerate(self.tileset.blocks[block]): + # Tile gfx are split in half to make vram mapping easier + if tile >= 0x80: + tile -= 0x20 + tile_x = block_x * 32 + (j % 4) * 8 + tile_y = block_y * 32 + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) class Tileset: - def __init__(self, tileset_id=0): - self.id = tileset_id - - self.tile_width = 8 - self.tile_height = 8 - self.block_width = 4 - self.block_height = 4 - - self.alpha = 255 - - if palettes_on: - self.get_palettes() - self.get_palette_map() - - self.get_blocks() - self.get_tiles() - - def get_tiles(self): - filename = os.path.join( - gfx_dir, - to_gfx_name(self.id) + '.png' - ) - self.img = Image.open(filename) - self.img.width, self.img.height = self.img.size - self.tiles = [] - cur_tile = 0 - for y in xrange(0, self.img.height, self.tile_height): - for x in xrange(0, self.img.width, self.tile_width): - tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) - - if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): - # Palette maps are padded to make vram mapping easier. - pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 - tile = self.colorize_tile(tile, self.palettes[pal]) - - self.tiles += [ImageTk.PhotoImage(tile)] - cur_tile += 1 - - def colorize_tile(self, tile, palette): - width, height = tile.size - tile = tile.convert("RGB") - px = tile.load() - for y in xrange(height): - for x in xrange(width): - # assume greyscale - which_color = 3 - (px[x, y][0] / 0x55) - r, g, b = [v * 8 for v in palette[which_color]] - px[x, y] = (r, g, b) - return tile - - def get_blocks(self): - filename = os.path.join( - block_dir, - to_gfx_name(self.id) + block_ext - ) - self.blocks = [] - block_length = self.block_width * self.block_height - blocks = bytearray(open(filename, 'rb').read()) - for block in xrange(len(blocks) / (block_length)): - i = block * block_length - self.blocks += [blocks[i : i + block_length]] - - def get_palette_map(self): - filename = os.path.join( - palmap_dir, - str(self.id).zfill(2) + '_palette_map.bin' - ) - self.palette_map = [] - palmap = bytearray(open(filename, 'rb').read()) - for i in xrange(len(palmap)): - self.palette_map += [palmap[i] & 0xf] - self.palette_map += [(palmap[i] >> 4) & 0xf] - - def get_palettes(self): - filename = os.path.join( - palette_dir, - ['morn', 'day', 'nite'][time_of_day] + '.pal' - ) - self.palettes = get_palettes(filename) + def __init__(self, tileset_id=0): + self.id = tileset_id + + self.tile_width = 8 + self.tile_height = 8 + self.block_width = 4 + self.block_height = 4 + + self.alpha = 255 + + if palettes_on: + self.get_palettes() + self.get_palette_map() + + self.get_blocks() + self.get_tiles() + + def get_tiles(self): + filename = os.path.join( + gfx_dir, + to_gfx_name(self.id) + '.png' + ) + self.img = Image.open(filename) + self.img.width, self.img.height = self.img.size + self.tiles = [] + cur_tile = 0 + for y in xrange(0, self.img.height, self.tile_height): + for x in xrange(0, self.img.width, self.tile_width): + tile = self.img.crop((x, y, x + self.tile_width, y + self.tile_height)) + + if hasattr(self, 'palette_map') and hasattr(self, 'palettes'): + # Palette maps are padded to make vram mapping easier. + pal = self.palette_map[cur_tile + 0x20 if cur_tile >= 0x60 else cur_tile] & 0x7 + tile = self.colorize_tile(tile, self.palettes[pal]) + + self.tiles += [ImageTk.PhotoImage(tile)] + cur_tile += 1 + + def colorize_tile(self, tile, palette): + width, height = tile.size + tile = tile.convert("RGB") + px = tile.load() + for y in xrange(height): + for x in xrange(width): + # assume greyscale + which_color = 3 - (px[x, y][0] / 0x55) + r, g, b = [v * 8 for v in palette[which_color]] + px[x, y] = (r, g, b) + return tile + + def get_blocks(self): + filename = os.path.join( + block_dir, + to_gfx_name(self.id) + block_ext + ) + self.blocks = [] + block_length = self.block_width * self.block_height + blocks = bytearray(open(filename, 'rb').read()) + for block in xrange(len(blocks) / (block_length)): + i = block * block_length + self.blocks += [blocks[i : i + block_length]] + + def get_palette_map(self): + filename = os.path.join( + palmap_dir, + str(self.id).zfill(2) + '_palette_map.bin' + ) + self.palette_map = [] + palmap = bytearray(open(filename, 'rb').read()) + for i in xrange(len(palmap)): + self.palette_map += [palmap[i] & 0xf] + self.palette_map += [(palmap[i] >> 4) & 0xf] + + def get_palettes(self): + filename = os.path.join( + palette_dir, + ['morn', 'day', 'nite'][time_of_day] + '.pal' + ) + self.palettes = get_palettes(filename) time_of_day = 1 def get_palettes(filename): - pals = bytearray(open(filename, 'rb').read()) + pals = bytearray(open(filename, 'rb').read()) - num_colors = 4 - color_length = 2 + num_colors = 4 + color_length = 2 - palette_length = num_colors * color_length + palette_length = num_colors * color_length - num_pals = len(pals) / palette_length + num_pals = len(pals) / palette_length - palettes = [] - for pal in xrange(num_pals): - palettes += [[]] + palettes = [] + for pal in xrange(num_pals): + palettes += [[]] - for color in xrange(num_colors): - i = pal * palette_length - i += color * color_length - word = pals[i] + pals[i+1] * 0x100 - palettes[pal] += [[ - c & 0x1f for c in [ - word >> 0, - word >> 5, - word >> 10, - ] - ]] - return palettes + for color in xrange(num_colors): + i = pal * palette_length + i += color * color_length + word = pals[i] + pals[i+1] * 0x100 + palettes[pal] += [[ + c & 0x1f for c in [ + word >> 0, + word >> 5, + word >> 10, + ] + ]] + return palettes def get_available_maps(): - for root, dirs, files in os.walk(map_dir): - for filename in files: - base_name, ext = os.path.splitext(filename) - if ext == '.blk': - yield base_name + for root, dirs, files in os.walk(map_dir): + for filename in files: + base_name, ext = os.path.splitext(filename) + if ext == '.blk': + yield base_name def map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() - label = name + '_MapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'dw', 'db' ] - attributes = [ - 'bank', - 'tileset_id', - 'permission', - 'second_map_header', - 'world_map_location', - 'music', - 'time_of_day', - 'fishing_group', - ] - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - return attrs - - elif version == 'red': - headers = open(header_path, 'r').read() - - # there has to be a better way to do this - lower_label = name + '_h' - i = headers.lower().find(lower_label) - if i == -1: - return {} - label = headers[i:i+len(lower_label)] - - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dw', 'db' ] - attributes = [ - 'tileset_id', - 'height', - 'width', - 'blockdata_label', - 'text_label', - 'script_label', - 'which_connections', - ] - values, l = read_header_macros(header, attributes, macros) - - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - - macros = [ 'dw' ] - attributes = [ - 'object_label', - ] - values, l = read_header_macros(header[l:], attributes, macros) - attrs.update(dict(zip(attributes, values))) - - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() + label = name + '_MapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'dw', 'db' ] + attributes = [ + 'bank', + 'tileset_id', + 'permission', + 'second_map_header', + 'world_map_location', + 'music', + 'time_of_day', + 'fishing_group', + ] + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + return attrs + + elif version == 'red': + headers = open(header_path, 'r').read() + + # there has to be a better way to do this + lower_label = name + '_h' + i = headers.lower().find(lower_label) + if i == -1: + return {} + label = headers[i:i+len(lower_label)] + + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dw', 'db' ] + attributes = [ + 'tileset_id', + 'height', + 'width', + 'blockdata_label', + 'text_label', + 'script_label', + 'which_connections', + ] + values, l = read_header_macros(header, attributes, macros) + + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + + macros = [ 'dw' ] + attributes = [ + 'object_label', + ] + values, l = read_header_macros(header[l:], attributes, macros) + attrs.update(dict(zip(attributes, values))) + + return attrs + + return {} def second_map_header(name): - if version == 'crystal': - headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() - label = name + '_SecondMapHeader' - header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] - attributes = [ - 'border_block', - 'height', - 'width', - 'blockdata_bank', - 'blockdata_label', - 'script_header_bank', - 'script_header_label', - 'map_event_header_label', - 'which_connections', - ] - - values, l = read_header_macros(header, attributes, macros) - attrs = dict(zip(attributes, values)) - attrs['connections'], l = connections(attrs['which_connections'], header, l) - return attrs - - return {} + if version == 'crystal': + headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() + label = name + '_SecondMapHeader' + header = asm_at_label(headers, label) + macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] + attributes = [ + 'border_block', + 'height', + 'width', + 'blockdata_bank', + 'blockdata_label', + 'script_header_bank', + 'script_header_label', + 'map_event_header_label', + 'which_connections', + ] + + values, l = read_header_macros(header, attributes, macros) + attrs = dict(zip(attributes, values)) + attrs['connections'], l = connections(attrs['which_connections'], header, l) + return attrs + + return {} def connections(which_connections, header, l=0): - directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } - macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] - - if version == 'crystal': - attributes = [ - 'map_group', - 'map_no', - ] - - elif version == 'red': - attributes = [ - 'map_id', - ] - - attributes += [ - 'strip_pointer', - 'strip_destination', - 'strip_length', - 'map_width', - 'y_offset', - 'x_offset', - 'window', - ] - for d in directions.keys(): - if d.upper() in which_connections: - values, l = read_header_macros(header, attributes, macros) - header = header[l:] - directions[d] = dict(zip(attributes, values)) - if version == 'crystal': - directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') - elif version == 'red': - directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') - return directions, l + directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } + macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] + + if version == 'crystal': + attributes = [ + 'map_group', + 'map_no', + ] + + elif version == 'red': + attributes = [ + 'map_id', + ] + + attributes += [ + 'strip_pointer', + 'strip_destination', + 'strip_length', + 'map_width', + 'y_offset', + 'x_offset', + 'window', + ] + for d in directions.keys(): + if d.upper() in which_connections: + values, l = read_header_macros(header, attributes, macros) + header = header[l:] + directions[d] = dict(zip(attributes, values)) + if version == 'crystal': + directions[d]['map_name'] = directions[d]['map_group'].replace('GROUP_', '').title().replace('_','') + elif version == 'red': + directions[d]['map_name'] = directions[d]['map_id'].title().replace('_','') + return directions, l def read_header_macros(header, attributes, macros): - values = [] - i = 0 - l = 0 - for l, (asm, comment) in enumerate(header): - if asm.strip() != '': - values += macro_values(asm, macros[i]) - i += 1 - if len(values) >= len(attributes): - l += 1 - break - return values, l + values = [] + i = 0 + l = 0 + for l, (asm, comment) in enumerate(header): + if asm.strip() != '': + values += macro_values(asm, macros[i]) + i += 1 + if len(values) >= len(attributes): + l += 1 + break + return values, l def event_header(asm, name): - return {} + return {} def script_header(asm, name): - return {} + return {} def macro_values(line, macro): - values = line[line.find(macro) + len(macro):].split(',') - return [v.replace('$','0x').strip() for v in values] + values = line[line.find(macro) + len(macro):].split(',') + return [v.replace('$','0x').strip() for v in values] def db_value(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) def db_values(line): - macro = 'db' - return macro_values(line, macro) + macro = 'db' + return macro_values(line, macro) from preprocessor import separate_comment def asm_at_label(asm, label): - label_def = label + ':' - start = asm.find(label_def) + len(label_def) - lines = asm[start:].split('\n') - # go until the next label - content = [] - for line in lines: - l, comment = separate_comment(line + '\n') - if ':' in l: - break - content += [[l, comment]] - return content + label_def = label + ':' + start = asm.find(label_def) + len(label_def) + lines = asm[start:].split('\n') + # go until the next label + content = [] + for line in lines: + l, comment = separate_comment(line + '\n') + if ':' in l: + break + content += [[l, comment]] + return content def main(): """ -- cgit v1.2.3 From 96574a08ee25c6118d4e1e5cb8ff334b118817f2 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 26 Sep 2013 02:26:09 -0400 Subject: map_editor: more flexible macro handling makes less assumptions about the structure of a header --- pokemontools/map_editor.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 9cd29ea..0a02cb0 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -473,7 +473,7 @@ def map_header(name): headers = open(os.path.join(header_dir, 'map_headers.asm'), 'r').read() label = name + '_MapHeader' header = asm_at_label(headers, label) - macros = [ 'db', 'dw', 'db' ] + macros = [ 'db', 'db', 'db', 'dw', 'db', 'db', 'db', 'db' ] attributes = [ 'bank', 'tileset_id', @@ -499,7 +499,7 @@ def map_header(name): label = headers[i:i+len(lower_label)] header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dw', 'db' ] + macros = [ 'db', 'db', 'db', 'dw', 'dw', 'dw', 'db' ] attributes = [ 'tileset_id', 'height', @@ -530,7 +530,7 @@ def second_map_header(name): headers = open(os.path.join(header_dir, 'second_map_headers.asm'), 'r').read() label = name + '_SecondMapHeader' header = asm_at_label(headers, label) - macros = [ 'db', 'db', 'dbw', 'dbw', 'dw', 'db' ] + macros = [ 'db', 'db', 'db', 'db', 'dw', 'db', 'dw', 'dw', 'db' ] attributes = [ 'border_block', 'height', @@ -552,19 +552,21 @@ def second_map_header(name): def connections(which_connections, header, l=0): directions = { 'north': {}, 'south': {}, 'west': {}, 'east': {} } - macros = [ 'db', 'dw', 'dw', 'db', 'db', 'dw' ] if version == 'crystal': + macros = [ 'db', 'db' ] attributes = [ 'map_group', 'map_no', ] elif version == 'red': + macros = [ 'db' ] attributes = [ 'map_id', ] + macros += [ 'dw', 'dw', 'db', 'db', 'db', 'db', 'dw' ] attributes += [ 'strip_pointer', 'strip_destination', @@ -591,8 +593,9 @@ def read_header_macros(header, attributes, macros): l = 0 for l, (asm, comment) in enumerate(header): if asm.strip() != '': - values += macro_values(asm, macros[i]) - i += 1 + mvalues = macro_values(asm, macros[i]) + values += mvalues + i += len(mvalues) if len(values) >= len(attributes): l += 1 break @@ -608,7 +611,10 @@ def script_header(asm, name): def macro_values(line, macro): values = line[line.find(macro) + len(macro):].split(',') - return [v.replace('$','0x').strip() for v in values] + values = [v.replace('$','0x').strip() for v in values] + if values[0] == 'w': # dbw + values = values[1:] + return values def db_value(line): macro = 'db' @@ -623,8 +629,12 @@ from preprocessor import separate_comment def asm_at_label(asm, label): label_def = label + ':' - start = asm.find(label_def) + len(label_def) - lines = asm[start:].split('\n') + lines = asm.split('\n') + for line in lines: + if line.startswith(label_def): + lines = lines[lines.index(line):] + lines[0] = lines[0][len(label_def):] + break # go until the next label content = [] for line in lines: -- cgit v1.2.3 From 92eeee28dd0633655d331e658cdd3a5c6b5c6fab Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 26 Sep 2013 02:49:37 -0400 Subject: map_editor: skip any nonexistent tiles in map rendering --- pokemontools/map_editor.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 0a02cb0..efd9678 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -322,12 +322,15 @@ class Map: # Draw one block (4x4 tiles) block = self.blockdata[block_y * self.width + block_x] for j, tile in enumerate(self.tileset.blocks[block]): - # Tile gfx are split in half to make vram mapping easier - if tile >= 0x80: - tile -= 0x20 - tile_x = block_x * 32 + (j % 4) * 8 - tile_y = block_y * 32 + (j / 4) * 8 - self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + try: + # Tile gfx are split in half to make vram mapping easier + if tile >= 0x80: + tile -= 0x20 + tile_x = block_x * 32 + (j % 4) * 8 + tile_y = block_y * 32 + (j / 4) * 8 + self.canvas.create_image(index + tile_x, indey + tile_y, image=self.tileset.tiles[tile]) + except: + pass class Tileset: -- cgit v1.2.3 From 001b6bc850b25f6cbd4d7a7cfa540c62cada88f6 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 26 Sep 2013 02:52:45 -0400 Subject: map_editor: create any pngs that don't exist yet --- pokemontools/map_editor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index efd9678..4970d9e 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -372,6 +372,9 @@ class Tileset: def get_tiles(self): filename = self.get_tileset_gfx_filename() + if not os.path.exists(filename): + import gfx + gfx.to_png(filename.replace('.png','.2bpp'), filename) self.img = Image.open(filename) self.img.width, self.img.height = self.img.size self.tiles = [] -- cgit v1.2.3 From 3496b81eb3b5af6fa7d694cbac1610b5ff2c85f2 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 26 Sep 2013 03:13:15 -0400 Subject: crystal.py: no more relative paths --- pokemontools/crystal.py | 53 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 8a12dad..089f694 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -61,6 +61,9 @@ import item_constants import wram import exceptions +import config +conf = config.Config() + from map_names import map_names # ---- script_parse_table explanation ---- @@ -106,17 +109,21 @@ def map_name_cleaner(input): rom = romstr.RomStr(None) -def direct_load_rom(filename="../baserom.gbc"): +def direct_load_rom(filename=None): """loads bytes into memory""" + if filename == None: + filename = os.path.join(conf.path, "baserom.gbc") global rom file_handler = open(filename, "rb") rom = romstr.RomStr(file_handler.read()) file_handler.close() return rom -def load_rom(filename="../baserom.gbc"): +def load_rom(filename=None): """checks that the loaded rom matches the path and then loads the rom if necessary.""" + if filename == None: + filename = os.path.join(conf.path, "baserom.gbc") global rom if rom != romstr.RomStr(None) and rom != None: return rom @@ -125,14 +132,18 @@ def load_rom(filename="../baserom.gbc"): elif os.lstat(filename).st_size != len(rom): return direct_load_rom(filename) -def direct_load_asm(filename="../main.asm"): +def direct_load_asm(filename=None): + if filename == None: + filename = os.path.join(conf.path, "main.asm") """returns asm source code (AsmList) from a file""" asm = open(filename, "r").read().split("\n") asm = romstr.AsmList(asm) return asm -def load_asm(filename="../main.asm"): +def load_asm(filename=None): """returns asm source code (AsmList) from a file (uses a global)""" + if filename == None: + filename = os.path.join(conf.path, "main.asm") global asm asm = direct_load_asm(filename=filename) return asm @@ -3185,7 +3196,9 @@ effect_classes = create_effect_command_classes() -def generate_macros(filename="../script_macros.asm"): +def generate_macros(filename=None): + if filename == None: + filename = os.path.join(conf.path, "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" @@ -5985,7 +5998,7 @@ def old_parse_second_map_header_at(address, map_group=None, map_id=None, debug=T class MapBlockData: base_label = "MapBlockData_" - maps_path = os.path.realpath(os.path.join(os.path.realpath("."), "../maps")) + maps_path = os.path.realpath(os.path.join(conf.path, "maps")) def __init__(self, address, map_group=None, map_id=None, debug=True, bank=None, label=None, width=None, height=None): self.address = address @@ -6887,9 +6900,11 @@ def reset_incbins(): isolate_incbins(asm=asm) process_incbins() -def find_incbin_to_replace_for(address, debug=False, rom_file="../baserom.gbc"): +def find_incbin_to_replace_for(address, debug=False, rom_file=None): """returns a line number for which incbin to edit if you were to insert bytes into main.asm""" + if rom_file == None: + rom_file = os.path.join(conf.path, "baserom.gbc") 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") @@ -6917,7 +6932,7 @@ def find_incbin_to_replace_for(address, debug=False, rom_file="../baserom.gbc"): return incbin_key return None -def split_incbin_line_into_three(line, start_address, byte_count, rom_file="../baserom.gbc"): +def split_incbin_line_into_three(line, start_address, byte_count, rom_file=None): """ splits an incbin line into three pieces. you can replace the middle one with the new content of length bytecount @@ -6925,6 +6940,8 @@ def split_incbin_line_into_three(line, start_address, byte_count, rom_file="../b start_address: where you want to start inserting bytes byte_count: how many bytes you will be inserting """ + if rom_file == None: + rom_file = os.path.join(conf.path, "baserom.gbc") 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") @@ -6984,9 +7001,9 @@ def generate_diff_insert(line_number, newline, debug=False): CalledProcessError = None try: - diffcontent = subprocess.check_output("diff -u ../main.asm " + newfile_filename, shell=True) + diffcontent = subprocess.check_output("diff -u " + os.path.join(conf.path, "main.asm") + " " + newfile_filename, shell=True) except (AttributeError, CalledProcessError): - p = subprocess.Popen(["diff", "-u", "../main.asm", newfile_filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen(["diff", "-u", os.path.join(conf.path, "main.asm"), newfile_filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() diffcontent = out @@ -7006,8 +7023,8 @@ def apply_diff(diff, try_fixing=True, do_compile=True): fh.close() # apply the patch - os.system("cp ../main.asm ../main1.asm") - os.system("patch ../main.asm temp.patch") + os.system("cp " + os.path.join(conf.path, "main.asm") + " " + os.path.join(conf.path, "main1.asm")) + os.system("patch " + os.path.join(conf.path, "main.asm") + " " + "temp.patch") # remove the patch os.system("rm temp.patch") @@ -7015,11 +7032,11 @@ def apply_diff(diff, try_fixing=True, do_compile=True): # confirm it's working if do_compile: try: - subprocess.check_call("cd ../; make clean; make", shell=True) + subprocess.check_call("cd " + conf.path + "; make clean; make", shell=True) return True except Exception, exc: if try_fixing: - os.system("mv ../main1.asm ../main.asm") + os.system("mv " + os.path.join(conf.path, "main1.asm") + " " + os.path.join(conf.path, "main.asm")) return False class AsmLine: @@ -7151,8 +7168,10 @@ class AsmSection: return self.line new_asm = None -def load_asm2(filename="../main.asm", force=False): +def load_asm2(filename=None, force=False): """loads the asm source code into memory""" + if filename == None: + filename = os.path.join(conf.path, "main.asm") global new_asm if new_asm == None or force: new_asm = Asm(filename=filename) @@ -7160,7 +7179,9 @@ def load_asm2(filename="../main.asm", force=False): class Asm: """controls the overall asm output""" - def __init__(self, filename="../main.asm", debug=True): + def __init__(self, filename=None, debug=True): + if filename == None: + filename = os.path.join(conf.path, "main.asm") self.parts = [] self.labels = [] self.filename = filename -- cgit v1.2.3 From 97fdec37ec9d1319f914b648d44dee385e03148b Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 17:17:18 -0500 Subject: fix crystal.py configuration Ugh, it's still a global? --- pokemontools/crystal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 7acb160..be02576 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -67,8 +67,8 @@ is_valid_address = addresses.is_valid_address import old_text_script OldTextScript = old_text_script -import config -conf = config.Config() +import configiguration +conf = configuration.Config() from map_names import map_names -- cgit v1.2.3 From f7b6808797afd31812b3168ca41c640bd8005e67 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Thu, 26 Sep 2013 17:18:46 -0500 Subject: fix configuration typo --- pokemontools/crystal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index be02576..7bd8a30 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -67,7 +67,7 @@ is_valid_address = addresses.is_valid_address import old_text_script OldTextScript = old_text_script -import configiguration +import configuration conf = configuration.Config() from map_names import map_names -- cgit v1.2.3 From 1b56d95aa0f39146a37043a36cf19f46b00f68c3 Mon Sep 17 00:00:00 2001 From: "U-Fish-PC\\Daniel" Date: Sun, 13 Oct 2013 10:49:07 -0400 Subject: Add pokered music commands --- pokemontools/crystal.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 45eb306..5d602c9 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -2453,7 +2453,22 @@ def create_music_command_classes(debug=False): return klasses music_classes = create_music_command_classes() - +class callchannel(Command): + id = 0xFD + macro_name = "callchannel" + size = 3 + param_types = { + 0: {"name": "address", "class": PointerLabelParam}, + } + +class loopchannel(Command): + id = 0xFE + macro_name = "loopchannel" + size = 4 + param_types = { + 0: {"name": "count", "class": SingleByteParam}, + 1: {"name": "address", "class": PointerLabelParam}, + } effect_commands = { 0x1: ['checkturn'], -- cgit v1.2.3 From c57e0f0706608a4acba89182945bec975a43acfd Mon Sep 17 00:00:00 2001 From: "U-Fish-PC\\Daniel" Date: Sun, 13 Oct 2013 10:50:09 -0400 Subject: Add pokered music tools --- pokemontools/redmusicdisasm.py | 306 +++++++++++++++++++++++++++++++++++++++++ pokemontools/redsfxdisasm.py | 149 ++++++++++++++++++++ pokemontools/redsfxheaders.py | 43 ++++++ 3 files changed, 498 insertions(+) create mode 100755 pokemontools/redmusicdisasm.py create mode 100755 pokemontools/redsfxdisasm.py create mode 100755 pokemontools/redsfxheaders.py diff --git a/pokemontools/redmusicdisasm.py b/pokemontools/redmusicdisasm.py new file mode 100755 index 0000000..ad370cd --- /dev/null +++ b/pokemontools/redmusicdisasm.py @@ -0,0 +1,306 @@ +import config +config = config.Config() +rom = bytearray(open(config.rom_path, "r").read()) + +songs = [ + "PalletTown", + "Pokecenter", + "Gym", + "Cities1", + "Cities2", + "Celadon", + "Cinnabar", + "Vermilion", + "Lavender", + "SSAnne", + "MeetProfOak", + "MeetRival", + "MuseumGuy", + "SafariZone", + "PkmnHealed", + "Routes1", + "Routes2", + "Routes3", + "Routes4", + "IndigoPlateau", + "GymLeaderBattle", + "TrainerBattle", + "WildBattle", + "FinalBattle", + "DefeatedTrainer", + "DefeatedWildMon", + "DefeatedGymLeader", + "TitleScreen", + "Credits", + "HallOfFame", + "OaksLab", + "JigglypuffSong", + "BikeRiding", + "Surfing", + "GameCorner", + "IntroBattle", + "Dungeon1", + "Dungeon2", + "Dungeon3", + "CinnabarMansion", + "PokemonTower", + "SilphCo", + "MeetEvilTrainer", + "MeetFemaleTrainer", + "MeetMaleTrainer", + "UnusedSong", + #"SurfingPikachu", + #"MeetJessieJames", + #"YellowUnusedSong", + ] + +music_commands = { + 0xd0: ["notetype", {"type": "nibble"}, 2], + 0xe0: ["octave", 1], + 0xe8: ["unknownmusic0xe8", 1], + 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3], + 0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3], + 0xec: ["duty", {"type": "byte"}, 2], + 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3], + 0xee: ["unknownmusic0xee", {"type": "byte"}, 2], + 0xf0: ["stereopanning", {"type": "byte"}, 2], + 0xf8: ["unknownmusic0xf8", 1], + 0xfc: ["dutycycle", {"type": "byte"}, 2], + 0xfd: ["callchannel", {"type": "label"}, 3], + 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4], + 0xff: ["endchannel", 1], + } + +param_lengths = { + "nibble": 1, + "byte": 1, + "label": 2, + } + +music_notes = { + 0x0: "C_", + 0x1: "C#", + 0x2: "D_", + 0x3: "D#", + 0x4: "E_", + 0x5: "F_", + 0x6: "F#", + 0x7: "G_", + 0x8: "G#", + 0x9: "A_", + 0xa: "A#", + 0xb: "B_", + } + +def printnoisechannel(songname, songfile, startingaddress, bank, output): + noise_commands = { + 0xfd: ["callchannel", {"type": "label"}, 3], + 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4], + 0xff: ["endchannel", 1], + } + + noise_instruments = { + 0x01: "snare1", + 0x02: "snare2", + 0x03: "snare3", + 0x04: "snare4", + 0x05: "snare5", + 0x06: "triangle1", + 0x07: "triangle2", + 0x08: "snare6", + 0x09: "snare7", + 0x0a: "snare8", + 0x0b: "snare9", + 0x0c: "cymbal1", + 0x0d: "cymbal2", + 0x0e: "cymbal3", + 0x0f: "mutedsnare1", + 0x10: "triangle3", + 0x11: "mutedsnare2", + 0x12: "mutedsnare3", + 0x13: "mutedsnare4", + } + + # pass 1, build a list of all addresses pointed to by calls and loops + address = startingaddress + labels = [] + labelsleft= [] + while 1: + byte = rom[address] + if byte < 0xc0: + command_length = 2 + elif byte < 0xe0: + command_length = 1 + else: + command_length = noise_commands[byte][-1] + if byte == 0xfd or byte == 0xfe: + label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2] + labels.append(label) + if label > address % 0x4000 + 0x4000: labelsleft.append(label) + address += command_length + if len(labelsleft) == 0 and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break + while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000) + # once the loop ends, start over from first address + if rom[address] == 0xff: address += 1 + end = address + address = startingaddress + byte = rom[address] + output += "Music_{}_Ch4: ; {:02x} ({:0x}:{:02x})\n".format(songname, address, bank, address % 0x4000 + 0x4000) + # pass 2, print commands and labels for addresses that are in labels + while address != end: + if address % 0x4000 + 0x4000 in labels and address != startingaddress: + output += "\nMusic_{}_branch_{:02x}:\n".format(songname, address) + if byte < 0xc0: + output += "\tdnote {}, {}".format(byte % 0x10 + 1, noise_instruments[rom[address + 1]]) + command_length = 2 + elif byte < 0xd0: + output += "\trest {}".format(byte % 0x10 + 1) + command_length = 1 + elif byte < 0xe0: + output += "\tdspeed {}".format(byte % 0x10) + command_length = 1 + else: + command = noise_commands[byte] + output += "\t{}".format(command[0]) + command_length = 1 + params = 1 + # print all params for current command + while params != len(noise_commands[byte]) - 1: + param_type = noise_commands[byte][params]["type"] + address += command_length + command_length = param_lengths[param_type] + param = rom[address] + if param_type == "byte": + output += " {}".format(param) + else: + param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000) + if param == startingaddress: output += " Music_{}_Ch4".format(songname) + else: output += " Music_{}_branch_{:02x}".format(songname, param) + params += 1 + if params != len(noise_commands[byte]) - 1: output += "," + output += "\n" + address += command_length + byte = rom[address] + output += "; {}".format(hex(address)) + songfile.write(output) + +for i, songname in enumerate(songs): + songfile = open("music/" + songname.lower() + ".asm", 'a') + if songname == "PalletTown": header = 0x822e + if songname == "GymLeaderBattle": header = 0x202be + if songname == "TitleScreen": header = 0x7c249 + if songname == "SurfingPikachu": header = 0x801cb + bank = header / 0x4000 + startingaddress = rom[header + 2] * 0x100 + rom[header + 1] - 0x4000 + (0x4000 * bank) + curchannel = 1 + lastchannel = (rom[header] >> 6) + 1 + exception = False + if songname == "MeetRival" or songname == "Cities1": + startingaddress -= 7 + exception = True + if songname == "UnusedSong": + bank = 2 + startingaddress = 0xa913 + lastchannel = 2 + output = '' + while 1: + # pass 1, build a list of all addresses pointed to by calls and loops + address = startingaddress + labels = [] + labelsleft = [] + if songname == "MeetRival": + if curchannel == 1: + labels.append(0x719b) + labelsleft.append(0x719b) + if curchannel == 2: + labels.append(0x721d) + labelsleft.append(0x721d) + if curchannel == 3: + labels.append(0x72b5) + labelsleft.append(0x72b5) + while 1: + byte = rom[address] + if byte < 0xd0: + command_length = 1 + elif byte < 0xe0: + command_length = 2 + elif byte < 0xe8: + command_length = 1 + else: + command_length = music_commands[byte][-1] + if byte == 0xfd or byte == 0xfe: + label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2] + labels.append(label) + if label > address % 0x4000 + 0x4000: labelsleft.append(label) + address += command_length + if len(labelsleft) == 0 and (exception == False or address > startingaddress + 7) and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break + while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000) + # once the loop breaks, start over from first address + if rom[address] == 0xff: address += 1 + end = address + if curchannel != lastchannel and songname != "UnusedSong": end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1)) + address = startingaddress + byte = rom[address] + # if song has an alternate start to channel 1, print a label and set startingaddress to true channel start + if exception: + output += "Music_{}_branch_{:02x}:\n".format(songname, address) + startingaddress += 7 + # pass 2, print commands and labels for addresses that are in labels + while address != end: + if address == startingaddress: + if exception: output += "\n" + output += "Music_{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(songname, curchannel, address, bank, address % 0x4000 + 0x4000) + elif address % 0x4000 + 0x4000 in labels: + output += "\nMusic_{}_branch_{:02x}:\n".format(songname, address) + if byte < 0xc0: + output += "\tnote {}, {}".format(music_notes[byte >> 4], byte % 0x10 + 1) + command_length = 1 + elif byte < 0xd0: + output += "\trest {}".format(byte % 0x10 + 1) + command_length = 1 + else: + if byte < 0xe0: + command = music_commands[0xd0] + output += "\t{} {},".format(command[0], byte % 0x10) + byte = 0xd0 + elif byte < 0xe8: + command = music_commands[0xe0] + output += "\t{} {}".format(command[0], 0xe8 - byte) + byte = 0xe0 + else: + command = music_commands[byte] + output += "\t{}".format(command[0]) + command_length = 1 + params = 1 + # print all params for current command + while params != len(music_commands[byte]) - 1: + param_type = music_commands[byte][params]["type"] + address += command_length + command_length = param_lengths[param_type] + param = rom[address] + if param_type == "nibble": + output += " {}, {}".format(param >> 4, param % 0x10) + elif param_type == "byte": + output += " {}".format(param) + else: + param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000) + if param == startingaddress: output += " Music_{}_Ch{}".format(songname, curchannel) + else: output += " Music_{}_branch_{:02x}".format(songname, param) + params += 1 + if params != len(music_commands[byte]) - 1: output += "," + output += "\n" + address += command_length + byte = rom[address] + header += 3 + if curchannel == lastchannel: + output += "; {}".format(hex(address)) + songfile.write(output) + break + curchannel += 1 + output += "\n\n" + startingaddress = end + exception = False + if curchannel == 4: + printnoisechannel(songname, songfile, startingaddress, bank, output) + header += 3 + break \ No newline at end of file diff --git a/pokemontools/redsfxdisasm.py b/pokemontools/redsfxdisasm.py new file mode 100755 index 0000000..b84becb --- /dev/null +++ b/pokemontools/redsfxdisasm.py @@ -0,0 +1,149 @@ +import config +config = config.Config() +rom = bytearray(open(config.rom_path, "r").read()) + +banks = { + 0x02: 0x60, + 0x08: 0x78, + 0x1f: 0x68, + } + +music_commands = { + 0xd0: ["notetype", {"type": "nibble"}, 2], + 0xe0: ["octave", 1], + 0xe8: ["unknownmusic0xe8", 1], + 0xe9: ["unknownmusic0xe9", 1], + 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3], + 0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3], + 0xec: ["duty", {"type": "byte"}, 2], + 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3], + 0xee: ["unknownmusic0xee", {"type": "byte"}, 2], + 0xef: ["unknownmusic0xef", 1], + 0xf0: ["stereopanning", {"type": "byte"}, 2], + 0xf1: ["unknownmusic0xf1", 1], + 0xf2: ["unknownmusic0xf2", 1], + 0xf3: ["unknownmusic0xf3", 1], + 0xf4: ["unknownmusic0xf4", 1], + 0xf5: ["unknownmusic0xf5", 1], + 0xf6: ["unknownmusic0xf6", 1], + 0xf7: ["unknownmusic0xf7", 1], + 0xf8: ["unknownmusic0xf8", 1], + 0xf9: ["unknownmusic0xf9", 1], + 0xfa: ["unknownmusic0xfa", 1], + #0xfb: ["unknownmusic0xfb", 1], + 0xfc: ["dutycycle", {"type": "byte"}, 2], + 0xfd: ["callchannel", {"type": "label"}, 3], + 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4], + 0xff: ["endchannel", 1], + } + +param_lengths = { + "nibble": 1, + "byte": 1, + "label": 2, + } + +music_notes = { + 0x0: "C_", + 0x1: "C#", + 0x2: "D_", + 0x3: "D#", + 0x4: "E_", + 0x5: "F_", + 0x6: "F#", + 0x7: "G_", + 0x8: "G#", + 0x9: "A_", + 0xa: "A#", + 0xb: "B_", + } + +for bank in banks: + header = bank * 0x4000 + 3 + for sfx in range(1,banks[bank]): + sfxname = "SFX_{:02x}_{:02x}".format(bank, sfx) + sfxfile = open("music/sfx/" + sfxname.lower() + ".asm", 'a') + startingaddress = rom[header + 2] * 0x100 + rom[header + 1] + (0x4000 * (bank - 1)) + curchannel = 1 + lastchannel = (rom[header] >> 6) + 1 + output = '' + while 1: + # pass 1, build a list of all addresses pointed to by calls and loops + address = startingaddress + labels = [] + labelsleft = [] + while 1: + byte = rom[address] + if byte < 0xd0: + command_length = 1 + elif byte < 0xe0: + command_length = 2 + elif byte < 0xe8: + command_length = 1 + else: + command_length = music_commands[byte][-1] + if byte == 0xfd or byte == 0xfe: + label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2] + labels.append(label) + if label > address % 0x4000 + 0x4000: labelsleft.append(label) + address += command_length + if len(labelsleft) == 0 and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break + while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000) + # once the loop breaks, start over from first address + end = address + if curchannel != lastchannel: end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1)) + address = startingaddress + byte = rom[address] + # pass 2, print commands and labels for addresses that are in labels + while address != end: + if address == startingaddress: + output += "{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(sfxname, curchannel, address, bank, address % 0x4000 + 0x4000) + elif address % 0x4000 + 0x4000 in labels: + output += "\n{}_branch_{:02x}:\n".format(sfxname, address) + if byte < 0xc0: + output += "\tnote {}, {}".format(music_notes[byte >> 4], byte % 0x10 + 1) + command_length = 1 + elif byte < 0xd0: + output += "\trest {}".format(byte % 0x10 + 1) + command_length = 1 + else: + if byte < 0xe0: + command = music_commands[0xd0] + output += "\t{} {},".format(command[0], byte % 0x10) + byte = 0xd0 + elif byte < 0xe8: + command = music_commands[0xe0] + output += "\t{} {}".format(command[0], 0xe8 - byte) + byte = 0xe0 + else: + command = music_commands[byte] + output += "\t{}".format(command[0]) + command_length = 1 + params = 1 + # print all params for current command + while params != len(music_commands[byte]) - 1: + param_type = music_commands[byte][params]["type"] + address += command_length + command_length = param_lengths[param_type] + param = rom[address] + if param_type == "nibble": + output += " {}, {}".format(param >> 4, param % 0x10) + elif param_type == "byte": + output += " {}".format(param) + else: + param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000) + if param == startingaddress: output += " {}_Ch{}".format(sfxname, curchannel) + else: output += " {}_branch_{:02x}".format(sfxname, param) + params += 1 + if params != len(music_commands[byte]) - 1: output += "," + output += "\n" + address += command_length + byte = rom[address] + header += 3 + if curchannel == lastchannel: + output += "; {}".format(hex(address)) + sfxfile.write(output) + break + output += "\n\n" + startingaddress = end + curchannel += 1 \ No newline at end of file diff --git a/pokemontools/redsfxheaders.py b/pokemontools/redsfxheaders.py new file mode 100755 index 0000000..7b04701 --- /dev/null +++ b/pokemontools/redsfxheaders.py @@ -0,0 +1,43 @@ +import config +config = config.Config() +rom = bytearray(open(config.rom_path, "r").read()) + +headerlist = ( + ["sfxheaders02.asm", 0x8003, 0x822e], + ["sfxheaders08.asm", 0x20003, 0x202be], + ["sfxheaders1f.asm", 0x7c003, 0x7c249], + ) + +numberofchannels = { + 0x0: 1, + 0x4: 2, + 0x8: 3, + 0xC: 4, + } + +def printsfxheaders(filename, address, end): + file = open(filename, 'w') + bank = address / 0x4000 + byte = rom[address] + sfx = 1 + channel = 1 + file.write("SFX_Headers_{:02x}:\n".format(bank)) + file.write("\tdb $ff, $ff, $ff ; padding\n") + while address != end: + left = numberofchannels[byte >> 4] + file.write("\nSFX_{:02x}_{:02x}: ; {:02x} ({:0x}:{:02x})\n".format(bank, sfx, address, bank, address % 0x4000 + 0x4000)) + while left != 0: + pointer = rom[address + 2] * 0x100 + rom[address + 1] + if byte >> 4 != 0: file.write(" db ( ${:0x}0 | CH{:0x} )\n".format(byte >> 4, byte % 0x10)) + else: file.write("\tdb CH{:0x}\n".format(byte)) + file.write("\tdw SFX_{:02x}_{:02x}_Ch{}\n".format(bank, sfx, channel)) + address += 3 + byte = rom[address] + channel += 1 + left -= 1 + channel = 1 + sfx += 1 + file.write("\n; {}".format(hex(address))) + +for header in headerlist: + printsfxheaders(header[0], header[1], header[2]) \ No newline at end of file -- cgit v1.2.3 From ed174d243287d8c1fdae31d0b30e014d554c0241 Mon Sep 17 00:00:00 2001 From: "U-Fish-PC\\Daniel" Date: Tue, 22 Oct 2013 02:24:37 -0400 Subject: Update redmusicdisasm and redsfxdisasm --- pokemontools/redmusicdisasm.py | 23 +++++++----- pokemontools/redsfxdisasm.py | 80 ++++++++++++++++-------------------------- 2 files changed, 45 insertions(+), 58 deletions(-) diff --git a/pokemontools/redmusicdisasm.py b/pokemontools/redmusicdisasm.py index ad370cd..3ed5a4d 100755 --- a/pokemontools/redmusicdisasm.py +++ b/pokemontools/redmusicdisasm.py @@ -1,5 +1,5 @@ -import config -config = config.Config() +import configuration +config = configuration.Config() rom = bytearray(open(config.rom_path, "r").read()) songs = [ @@ -49,22 +49,26 @@ songs = [ "MeetFemaleTrainer", "MeetMaleTrainer", "UnusedSong", - #"SurfingPikachu", - #"MeetJessieJames", - #"YellowUnusedSong", ] - +""" +songs = [ + "YellowIntro", + "SurfingPikachu", + "MeetJessieJames", + "YellowUnusedSong", + ] +""" music_commands = { 0xd0: ["notetype", {"type": "nibble"}, 2], 0xe0: ["octave", 1], - 0xe8: ["unknownmusic0xe8", 1], + 0xe8: ["togglecall", 1], 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3], 0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3], 0xec: ["duty", {"type": "byte"}, 2], 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3], 0xee: ["unknownmusic0xee", {"type": "byte"}, 2], 0xf0: ["stereopanning", {"type": "byte"}, 2], - 0xf8: ["unknownmusic0xf8", 1], + 0xf8: ["executemusic", 1], 0xfc: ["dutycycle", {"type": "byte"}, 2], 0xfd: ["callchannel", {"type": "label"}, 3], 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4], @@ -189,6 +193,7 @@ for i, songname in enumerate(songs): if songname == "PalletTown": header = 0x822e if songname == "GymLeaderBattle": header = 0x202be if songname == "TitleScreen": header = 0x7c249 + if songname == "YellowIntro": header = 0x7c294 if songname == "SurfingPikachu": header = 0x801cb bank = header / 0x4000 startingaddress = rom[header + 2] * 0x100 + rom[header + 1] - 0x4000 + (0x4000 * bank) @@ -212,6 +217,8 @@ for i, songname in enumerate(songs): if curchannel == 1: labels.append(0x719b) labelsleft.append(0x719b) + labels.append(0x71a2) + labelsleft.append(0x71a2) if curchannel == 2: labels.append(0x721d) labelsleft.append(0x721d) diff --git a/pokemontools/redsfxdisasm.py b/pokemontools/redsfxdisasm.py index b84becb..3f145cf 100755 --- a/pokemontools/redsfxdisasm.py +++ b/pokemontools/redsfxdisasm.py @@ -1,5 +1,5 @@ -import config -config = config.Config() +import configuration +config = configuration.Config() rom = bytearray(open(config.rom_path, "r").read()) banks = { @@ -11,28 +11,13 @@ banks = { music_commands = { 0xd0: ["notetype", {"type": "nibble"}, 2], 0xe0: ["octave", 1], - 0xe8: ["unknownmusic0xe8", 1], - 0xe9: ["unknownmusic0xe9", 1], + 0xe8: ["togglecall", 1], 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3], - 0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3], 0xec: ["duty", {"type": "byte"}, 2], 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3], - 0xee: ["unknownmusic0xee", {"type": "byte"}, 2], - 0xef: ["unknownmusic0xef", 1], 0xf0: ["stereopanning", {"type": "byte"}, 2], - 0xf1: ["unknownmusic0xf1", 1], - 0xf2: ["unknownmusic0xf2", 1], - 0xf3: ["unknownmusic0xf3", 1], - 0xf4: ["unknownmusic0xf4", 1], - 0xf5: ["unknownmusic0xf5", 1], - 0xf6: ["unknownmusic0xf6", 1], - 0xf7: ["unknownmusic0xf7", 1], - 0xf8: ["unknownmusic0xf8", 1], - 0xf9: ["unknownmusic0xf9", 1], - 0xfa: ["unknownmusic0xfa", 1], - #0xfb: ["unknownmusic0xfb", 1], + 0xf8: ["executemusic", 1], 0xfc: ["dutycycle", {"type": "byte"}, 2], - 0xfd: ["callchannel", {"type": "label"}, 3], 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4], 0xff: ["endchannel", 1], } @@ -62,45 +47,38 @@ for bank in banks: header = bank * 0x4000 + 3 for sfx in range(1,banks[bank]): sfxname = "SFX_{:02x}_{:02x}".format(bank, sfx) - sfxfile = open("music/sfx/" + sfxname.lower() + ".asm", 'a') + sfxfile = open("music/sfx/" + sfxname.lower() + ".asm", 'w') startingaddress = rom[header + 2] * 0x100 + rom[header + 1] + (0x4000 * (bank - 1)) + end = 0 curchannel = 1 lastchannel = (rom[header] >> 6) + 1 + channelnumber = rom[header] % 0x10 output = '' while 1: - # pass 1, build a list of all addresses pointed to by calls and loops address = startingaddress - labels = [] - labelsleft = [] + if curchannel != lastchannel: + end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1)) + byte = rom[address] + if byte == 0xf8 or (bank == 2 and sfx == 0x5e): executemusic = True + else: executemusic = False + output += "{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(sfxname, curchannel, address, bank, address % 0x4000 + 0x4000) while 1: - byte = rom[address] - if byte < 0xd0: + if address == 0x2062a or address == 0x2063d or address == 0x20930: + output += "\n{}_branch_{:02x}:\n".format(sfxname, address) + if byte < 0x10 and not executemusic: + output += "\tunknownsfx0x{:02x}".format(byte) command_length = 1 - elif byte < 0xe0: + elif byte == 0x10 and not executemusic: + output += "\tunknownsfx0x{:02x} {}".format(byte, rom[address + 1]) command_length = 2 - elif byte < 0xe8: - command_length = 1 - else: - command_length = music_commands[byte][-1] - if byte == 0xfd or byte == 0xfe: - label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2] - labels.append(label) - if label > address % 0x4000 + 0x4000: labelsleft.append(label) - address += command_length - if len(labelsleft) == 0 and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break - while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000) - # once the loop breaks, start over from first address - end = address - if curchannel != lastchannel: end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1)) - address = startingaddress - byte = rom[address] - # pass 2, print commands and labels for addresses that are in labels - while address != end: - if address == startingaddress: - output += "{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(sfxname, curchannel, address, bank, address % 0x4000 + 0x4000) - elif address % 0x4000 + 0x4000 in labels: - output += "\n{}_branch_{:02x}:\n".format(sfxname, address) - if byte < 0xc0: + elif byte < 0x30 and not executemusic: + if channelnumber == 7: + output += "\tunknownnoise0x20 {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2]) + command_length = 3 + else: + output += "\tunknownsfx0x20 {}, {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2], rom[address + 3]) + command_length = 4 + elif byte < 0xc0: output += "\tnote {}, {}".format(music_notes[byte >> 4], byte % 0x10 + 1) command_length = 1 elif byte < 0xd0: @@ -138,12 +116,14 @@ for bank in banks: if params != len(music_commands[byte]) - 1: output += "," output += "\n" address += command_length + if byte == 0xff or address == end: break byte = rom[address] header += 3 + channelnumber = rom[header] if curchannel == lastchannel: output += "; {}".format(hex(address)) sfxfile.write(output) break output += "\n\n" - startingaddress = end + startingaddress = address curchannel += 1 \ No newline at end of file -- cgit v1.2.3 From 71204998edd39b6bdb531b66294ede643dd452fe Mon Sep 17 00:00:00 2001 From: "U-Fish-PC\\Daniel" Date: Fri, 1 Nov 2013 10:25:40 -0400 Subject: Update red music tools --- pokemontools/redsfxdisasm.py | 5 +---- pokemontools/redsfxheaders.py | 13 +++---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/pokemontools/redsfxdisasm.py b/pokemontools/redsfxdisasm.py index 3f145cf..9e9f01b 100755 --- a/pokemontools/redsfxdisasm.py +++ b/pokemontools/redsfxdisasm.py @@ -65,10 +65,7 @@ for bank in banks: while 1: if address == 0x2062a or address == 0x2063d or address == 0x20930: output += "\n{}_branch_{:02x}:\n".format(sfxname, address) - if byte < 0x10 and not executemusic: - output += "\tunknownsfx0x{:02x}".format(byte) - command_length = 1 - elif byte == 0x10 and not executemusic: + if byte == 0x10 and not executemusic: output += "\tunknownsfx0x{:02x} {}".format(byte, rom[address + 1]) command_length = 2 elif byte < 0x30 and not executemusic: diff --git a/pokemontools/redsfxheaders.py b/pokemontools/redsfxheaders.py index 7b04701..c854c20 100755 --- a/pokemontools/redsfxheaders.py +++ b/pokemontools/redsfxheaders.py @@ -1,5 +1,5 @@ -import config -config = config.Config() +import configuration +config = configuration.Config() rom = bytearray(open(config.rom_path, "r").read()) headerlist = ( @@ -8,13 +8,6 @@ headerlist = ( ["sfxheaders1f.asm", 0x7c003, 0x7c249], ) -numberofchannels = { - 0x0: 1, - 0x4: 2, - 0x8: 3, - 0xC: 4, - } - def printsfxheaders(filename, address, end): file = open(filename, 'w') bank = address / 0x4000 @@ -24,7 +17,7 @@ def printsfxheaders(filename, address, end): file.write("SFX_Headers_{:02x}:\n".format(bank)) file.write("\tdb $ff, $ff, $ff ; padding\n") while address != end: - left = numberofchannels[byte >> 4] + left = (byte >> 6) + 1 file.write("\nSFX_{:02x}_{:02x}: ; {:02x} ({:0x}:{:02x})\n".format(bank, sfx, address, bank, address % 0x4000 + 0x4000)) while left != 0: pointer = rom[address + 2] * 0x100 + rom[address + 1] -- cgit v1.2.3