diff --git a/gamespy_natneg_server.py b/gamespy_natneg_server.py index 87bab4f..cda1716 100644 --- a/gamespy_natneg_server.py +++ b/gamespy_natneg_server.py @@ -5,6 +5,7 @@ import logging import socket import ctypes import struct +import threading import gamespy.gs_utility as gs_utils import other.utils as utils @@ -52,160 +53,160 @@ class GameSpyNatNegServer(object): recv_data, addr = s.recvfrom(2048) time.sleep(0.05) - logger.log(logging.DEBUG, "Connection from %s:%d..." % (addr[0], addr[1])) + packet_thread = threading.Thread(target=self.handle_packet, args=(s, recv_data, addr)) + packet_thread.start() + + def handle_packet(self, s, recv_data, addr): + logger.log(logging.DEBUG, "Connection from %s:%d..." % (addr[0], addr[1])) + logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) + + # Make sure it's a legal packet + if recv_data[0:6] != bytearray([0xfd, 0xfc, 0x1e, 0x66, 0x6a, 0xb2]): + return + + session_id = struct.unpack(" client 0x01 -> server 0x0a. - # Has no other data besides the client ID. - # - # - \xfd\xfc commands get passed directly between the other player(s)? - # - # - # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr - # Use as reference. + packet_thread = threading.Thread(target=self.handle_packet, args=(self.socket, recv_data, address)) + packet_thread.start() - if recv_data[0] != '\x09': - # Don't add a session if the client is trying to check if the game is available or not - session_id = struct.unpack(" client 0x01 -> server 0x0a. + # Has no other data besides the client ID. + # + # - \xfd\xfc commands get passed directly between the other player(s)? + # + # + # Open source version of GameSpy found here: https://github.com/sfcspanky/Openspy-Core/tree/master/qr + # Use as reference. - # Handle commands - if recv_data[0] == '\x00': # Query - self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - elif recv_data[0] == '\x01': # Challenge - self.log(logging.DEBUG, address, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - # Prepare the challenge sent from the server to be compared - challenge = gs_utils.prepare_rc4_base64(self.sessions[session_id].secretkey, self.sessions[session_id].challenge) - - # Compare challenge - client_challenge = recv_data[5:-1] - if client_challenge == challenge: - # Challenge succeeded - - # Send message back to client saying it was accepted - packet = bytearray([0xfe, 0xfd, 0x0a]) # Send client registered command - packet.extend(session_id_raw) # Get the session ID - self.socket.sendto(packet, address) - self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1])) - else: - # Failed the challenge, request another during the next heartbeat - self.sessions[session_id].sent_challenge = False - - elif recv_data[0] == '\x02': # Echo - self.log(logging.DEBUG, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - elif recv_data[0] == '\x03': # Heartbeat - data = recv_data[5:] - self.log(logging.DEBUG, address, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data)) - - # Parse information from heartbeat here - d = data.rstrip('\0').split('\0') - - # It may be safe to ignore "unknown" keys because the proper key names get filled in later... - k = {} - for i in range(0, len(d), 2): - #self.log(logging.DEBUG, address, "%s = %s" % (d[i], d[i+1])) - k[d[i]] = d[i+1] - - if "gamename" in k: - self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']] - #print "Got secret key %s for %s" % (self.sessions[session_id].secretkey, k['gamename']) - - if self.sessions[session_id].playerid == 0 and "dwc_pid" in k: - # Get the player's id and then query the profile to figure out what console they are on. - # The endianness of some server data depends on the endianness of the console, so we must be able - # to account for that. - self.sessions[session_id].playerid = int(k['dwc_pid']) - profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid) - - if "console" in profile: - self.sessions[session_id].console = profile['console'] - - - if self.sessions[session_id].sent_challenge == False: - addr_hex = ''.join(["%02X" % int(x) for x in address[0].split('.')]) - port_hex = "%04X" % int(address[1]) - server_challenge = utils.generate_random_str(8) + addr_hex + port_hex - - self.sessions[session_id].challenge = server_challenge - - packet = bytearray([0xfe, 0xfd, 0x01]) # Send challenge command - packet.extend(session_id_raw) # Get the session ID - packet.extend(server_challenge) - packet.extend('\x00') - - self.socket.sendto(packet, address) - self.log(logging.DEBUG, address, "Sent challenge to %s:%s..." % (address[0], address[1])) - - self.sessions[session_id].sent_challenge = True - - if 'publicip' in k and k['publicip'] == "0": #and k['dwc_hoststate'] == "2": # When dwc_hoststate == 2 then it doesn't send an IP, so calculate it ourselves - if self.sessions[session_id].console != 0: - k['publicip'] = str(ctypes.c_int32(utils.get_int_be(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # Wii - else: - k['publicip'] = str(ctypes.c_int32(utils.get_int(bytearray([int(x) for x in address[0].split('.')]), 0)).value) # DS - - if "statechanged" in k: - if k['statechanged'] == "1": # Create server - #if k['publicport'] != "0" and k['publicip'] != "0": - # dwc_mtype controls what kind of server query we're looking for. - # dwc_mtype = 0 is used when looking for a matchmaking game. - # dwc_mtype = 1 is unknown. - # dwc_mtype = 2 is used when hosting a friends only game (possibly other uses too). - # dwc_mtype = 3 is used when looking for a friends only game (possibly other uses too). - - # Some memory could be saved by clearing out any unwanted fields from k before sending. - self.server_manager.update_server_list(k['gamename'], session_id, k, self.sessions[session_id].console)._getvalue() - - if session_id in self.sessions: - self.sessions[session_id].gamename = k['gamename'] - elif k['statechanged'] == "2": # Close server - self.server_manager.delete_server(k['gamename'] , session_id) - - if session_id in self.sessions: - self.sessions.pop(session_id) - - - elif recv_data[0] == '\x04': # Add Error - self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - elif recv_data[0] == '\x05': # Echo Response - self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - elif recv_data[0] == '\x06': # Client Message - self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - - elif recv_data[0] == '\x07': # Client Message Ack - #self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:])) - self.log(logging.DEBUG, address, "Received client message ack from %s:%s..." % (address[0], address[1])) - - elif recv_data[0] == '\x08': # Keep Alive - self.log(logging.DEBUG, address, "Received keep alive from %s:%s..." % (address[0], address[1])) + if recv_data[0] != '\x09': + # Don't add a session if the client is trying to check if the game is available or not + session_id = struct.unpack("