diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/progress.py | 225 | ||||
-rw-r--r-- | tools/script_extractor.py | 284 | ||||
-rw-r--r-- | tools/tcgdisasm.py | 30 |
3 files changed, 536 insertions, 3 deletions
diff --git a/tools/progress.py b/tools/progress.py new file mode 100644 index 0000000..b1e1160 --- /dev/null +++ b/tools/progress.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3# +import subprocess +import math +import argparse + +# TODO - +# -- reportINCROMs -- + # add in percentage +# -- reportUnnamedSymbols -- + +# global vals +banks = 0x40 + +def main(): + parser = argparse.ArgumentParser(description='Progress checker for poketcg') + parser.add_argument('-i', '--incrom', action='store_true', help="Turns on incrom report") + parser.add_argument('-d', '--directory', default=".", help="Override incrom search directory. Ignores if incrom report is off") + parser.add_argument('-s', '--symfile', default=None, type=argparse.FileType('r'), help="Turns on Unnamed Symbol report using given sym file") + parser.add_argument('-f', '--function_source', action='store_true', help='Shows a breakdown of what bank each unnamed function comes from. Ignores if symfile report is off') + parser.add_argument('-o', '--other_unnamed', action='store_true', help='Shows all other unnamed symbols and a count of how many there are. Ignores if symfile report is off') + parser.add_argument('--list_funcs', nargs="+", default=None, help="Lists every unnamed function in the given banks. WILL BE LONG. ignores if symfile report is off") + + args = parser.parse_args() + + if args.incrom: + reportINCROMs(args.directory) + print("\n") + + if args.symfile != None: + # parse the list command + listBankSet = set([]) + if args.list_funcs != None: + listBankSet = parseBankList(args.list_funcs) + reportUnnamedSymbols(args.symfile,listBankSet, args.function_source, args.other_unnamed) + +def reportINCROMs(incromDir): + grepProc = subprocess.Popen(['grep', '-r', 'INCROM', incromDir], stdout=subprocess.PIPE) + targetLines = grepProc.communicate()[0].decode().split('\n') + incromBytes = [0]*banks + incromByteTotal = 0 + for line in targetLines: + line = line.lower() # ignore case + + # ignore the actual definition of the macro + if 'macro' in line: + continue + + # ignore anything in tools + if '/tools/' in line: + continue + + # ignore binary files in case swp's exist + if 'binary file' in line: + continue + + # find the last two hex location values + splitLine = line.split("$") + + # not sure what the line is, but it's not working so skip + if len(splitLine) < 3: + continue + + incEnd = int(splitLine[-1],16) + incStart = int(splitLine[-2].split(",",1)[0],16) + incBank = math.floor(incStart / 0x4000) + diff = incEnd - incStart + incromBytes[incBank] += diff + incromByteTotal += diff + print("Total: " + str(incromByteTotal) + " bytes") + print("Made up of the following: ") + for i in range(0,banks): + if incromBytes[i] == 0: + continue + + bankName= "bank" + format(i,"02x") + ": " + if i == 0: + bankName = "home: " + bytesString = str(incromBytes[i]) + formattingStrings = " "*(8-len(bytesString)) + print(bankName + bytesString + formattingStrings + "bytes") + + +# reads sym files and looks for instances of tcgdisasm's automatic symbols +def reportUnnamedSymbols(symfile, listBankSet, showFunctionBanks, showOtherUnnamed): + data = symfile.read().split("\n") + + # format [ [ "type" : number ], ... ] + typeCounts = [] + + # to cut back on for loops I'll manually list the super common ones, such as Func + funcCounts = [0]*banks + funcCount = 1 + branchCount = 0 + wramCount = 0 + sramCount = 0 + hramCount = 0 + + labelTotal = 0 + localLabelTotal = 0 + unnamedLocalLabelTotal = 0 + unnamedLabelTotal = 0 + + # expecting all lines to be formated as `bank:addr name` + for line in data: + + splitline = line.split(":") + + # line not formatted as expected + if len(splitline) < 2: + continue + + # at this point it's probably some form of label + if "." in line: + localLabelTotal += 1 + else: + labelTotal += 1 + + bank = int(splitline[0], 16) + splitline = splitline[1].split(" ") + + # line not formatted as expected + if len(splitline) < 2: + continue + + localAddr = int(splitline[0], 16) + name = splitline[1] + + globalAddr = bank*0x4000 + localAddr + if bank > 0: + globalAddr -= 0x4000 + + globalAddrString = format(globalAddr,"04x") + if name.endswith(globalAddrString): + + # don't pay as much attention to local labels + if "." in line: + unnamedLocalLabelTotal += 1 + continue + else: + unnamedLabelTotal += 1 + + labelType = name[0:len(globalAddrString)*-1] + + # take care of the common ones before looping + if labelType == "Func_": + if bank in listBankSet: + print("bank " + format(bank,'02x') + ":" + name) + funcCounts[bank] += 1 + funcCount += 1 + continue + elif labelType == "Branch_": + branchCount += 1 + continue + elif labelType == "w": + wramCount += 1 + continue + elif labelType in ["s0","s1","s2","s3"]: # all that are listed in sram.asm + sramCount += 1 + continue + elif labelType == "h": + hramCount += 1 + continue + + foundType = False + for tc in typeCounts: + if tc[0] == labelType: + tc[1] += 1 + foundType = True + + if not foundType: + typeCounts.append([labelType,1]) + + + # there are so many that I did them manually, but they're a misc type + typeCounts.append(["Branch_", branchCount]) + + # do some sorting. + typeCounts = sorted(typeCounts, key = lambda x: x[1], reverse = True) + + namedLabelTotal = labelTotal - unnamedLabelTotal + namedLabelPercent = round((namedLabelTotal / labelTotal)*100, 3) + namedLocalLabelTotal = localLabelTotal - unnamedLocalLabelTotal + namedLocalLabelPercent = round((namedLocalLabelTotal / localLabelTotal)*100, 3) + + print("Named Labels: " + str(namedLabelTotal) + "/" + str(labelTotal) + " (" + str(namedLabelPercent) + "%)") + print("Named Local Labels: " + str(namedLocalLabelTotal) + "/" + str(localLabelTotal) + " (" + str(namedLocalLabelPercent) + "%)") + print() + print("func count: " + str(funcCount)) + if showFunctionBanks: + for i in range(0,banks): + if funcCounts[i] == 0: + continue + bank = "bank" + format(i,"02x") + ":" + if i == 0: + bank = "home: " + print("\t" + bank + " " + str(funcCounts[i])) + + print("wram count: " + str(wramCount)) + print("sram count: " + str(sramCount)) + print("hram count: " + str(hramCount)) + if showOtherUnnamed: + print() + print("Additional types:") + + for tc in typeCounts: + spaces = " " * (30 - len(tc[0])) + if tc[1] == 1: + print(tc[0]) + continue + print(tc[0] + spaces + "x" + format(tc[1],"02")) + +def parseBankList(strList): + retSet = set([]) + for bankName in strList: + if bankName == "home": + retSet.add(0) + elif bankName.startswith("bank"): + retSet.add(int(bankName[4:],16)) + else: + retSet.add(int(bankName,0)) + return retSet + + +if __name__ == '__main__': + main() diff --git a/tools/script_extractor.py b/tools/script_extractor.py new file mode 100644 index 0000000..f043010 --- /dev/null +++ b/tools/script_extractor.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +############################################################################### +###### Use: python3 tools/script_extractor --noauto --error location ###### +###### --noauto turns off automatic script parsing (enter to continue) ###### +###### --error stops execution if an error occurs ###### +###### location can be local to bank or global. This program assumes ###### +###### every script is in bank 3, which seems to be the case. ###### +###### ###### +###### Script list is a work in progress. The following arguments are ###### +###### accepted and accounted for. ###### +###### b - byte w - word j - jump (within script) t - text (tx) ###### +###### f - flag d - direction i - decimal byte m - npc move ptr ###### +###### q - Used when the script's arguments have not been determined yet ###### +############################################################################### +import argparse + +# Quit Types +DO_NOT_QUIT = 0 +QUIT_CONTINUE_CODE = 1 +QUIT_JUMP = 2 +QUIT_SPECIAL = 3 +QUIT_DEBUG = -1 + +dir_list = ["NORTH","EAST","SOUTH","WEST"] + +def printHeader(loc, prefix): + ls = format(loc,"04x") + lsa = format(loc-0x8000,"04x") + print(prefix + ls + ": ; " + ls + " (3:" + lsa + ")" ) + +def extractMovement(game_data, loc, errQuit): + printHeader(loc, "NPCMovement_") + loc -= 1 # so we can continue without breaking things + while game_data[loc+1] != 0xff: + loc += 1 + dirLow = game_data[loc] & 0x0f + if dirLow > 3: + print("ERROR: [" + format(loc,"04x") + "] was not a valid direction. Got: " + format(game_data[loc],"02x")) + if errQuit: + return QUIT_DEBUG + continue + lineStr = "\tdb " + dir_list[dirLow] + dirHigh = game_data[loc] & 0xf0 + if dirHigh == 0x80: + lineStr += " | NO_MOVE" + elif dirHigh != 0x00: + print("ERROR: [" + format(loc,"04x") + "] was not a valid direction. Got: " + format(game_data[loc],"02x")) + if errQuit: + return QUIT_DEBUG + continue + print(lineStr) + print("\tdb $ff") + print("; " + format(loc+2,"04x")) + return DO_NOT_QUIT + +def decodeLine(scriptList, game_data, loc, ignore_broken, locList): + currLine = scriptList[game_data[loc]] + ret = "\trun_command " + currLine[0] + "\n" + loc+=1 + quit = currLine[2] + for c in currLine[1]: + if c == "b": + ret += "\tdb $" + format(game_data[loc],"02x") + "\n" + loc += 1 + elif c == "i": + ret += "\tdb " + str(game_data[loc]) + "\n" + loc += 1 + elif c == "w": + ret += "\tdw $" + format((game_data[loc] + (game_data[loc+1]<<8)),"04x") + "\n" + loc += 2 + elif c == "j": + wordLoc = (game_data[loc] + (game_data[loc+1]<<8)) + if wordLoc == 0000: + ret += "\tdw NO_JUMP\n" + else: + ret += "\tdw .ows_" + format(wordLoc+0x8000,"04x") + "\n" + locList.append(wordLoc) + loc += 2 + elif c == "t": + addr = (game_data[loc] + (game_data[loc+1]<<8)) + if addr == 0: + ret += "\tdw $0000\n" + else: + ret += "\ttx Text" + format(addr,"04x") + "\n" + loc += 2 + elif c == "f": + ret += "\tdb EVENT_FLAG_" + format(game_data[loc],"02X") + "\n" + loc += 1 + elif c == "d": + ret += "\tdb " + dir_list[game_data[loc]] + "\n" + loc += 1 + elif c == "m": + wordLoc = (game_data[loc] + (game_data[loc+1]<<8)) + ret += "\tdw NPCMovement_" + format(wordLoc + 0x8000, "04x") + "\n" + loc += 2 + elif c == "q": + print("haven't updated data for this yet") + if not ignore_broken: + quit = QUIT_DEBUG + else: + print("UNACCEPTED CHARACTER: " + c) + return (loc, ret, quit) + +def main(): + scriptList = createList() + locList = [] + + parser = argparse.ArgumentParser(description='Pokemon TCG Script Extractor') + parser.add_argument('--noauto', action='store_true', help='turns off automatic script parsing') + parser.add_argument('--error', action='store_true', help='stops execution if an error occurs') + parser.add_argument('-m', '--movement', action='store_true', help='interprets bytes as a movement sequence rather than a Script') + parser.add_argument('-r', '--rom', default="baserom.gbc", help='rom file to extract script from') + parser.add_argument('locations', nargs="+", help='locations to extract from. May be local to bank or global.') + args = parser.parse_args() + for locStr in args.locations: + loc = int(locStr,0) + if loc > 0x7fff: + # Must be a global location + loc -= 0x8000 + locList.append(loc) + + # this is a list of every start location we've read to avoid infinite loops + exploredList = [] + + with open(args.rom, "rb") as file: + game_data = file.read() + + auto = not args.noauto + end = DO_NOT_QUIT + ignore_broken = not args.error + while (len(locList) > 0 and end != QUIT_DEBUG): + locList.sort() # export parts in order somewhat + loc = locList.pop(0) + 0x8000 + if args.movement: + end = extractMovement(game_data,loc, args.error) + else: + end = printScript(game_data, loc, auto, ignore_broken, scriptList,\ + locList, exploredList) + +def printScript(game_data, loc, auto, ignore_broken, scriptList, \ + locList, exploredList): + if loc in exploredList: + return + exploredList.append(loc) + script = "" + end = DO_NOT_QUIT + if game_data[loc] != 0xe7: + #print("Error: first byte was not start_script") + print(".ows_" + format(loc,"04x")) + else: + + # TODO this is hacky please don't do this + printHeader(loc, "Script_") + loc += 1 + print("\tstart_script") + while end == DO_NOT_QUIT: + loc, outstr, end = decodeLine(scriptList,game_data,loc,ignore_broken,locList) + outstr = outstr[:-1] # [:-1] strips the newline at the end + if auto: + print(outstr) + else: + input(outstr) + warning = "" + if end == QUIT_CONTINUE_CODE: + warning = " WARNING: There is probably regular assembly here" + + print("; " + hex(loc) + warning) + + # if the next byte is a ret, print it for the continue_code case + if game_data[loc] == 0xc9: + print("\tret") + + return end + +def createList(): # this is a func just so all this can go at the bottom + # name, arg list, is an ender + return [ + ("ScriptCommand_EndScriptLoop1", "", QUIT_CONTINUE_CODE), + ("ScriptCommand_CloseAdvancedTextBox", "", DO_NOT_QUIT), + ("ScriptCommand_PrintTextString", "t", DO_NOT_QUIT), + ("Func_ccdc", "t", DO_NOT_QUIT), + ("ScriptCommand_AskQuestionJump", "tj", DO_NOT_QUIT), # more complex behavior too (jumping) + ("ScriptCommand_StartBattle", "bbb", DO_NOT_QUIT), + ("ScriptCommand_PrintVariableText", "tt", DO_NOT_QUIT), + ("Func_cda8", "bbbb", DO_NOT_QUIT), + ("ScriptCommand_PrintTextQuitFully", "t", QUIT_SPECIAL), + ("Func_cdcb", "", DO_NOT_QUIT), + ("ScriptCommand_MoveActiveNPCByDirection", "w", DO_NOT_QUIT), + ("ScriptCommand_CloseTextBox", "", DO_NOT_QUIT), + ("ScriptCommand_GiveBoosterPacks", "bbb", DO_NOT_QUIT), + ("Func_cf0c", "bj", DO_NOT_QUIT), # more complex behavior too (jumping) + ("Func_cf12", "bj", DO_NOT_QUIT), + ("ScriptCommand_GiveCard", "b", DO_NOT_QUIT), + ("ScriptCommand_TakeCard", "b", DO_NOT_QUIT), + ("Func_cf53", "w", DO_NOT_QUIT), # more complex behavior too (jumping) + ("Func_cf7b", "", DO_NOT_QUIT), + ("Func_cf2d", "bbbb", DO_NOT_QUIT), # more complex behavior too (jumping + ??) + ("Func_cf96", "w", DO_NOT_QUIT), # only jumps? still needs args to do that though + ("Func_cfc6", "b", DO_NOT_QUIT), + ("Func_cfd4", "", DO_NOT_QUIT), + ("Func_d00b", "", DO_NOT_QUIT), # includes something with random and extra data + ("Func_d025", "w", DO_NOT_QUIT), # possibly only jumps, still needs args + ("Func_d032", "w", DO_NOT_QUIT), # see above + ("Func_d03f", "", DO_NOT_QUIT), + ("ScriptCommand_Jump", "j", QUIT_JUMP), # jumps to d + ("ScriptCommand_TryGiveMedalPCPacks", "", DO_NOT_QUIT), + ("ScriptCommand_SetPlayerDirection", "d", DO_NOT_QUIT), + ("ScriptCommand_MovePlayer", "db", DO_NOT_QUIT), + ("ScriptCommand_ShowCardReceivedScreen", "b", DO_NOT_QUIT), + ("ScriptCommand_SetDialogName", "b", DO_NOT_QUIT), + ("ScriptCommand_SetNextNPCandScript", "bj", DO_NOT_QUIT), + ("Func_d095", "bbb", DO_NOT_QUIT), + ("Func_d0be", "bb", DO_NOT_QUIT), + ("ScriptCommand_DoFrames", "i", DO_NOT_QUIT), + ("Func_d0d9", "bbw", DO_NOT_QUIT), # jumps but still needs args + ("ScriptCommand_JumpIfPlayerCoordMatches", "iij", DO_NOT_QUIT), # jumps but still needs args + ("ScriptCommand_MoveActiveNPC", "m", DO_NOT_QUIT), + ("ScriptCommand_GiveOneOfEachTrainerBooster", "", DO_NOT_QUIT), + ("Func_d103", "q", DO_NOT_QUIT), + ("Func_d125", "b", DO_NOT_QUIT), + ("Func_d135", "b", DO_NOT_QUIT), + ("Func_d16b", "b", DO_NOT_QUIT), + ("Func_cd4f", "bbb", DO_NOT_QUIT), + ("Func_cd94", "q", DO_NOT_QUIT), + ("ScriptCommand_MoveWramNPC", "m", DO_NOT_QUIT), + ("Func_cdd8", "", DO_NOT_QUIT), + ("Func_cdf5", "bb", DO_NOT_QUIT), + ("Func_d195", "", DO_NOT_QUIT), + ("Func_d1ad", "", DO_NOT_QUIT), + ("Func_d1b3", "", DO_NOT_QUIT), + ("ScriptCommand_QuitScriptFully", "", QUIT_SPECIAL), + ("Func_d244", "q", DO_NOT_QUIT), + ("Func_d24c", "q", DO_NOT_QUIT), + ("ScriptCommand_OpenDeckMachine", "b", DO_NOT_QUIT), + ("Func_d271", "q", DO_NOT_QUIT), + ("ScriptCommand_EnterMap", "bbood", DO_NOT_QUIT), + ("ScriptCommand_MoveArbitraryNPC", "bm", DO_NOT_QUIT), + ("Func_d209", "", DO_NOT_QUIT), + ("Func_d38f", "b", DO_NOT_QUIT), + ("Func_d396", "b", DO_NOT_QUIT), + ("Func_cd76", "", DO_NOT_QUIT), + ("Func_d39d", "b", DO_NOT_QUIT), + ("Func_d3b9", "", DO_NOT_QUIT), + ("ScriptCommand_TryGivePCPack", "b", DO_NOT_QUIT), + ("ScriptCommand_nop", "", DO_NOT_QUIT), + ("Func_d3d4", "q", DO_NOT_QUIT), + ("Func_d3e0", "", DO_NOT_QUIT), + ("Func_d3fe", "q", DO_NOT_QUIT), + ("Func_d408", "b", DO_NOT_QUIT), + ("Func_d40f", "q", DO_NOT_QUIT), + ("ScriptCommand_PlaySFX", "b", DO_NOT_QUIT), + ("ScriptCommand_PauseSong", "q", DO_NOT_QUIT), + ("ScriptCommand_ResumeSong", "q", DO_NOT_QUIT), + ("Func_d41d", "", DO_NOT_QUIT), + ("ScriptCommand_WaitForSongToFinish", "q", DO_NOT_QUIT), + ("Func_d435", "b", DO_NOT_QUIT), + ("ScriptCommand_AskQuestionJumpDefaultYes", "tj", DO_NOT_QUIT), + ("Func_d2f6", "q", DO_NOT_QUIT), + ("Func_d317", "", DO_NOT_QUIT), + ("Func_d43d", "", DO_NOT_QUIT), + ("ScriptCommand_EndScriptLoop2", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop3", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop4", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop5", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop6", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_SetFlagValue", "fb", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagZero1", "fj", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagNonzero1", "q", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagEqual", "fbj", DO_NOT_QUIT), # also capable of jumping + ("ScriptCommand_JumpIfFlagNotEqual", "fbj", DO_NOT_QUIT), # jumps + ("ScriptCommand_JumpIfFlagNotLessThan", "fbj", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagLessThan", "fbj", DO_NOT_QUIT), + ("ScriptCommand_MaxOutFlagValue", "f", DO_NOT_QUIT), + ("ScriptCommand_ZeroOutFlagValue", "f", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagNonzero2", "fj", DO_NOT_QUIT), + ("ScriptCommand_JumpIfFlagZero2", "fj", DO_NOT_QUIT), + ("ScriptCommand_IncrementFlagValue", "f", DO_NOT_QUIT), + ("ScriptCommand_EndScriptLoop7", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop8", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop9", "q", QUIT_CONTINUE_CODE), + ("ScriptCommand_EndScriptLoop10", "q", QUIT_CONTINUE_CODE) + ] + +main() diff --git a/tools/tcgdisasm.py b/tools/tcgdisasm.py index d6731d0..579d577 100644 --- a/tools/tcgdisasm.py +++ b/tools/tcgdisasm.py @@ -304,12 +304,14 @@ bit_ops_table = [ "set 7, b", "set 7, c", "set 7, d", "set 7, e", "set 7, h", "set 7, l", "set 7, [hl]", "set 7, a" # $f8 - $ff ] -unconditional_returns = [0xc9, 0xd9] +unconditional_returns = [0xc9, 0xd9, 0xe7] # e7 begins a script, which is not handled by tcgdisasm absolute_jumps = [0xc3, 0xc2, 0xca, 0xd2, 0xda] call_commands = [0xcd, 0xc4, 0xcc, 0xd4, 0xdc, 0xdf, 0xef] relative_jumps = [0x18, 0x20, 0x28, 0x30, 0x38] unconditional_jumps = [0xc3, 0x18] +# the flag macros found in bank 3. They db a byte after calling so need to be treated specially +flag_macros = [(0xca8f,"set_flag_value {}"),(0xcacd,"zero_flag_value {}"),(0xca84,"zero_flag_value2 {}"), (0xcac2,"max_flag_value {}"), (0xca69,"get_flag_value {}")] def asm_label(address): """ @@ -740,11 +742,17 @@ class Disassembler(object): opcode_output_str = bit_ops_table[opcode_arg_1] elif opcode_nargs == 2: + + # define opcode_output_str as None so we can substitute our own if a macro appears + opcode_output_str = None + # opcodes with a pointer as an argument # format the two arguments into a little endian 16-bit pointer local_target_offset = opcode_arg_2 << 8 | opcode_arg_1 + # get the global offset of the pointer target_offset = get_global_address(local_target_offset, bank_id) + # attempt to look for a matching label if opcode_byte == 0xdf: # bank1call @@ -753,7 +761,22 @@ class Disassembler(object): # regular call or jump instructions target_label = self.find_label(local_target_offset, bank_id) - if opcode_byte in call_commands + absolute_jumps: + # handle the special flag macros + found_flag_macro = False + for flag_macro in flag_macros: + if flag_macro[0] == target_offset: + found_flag_macro = True + current_flag_macro = flag_macro + event_flag = "EVENT_FLAG_" + format(opcode_arg_3, "02X") + opcode_output_str = flag_macro[1].format(event_flag) + + # we need to skip a byte since this macro takes one extra + opcode_nargs+=1 + break + + + if not found_flag_macro and opcode_byte in call_commands + absolute_jumps: + if target_label is None: # if this is a call or jump opcode and the target label is not defined, create an undocumented label descriptor target_label = "Func_%x" % target_offset @@ -793,7 +816,8 @@ class Disassembler(object): data_tables[local_target_offset]["definition"] = False # format the label that was created into the opcode string - opcode_output_str = opcode_str.format(target_label) + if opcode_output_str is None: + opcode_output_str = opcode_str.format(target_label) elif opcode_nargs == 3: # macros with bank and pointer as an argument |