nes-gg.py with syntax coloring

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


   1"""
   2NES Game Genie code decoder/encoder
   3By Kalle (http://qalle.net)
   4Source of format: "NES Game Genie Code Format DOC v0.71" by Benzene
   5(http://nesdev.com/nesgg.txt)
   6"""
   7
   8HELP_TEXT = """\
   9Argument: one of the following:
  10    - 6-letter NES Game Genie code (e.g. "SXIOPO")
  11    - 8-letter NES Game Genie code (e.g. "YEUZUGAA")
  12    - 4-digit address and 2-digit replacement value, both in hexadecimal,
  13      with the latter optionally preceded by a colon (e.g. "91D9AD" or
  14      "91D9:AD")
  15    - 4-digit address, 2-digit compare value and 2-digit replacement value,
  16      all in hexadecimal, with the compare value optionally preceded by a
  17      question mark and the replacement value optionally preceded by a colon
  18      (e.g. "ACB30007" or "ACB3?00:07")\
  19"""
  20
  21import sys
  22import re
  23
  24LETTERS = tuple("APZLGITYEOXUKSVN")
  25
  26# which decoded hexadecimal digit each letter of a 6-letter Game Genie code
  27# corresponds to
  28DIGIT_POS_SHORT_HI = (4, 2, 0, 3, 1, 5)
  29DIGIT_POS_SHORT_LO = (5, 4, 2, 0, 3, 1)  # prev. table shifted right
  30
  31# which decoded hexadecimal digit each letter of an 8-letter Game Genie code
  32# corresponds to
  33DIGIT_POS_LONG_HI = (6, 2, 0, 3, 1, 5, 4, 7)
  34DIGIT_POS_LONG_LO = (7, 6, 2, 0, 3, 1, 5, 4)  # prev. table shifted right
  35
  36# regexes for validating input
  37RE_CODE6 = re.compile(r"[APZLGITYEOXUKSVN]{6}", re.A | re.I)
  38RE_CODE8 = re.compile(r"[APZLGITYEOXUKSVN]{8}", re.A | re.I)
  39RE_NUMERIC6 = re.compile(r"[\dA-F]{4}:?[\dA-F]{2}", re.A | re.I)
  40RE_NUMERIC8 = re.compile(r"[\dA-F]{4}\??[\dA-F]{2}:?[\dA-F]{2}", re.A | re.I)
  41
  42def decode_short(code):
  43    """Decodes a six-letter Game Genie code."""
  44
  45    code = code.upper()
  46    digits = []
  47
  48    for digitPos in range(6):
  49        posHi = DIGIT_POS_SHORT_HI.index(digitPos)
  50        posLo = DIGIT_POS_SHORT_LO.index(digitPos)
  51        valueHi = LETTERS.index(code[posHi])
  52        valueLo = LETTERS.index(code[posLo])
  53        digits.append(valueHi & 0x8 | valueLo & 0x7)
  54
  55    decodedNumber = int("".join(format(digit, "x") for digit in digits), 16)
  56    (address, replace) = divmod(decodedNumber, 0x100)
  57
  58    return (address | 0x8000, replace)
  59
  60def decode_long(code):
  61    """Decodes an eight-letter Game Genie code."""
  62
  63    code = code.upper()
  64    digits = []
  65
  66    for digitPos in range(8):
  67        posHi = DIGIT_POS_LONG_HI.index(digitPos)
  68        posLo = DIGIT_POS_LONG_LO.index(digitPos)
  69        valueHi = LETTERS.index(code[posHi])
  70        valueLo = LETTERS.index(code[posLo])
  71        digits.append(valueHi & 0x8 | valueLo & 0x7)
  72
  73    decodedNumber = int("".join(format(digit, "x") for digit in digits), 16)
  74    (addressAndCompare, replace) = divmod(decodedNumber, 0x100)
  75    (address, compare) = divmod(addressAndCompare, 0x100)
  76
  77    return (address | 0x8000, compare, replace)
  78
  79def encode_short(address, replace):
  80    """Encodes a six-letter Game Genie code."""
  81
  82    # clear MSB of address (causes 3rd letter to be one of APZLGITY)
  83    addressAndReplace = ((address & 0x7FFF) << 8) | replace
  84
  85    letterValues = []
  86
  87    for letterPos in range(6):
  88        digitPosHi = DIGIT_POS_SHORT_HI[letterPos]
  89        digitPosLo = DIGIT_POS_SHORT_LO[letterPos]
  90        digitHi = addressAndReplace >> (5 - digitPosHi) * 4 & 0xF
  91        digitLo = addressAndReplace >> (5 - digitPosLo) * 4 & 0xF
  92        letterValues.append(digitHi & 0x8 | digitLo & 0x7)
  93
  94    code = "".join(LETTERS[value] for value in letterValues)
  95
  96    return code
  97
  98def encode_long(address, compare, replace):
  99    """Encodes an eight-letter Game Genie code."""
 100
 101    # set MSB of address (causes 3rd letter to be one of EOXUKSVN)
 102    addressAndCompareAndReplace = \
 103    ((address | 0x8000) << 16) | (compare << 8) | replace
 104
 105    letterValues = []
 106
 107    for letterPos in range(8):
 108        digitPosHi = DIGIT_POS_LONG_HI[letterPos]
 109        digitPosLo = DIGIT_POS_LONG_LO[letterPos]
 110        digitHi = addressAndCompareAndReplace >> (7 - digitPosHi) * 4 & 0xF
 111        digitLo = addressAndCompareAndReplace >> (7 - digitPosLo) * 4 & 0xF
 112        letterValues.append(digitHi & 0x8 | digitLo & 0x7)
 113
 114    code = "".join(LETTERS[value] for value in letterValues)
 115
 116    return code
 117
 118def extract_values_short(argument):
 119    """Extracts address and replacement value from input."""
 120
 121    argument = argument.replace(":", "")
 122
 123    return (
 124        int(argument[:4], 16),
 125        int(argument[4:], 16)
 126    )
 127
 128def extract_values_long(argument):
 129    """Extracts address, compare value and replacement value from input."""
 130
 131    argument = argument.replace("?", "").replace(":", "")
 132
 133    return (
 134        int(argument[:4], 16),
 135        int(argument[4:6], 16),
 136        int(argument[6:], 16)
 137    )
 138
 139def main():
 140    if len(sys.argv) != 2:
 141        exit(HELP_TEXT)
 142
 143    argument = sys.argv[1]
 144
 145    if RE_CODE6.fullmatch(argument) is not None:
 146        print("{:04X}:{:02X}".format(*decode_short(argument)))
 147    elif RE_CODE8.fullmatch(argument) is not None:
 148        print("{:04X}?{:02X}:{:02X}".format(*decode_long(argument)))
 149    elif RE_NUMERIC6.fullmatch(argument) is not None:
 150        print(encode_short(*extract_values_short(argument)))
 151    elif RE_NUMERIC8.fullmatch(argument) is not None:
 152        print(encode_long(*extract_values_long(argument)))
 153    else:
 154        exit("Invalid input. Run without arguments to see help.")
 155
 156if __name__ == "__main__":
 157    main()