mirror of
https://github.com/barronwaffles/dwc_network_server_emulator.git
synced 2026-03-21 17:34:21 -05:00
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.
This commit is contained in:
commit
f9c710af99
22
.gitattributes
vendored
Normal file
22
.gitattributes
vendored
Normal file
|
|
@ -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
|
||||
215
.gitignore
vendored
Normal file
215
.gitignore
vendored
Normal file
|
|
@ -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
|
||||
0
gamespy/__init__.py
Normal file
0
gamespy/__init__.py
Normal file
198
gamespy/gs_database.py
Normal file
198
gamespy/gs_database.py
Normal file
|
|
@ -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
|
||||
116
gamespy/gs_query.py
Normal file
116
gamespy/gs_query.py
Normal file
|
|
@ -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
|
||||
65
gamespy/gs_utility.py
Normal file
65
gamespy/gs_utility.py
Normal file
|
|
@ -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()
|
||||
|
||||
129
gamespy_server.py
Normal file
129
gamespy_server.py
Normal file
|
|
@ -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()
|
||||
29
master_server.py
Normal file
29
master_server.py
Normal file
|
|
@ -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]))
|
||||
|
||||
0
other/__init__.py
Normal file
0
other/__init__.py
Normal file
89
other/utils.py
Normal file
89
other/utils.py
Normal file
|
|
@ -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 ""
|
||||
12
www/conntest/index.html
Normal file
12
www/conntest/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>HTML Page</title>
|
||||
</head>
|
||||
|
||||
<body bgcolor="#FFFFFF">
|
||||
This is test.html page
|
||||
</body>
|
||||
|
||||
</html>
|
||||
133
www/nas/ac.php
Normal file
133
www/nas/ac.php
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
// Return the same headers as the real server does for the sake of completeness.
|
||||
// They never get checked so they are entirely optional.
|
||||
header("NODE: wifiappw3");
|
||||
header("Server: Nintendo Wii (http)");
|
||||
|
||||
function parse($key, $value)
|
||||
{
|
||||
$value2 = frombase64($value);
|
||||
$str = "{$key} = {$value} ({$value2})";
|
||||
|
||||
if($key == "token" && substr($value2, 0, 3) == "NDS")
|
||||
{
|
||||
$b64 = base64_decode(substr($value2, 3));
|
||||
}
|
||||
|
||||
$str .= "\r\n";
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
function tobase64($str)
|
||||
{
|
||||
$str = str_replace("=", "*", base64_encode($str));
|
||||
return $str;
|
||||
}
|
||||
|
||||
function frombase64($str)
|
||||
{
|
||||
$str = base64_decode(str_replace("*", "=", $str));
|
||||
return $str;
|
||||
}
|
||||
|
||||
function gen_random_str($len)
|
||||
{
|
||||
$valid = "abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||
$validlen = strlen($valid);
|
||||
|
||||
$output = "";
|
||||
$i = 0;
|
||||
while($i++ < $len)
|
||||
{
|
||||
$output .= $valid[mt_rand(0, $validlen - 1)];
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Debug log file
|
||||
$file = fopen("output.txt","a+");
|
||||
|
||||
$authkey = "";
|
||||
$str = "POST:\r\n";
|
||||
foreach ($_POST 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";
|
||||
|
||||
// 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;
|
||||
?>
|
||||
Loading…
Reference in New Issue
Block a user