diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index 488ff88..72ddfb1 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -26,11 +26,18 @@ import time import other.utils as utils + def generate_secret_keys(filename="gslist.cfg"): + """Generate list of secret keys based on a config file. + + gslist.cfg is the default config file and may be incomplete. + TODO: Parse the config file in a cleaner way. (ex: using CSV module) + """ secret_key_list = {} with open(filename) as key_file: for line in key_file.readlines(): - #name = line[:54].strip() # Probably won't do anything with the name for now. + # name = line[:54].strip() + # Probably won't do anything with the name for now. id = line[54:54+19].strip() key = line[54+19:].strip() @@ -38,23 +45,37 @@ def generate_secret_keys(filename="gslist.cfg"): return secret_key_list -# GameSpy uses a slightly modified version of base64 which replaces +/= with []_ + def base64_encode(input): - output = base64.b64encode(input).replace('+', '[').replace('/', ']').replace('=', '_') + """Encode input in base64 using GameSpy variant. + + GameSpy uses a slightly modified version of base64 which replaces + +/= with []_ + """ + output = base64.b64encode(input).replace('+', '[') \ + .replace('/', ']') \ + .replace('=', '_') return output def base64_decode(input): - output = base64.b64decode(input.replace('[', '+').replace(']', '/').replace('_', '=')) + """Decode input in base64 using GameSpy variant.""" + output = base64.b64decode(input.replace('[', '+') + .replace(']', '/') + .replace('_', '=')) return output -# Tetris DS overlay 10 @ 0216E9B8 + def rc4_encrypt(_key, _data): + """ + Tetris DS overlay 10 @ 0216E9B8 + """ key = bytearray(_key) data = bytearray(_data) if len(key) == 0: - # This shouldn't happen but it apparently can on a rare occasion. key should always be set. + # This shouldn't happen but it apparently can on a rare occasion. + # Key should always be set. return # Key-scheduling algorithm @@ -71,8 +92,9 @@ def rc4_encrypt(_key, _data): # Pseudo-random generation algorithm + encryption i = 0 j = 0 - for x in range(len(data)): - i = (i + 1 + data[x]) & 0xff # Modified RC4? What's this data[x] doing here? + for x, val in enumerate(data): + # Modified RC4? + i = (i + 1 + val) & 0xff j = (j + S[i]) & 0xff S[i], S[j] = S[j], S[i] @@ -81,10 +103,13 @@ def rc4_encrypt(_key, _data): return data -# Tetris DS overlay 10 @ 0216E9B8 -# Used by the master server to send some data between the client and server. -# This seems to be what Luigi Auriemma calls "Gsmsalg". + def prepare_rc4_base64(_key, _data): + """Tetris DS overlay 10 @ 0216E9B8 + + Used by the master server to send some data between the client and server. + This seems to be what Luigi Auriemma calls "Gsmsalg". + """ data = rc4_encrypt(_key, _data) if data is None: @@ -94,28 +119,31 @@ def prepare_rc4_base64(_key, _data): return base64.b64encode(buffer(data)) -# get the login data from nas.nintendowifi.net/ac from an authtoken + def parse_authtoken(authtoken, db): + """Get the login data from nas.nintendowifi.net/ac from an authtoken""" return db.get_nas_login(authtoken) + def login_profile_via_parsed_authtoken(authtoken_parsed, db): console = 0 userid = authtoken_parsed['userid'] - csnum = authtoken_parsed.get('csnum', '') # Wii: Serial number - cfc = authtoken_parsed.get('cfc', '') # Wii: Friend code - bssid = authtoken_parsed.get('bssid', '') # NDS: Wifi network's BSSID - devname = authtoken_parsed.get('devname', '') # NDS: Device name - birth = authtoken_parsed.get('birth', '') # NDS: User's birthday + csnum = authtoken_parsed.get('csnum', '') # Wii: Serial number + cfc = authtoken_parsed.get('cfc', '') # Wii: Friend code + bssid = authtoken_parsed.get('bssid', '') # NDS: Wifi network's BSSID + devname = authtoken_parsed.get('devname', '') # NDS: Device name + birth = authtoken_parsed.get('birth', '') # NDS: User's birthday - # The Wii does not use passwd, so take another uniquely generated string as the password. + # The Wii does not use passwd, so take another uniquely generated string + # as the password. # if "passwd" in authtoken_parsed: # password = authtoken_parsed['passwd'] # else: # password = authtoken_parsed['gsbrcd'] # console = 1 - if not "passwd" in authtoken_parsed: + if "passwd" not in authtoken_parsed: console = 1 password = authtoken_parsed['gsbrcd'] @@ -123,7 +151,7 @@ def login_profile_via_parsed_authtoken(authtoken_parsed, db): gameid = gsbrcd[:4] macadr = authtoken_parsed['macadr'] uniquenick = utils.base32_encode(int(userid)) + gsbrcd - email = uniquenick + "@nds" # The Wii also seems to use @nds. + email = uniquenick + "@nds" # The Wii also seems to use @nds. if "csnum" in authtoken_parsed: console = 1 @@ -132,13 +160,17 @@ def login_profile_via_parsed_authtoken(authtoken_parsed, db): valid_user = db.check_user_exists(userid, gsbrcd) if valid_user is False: - profileid = db.create_user(userid, password, email, uniquenick, gsbrcd, console, csnum, cfc, bssid, devname, birth, gameid, macadr) + profileid = db.create_user(userid, password, email, uniquenick, + gsbrcd, console, csnum, cfc, bssid, + devname, birth, gameid, macadr) else: profileid = db.perform_login(userid, password, gsbrcd) return userid, profileid, gsbrcd, uniquenick + def generate_response(challenge, ac_challenge, secretkey, authtoken): + """Generate a challenge response.""" md5 = hashlib.md5() md5.update(ac_challenge) @@ -155,9 +187,14 @@ def generate_response(challenge, ac_challenge, secretkey, authtoken): return md5_2.hexdigest() -# The proof is practically the same thing as the response, except it has the challenge and the secret key swapped. -# Maybe combine the two functions later? def generate_proof(challenge, ac_challenge, secretkey, authtoken): + """Generate a challenge proof. + + The proof is practically the same thing as the response, except it has + the challenge and the secret key swapped. + + Maybe combine the two functions later? + """ md5 = hashlib.md5() md5.update(ac_challenge) @@ -173,8 +210,11 @@ def generate_proof(challenge, ac_challenge, secretkey, authtoken): return md5_2.hexdigest() -# Code: Tetris DS @ 02057A14 + def get_friendcode_from_profileid(profileid, gameid): + """ + Code: Tetris DS @ 02057A14 + """ friendcode = 0 # Combine the profileid and gameid into one buffer @@ -189,14 +229,20 @@ def get_friendcode_from_profileid(profileid, gameid): return friendcode + def get_profileid_from_friendcode(friendcode): + """Return profile ID from Friend Code.""" # Get the lower 32 bits as the profile id profileid = friendcode & 0xffffffff return profileid -# Code from Luigi Auriemma's enctypex_decoder.c -# It's kind of sloppy in parts, but it works. Unless there's some issues then it'll probably not change any longer. + class EncTypeX: + """Code from Luigi Auriemma's enctypex_decoder.c + + It's kind of sloppy in parts, but it works. Unless there's some issues + then it'll probably not change any longer. + """ def __init__(self): return @@ -214,7 +260,8 @@ class EncTypeX: if not key or not validate or not data: return None - # Convert data from strings to byte arrays before use or else it'll raise an error + # Convert data from strings to byte arrays before use or else + # it'll raise an error key = bytearray(key) validate = bytearray(validate) @@ -236,15 +283,16 @@ class EncTypeX: data[2] = 0x00 data[header_len - 1] = (tmp_len - header_len) ^ 0xea - header = data[:tmp_len] # The header of the data gets chopped off in init(), so save it + # The header of the data gets chopped off in init(), so save it + header = data[:tmp_len] encxkey = bytearray([0] * 261) data = self.init(encxkey, key, validate, data) self.func6e(encxkey, data, len(data)) - # Reappend header that we saved earlier before returning to make the complete buffer + # Reappend header that we saved earlier before returning to make + # the complete buffer return header + data - def init(self, encxkey, key, validate, data): data_len = len(data) @@ -259,9 +307,15 @@ class EncTypeX: if data_len < (header_len + data_start): return None - data = self.enctypex_funcx(encxkey, bytearray(key), bytearray(validate), data[header_len:], data_start) - return data[data_start:] + data = self.enctypex_funcx( + encxkey, + bytearray(key), + bytearray(validate), + data[header_len:], + data_start + ) + return data[data_start:] def enctypex_funcx(self, encxkey, key, validate, data, datalen): keylen = len(key) @@ -281,7 +335,7 @@ class EncTypeX: n1 = 0 n2 = 0 - for i in range(255,-1,-1): + for i in range(255, -1, -1): t1, n1, n2 = self.func5(encxkey, i, id, idlen, n1, n2) t2 = encxkey[i] encxkey[i] = encxkey[t1]