From 359f4eb3e8a3dab265fc9635ec96a376263d6f31 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 ed38b0c82391ae1f92ebd1ca8d619e8d3b6a0ce4 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 6a3c0ed..0e5c4e7 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -80,6 +80,21 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): 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 + 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)) @@ -100,7 +115,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 3d6670339d8adf5dd433f3abae792c50a82a154c 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 0e5c4e7..77ae260 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -162,8 +162,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 1557108c9c3aa743ec95a92fe9d0b7ecb9c03844 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 e912be417aba2a77ac70cdc88dce914a790f774d Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 15 May 2014 22:08:14 +0200 Subject: [PATCH 5/6] File list filtering: If no files, return a newline. --- nintendo_nas_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index 304485d..f4f27b8 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -231,6 +231,9 @@ class NintendoNasHTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler): if matched == True: output += line + '\r\n' + if output == '': + # if nothing matches, at least return a newline; Pokemon BW at least expects this and will error without it + output = '\r\n' return output From 77470afeb5beb240634e9f6c5a75269ed4058b70 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Fri, 16 May 2014 03:27:19 +0200 Subject: [PATCH 6/6] SVCLOC action: Reply to server 0000. --- nintendo_nas_server.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/nintendo_nas_server.py b/nintendo_nas_server.py index f4f27b8..276b54c 100644 --- a/nintendo_nas_server.py +++ b/nintendo_nas_server.py @@ -72,15 +72,19 @@ 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 + ret["returncd"] = "007" + ret["statusdata"] = "Y" + authtoken = self.server.db.generate_authtoken(post["userid"], post) + if post["svc"] == "9000" or post["svc"] == "9001": # DLC host = 9000 - ret["returncd"] = "007" - ret["statusdata"] = "Y" ret["svchost"] = self.headers['host'] # in case the client's DNS isn't redirecting dls1.nintendowifi.net - authtoken = self.server.db.generate_authtoken(post["userid"], post) if post["svc"] == 9000: ret["token"] = authtoken else: ret["servicetoken"] = authtoken + elif post["svc"] == "0000": # Pokemon requests this for some things + ret["servicetoken"] = authtoken + ret["svchost"] = "n/a" logger.log(logging.DEBUG, "svcloc response to %s", self.client_address) logger.log(logging.DEBUG, ret)