summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pokemontools/audio.py385
-rw-r--r--pokemontools/cry_names.py4
-rw-r--r--pokemontools/crystal.py172
-rw-r--r--pokemontools/preprocessor.py18
-rw-r--r--pokemontools/sfx_names.py211
-rw-r--r--pokemontools/song_names.py107
6 files changed, 846 insertions, 51 deletions
diff --git a/pokemontools/audio.py b/pokemontools/audio.py
new file mode 100644
index 0000000..1cce1fe
--- /dev/null
+++ b/pokemontools/audio.py
@@ -0,0 +1,385 @@
+# coding: utf-8
+
+import os
+
+from math import ceil
+
+from gbz80disasm import get_global_address, get_local_address
+
+import crystal
+from crystal import music_classes as sound_classes
+from crystal import Command
+
+from crystal import load_rom
+rom = load_rom()
+rom = bytearray(rom)
+
+import config
+conf = config.Config()
+
+
+def sort_asms(asms):
+ """sort and remove duplicates from a list of tuples
+ format (address, asm, last_address)"""
+ return sorted(set(asms), key=lambda (x,y,z):(x,z,not y.startswith(';'), ':' not in y))
+
+class NybbleParam:
+ size = 0.5
+ byte_type = 'dn'
+ which = None
+
+ def __init__(self, address, name):
+ if self.which == None:
+ self.which = {0.0: 'lo', 0.5: 'hi'}[address % 1]
+ self.address = int(address)
+ self.name = name
+ self.parse()
+
+ def parse(self):
+ self.nybble = (rom[self.address] >> {'lo': 0, 'hi': 4}[self.which]) & 0xf
+
+class HiNybbleParam(NybbleParam):
+ which = 'hi'
+ def to_asm(self):
+ return '%d' % self.nybble
+
+class LoNybbleParam(NybbleParam):
+ which = 'lo'
+ def to_asm(self):
+ return '%d' % self.nybble
+
+class PitchParam(HiNybbleParam):
+ def to_asm(self):
+ """E and B cant be sharp"""
+ if self.nybble == 0:
+ pitch = '__'
+ else:
+ pitch = 'CCDDEFFGGAAB'[(self.nybble - 1)]
+ if self.nybble in [2, 4, 7, 9, 11]:
+ pitch += '#'
+ else:
+ pitch += '_'
+ return pitch
+
+
+class Note(Command):
+ macro_name = "note"
+ size = 1
+ end = False
+ param_types = {
+ 0: {"name": "pitch", "class": PitchParam},
+ 1: {"name": "duration", "class": LoNybbleParam},
+ }
+ allowed_lengths = [2]
+ override_byte_check = True
+ is_rgbasm_macro = True
+
+ def parse(self):
+ self.params = []
+ byte = rom[self.address]
+ current_address = self.address
+ for (key, param_type) in self.param_types.items():
+ name = param_type["name"]
+ class_ = param_type["class"]
+
+ # by making an instance, obj.parse() is called
+ obj = class_(address=int(current_address), name=name)
+ self.params += [obj]
+
+ current_address += obj.size
+
+ self.params = dict(enumerate(self.params))
+
+ # obj sizes were 0.5, but were working with ints
+ current_address = int(ceil(current_address))
+ self.size = int(ceil(self.size))
+
+ self.last_address = current_address
+ return True
+
+
+class Noise(Note):
+ macro_name = "noise"
+ size = 0
+ end = False
+ param_types = {
+ 0: {"name": "duration", "class": LoNybbleParam},
+ 1: {"name": "intensity", "class": SingleByteParam},
+ 2: {"name": "frequency", "class": MultiByteParam},
+ }
+ allowed_lengths = [2,3]
+ override_byte_check = True
+ is_rgbasm_macro = False
+
+
+
+class Channel:
+ """A sound channel data parser."""
+
+ def __init__(self, address, channel=1, base_label='Sound'):
+ self.start_address = address
+ self.address = address
+ self.channel = channel
+ self.base_label = base_label
+ self.output = []
+ self.labels = []
+ self.parse()
+
+ def parse(self):
+ noise = False
+ done = False
+ while not done:
+ cmd = rom[self.address]
+
+ class_ = self.get_sound_class(cmd)(address=self.address, channel=self.channel)
+
+ # notetype loses the intensity param on channel 4
+ if class_.macro_name == 'notetype':
+ if self.channel in [4, 8]:
+ class_.size -= 1
+ del class_.params[class_.size - 1]
+
+ # togglenoise only has a param when toggled on
+ elif class_.macro_name in ['togglenoise', 'sfxtogglenoise']:
+ if noise:
+ class_.size -= 1
+ del class_.params[class_.size - 1]
+ noise = not noise
+
+ asm = class_.to_asm()
+
+ # label any jumps or calls
+ for key, param in class_.param_types.items():
+ if param['class'] == crystal.PointerLabelParam:
+ label_address = class_.params[key].parsed_address
+ label = '%s_branch_%x' % (
+ self.base_label,
+ label_address
+ )
+ label_output = (
+ label_address,
+ '\n%s: ; %x' % (label, label_address),
+ label_address
+ )
+ self.labels += [label_output]
+ asm = asm.replace(
+ '$%x' % (get_local_address(label_address)),
+ label
+ )
+
+ self.output += [(self.address, '\t' + asm, self.address + class_.size)]
+ self.address += class_.size
+
+ done = class_.end
+ # infinite loops are enders
+ if class_.macro_name == 'loopchannel':
+ if class_.params[0].byte == 0:
+ done = True
+
+ # keep going past enders if theres more to parse
+ if any(self.address <= address for address, asm, last_address in self.output + self.labels):
+ if done:
+ self.output += [(self.address, '; %x' % self.address, self.address)]
+ done = False
+
+ # dumb safety checks
+ if (
+ self.address >= len(rom) or
+ self.address / 0x4000 != self.start_address / 0x4000
+ ) and not done:
+ done = True
+ raise Exception, 'reached the end of the bank without finishing!'
+
+ def to_asm(self):
+ output = sort_asms(self.output + self.labels)
+ text = ''
+ for i, (address, asm, last_address) in enumerate(output):
+ if ':' in asm:
+ # dont print labels for empty chunks
+ for (address_, asm_, last_address_) in output[i:]:
+ if ':' not in asm_:
+ text += '\n' + asm + '\n'
+ break
+ else:
+ text += asm + '\n'
+ text += '; %x' % (last_address) + '\n'
+ return text
+
+ def get_sound_class(self, i):
+ for class_ in sound_classes:
+ if class_.id == i:
+ return class_
+ if self.channel in [4. 8]: return Noise
+ return Note
+
+
+class Sound:
+ """Interprets a sound data header."""
+
+ def __init__(self, address, name=''):
+ self.start_address = address
+ self.bank = address / 0x4000
+ self.address = address
+
+ self.name = name
+ self.base_label = 'Sound_%x' % self.start_address
+ if self.name != '':
+ self.base_label = self.name
+
+ self.output = []
+ self.labels = []
+ self.asms = []
+ self.parse()
+
+ def parse(self):
+ self.num_channels = (rom[self.address] >> 6) + 1
+ self.channels = []
+ for ch in xrange(self.num_channels):
+ current_channel = (rom[self.address] & 0xf) + 1
+ self.address += 1
+ address = rom[self.address] + rom[self.address + 1] * 0x100
+ address = self.bank * 0x4000 + address % 0x4000
+ self.address += 2
+ channel = Channel(address, current_channel, self.base_label)
+ self.channels += [(current_channel, channel)]
+
+ self.labels += channel.labels
+
+ label_text = '\n%s_Ch%d: ; %x' % (
+ self.base_label,
+ current_channel,
+ channel.start_address
+ )
+ label_output = (channel.start_address, label_text, channel.start_address)
+ self.labels += [label_output]
+
+ asms = []
+
+ text = '%s: ; %x' % (self.base_label, self.start_address) + '\n'
+ for i, (num, channel) in enumerate(self.channels):
+ channel_id = num - 1
+ if i == 0:
+ channel_id += (len(self.channels) - 1) << 6
+ text += '\tdbw $%.2x, %s_Ch%d' % (channel_id, self.base_label, num) + '\n'
+ text += '; %x\n' % self.address
+ asms += [(self.start_address, text, self.start_address + len(self.channels) * 3)]
+
+ for num, channel in self.channels:
+ asms += channel.output
+
+ asms = sort_asms(asms)
+ self.last_address = asms[-1][2]
+ asms += [(self.last_address,'; %x' % self.last_address, self.last_address)]
+
+ self.asms += asms
+
+ def to_asm(self, labels=[]):
+ """insert outside labels here"""
+ asms = self.asms
+
+ # incbins dont really count as parsed data
+ incbins = []
+ for i, (address, asm, last_address) in enumerate(asms):
+ if i + 1 < len(asms):
+ next_address = asms[i + 1][0]
+ if last_address != next_address:
+ incbins += [(last_address, 'INCBIN "baserom.gbc", $%x, $%x - $%x' % (last_address, next_address, last_address), next_address)]
+ asms += incbins
+ for label in self.labels + labels:
+ if self.start_address <= label[0] < self.last_address:
+ asms += [label]
+
+ return '\n'.join(asm for address, asm, last_address in sort_asms(asms))
+
+
+def read_bank_address_pointer(addr):
+ bank, address = rom[addr], rom[addr+1] + rom[addr+2] * 0x100
+ return get_global_address(address, bank)
+
+
+def dump_sounds(origin, names, base_label='Sound_'):
+ """Dump sound data from a pointer table."""
+
+ # first pass to grab labels and boundaries
+ labels = []
+ addresses = []
+ for i, name in enumerate(names):
+ sound_at = read_bank_address_pointer(origin + i * 3)
+ sound = Sound(sound_at, base_label + name)
+ labels += sound.labels
+ addresses += [(sound.start_address, sound.last_address)]
+ addresses = sorted(addresses)
+
+ outputs = []
+ for i, name in enumerate(names):
+ sound_at = read_bank_address_pointer(origin + i * 3)
+ sound = Sound(sound_at, base_label + name)
+ output = sound.to_asm(labels) + '\n'
+
+ # incbin trailing commands that didnt get picked up
+ index = addresses.index((sound.start_address, sound.last_address))
+ if index + 1 < len(addresses):
+ next_address = addresses[index + 1][0]
+ if 5 > next_address - sound.last_address > 0:
+ if next_address / 0x4000 == sound.last_address / 0x4000:
+ output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n' % (sound.last_address, next_address, sound.last_address)
+
+ filename = name.lower() + '.asm'
+ outputs += [(filename, output)]
+ return outputs
+
+
+def export_sounds(origin, names, path, base_label='Sound_'):
+ for filename, output in dump_sounds(origin, names, base_label):
+ with open(os.path.join(path, filename), 'w') as out:
+ out.write(output)
+
+
+def dump_sound_clump(origin, names, base_label='Sound_'):
+ """some sounds are grouped together and/or share most components.
+ these can't reasonably be split into files for each sound."""
+
+ output = []
+ for i, name in enumerate(names):
+ sound_at = read_bank_address_pointer(origin + i * 3)
+ sound = Sound(sound_at, base_label + name)
+ output += sound.asms + sound.labels
+ output = sort_asms(output)
+ return output
+
+
+def export_sound_clump(origin, names, path, base_label='Sound_'):
+ output = dump_sound_clump(origin, names, base_label)
+ with open(path, 'w') as out:
+ out.write('\n'.join(asm for address, asm, last_address in output))
+
+
+def dump_crystal_music():
+ from song_names import song_names
+ export_sounds(0xe906e, song_names, os.path.join(conf.path, 'audio', 'music'), 'Music_')
+
+def generate_crystal_music_pointers():
+ from song_names import song_names
+ return '\n'.join('\tdbw BANK({0}), {0}'.format('Music_' + label) for label in song_names)
+
+def dump_crystal_sfx():
+ from sfx_names import sfx_names
+ export_sound_clump(0xe927c, sfx_names, os.path.join(conf.path, 'audio', 'sfx.asm'), 'Sfx_')
+
+def generate_crystal_sfx_pointers():
+ from sfx_names import sfx_names
+ return '\n'.join('\tdbw BANK({0}), {0}'.format('Sfx_' + label) for label in sfx_names)
+
+def dump_crystal_cries():
+ from cry_names import cry_names
+ export_sound_clump(0xe91b0, cry_names, os.path.join(conf.path, 'audio', 'cries.asm'), 'Cry_')
+
+def generate_crystal_cry_pointers():
+ from cry_names import cry_names
+ return '\n'.join('\tdbw BANK({0}), {0}'.format('Cry_' + label) for label in cry_names)
+
+
+if __name__ == '__main__':
+ dump_crystal_music()
+ dump_crystal_sfx()
+
diff --git a/pokemontools/cry_names.py b/pokemontools/cry_names.py
new file mode 100644
index 0000000..af08fe1
--- /dev/null
+++ b/pokemontools/cry_names.py
@@ -0,0 +1,4 @@
+# coding: utf-8
+
+cry_names = ['%.2X' % x for x in xrange(0x44)]
+
diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py
index 5d602c9..cdab01f 100644
--- a/pokemontools/crystal.py
+++ b/pokemontools/crystal.py
@@ -174,7 +174,7 @@ def how_many_until(byte, starting, rom):
def load_map_group_offsets(map_group_pointer_table, map_group_count, rom=None):
"""reads the map group table for the list of pointers"""
map_group_offsets = [] # otherwise this method can only be used once
- data = rom_interval(map_group_pointer_table, map_group_count*2, strings=False, rom=rom)
+ data = rom.interval(map_group_pointer_table, map_group_count*2, strings=False, rom=rom)
data = helpers.grouper(data)
for pointer_parts in data:
pointer = pointer_parts[0] + (pointer_parts[1] << 8)
@@ -548,7 +548,7 @@ def parse_text_from_bytes(bytes, debug=True, japanese=False):
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)
+ 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
@@ -569,7 +569,7 @@ def parse_text_at3(address, map_group=None, map_id=None, debug=False):
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)])
+ return "".join([chr(x) for x in rom.interval(address, count, strings=False)])
def get_map_constant_label(map_group=None, map_id=None, map_internal_ids=None):
"""returns PALLET_TOWN for some map group/id pair"""
@@ -764,6 +764,10 @@ class SingleByteParam():
else:
return str(self.byte)
+ @staticmethod
+ def from_asm(value):
+ return value
+
class DollarSignByte(SingleByteParam):
def to_asm(self):
return hex(self.byte).replace("0x", "$")
@@ -803,7 +807,7 @@ class MultiByteParam():
self.parse()
def parse(self):
- self.bytes = rom_interval(self.address, self.size, strings=False)
+ 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)
@@ -822,6 +826,10 @@ class MultiByteParam():
decimal = int("0x"+"".join([("%.2x")%x for x in reversed(self.bytes)]), 16)
return str(decimal)
+ @staticmethod
+ def from_asm(value):
+ return value
+
class PointerLabelParam(MultiByteParam):
# default size is 2 bytes
@@ -1000,6 +1008,7 @@ class RAMAddressParam(MultiByteParam):
class MoneyByteParam(MultiByteParam):
size = 3
+ byte_type = "db"
max_value = 0x0F423F
should_be_decimal = True
def parse(self):
@@ -1236,6 +1245,7 @@ class Command:
# 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
+ is_rgbasm_macro = False
base_label = "UnseenLabel_"
def __init__(self, address=None, *pargs, **kwargs):
@@ -1723,6 +1733,7 @@ class TextCommand(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
@@ -1769,7 +1780,7 @@ class MainText(TextCommand):
# 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)
+ self.bytes = rom.interval(offset, jump, strings=False)
# include the original command in the size calculation
self.size = jump
@@ -2372,41 +2383,70 @@ def create_command_classes(debug=False):
command_classes = create_command_classes()
+class BigEndianParam:
+ """big-endian word"""
+ size = 2
+ should_be_decimal = False
+ byte_type = "bigdw"
+
+ def __init__(self, *args, **kwargs):
+ self.prefix = '$'
+ for (key, value) in kwargs.items():
+ setattr(self, key, value)
+ self.parse()
+
+ def parse(self):
+ self.bytes = rom.interval(self.address, 2, strings=False)
+ self.parsed_number = self.bytes[0] * 0x100 + self.bytes[1]
+
+ def to_asm(self):
+ if not self.should_be_decimal:
+ return self.prefix+"".join([("%.2x")%x for x in self.bytes])
+ elif self.should_be_decimal:
+ decimal = int("0x"+"".join([("%.2x")%x for x in self.bytes]), 16)
+ return str(decimal)
+
+ @staticmethod
+ def from_asm(value):
+ return value
+
+class DecimalBigEndianParam(BigEndianParam):
+ should_be_decimal = True
-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
+music_commands = {
+ 0xD0: ["octave 8"],
+ 0xD1: ["octave 7"],
+ 0xD2: ["octave 6"],
+ 0xD3: ["octave 5"],
+ 0xD4: ["octave 4"],
+ 0xD5: ["octave 3"],
+ 0xD6: ["octave 2"],
+ 0xD7: ["octave 1"],
+ 0xD8: ["notetype", ["note_length", SingleByteParam], ["intensity", SingleByteParam]], # no intensity on channel 4/8
0xD9: ["forceoctave", ["octave", SingleByteParam]],
- 0xDA: ["tempo", ["tempo", MultiByteParam]],
+ 0xDA: ["tempo", ["tempo", DecimalBigEndianParam]],
0xDB: ["dutycycle", ["duty_cycle", SingleByteParam]],
0xDC: ["intensity", ["intensity", SingleByteParam]],
0xDD: ["soundinput", ["input", SingleByteParam]],
- 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]], # also updates duty cycle
+ 0xDE: ["unknownmusic0xde", ["unknown", SingleByteParam]],
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!
+ 0xE3: ["togglenoise", ["id", SingleByteParam]], # no parameters on toggle off
0xE4: ["panning", ["tracks", SingleByteParam]],
0xE5: ["volume", ["volume", SingleByteParam]],
- 0xE6: ["tone", ["tone", MultiByteParam]], # big endian
+ 0xE6: ["tone", ["tone", BigEndianParam]],
0xE7: ["unknownmusic0xe7", ["unknown", SingleByteParam]],
0xE8: ["unknownmusic0xe8", ["unknown", SingleByteParam]],
- 0xE9: ["globaltempo", ["value", MultiByteParam]],
+ 0xE9: ["globaltempo", ["value", DecimalBigEndianParam]],
0xEA: ["restartchannel", ["address", PointerLabelParam]],
- 0xEB: ["newsong", ["id", MultiByteParam]],
+ 0xEB: ["newsong", ["id", DecimalBigEndianParam]],
0xEC: ["sfxpriorityon"],
0xED: ["sfxpriorityoff"],
0xEE: ["unknownmusic0xee", ["address", PointerLabelParam]],
0xEF: ["stereopanning", ["tracks", SingleByteParam]],
- 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # 0-1 params
+ 0xF0: ["sfxtogglenoise", ["id", SingleByteParam]], # no parameters on toggle off
0xF1: ["music0xf1"], # nothing
0xF2: ["music0xf2"], # nothing
0xF3: ["music0xf3"], # nothing
@@ -2419,19 +2459,29 @@ music_commands_new = {
0xFA: ["setcondition", ["condition", SingleByteParam]],
0xFB: ["jumpif", ["condition", SingleByteParam], ["address", PointerLabelParam]],
0xFC: ["jumpchannel", ["address", PointerLabelParam]],
- 0xFD: ["loopchannel", ["count", SingleByteParam], ["address", PointerLabelParam]],
+ 0xFD: ["loopchannel", ["count", DecimalParam], ["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)
+music_command_enders = [
+ "restartchannel",
+ "newsong",
+ "unknownmusic0xee",
+ "jumpchannel",
+ "endchannel",
+]
def create_music_command_classes(debug=False):
klasses = []
- for (byte, cmd) in music_commands_new.items():
+ for (byte, cmd) in music_commands.items():
cmd_name = cmd[0].replace(" ", "_")
- params = {"id": byte, "size": 1, "end": byte in music_command_enders, "macro_name": cmd_name}
+ params = {
+ "id": byte,
+ "size": 1,
+ "end": cmd[0] in music_command_enders,
+ "macro_name": cmd[0]
+ }
params["param_types"] = {}
if len(cmd) > 1:
param_types = cmd[1:]
@@ -2451,24 +2501,54 @@ def create_music_command_classes(debug=False):
klasses.append(klass)
# later an individual klass will be instantiated to handle something
return klasses
+
music_classes = create_music_command_classes()
+class OctaveParam(DecimalParam):
+ @staticmethod
+ def from_asm(value):
+ value = int(value)
+ return hex(0xd8 - value).replace("0x", "$")
+
+class OctaveCommand(Command):
+ macro_name = "octave"
+ size = 0
+ end = False
+ param_types = {
+ 0: {"name": "octave", "class": OctaveParam},
+ }
+ allowed_lengths = [1]
+ override_byte_check = True
+
+class ChannelCommand(Command):
+ macro_name = "channel"
+ size = 3
+ override_byte_check = True
+ param_types = {
+ 0: {"name": "id", "class": DecimalParam},
+ 1: {"name": "address", "class": PointerLabelParam},
+ }
+
+
+# pokered
+
class callchannel(Command):
- id = 0xFD
- macro_name = "callchannel"
- size = 3
- param_types = {
- 0: {"name": "address", "class": PointerLabelParam},
- }
+ id = 0xFD
+ macro_name = "callchannel"
+ size = 3
+ param_types = {
+ 0: {"name": "address", "class": PointerLabelParam},
+ }
class loopchannel(Command):
- id = 0xFE
- macro_name = "loopchannel"
- size = 4
- param_types = {
- 0: {"name": "count", "class": SingleByteParam},
- 1: {"name": "address", "class": PointerLabelParam},
- }
+ id = 0xFE
+ macro_name = "loopchannel"
+ size = 4
+ param_types = {
+ 0: {"name": "count", "class": SingleByteParam},
+ 1: {"name": "address", "class": PointerLabelParam},
+ }
+
effect_commands = {
0x1: ['checkturn'],
@@ -4272,7 +4352,7 @@ class Signpost(Command):
address = self.address
bank = self.bank
self.last_address = self.address + self.size
- bytes = rom_interval(self.address, self.size) #, signpost_byte_size)
+ bytes = rom.interval(self.address, self.size) #, signpost_byte_size)
self.y = int(bytes[0], 16)
self.x = int(bytes[1], 16)
@@ -4526,7 +4606,7 @@ class SecondMapHeader:
def parse(self):
address = self.address
- bytes = rom_interval(address, second_map_header_byte_size, strings=False)
+ bytes = rom.interval(address, second_map_header_byte_size, strings=False)
size = second_map_header_byte_size
# for later
@@ -5339,7 +5419,7 @@ class MapBlockData:
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.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)
@@ -5407,7 +5487,7 @@ class MapEventHeader:
# 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 = 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
@@ -5416,7 +5496,7 @@ class MapEventHeader:
# 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_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=pointers.calculate_bank(after_signposts+2), map_group=map_group, map_id=map_id, debug=debug)
self.people_event_count = people_event_count
@@ -5577,7 +5657,7 @@ class MapScriptHeader:
self.trigger_count = ord(rom[address])
self.triggers = []
ptr_line_size = 4
- groups = helpers.grouper(rom_interval(address+1, self.trigger_count * ptr_line_size, strings=False), count=ptr_line_size)
+ groups = helpers.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):
logging.debug(
diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py
index 5fe0851..f4e92b6 100644
--- a/pokemontools/preprocessor.py
+++ b/pokemontools/preprocessor.py
@@ -556,8 +556,6 @@ class Preprocessor(object):
# remove trailing newline
if line[-1] == "\n":
line = line[:-1]
- else:
- original_line += "\n"
# remove first tab
has_tab = False
@@ -589,6 +587,12 @@ class Preprocessor(object):
sys.stdout.write(original_line)
return
+ # rgbasm can handle other macros too
+ if "is_rgbasm_macro" in dir(macro):
+ if macro.is_rgbasm_macro:
+ sys.stdout.write(original_line)
+ return
+
# certain macros don't need an initial byte written
# do: all scripting macros
# don't: signpost, warp_def, person_event, xy_trigger
@@ -608,7 +612,7 @@ class Preprocessor(object):
index = 0
while index < len(params):
param_type = macro.param_types[index - correction]
- description = param_type["name"]
+ description = param_type["name"].strip()
param_klass = param_type["class"]
byte_type = param_klass.byte_type # db or dw
size = param_klass.size
@@ -638,7 +642,7 @@ class Preprocessor(object):
index += 2
correction += 1
elif size == 3 and "from_asm" in dir(param_klass):
- output += ("db " + param_klass.from_asm(param) + "\n")
+ output += ("\t" + byte_type + " " + param_klass.from_asm(param) + "\n")
index += 1
else:
raise exceptions.MacroException(
@@ -649,9 +653,13 @@ class Preprocessor(object):
)
)
+ elif "from_asm" in dir(param_klass):
+ output += ("\t" + byte_type + " " + param_klass.from_asm(param) + " ; " + description + "\n")
+ index += 1
+
# or just print out the byte
else:
- output += (byte_type + " " + param + " ; " + description + "\n")
+ output += ("\t" + byte_type + " " + param + " ; " + description + "\n")
index += 1
diff --git a/pokemontools/sfx_names.py b/pokemontools/sfx_names.py
new file mode 100644
index 0000000..f7da967
--- /dev/null
+++ b/pokemontools/sfx_names.py
@@ -0,0 +1,211 @@
+# coding: utf-8
+
+sfx_names = [
+ 'DexFanfare5079',
+ 'Item',
+ 'CaughtMon',
+ 'PokeballsPlacedOnTable',
+ 'Potion',
+ 'FullHeal',
+ 'Menu',
+ 'ReadText',
+ 'ReadText2',
+ 'DexFanfare2049',
+ 'DexFanfare80109',
+ 'Poison',
+ 'GotSafariBalls',
+ 'BootPc',
+ 'ShutDownPc',
+ 'ChoosePcOption',
+ 'EscapeRope',
+ 'PushButton',
+ 'SecondPartOfItemfinder',
+ 'WarpTo',
+ 'WarpFrom',
+ 'ChangeDexMode',
+ 'JumpOverLedge',
+ 'GrassRustle',
+ 'Fly',
+ 'Wrong',
+ 'Squeak',
+ 'Strength',
+ 'Boat',
+ 'WallOpen',
+ 'PlacePuzzlePieceDown',
+ 'EnterDoor',
+ 'SwitchPokemon',
+ 'Tally',
+ 'Transaction',
+ 'ExitBuilding',
+ 'Bump',
+ 'Save',
+ 'Pokeflute',
+ 'ElevatorEnd',
+ 'ThrowBall',
+ 'BallPoof',
+ 'Unknown3A',
+ 'Run',
+ 'SlotMachineStart',
+ 'Fanfare',
+ 'Peck',
+ 'Kinesis',
+ 'Lick',
+ 'Pound',
+ 'MovePuzzlePiece',
+ 'CometPunch',
+ 'MegaPunch',
+ 'Scratch',
+ 'Vicegrip',
+ 'RazorWind',
+ 'Cut',
+ 'WingAttack',
+ 'Whirlwind',
+ 'Bind',
+ 'VineWhip',
+ 'DoubleKick',
+ 'MegaKick',
+ 'Headbutt',
+ 'HornAttack',
+ 'Tackle',
+ 'PoisonSting',
+ 'Powder',
+ 'Doubleslap',
+ 'Bite',
+ 'JumpKick',
+ 'Stomp',
+ 'TailWhip',
+ 'KarateChop',
+ 'Submission',
+ 'WaterGun',
+ 'SwordsDance',
+ 'Thunder',
+ 'Supersonic',
+ 'Leer',
+ 'Ember',
+ 'Bubblebeam',
+ 'HydroPump',
+ 'Surf',
+ 'Psybeam',
+ 'Charge',
+ 'Thundershock',
+ 'Psychic',
+ 'Screech',
+ 'BoneClub',
+ 'Sharpen',
+ 'EggBomb',
+ 'Sing',
+ 'HyperBeam',
+ 'Shine',
+ 'Unknown5F',
+ 'Unknown60',
+ 'Unknown61',
+ 'Unknown62',
+ 'Unknown63',
+ 'Burn',
+ 'TitleScreenEntrance',
+ 'Unknown66',
+ 'GetCoinFromSlots',
+ 'PayDay',
+ 'Metronome',
+ 'Call',
+ 'HangUp',
+ 'NoSignal',
+ 'Sandstorm',
+ 'Elevator',
+ 'Protect',
+ 'Sketch',
+ 'RainDance',
+ 'Aeroblast',
+ 'Spark',
+ 'Curse',
+ 'Rage',
+ 'Thief',
+ 'Thief2',
+ 'SpiderWeb',
+ 'MindReader',
+ 'Nightmare',
+ 'Snore',
+ 'SweetKiss',
+ 'SweetKiss2',
+ 'BellyDrum',
+ 'Unknown7F',
+ 'SludgeBomb',
+ 'Foresight',
+ 'Spite',
+ 'Outrage',
+ 'PerishSong',
+ 'GigaDrain',
+ 'Attract',
+ 'Kinesis2',
+ 'ZapCannon',
+ 'MeanLook',
+ 'HealBell',
+ 'Return',
+ 'ExpBar',
+ 'MilkDrink',
+ 'Present',
+ 'MorningSun',
+ 'LevelUp',
+ 'KeyItem',
+ 'Fanfare2',
+ 'RegisterPhoneNumber',
+ '3RdPlace',
+ 'GetEggFromDaycareMan',
+ 'GetEggFromDaycareLady',
+ 'MoveDeleted',
+ '2NdPlace',
+ '1StPlace',
+ 'ChooseACard',
+ 'GetTm',
+ 'GetBadge',
+ 'QuitSlots',
+ 'EggCrack',
+ 'DexFanfareLessThan20',
+ 'DexFanfare140169',
+ 'DexFanfare170199',
+ 'DexFanfare200229',
+ 'DexFanfare230Plus',
+ 'Evolved',
+ 'MasterBall',
+ 'EggHatch',
+ 'GsIntroCharizardFireball',
+ 'GsIntroPokemonAppears',
+ 'Flash',
+ 'GameFreakLogoGs',
+ 'NotVeryEffective',
+ 'Damage',
+ 'SuperEffective',
+ 'BallBounce',
+ 'Moonlight',
+ 'Encore',
+ 'BeatUp',
+ 'BatonPass',
+ 'BallWiggle',
+ 'SweetScent',
+ 'SweetScent2',
+ 'HitEndOfExpBar',
+ 'GiveTrademon',
+ 'GetTrademon',
+ 'TrainArrived',
+ 'StopSlot',
+ '2Boops',
+ 'GlassTing',
+ 'GlassTing2',
+ 'IntroUnown1',
+ 'IntroUnown2',
+ 'IntroUnown3',
+ 'DittoPopUp',
+ 'DittoTransform',
+ 'IntroSuicune1',
+ 'IntroPichu',
+ 'IntroSuicune2',
+ 'IntroSuicune3',
+ 'DittoBounce',
+ 'IntroSuicune4',
+ 'GameFreakPresents',
+ 'Tingle',
+ 'UnknownCb',
+ 'TwoPcBeeps',
+ '4NoteDitty',
+ 'Twinkle',
+]
diff --git a/pokemontools/song_names.py b/pokemontools/song_names.py
new file mode 100644
index 0000000..1e48ca7
--- /dev/null
+++ b/pokemontools/song_names.py
@@ -0,0 +1,107 @@
+# coding: utf-8
+
+song_names = [
+ 'Nothing',
+ 'TitleScreen',
+ 'Route1',
+ 'Route3',
+ 'Route12',
+ 'MagnetTrain',
+ 'KantoGymBattle',
+ 'KantoTrainerBattle',
+ 'KantoWildBattle',
+ 'PokemonCenter',
+ 'LookHiker',
+ 'LookLass',
+ 'LookOfficer',
+ 'HealPokemon',
+ 'LavenderTown',
+ 'Route2',
+ 'MtMoon',
+ 'ShowMeAround',
+ 'GameCorner',
+ 'Bicycle',
+ 'HallOfFame',
+ 'ViridianCity',
+ 'CeladonCity',
+ 'TrainerVictory',
+ 'WildPokemonVictory',
+ 'GymLeaderVictory',
+ 'MtMoonSquare',
+ 'Gym',
+ 'PalletTown',
+ 'ProfOaksPokemonTalk',
+ 'ProfOak',
+ 'LookRival',
+ 'AfterTheRivalFight',
+ 'Surf',
+ 'Evolution',
+ 'NationalPark',
+ 'Credits',
+ 'AzaleaTown',
+ 'CherrygroveCity',
+ 'LookKimonoGirl',
+ 'UnionCave',
+ 'JohtoWildBattle',
+ 'JohtoTrainerBattle',
+ 'Route30',
+ 'EcruteakCity',
+ 'VioletCity',
+ 'JohtoGymBattle',
+ 'ChampionBattle',
+ 'RivalBattle',
+ 'RocketBattle',
+ 'ElmsLab',
+ 'DarkCave',
+ 'Route29',
+ 'Route36',
+ 'SSAqua',
+ 'LookYoungster',
+ 'LookBeauty',
+ 'LookRocket',
+ 'LookPokemaniac',
+ 'LookSage',
+ 'NewBarkTown',
+ 'GoldenrodCity',
+ 'VermilionCity',
+ 'PokemonChannel',
+ 'PokeFluteChannel',
+ 'TinTower',
+ 'SproutTower',
+ 'BurnedTower',
+ 'Lighthouse',
+ 'LakeOfRage',
+ 'IndigoPlateau',
+ 'Route37',
+ 'RocketHideout',
+ 'DragonsDen',
+ 'JohtoWildBattleNight',
+ 'RuinsOfAlphRadio',
+ 'SuccessfulCapture',
+ 'Route26',
+ 'Mom',
+ 'VictoryRoad',
+ 'PokemonLullaby',
+ 'PokemonMarch',
+ 'GoldSilverOpening',
+ 'GoldSilverOpening2',
+ 'MainMenu',
+ 'RuinsOfAlphInterior',
+ 'RocketTheme',
+ 'DancingHall',
+ 'ContestResults',
+ 'BugCatchingContest',
+ 'LakeOfRageRocketRadio',
+ 'Printer',
+ 'PostCredits',
+ 'Clair',
+ 'MobileAdapterMenu',
+ 'MobileAdapter',
+ 'BuenasPassword',
+ 'LookMysticalMan',
+ 'CrystalOpening',
+ 'BattleTowerTheme',
+ 'SuicuneBattle',
+ 'BattleTowerLobby',
+ 'MobileCenter',
+]