78 lines
2.7 KiB
Python
Executable File
78 lines
2.7 KiB
Python
Executable File
# -*- coding: utf-8 -*-
|
|
|
|
# https://www.reddit.com/r/SwitchHacks/comments/f7psrk/anatomy_of_an_eshop_card_code/
|
|
|
|
CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
|
|
def safe2real(code: str) -> str:
|
|
"""Converts a code with unambiguous characters to the real code"""
|
|
code = code.upper()
|
|
code_first = code[0]
|
|
if code_first != 'A':
|
|
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 real2safe(code: str) -> str:
|
|
"""Converts an actual code to a representation with unambiguous characters"""
|
|
code_first = code[0]
|
|
code = code.upper()
|
|
if code_first != 'A':
|
|
code = code.replace('A', 'V') # A might be confused with 4
|
|
code = code.replace('E', 'W') # E might be confused with 3
|
|
code = code.replace('I', 'X') # I might be confused with 1
|
|
code = code.replace('O', 'Y') # O might be confused with 0
|
|
code = code_first + code[1:]
|
|
return code
|
|
|
|
def generate_checksum(base_code, base = 30):
|
|
if len(base_code) != 15:
|
|
raise RuntimeError("Bad argument to checksum")
|
|
# https://www.reddit.com/r/SwitchHacks/comments/f7psrk/anatomy_of_an_eshop_card_code/fijt5d4/
|
|
cksum = sum(int(c, base) for c in base_code) % base
|
|
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]
|
|
code_type = get_type(code)
|
|
base = 33 if code_type == '_A_' else 30
|
|
mine = generate_checksum(unmangled_code[0:15], base)
|
|
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'
|