#!/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 = [ '4237082951213129', # https://cr-cs.tumblr.com/post/153860583013/3ds-store-3ds-redeem-codes '5753437561933377', '5753437675070492', 'A0387RMN33YJNGMC', # https://cr-cs.tumblr.com/post/155573566497/cheap-eshop-codes-3ds-prepaid-card-code-generator-online 'A039XJDX0WMEHG6W', 'A03RL0KB2XHHRRBD', 'A03RL0KC22JL2MEA', 'A03RL0KD0UXF49FK', 'A03RL0KE1X8GGMS6', 'A05A259G2H2PJL5G', 'A0693YFS0BPM6K2G', # 'A071WNTC1BPR5EVU', # https://www.nintendolife.com/news/2015/01/rumour_code_name_steam_demo_to_be_distributed_via_gamestop_stores 'A0775H3G1MYCJW1Q', # https://www.youtube.com/watch?v=RMsuHPbI04Y 'A0796BH71Q1DSSYN', 'A07K7E3702LYF1NM', # https://www.youtube.com/watch?v=tIEnTXxSM98 'B0GLFY1H1QR5GGDT', 'B0K777732YWS7XVF', 'B0QFT5QX0F2XMF3Q', 'B0S0W8RT3QBSRHK9', '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', ] CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 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: raise RuntimeError("Bad argument to checksum") # Mystery/bug: Why is this ord('7') in particular? 55, 0o55 and 0x55 are all unrelated to anything in base 33. The check looks like it's trying to do c - 'A', but does the weirdest thing instead. cksum = reduce(lambda cksum, c: (cksum + (ord(c) - ord('0') if ord(c) <= ord('9') else ord(c) - ord('7'))) % 33, base_code, 0) return CHARSET[cksum] 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 = safe2real(code) theirs = unmangled_code[15] mine = generateChecksum(unmangled_code[0:15]) return theirs == mine def parse(code): serial = None external_validator = None code_type = get_type(code) unmangled_code = safe2real(code) if code_type == 'NUM': serial = int(code[0:8]) external_validator = int(code[8:]) elif code_type == '_A_': serial = int(unmangled_code[1:8], 33) external_validator = int(unmangled_code[8:15], 33) elif code_type == '_B_' or code_type == '_C_': serial = int(unmangled_code[1:8], 30) external_validator = int(unmangled_code[8:15], 30) else: print(" unknown card generation '%s'" % code[0]) return 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 args = None if len(sys.argv) == 1: args = EXAMPLE_CODES else: args = sys.argv[1:] seen_chars = {} for s in args: 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('{} {}'.format(s, "☑ " if valid else "☒ "), end='') if not valid: print() continue parse(s) else: print('{} ??'.format(s), end='') parse(s) for ct, cc in seen_chars.items(): cc = sorted(list(cc)) print('Found characters in type {} codes: '.format(ct), end='') for c in CHARSET: if c in cc: print(c, end='') else: print('-', end='') print()