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