From 79630cd43b8e814afa517b473344d065217c851a Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 14:43:43 -0500 Subject: wram: more consistent rgbasm value conversion --- pokemontools/wram.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pokemontools/wram.py b/pokemontools/wram.py index 1f61ff5..d34737e 100644 --- a/pokemontools/wram.py +++ b/pokemontools/wram.py @@ -9,6 +9,11 @@ import os NUM_OBJECTS = 0x10 OBJECT_LENGTH = 0x10 + +def rgbasm_to_py(text): + return text.replace('$', '0x').replace('%', '0b') + + def make_wram_labels(wram_sections): wram_labels = {} for section in wram_sections: @@ -30,7 +35,7 @@ def read_bss_sections(bss): if 'SECTION' in line: if section: sections.append(section) # last section - address = eval(line[line.find('[')+1:line.find(']')].replace('$','0x')) + address = eval(rgbasm_to_py(line[line.find('[')+1:line.find(']')])) section = { 'name': line.split('"')[1], #'type': line.split(',')[1].split('[')[0].strip(), @@ -49,7 +54,7 @@ def read_bss_sections(bss): }] elif line[:3] == 'ds ': - length = eval(line[3:line.find(';')].replace('$','0x')) + length = eval(rgbasm_to_py(line[3:line.find(';')])) address += length # adjacent labels use the same space for label in section['labels'][::-1]: @@ -61,14 +66,14 @@ def read_bss_sections(bss): elif 'EQU' in line: # some space is defined using constants name, value = line.split('EQU') - name, value = name.strip(), value.strip().replace('$','0x').replace('%','0b') + name, value = name.strip(), rgbasm_to_py(value) globals()[name] = eval(value) sections.append(section) return sections def constants_to_dict(constants): - return dict((eval(constant[constant.find('EQU')+3:constant.find(';')].replace('$','0x')), constant[:constant.find('EQU')].strip()) for constant in constants) + return dict((eval(rgbasm_to_py(constant[constant.find('EQU')+3:constant.find(';')])), constant[:constant.find('EQU')].strip()) for constant in constants) def scrape_constants(text): if type(text) is not list: -- cgit v1.2.3 From 765e846a9eac19008e00a15cf4dcf05653e614c9 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 14:45:52 -0500 Subject: crystal: fix storetext and rework PointerLabel{Before,After}Bank - storetext only takes one param - the logic for PointerLabel*Bank was actually in preprocessor.py. theyve also been changed to take a single label --- pokemontools/crystal.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index eb88b6b..3385945 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -945,12 +945,19 @@ class PointerLabelParam(MultiByteParam): class PointerLabelBeforeBank(PointerLabelParam): bank = True # bank appears first, see calculate_pointer_from_bytes_at - size = 3 - byte_type = "dw" + byte_type = 'db' + + @staticmethod + def from_asm(value): + return 'BANK({0})\n\tdw {0}'.format(value) class PointerLabelAfterBank(PointerLabelParam): bank = "reverse" # bank appears last, see calculate_pointer_from_bytes_at - size = 3 + byte_type = 'dw' + + @staticmethod + def from_asm(value): + return '{0}\n\tdb BANK({0})'.format(value) class ScriptPointerLabelParam(PointerLabelParam): pass @@ -2358,7 +2365,7 @@ pksv_crystal_more = { 0xA1: ["halloffame"], 0xA2: ["credits"], 0xA3: ["warpfacing", ["facing", SingleByteParam], ["map_group", MapGroupParam], ["map_id", MapIdParam], ["x", SingleByteParam], ["y", SingleByteParam]], - 0xA4: ["storetext", ["pointer", PointerLabelBeforeBank], ["memory", SingleByteParam]], + 0xA4: ["storetext", ["memory", SingleByteParam]], 0xA5: ["displaylocation", ["id", SingleByteParam], ["memory", SingleByteParam]], 0xA6: ["trainerclassname", ["id", SingleByteParam]], 0xA7: ["name", ["type", SingleByteParam], ["id", SingleByteParam]], -- cgit v1.2.3 From 07f7b1b892ac39597cb7e17fa95e3f52e617f5a2 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 14:56:06 -0500 Subject: preprocessor: get rid of PointerLabel cruft now pretty much everything uses from_asm --- pokemontools/preprocessor.py | 46 +++----------------------------------------- 1 file changed, 3 insertions(+), 43 deletions(-) diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index f4e92b6..d6b7a43 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -604,64 +604,24 @@ class Preprocessor(object): if do_macro_sanity_check: self.check_macro_sanity(params, macro, original_line) - # used for storetext - correction = 0 output = "" index = 0 while index < len(params): - param_type = macro.param_types[index - correction] + param_type = macro.param_types[index] description = param_type["name"].strip() param_klass = param_type["class"] byte_type = param_klass.byte_type # db or dw - size = param_klass.size param = params[index].strip() - # param_klass.to_asm() won't work here because it doesn't - # include db/dw. - - # some parameters are really multiple types of bytes - if (byte_type == "dw" and size != 2) or \ - (byte_type == "db" and size != 1): - - output += ("; " + description + "\n") - - if size == 3 and is_based_on(param_klass, "PointerLabelBeforeBank"): - # write the bank first - output += ("db " + param + "\n") - # write the pointer second - output += ("dw " + params[index+1].strip() + "\n") - index += 2 - correction += 1 - elif size == 3 and is_based_on(param_klass, "PointerLabelAfterBank"): - # write the pointer first - output += ("dw " + param + "\n") - # write the bank second - output += ("db " + params[index+1].strip() + "\n") - index += 2 - correction += 1 - elif size == 3 and "from_asm" in dir(param_klass): - output += ("\t" + byte_type + " " + param_klass.from_asm(param) + "\n") - index += 1 - else: - raise exceptions.MacroException( - "dunno what to do with this macro param ({klass}) in line: {line}" - .format( - klass=param_klass, - line=original_line, - ) - ) - - elif "from_asm" in dir(param_klass): + if "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 += ("\t" + byte_type + " " + param + " ; " + description + "\n") - index += 1 + index += 1 sys.stdout.write(output) -- cgit v1.2.3 From a648e0a8cdc6c7d981a5641f4dc4d5b02e23e81e Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 15:08:19 -0500 Subject: preprocessor: remove some redundant code in macro_translator --- pokemontools/preprocessor.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index d6b7a43..1fac44d 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -581,12 +581,6 @@ class Preprocessor(object): if show_original_lines: sys.stdout.write("; original_line: " + original_line) - # rgbasm can handle "db" so no preprocessing is required, plus this wont be - # reached because of earlier checks in macro_test. - if macro.macro_name in ["db", "dw"]: - sys.stdout.write(original_line) - return - # rgbasm can handle other macros too if "is_rgbasm_macro" in dir(macro): if macro.is_rgbasm_macro: -- cgit v1.2.3 From 3a586ef165b8022b0f5159dfdd586789f3166e70 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 15:15:19 -0500 Subject: preprocessor: simplify macro_translator --- pokemontools/preprocessor.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index 1fac44d..e032125 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -598,24 +598,18 @@ class Preprocessor(object): if do_macro_sanity_check: self.check_macro_sanity(params, macro, original_line) - output = "" - - index = 0 - while index < len(params): + for index in xrange(len(params)): param_type = macro.param_types[index] description = param_type["name"].strip() param_klass = param_type["class"] - byte_type = param_klass.byte_type # db or dw + byte_type = param_klass.byte_type param = params[index].strip() if "from_asm" in dir(param_klass): - output += ("\t" + byte_type + " " + param_klass.from_asm(param) + " ; " + description + "\n") - - else: - output += ("\t" + byte_type + " " + param + " ; " + description + "\n") + param = param_klass.from_asm(param) - index += 1 + output += ("\t" + byte_type + " " + param + " ; " + description + "\n") sys.stdout.write(output) -- cgit v1.2.3 From fef1cdd4f2d5760ce6742e40cb256296f597b836 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 2 Dec 2013 23:48:24 -0500 Subject: preprocessor: str.split() already removes preceding/trailing whitespace --- pokemontools/preprocessor.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/pokemontools/preprocessor.py b/pokemontools/preprocessor.py index e032125..5ac7724 100644 --- a/pokemontools/preprocessor.py +++ b/pokemontools/preprocessor.py @@ -553,29 +553,17 @@ class Preprocessor(object): original_line = line - # remove trailing newline - if line[-1] == "\n": - line = line[:-1] + has_tab = line[0] == "\t" - # remove first tab - has_tab = False - if line[0] == "\t": - has_tab = True - line = line[1:] - - # remove duplicate whitespace (also trailing) + # remove whitespace line = " ".join(line.split()) - params = [] - # check if the line has params if " " in line: # split the line into separate parameters params = line.replace(token, "").split(",") - - # check if there are no params (redundant) - if len(params) == 1 and params[0] == "": - raise exceptions.MacroException("macro has no params?") + else: + params = [] # write out a comment showing the original line if show_original_lines: -- cgit v1.2.3 From a401ebc620fdca69aab2ed8feb91fa696647b17c Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 4 Dec 2013 01:12:45 -0500 Subject: crystal: make PointerLabelParam output just a label to match the new from_asm handling --- pokemontools/crystal.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pokemontools/crystal.py b/pokemontools/crystal.py index 3385945..eed7c32 100644 --- a/pokemontools/crystal.py +++ b/pokemontools/crystal.py @@ -853,8 +853,6 @@ class PointerLabelParam(MultiByteParam): # bank can be overriden if "bank" in kwargs.keys(): if kwargs["bank"] != False and kwargs["bank"] != None and kwargs["bank"] in [True, "reverse"]: - # not +=1 because child classes set size=3 already - self.size = self.default_size + 1 self.given_bank = kwargs["bank"] #if kwargs["bank"] not in [None, False, True, "reverse"]: # raise Exception("bank cannot be: " + str(kwargs["bank"])) @@ -927,8 +925,11 @@ class PointerLabelParam(MultiByteParam): bank_part = "$%.2x" % (pointers.calculate_bank(caddress)) else: bank_part = "BANK("+label+")" + # for labels, expand bank_part at build time + if bank in ["reverse", True] and label: + return pointer_part # return the asm based on the order the bytes were specified to be in - if bank == "reverse": # pointer, bank + elif bank == "reverse": # pointer, bank return pointer_part+", "+bank_part elif bank == True: # bank, pointer return bank_part+", "+pointer_part @@ -944,6 +945,7 @@ class PointerLabelParam(MultiByteParam): raise Exception("this should never happen") class PointerLabelBeforeBank(PointerLabelParam): + size = 3 bank = True # bank appears first, see calculate_pointer_from_bytes_at byte_type = 'db' @@ -952,6 +954,7 @@ class PointerLabelBeforeBank(PointerLabelParam): return 'BANK({0})\n\tdw {0}'.format(value) class PointerLabelAfterBank(PointerLabelParam): + size = 3 bank = "reverse" # bank appears last, see calculate_pointer_from_bytes_at byte_type = 'dw' -- cgit v1.2.3 From 697e2fa2ade2face0756f17858057548071bf506 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 8 Dec 2013 17:19:38 -0500 Subject: audio: sort output by content --- pokemontools/audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 38fd65f..0e7d375 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -26,7 +26,7 @@ conf = configuration.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)) + return sorted(set(asms), key=lambda (x,y,z):(x,z,not y.startswith(';'), ':' not in y, y)) class NybbleParam: size = 0.5 -- cgit v1.2.3 From 049891c4edc4c78e649be3d925e28e057072e142 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sun, 8 Dec 2013 22:16:20 -0500 Subject: battle animations --- pokemontools/battle_animations.py | 294 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 pokemontools/battle_animations.py diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py new file mode 100644 index 0000000..96f0090 --- /dev/null +++ b/pokemontools/battle_animations.py @@ -0,0 +1,294 @@ +# coding: utf-8 + +import os +from new import classobj + +import configuration +conf = configuration.Config() + +from crystal import ( + SingleByteParam, + PointerLabelParam, + DecimalParam, + BigEndianParam, + Command, + load_rom +) + +from gbz80disasm import get_local_address, get_global_address +from audio import sort_asms + + +from wram import read_constants + +rom = bytearray(load_rom()) + +sfx_constants = read_constants(os.path.join(conf.path, 'constants/sfx_constants.asm')) +class SoundEffectParam(SingleByteParam): + def to_asm(self): + if self.byte in sfx_constants.keys(): + sfx_constant = sfx_constants[self.byte] + return sfx_constant + return SingleByteParam.to_asm(self) + +anim_gfx_constants = read_constants(os.path.join(conf.path, 'constants/gfx_constants.asm')) +class AnimGFXParam(SingleByteParam): + def to_asm(self): + if self.byte in anim_gfx_constants.keys(): + return anim_gfx_constants[self.byte] + return SingleByteParam.to_asm(self) + +anims = read_constants(os.path.join(conf.path, 'constants/animation_constants.asm')) +objs = { k: v for k, v in anims.items() if 'ANIM_OBJ' in v } +bgs = { k: v for k, v in anims.items() if 'ANIM_BG' in v } +anims = { k: v.replace('ANIM_','') for k, v in anims.items() } +from move_constants import moves +anims.update(moves) + +class AnimObjParam(SingleByteParam): + def to_asm(self): + if self.byte in objs.keys(): + return objs[self.byte] + return SingleByteParam.to_asm(self) + +class BGEffectParam(SingleByteParam): + def to_asm(self): + if self.byte in bgs.keys(): + return bgs[self.byte] + return SingleByteParam.to_asm(self) + + +battle_animation_commands = { + 0xd0: ['anim_obj', ['obj', AnimObjParam], ['x', DecimalParam], ['y', DecimalParam], ['param', SingleByteParam]], + 0xd1: ['anim_1gfx', ['gfx1', AnimGFXParam]], + 0xd2: ['anim_2gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam]], + 0xd3: ['anim_3gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam]], + 0xd4: ['anim_4gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam]], + 0xd5: ['anim_5gfx', ['gfx1', AnimGFXParam], ['gfx2', AnimGFXParam], ['gfx3', AnimGFXParam], ['gfx4', AnimGFXParam], ['gfx5', AnimGFXParam]], + 0xd6: ['anim_incobj', ['id', SingleByteParam]], + 0xd7: ['anim_setobj', ['id', SingleByteParam], ['obj', AnimObjParam]], # bug: second param is interpreted as a command if not found in the object array + 0xd8: ['anim_incbgeffect', ['effect', BGEffectParam]], + 0xd9: ['anim_enemyfeetobj'], + 0xda: ['anim_playerheadobj'], + 0xdb: ['anim_checkpokeball'], + 0xdc: ['anim_transform'], + 0xdd: ['anim_raisesub'], + 0xde: ['anim_dropsub'], + 0xdf: ['anim_resetobp0'], + 0xe0: ['anim_sound', ['tracks', SingleByteParam], ['id', SoundEffectParam]], + 0xe1: ['anim_cry', ['pitch', SingleByteParam]], + 0xe2: ['anim_minimizeopp'], # unused + 0xe3: ['anim_oamon'], + 0xe4: ['anim_oamoff'], + 0xe5: ['anim_clearobjs'], + 0xe6: ['anim_beatup'], + 0xe7: ['anim_0xe7'], # nothing + 0xe8: ['anim_updateactorpic'], + 0xe9: ['anim_minimize'], + 0xea: ['anim_0xea'], # nothing + 0xeb: ['anim_0xeb'], # nothing + 0xec: ['anim_0xec'], # nothing + 0xed: ['anim_0xed'], # nothing + 0xee: ['anim_jumpand', ['value', SingleByteParam], ['address', PointerLabelParam]], + 0xef: ['anim_jumpuntil', ['address', PointerLabelParam]], + 0xf0: ['anim_bgeffect', ['effect', BGEffectParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam], ['unknown', SingleByteParam]], + 0xf1: ['anim_bgp', ['colors', SingleByteParam]], + 0xf2: ['anim_obp0', ['colors', SingleByteParam]], + 0xf3: ['anim_obp1', ['colors', SingleByteParam]], + 0xf4: ['anim_clearsprites'], + 0xf5: ['anim_0xf5'], # nothing + 0xf6: ['anim_0xf6'], # nothing + 0xf7: ['anim_0xf7'], # nothing + 0xf8: ['anim_jumpif', ['value', SingleByteParam], ['address', PointerLabelParam]], + 0xf9: ['anim_setvar', ['value', SingleByteParam]], + 0xfa: ['anim_incvar'], + 0xfb: ['anim_jumpvar', ['value', SingleByteParam], ['address', PointerLabelParam]], + 0xfc: ['anim_jump', ['address', PointerLabelParam]], + 0xfd: ['anim_loop', ['count', SingleByteParam], ['address', PointerLabelParam]], + 0xfe: ['anim_call', ['address', PointerLabelParam]], + 0xff: ['anim_ret'], +} + +battle_animation_enders = [ + 'anim_jump', + 'anim_ret', +] + +def create_battle_animation_classes(): + classes = [] + for cmd, command in battle_animation_commands.items(): + cmd_name = command[0] + params = { + 'id': cmd, + 'size': 1, + 'end': cmd_name in battle_animation_enders, + 'macro_name': cmd_name, + 'param_types': {}, + } + for i, (name, class_) in enumerate(command[1:]): + params['param_types'][i] = {'name': name, 'class': class_} + params['size'] += class_.size + class_name = cmd_name + 'Command' + class_ = classobj(class_name, (Command,), params) + globals()[class_name] = class_ + classes += [class_] + return classes + +battle_animation_classes = create_battle_animation_classes() + + +class Wait(Command): + macro_name = 'anim_wait' + size = 1 + end = macro_name in battle_animation_enders + param_types = { + 0: {'name': 'duration', 'class': DecimalParam}, + } + override_byte_check = True + + +class BattleAnim: + + def __init__(self, address, base_label=None, label=None, used_labels=[]): + self.start_address = address + self.address = address + + self.base_label = base_label + if self.base_label == None: + self.base_label = 'BattleAnim_' + hex(self.start_address) + + self.label = label + if self.label == None: + self.label = self.base_label + + self.used_labels = used_labels + + self.output = [] + self.labels = [] + self.label_asm = ( + self.start_address, + '%s: ; %x' % (self.label, self.start_address), + self.start_address + ) + self.labels += [self.label_asm] + self.used_labels += [self.label_asm] + + self.parse() + + def parse(self): + + done = False + while not done: + cmd = rom[self.address] + class_ = self.get_command_class(cmd)(address=self.address) + asm = class_.to_asm() + + # label jumps/calls + for key, param in class_.param_types.items(): + if param['class'] == PointerLabelParam: + label_address = class_.params[key].parsed_address + label = '%s_branch_%x' % (self.base_label, label_address) + label_def = '%s: ; %x' % (label, label_address) + label_asm = (label_address, label_def, label_address) + if label_asm not in self.used_labels: + self.labels += [label_asm] + 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 == 'anim_loop': + if class_.params[0].byte == 0: + done = True + + # last_address comment + self.output += [(self.address, '; %x\n' % self.address, self.address)] + + # parse any other branches too + self.labels = list(set(self.labels)) + for address, asm, last_address in self.labels: + if not (self.start_address <= address < self.address) and (address, asm, last_address) not in self.used_labels: + self.used_labels += [(address, asm, last_address)] + sub = BattleAnim(address=address, base_label=self.base_label, label=asm.split(':')[0], used_labels=self.used_labels) + self.output += sub.output + self.labels += sub.labels + + self.output = list(set(self.output)) + self.labels = list(set(self.labels)) + + def to_asm(self): + output = sorted(self.output + self.labels, key = lambda (x, y, z): (x, z)) + text = '' + for (address, asm, last_address) in output: + text += asm + '\n' + #text += '; %x\n' % last_address + return text + + def get_command_class(self, cmd): + if cmd < 0xd0: + return Wait + for class_ in battle_animation_classes: + if class_.id == cmd: + return class_ + return None + + +def battle_anim_label(i): + if i in anims.keys(): + base_label = 'BattleAnim_%s' % anims[i].title().replace('_','') + else: + base_label = 'BattleAnim_%d' % i + return base_label + +def dump_battle_anims(table_address=0xc906f, num_anims=278): + """ + Dump each battle animation from a pointer table. + """ + + asms = [] + + asms += [(table_address, 'BattleAnimations: ; %x' % table_address, table_address)] + + address = table_address + bank = address / 0x4000 + + for i in xrange(num_anims): + pointer_address = address + anim_address = rom[pointer_address] + rom[pointer_address + 1] * 0x100 + anim_address = get_global_address(anim_address, bank) + base_label = battle_anim_label(i) + address += 2 + + # anim pointer + asms += [(pointer_address, '\tdw %s' % base_label, address)] + + # anim script + anim = BattleAnim(address=anim_address, base_label=base_label) + asms += anim.output + anim.labels + + asms += [(address, '; %x\n' % address, address)] + + # jp sonicboom + anim = BattleAnim(address=0xc9c00, base_label='BattleAnim_Sonicboom_JP') + asms += anim.output + anim.labels + + asms = sort_asms(asms) + return asms + +def print_asm_list(asms): + # incbin any unknown areas + # not really needed since there are no gaps + last = asms[0][0] + for addr, asm, last_addr in asms: + if addr > last: + print '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n' % (last, addr, last) + if addr >= last: + print asm + last = last_addr + +if __name__ == '__main__': + asms = dump_battle_anims() + print_asm_list(asms) + -- cgit v1.2.3 From 973a78167b104479bb6c8c4c528ae914106b1b53 Mon Sep 17 00:00:00 2001 From: yenatch Date: Thu, 19 Dec 2013 19:35:21 -0500 Subject: gfx: use RGB asm macros for .pal files simpler to edit by hand than bin dumps --- pokemontools/gfx.py | 108 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index 8397337..e452da4 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1012,7 +1012,7 @@ def get_uncompressed_gfx(start, num_tiles, filename): -def hex_to_rgb(word): +def bin_to_rgb(word): red = word & 0b11111 word >>= 5 green = word & 0b11111 @@ -1020,23 +1020,39 @@ def hex_to_rgb(word): blue = word & 0b11111 return (red, green, blue) -def grab_palettes(address, length=0x80): +def rgb_from_rom(address, length=0x80): + return convert_binary_pal_to_text(rom[address:address+length]) + +def convert_binary_pal_to_text_by_filename(filename): + with open(filename) as f: + pal = bytearray(f.read()) + return convert_binary_pal_to_text(pal) + +def convert_binary_pal_to_text(pal): output = '' - for word in range(length/2): - color = ord(rom[address+1])*0x100 + ord(rom[address]) - address += 2 - color = hex_to_rgb(color) - red = str(color[0]).zfill(2) - green = str(color[1]).zfill(2) - blue = str(color[2]).zfill(2) - output += '\tRGB '+red+', '+green+', '+blue + words = [hi * 0x100 + lo for lo, hi in zip(pal[::2], pal[1::2])] + for word in words: + red, green, blue = ['%.2d' % c for c in bin_to_rgb(word)] + output += '\tRGB ' + ', '.join((red, green, blue)) output += '\n' return output +def read_rgb_macros(lines): + colors = [] + for line in lines: + macro = line.split(" ")[0].strip() + if macro == 'RGB': + params = ' '.join(line.split(" ")[1:]).split(',') + red, green, blue = [int(v) for v in params] + colors += [[red, green, blue]] + return colors - - +def rewrite_binary_pals_to_text(filenames): + for filename in filenames: + pal_text = convert_binary_pal_to_text_by_filename(filename) + with open(filename, 'w') as out: + out.write(pal_text) def dump_monster_pals(): @@ -1147,6 +1163,9 @@ def to_lines(image, width): def dmg2rgb(word): + """ + For PNGs. + """ def shift(value): while True: yield value & (2**5 - 1) @@ -1159,27 +1178,49 @@ def dmg2rgb(word): def rgb_to_dmg(color): + """ + For PNGs. + """ word = (color['r'] / 8) word += (color['g'] / 8) << 5 word += (color['b'] / 8) << 10 return word -def png_pal(filename): - with open(filename, 'rb') as pal_data: - words = pal_data.read() - dmg_pals = [] - for word in range(len(words)/2): - dmg_pals.append(ord(words[word*2]) + ord(words[word*2+1])*0x100) +def pal_to_png(filename): + """ + Interpret a .pal file as a png palette. + """ + with open(filename) as rgbs: + colors = read_rgb_macros(rgbs.readlines()) + a = 255 palette = [] + for color in colors: + # even distribution over 000-255 + r, g, b = [int(hue * 8.25) for hue in color] + palette += [(r, g, b, a)] white = (255,255,255,255) black = (000,000,000,255) - for word in dmg_pals: palette += [dmg2rgb(word)] - if white not in dmg_pals and len(palette) < 4: palette = [white] + palette - if black not in dmg_pals and len(palette) < 4: palette += [black] + if white not in palette and len(palette) < 4: + palette = [white] + palette + if black not in palette and len(palette) < 4: + palette = palette + [black] return palette +def png_to_rgb(palette): + """ + Convert a png palette to rgb macros. + """ + output = '' + for color in palette: + r, g, b = [color[c] / 8 for c in 'rgb'] + output += '\tRGB ' + ', '.join(['%.2d' % hue for hue in (r, g, b)]) + output += '\n' + return output + + + def export_2bpp_to_png(filein, fileout=None, pal_file=None, height=0, width=0): if fileout == None: fileout = os.path.splitext(filein)[0] + '.png' @@ -1234,7 +1275,7 @@ def convert_2bpp_to_png(image, width=0, height=0, pal_file=None): px_map = [[3 - pixel for pixel in line] for line in lines] else: # gbc color - palette = png_pal(pal_file) + palette = pal_to_png(pal_file) greyscale = False bitdepth = 8 px_map = [[pixel for pixel in line] for line in lines] @@ -1371,13 +1412,22 @@ def png_to_2bpp(filein): def export_palette(palette, filename): + """ + Export a palette from png to rgb macros in a .pal file. + """ + if os.path.exists(filename): - output = [] - for color in palette: - word = rgb_to_dmg(color) - output += [word & 0xff] - output += [word >> 8] - to_file(filename, output) + + # Pic palettes are 2 colors (black/white are added later). + with open(filename) as rgbs: + colors = read_rgb_macros(rgbs.readlines()) + + if len(colors) == 2: + palette = palette[1:3] + + text = png_to_rgb(palette) + with open(filename, 'w') as out: + out.write(text) def png_to_lz(filein): @@ -1566,6 +1616,8 @@ def expand_pic_palettes(): with open(filename, 'wb') as out: out.write(w + palette + b) + + if __name__ == "__main__": debug = False -- cgit v1.2.3 From b5c3ef9b4fbbc14d9baa125b10ebf4d96a79de7e Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 21 Dec 2013 00:38:11 -0500 Subject: gfx: 1bpp-to-png option --- pokemontools/gfx.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index e452da4..147621b 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -1679,6 +1679,9 @@ if __name__ == "__main__": elif argv[1] == 'png-to-1bpp': export_png_to_1bpp(argv[2]) + elif argv[1] == '1bpp-to-png': + export_1bpp_to_png(argv[2]) + elif argv[1] == '2bpp-to-lz': if argv[2] == '--vert': filein = argv[3] -- cgit v1.2.3 From d996bcf18e89826563e548014bbddddc677af229 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 23 Dec 2013 03:32:50 -0500 Subject: map_editor: read rgb macros instead of binary data for palettes also fix tileset graphics handling --- pokemontools/map_editor.py | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/pokemontools/map_editor.py b/pokemontools/map_editor.py index 43042cb..b9a6b61 100644 --- a/pokemontools/map_editor.py +++ b/pokemontools/map_editor.py @@ -445,7 +445,7 @@ class Tileset: def get_tiles(self): filename = self.get_tileset_gfx_filename() if not os.path.exists(filename): - gfx.to_png(filename.replace('.png','.2bpp'), filename) + gfx.export_lz_to_png(filename.replace('.png','.lz')) self.img = Image.open(filename) self.img.width, self.img.height = self.img.size self.tiles = [] @@ -505,30 +505,9 @@ class Tileset: self.palettes = get_palettes(filename) def get_palettes(filename): - pals = bytearray(open(filename, 'rb').read()) - - num_colors = 4 - color_length = 2 - - palette_length = num_colors * color_length - - num_pals = len(pals) / palette_length - - palettes = [] - for pal in xrange(num_pals): - palettes += [[]] - - for color in xrange(num_colors): - i = pal * palette_length - i += color * color_length - word = pals[i] + pals[i+1] * 0x100 - palettes[pal] += [[ - c & 0x1f for c in [ - word >> 0, - word >> 5, - word >> 10, - ] - ]] + lines = open(filename, 'r').readlines() + colors = gfx.read_rgb_macros(lines) + palettes = [colors[i:i+4] for i in xrange(0, len(colors), 4)] return palettes def get_available_maps(config=config): -- cgit v1.2.3 From 9dc5b70cac00b23b8f61d485bd938a7adc30abf8 Mon Sep 17 00:00:00 2001 From: yenatch Date: Mon, 23 Dec 2013 03:40:04 -0500 Subject: map_gfx: read rgb macros for palettes --- pokemontools/map_gfx.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/pokemontools/map_gfx.py b/pokemontools/map_gfx.py index 77e7d56..b06f0df 100644 --- a/pokemontools/map_gfx.py +++ b/pokemontools/map_gfx.py @@ -178,29 +178,9 @@ def read_palettes(time_of_day=1, config=config): filename = "{}.pal".format(actual_time_of_day) filepath = os.path.join(config.palette_dir, filename) - num_colors = 4 - color_length = 2 - palette_length = num_colors * color_length - - pals = bytearray(open(filepath, "rb").read()) - num_pals = len(pals) / palette_length - - for pal in xrange(num_pals): - palettes += [[]] - - for color in xrange(num_colors): - i = pal * palette_length - i += color * color_length - word = pals[i] + pals[i+1] * 0x100 - - palettes[pal] += [[ - c & 0x1f for c in [ - word >> 0, - word >> 5, - word >> 10, - ] - ]] - + lines = open(filepath, "r").readlines() + colors = gfx.read_rgb_macros(lines) + palettes = [colors[i:i+4] for i in xrange(0, len(colors), 4)] return palettes def load_sprite_image(address, config=config): -- cgit v1.2.3 From 12f0578a6c2dd0c75a4ef1aa9499b8137d094e1e Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:10:44 -0500 Subject: audio: more readable asm list sorting --- pokemontools/audio.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 0e7d375..9b0ff85 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -24,9 +24,22 @@ conf = configuration.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, y)) + """Sort and remove duplicates from a list of tuples. + Format (address, asm, last_address)""" + def is_comment(asm): + return asm.startswith(';') + def is_label(asm): + return ':' in asm + def sort_method(asm_list): + address, asm, last_address = asm_list + return ( + address, + last_address, + not is_comment(asm), + not is_label(asm), + asm + ) + return sorted(set(asms), key=sort_method) class NybbleParam: size = 0.5 -- cgit v1.2.3 From f0538721a3cc55a909462f42af077a749aca5e38 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:11:03 -0500 Subject: battle_animations: dont combine print and text conversion --- pokemontools/battle_animations.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py index 96f0090..f32ed1b 100644 --- a/pokemontools/battle_animations.py +++ b/pokemontools/battle_animations.py @@ -277,18 +277,19 @@ def dump_battle_anims(table_address=0xc906f, num_anims=278): asms = sort_asms(asms) return asms -def print_asm_list(asms): - # incbin any unknown areas - # not really needed since there are no gaps +def asm_list_to_text(asms): + output = '' last = asms[0][0] for addr, asm, last_addr in asms: if addr > last: - print '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n' % (last, addr, last) + # incbin any unknown areas + output += '\nINCBIN "baserom.gbc", $%x, $%x - $%x\n\n\n' % (last, addr, last) if addr >= last: - print asm + output += asm + '\n' last = last_addr + return output if __name__ == '__main__': asms = dump_battle_anims() - print_asm_list(asms) + print asm_list_to_text(asms) -- cgit v1.2.3 From dd471c6d5ddde53be5fffffa44ad323bde95ade9 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:19:06 -0500 Subject: rename battle animation command class Wait to BattleAnimWait --- pokemontools/battle_animations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py index f32ed1b..349c6fa 100644 --- a/pokemontools/battle_animations.py +++ b/pokemontools/battle_animations.py @@ -137,7 +137,7 @@ def create_battle_animation_classes(): battle_animation_classes = create_battle_animation_classes() -class Wait(Command): +class BattleAnimWait(Command): macro_name = 'anim_wait' size = 1 end = macro_name in battle_animation_enders @@ -228,7 +228,7 @@ class BattleAnim: def get_command_class(self, cmd): if cmd < 0xd0: - return Wait + return BattleAnimWait for class_ in battle_animation_classes: if class_.id == cmd: return class_ -- cgit v1.2.3 From bddb346ca075e8cc666957b190207c68e1152a76 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:31:58 -0500 Subject: battle_animations: dont use globals for macros in class BattleAnim --- pokemontools/battle_animations.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py index 349c6fa..4b641e7 100644 --- a/pokemontools/battle_animations.py +++ b/pokemontools/battle_animations.py @@ -149,7 +149,7 @@ class BattleAnimWait(Command): class BattleAnim: - def __init__(self, address, base_label=None, label=None, used_labels=[]): + def __init__(self, address, base_label=None, label=None, used_labels=[], macros=[]): self.start_address = address self.address = address @@ -173,6 +173,8 @@ class BattleAnim: self.labels += [self.label_asm] self.used_labels += [self.label_asm] + self.macros = macros + self.parse() def parse(self): @@ -211,7 +213,13 @@ class BattleAnim: for address, asm, last_address in self.labels: if not (self.start_address <= address < self.address) and (address, asm, last_address) not in self.used_labels: self.used_labels += [(address, asm, last_address)] - sub = BattleAnim(address=address, base_label=self.base_label, label=asm.split(':')[0], used_labels=self.used_labels) + sub = BattleAnim( + address=address, + base_label=self.base_label, + label=asm.split(':')[0], + used_labels=self.used_labels, + macros=self.macros + ) self.output += sub.output self.labels += sub.labels @@ -229,7 +237,7 @@ class BattleAnim: def get_command_class(self, cmd): if cmd < 0xd0: return BattleAnimWait - for class_ in battle_animation_classes: + for class_ in self.macros: if class_.id == cmd: return class_ return None @@ -242,7 +250,7 @@ def battle_anim_label(i): base_label = 'BattleAnim_%d' % i return base_label -def dump_battle_anims(table_address=0xc906f, num_anims=278): +def dump_battle_anims(table_address=0xc906f, num_anims=278, macros=battle_animation_classes): """ Dump each battle animation from a pointer table. """ @@ -265,13 +273,21 @@ def dump_battle_anims(table_address=0xc906f, num_anims=278): asms += [(pointer_address, '\tdw %s' % base_label, address)] # anim script - anim = BattleAnim(address=anim_address, base_label=base_label) + anim = BattleAnim( + address=anim_address, + base_label=base_label, + macros=macros + ) asms += anim.output + anim.labels asms += [(address, '; %x\n' % address, address)] # jp sonicboom - anim = BattleAnim(address=0xc9c00, base_label='BattleAnim_Sonicboom_JP') + anim = BattleAnim( + address=0xc9c00, + base_label='BattleAnim_Sonicboom_JP', + macros=macros + ) asms += anim.output + anim.labels asms = sort_asms(asms) -- cgit v1.2.3 From 906472a8f1758743a04beb1727f7d05b72174596 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:44:31 -0500 Subject: battle_animations: docstring for BattleAnim, consistent sorting --- pokemontools/battle_animations.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py index 4b641e7..678e9ce 100644 --- a/pokemontools/battle_animations.py +++ b/pokemontools/battle_animations.py @@ -148,6 +148,17 @@ class BattleAnimWait(Command): class BattleAnim: + """ + A list of battle animation commands read from a given address. + + Results in a list of commands (self.output) and a list of labels (self.labels). + Format is (address, asm, last_address). Includes any subroutines and their output. + + To convert to text, use self.to_asm(). + + For combining multiple BattleAnims, take self.output + self.labels from each + and sort with sort_asms. + """ def __init__(self, address, base_label=None, label=None, used_labels=[], macros=[]): self.start_address = address @@ -227,11 +238,10 @@ class BattleAnim: self.labels = list(set(self.labels)) def to_asm(self): - output = sorted(self.output + self.labels, key = lambda (x, y, z): (x, z)) + output = sort_asms(self.output + self.labels) text = '' for (address, asm, last_address) in output: text += asm + '\n' - #text += '; %x\n' % last_address return text def get_command_class(self, cmd): -- cgit v1.2.3 From d983b8a02ad046ee9a671f2a5b7c330b4084b8ec Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:52:55 -0500 Subject: battle_animations: docstring for battle_anim_label --- pokemontools/battle_animations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pokemontools/battle_animations.py b/pokemontools/battle_animations.py index 678e9ce..ffc89e4 100644 --- a/pokemontools/battle_animations.py +++ b/pokemontools/battle_animations.py @@ -254,6 +254,9 @@ class BattleAnim: def battle_anim_label(i): + """ + Return a label matching the name of a battle animation by id. + """ if i in anims.keys(): base_label = 'BattleAnim_%s' % anims[i].title().replace('_','') else: -- cgit v1.2.3 From 020ae38d348d895ccdd36f9c2bdce4c22505d627 Mon Sep 17 00:00:00 2001 From: yenatch Date: Wed, 25 Dec 2013 06:54:52 -0500 Subject: wram: return an empty dict for missing constants files --- pokemontools/wram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pokemontools/wram.py b/pokemontools/wram.py index 1029c8e..2133444 100644 --- a/pokemontools/wram.py +++ b/pokemontools/wram.py @@ -117,10 +117,10 @@ def read_constants(filepath): """ Load lines from a file and call scrape_constants. """ - lines = None - - with open(filepath, "r") as file_handler: - lines = file_handler.readlines() + lines = [] + if os.path.exists(filepath): + with open(filepath, "r") as file_handler: + lines = file_handler.readlines() constants = scrape_constants(lines) return constants -- cgit v1.2.3 From 486483ef19dcfbb3041f6a9bcf20b4b7412cba57 Mon Sep 17 00:00:00 2001 From: yenatch Date: Fri, 27 Dec 2013 21:46:29 -0500 Subject: don't use children functions in sort_asms --- pokemontools/audio.py | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 9b0ff85..c310a50 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -23,23 +23,37 @@ import configuration conf = configuration.Config() +def is_label(asm): + return ':' in asm + +def is_comment(asm): + return asm.startswith(';') + +def asm_sort(asm_def): + """ + Sort key for asm lists. + + Usage: + list.sort(key=asm_sort) + sorted(list, key=asm_sort) + """ + address, asm, last_address = asm_def + return ( + address, + last_address, + not is_comment(asm), + not is_label(asm), + asm + ) + def sort_asms(asms): - """Sort and remove duplicates from a list of tuples. - Format (address, asm, last_address)""" - def is_comment(asm): - return asm.startswith(';') - def is_label(asm): - return ':' in asm - def sort_method(asm_list): - address, asm, last_address = asm_list - return ( - address, - last_address, - not is_comment(asm), - not is_label(asm), - asm - ) - return sorted(set(asms), key=sort_method) + """ + Sort and remove duplicates from an asm list. + + Format: [(address, asm, last_address), ...] + """ + return sorted(set(asms), key=asm_sort) + class NybbleParam: size = 0.5 @@ -212,10 +226,10 @@ class Channel: output = sort_asms(self.output + self.labels) text = '' for i, (address, asm, last_address) in enumerate(output): - if ':' in asm: + if is_label(asm): # dont print labels for empty chunks for (address_, asm_, last_address_) in output[i:]: - if ':' not in asm_: + if not is_label(asm_): text += '\n' + asm + '\n' break else: -- cgit v1.2.3 From 981e0c634f6da555fb74e5e2c948c13a820ec6ef Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 28 Dec 2013 18:32:35 -0500 Subject: audio: fix Noise and size handling for nybble params --- pokemontools/audio.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index c310a50..699bebf 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -70,15 +70,18 @@ class NybbleParam: 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 + @staticmethod + def from_asm(value): + return value + +class HiNybbleParam(NybbleParam): + which = 'hi' + class LoNybbleParam(NybbleParam): which = 'lo' - def to_asm(self): - return '%d' % self.nybble class PitchParam(HiNybbleParam): def to_asm(self): @@ -93,10 +96,9 @@ class PitchParam(HiNybbleParam): pitch += '_' return pitch - class Note(Command): macro_name = "note" - size = 1 + size = 0 end = False param_types = { 0: {"name": "pitch", "class": PitchParam}, @@ -110,6 +112,7 @@ class Note(Command): self.params = [] byte = rom[self.address] current_address = self.address + size = 0 for (key, param_type) in self.param_types.items(): name = param_type["name"] class_ = param_type["class"] @@ -119,12 +122,20 @@ class Note(Command): self.params += [obj] current_address += obj.size + size += obj.size + + # can't fit bytes into nybbles + if obj.size > 0.5: + if current_address % 1: + current_address = int(ceil(current_address)) + if size % 1: + size = int(ceil(size)) self.params = dict(enumerate(self.params)) - # obj sizes were 0.5, but were working with ints + # obj sizes were 0.5, but we're working with ints current_address = int(ceil(current_address)) - self.size = int(ceil(self.size)) + self.size += int(ceil(size)) self.last_address = current_address return True @@ -132,7 +143,6 @@ class Note(Command): class Noise(Note): macro_name = "noise" - size = 0 end = False param_types = { 0: {"name": "duration", "class": LoNybbleParam}, @@ -241,7 +251,7 @@ class Channel: for class_ in sound_classes: if class_.id == i: return class_ - if self.channel in [4, 8]: return Noise + if self.channel == 8: return Noise return Note @@ -347,7 +357,6 @@ def dump_sounds(origin, names, base_label='Sound_'): 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): @@ -413,5 +422,4 @@ def generate_crystal_cry_pointers(): if __name__ == '__main__': dump_crystal_music() - dump_crystal_sfx() -- cgit v1.2.3 From 82389c999856e58b443c025228ecddd24dc6e4ab Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 28 Dec 2013 19:53:32 -0500 Subject: audio: note duration from 0-15 to 1-16 to match pokered --- pokemontools/audio.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 699bebf..898c646 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -96,13 +96,23 @@ class PitchParam(HiNybbleParam): pitch += '_' return pitch +class NoteDurationParam(LoNybbleParam): + def to_asm(self): + self.nybble += 1 + return LoNybbleParam.to_asm(self) + + @staticmethod + def from_asm(value): + value = str(int(value) - 1) + return LoNybbleParam.from_asm(value) + class Note(Command): macro_name = "note" size = 0 end = False param_types = { 0: {"name": "pitch", "class": PitchParam}, - 1: {"name": "duration", "class": LoNybbleParam}, + 1: {"name": "duration", "class": NoteDurationParam}, } allowed_lengths = [2] override_byte_check = True -- cgit v1.2.3 From 70cd4f7c00b33a398ed7af071773c06ca335c105 Mon Sep 17 00:00:00 2001 From: yenatch Date: Sat, 28 Dec 2013 19:57:37 -0500 Subject: audio: use labels.line_has_label and clean up crystal imports --- pokemontools/audio.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pokemontools/audio.py b/pokemontools/audio.py index 898c646..0cdfcbc 100644 --- a/pokemontools/audio.py +++ b/pokemontools/audio.py @@ -5,14 +5,13 @@ import os from math import ceil from gbz80disasm import get_global_address, get_local_address - -import crystal +from labels import line_has_label from crystal import music_classes as sound_classes - from crystal import ( Command, SingleByteParam, MultiByteParam, + PointerLabelParam, load_rom, ) @@ -23,9 +22,6 @@ import configuration conf = configuration.Config() -def is_label(asm): - return ':' in asm - def is_comment(asm): return asm.startswith(';') @@ -42,7 +38,7 @@ def asm_sort(asm_def): address, last_address, not is_comment(asm), - not is_label(asm), + not line_has_label(asm), asm ) @@ -202,7 +198,7 @@ class Channel: # label any jumps or calls for key, param in class_.param_types.items(): - if param['class'] == crystal.PointerLabelParam: + if param['class'] == PointerLabelParam: label_address = class_.params[key].parsed_address label = '%s_branch_%x' % ( self.base_label, @@ -246,10 +242,10 @@ class Channel: output = sort_asms(self.output + self.labels) text = '' for i, (address, asm, last_address) in enumerate(output): - if is_label(asm): + if line_has_label(asm): # dont print labels for empty chunks for (address_, asm_, last_address_) in output[i:]: - if not is_label(asm_): + if not line_has_label(asm_): text += '\n' + asm + '\n' break else: -- cgit v1.2.3