def get_prg_addresses(fileInfo, args, handle): # get PRG ROM addresses affected by code in file1 (cpuAddr, repl, comp) = qneslib.game_genie_decode(args.code) if comp is None and qneslib.is_prg_bankswitched(fileInfo["prgSize"], fileInfo["mapper"]): sys.exit( "Six-letter codes not supported because file1 uses PRG ROM bankswitching." ) prgBankSize = qneslib.min_prg_bank_size(fileInfo["prgSize"], fileInfo["mapper"]) prgAddresses = set() if comp is None or comp != repl: if comp is None: # 6-letter code (old value must not equal replace value) validValues = set(range(0x100)) - {repl} else: # 8-letter code (old value must equal compare value) validValues = {comp} for prgAddr in qneslib.address_cpu_to_prg(cpuAddr, prgBankSize, fileInfo["prgSize"]): handle.seek(fileInfo["prgStart"] + prgAddr) if handle.read(1)[0] in validValues: prgAddresses.add(prgAddr) return prgAddresses
def get_fake_compare_value(handle, args): # get a fake compare value from file1 for user's six-letter code cpuAddr = qneslib.game_genie_decode(args.code)[0] fileInfo = qneslib.ines_header_decode(handle) prgAddr = cpuAddr & (fileInfo["prgSize"] - 1) handle.seek(fileInfo["prgStart"] + prgAddr) return handle.read(1)[0]
def print_results(cpuAddresses, compareValue, args): # print codes with new addresses (sort by difference from original address) (origCpuAddr, replaceValue) = qneslib.game_genie_decode(args.code)[:2] cpuAddresses = sorted(cpuAddresses) cpuAddresses.sort(key=lambda addr: abs(addr - origCpuAddr)) print( "Game Genie codes for file2 (try the first one first):", ", ".join( qneslib.game_genie_encode(a, replaceValue, compareValue) for a in cpuAddresses))
def print_decoded_code(args): (addr, repl, comp) = qneslib.game_genie_decode(args.code) comp = "none" if comp is None else f"{comp:02x}" print( f"Code: CPU address={addr:04x}, replace value={repl:02x}, compare value={comp}" )
def parse_arguments(): # parse command line arguments using argparse parser = argparse.ArgumentParser( description= "Convert an NES Game Genie code from one version of a game to another using " "both iNES ROM files (.nes). Technical explanation: decode the code; find out PRG ROM " "addresses affected in file1; see what's in and around them; look for similar bytestrings " "in file2's PRG ROM; convert the addresses back into CPU addresses; encode them into " "codes.") parser.add_argument( "-s", "--slice-length", type=int, default=4, help= "How many PRG ROM bytes to compare both before and after the relevant byte (that is, " "total number of bytes compared is twice this value, plus one). Fewer bytes will be " "compared if the relevant byte is too close to start or end of PRG ROM. 1 to 20, " "default=4. Decrease to get more results.") parser.add_argument( "-d", "--max-different-bytes", type=int, default=1, help= "Maximum number of non-matching bytes allowed in each pair of PRG ROM slices to " "compare. (The relevant byte must always match.) Minimum=0, default=1, maximum=twice " "--slice-length, minus one. Increase to get more results.") parser.add_argument( "-v", "--verbose", action="store_true", help= "Print more information. Note: all printed numbers are hexadecimal.") parser.add_argument( "code", help= "An NES Game Genie code that is known to work with file1. Six-letter codes are not " "allowed if file1 uses PRG ROM bankswitching.") parser.add_argument( "file1", help= "An iNES ROM file (.nes) to read. The game your code is known to work with." ) parser.add_argument( "file2", help= "Another iNES ROM file (.nes) to read. The equivalent code for this game will be " "searched for.") args = parser.parse_args() if not 1 <= args.slice_length <= 20: sys.exit("Invalid --slice-length.") if not 0 <= args.max_different_bytes < 2 * args.slice_length: sys.exit("Invalid --max-different-bytes.") if qneslib.game_genie_decode(args.code) is None: sys.exit("Invalid code.") if not os.path.isfile(args.file1): sys.exit("file1 not found.") if not os.path.isfile(args.file2): sys.exit("file2 not found.") return args
def main(): args = parse_arguments() if args.verbose: print_decoded_code(args) compareValue = qneslib.game_genie_decode(args.code)[2] # get PRG addresses, PRG slices and optionally a fake compare value from file1 try: with open(args.file1, "rb") as handle: fileInfo = qneslib.ines_header_decode(handle) if fileInfo is None: sys.exit("file1 is not a valid iNES ROM file.") prgAddresses = get_prg_addresses(fileInfo, args, handle) if not prgAddresses: sys.exit("Your code seems to affect file1 in no way.") slices = set(get_prg_slices(prgAddresses, fileInfo, args, handle)) if compareValue is None: compareValue = get_fake_compare_value(handle, args) if args.verbose: print(f"Using fake compare value: {compareValue:02x}") except OSError: sys.exit("Error reading file1.") if args.verbose: print("PRG addresses in file1:", ", ".join(f"{addr:04x}" for addr in sorted(prgAddresses))) print_slices(slices, compareValue) # find PRG addresses in file2 try: with open(args.file2, "rb") as handle: fileInfo = qneslib.ines_header_decode(handle) if fileInfo is None: sys.exit("file2 is not a valid iNES ROM file.") prgAddresses = set( find_slices_in_prg(handle, slices, compareValue, args)) except OSError: sys.exit("Error reading file2.") if not prgAddresses: sys.exit( "file2 contains nothing similar to what your code affects in file1." ) if args.verbose: print("PRG address matches in file2:", ", ".join(f"{a:04x}" for a in sorted(prgAddresses))) # convert PRG addresses into CPU addresses cpuAddresses = set() prgBankSize = qneslib.min_prg_bank_size(fileInfo["prgSize"], fileInfo["mapper"]) for prgAddr in prgAddresses: cpuAddresses.update(qneslib.address_prg_to_cpu(prgAddr, prgBankSize)) if args.verbose: print("CPU address matches in file2:", ", ".join(f"{a:04x}" for a in sorted(cpuAddresses))) # if file2 not bankswitched, discard compare value to output six-letter codes if not qneslib.is_prg_bankswitched(fileInfo["prgSize"], fileInfo["mapper"]): compareValue = None print_results(cpuAddresses, compareValue, args)
import sys import qneslib # qalle's NES library, https://github.com/qalle2/nes-util if len(sys.argv) == 2: # decode Game Genie code values = qneslib.game_genie_decode(sys.argv[1]) if values is None: sys.exit("Invalid Game Genie code.") # reencode to get canonical form code = qneslib.game_genie_encode(*values) elif 3 <= len(sys.argv) <= 4: # encode Game Genie code try: values = [int(n, 16) for n in sys.argv[1:]] except ValueError: sys.exit("Invalid hexadecimal integers.") code = qneslib.game_genie_encode(*values) if code is None: sys.exit("Hexadecimal integers out of range.") # redecode to get canonical form values = qneslib.game_genie_decode(code) else: sys.exit( "Encode and decode NES Game Genie codes. Argument: six-letter code, eight-letter code, " "AAAA RR or AAAA RR CC (AAAA = address in hexadecimal, RR = replacement value in " "hexadecimal, CC = compare value in hexadecimal)." ) comp = "none" if values[2] is None else f"0x{values[2]:02x}" print( f"{code}: CPU address = 0x{values[0]:04x}, replace value = 0x{values[1]:02x}, compare value = "
import os, sys import qneslib # qalle's NES library, https://github.com/qalle2/nes-util # read args if len(sys.argv) != 3: sys.exit( "Find the PRG ROM addresses affected by an NES Game Genie code in an iNES ROM file (.nes). " "Args: file code") (filename, code) = sys.argv[1:] # decode the code values = qneslib.game_genie_decode(code) if values is None: sys.exit("Invalid code.") (cpuAddr, replaceValue, compareValue) = values if not os.path.isfile(filename): sys.exit("File not found.") try: with open(filename, "rb") as handle: # get file info fileInfo = qneslib.ines_header_decode(handle) if fileInfo is None: sys.exit("Invalid iNES ROM file.") # get PRG ROM addresses prgAddresses = [] if compareValue is None or compareValue != replaceValue: if compareValue is None: # 6-letter code (old value must not equal replace value) validValues = set(range(0x100)) - {