ines-extract.py with syntax coloring

This HTML file was generated with Kalle's syntaxcolor.py


   1"""
   2Extracts PRG-ROM data or CHR-ROM data from an iNES ROM file (.nes).
   3By Kalle (http://qalle.net)
   4Source of format: http://wiki.nesdev.com/w/index.php/INES
   5"""
   6
   7import sys
   8import os.path
   9
  10HELP_TEXT = """\
  11Extracts PRG-ROM data or CHR-ROM data from an iNES ROM file (.nes).
  12Arguments: sourceFile.nes whatToExtract targetFile
  13(whatToExtract: "p" or "P" = PRG-ROM, "c" or "C" = CHR-ROM)\
  14"""
  15
  16INES_HEADER_SIZE = 0x10
  17INES_IDENTIFIER = b"NES\x1a"
  18
  19# file buffer size in bytes (1 or greater; larger values are faster but take up
  20# more RAM)
  21BUFFER_SIZE = 2 ** 16
  22
  23def INES_get_info(handle):
  24    """Read an iNES file, validate it and return essential info."""
  25
  26    fileSize = handle.seek(0, 2)
  27    if fileSize < INES_HEADER_SIZE:
  28        exit("Invalid iNES file (smaller than correct header size).")
  29
  30    handle.seek(0)
  31    header = handle.read(INES_HEADER_SIZE)
  32
  33    if header[:4] != INES_IDENTIFIER:
  34        exit("Invalid iNES file (invalid identifier).")
  35
  36    prgSize = header[4] * 16 * 1024
  37    chrSize = header[5] * 8 * 1024
  38    trainerSize = (header[6] >> 2 & 0x1) * 512
  39
  40    if fileSize < INES_HEADER_SIZE + trainerSize + prgSize + chrSize:
  41        exit("Invalid iNES file (too small).")
  42
  43    return {
  44        "prgAddr": INES_HEADER_SIZE + trainerSize,
  45        "prgSize": prgSize,
  46        "chrAddr": INES_HEADER_SIZE + trainerSize + prgSize,
  47        "chrSize": chrSize,
  48    }
  49
  50def chunk_copy(sourceHnd, startAddr, bytesLeft, targetHnd):
  51    """Copies part of source file to target file in BUFFER_SIZE chunks.
  52    Returns number of bytes copied.
  53    """
  54
  55    sourceHnd.seek(startAddr)
  56    targetHnd.seek(0)
  57
  58    while bytesLeft > 0:
  59        chunkSize = min(bytesLeft, BUFFER_SIZE)
  60        targetHnd.write(sourceHnd.read(chunkSize))
  61        bytesLeft -= chunkSize
  62
  63    return targetHnd.tell()
  64
  65def INES_extract(sourceHnd, whatToExtract, targetHnd):
  66    """Copy data and return number of bytes copied."""
  67
  68    INESInfo = INES_get_info(sourceHnd)
  69
  70    if whatToExtract == "P":
  71        startAddr = INESInfo["prgAddr"]
  72        size = INESInfo["prgSize"]
  73    else:
  74        startAddr = INESInfo["chrAddr"]
  75        size = INESInfo["chrSize"]
  76
  77    if size == 0:
  78        exit("No data to copy!")
  79
  80    bytesCopied = chunk_copy(sourceHnd, startAddr, size, targetHnd)
  81
  82    return bytesCopied
  83
  84def main():
  85    if len(sys.argv) != 4:
  86        exit(HELP_TEXT)
  87
  88    (source, whatToExtract, target) = sys.argv[1:]
  89    whatToExtract = whatToExtract.upper()
  90
  91    try:
  92        if os.path.samefile(source, target):
  93            exit("Source and target files must not be the same.")
  94    except OSError:
  95        pass
  96
  97    if whatToExtract not in ("P", "C"):
  98        exit("Argument error. Run without arguments to see help.")
  99
 100    try:
 101        with open(source, "rb") as sourceHnd:
 102            with open(target, "wb") as targetHnd:
 103                bytesCopied = INES_extract(sourceHnd, whatToExtract, targetHnd)
 104    except FileNotFoundError:
 105        exit("File or path not found.")
 106    except PermissionError:
 107        exit("Permission denied.")
 108    except OSError:
 109        exit("File read/write error.")
 110
 111    print(
 112        "{:d} bytes of {:s}-ROM extracted."
 113        .format(bytesCopied, ("PRG" if whatToExtract == "P" else "CHR"))
 114    )
 115
 116if __name__ == "__main__":
 117    main()