Cleaned up

Cleaned up the code using PyCharm. Also added a few comments about the
master server.
This commit is contained in:
polaris 2014-03-29 23:41:47 -04:00
parent 107017c0af
commit eae79e9def
7 changed files with 473 additions and 421 deletions

7
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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]))

View File

@ -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 ""
print "[%s] %s" % (time.strftime("%c"), text)
print ""