From c6f8629fdb7925343ff28316d60ec257e64a1e8f Mon Sep 17 00:00:00 2001 From: Markus Birth Date: Sat, 22 Feb 2020 18:45:23 +0100 Subject: [PATCH] Rewrite to Python3 (it's 2020 after all) and added lots of example codes and a few more things. --- ninty-codes.py | 149 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 33 deletions(-) diff --git a/ninty-codes.py b/ninty-codes.py index a4967a1..1f79289 100755 --- a/ninty-codes.py +++ b/ninty-codes.py @@ -1,4 +1,79 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from functools import reduce + +# https://www.reddit.com/r/SwitchHacks/comments/f7psrk/anatomy_of_an_eshop_card_code/ + +# taken from the interwebs, none of these are new +EXAMPLE_CODES = [ + '5753437561933377', + '5753437675070492', + + 'A039XJDX0WMEHG6W', + 'A03RL0KB2XHHRRBD', + 'A03RL0KC22JL2MEA', + 'A03RL0KD0UXF49FK', + 'A03RL0KE1X8GGMS6', + 'A05A259G2H2PJL5G', + 'A0693YFS0BPM6K2G', + 'A0796BH71Q1DSSYN', + + 'B0GLFY1H1QR5GGDT', + 'B0K777732YWS7XVF', + 'B0QFT5QX0F2XMF3Q', + 'B0TTY433034MJ8FY', + 'B14WC53G32Y9VWVX', + 'B14CP5QN168XKTXR', + 'B14CP5QY1236N081', + 'B14CP5QP5CB1XY44', + 'B14CP5QQ311FQ5FQ', + 'B14CP5QR4Y7BG4QN', + 'B14CP5QS1X46M2L6', + 'B14CP5QT2S17K53T', + 'B14D8WQ610Q2S26S', + 'B14D8WQ7113VV6V4', + 'B14D8WQ834BFNP0G', + 'B14D8WQV3Q41N3KH', + 'B14D8WQB45PJ1M7L', + + 'C00V1HK6KC0VMDLW', + 'C00V1HK7N1WF5SYR', + 'C00V1HK8PF42N06N', + 'C00V1HK90VSR0PGP', + 'C00V1HKBL941HH9T', + 'C00V1HKCJFNTF1QK', + 'C00V1HKDRW7VD14T', + 'C00V1HKFB2XP76V4', + 'C048GTST9QGLPCSY', + 'C049GTT0V2K86RKC', + 'C049GTT1QTLK1H26', + 'C049GTT277GG78L3', + 'C049GTT302810G4D', + 'C049GTT44SLTW4HV', + 'C049GTT55RYWV004', + 'C049GTT6V4S1V5M5', + 'C01DHQVVS3THVW6G', + 'C01WB9K31B1H3K03', + 'C01WRRQKCJ6YXFM3', + 'C080M7Y73KC7B37N', + 'C080M7Y8JGY35R0P', + 'C080M7Y9YWPX531M', + 'C080M7YV4MM8XG81', + 'C080M7YB7M3V2XRN', + 'C080M7YCDBS7Y13M', + 'C080M7YD74CPT83Y', + 'C080M7YW8XLRMTXK', +] + +def safe2real(code: str): + """Converts a code with unambiguous characters to the real code""" + code = code.upper() + code = code.replace('V', 'A') # A might be confused with 4 + code = code.replace('W', 'E') # E might be confused with 3 + code = code.replace('X', 'I') # I might be confused with 1 + code = code.replace('Y', 'O') # O might be confused with 0 + return code def generateChecksum(base_code): if len(base_code) != 15: @@ -10,7 +85,7 @@ def generateChecksum(base_code): def validate(code): if len(code) != 16: return False # Make uppercase if necessary, then change X and Y back to their base 33 equivalents - unmangled_code = code.upper().replace('X', 'I').replace('Y', 'O') + unmangled_code = safe2real(code) theirs = unmangled_code[15] mine = generateChecksum(unmangled_code[0:15]) return theirs == mine @@ -18,50 +93,58 @@ def validate(code): def parse(code): serial = None external_validator = None - unmangled_code = code.upper().replace('X', 'I').replace('Y', 'O') - if code[0].decode('ASCII').isnumeric(): - serial = long(code[0:8]) - external_validator = long(code[8:]) + unmangled_code = safe2real(code) + if code[0].isnumeric(): + serial = int(code[0:8]) + external_validator = int(code[8:]) elif code[0] == 'A': - serial = long(unmangled_code[1:8], 33) - external_validator = long(unmangled_code[8:15], 33) - elif code[0] == 'B': - serial = long(unmangled_code[1:8], 33) - external_validator = long(unmangled_code[8:15], 33) - print "warning: Format for 'B' generation speculative" + serial = int(unmangled_code[1:8], 33) + external_validator = int(unmangled_code[8:15], 33) + elif code[0] == 'B' or code[0] == 'C': + serial = int(unmangled_code[1:8], 30) + external_validator = int(unmangled_code[8:15], 30) else: - print "unknown card generation '%s'" % code[0] + print(" unknown card generation '%s'" % code[0]) return - print "Serial number: {0:010d}".format(serial) - print "External validator: {0:010d}".format(external_validator) + print(" ser#: {:010d} / Ext. validator: {:010d}".format(serial, external_validator)) + +def get_type(code: str) -> str: + if code[0].isnumeric(): + return 'NUM' + elif code[0] == 'A': + return '_A_' + elif code[0] == 'B': + return '_B_' + elif code[0] == 'C': + return '_C_' + else: + return 'UNK' if __name__ == '__main__': import sys - # taken from the interwebs, none of these are new - predef = ( - '5753437561933377', - '5753437675070492', - 'A03RL0KB2XHHRRBD', - 'A03RL0KC22JL2MEA', - 'A03RL0KD0UXF49FK', - 'A03RL0KE1X8GGMS6', - 'A0796BH71Q1DSSYN', - 'B0GLFY1H1QR5GGDT', - 'B0K777732YWS7XVF', - ) args = None if len(sys.argv) == 1: - args = predef + args = EXAMPLE_CODES else: args = sys.argv[1:] + seen_chars = {} for s in args: - if s[0] == 'A': + code_type = get_type(s) + if not code_type in seen_chars: + seen_chars[code_type] = set() + for c in s[1:]: + seen_chars[code_type].add(c) + if code_type == '_A_': valid = validate(s) - print '%s: %s' % (s, "ok" if valid else "bad") - if not valid: continue + print('{} {}'.format(s, "☑ " if valid else "☒ "), end='') + if not valid: + print() + continue parse(s) else: - print s + print('{} ??'.format(s), end='') parse(s) - print "" + for ct, cc in seen_chars.items(): + cc = sorted(list(cc)) + print('Found characters in type {} codes: {}'.format(ct, "".join(cc)))