Merge pull request #51 from yenatch/master

sound dumps and from_asm() method in preprocessor macros
This commit is contained in:
Bryan Bishop
2013-11-14 11:22:31 -08:00
6 changed files with 846 additions and 51 deletions

385
pokemontools/audio.py Normal file
View File

@@ -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()

View File

@@ -0,0 +1,4 @@
# coding: utf-8
cry_names = ['%.2X' % x for x in xrange(0x44)]

View File

@@ -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"
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
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 = {
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(

View File

@@ -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

211
pokemontools/sfx_names.py Normal file
View File

@@ -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',
]

107
pokemontools/song_names.py Normal file
View File

@@ -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',
]