diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-11-14 11:22:31 -0800 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-11-14 11:22:31 -0800 |
commit | 783a4916f330894f4f0eb17adf09ed77e18f664a (patch) | |
tree | 065c5934de6076aaffafdad2bf02c16757dc0bc6 | |
parent | 4a002c193b2225f9e370bf640b0c547252e42cd1 (diff) | |
parent | f5ad14d634973ff6b340853df8b61bbdf61e0060 (diff) |
Merge pull request #51 from yenatch/master
sound dumps and from_asm() method in preprocessor macros
-rw-r--r-- | pokemontools/audio.py | 385 | ||||
-rw-r--r-- | pokemontools/cry_names.py | 4 | ||||
-rw-r--r-- | pokemontools/crystal.py | 172 | ||||
-rw-r--r-- | pokemontools/preprocessor.py | 18 | ||||
-rw-r--r-- | pokemontools/sfx_names.py | 211 | ||||
-rw-r--r-- | pokemontools/song_names.py | 107 |
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', +] |