mirror of
https://github.com/pret/pokemon-reverse-engineering-tools.git
synced 2026-04-26 01:11:24 -05:00
317 lines
9.5 KiB
Python
Executable File
317 lines
9.5 KiB
Python
Executable File
from __future__ import absolute_import
|
|
from . import configuration
|
|
config = configuration.Config()
|
|
rom = bytearray(open(config.rom_path, "r").read())
|
|
|
|
songs = [
|
|
"PalletTown",
|
|
"Pokecenter",
|
|
"Gym",
|
|
"Cities1",
|
|
"Cities2",
|
|
"Celadon",
|
|
"Cinnabar",
|
|
"Vermilion",
|
|
"Lavender",
|
|
"SSAnne",
|
|
"MeetProfOak",
|
|
"MeetRival",
|
|
"MuseumGuy",
|
|
"SafariZone",
|
|
"PkmnHealed",
|
|
"Routes1",
|
|
"Routes2",
|
|
"Routes3",
|
|
"Routes4",
|
|
"IndigoPlateau",
|
|
"GymLeaderBattle",
|
|
"TrainerBattle",
|
|
"WildBattle",
|
|
"FinalBattle",
|
|
"DefeatedTrainer",
|
|
"DefeatedWildMon",
|
|
"DefeatedGymLeader",
|
|
"TitleScreen",
|
|
"Credits",
|
|
"HallOfFame",
|
|
"OaksLab",
|
|
"JigglypuffSong",
|
|
"BikeRiding",
|
|
"Surfing",
|
|
"GameCorner",
|
|
"IntroBattle",
|
|
"Dungeon1",
|
|
"Dungeon2",
|
|
"Dungeon3",
|
|
"CinnabarMansion",
|
|
"PokemonTower",
|
|
"SilphCo",
|
|
"MeetEvilTrainer",
|
|
"MeetFemaleTrainer",
|
|
"MeetMaleTrainer",
|
|
"UnusedSong",
|
|
]
|
|
"""
|
|
songs = [
|
|
"YellowIntro",
|
|
"SurfingPikachu",
|
|
"MeetJessieJames",
|
|
"YellowUnusedSong",
|
|
]
|
|
"""
|
|
music_commands = {
|
|
0xd0: ["notetype", {"type": "nibble"}, 2],
|
|
0xe0: ["octave", 1],
|
|
0xe8: ["toggleperfectpitch", 1],
|
|
0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3],
|
|
0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3],
|
|
0xec: ["duty", {"type": "byte"}, 2],
|
|
0xed: ["tempo", {"type": "word"}, 3],
|
|
0xee: ["stereopanning", {"type": "byte"}, 2],
|
|
0xf0: ["volume", {"type": "nibble"}, 2],
|
|
0xf8: ["executemusic", 1],
|
|
0xfc: ["dutycycle", {"type": "byte"}, 2],
|
|
0xfd: ["callchannel", {"type": "label"}, 3],
|
|
0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
|
|
0xff: ["endchannel", 1],
|
|
}
|
|
|
|
param_lengths = {
|
|
"nibble": 1,
|
|
"byte": 1,
|
|
"word": 2,
|
|
"label": 2,
|
|
}
|
|
|
|
music_notes = {
|
|
0x0: "C_",
|
|
0x1: "C#",
|
|
0x2: "D_",
|
|
0x3: "D#",
|
|
0x4: "E_",
|
|
0x5: "F_",
|
|
0x6: "F#",
|
|
0x7: "G_",
|
|
0x8: "G#",
|
|
0x9: "A_",
|
|
0xa: "A#",
|
|
0xb: "B_",
|
|
}
|
|
|
|
def printnoisechannel(songname, songfile, startingaddress, bank, output):
|
|
noise_commands = {
|
|
0xfd: ["callchannel", {"type": "label"}, 3],
|
|
0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
|
|
0xff: ["endchannel", 1],
|
|
}
|
|
|
|
noise_instruments = {
|
|
0x01: "snare1",
|
|
0x02: "snare2",
|
|
0x03: "snare3",
|
|
0x04: "snare4",
|
|
0x05: "snare5",
|
|
0x06: "triangle1",
|
|
0x07: "triangle2",
|
|
0x08: "snare6",
|
|
0x09: "snare7",
|
|
0x0a: "snare8",
|
|
0x0b: "snare9",
|
|
0x0c: "cymbal1",
|
|
0x0d: "cymbal2",
|
|
0x0e: "cymbal3",
|
|
0x0f: "mutedsnare1",
|
|
0x10: "triangle3",
|
|
0x11: "mutedsnare2",
|
|
0x12: "mutedsnare3",
|
|
0x13: "mutedsnare4",
|
|
}
|
|
|
|
# pass 1, build a list of all addresses pointed to by calls and loops
|
|
address = startingaddress
|
|
labels = []
|
|
labelsleft= []
|
|
while 1:
|
|
byte = rom[address]
|
|
if byte < 0xc0:
|
|
command_length = 2
|
|
elif byte < 0xe0:
|
|
command_length = 1
|
|
else:
|
|
command_length = noise_commands[byte][-1]
|
|
if byte == 0xfd or byte == 0xfe:
|
|
label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2]
|
|
labels.append(label)
|
|
if label > address % 0x4000 + 0x4000: labelsleft.append(label)
|
|
address += command_length
|
|
if len(labelsleft) == 0 and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break
|
|
while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000)
|
|
# once the loop ends, start over from first address
|
|
if rom[address] == 0xff: address += 1
|
|
end = address
|
|
address = startingaddress
|
|
byte = rom[address]
|
|
output += "Music_{}_Ch4:: ; {:02x} ({:0x}:{:02x})\n".format(songname, address, bank, address % 0x4000 + 0x4000)
|
|
# pass 2, print commands and labels for addresses that are in labels
|
|
while address != end:
|
|
if address % 0x4000 + 0x4000 in labels and address != startingaddress:
|
|
output += "\nMusic_{}_branch_{:02x}::\n".format(songname, address)
|
|
if byte < 0xc0:
|
|
output += "\t{} {}".format(noise_instruments[rom[address + 1]], byte % 0x10 + 1)
|
|
command_length = 2
|
|
elif byte < 0xd0:
|
|
output += "\trest {}".format(byte % 0x10 + 1)
|
|
command_length = 1
|
|
elif byte < 0xe0:
|
|
output += "\tdspeed {}".format(byte % 0x10)
|
|
command_length = 1
|
|
else:
|
|
command = noise_commands[byte]
|
|
output += "\t{}".format(command[0])
|
|
command_length = 1
|
|
params = 1
|
|
# print all params for current command
|
|
while params != len(noise_commands[byte]) - 1:
|
|
param_type = noise_commands[byte][params]["type"]
|
|
address += command_length
|
|
command_length = param_lengths[param_type]
|
|
param = rom[address]
|
|
if param_type == "byte":
|
|
output += " {}".format(param)
|
|
else:
|
|
param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
|
|
if param == startingaddress: output += " Music_{}_Ch4".format(songname)
|
|
else: output += " Music_{}_branch_{:02x}".format(songname, param)
|
|
params += 1
|
|
if params != len(noise_commands[byte]) - 1: output += ","
|
|
output += "\n"
|
|
address += command_length
|
|
byte = rom[address]
|
|
output += "; {}\n".format(hex(address))
|
|
songfile.write(output)
|
|
|
|
for i, songname in enumerate(songs):
|
|
songfile = open("music/" + songname.lower() + ".asm", 'a')
|
|
if songname == "PalletTown": header = 0x822e
|
|
if songname == "GymLeaderBattle": header = 0x202be
|
|
if songname == "TitleScreen": header = 0x7c249
|
|
if songname == "YellowIntro": header = 0x7c294
|
|
if songname == "SurfingPikachu": header = 0x801cb
|
|
bank = header / 0x4000
|
|
startingaddress = rom[header + 2] * 0x100 + rom[header + 1] - 0x4000 + (0x4000 * bank)
|
|
curchannel = 1
|
|
lastchannel = (rom[header] >> 6) + 1
|
|
exception = False
|
|
if songname == "MeetRival" or songname == "Cities1":
|
|
startingaddress -= 7
|
|
exception = True
|
|
if songname == "UnusedSong":
|
|
bank = 2
|
|
startingaddress = 0xa913
|
|
lastchannel = 2
|
|
output = ''
|
|
while 1:
|
|
# pass 1, build a list of all addresses pointed to by calls and loops
|
|
address = startingaddress
|
|
labels = []
|
|
labelsleft = []
|
|
if songname == "MeetRival":
|
|
if curchannel == 1:
|
|
labels.append(0x719b)
|
|
labelsleft.append(0x719b)
|
|
labels.append(0x71a2)
|
|
labelsleft.append(0x71a2)
|
|
if curchannel == 2:
|
|
labels.append(0x721d)
|
|
labelsleft.append(0x721d)
|
|
if curchannel == 3:
|
|
labels.append(0x72b5)
|
|
labelsleft.append(0x72b5)
|
|
while 1:
|
|
byte = rom[address]
|
|
if byte < 0xd0:
|
|
command_length = 1
|
|
elif byte < 0xe0:
|
|
command_length = 2
|
|
elif byte < 0xe8:
|
|
command_length = 1
|
|
else:
|
|
command_length = music_commands[byte][-1]
|
|
if byte == 0xfd or byte == 0xfe:
|
|
label = rom[address + command_length - 1] * 0x100 + rom[address + command_length - 2]
|
|
labels.append(label)
|
|
if label > address % 0x4000 + 0x4000: labelsleft.append(label)
|
|
address += command_length
|
|
if len(labelsleft) == 0 and (exception == False or address > startingaddress + 7) and (byte == 0xfe and rom[address - command_length + 1] == 0 and rom[address - 1] * 0x100 + rom[address - 2] < address % 0x4000 + 0x4000 or byte == 0xff): break
|
|
while address % 0x4000 + 0x4000 in labelsleft: labelsleft.remove(address % 0x4000 + 0x4000)
|
|
# once the loop breaks, start over from first address
|
|
if rom[address] == 0xff: address += 1
|
|
end = address
|
|
if curchannel != lastchannel and songname != "UnusedSong": end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1))
|
|
address = startingaddress
|
|
byte = rom[address]
|
|
# if song has an alternate start to channel 1, print a label and set startingaddress to true channel start
|
|
if exception:
|
|
output += "Music_{}_branch_{:02x}::\n".format(songname, address)
|
|
startingaddress += 7
|
|
# pass 2, print commands and labels for addresses that are in labels
|
|
while address != end:
|
|
if address == startingaddress:
|
|
if exception: output += "\n"
|
|
output += "Music_{}_Ch{}:: ; {:02x} ({:0x}:{:02x})\n".format(songname, curchannel, address, bank, address % 0x4000 + 0x4000)
|
|
elif address % 0x4000 + 0x4000 in labels:
|
|
output += "\nMusic_{}_branch_{:02x}::\n".format(songname, address)
|
|
if byte < 0xc0:
|
|
output += "\t{} {}".format(music_notes[byte >> 4], byte % 0x10 + 1)
|
|
command_length = 1
|
|
elif byte < 0xd0:
|
|
output += "\trest {}".format(byte % 0x10 + 1)
|
|
command_length = 1
|
|
else:
|
|
if byte < 0xe0:
|
|
command = music_commands[0xd0]
|
|
output += "\t{} {},".format(command[0], byte % 0x10)
|
|
byte = 0xd0
|
|
elif byte < 0xe8:
|
|
command = music_commands[0xe0]
|
|
output += "\t{} {}".format(command[0], 0xe8 - byte)
|
|
byte = 0xe0
|
|
else:
|
|
command = music_commands[byte]
|
|
output += "\t{}".format(command[0])
|
|
command_length = 1
|
|
params = 1
|
|
# print all params for current command
|
|
while params != len(music_commands[byte]) - 1:
|
|
param_type = music_commands[byte][params]["type"]
|
|
address += command_length
|
|
command_length = param_lengths[param_type]
|
|
param = rom[address]
|
|
if param_type == "nibble":
|
|
output += " {}, {}".format(param >> 4, param % 0x10)
|
|
elif param_type == "byte":
|
|
output += " {}".format(param)
|
|
elif param_type == "word":
|
|
output += " {}".format(param * 0x100 + rom[address + 1])
|
|
else:
|
|
param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
|
|
if param == startingaddress: output += " Music_{}_Ch{}".format(songname, curchannel)
|
|
else: output += " Music_{}_branch_{:02x}".format(songname, param)
|
|
params += 1
|
|
if params != len(music_commands[byte]) - 1: output += ","
|
|
output += "\n"
|
|
address += command_length
|
|
byte = rom[address]
|
|
header += 3
|
|
if curchannel == lastchannel:
|
|
output += "; {}\n".format(hex(address))
|
|
songfile.write(output)
|
|
break
|
|
curchannel += 1
|
|
output += "\n\n"
|
|
startingaddress = end
|
|
exception = False
|
|
if curchannel == 4:
|
|
printnoisechannel(songname, songfile, startingaddress, bank, output)
|
|
header += 3
|
|
break |