diff --git a/gamespy_gamestats_server.py b/gamespy_gamestats_server.py index 0b548b7..3fa6c5b 100644 --- a/gamespy_gamestats_server.py +++ b/gamespy_gamestats_server.py @@ -41,20 +41,25 @@ logger_output_to_console = True logger_output_to_file = True logger_name = "GameSpyGamestatsServer" logger_filename = "gamespy_gamestats_server.log" -logger = utils.create_logger(logger_name, logger_filename, -1, logger_output_to_console, logger_output_to_file) +logger = utils.create_logger(logger_name, logger_filename, -1, + logger_output_to_console, logger_output_to_file) address = ("0.0.0.0", 29920) + class GameSpyGamestatsServer(object): def __init__(self): pass def start(self): - endpoint_search = serverFromString(reactor, "tcp:%d:interface=%s" % (address[1], address[0])) + endpoint_search = serverFromString( + reactor, + "tcp:%d:interface=%s" % (address[1], address[0]) + ) conn_search = endpoint_search.listen(GamestatsFactory()) try: - if reactor.running is False: + if not reactor.running: reactor.run(installSignalHandlers=0) except ReactorAlreadyRunning: pass @@ -62,7 +67,9 @@ class GameSpyGamestatsServer(object): class GamestatsFactory(Factory): def __init__(self): - logger.log(logging.INFO, "Now listening for connections on %s:%d...", address[0], address[1]) + logger.log(logging.INFO, + "Now listening for connections on %s:%d...", + address[0], address[1]) self.sessions = {} def buildProtocol(self, address): @@ -71,13 +78,15 @@ class GamestatsFactory(Factory): class Gamestats(LineReceiver): def __init__(self, sessions, address): - self.setRawMode() # We're dealing with binary data so set to raw mode + self.setRawMode() # We're dealing with binary data so set to raw mode self.db = gs_database.GamespyDatabase() self.sessions = sessions self.address = address - self.remaining_message = "" # Stores any unparsable/incomplete commands until the next rawDataReceived + # Stores any unparsable/incomplete commands until the next + # rawDataReceived + self.remaining_message = "" self.session = "" self.gameid = "" @@ -86,26 +95,39 @@ class Gamestats(LineReceiver): self.data = "" - def log(self, level, message): - if self.session == "": - if self.gameid == "": - logger.log(level, "[%s:%d] %s", self.address.host, self.address.port,message) + def log(self, level, msg, *args, **kwargs): + if not self.session: + if not self.gameid: + logger.log(level, "[%s:%d] " + msg, + self.address.host, self.address.port, + *args, **kwargs) else: - logger.log(level, "[%s:%d | %s] %s", self.address.host, self.address.port, self.gameid, message) + logger.log(level, "[%s:%d | %s] " + msg, + self.address.host, self.address.port, self.gameid, + *args, **kwargs) else: - if self.gameid == "": - logger.log(level, "[%s:%d | %s] %s", self.address.host, self.address.port, self.session, message) + if not self.gameid: + logger.log(level, "[%s:%d | %s] " + msg, + self.address.host, self.address.port, self.session, + *args, **kwargs) else: - logger.log(level, "[%s:%d | %s | %s] %s", self.address.host, self.address.port, self.session, self.gameid, message) + logger.log(level, "[%s:%d | %s | %s] " + msg, + self.address.host, self.address.port, self.session, + self.gameid, *args, **kwargs) def connectionMade(self): try: - self.log(logging.INFO, "Received connection from %s:%d" % (self.address.host, self.address.port)) + self.log(logging.INFO, + "Received connection from %s:%d", + self.address.host, self.address.port) # Generate a random challenge string - self.challenge = utils.generate_random_str(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") + self.challenge = utils.generate_random_str( + 10, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) - # The first command sent to the client is always a login challenge containing the server challenge key. + # The first command sent to the client is always a login challenge + # containing the server challenge key. msg = gs_query.create_gamespy_message([ ('__cmd__', "lc"), ('__cmd_val__', "1"), @@ -113,12 +135,14 @@ class Gamestats(LineReceiver): ('id', "1"), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def connectionLost(self, reason): return @@ -135,31 +159,36 @@ class Gamestats(LineReceiver): self.data = msg self.remaining_message = "" - commands, self.remaining_message = gs_query.parse_gamespy_message(msg) - logger.log(logging.DEBUG, "STATS RESPONSE: %s" % msg) + commands, self.remaining_message = \ + gs_query.parse_gamespy_message(msg) + logger.log(logging.DEBUG, "STATS RESPONSE: %s", msg) cmds = { - "auth": self.perform_auth, - "authp": self.perform_authp, - "ka": self.perform_ka, - "setpd": self.perform_setpd, - "getpd": self.perform_getpd, - "newgame": self.perform_newgame, - "updgame": self.perform_updgame, + "auth": self.perform_auth, + "authp": self.perform_authp, + "ka": self.perform_ka, + "setpd": self.perform_setpd, + "getpd": self.perform_getpd, + "newgame": self.perform_newgame, + "updgame": self.perform_updgame, } def cmd_err(data_parsed): - logger.log(logging.DEBUG, "Found unknown command, don't know how to handle '%s'.", data_parsed['__cmd__']) + logger.log(logging.DEBUG, + "Found unknown command, don't know how to" + " handle '%s'.", data_parsed['__cmd__']) for data_parsed in commands: print(data_parsed) cmds.get(data_parsed['__cmd__'], cmd_err)(data_parsed) except: - self.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc()) + self.log(logging.ERROR, + "Unknown exception: %s", + traceback.format_exc()) def perform_auth(self, data_parsed): - self.log(logging.DEBUG, "Parsing 'auth'...") + self.log(logging.DEBUG, "%s", "Parsing 'auth'...") if "gamename" in data_parsed: self.gameid = data_parsed['gamename'] @@ -174,22 +203,26 @@ class Gamestats(LineReceiver): ('id', "1"), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) def perform_authp(self, data_parsed): - authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], self.db) - #print authtoken_parsed + authtoken_parsed = gs_utils.parse_authtoken(data_parsed['authtoken'], + self.db) + # print authtoken_parsed if "lid" in data_parsed: self.lid = data_parsed['lid'] - userid, profileid, gsbrcd, uniquenick = gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, self.db) + userid, profileid, gsbrcd, uniquenick = \ + gs_utils.login_profile_via_parsed_authtoken(authtoken_parsed, + self.db) if profileid is not None: - # Successfully logged in or created account, continue creating session. + # Successfully logged in or created account, continue + # creating session. sesskey = self.db.create_session(profileid, '') self.sessions[profileid] = self self.profileid = int(profileid) @@ -209,7 +242,7 @@ class Gamestats(LineReceiver): ('errmsg', 'Invalid Validation'), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) @@ -220,7 +253,7 @@ class Gamestats(LineReceiver): ('__cmd_val__', ""), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) @@ -237,80 +270,106 @@ class Gamestats(LineReceiver): ('mod', int(time.time())), ]) - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) # TODO: Return error message. if int(data_parsed['pid']) != self.profileid: - logger.log(logging.WARNING, "ERROR: %d tried to update %d's profile" % (int(data_parsed['pid']), self.profileid)) + logger.log(logging.WARNING, + "ERROR: %d tried to update %d's profile", + int(data_parsed['pid']), self.profileid) return data_str = "\\data\\" length = int(data_parsed['length']) if len(data) < length: - # The packet isn't complete yet, keep loop until we get the entire packet. - # The length entire packet SHOULD always be greater than the data field, so this check should be fine. + # The packet isn't complete yet, keep loop until we get the + # entire packet. The length entire packet SHOULD always be + # greater than the data field, so this check should be fine. return if data_str in data: idx = data.index(data_str) + len(data_str) - data = data[idx:idx+length].rstrip("\\") + data = data[idx:idx + length].rstrip("\\") else: - logger.log(logging.ERROR, "ERROR: Could not find \data\ in setpd command: %s", data) + logger.log(logging.ERROR, + "ERROR: Could not find \data\ in setpd command: %s", + data) data = "" - current_data = self.db.pd_get(self.profileid, data_parsed['dindex'], data_parsed['ptype']) + current_data = self.db.pd_get(self.profileid, + data_parsed['dindex'], + data_parsed['ptype']) if current_data and data and 'data' in current_data: current_data = current_data['data'].lstrip('\\').split('\\') new_data = data.lstrip('\\').split('\\') - current_data = dict(zip(current_data[0::2],current_data[1::2])) - new_data = dict(zip(new_data[0::2],new_data[1::2])) + current_data = dict(zip(current_data[0::2], + current_data[1::2])) + new_data = dict(zip(new_data[0::2], new_data[1::2])) for k in new_data.keys(): current_data[k] = new_data[k] + # TODO: use str.join() data = "\\" for k in current_data.keys(): - data += k+"\\"+current_data[k]+"\\" - data = data.rstrip("\\") # Don't put trailing \ into db + data += k + "\\" + current_data[k] + "\\" + data = data.rstrip("\\") # Don't put trailing \ into db - self.db.pd_insert(self.profileid, data_parsed['dindex'], data_parsed['ptype'], data) + self.db.pd_insert(self.profileid, + data_parsed['dindex'], + data_parsed['ptype'], + data) def perform_getpd(self, data_parsed): pid = int(data_parsed['pid']) - profile = self.db.pd_get(pid, data_parsed['dindex'], data_parsed['ptype']) + profile = self.db.pd_get(pid, + data_parsed['dindex'], + data_parsed['ptype']) if profile is None: - self.log(logging.WARNING, "Could not find profile for %d %s %s" % (pid, data_parsed['dindex'], data_parsed['ptype'])) + self.log(logging.WARNING, + "Could not find profile for %d %s %s", + pid, data_parsed['dindex'], data_parsed['ptype']) keys = data_parsed['keys'].split('\x01') profile_data = None data = "" - # Someone figure out if this is actually a good way to handle this when no profile is found + # Someone figure out if this is actually a good way to handle this + # when no profile is found if profile is not None and 'data' in profile: profile_data = profile['data'] if profile_data.endswith("\\"): profile_data = profile_data[:-1] - profile_data = gs_query.parse_gamespy_message("\\prof\\" + profile_data + "\\final\\") + profile_data = \ + gs_query.parse_gamespy_message("\\prof\\" + profile_data + + "\\final\\") if profile_data is not None: profile_data = profile_data[0][0] else: - self.log(logging.WARNING, "Could not get data section from profile for %d" % pid) + self.log(logging.WARNING, + "Could not get data section from profile for %d", + pid) if len(keys): - for key in (key for key in keys if key not in ("__cmd__", "__cmd_val__", "")): + # TODO: more clean/pythonic way to do (join?) + for key in keys: + if key in ("__cmd__", "__cmd_val__", ""): + continue data += "\\" + key + "\\" if profile_data is not None and key in profile_data: data += profile_data[key] else: - self.log(logging.WARNING, "No keys requested, defaulting to all keys: %s" % (profile['data'])) + self.log(logging.WARNING, + "No keys requested, defaulting to all keys: %s", + profile['data']) data = profile['data'] modified = int(time.time()) @@ -325,30 +384,31 @@ class Gamestats(LineReceiver): ('data', data), ]) - msg = msg.replace("\\data\\","\\data\\\\") # data needs to be preceded by an extra slash + # data needs to be preceded by an extra slash + msg = msg.replace("\\data\\", "\\data\\\\") datastring = "" try: - datastring = re.findall('.*data\\\\(.*)',msg)[0].replace("\\final\\","") + datastring = re.findall('.*data\\\\(.*)', msg)[0] \ + .replace("\\final\\", "") except: pass - # This works because the data string is a key-value pair, splitting the - # string by \ should yield a list with an even number of elements. But, - # because of the extra \ prepended to the datastring, it'll be odd. - # So ultimately I expect the list to have an odd number of elements. - # If it's even, len(list)%2 will be zero... and that means the last - # field in the datastring is empty and doesn't have a closing \. + # This works because the data string is a key-value pair, splitting + # the string by \ should yield a list with an even number of elements. + # But, because of the extra \ prepended to the datastring, it'll be + # odd. So ultimately I expect the list to have an odd number of + # elements. If it's even, len(list)%2 will be zero... and that means + # the last field in the datastring is empty and doesn't have a + # closing \. if datastring and not len(datastring.split('\\')) % 2: - msg = msg.replace("\\final\\","\\\\final\\") # An empty field must be terminated by \ before \final\ + # An empty field must be terminated by \ before \final\ + msg = msg.replace("\\final\\", "\\\\final\\") - self.log(logging.DEBUG, "SENDING: '%s'..." % msg) + self.log(logging.DEBUG, "SENDING: '%s'...", msg) msg = self.crypt(msg) self.transport.write(bytes(msg)) - - - def perform_newgame(self, data_parsed): # No op return @@ -358,7 +418,8 @@ class Gamestats(LineReceiver): return def crypt(self, data): - key = "GameSpy3D" + key = bytearray(b"GameSpy3D") + key_len = len(key) output = bytearray(data.encode("ascii")) if "\\final\\" in output: @@ -367,11 +428,11 @@ class Gamestats(LineReceiver): end = len(output) for i in range(end): - output[i] ^= ord(key[i % len(key)]) + output[i] ^= key[i % key_len] return output + if __name__ == "__main__": gsss = GameSpyGamestatsServer() gsss.start() -