From f9c710af99fd3dca0bc66a30c5eea0f37cfdbc01 Mon Sep 17 00:00:00 2001 From: andromeda32 Date: Sat, 29 Mar 2014 21:41:02 -0400 Subject: [PATCH] Initial code. Contains enough code to allow Tetris DS to get to the post-login wifi menu. This is very rough, basic code. I expect it to change quite a bit from its current format once I start adding more features. It's nowhere near ready for public use yet. --- .gitattributes | 22 ++++ .gitignore | 215 ++++++++++++++++++++++++++++++++++++++++ gamespy/__init__.py | 0 gamespy/gs_database.py | 198 ++++++++++++++++++++++++++++++++++++ gamespy/gs_query.py | 116 ++++++++++++++++++++++ gamespy/gs_utility.py | 65 ++++++++++++ gamespy_server.py | 129 ++++++++++++++++++++++++ master_server.py | 29 ++++++ other/__init__.py | 0 other/utils.py | 89 +++++++++++++++++ www/conntest/index.html | 12 +++ www/nas/ac.php | 133 +++++++++++++++++++++++++ 12 files changed, 1008 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 gamespy/__init__.py create mode 100644 gamespy/gs_database.py create mode 100644 gamespy/gs_query.py create mode 100644 gamespy/gs_utility.py create mode 100644 gamespy_server.py create mode 100644 master_server.py create mode 100644 other/__init__.py create mode 100644 other/utils.py create mode 100644 www/conntest/index.html create mode 100644 www/nas/ac.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/gamespy/__init__.py b/gamespy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py new file mode 100644 index 0000000..4c99e8c --- /dev/null +++ b/gamespy/gs_database.py @@ -0,0 +1,198 @@ +import sqlite3 +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 + + 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 get_dict(self, row): + if row == None: + return None + + return dict(itertools.izip(row.keys(), row)) + + # User functions + def get_next_free_profileid(self): + c = self.conn.cursor() + c.execute("SELECT max(profileid) FROM users") + + r = c.fetchone() + + profileid = 476639431 #100000000 + if r != None and r['max(profileid)'] != None: + profileid = int(r[0]) + 1 + + c.close() + + return profileid + + def check_user_exists(self, userid): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE userid = ?", [userid]) + + r = self.get_dict(c.fetchone()) + + valid_user = False # Default, user doesn't exist + if r != None: + valid_user = True # Valid password + + c.close() + return valid_user + + def check_profile_exists(self, profileid): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE profileid = ?", [profileid]) + + r = self.get_dict(c.fetchone()) + + valid_profile = False # Default, user doesn't exist + if r != None: + valid_profile = True # Valid password + + c.close() + return valid_profile + + def perform_login(self, userid, password): + c = self.conn.cursor() + c.execute("SELECT * FROM users WHERE userid = ?", [userid]) + + r = self.get_dict(c.fetchone()) + + profileid = None # Default, user doesn't exist + if r != None: + md5 = hashlib.md5() + md5.update(password) + + if r['password'] == md5.hexdigest(): + profileid = r['profileid'] # Valid password + + c.close() + return profileid + + 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 = "" + + # 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() + + c = self.conn.cursor() + c.execute("INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?)", [profileid, str(userid), password, email, uniquenick, pid, lon, lat, loc, lastname]) + c.close() + + self.conn.commit() + + return profileid + + def get_user_list(self): + c = self.conn.cursor() + + 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() + + + + # 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 generate_session_key(self, min_size): + session_key = utils.generate_random_number_str(min_size) + + 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) + + return session_key + + def create_session(self, profileid): + if profileid != None and self.check_profile_exists(profileid) == False: + return None + + # Remove any old sessions associated with this user id + self.delete_session(profileid) + + # 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() + + 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() + + 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)) + + return sessions diff --git a/gamespy/gs_query.py b/gamespy/gs_query.py new file mode 100644 index 0000000..55ba9d8 --- /dev/null +++ b/gamespy/gs_query.py @@ -0,0 +1,116 @@ +import copy + +def parse_gamespy_message(message): + stack = [] + messages = {} + msg = message + + 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:] + + 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 + + messages[key] = value + + 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) + + cmd = "" + cmd_val = "" + + 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 in messages: + messages.pop(cmd, None) + + l = [] + l.append(("__cmd__", cmd)) + l.append(("__cmd_val__", cmd_val)) + + for message in messages: + l.append((message, messages[message])) + + return l + + +def create_gamespy_message_from_list(messages): + 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]) + + if cmd != "": + # Prepend the main command if one was found. + query = "\\%s\\%s%s" % (cmd, cmd_val, 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) + + # 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 + + query = create_gamespy_message_from_list(messages) + + if id != None: + query += create_gamespy_message_from_list([("id", id)]) + + query += "\\final\\" + + return query diff --git a/gamespy/gs_utility.py b/gamespy/gs_utility.py new file mode 100644 index 0000000..c35599e --- /dev/null +++ b/gamespy/gs_utility.py @@ -0,0 +1,65 @@ +from ctypes import c_uint +import sys +import base64 +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 + +def base64_decode(input): + 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 = {} + + if authtoken[:3] == "NDS": + authtoken = authtoken[3:] + + dec = base64.standard_b64decode(authtoken) + + for item in dec.split('|'): + s = item.split('\\') + messages[s[0]] = s[1] + + return messages + + +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() + + 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) + + 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() + \ No newline at end of file diff --git a/gamespy_server.py b/gamespy_server.py new file mode 100644 index 0000000..252da1b --- /dev/null +++ b/gamespy_server.py @@ -0,0 +1,129 @@ +# Server emulator for gpcm.gs.nintendowifi.net +import socket +import gamespy.gs_database as gs_database +import gamespy.gs_query as gs_query +import gamespy.gs_utility as gs_utils +import other.utils as utils + +db = gs_database.GamespyDatabase() + +address = ('0.0.0.0', 29900) +backlog = 10 +size = 2048 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.bind(address) +s.listen(backlog) + +utils.print_log("Server is now listening on listening on %s:%s..." % (address[0], address[1])) + +while 1: + 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])) + + # 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) + + 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) + + commands = gs_query.parse_gamespy_message(data) + + for data_parsed in commands: + msg_d = [] + + print data_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 + + # 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']) + + 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" + + sesskey = db.create_session(profileid) + + 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) + + elif data_parsed['__cmd__'] == "getprofile": + 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__'] == "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__'] == "status": + # Handle status update + msg = "" + + elif data_parsed['__cmd__'] == "logout": + print "Session %s has logged off" % (data_parsed['sesskey']) + db.delete_session(data_parsed['sesskey']) + accept_connection = False + + utils.print_log("SENDING: %s" % msg) + client.send(msg) + + client.close() diff --git a/master_server.py b/master_server.py new file mode 100644 index 0000000..54165c1 --- /dev/null +++ b/master_server.py @@ -0,0 +1,29 @@ +# Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net +import socket +import time +import other.utils as utils + +def get_game_id(data): + 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) + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(address) + +utils.print_log("Server is now listening on 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])) + diff --git a/other/__init__.py b/other/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/other/utils.py b/other/utils.py new file mode 100644 index 0000000..7b134d9 --- /dev/null +++ b/other/utils.py @@ -0,0 +1,89 @@ +import random +import time + +def generate_random_str(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)) + +# 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 + + for b in input: + crc = crc_table[(b ^ crc) & 0xff] + + return crc + + +def base32_encode(num, reverse = True): + alpha = "0123456789abcdefghijklmnopqrstuv" + + encoded = "" + while num > 0: + n = num & 0x1f + num = num >> 5 + encoded += alpha[n] + + if reverse == True: + encoded = encoded[::-1] # Reverse string + + return encoded + + +def base32_decode(str, reverse = False): + alpha = "0123456789abcdefghijklmnopqrstuv" + + if reverse == True: + str = str[::-1] # Reverse string + + orig = 0 + for b in str: + orig = orig << 5 + orig = orig | alpha.index(b) + + return orig + +# Code: Tetris DS @ 02057A14 +def get_friendcode_from_profileid(profileid, gameid): + 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) + + # 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 + + +# For server logging +def print_log(text): + print "[%s] %s" % (time.strftime("%c"), text) + print "" \ No newline at end of file diff --git a/www/conntest/index.html b/www/conntest/index.html new file mode 100644 index 0000000..e55b020 --- /dev/null +++ b/www/conntest/index.html @@ -0,0 +1,12 @@ + + + + + HTML Page + + + +This is test.html page + + + diff --git a/www/nas/ac.php b/www/nas/ac.php new file mode 100644 index 0000000..b85fb59 --- /dev/null +++ b/www/nas/ac.php @@ -0,0 +1,133 @@ + $value) +{ + $str .= parse($key, $value); + + if(//$key == "action" || + $key == "gsbrcd" || + //$key == "sdkver" || + $key == "userid" || + $key == "passwd" || + //$key == "bssid" || + //$key == "apinfo" || + //$key == "gamecd" || + //$key == "makercd" || + //$key == "unitcd" || + //$key == "macadr" || + //$key == "lang" || + //$key == "birth" || + //$key == "devtime" || + $key == "devname" || + $key == "ingamesn") + { + $authkey .= $key . "\\" . frombase64($value) . "|"; + } +} +$str .= "\r\n"; + +// Gets are not a part of the spec, but they allow for easy testing without having to POST every time +$str .= "GET:\r\n"; +foreach ($_GET as $key => $value) +{ + $str .= parse($key, $value); + + if(//$key == "action" || + $key == "gsbrcd" || + //$key == "sdkver" || + $key == "userid" || + $key == "passwd" || + //$key == "bssid" || + //$key == "apinfo" || + //$key == "gamecd" || + //$key == "makercd" || + //$key == "unitcd" || + //$key == "macadr" || + //$key == "lang" || + //$key == "birth" || + //$key == "devtime" || + $key == "devname" || + $key == "ingamesn") + { + $authkey .= $key . "\\" . frombase64($value) . "|"; + } +} +$str .= "\r\n"; +$str .= "\r\n"; +$str .= "\r\n"; + +// Write data gotten from POST/GET so we can view it later more easily +fwrite($file, $str); +fclose($file); + +$now = getdate(); +$time = sprintf("%04d%02d%02d%02d%02d%02d", $now['year'], $now['mon'], $now['mday'], $now['hours'], $now['minutes'], $now['seconds']); +$time = base64_encode($time); +$time = str_replace("=", "*", $time); + +$challenge_key = gen_random_str(8); +$challenge = tobase64($challenge_key); +$locator = tobase64("gamespy.com"); +$retry = tobase64("0"); +$returncd = tobase64("001"); + +$authkey .= "challenge\\" . $challenge_key; + +// Encode the information we need to handle logins on the gamespy server. +// This informaiton is not the same as the real server would return, but we don't need to maintain +// interoperability with the real server, so we can ignore that detail. +$token = tobase64("NDS" . base64_encode($authkey)); + +echo "challenge=" . $challenge . "&locator=" . $locator . "&retry=" . $retry . "&returncd=" . $returncd . "&token=" . $token . "&datetime=" . $time; +?> \ No newline at end of file