def generate_server_list_header_data(self, address, fields): output = bytearray() # Write the address output += bytearray([int(x) for x in address.host.split('.')]) # Write the port output += utils.get_bytes_from_short(address.port, True) # Write number of fields that will be returned. key_count = len(fields) output += utils.get_bytes_from_short(key_count) if key_count != len(fields): # For some reason we didn't get all of the expected data. self.log(logging.WARNING, "key_count[%d] != len(fields)[%d]", key_count, len(fields)) self.log(logging.WARNING, "%s", fields) # Write the fields for field in fields: output += bytearray(field) + '\0\0' return output
def generate_server_list_data(self, address, fields, server_info, finalize=False): output = bytearray() flags_buffer = bytearray() if len(server_info) > 0: # Start server loop here instead of including all of the fields and stuff again flags = 0 if len(server_info) != 0: flags |= ServerListFlags.HAS_KEYS_FLAG if "natneg" in server_info: flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG ip = utils.get_bytes_from_int_signed( int(server_info['publicip']), self.console) flags_buffer += ip flags |= ServerListFlags.NONSTANDARD_PORT_FLAG if server_info['publicport'] != "0": flags_buffer += utils.get_bytes_from_short( int(server_info['publicport']), True) else: flags_buffer += utils.get_bytes_from_short( int(server_info['localport']), True) if "localip0" in server_info: # How to handle multiple localips? flags |= ServerListFlags.PRIVATE_IP_FLAG flags_buffer += bytearray([ int(x) for x in server_info['localip0'].split('.') ]) #ip if "localport" in server_info: flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG flags_buffer += utils.get_bytes_from_short( int(server_info['localport']), True) flags |= ServerListFlags.ICMP_IP_FLAG flags_buffer += bytearray( [int(x) for x in "0.0.0.0".split('.')]) output += bytearray([flags & 0xff]) output += flags_buffer if (flags & ServerListFlags.HAS_KEYS_FLAG): # Write data for associated fields if 'requested' in server_info: for field in fields: output += '\xff' + bytearray( server_info['requested'][field]) + '\0' return output
def generate_server_list_data(self, address, fields, server_info, finalize = False): output = bytearray() flags_buffer = bytearray() if len(server_info) > 0: # Start server loop here instead of including all of the fields and stuff again flags = 0 if len(server_info) != 0: flags |= ServerListFlags.HAS_KEYS_FLAG if "natneg" in server_info: flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG ip = utils.get_bytes_from_int_signed(int(server_info['publicip']), self.console) flags_buffer += ip flags |= ServerListFlags.NONSTANDARD_PORT_FLAG if server_info['publicport'] != "0": flags_buffer += utils.get_bytes_from_short(int(server_info['publicport']), True) else: flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True) if "localip0" in server_info: # How to handle multiple localips? flags |= ServerListFlags.PRIVATE_IP_FLAG flags_buffer += bytearray([int(x) for x in server_info['localip0'].split('.')]) #ip if "localport" in server_info: flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG flags_buffer += utils.get_bytes_from_short(int(server_info['localport']), True) flags |= ServerListFlags.ICMP_IP_FLAG flags_buffer += bytearray([int(x) for x in "0.0.0.0".split('.')]) output += bytearray([flags & 0xff]) output += flags_buffer if (flags & ServerListFlags.HAS_KEYS_FLAG): # Write data for associated fields if 'requested' in server_info: for field in fields: output += '\xff' + bytearray(server_info['requested'][field]) + '\0' return output
def handle_natneg_address_check(nn, recv_data, addr, socket): """Command: 0x0A - NN_ADDRESS_CHECK. Send by the client during connection test. Example: fd fc 1e 66 6a b2 03 0a 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Description: fd fc 1e 66 6a b2 - NATNEG magic 03 - NATNEG version 0a - NATNEG record type 00 00 00 00 - Session id 01 - Port type (between 0x00 and 0x03) - 60 bytes padding? 00 - Client index (0x00 - Client, 0x01 - Host) 00 - NATNEG result? 00 00 00 00 - NAT type? 00 00 00 00 - NAT mapping scheme? 00 (x50) - Game name? """ client_id = "%02x" % ord(recv_data[13]) logger.log(logging.DEBUG, "Received address check command from %s:%d...", *addr) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data)) output = bytearray(recv_data[0:15]) output += utils.get_bytes_from_ip_str(addr[0]) output += utils.get_bytes_from_short(addr[1], True) output += bytearray(recv_data[len(output):]) output[7] = 0x0b # NN_ADDRESS_REPLY nn.write_queue.put((output, addr, socket)) logger.log(logging.DEBUG, "Sent address check response to %s:%d...", *addr) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def rawDataReceived(self, data): try: # First 2 bytes are the packet size. # # Third byte is the command byte. # According to Openspy-Core: # 0x00 - Server list request # 0x01 - Server info request # 0x02 - Send message request # 0x03 - Keep alive reply # 0x04 - Map loop request (?) # 0x05 - Player search request # # For Tetris DS, at the very least 0x00 and 0x02 need to be # implemented. self.buffer += data while len(self.buffer) > 0: packet_len = utils.get_short(self.buffer, 0, True) packet = None if len(self.buffer) >= packet_len: packet = self.buffer[:packet_len] self.buffer = self.buffer[packet_len:] if packet is None: # Don't have enough for the entire packet, break. break if packet[2] == '\x00': # Server list request self.log(logging.DEBUG, "Received server list request from %s:%s...", self.address.host, self.address.port) # This code is so... not python. The C programmer in me is # coming out strong. # TODO: Rewrite this section later? idx = 3 list_version = ord(packet[idx]) idx += 1 encoding_version = ord(packet[idx]) idx += 1 game_version = utils.get_int(packet, idx) idx += 4 query_game = utils.get_string(packet, idx) idx += len(query_game) + 1 game_name = utils.get_string(packet, idx) idx += len(game_name) + 1 challenge = ''.join(packet[idx:idx + 8]) idx += 8 filter = utils.get_string(packet, idx) idx += len(filter) + 1 fields = utils.get_string(packet, idx) idx += len(fields) + 1 options = utils.get_int(packet, idx, True) idx += 4 source_ip = 0 max_servers = 0 NO_SERVER_LIST = 0x02 ALTERNATE_SOURCE_IP = 0x08 LIMIT_RESULT_COUNT = 0x80 send_ip = False if (options & LIMIT_RESULT_COUNT): max_servers = utils.get_int(packet, idx) elif (options & ALTERNATE_SOURCE_IP): source_ip = utils.get_ip(packet, idx) elif (options & NO_SERVER_LIST): send_ip = True if '\\' in fields: fields = [ x for x in fields.split('\\') if x and not x.isspace() ] # print "%02x %02x %08x" % \ # (list_version, encoding_version, game_version) # print "%s" % query_game # print "%s" % game_name # print "%s" % challenge # print "%s" % filter # print "%s" % fields # print "%08x" % options # print "%d %08x" % (max_servers, source_ip) self.log( logging.DEBUG, "list version: %02x / encoding version: %02x /" " game version: %08x / query game: %s /" " game name: %s / challenge: %s / filter: %s /" " fields: %s / options: %08x / max servers: %d /" " source ip: %08x" % (list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip)) # Requesting ip and port of client, not server if not filter and not fields or send_ip: output = bytearray( [int(x) for x in self.address.host.split('.')]) # Does this ever change? output += utils.get_bytes_from_short(6500, True) enc = gs_utils.EncTypeX() output_enc = enc.encrypt( self.secret_key_list[game_name], challenge, output) self.transport.write(bytes(output_enc)) self.log(logging.DEBUG, "%s", "Responding with own IP and game port...") self.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) else: self.find_server(query_game, filter, fields, max_servers, game_name, challenge) elif packet[2] == '\x02': # Send message request packet_len = utils.get_short(packet, 0, True) dest_addr = '.'.join(["%d" % ord(x) for x in packet[3:7]]) # What's the pythonic way to do this? unpack? dest_port = utils.get_short(packet, 7, True) dest = (dest_addr, dest_port) self.log( logging.DEBUG, "Received send message request from %s:%s to" " %s:%d... expecting %d byte packet.", self.address.host, self.address.port, dest_addr, dest_port, packet_len) self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(packet))) if packet_len == len(packet): # Contains entire packet, send immediately. self.forward_data_to_client(packet[9:], dest) else: self.log(logging.ERROR, "%s", "ERROR: Could not find entire packet.") elif packet[2] == '\x03': # Keep alive reply self.log(logging.DEBUG, "Received keep alive from %s:%s...", (self.address.host, self.address.port)) else: self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s...", ord(packet[2]), self.address.host, self.address.port) self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(packet))) except: self.log(logging.ERROR, "Unknown exception: %s", traceback.format_exc())
def handle_packet(self, recv_data, addr): """Handle NATNEG. TODO: Pointer to methods for recv_data[7].""" 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("<I", recv_data[8:12])[0] session_id_raw = recv_data[8:12] # Handle commands if recv_data[7] == '\x00': logger.log(logging.DEBUG, "Received initialization from %s:%s...", addr[0], addr[1]) output = bytearray(recv_data[0:14]) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime # Hunters, and this seems to be the standard response to 0x00 output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]) output[7] = 0x01 # Initialization response self.write_queue.put((output, addr)) # Try to connect to the server gameid = utils.get_string(recv_data, 0x15) client_id = "%02x" % ord(recv_data[13]) localip_raw = recv_data[15:19] localip_int_le = utils.get_ip(recv_data, 15) localip_int_be = utils.get_ip(recv_data, 15, True) localip = '.'.join(["%d" % ord(x) for x in localip_raw]) localport_raw = recv_data[19:21] localport = utils.get_short(localport_raw, 0, True) localaddr = (localip, localport, localip_int_le, localip_int_be) self.session_list \ .setdefault(session_id, {}) \ .setdefault(client_id, { 'connected': False, 'addr': '', 'localaddr': None, 'serveraddr': None, 'gameid': None }) # In fact, it's a pointer (cf. shallow copy) client_id_session = self.session_list[session_id][client_id] client_id_session['gameid'] = gameid client_id_session['addr'] = addr client_id_session['localaddr'] = localaddr clients = len(self.session_list[session_id]) # Unused? for client in self.session_list[session_id]: # Another shallow copy client_session = self.session_list[session_id][client] if client_session['connected'] or client == client_id: continue # if client_session['serveraddr'] \ # is None: serveraddr = self.get_server_info(gameid, session_id, client) if serveraddr is None: serveraddr = self.get_server_info_alt( gameid, session_id, client ) client_session['serveraddr'] = serveraddr logger.log(logging.DEBUG, "Found server from local ip/port: %s from %d", serveraddr, session_id) publicport = client_session['addr'][1] if client_session['localaddr'][1]: publicport = client_session['localaddr'][1] if client_session['serveraddr'] is not None: publicport = int( client_session['serveraddr']['publicport'] ) # Send to requesting client output = bytearray(recv_data[0:12]) output += bytearray([ int(x) for x in client_session['addr'][0].split('.') ]) output += utils.get_bytes_from_short(publicport, True) # Unknown, always seems to be \x42\x00 output += bytearray([0x42, 0x00]) output[7] = 0x05 # self.write_queue.put(( # output, # (client_id_session['addr']) # )) self.write_queue.put(( output, (client_id_session['addr'][0], client_id_session['addr'][1]) )) logger.log(logging.DEBUG, "Sent connection request to %s:%d...", client_id_session['addr'][0], client_id_session['addr'][1]) logger.log(logging.DEBUG, '%s', utils.pretty_print_hex(output)) # Send to other client # if client_id_session['serveraddr'] is None: serveraddr = self.get_server_info( gameid, session_id, client_id ) if serveraddr is None: serveraddr = self.get_server_info_alt( gameid, session_id, client_id ) client_id_session['serveraddr'] = serveraddr logger.log(logging.DEBUG, "Found server 2 from local ip/port: %s from %d", serveraddr, session_id) publicport = client_id_session['addr'][1] if client_id_session['localaddr'][1]: publicport = client_id_session['localaddr'][1] if client_id_session['serveraddr'] is not None: publicport = int( client_id_session['serveraddr']['publicport'] ) output = bytearray(recv_data[0:12]) output += bytearray( [int(x) for x in client_id_session['addr'][0].split('.')] ) output += utils.get_bytes_from_short(publicport, True) # Unknown, always seems to be \x42\x00 output += bytearray([0x42, 0x00]) output[7] = 0x05 # self.write_queue.put((output, (client_session['addr']))) self.write_queue.put((output, (client_session['addr'][0], client_session['addr'][1]))) logger.log(logging.DEBUG, "Sent connection request to %s:%d...", client_session['addr'][0], client_session['addr'][1]) logger.log(logging.DEBUG, '%s', utils.pretty_print_hex(output)) elif recv_data[7] == '\x06': # Was able to connect client_id = "%02x" % ord(recv_data[13]) logger.log(logging.DEBUG, "Received connected command from %s:%s...", addr[0], addr[1]) if session_id in self.session_list and \ client_id in self.session_list[session_id]: self.session_list[session_id][client_id]['connected'] = True elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED! client_id = "%02x" % ord(recv_data[13]) logger.log(logging.DEBUG, "Received address check command from %s:%s...", addr[0], addr[1]) output = bytearray(recv_data[0:15]) output += bytearray([int(x) for x in addr[0].split('.')]) output += utils.get_bytes_from_short(addr[1], True) output += bytearray(recv_data[len(output):]) output[7] = 0x0b self.write_queue.put((output, addr)) logger.log(logging.DEBUG, "Sent address check response to %s:%d...", addr[0], addr[1]) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) elif recv_data[7] == '\x0c': # Natify port_type = "%02x" % ord(recv_data[12]) logger.log(logging.DEBUG, "Received natify command from %s:%s...", addr[0], addr[1]) output = bytearray(recv_data) output[7] = 0x02 # ERT Test self.write_queue.put((output, addr)) logger.log(logging.DEBUG, "Sent natify response to %s:%d...", addr[0], addr[1]) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) elif recv_data[7] == '\x0d': # Report logger.log(logging.DEBUG, "Received report command from %s:%s...", addr[0], addr[1]) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data)) # Report response output = bytearray(recv_data[:21]) output[7] = 0x0e # Report response output[14] = 0 # Clear byte to match real server's response self.write_queue.put((output, addr)) elif recv_data[7] == '\x0f': # Natneg v4 command thanks to Pipian. # Only seems to be used in very few DS games (namely, # Pokemon Black/White/Black 2/White 2). logger.log(logging.DEBUG, "Received pre-init command from %s:%s...", addr[0], addr[1]) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(recv_data)) session = utils.get_int(recv_data[-4:], 0) # Report response output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0]) output[7] = 0x10 # Pre-init response if not session: # What's the correct behavior when session == 0? output[13] = 2 elif session in self.natneg_preinit_session: # Should this be sent to both clients or just the one that # connected most recently? # I can't tell from a one sided packet capture of Pokemon. # For the time being, send to both clients just in case. output[13] = 2 self.write_queue.put((output, self.natneg_preinit_session[session])) output[12] = (1, 0)[output[12]] # Swap the index del self.natneg_preinit_session[session] else: output[13] = 0 self.natneg_preinit_session[session] = addr self.write_queue.put((output, addr)) 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 handle_natneg_init(nn, recv_data, addr, socket): """Command: 0x00 - NN_INIT. Send by the client to initialize the connection. Example: fd fc 1e 66 6a b2 03 00 3d f1 00 71 00 00 01 0a 00 01 e2 00 00 6d 61 72 69 6f 6b 61 72 74 77 69 69 00 Description: fd fc 1e 66 6a b2 - NATNEG magic 03 - NATNEG version 00 - NATNEG record type 3d f1 00 71 - Session id 00 - Port type (between 0x00 and 0x03) 00 - Client index (0x00 - Client, 0x01 - Host) 01 - Use game port 0a 00 01 e2 - Local IP 00 00 - Local port GAME_NAME 00 - Game name """ logger.log(logging.DEBUG, "Received initialization from %s:%d...", *addr) session_id = utils.get_int(recv_data, 8) output = bytearray(recv_data[0:14]) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime # Hunters, and this seems to be the standard response to 0x00 output += bytearray([0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea]) output[7] = 0x01 # Initialization response nn.write_queue.put((output, addr, socket)) # Try to connect to the server gameid = utils.get_string(recv_data, 0x15) client_id = "%02x" % ord(recv_data[13]) localaddr = utils.get_local_addr(recv_data, 15) nn.session_list \ .setdefault(session_id, {}) \ .setdefault(client_id, { 'connected': False, 'addr': '', 'localaddr': None, 'serveraddr': None, 'gameid': None }) # In fact, it's a pointer client_id_session = nn.session_list[session_id][client_id] client_id_session['gameid'] = gameid client_id_session['addr'] = addr client_id_session['localaddr'] = localaddr for client in nn.session_list[session_id]: # Another pointer client_session = nn.session_list[session_id][client] if client_session['connected'] or client == client_id: continue # --- Send to requesting client # Get server info serveraddr = nn.get_server_addr(gameid, session_id, client) client_session['serveraddr'] = serveraddr logger.log(logging.DEBUG, "Found server from local ip/port: %s from %d", serveraddr, session_id) # Get public port if client_session['serveraddr'] is not None: publicport = int(client_session['serveraddr']['publicport']) else: publicport = \ client_session['localaddr'][1] or \ client_session['addr'][1] output = bytearray(recv_data[0:12]) output += utils.get_bytes_from_ip_str(client_session['addr'][0]) output += utils.get_bytes_from_short(publicport, True) # Unknown, always seems to be \x42\x00 output += bytearray([0x42, 0x00]) output[7] = 0x05 # NN_CONNECT nn.write_queue.put((output, client_id_session['addr'], socket)) logger.log(logging.DEBUG, "Sent connection request to %s:%d...", *client_id_session['addr']) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) # --- Send to other client # Get server info serveraddr = nn.get_server_addr(gameid, session_id, client_id) client_id_session['serveraddr'] = serveraddr logger.log(logging.DEBUG, "Found server 2 from local ip/port: %s from %d", serveraddr, session_id) # Get public port if client_id_session['serveraddr'] is not None: publicport = int(client_id_session['serveraddr']['publicport']) else: publicport = \ client_id_session['localaddr'][1] or \ client_id_session['addr'][1] output = bytearray(recv_data[0:12]) output += utils.get_bytes_from_ip_str(client_id_session['addr'][0]) output += utils.get_bytes_from_short(publicport, True) # Unknown, always seems to be \x42\x00 output += bytearray([0x42, 0x00]) output[7] = 0x05 # NN_CONNECT nn.write_queue.put((output, client_session['addr'], socket)) logger.log(logging.DEBUG, "Sent connection request to %s:%d...", *client_session['addr']) logger.log(logging.DEBUG, "%s", utils.pretty_print_hex(output))
def generate_server_list_data(self, address, fields, server_info): output = bytearray() # Write the address output += bytearray([int(x) for x in address.host.split('.')]) # Write the port output += utils.get_bytes_from_short_be(address.port) #if len(server_info) > 0: if True: # Write number of fields that will be returned. key_count = len(fields) output += utils.get_bytes_from_short(key_count) if key_count != len(fields): # For some reason we didn't get all of the expected data. self.log(logging.WARNING, "key_count[%d] != len(fields)[%d]" % (key_count, len(fields))) self.log(logging.WARNING, fields) flags_buffer = bytearray() # Write the fields for field in fields: output += bytearray(field) + '\0\0' # Start server loop here instead of including all of the fields and stuff again flags = 0 if len(server_info) != 0: flags |= ServerListFlags.HAS_KEYS_FLAG if "natneg" in server_info: flags |= ServerListFlags.CONNECT_NEGOTIATE_FLAG ip = 0 if self.console != 0: ip = utils.get_bytes_from_int_be(int(server_info['publicip'])) # Wii flags_buffer += ip else: ip = utils.get_bytes_from_int(int(server_info['publicip'])) # DS flags_buffer += ip flags |= ServerListFlags.NONSTANDARD_PORT_FLAG flags_buffer += utils.get_bytes_from_short_be(int(server_info['publicport'])) if "localip0" in server_info: flags |= ServerListFlags.PRIVATE_IP_FLAG flags_buffer += ip #bytearray([int(x) for x in server_info['localip'].split('.')]) if "localport" in server_info: flags |= ServerListFlags.NONSTANDARD_PRIVATE_PORT_FLAG flags_buffer += utils.get_bytes_from_short_be(int(server_info['localport'])) flags |= ServerListFlags.ICMP_IP_FLAG flags_buffer += bytearray([int(x) for x in "0.0.0.0".split('.')]) output += bytearray([flags & 0xff]) output += flags_buffer if (flags & ServerListFlags.HAS_KEYS_FLAG): # Write data for associated fields for field in fields: output += '\xff' + bytearray(server_info['requested'][field]) + '\0' output += '\0' output += utils.get_bytes_from_int(-1) return output
def handle_packet(self, 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("<I", recv_data[8:12])[0] session_id_raw = recv_data[8:12] # Handle commands if recv_data[7] == '\x00': logger.log( logging.DEBUG, "Received initialization from %s:%s..." % (addr[0], addr[1])) output = bytearray(recv_data[0:14]) output += bytearray( [0xff, 0xff, 0x6d, 0x16, 0xb5, 0x7d, 0xea] ) # Checked with Tetris DS, Mario Kart DS, and Metroid Prime Hunters, and this seems to be the standard response to 0x00 output[7] = 0x01 # Initialization response self.write_queue.put((output, addr)) # Try to connect to the server gameid = utils.get_string(recv_data, 0x15) client_id = "%02x" % ord(recv_data[13]) localip_raw = recv_data[15:19] localip_int_le = utils.get_ip(recv_data, 15) localip_int_be = utils.get_ip(recv_data, 15, True) localip = '.'.join(["%d" % ord(x) for x in localip_raw]) localport_raw = recv_data[19:21] localport = utils.get_short(localport_raw, 0, True) localaddr = (localip, localport, localip_int_le, localip_int_be) self.session_list.setdefault(session_id, {}).setdefault( client_id, { 'connected': False, 'addr': '', 'localaddr': None, 'serveraddr': None, 'gameid': None }) self.session_list[session_id][client_id]['gameid'] = gameid self.session_list[session_id][client_id]['addr'] = addr self.session_list[session_id][client_id]['localaddr'] = localaddr clients = len(self.session_list[session_id]) for client in self.session_list[session_id]: if self.session_list[session_id][client][ 'connected'] == False: # and self.session_list[session_id][client]['localaddr'][1] != 0: if client == client_id: continue #if self.session_list[session_id][client]['serveraddr'] == None: serveraddr = self.get_server_info(gameid, session_id, client) if serveraddr == None: serveraddr = self.get_server_info_alt( gameid, session_id, client) self.session_list[session_id][client][ 'serveraddr'] = serveraddr logger.log( logging.DEBUG, "Found server from local ip/port: %s from %d" % (serveraddr, session_id)) publicport = self.session_list[session_id][client]['addr'][ 1] if self.session_list[session_id][client]['localaddr'][ 1] != 0: publicport = self.session_list[session_id][client][ 'localaddr'][1] if self.session_list[session_id][client][ 'serveraddr'] != None: publicport = int(self.session_list[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[session_id][client] ['addr'][0].split('.') ]) output += utils.get_bytes_from_short(publicport, True) output += bytearray( [0x42, 0x00]) # Unknown, always seems to be \x42\x00 output[7] = 0x05 #self.write_queue.put((output, (self.session_list[session_id][client_id]['addr']))) self.write_queue.put((output, ( self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1]))) logger.log( logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client_id]['addr'][0], self.session_list[session_id][client_id]['addr'][1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) # Send to other client #if self.session_list[session_id][client_id]['serveraddr'] == None: serveraddr = self.get_server_info(gameid, session_id, client_id) if serveraddr == None: serveraddr = self.get_server_info_alt( gameid, session_id, client_id) self.session_list[session_id][client_id][ 'serveraddr'] = serveraddr logger.log( logging.DEBUG, "Found server 2 from local ip/port: %s from %d" % (serveraddr, session_id)) publicport = self.session_list[session_id][client_id][ 'addr'][1] if self.session_list[session_id][client_id]['localaddr'][ 1] != 0: publicport = self.session_list[session_id][client_id][ 'localaddr'][1] if self.session_list[session_id][client_id][ 'serveraddr'] != None: publicport = int( self.session_list[session_id][client_id] ['serveraddr']['publicport']) output = bytearray(recv_data[0:12]) output += bytearray([ int(x) for x in self.session_list[session_id] [client_id]['addr'][0].split('.') ]) output += utils.get_bytes_from_short(publicport, True) output += bytearray( [0x42, 0x00]) # Unknown, always seems to be \x42\x00 output[7] = 0x05 #self.write_queue.put((output, (self.session_list[session_id][client]['addr']))) self.write_queue.put( (output, (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1]))) logger.log( logging.DEBUG, "Sent connection request to %s:%d..." % (self.session_list[session_id][client]['addr'][0], self.session_list[session_id][client]['addr'][1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) elif recv_data[7] == '\x06': # Was able to connect client_id = "%02x" % ord(recv_data[13]) logger.log( logging.DEBUG, "Received connected command from %s:%s..." % (addr[0], addr[1])) if session_id in self.session_list and client_id in self.session_list[ session_id]: self.session_list[session_id][client_id]['connected'] = True elif recv_data[7] == '\x0a': # Address check. Note: UNTESTED! client_id = "%02x" % ord(recv_data[13]) logger.log( logging.DEBUG, "Received address check command from %s:%s..." % (addr[0], addr[1])) output = bytearray(recv_data[0:15]) output += bytearray([int(x) for x in addr[0].split('.')]) output += utils.get_bytes_from_short(addr[1], True) output += bytearray(recv_data[len(output):]) output[7] = 0x0b self.write_queue.put((output, addr)) logger.log( logging.DEBUG, "Sent address check response to %s:%d..." % (addr[0], addr[1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) elif recv_data[7] == '\x0c': # Natify port_type = "%02x" % ord(recv_data[12]) logger.log( logging.DEBUG, "Received natify command from %s:%s..." % (addr[0], addr[1])) output = bytearray(recv_data) output[7] = 0x02 # ERT Test self.write_queue.put((output, addr)) logger.log(logging.DEBUG, "Sent natify response to %s:%d..." % (addr[0], addr[1])) logger.log(logging.DEBUG, utils.pretty_print_hex(output)) elif recv_data[7] == '\x0d': # Report logger.log( logging.DEBUG, "Received report command from %s:%s..." % (addr[0], addr[1])) logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) # Report response output = bytearray(recv_data[:21]) output[7] = 0x0e # Report response output[14] = 0 # Clear byte to match real server's response self.write_queue.put((output, addr)) elif recv_data[7] == '\x0f': # Natneg v4 command thanks to Pipian. # Only seems to be used in very few DS games (namely, Pokemon Black/White/Black 2/White 2). logger.log( logging.DEBUG, "Received pre-init command from %s:%s..." % (addr[0], addr[1])) logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data)) session = utils.get_int(recv_data[-4:], 0) # Report response output = bytearray(recv_data[:-4]) + bytearray([0, 0, 0, 0]) output[7] = 0x10 # Pre-init response if session == 0: # What's the correct behavior when session == 0? output[13] = 2 elif session in self.natneg_preinit_session: # Should this be sent to both clients or just the one that connected most recently? # I can't tell from a one sided packet capture of Pokemon. # For the time being, send to both clients just in case. output[13] = 2 self.write_queue.put( (output, self.natneg_preinit_session[session])) output[12] = (1, 0)[output[12]] # Swap the index del self.natneg_preinit_session[session] else: output[13] = 0 self.natneg_preinit_session[session] = addr self.write_queue.put((output, addr)) 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 rawDataReceived(self, data): try: # First 2 bytes are the packet size. # # Third byte is the command byte. # According to Openspy-Core: # 0x00 - Server list request # 0x01 - Server info request # 0x02 - Send message request # 0x03 - Keep alive reply # 0x04 - Map loop request (?) # 0x05 - Player search request # # For Tetris DS, at the very least 0x00 and 0x02 need to be # implemented. self.buffer += data while len(self.buffer) > 0: packet_len = utils.get_short(self.buffer, 0, True) packet = None if len(self.buffer) >= packet_len: packet = self.buffer[:packet_len] self.buffer = self.buffer[packet_len:] if packet is None: # Don't have enough for the entire packet, break. break if packet[2] == '\x00': # Server list request self.log(logging.DEBUG, "Received server list request from %s:%s...", self.address.host, self.address.port) # This code is so... not python. The C programmer in me is # coming out strong. # TODO: Rewrite this section later? idx = 3 list_version = ord(packet[idx]) idx += 1 encoding_version = ord(packet[idx]) idx += 1 game_version = utils.get_int(packet, idx) idx += 4 query_game = utils.get_string(packet, idx) idx += len(query_game) + 1 game_name = utils.get_string(packet, idx) idx += len(game_name) + 1 challenge = ''.join(packet[idx:idx+8]) idx += 8 filter = utils.get_string(packet, idx) idx += len(filter) + 1 fields = utils.get_string(packet, idx) idx += len(fields) + 1 options = utils.get_int(packet, idx, True) idx += 4 source_ip = 0 max_servers = 0 NO_SERVER_LIST = 0x02 ALTERNATE_SOURCE_IP = 0x08 LIMIT_RESULT_COUNT = 0x80 send_ip = False if (options & LIMIT_RESULT_COUNT): max_servers = utils.get_int(packet, idx) elif (options & ALTERNATE_SOURCE_IP): source_ip = utils.get_ip(packet, idx) elif (options & NO_SERVER_LIST): send_ip = True if '\\' in fields: fields = [x for x in fields.split('\\') if x and not x.isspace()] # print "%02x %02x %08x" % \ # (list_version, encoding_version, game_version) # print "%s" % query_game # print "%s" % game_name # print "%s" % challenge # print "%s" % filter # print "%s" % fields # print "%08x" % options # print "%d %08x" % (max_servers, source_ip) self.log(logging.DEBUG, "list version: %02x / encoding version: %02x /" " game version: %08x / query game: %s /" " game name: %s / challenge: %s / filter: %s /" " fields: %s / options: %08x / max servers: %d /" " source ip: %08x", list_version, encoding_version, game_version, query_game, game_name, challenge, filter, fields, options, max_servers, source_ip) # Requesting ip and port of client, not server if not filter and not fields or send_ip: output = bytearray( [int(x) for x in self.address.host.split('.')] ) # Does this ever change? output += utils.get_bytes_from_short(6500, True) enc = gs_utils.EncTypeX() output_enc = enc.encrypt( self.secret_key_list[game_name], challenge, output ) self.transport.write(bytes(output_enc)) self.log(logging.DEBUG, "%s", "Responding with own IP and game port...") self.log(logging.DEBUG, "%s", utils.pretty_print_hex(output)) else: self.find_server(query_game, filter, fields, max_servers, game_name, challenge) elif packet[2] == '\x02': # Send message request packet_len = utils.get_short(packet, 0, True) dest_addr = '.'.join(["%d" % ord(x) for x in packet[3:7]]) # What's the pythonic way to do this? unpack? dest_port = utils.get_short(packet, 7, True) dest = (dest_addr, dest_port) self.log(logging.DEBUG, "Received send message request from %s:%s to" " %s:%d... expecting %d byte packet.", self.address.host, self.address.port, dest_addr, dest_port, packet_len) self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(packet))) if packet_len == len(packet): # Contains entire packet, send immediately. self.forward_data_to_client(packet[9:], dest) else: self.log(logging.ERROR, "%s", "ERROR: Could not find entire packet.") elif packet[2] == '\x03': # Keep alive reply self.log(logging.DEBUG, "Received keep alive from %s:%s...", self.address.host, self.address.port) else: self.log(logging.DEBUG, "Received unknown command (%02x) from %s:%s...", ord(packet[2]), self.address.host, self.address.port) self.log(logging.DEBUG, "%s", utils.pretty_print_hex(bytearray(packet))) except: self.log(logging.ERROR, "Unknown exception: %s", traceback.format_exc())