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:
andromeda32 2014-03-29 21:41:02 -04:00
commit f9c710af99
12 changed files with 1008 additions and 0 deletions

22
.gitattributes vendored Normal file
View 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
View 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
View File

198
gamespy/gs_database.py Normal file
View 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
View 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
View 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
View 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
View 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
View File

89
other/utils.py Normal file
View 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
View 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
View 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;
?>