diff --git a/.gitignore b/.gitignore index b9d6bd9..21cc4db 100644 --- a/.gitignore +++ b/.gitignore @@ -213,3 +213,10 @@ pip-log.txt #Mr Developer .mr.developer.cfg +.idea/.name +.idea/encodings.xml +.idea/misc.xml +.idea/nintendo_dwc_emulator.iml +.idea/scopes/scope_settings.xml +.idea/vcs.xml +.idea/workspace.xml \ No newline at end of file diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 4c99e8c..fd0b9d9 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -3,196 +3,213 @@ import hashlib import itertools import other.utils as utils + class GamespyDatabase(object): - def __init__(self, filename='gpcm.db'): - self.conn = sqlite3.connect(filename) - self.conn.row_factory = sqlite3.Row + def __init__(self, filename='gpcm.db'): + self.conn = sqlite3.connect(filename) + self.conn.row_factory = sqlite3.Row - self.initialize_database(self.conn) + self.initialize_database(self.conn) - def initialize_database(self, conn): - c = self.conn.cursor() - c.execute("SELECT * FROM sqlite_master WHERE name = 'users' AND type = 'table'") - - if c.fetchone() == None: - # I highly doubt having everything in a database be of the type TEXT is a good practice, - # but I'm not good with databases and I'm not 100% positive that, for instance, that all - # user id's will be ints, or all passwords will be ints, etc, despite not seeing any - # evidence yet to say otherwise as far as Nintendo DS games go. - c.execute('''CREATE TABLE users (profileid INT, userid TEXT, password TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, lastname TEXT)''') - c.execute('''CREATE TABLE sessions (session TEXT, profileid INT)''') - self.conn.commit() + def initialize_database(self, conn): + c = self.conn.cursor() + c.execute("SELECT * FROM sqlite_master WHERE name = 'users' AND type = 'table'") - def get_dict(self, row): - if row == None: - return None + if c.fetchone() == None: + # I highly doubt having everything in a database be of the type TEXT is a good practice, + # but I'm not good with databases and I'm not 100% positive that, for instance, that all + # user id's will be ints, or all passwords will be ints, etc, despite not seeing any + # evidence yet to say otherwise as far as Nintendo DS games go. + c.execute('''CREATE TABLE users (profileid INT, userid TEXT, password TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, lastname TEXT)''') + c.execute('''CREATE TABLE sessions (session TEXT, profileid INT)''') + self.conn.commit() - return dict(itertools.izip(row.keys(), row)) + def get_dict(self, row): + if row == None: + return None - # User functions - def get_next_free_profileid(self): - c = self.conn.cursor() - c.execute("SELECT max(profileid) FROM users") + return dict(itertools.izip(row.keys(), row)) - r = c.fetchone() + # User functions + def get_next_free_profileid(self): + c = self.conn.cursor() + c.execute("SELECT max(profileid) FROM users") - profileid = 476639431 #100000000 - if r != None and r['max(profileid)'] != None: - profileid = int(r[0]) + 1 + r = c.fetchone() - c.close() + profileid = 476639431 #100000000 + if r != None and r['max(profileid)'] != None: + profileid = int(r[0]) + 1 - return profileid + c.close() - def check_user_exists(self, userid): - c = self.conn.cursor() - c.execute("SELECT * FROM users WHERE userid = ?", [userid]) + return profileid - r = self.get_dict(c.fetchone()) + def check_user_exists(self, userid): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE userid = ?", [userid]) - valid_user = False # Default, user doesn't exist - if r != None: - valid_user = True # Valid password + r = self.get_dict(c.fetchone()) - c.close() - return valid_user + valid_user = False # Default, user doesn't exist + if r != None: + valid_user = True # Valid password - def check_profile_exists(self, profileid): - c = self.conn.cursor() - c.execute("SELECT * FROM users WHERE profileid = ?", [profileid]) + c.close() + return valid_user - r = self.get_dict(c.fetchone()) + def check_profile_exists(self, profileid): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE profileid = ?", [profileid]) - valid_profile = False # Default, user doesn't exist - if r != None: - valid_profile = True # Valid password + r = self.get_dict(c.fetchone()) - c.close() - return valid_profile + valid_profile = False # Default, user doesn't exist + if r != None: + valid_profile = True # Valid password - def perform_login(self, userid, password): - c = self.conn.cursor() - c.execute("SELECT * FROM users WHERE userid = ?", [userid]) + c.close() + return valid_profile - r = self.get_dict(c.fetchone()) + def perform_login(self, userid, password): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE userid = ?", [userid]) - profileid = None # Default, user doesn't exist - if r != None: - md5 = hashlib.md5() - md5.update(password) + r = self.get_dict(c.fetchone()) - if r['password'] == md5.hexdigest(): - profileid = r['profileid'] # Valid password + profileid = None # Default, user doesn't exist + if r != None: + md5 = hashlib.md5() + md5.update(password) - c.close() - return profileid + if r['password'] == md5.hexdigest(): + profileid = r['profileid'] # Valid password - def create_user(self, userid, password, email, uniquenick): - if self.check_user_exists(userid) == 0: - profileid = self.get_next_free_profileid() - - pid = "11" # Always 11??? Is this important? Not to be confused with dwc_pid. The three games I found it in (Tetris DS, Advance Wars - Days of Ruin, and Animal Crossing: Wild World) all use \pid\11. - lon = "0.000000" # Always 0.000000? - lat = "0.000000" # Always 0.000000? - loc = "" # Always blank? - lastname = "" + c.close() + return profileid - # Hash password before entering it into the database. - # For now I'm using a very simple MD5 hash. - # TODO: Replace with something stronger later, although it's overkill for the NDS. - md5 = hashlib.md5() - md5.update(password) - password = md5.hexdigest() + def create_user(self, userid, password, email, uniquenick): + if self.check_user_exists(userid) == 0: + profileid = self.get_next_free_profileid() - c = self.conn.cursor() - c.execute("INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?)", [profileid, str(userid), password, email, uniquenick, pid, lon, lat, loc, lastname]) - c.close() + pid = "11" # Always 11??? Is this important? Not to be confused with dwc_pid. The three games I found it in (Tetris DS, Advance Wars - Days of Ruin, and Animal Crossing: Wild World) all use \pid\11. + lon = "0.000000" # Always 0.000000? + lat = "0.000000" # Always 0.000000? + loc = "" # Always blank? + lastname = "" - self.conn.commit() + # Hash password before entering it into the database. + # For now I'm using a very simple MD5 hash. + # TODO: Replace with something stronger later, although it's overkill for the NDS. + md5 = hashlib.md5() + md5.update(password) + password = md5.hexdigest() - return profileid + c = self.conn.cursor() + c.execute("INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?)", + [profileid, str(userid), password, email, uniquenick, pid, lon, lat, loc, lastname]) + c.close() - def get_user_list(self): - c = self.conn.cursor() - - users = [] - for row in c.execute("SELECT * FROM users"): - users.append(self.get_dict(row)) + self.conn.commit() - return users + return profileid - def update_profile(self, session_key, fields): - profileid = self.get_profileid_from_session_key(session_key) + def get_user_list(self): + c = self.conn.cursor() - if profileid != -1: - # Found profile id associated with session key. - # Start replacing each field one by one. - # TODO: Optimize this so it's done all in one update. - # FIXME: Possible security issue due to embedding an unsanitized string directly into the statement. - c = self.conn.cursor() - for field in fields: - c.execute("UPDATE users SET %s = ? WHERE profileid = ?" % (field), [fields[field], profileid]) + users = [] + for row in c.execute("SELECT * FROM users"): + users.append(self.get_dict(row)) + + return users + + def update_profile(self, session_key, fields): + profileid = self.get_profileid_from_session_key(session_key) + + if profileid != -1: + # Found profile id associated with session key. + # Start replacing each field one by one. + # TODO: Optimize this so it's done all in one update. + # FIXME: Possible security issue due to embedding an unsanitized string directly into the statement. + c = self.conn.cursor() + for field in fields: + c.execute("UPDATE users SET %s = ? WHERE profileid = ?" % (field), [fields[field], profileid]) + + self.conn.commit() - self.conn.commit() + # Session functions + # TODO: Cache session keys so we don't have to query the database every time we get a profile id. + def get_profileid_from_session_key(self, session_key): + c = self.conn.cursor() + c.execute("SELECT profileid FROM sessions WHERE session = ?", [session_key]) + + r = self.get_dict(c.fetchone()) + + profileid = -1 # Default, invalid session key + if r != None: + profileid = r['profileid'] + + c.close() + return profileid + + def get_profile_from_session_key(self, session_key): + profileid = self.get_profileid_from_session_key(session_key) + + profile = {} + if profileid != None: + c = self.conn.cursor() + c.execute("SELECT profileid FROM sessions WHERE session = ?", [session_key]) + + profile = self.get_dict(c.fetchone()) + + c.close() + return profile +def generate_session_key(self, min_size): + session_key = utils.generate_random_number_str(min_size) - # Session functions - # TODO: Cache session keys so we don't have to query the database every time we get a profile id. - def get_profileid_from_session_key(self, session_key): - c = self.conn.cursor() - c.execute("SELECT profileid FROM sessions WHERE session = ?", [session_key]) + c = self.conn.cursor() + for r in c.execute("SELECT session FROM sessions WHERE session = ?", [session_key]): + session_key = utils.generate_random_number_str(min_size) - r = self.get_dict(c.fetchone()) - - profileid = -1 # Default, invalid session key - if r != None: - profileid = r['profileid'] + return session_key - c.close() - return profileid - def generate_session_key(self, min_size): - session_key = utils.generate_random_number_str(min_size) +def create_session(self, profileid): + if profileid != None and self.check_profile_exists(profileid) == False: + return None - c = self.conn.cursor() - for r in c.execute("SELECT session FROM sessions WHERE session = ?", [session_key]): - session_key = utils.generate_random_number_str(min_size) + # Remove any old sessions associated with this user id + self.delete_session(profileid) - return session_key + # Create new session + session_key = self.generate_session_key(9) - def create_session(self, profileid): - if profileid != None and self.check_profile_exists(profileid) == False: - return None + c = self.conn.cursor() + c.execute("INSERT INTO sessions VALUES (?, ?)", [session_key, profileid]) + self.conn.commit() - # Remove any old sessions associated with this user id - self.delete_session(profileid) + return session_key - # Create new session - session_key = self.generate_session_key(9) - c = self.conn.cursor() - c.execute("INSERT INTO sessions VALUES (?, ?)", [session_key, profileid]) - self.conn.commit() +def delete_session(self, profileid): + c = self.conn.cursor() + c.execute("DELETE FROM sessions WHERE profileid = ?", [profileid]) + self.conn.commit() - return session_key - def delete_session(self, profileid): - c = self.conn.cursor() - c.execute("DELETE FROM sessions WHERE profileid = ?", [profileid]) - self.conn.commit() +def get_session_list(self, profileid=None): + c = self.conn.cursor() - def get_session_list(self, profileid=None): - c = self.conn.cursor() - - sessions = [] - if profileid != None: - r = c.execute("SELECT * FROM sessions WHERE profileid = ?", [profileid]) - else: - r = c.execute("SELECT * FROM sessions") + sessions = [] + if profileid != None: + r = c.execute("SELECT * FROM sessions WHERE profileid = ?", [profileid]) + else: + r = c.execute("SELECT * FROM sessions") - for row in r: - sessions.append(self.get_dict(row)) + for row in r: + sessions.append(self.get_dict(row)) - return sessions + return sessions diff --git a/gamespy/gs_query.py b/gamespy/gs_query.py index 55ba9d8..21115c0 100644 --- a/gamespy/gs_query.py +++ b/gamespy/gs_query.py @@ -1,116 +1,118 @@ import copy def parse_gamespy_message(message): - stack = [] - messages = {} - msg = message + stack = [] + messages = {} + msg = message - while len(msg) > 0: - # Find the command + while len(msg) > 0: + # Find the command - found_command = False - while len(msg) > 0 and msg[0] == '\\': - keyEnd = msg[1:].index('\\') + 1 - key = msg[1:keyEnd] - msg = msg[keyEnd + 1:] + found_command = False + while len(msg) > 0 and msg[0] == '\\': + keyEnd = msg[1:].index('\\') + 1 + key = msg[1:keyEnd] + msg = msg[keyEnd + 1:] - if key == "final": - break - - if '\\' in msg: - if msg[0] == '\\': - value = "" - else: - valueEnd = msg[1:].index('\\') - value = msg[:valueEnd + 1] - msg = msg[valueEnd + 1:] - else: - value = msg - - if found_command == False: - messages['__cmd__'] = key - messages['__cmd_val__'] = value - found_command = True + if key == "final": + break - messages[key] = value + if '\\' in msg: + if msg[0] == '\\': + value = "" + else: + valueEnd = msg[1:].index('\\') + value = msg[:valueEnd + 1] + msg = msg[valueEnd + 1:] + else: + value = msg + + if found_command == False: + messages['__cmd__'] = key + messages['__cmd_val__'] = value + found_command = True + + messages[key] = value + + stack.append(messages) + messages = {} + + return stack - stack.append(messages) - messages = {} - - return stack # Generate a list based on the input dictionary. # The main command must also be stored in __cmd__ for it to put the parameter at the beginning. def create_gamespy_message_from_dict(messages_orig): - # Deep copy the dictionary because we don't want the original to be modified - messages = copy.deepcopy(messages_orig) + # Deep copy the dictionary because we don't want the original to be modified + messages = copy.deepcopy(messages_orig) - cmd = "" - cmd_val = "" + cmd = "" + cmd_val = "" - if "__cmd__" in messages: - cmd = messages['__cmd__'] - messages.pop('__cmd__', None) + if "__cmd__" in messages: + cmd = messages['__cmd__'] + messages.pop('__cmd__', None) - if "__cmd_val__" in messages: - cmd_val = messages['__cmd_val__'] - messages.pop('__cmd_val__', None) + if "__cmd_val__" in messages: + cmd_val = messages['__cmd_val__'] + messages.pop('__cmd_val__', None) - if cmd in messages: - messages.pop(cmd, None) + if cmd in messages: + messages.pop(cmd, None) - l = [] - l.append(("__cmd__", cmd)) - l.append(("__cmd_val__", cmd_val)) + l = [] + l.append(("__cmd__", cmd)) + l.append(("__cmd_val__", cmd_val)) - for message in messages: - l.append((message, messages[message])) + for message in messages: + l.append((message, messages[message])) - return l + return l def create_gamespy_message_from_list(messages): - d = {} - cmd = "" - cmd_val = "" + d = {} + cmd = "" + cmd_val = "" - query = "" - for message in messages: - if message[0] == "__cmd__": - cmd = message[1] - elif message[0] == "__cmd_val__": - cmd_val = message[1] - else: - query += "\\%s\\%s" % (message[0], message[1]) + query = "" + for message in messages: + if message[0] == "__cmd__": + cmd = message[1] + elif message[0] == "__cmd_val__": + cmd_val = message[1] + else: + query += "\\%s\\%s" % (message[0], message[1]) - if cmd != "": - # Prepend the main command if one was found. - query = "\\%s\\%s%s" % (cmd, cmd_val, query) + if cmd != "": + # Prepend the main command if one was found. + query = "\\%s\\%s%s" % (cmd, cmd_val, query) + + return query - return query # Create a message based on a dictionary (or list) of parameters. -def create_gamespy_message(messages, id = None): - query = "" - - if isinstance(messages, dict): - messages = create_gamespy_message_from_dict(messages) +def create_gamespy_message(messages, id=None): + query = "" - # Check for an id if the id needs to be updated. - # If it already exists in the list then update it, else add it - if id != None: - for message in messages: - if message[0] == "id": - messages.pop(messages.index(message)) - messages.append(("id", str(id))) - id = None # Updated id, so don't add it to the query later - break # Found id, stop searching list + if isinstance(messages, dict): + messages = create_gamespy_message_from_dict(messages) - query = create_gamespy_message_from_list(messages) + # Check for an id if the id needs to be updated. + # If it already exists in the list then update it, else add it + if id != None: + for message in messages: + if message[0] == "id": + messages.pop(messages.index(message)) + messages.append(("id", str(id))) + id = None # Updated id, so don't add it to the query later + break # Found id, stop searching list - if id != None: - query += create_gamespy_message_from_list([("id", id)]) + query = create_gamespy_message_from_list(messages) - query += "\\final\\" + if id != None: + query += create_gamespy_message_from_list([("id", id)]) - return query + query += "\\final\\" + + return query diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py index c35599e..c7a68e8 100644 --- a/gamespy/gs_utility.py +++ b/gamespy/gs_utility.py @@ -5,61 +5,63 @@ import hashlib # GameSpy uses a slightly modified version of base64 which replaces +/= with []_ def base64_encode(input): - output = base64.b64encode(input).replace('+', '[').replace('/',']').replace('=','_') - return output + output = base64.b64encode(input).replace('+', '[').replace('/', ']').replace('=', '_') + return output + def base64_decode(input): - output = base64.b64decode(input.replace('[', '+').replace('/',']').replace('_','=')) - return output + output = base64.b64decode(input.replace('[', '+').replace('/', ']').replace('_', '=')) + return output + # Parse my custom authtoken generated by the emulated nas.nintendowifi.net/ac def parse_authtoken(authtoken): - messages = {} + messages = {} - if authtoken[:3] == "NDS": - authtoken = authtoken[3:] + if authtoken[:3] == "NDS": + authtoken = authtoken[3:] - dec = base64.standard_b64decode(authtoken) + dec = base64.standard_b64decode(authtoken) - for item in dec.split('|'): - s = item.split('\\') - messages[s[0]] = s[1] + for item in dec.split('|'): + s = item.split('\\') + messages[s[0]] = s[1] - return messages + return messages -def generate_response(challenge, ac_challenge, secretkey, authtoken): - md5 = hashlib.md5() - md5.update(ac_challenge) +def generate_response(challenge, ac_challenge, secretkey, authtoken): + md5 = hashlib.md5() + md5.update(ac_challenge) - output = md5.hexdigest() - output += ' ' * 0x30 - output += authtoken - output += secretkey - output += challenge - output += md5.hexdigest() + output = md5.hexdigest() + output += ' ' * 0x30 + output += authtoken + output += secretkey + output += challenge + output += md5.hexdigest() - md5_2 = hashlib.md5() - md5_2.update(output) - - return md5_2.hexdigest() + md5_2 = hashlib.md5() + md5_2.update(output) + + 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): - md5 = hashlib.md5() - md5.update(ac_challenge) +def generate_proof(challenge, ac_challenge, secretkey, authtoken): + md5 = hashlib.md5() + md5.update(ac_challenge) - output = md5.hexdigest() - output += ' ' * 0x30 - output += authtoken - output += challenge - output += secretkey - output += md5.hexdigest() + output = md5.hexdigest() + output += ' ' * 0x30 + output += authtoken + output += challenge + output += secretkey + output += md5.hexdigest() - md5_2 = hashlib.md5() - md5_2.update(output) - - return md5_2.hexdigest() + md5_2 = hashlib.md5() + md5_2.update(output) + + return md5_2.hexdigest() \ No newline at end of file diff --git a/gamespy_server.py b/gamespy_server.py index 8548b2f..51e02bf 100644 --- a/gamespy_server.py +++ b/gamespy_server.py @@ -18,112 +18,113 @@ s.listen(backlog) utils.print_log("Server is now listening on %s:%s..." % (address[0], address[1])) while 1: - client, address = s.accept() + client, address = s.accept() - # TODO: Redo this part of the server so it'll handle multiple connections - utils.print_log("Received connection from %s:%s" % (address[0], address[1])) + # TODO: Redo this part of the server so it'll handle multiple connections + utils.print_log("Received connection from %s:%s" % (address[0], address[1])) - # Send request for login information - challenge = utils.generate_random_str(8) + # Send request for login information + challenge = utils.generate_random_str(8) - msg_d = [] - msg_d.append(('__cmd__', "lc")) - msg_d.append(('__cmd_val__', "1")) - msg_d.append(('challenge', challenge)) - msg_d.append(('id', "1")) - msg = gs_query.create_gamespy_message(msg_d) + msg_d = [] + msg_d.append(('__cmd__', "lc")) + msg_d.append(('__cmd_val__', "1")) + msg_d.append(('challenge', challenge)) + msg_d.append(('id', "1")) + msg = gs_query.create_gamespy_message(msg_d) - utils.print_log("SENDING: '%s'..." % msg) - client.send(msg) + utils.print_log("SENDING: '%s'..." % msg) + client.send(msg) - # Receive any command - accept_connection = True - while accept_connection: - data = client.recv(size).rstrip() - utils.print_log("RESPONSE: %s" % data) + # Receive any command + accept_connection = True + while accept_connection: + data = client.recv(size).rstrip() + utils.print_log("RESPONSE: %s" % data) - commands = gs_query.parse_gamespy_message(data) + commands = gs_query.parse_gamespy_message(data) - for data_parsed in commands: - msg_d = [] + for data_parsed in commands: + msg_d = [] - print data_parsed + print data_parsed - if data_parsed['__cmd__'] == "login": - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken']) - print authtoken_parsed + if data_parsed['__cmd__'] == "login": + authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken']) + print authtoken_parsed - # get correct information - userid = authtoken_parsed['userid'] - password = authtoken_parsed['passwd'] - uniquenick = utils.base32_encode(int(userid)) + authtoken_parsed['gsbrcd'] - email = uniquenick + "@nds" - nick = uniquenick + # get correct information + userid = authtoken_parsed['userid'] + password = authtoken_parsed['passwd'] + uniquenick = utils.base32_encode(int(userid)) + authtoken_parsed['gsbrcd'] + email = uniquenick + "@nds" + nick = uniquenick - # Verify the client's response - valid_response = gs_utils.generate_response(challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) - if data_parsed['response'] != valid_response: - utils.print_log("ERROR: Got invalid response. Got %s, expected %s" % (data_parsed['response'], valid_response)) - - proof = gs_utils.generate_proof(challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) + # Verify the client's response + valid_response = gs_utils.generate_response(challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) + if data_parsed['response'] != valid_response: + utils.print_log("ERROR: Got invalid response. Got %s, expected %s" % (data_parsed['response'], valid_response)) - valid_user = db.check_user_exists(userid) - profileid = None - if valid_user == False: - profileid = db.create_user(userid, password, email, uniquenick) - else: - profileid = db.perform_login(userid, password) - if profileid == None: - # Handle case where the user is invalid - print "Invalid password" + proof = gs_utils.generate_proof(challenge, authtoken_parsed['challenge'], data_parsed['challenge'], data_parsed['authtoken']) - sesskey = db.create_session(profileid) + valid_user = db.check_user_exists(userid) + profileid = None + if valid_user == False: + profileid = db.create_user(userid, password, email, uniquenick) + else: + profileid = db.perform_login(userid, password) + if profileid == None: + # Handle case where the user is invalid + print "Invalid password" - msg_d.append(('__cmd__', "lc")) - msg_d.append(('__cmd_val__', "2")) - msg_d.append(('sesskey', sesskey)) - msg_d.append(('proof', proof)) - msg_d.append(('userid', userid)) - msg_d.append(('profileid', db.get_profileid_from_session_key(sesskey))) - msg_d.append(('uniquenick', uniquenick)) - msg_d.append(('lt', gs_utils.base64_encode(utils.generate_random_str(16)))) # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated. - msg_d.append(('id', data_parsed['id'])) - msg = gs_query.create_gamespy_message(msg_d) + sesskey = db.create_session(profileid) - elif data_parsed['__cmd__'] == "getprofile": - msg_d = [] + msg_d.append(('__cmd__', "lc")) + msg_d.append(('__cmd_val__', "2")) + msg_d.append(('sesskey', sesskey)) + msg_d.append(('proof', proof)) + msg_d.append(('userid', userid)) + msg_d.append(('profileid', db.get_profileid_from_session_key(sesskey))) + msg_d.append(('uniquenick', uniquenick)) + msg_d.append(('lt', gs_utils.base64_encode(utils.generate_random_str(16)))) # Some kind of token... don't know it gets used or generated, but it doesn't seem to have any negative effects if it's not properly generated. + msg_d.append(('id', data_parsed['id'])) + msg = gs_query.create_gamespy_message(msg_d) - msg_d.append(('__cmd__', "pi")) - msg_d.append(('__cmd_val__', "")) - msg_d.append(('profileid', db.get_profileid_from_session_key(data_parsed.append(('sesskey'))))) - msg_d.append(('nick', nick)) - msg_d.append(('userid', userid)) - msg_d.append(('email', email)) - msg_d.append(('sig', sig)) - msg_d.append(('uniquenick', uniquenick)) - msg_d.append(('pid', pid)) - msg_d.append(('lastname', lastname)) - msg_d.append(('lon', lon)) - msg_d.append(('lat', lat)) - msg_d.append(('loc', loc)) - msg_d.append(('id', data_parsed['id'])) - msg = gs_query.create_gamespy_message(msg_d) + elif data_parsed['__cmd__'] == "getprofile": + msg_d = [] - elif data_parsed['__cmd__'] == "updatepro": - # Handle properly later - # Assume that there will be other parameters besides lastname, so make it a loop or something along those lines later - db.update_profile(data_parsed['sesskey'], [("lastname", data_parsed['lastname'])]) + profile = db.get_profile_from_session_key(data_parsed.append(('sesskey'))) + msg_d.append(('__cmd__', "pi")) + msg_d.append(('__cmd_val__', "")) + msg_d.append(('profileid', profile['profileid'])) + msg_d.append(('nick', profile['nick'])) + msg_d.append(('userid', profile['userid'])) + msg_d.append(('email', profile['email'])) + msg_d.append(('sig', utils.generate_random_hex_str(32))) + msg_d.append(('uniquenick', profile['uniquenick'])) + msg_d.append(('pid', profile['pid'])) + msg_d.append(('lastname', profile['lastname'])) + msg_d.append(('lon', profile['lon'])) + msg_d.append(('lat', profile['lat'])) + msg_d.append(('loc', profile['loc'])) + msg_d.append(('id', data_parsed['id'])) + msg = gs_query.create_gamespy_message(msg_d) - elif data_parsed['__cmd__'] == "status": - # Handle status update - msg = "" + elif data_parsed['__cmd__'] == "updatepro": + # Handle properly later + # Assume that there will be other parameters besides lastname, so make it a loop or something along those lines later + db.update_profile(data_parsed['sesskey'], [("lastname", data_parsed['lastname'])]) - elif data_parsed['__cmd__'] == "logout": - print "Session %s has logged off" % (data_parsed['sesskey']) - db.delete_session(data_parsed['sesskey']) - accept_connection = False + elif data_parsed['__cmd__'] == "status": + # Handle status update + msg = "" - utils.print_log("SENDING: %s" % msg) - client.send(msg) + elif data_parsed['__cmd__'] == "logout": + print "Session %s has logged off" % (data_parsed['sesskey']) + db.delete_session(data_parsed['sesskey']) + accept_connection = False - client.close() + utils.print_log("SENDING: %s" % msg) + client.send(msg) + + client.close() diff --git a/master_server.py b/master_server.py index 1cd0da9..15e5160 100644 --- a/master_server.py +++ b/master_server.py @@ -3,27 +3,47 @@ import socket import time import other.utils as utils + def get_game_id(data): - game_id = data[5: -1] - return game_id + game_id = data[5: -1] + return game_id #address = ('127.0.0.1', 27900) # accessible to only the local computer -address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing) +address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(address) utils.print_log("Server is now listening on %s:%s..." % (address[0], address[1])) -while(1): - recv_data, addr = s.recvfrom(2048) - - if [ord(x) for x in recv_data[0:5]] == [0x09, 0x00, 0x00, 0x00, 0x00]: - utils.print_log("Received request for '%s' from %s:%s... %s" % (get_game_id(recv_data), addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) - - # I have not seen any games that use anything other than \x09\x00\x00\x00\x00 + null terminated game id, - # but just in case there are others out there, copy the data received from the game as the response. - s.sendto(bytearray([0xfe, 0xfd, recv_data[0], recv_data[1], recv_data[2], recv_data[3], recv_data[4]]), addr) - else: - utils.print_log("Unknown request from %s:%s: %s" % (addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) +while 1: + recv_data, addr = s.recvfrom(2048) + + # After some more packet inspection, it seems the format goes something like this: + # - All server messages seem to always start with \xfe\xfe. + # - The first byte from the client (or third byte from the server) is a command. + # - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a + # session-like ID because the number changes between connections. + # + # - Commands + # CLIENT: + # 0x03 - Send client state? + # 0x07 - Unknown, related to server's 0x06 (returns value sent from server) + # 0x08 - Keep alive? + # 0x09 - Availability check + # SERVER: + # 0x01 - Unknown + # 0x06 - Unknown + # 0x0a - Unknown + + if [ord(x) for x in recv_data[0:5]] == [0x09, 0x00, 0x00, 0x00, 0x00]: + utils.print_log("Received request for '%s' from %s:%s... %s" % ( + get_game_id(recv_data), addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) + + # I have not seen any games that use anything other than \x09\x00\x00\x00\x00 + null terminated game id, + # but just in case there are others out there, copy the data received from the game as the response. + s.sendto(bytearray([0xfe, 0xfd, recv_data[0], recv_data[1], recv_data[2], recv_data[3], recv_data[4]]), addr) + else: + utils.print_log( + "Unknown request from %s:%s: %s" % (addr[0], addr[1], [elem.encode("hex") for elem in recv_data])) diff --git a/other/utils.py b/other/utils.py index 7b134d9..ad8703a 100644 --- a/other/utils.py +++ b/other/utils.py @@ -1,89 +1,92 @@ import random import time - + def generate_random_str(len): - return ''.join(random.choice("abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") for _ in range(len)) + return ''.join(random.choice("abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") for _ in range(len)) def generate_random_number_str(len): - return ''.join(random.choice("1234567890") for _ in range(len)) + return ''.join(random.choice("1234567890") for _ in range(len)) + +def generate_random_hex_str(len): + return ''.join(random.choice("1234567890abcdef") for _ in range(len)) # Code: Tetris DS @ 020573F4 def calculate_crc8(input): - crc_table = [ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, - 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, - 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, - 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, - 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, - 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, - 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, - 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, - 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, - 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, - 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, - 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, - 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, - 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, - 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, - 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 ] - crc = 0 + crc_table = [ 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, + 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, + 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, + 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, + 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, + 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, + 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, + 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, + 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, + 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, + 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, + 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, + 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, + 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, + 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, + 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 ] + crc = 0 - for b in input: - crc = crc_table[(b ^ crc) & 0xff] + for b in input: + crc = crc_table[(b ^ crc) & 0xff] - return crc + return crc def base32_encode(num, reverse = True): - alpha = "0123456789abcdefghijklmnopqrstuv" + alpha = "0123456789abcdefghijklmnopqrstuv" - encoded = "" - while num > 0: - n = num & 0x1f - num = num >> 5 - encoded += alpha[n] + encoded = "" + while num > 0: + n = num & 0x1f + num = num >> 5 + encoded += alpha[n] - if reverse == True: - encoded = encoded[::-1] # Reverse string + if reverse == True: + encoded = encoded[::-1] # Reverse string - return encoded + return encoded def base32_decode(str, reverse = False): - alpha = "0123456789abcdefghijklmnopqrstuv" + alpha = "0123456789abcdefghijklmnopqrstuv" - if reverse == True: - str = str[::-1] # Reverse string + if reverse == True: + str = str[::-1] # Reverse string - orig = 0 - for b in str: - orig = orig << 5 - orig = orig | alpha.index(b) + orig = 0 + for b in str: + orig = orig << 5 + orig = orig | alpha.index(b) - return orig + return orig # Code: Tetris DS @ 02057A14 def get_friendcode_from_profileid(profileid, gameid): - friendcode = 0 + friendcode = 0 - # Combine the profileid and gameid into one buffer - buffer = [(profileid >> (8 * i)) & 0xff for i in range(4)] - buffer += [ord(c) for c in gameid] - - crc = calculate_crc8(buffer) + # Combine the profileid and gameid into one buffer + buffer = [(profileid >> (8 * i)) & 0xff for i in range(4)] + buffer += [ord(c) for c in gameid] - # The upper 32 bits is the crc8 of the combined buffer. - # The lower 32 bits of the friend code is the profileid. - friendcode = ((crc & 0x7f) << 32) | profileid + crc = calculate_crc8(buffer) - return friendcode + # The upper 32 bits is the crc8 of the combined buffer. + # The lower 32 bits of the friend code is the profileid. + friendcode = ((crc & 0x7f) << 32) | profileid + + return friendcode def get_profileid_from_friendcode(friendcode): - # Get the lower 32 bits as the profile id - profileid = friendcode & 0xffffffff - return profileid + # Get the lower 32 bits as the profile id + profileid = friendcode & 0xffffffff + return profileid # For server logging def print_log(text): - print "[%s] %s" % (time.strftime("%c"), text) - print "" \ No newline at end of file + print "[%s] %s" % (time.strftime("%c"), text) + print "" \ No newline at end of file