diff options
author | Bryan Bishop <kanzure@gmail.com> | 2013-11-09 15:16:48 -0600 |
---|---|---|
committer | Bryan Bishop <kanzure@gmail.com> | 2013-11-09 15:16:48 -0600 |
commit | 8c7e2bcae7d07e28ee229ae958d94ec3809dc763 (patch) | |
tree | 1d710e4654c3ac8420f7d5ee3bdd65b390975e25 | |
parent | cc403392982cfef8ccc9ed2b54ecda5934873a4f (diff) | |
parent | 1d2239e091e9cbf9026ec5afe7e86b718b9b8f38 (diff) |
Merge branch 'master' into vba-automation
-rw-r--r-- | pokemontools/crystal.py | 17 | ||||
-rw-r--r-- | pokemontools/crystalparts/old_parsers.py | 2 | ||||
-rwxr-xr-x | pokemontools/redmusicdisasm.py | 313 | ||||
-rwxr-xr-x | pokemontools/redsfxdisasm.py | 126 | ||||
-rwxr-xr-x | pokemontools/redsfxheaders.py | 36 | ||||
-rw-r--r-- | tests/integration/tests.py | 5 |
6 files changed, 496 insertions, 3 deletions
diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 45eb306..5d602c9 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -2453,7 +2453,22 @@ def create_music_command_classes(debug=False): return klasses music_classes = create_music_command_classes() - +class callchannel(Command): + 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}, + } effect_commands = { 0x1: ['checkturn'], diff --git a/pokemontools/crystalparts/old_parsers.py b/pokemontools/crystalparts/old_parsers.py index e07082d..2c1d2b2 100644 --- a/pokemontools/crystalparts/old_parsers.py +++ b/pokemontools/crystalparts/old_parsers.py @@ -2,7 +2,7 @@ Some old methods rescued from crystal.py """ -import pointers +import pokemontools.pointers as pointers map_header_byte_size = 9 diff --git a/pokemontools/redmusicdisasm.py b/pokemontools/redmusicdisasm.py new file mode 100755 index 0000000..3ed5a4d --- /dev/null +++ b/pokemontools/redmusicdisasm.py @@ -0,0 +1,313 @@ +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: ["togglecall", 1],
+ 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3],
+ 0xeb: ["pitchbend", {"type": "byte"}, {"type": "byte"}, 3],
+ 0xec: ["duty", {"type": "byte"}, 2],
+ 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3],
+ 0xee: ["unknownmusic0xee", {"type": "byte"}, 2],
+ 0xf0: ["stereopanning", {"type": "byte"}, 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,
+ "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 += "\tdnote {}, {}".format(byte % 0x10 + 1, noise_instruments[rom[address + 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 += "; {}".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 += "\tnote {}, {}".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)
+ 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 += "; {}".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
\ No newline at end of file diff --git a/pokemontools/redsfxdisasm.py b/pokemontools/redsfxdisasm.py new file mode 100755 index 0000000..9e9f01b --- /dev/null +++ b/pokemontools/redsfxdisasm.py @@ -0,0 +1,126 @@ +import configuration
+config = configuration.Config()
+rom = bytearray(open(config.rom_path, "r").read())
+
+banks = {
+ 0x02: 0x60,
+ 0x08: 0x78,
+ 0x1f: 0x68,
+ }
+
+music_commands = {
+ 0xd0: ["notetype", {"type": "nibble"}, 2],
+ 0xe0: ["octave", 1],
+ 0xe8: ["togglecall", 1],
+ 0xea: ["vibrato", {"type": "byte"}, {"type": "nibble"}, 3],
+ 0xec: ["duty", {"type": "byte"}, 2],
+ 0xed: ["tempo", {"type": "byte"}, {"type": "byte"}, 3],
+ 0xf0: ["stereopanning", {"type": "byte"}, 2],
+ 0xf8: ["executemusic", 1],
+ 0xfc: ["dutycycle", {"type": "byte"}, 2],
+ 0xfe: ["loopchannel", {"type": "byte"}, {"type": "label"}, 4],
+ 0xff: ["endchannel", 1],
+ }
+
+param_lengths = {
+ "nibble": 1,
+ "byte": 1,
+ "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_",
+ }
+
+for bank in banks:
+ header = bank * 0x4000 + 3
+ for sfx in range(1,banks[bank]):
+ sfxname = "SFX_{:02x}_{:02x}".format(bank, sfx)
+ sfxfile = open("music/sfx/" + sfxname.lower() + ".asm", 'w')
+ startingaddress = rom[header + 2] * 0x100 + rom[header + 1] + (0x4000 * (bank - 1))
+ end = 0
+ curchannel = 1
+ lastchannel = (rom[header] >> 6) + 1
+ channelnumber = rom[header] % 0x10
+ output = ''
+ while 1:
+ address = startingaddress
+ if curchannel != lastchannel:
+ end = rom[header + 5] * 0x100 + rom[header + 4] + (0x4000 * (bank - 1))
+ byte = rom[address]
+ if byte == 0xf8 or (bank == 2 and sfx == 0x5e): executemusic = True
+ else: executemusic = False
+ output += "{}_Ch{}: ; {:02x} ({:0x}:{:02x})\n".format(sfxname, curchannel, address, bank, address % 0x4000 + 0x4000)
+ while 1:
+ if address == 0x2062a or address == 0x2063d or address == 0x20930:
+ output += "\n{}_branch_{:02x}:\n".format(sfxname, address)
+ if byte == 0x10 and not executemusic:
+ output += "\tunknownsfx0x{:02x} {}".format(byte, rom[address + 1])
+ command_length = 2
+ elif byte < 0x30 and not executemusic:
+ if channelnumber == 7:
+ output += "\tunknownnoise0x20 {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2])
+ command_length = 3
+ else:
+ output += "\tunknownsfx0x20 {}, {}, {}, {}".format(byte % 0x10, rom[address + 1], rom[address + 2], rom[address + 3])
+ command_length = 4
+ elif byte < 0xc0:
+ output += "\tnote {}, {}".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)
+ else:
+ param += rom[address + 1] * 0x100 - 0x4000 + (bank * 0x4000)
+ if param == startingaddress: output += " {}_Ch{}".format(sfxname, curchannel)
+ else: output += " {}_branch_{:02x}".format(sfxname, param)
+ params += 1
+ if params != len(music_commands[byte]) - 1: output += ","
+ output += "\n"
+ address += command_length
+ if byte == 0xff or address == end: break
+ byte = rom[address]
+ header += 3
+ channelnumber = rom[header]
+ if curchannel == lastchannel:
+ output += "; {}".format(hex(address))
+ sfxfile.write(output)
+ break
+ output += "\n\n"
+ startingaddress = address
+ curchannel += 1
\ No newline at end of file diff --git a/pokemontools/redsfxheaders.py b/pokemontools/redsfxheaders.py new file mode 100755 index 0000000..c854c20 --- /dev/null +++ b/pokemontools/redsfxheaders.py @@ -0,0 +1,36 @@ +import configuration
+config = configuration.Config()
+rom = bytearray(open(config.rom_path, "r").read())
+
+headerlist = (
+ ["sfxheaders02.asm", 0x8003, 0x822e],
+ ["sfxheaders08.asm", 0x20003, 0x202be],
+ ["sfxheaders1f.asm", 0x7c003, 0x7c249],
+ )
+
+def printsfxheaders(filename, address, end):
+ file = open(filename, 'w')
+ bank = address / 0x4000
+ byte = rom[address]
+ sfx = 1
+ channel = 1
+ file.write("SFX_Headers_{:02x}:\n".format(bank))
+ file.write("\tdb $ff, $ff, $ff ; padding\n")
+ while address != end:
+ left = (byte >> 6) + 1
+ file.write("\nSFX_{:02x}_{:02x}: ; {:02x} ({:0x}:{:02x})\n".format(bank, sfx, address, bank, address % 0x4000 + 0x4000))
+ while left != 0:
+ pointer = rom[address + 2] * 0x100 + rom[address + 1]
+ if byte >> 4 != 0: file.write(" db ( ${:0x}0 | CH{:0x} )\n".format(byte >> 4, byte % 0x10))
+ else: file.write("\tdb CH{:0x}\n".format(byte))
+ file.write("\tdw SFX_{:02x}_{:02x}_Ch{}\n".format(bank, sfx, channel))
+ address += 3
+ byte = rom[address]
+ channel += 1
+ left -= 1
+ channel = 1
+ sfx += 1
+ file.write("\n; {}".format(hex(address)))
+
+for header in headerlist:
+ printsfxheaders(header[0], header[1], header[2])
\ No newline at end of file diff --git a/tests/integration/tests.py b/tests/integration/tests.py index 40933e5..4f96699 100644 --- a/tests/integration/tests.py +++ b/tests/integration/tests.py @@ -42,6 +42,10 @@ from pokemontools.helpers import ( index, ) +from pokemontools.crystalparts.old_parsers import ( + old_parse_map_header_at, +) + from pokemontools.crystal import ( rom, load_rom, @@ -65,7 +69,6 @@ from pokemontools.crystal import ( all_labels, write_all_labels, parse_map_header_at, - old_parse_map_header_at, process_00_subcommands, parse_all_map_headers, translate_command_byte, |