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