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 get_chr_addr_and_size(handle): # detect file type and get (address, size) of CHR ROM data fileInfo = qneslib.ines_header_decode(handle) if fileInfo is not None: # iNES ROM file if fileInfo["chrSize"] == 0: sys.exit("The input file is an iNES ROM file but has no CHR ROM.") return (fileInfo["chrStart"], fileInfo["chrSize"]) else: fileSize = handle.seek(0, 2) if fileSize == 0 or fileSize % 256: sys.exit( "The input file is neither an iNES ROM file nor a raw CHR data file." ) # raw CHR data file return (0, fileSize)
def main(): args = parse_arguments() try: with open(args.input_file, "rb") as source: # get file info fileInfo = qneslib.ines_header_decode(source) if fileInfo is None: sys.exit("Invalid iNES ROM file.") if fileInfo["chrSize"] == 0: sys.exit("Input file has no CHR ROM.") # length of CHR data before the tiles to modify beforeLen = args.first_tile * 16 if beforeLen >= fileInfo["chrSize"]: sys.exit("--first-tile is too large.") # length of CHR data at/after the tiles to modify if args.tile_count: modifyLen = args.tile_count * 16 afterLen = fileInfo["chrSize"] - beforeLen - modifyLen if afterLen < 0: sys.exit( "Sum of --first-tile and --tile-count is too large.") else: afterLen = 0 modifyLen = fileInfo["chrSize"] - beforeLen - afterLen # copy input file to output file source.seek(0) with open(args.output_file, "wb") as target: target.seek(0) # copy data before/at/after the tiles to be modified for chunk in read_file_slice(source, fileInfo["chrStart"] + beforeLen): target.write(chunk) for chunk in read_file_slice(source, modifyLen): target.write(swap_colors(chunk, args.colors)) for chunk in read_file_slice(source, afterLen): target.write(chunk) except OSError: sys.exit("Error reading/writing files.")
def find_slices_in_prg(handle, slices, comp, args): # generate PRG addresses of each slice (used with file2; comp = compare value) # read all PRG data fileInfo = qneslib.ines_header_decode(handle) handle.seek(fileInfo["prgStart"]) prgData = handle.read(fileInfo["prgSize"]) for (sliceBefore, sliceAfter) in slices: slice_ = sliceBefore + bytes((comp, )) + sliceAfter # PRG addresses of possible relevant bytes for prgAddr in range(len(sliceBefore), len(prgData) - len(sliceAfter)): # the relevant byte must always match if prgData[prgAddr] == comp: # if not too many different bytes around, yield PRG address of relevant byte prgSlice = prgData[prgAddr - len(sliceBefore):prgAddr + len(sliceAfter) + 1] differentByteCnt = sum(1 for (byte1, byte2) in zip(slice_, prgSlice) if byte1 != byte2) if differentByteCnt <= args.max_different_bytes: yield prgAddr
def main(): args = parse_arguments() try: with open(args.input_file, "rb") as source: # get info fileInfo = qneslib.ines_header_decode(source) if fileInfo is None: sys.exit("Invalid iNES ROM file.") # copy PRG ROM data if args.prg is not None: with open(args.prg, "wb") as target: source.seek(fileInfo["prgStart"]) target.seek(0) copy_file_slice(source, fileInfo["prgSize"], target) # copy CHR ROM data if args.chr is not None and fileInfo["chrSize"]: with open(args.chr, "wb") as target: source.seek(fileInfo["chrStart"]) target.seek(0) copy_file_slice(source, fileInfo["chrSize"], target) except OSError: sys.exit("Error reading/writing files.")
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)
def extract_map(sourceHnd, args): # extract one ultra-subblock image, subblock image, block image and/or map image from the file # parse iNES header fileInfo = qneslib.ines_header_decode(sourceHnd) if fileInfo is None: sys.exit("Not a valid iNES ROM file.") if not is_blaster_master(fileInfo): sys.exit("The file doesn't look like Blaster Master.") (prgBank, worldPtr, chrBank) = MAP_DATA_ADDRESSES[args.map_number] prgBank = 4 if prgBank == 2 and args.japan else prgBank # the only version difference scrollPtr = worldPtr + 2 # read PRG bank sourceHnd.seek(fileInfo["prgStart"] + prgBank * 16 * 1024) prgBankData = sourceHnd.read(16 * 1024) worldAddr = decode_offset(prgBankData[worldPtr:worldPtr + 2]) scrollAddr = decode_offset(prgBankData[scrollPtr:scrollPtr + 2]) if args.verbose: print(f"Map={args.map_number}, PRG bank={prgBank}, CHR bank={chrBank}") print(f"World data @ {worldAddr:04x}, scroll data @ {scrollAddr:04x}") (palAddr, usbAttrAddr, usbAddr, sbAddr, blockAddr, mapAddr) = (decode_offset(prgBankData[pos:pos + 2]) for pos in range(worldAddr, worldAddr + 12, 2)) if args.verbose: print(f"Palette @ {palAddr:04x}, " f"USBs @ {usbAddr:04x}, " f"SBs @ {sbAddr:04x}, " f"blocks @ {blockAddr:04x}, " f"map @ {mapAddr:04x}, " f"USB attributes @ {usbAttrAddr:04x}") assert worldAddr < palAddr < usbAddr < sbAddr < blockAddr < mapAddr \ < min(usbAttrAddr, scrollAddr) # palette (4*4 NES color numbers) worldPalette = prgBankData[palAddr:palAddr + 16] assert max(worldPalette) <= 0x3f # ultra-subblock data assert sbAddr - usbAddr in range(4, 257 * 4, 4) usbData = tuple(prgBankData[i:i + 4] for i in range(usbAddr, sbAddr, 4)) # subblock data assert blockAddr - sbAddr in range(4, 257 * 4, 4) sbData = tuple(prgBankData[i:i + 4] for i in range(sbAddr, blockAddr, 4)) assert max(itertools.chain.from_iterable(sbData)) < len(usbData) # block data assert mapAddr - blockAddr in range(4, 257 * 4, 4) blockData = tuple(prgBankData[i:i + 4] for i in range(blockAddr, mapAddr, 4)) assert max(itertools.chain.from_iterable(blockData)) < len(sbData) # map data mapEnd = min(usbAttrAddr, scrollAddr) assert mapEnd - mapAddr in range(32, 33 * 32, 32) mapData = prgBankData[mapAddr:mapEnd] assert max(set(mapData)) < len(blockData) # read ultra-subblock attribute data (1 byte/ultra-subblock) usbAttrData = prgBankData[usbAttrAddr:usbAttrAddr + len(usbData)] # read and decode tile data sourceHnd.seek(fileInfo["chrStart"] + chrBank * 4 * 1024) tileData = list(get_tile_data(sourceHnd)) # create ultra-subblock image (needed for creating all other images) usbImg = create_ultra_subblock_image(usbData, usbAttrData, tileData, worldPalette) if args.ultra_subblock_image is not None: # save ultra-subblock image with open(args.ultra_subblock_image, "wb") as target: usbImg.save(target) if args.subblock_image is not None: # create and save subblock image sbImg = create_subblock_image(sbData, usbImg, worldPalette) with open(args.subblock_image, "wb") as target: sbImg.save(target) if args.block_image is not None: # create and save block image blockImg = create_block_image(blockData, sbData, usbImg, worldPalette) with open(args.block_image, "wb") as target: blockImg.save(target) if args.map_image is not None: # create and save map image mapImg = create_map_image(mapData, blockData, sbData, usbImg, worldPalette) with open(args.map_image, "wb") as target: target.seek(0) mapImg.save(target)
import os, sys import qneslib # qalle's NES library, https://github.com/qalle2/nes-util # parse command line arguments if len(sys.argv) != 2: sys.exit("Print information of an iNES ROM file (.nes).") inputFile = sys.argv[1] if not os.path.isfile(inputFile): sys.exit("File not found.") # get info try: with open(inputFile, "rb") as handle: fileInfo = qneslib.ines_header_decode(handle) except OSError: sys.exit("Error reading the file.") if fileInfo is None: sys.exit("Invalid iNES ROM file.") # print info print("trainer size:", fileInfo["trainerSize"]) print("PRG ROM size:", fileInfo["prgSize"]) print("CHR ROM size:", fileInfo["chrSize"]) print("iNES mapper number:", fileInfo["mapper"]) print("name table mirroring:", { "h": "horizontal", "v": "vertical", "f": "four-screen" }[fileInfo["mirroring"]]) print("has extra RAM:", ["no", "yes"][fileInfo["extraRam"]])