Exemplo n.º 1
0
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]
Exemplo n.º 2
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)
Exemplo n.º 3
0
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.")
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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.")
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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"]])