From 506accf5d8ab26cd2478d1f2dd3f7c7250f41434 Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 07:31:58 -0400 Subject: [PATCH 1/6] Fixes to matchmaking --- gamespy_backend_server.py | 24 +++++++++ gamespy_natneg_server.py | 86 ++++++++++++++++++++++++++++---- gamespy_server_browser_server.py | 6 ++- 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/gamespy_backend_server.py b/gamespy_backend_server.py index 8e76b4f..00d3a39 100644 --- a/gamespy_backend_server.py +++ b/gamespy_backend_server.py @@ -50,6 +50,7 @@ class GameSpyBackendServer(object): GameSpyServerDatabase.register("get_server_list", callable=lambda:self.server_list) GameSpyServerDatabase.register("find_servers", callable=self.find_servers) GameSpyServerDatabase.register("find_server_by_address", callable=self.find_server_by_address) + GameSpyServerDatabase.register("find_server_by_local_address", callable=self.find_server_by_local_address) GameSpyServerDatabase.register("update_server_list", callable=self.update_server_list) GameSpyServerDatabase.register("delete_server", callable=self.delete_server) @@ -390,6 +391,29 @@ class GameSpyBackendServer(object): return None + def find_server_by_local_address(self, publicip, localip, localport, gameid = None): + if gameid == None: + # Search all servers + for gameid in self.server_list: + for server in self.server_list[gameid]: + logger.log(logging.DEBUG, "publicip: %s == %s ? %d" % (server['publicip'], publicip, server['publicip'] == publicip)) + if server['publicip'] == publicip and server['localport'] == str(localport): + for x in range(0, 10): + s = 'localip%d' % x + if s in server: + if server[s] == localip: + return server + else: + for server in self.server_list[gameid]: + logger.log(logging.DEBUG, "publicip: %s == %s ? %d" % (server['publicip'], publicip, server['publicip'] == publicip)) + if server['publicip'] == publicip and server['localport'] == str(localport): + for x in range(0, 10): + s = 'localip%d' % x + if s in server: + if server[s] == localip: + return server + return None + if __name__ == '__main__': freeze_support() diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 6bf0f6b..b2c290c 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -3,10 +3,13 @@ import logging import socket +import ctypes import struct import gamespy.gs_utility as gs_utils import other.utils as utils +from multiprocessing.managers import BaseManager + # Logger settings logger_output_to_console = True logger_output_to_file = True @@ -14,11 +17,25 @@ logger_name = "GameSpyNatNegServer" logger_filename = "gamespy_natneg_server.log" logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +class GameSpyServerDatabase(BaseManager): + pass + +GameSpyServerDatabase.register("get_server_list") +GameSpyServerDatabase.register("modify_server_list") +GameSpyServerDatabase.register("find_servers") +GameSpyServerDatabase.register("find_server_by_address") +GameSpyServerDatabase.register("find_server_by_local_address") + class GameSpyNatNegServer(object): def __init__(self): self.session_list = {} self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg") + manager_address = ("127.0.0.1", 27500) + manager_password = "" + self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password) + self.server_manager.connect() + def start(self): # Start natneg server address = ('0.0.0.0', 27901) # accessible to outside connections (use this if you don't know what you're doing) @@ -54,46 +71,72 @@ class GameSpyNatNegServer(object): gameid = utils.get_string(recv_data, 0x16) client_id = "%02x" % ord(recv_data[13]) + localip_raw = recv_data[15:19] + localip = '.'.join(["%d" % ord(x) for x in localip_raw]) + localport_raw = recv_data[19:21] + localport = utils.get_short_be(localport_raw, 0) + localaddr = (localip, localport) + if gameid not in self.session_list: self.session_list[gameid] = {} if session_id not in self.session_list[gameid]: self.session_list[gameid][session_id] = {} if client_id not in self.session_list[gameid][session_id]: - self.session_list[gameid][session_id][client_id] = { 'connected': False, 'addr': '' } + self.session_list[gameid][session_id][client_id] = { 'connected': False, 'addr': '', 'localaddr': None, 'serveraddr': None, 'gameid': None } + self.session_list[gameid][session_id][client_id]['gameid'] = utils.get_string(recv_data[21:], 0) self.session_list[gameid][session_id][client_id]['addr'] = addr + self.session_list[gameid][session_id][client_id]['localaddr'] = localaddr clients = len(self.session_list[gameid][session_id]) for client in self.session_list[gameid][session_id]: - if self.session_list[gameid][session_id][client]['connected'] == False: + if self.session_list[gameid][session_id][client]['connected'] == False and self.session_list[gameid][session_id][client]['localaddr'][1] != 0: if client == client_id: continue + if self.session_list[gameid][session_id][client]['serveraddr'] == None: + serveraddr = self.get_server_info(gameid, session_id, client) + self.session_list[gameid][session_id][client]['serveraddr'] = serveraddr + logger.log(logging.DEBUG, "Found server from local ip/port: %s" % serveraddr) + + publicport = 0 + if self.session_list[gameid][session_id][client]['serveraddr'] != None: + publicport = int(self.session_list[gameid][session_id][client]['serveraddr']['publicport']) + # Send to requesting client output = bytearray(recv_data[0:12]) output += bytearray([int(x) for x in self.session_list[gameid][session_id][client]['addr'][0].split('.')]) - output += utils.get_bytes_from_short_be(self.session_list[gameid][session_id][client]['addr'][1]) + output += utils.get_bytes_from_short_be(publicport) output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00 output[7] = 0x05 - s.sendto(output, (self.session_list[gameid][session_id][client_id]['addr'])) + #s.sendto(output, (self.session_list[gameid][session_id][client_id]['addr'])) + s.sendto(output, (self.session_list[gameid][session_id][client_id]['addr'][0], self.session_list[gameid][session_id][client_id]['addr'][1])) logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[gameid][session_id][client_id]['addr'][0], self.session_list[gameid][session_id][client_id]['addr'][1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) - logger.log(logging.DEBUG, "") # Send to other client + if self.session_list[gameid][session_id][client_id]['serveraddr'] == None: + serveraddr = self.get_server_info(gameid, session_id, client_id) + self.session_list[gameid][session_id][client_id]['serveraddr'] = serveraddr + logger.log(logging.DEBUG, "Found server 2 from local ip/port: %s" % serveraddr) + + publicport = 0 + if self.session_list[gameid][session_id][client_id]['serveraddr'] != None: + publicport = int(self.session_list[gameid][session_id][client_id]['serveraddr']['publicport']) + output = bytearray(recv_data[0:12]) output += bytearray([int(x) for x in self.session_list[gameid][session_id][client_id]['addr'][0].split('.')]) - output += utils.get_bytes_from_short_be(self.session_list[gameid][session_id][client_id]['addr'][1]) + output += utils.get_bytes_from_short_be(publicport) output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00 output[7] = 0x05 - s.sendto(output, (self.session_list[gameid][session_id][client]['addr'])) + #s.sendto(output, (self.session_list[gameid][session_id][client]['addr'])) + s.sendto(output, (self.session_list[gameid][session_id][client]['addr'][0], self.session_list[gameid][session_id][client]['addr'][1])) logger.log(logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[gameid][session_id][client]['addr'][0], self.session_list[gameid][session_id][client]['addr'][1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) - logger.log(logging.DEBUG, "") elif recv_data[7] == '\x06': # Was able to connect client_id = "%02x" % ord(recv_data[13]) @@ -106,7 +149,7 @@ class GameSpyNatNegServer(object): if client_id not in self.session_list[gameid][session_id]: pass - #self.session_list[gameid][session_id][client_id]['connected'] = True + self.session_list[gameid][session_id][client_id]['connected'] = True elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED! client_id = "%02x" % ord(recv_data[13]) @@ -135,6 +178,31 @@ class GameSpyNatNegServer(object): else: # Was able to connect logger.log(logging.DEBUG, "Received unknown command %02x from %s:%s..." % (ord(recv_data[7]), addr[0], addr[1])) + def get_server_info(self, gameid, session_id, client_id): + console = 0 + ipstr = self.session_list[gameid][session_id][client_id]['addr'][0] + + if console != 0: + ip = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in ipstr.split('.')]), 0)).value) # Wii + console = 0 + else: + ip = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in ipstr.split('.')]), 0)).value) # DS + console = 1 + + serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[gameid][session_id][client_id]['localaddr'][0], self.session_list[gameid][session_id][client_id]['localaddr'][1], self.session_list[gameid][session_id][client_id]['gameid'])._getvalue() + + if serveraddr == None: + if console != 0: + ip = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in ipstr.split('.')]), 0)).value) # Wii + console = 0 + else: + ip = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in ipstr.split('.')]), 0)).value) # DS + console = 1 + + serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[gameid][session_id][client_id]['localaddr'][0], self.session_list[gameid][session_id][client_id]['localaddr'][1], self.session_list[gameid][session_id][client_id]['gameid'])._getvalue() + + return serveraddr + if __name__ == "__main__": natneg_server = GameSpyNatNegServer() natneg_server.start() \ No newline at end of file diff --git a/gamespy_server_browser_server.py b/gamespy_server_browser_server.py index 21bb04d..2ddaea3 100644 --- a/gamespy_server_browser_server.py +++ b/gamespy_server_browser_server.py @@ -364,6 +364,7 @@ class Session(LineReceiver): # #self.server_cache.pop((publicip + str(self.forward_client[1]))) server = self.server_manager.find_server_by_address(ip, self.forward_client[1])._getvalue() + self.log(logging.DEBUG, "find_server_in_cache is returning: %s %s" % (server, ip)) return server, ip @@ -371,6 +372,9 @@ class Session(LineReceiver): # Find session id of server # Iterate through the list of servers sent to the client and match by IP and port. # Is there a better way to determine this information? + if self.forward_client == None or len(self.forward_client) != 2: + return + server, ip = self.find_server_in_cache(self.forward_client[0], self.forward_client[1], self.console) if server == None: @@ -389,7 +393,7 @@ class Session(LineReceiver): self.log(logging.DEBUG, "%s %s" % (ip, server['publicip'])) if server['publicip'] == ip and server['publicport'] == str(self.forward_client[1]): # Send command to server to get it to connect to natneg - natneg_session = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else late.r + natneg_session = int(utils.generate_random_hex_str(8), 16) # Quick and lazy way to get a random 32bit integer. Replace with something else later output = bytearray([0xfe, 0xfd, 0x06]) output += utils.get_bytes_from_int(server['__session__']) From 8d35ad085b6159d06b7c78b3b3b5f4930e4a7d2d Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 07:32:03 -0400 Subject: [PATCH 2/6] Added svcloc to NAS --- nintendo_nas_server.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index b8f48b9..ecc9ac4 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -71,6 +71,21 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.wfile.write(self.dict_to_str(ret)) + elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number + if post["svc"] == "9000": # DLC host = 9000 + ret["returncd"] = "007" + ret["statusdata"] = "Y" + ret["svchost"] = "dls1.nintendowifi.net" + authtoken = self.server.db.generate_authtoken(post["userid"], post) + ret["token"] = authtoken + + logger.log(logging.DEBUG, "svcloc response to %s", self.client_address) + logger.log(logging.DEBUG, ret) + + self.wfile.write(self.dict_to_str(ret)) + + + elif self.path == "/pr": length = int(self.headers['content-length']) post = self.str_to_dict(self.rfile.read(length)) @@ -91,7 +106,6 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): action = post["action"] - # TODO: Cache all DLC information in a database instead of querying the folder. ret = "" dlcpath = "dlc/" + post["gamecd"] dlc_contenttype = False From 783376909f7673f5ee3cce3828b63731e78bba10 Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 08:04:02 -0400 Subject: [PATCH 3/6] Remove noisy log statement from NAS --- nintendo_nas_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index ecc9ac4..043dbc3 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -149,8 +149,8 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.log(logging.DEBUG, "download response to %s", self.client_address) - if dlc_contenttype == False: - logger.log(logging.DEBUG, ret) + #if dlc_contenttype == False: + # logger.log(logging.DEBUG, ret) self.wfile.write(ret) From c28ad4b7a23ff8d5d9230aa755781149678337a1 Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 08:11:50 -0400 Subject: [PATCH 4/6] Revert "Implement "SVCLOC" action." This reverts commit a54cfa48e067ed0a0a67fa138c2952dc217c3288. --- nintendo_nas_server.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index 77ae260..7c8c855 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -70,15 +70,6 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): logger.log(logging.DEBUG, ret) self.wfile.write(self.dict_to_str(ret)) - - elif action == "SVCLOC": - ret["returncd"] = "007" - ret["statusdata"] = "Y" - ret["svchost"] = "dls1.nintendowifi.net" - - logger.log(logging.DEBUG, "SVCLOC response to %s", self.client_address) - logger.log(logging.DEBUG, ret) - self.wfile.write(self.dict_to_str(ret)) elif action == "SVCLOC" or action == "svcloc": # Get service based on service id number if post["svc"] == "9000": # DLC host = 9000 From d1912078605ce7e7aa1039c575c2a6f2441871c0 Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 16:55:31 -0400 Subject: [PATCH 5/6] Add filtering to dls1 file listing --- nintendo_nas_server.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index 7c8c855..3b2e54d 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -126,11 +126,22 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): num = int(post["num"]) offset = int(post["offset"]) + attr1 = None + if "attr1" in post: + attr1 = post["attr1"] + attr2 = None + if "attr2" in post: + attr2 = post["attr2"] + attr3 = None + if "attr3" in post: + attr3 = post["attr3"] + if os.path.exists(dlcpath): # Look for a list file first. # If the list file exists, send the entire thing back to the client. if os.path.isfile(dlcpath + "/_list.txt"): ret = open(dlcpath + "/_list.txt", "rb").read() + ret = self.filter_list(ret, attr1, attr2, attr3) if action == "contents": # Get only the base filename just in case there is a path involved somewhere in the filename string. @@ -173,6 +184,39 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): # nas(wii).nintendowifi.net has a URL query-like format but does not use encoding for special characters return "&".join("{!s}={!s}".format(k, v) for k, v in dict.items()) + def filter_list(self, data, attr1 = None, attr2 = None, attr3 = None): + if attr1 == None and attr2 == None and attr3 == None: + # Nothing to filter, just return the input data + return data + + # Filter the list based on the attribute fields + output = "" + + for line in data.split('\n'): + s = line.split('\t') + + data = {} + data['filename'] = s[0] + data['desc'] = s[1] + data['attr1'] = s[2] + data['attr2'] = s[3] + data['attr3'] = s[4] + data['filesize'] = s[5] + + matched = True + if attr1 != None and data['attr1'] != attr1: + matched = False + if attr2 != None and data['attr2'] != attr2: + matched = False + if attr3 != None and data['attr3'] != attr3: + matched = False + + if matched == True: + output += line + '\r\n' + + + return output + if __name__ == "__main__": nas = NintendoNasServer() nas.start() From 751b0066cfde285d2dab8343209420caab830470 Mon Sep 17 00:00:00 2001 From: polaris Date: Wed, 14 May 2014 16:56:03 -0400 Subject: [PATCH 6/6] Added more fields to the database --- gamespy/gs_database.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/gamespy/gs_database.py b/gamespy/gs_database.py index 2762c8f..d0261bf 100644 --- a/gamespy/gs_database.py +++ b/gamespy/gs_database.py @@ -31,7 +31,7 @@ class GamespyDatabase(object): # 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. - q = "CREATE TABLE users (profileid INT, userid TEXT, password TEXT, gsbrcd TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, firstname TEXT, lastname TEXT, stat TEXT, partnerid TEXT, console INT, csnum TEXT, cfc TEXT, bssid TEXT, devname TEXT, birth TEXT, gameid TEXT, enabled INT)" + q = "CREATE TABLE users (profileid INT, userid TEXT, password TEXT, gsbrcd TEXT, email TEXT, uniquenick TEXT, pid TEXT, lon TEXT, lat TEXT, loc TEXT, firstname TEXT, lastname TEXT, stat TEXT, partnerid TEXT, console INT, csnum TEXT, cfc TEXT, bssid TEXT, devname TEXT, birth TEXT, gameid TEXT, enabled INT, zipcode TEXT, aim TEXT)" logger.log(-1, q) c.execute(q) @@ -171,6 +171,8 @@ class GamespyDatabase(object): stat = "" partnerid = "" enabled = 1 + zipcode = "" + aim = "" # Hash password before entering it into the database. # For now I'm using a very simple MD5 hash. @@ -179,12 +181,12 @@ class GamespyDatabase(object): md5.update(password) password = md5.hexdigest() - q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - q2 = q.replace("?", "%s") % (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled) + q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + q2 = q.replace("?", "%s") % (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim) logger.log(-1, q2) c = self.conn.cursor() - c.execute(q, [profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled]) + c.execute(q, [profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim]) c.close() self.conn.commit() @@ -207,13 +209,17 @@ class GamespyDatabase(object): bssid = "" devname = "" birth = "" + zipcode = "" + aim = "" - q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - q2 = q.replace("?", "%s") % (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid) + enabled = 1 + + q = "INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + q2 = q.replace("?", "%s") % (profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim) logger.log(-1, q2) c = self.conn.cursor() - c.execute(q, [profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid]) + c.execute(q, [profileid, str(userid), password, gsbrcd, email, uniquenick, pid, lon, lat, loc, firstname, lastname, stat, partnerid, console, csnum, cfc, bssid, devname, birth, gameid, enabled, zipcode, aim]) c.close() self.conn.commit()