mirror of
https://github.com/barronwaffles/dwc_network_server_emulator.git
synced 2026-08-20 17:45:23 -05:00
Fixed bug in natneg, threading for UDP packets
This commit is contained in:
@@ -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("<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
|
||||
s.sendto(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_int(recv_data, 15)
|
||||
localip_int_be = utils.get_int_be(recv_data, 15)
|
||||
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, localip_int_le, localip_int_be)
|
||||
|
||||
if session_id not in self.session_list:
|
||||
self.session_list[session_id] = {}
|
||||
if client_id not in self.session_list[session_id]:
|
||||
self.session_list[session_id][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_be(publicport)
|
||||
|
||||
output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
|
||||
output[7] = 0x05
|
||||
#s.sendto(output, (self.session_list[session_id][client_id]['addr']))
|
||||
s.sendto(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)
|
||||
|
||||
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_be(publicport)
|
||||
|
||||
output += bytearray([0x42, 0x00]) # Unknown, always seems to be \x42\x00
|
||||
output[7] = 0x05
|
||||
#s.sendto(output, (self.session_list[session_id][client]['addr']))
|
||||
s.sendto(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 not in self.session_list:
|
||||
return
|
||||
if client_id not in self.session_list[session_id]:
|
||||
return
|
||||
|
||||
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_be(addr[1])
|
||||
output += bytearray(recv_data[len(output):])
|
||||
|
||||
output[7] = 0x0b
|
||||
s.sendto(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
|
||||
s.sendto(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':
|
||||
client_id = "%02x" % ord(recv_data[13])
|
||||
logger.log(logging.DEBUG, "Received report command from %s:%s..." % (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]):
|
||||
continue
|
||||
output = bytearray(recv_data)
|
||||
output[7] = 0x0e # Report response
|
||||
s.sendto(recv_data, addr)
|
||||
|
||||
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
|
||||
s.sendto(output, addr)
|
||||
|
||||
# Try to connect to the server
|
||||
gameid = utils.get_string(recv_data, 0x16)
|
||||
client_id = "%02x" % ord(recv_data[13])
|
||||
|
||||
localip_raw = recv_data[15:19]
|
||||
localip_int_le = utils.get_int(recv_data, 15)
|
||||
localip_int_be = utils.get_int_be(recv_data, 15)
|
||||
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, localip_int_le, localip_int_be)
|
||||
|
||||
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': '', '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: # 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)
|
||||
if serveraddr == None:
|
||||
serveraddr = self.get_server_info_alt(gameid, session_id, client)
|
||||
|
||||
self.session_list[gameid][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[gameid][session_id][client]['addr'][1]
|
||||
if self.session_list[gameid][session_id][client]['localaddr'][1] != 0:
|
||||
publicport = self.session_list[gameid][session_id][client]['localaddr'][1]
|
||||
|
||||
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(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'][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))
|
||||
|
||||
# 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)
|
||||
if serveraddr == None:
|
||||
serveraddr = self.get_server_info_alt(gameid, session_id, client)
|
||||
|
||||
self.session_list[gameid][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[gameid][session_id][client_id]['addr'][1]
|
||||
if self.session_list[gameid][session_id][client_id]['localaddr'][1] != 0:
|
||||
publicport = self.session_list[gameid][session_id][client_id]['localaddr'][1]
|
||||
|
||||
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(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'][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))
|
||||
|
||||
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 gameid not in self.session_list:
|
||||
continue
|
||||
if session_id not in self.session_list[gameid]:
|
||||
continue
|
||||
if client_id not in self.session_list[gameid][session_id]:
|
||||
continue
|
||||
|
||||
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])
|
||||
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_be(addr[1])
|
||||
output += bytearray(recv_data[len(output):])
|
||||
|
||||
output[7] = 0x0b
|
||||
s.sendto(output, addr)
|
||||
|
||||
logger.log(logging.DEBUG, "Sent address check response 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))
|
||||
|
||||
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
|
||||
s.sendto(output, addr)
|
||||
|
||||
logger.log(logging.DEBUG, "Sent natify response 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))
|
||||
|
||||
elif recv_data[7] == '\x0d':
|
||||
client_id = "%02x" % ord(recv_data[13])
|
||||
logger.log(logging.DEBUG, "Received report command from %s:%s..." % (addr[0], addr[1]))
|
||||
logger.log(logging.DEBUG, utils.pretty_print_hex(recv_data))
|
||||
|
||||
output = bytearray(recv_data)
|
||||
output[7] = 0x0e # Report response
|
||||
s.sendto(recv_data, 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]))
|
||||
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):
|
||||
server_info = None
|
||||
@@ -215,7 +216,7 @@ class GameSpyNatNegServer(object):
|
||||
return None
|
||||
|
||||
console = 0
|
||||
ipstr = self.session_list[gameid][session_id][client_id]['addr'][0]
|
||||
ipstr = self.session_list[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
|
||||
@@ -244,7 +245,7 @@ class GameSpyNatNegServer(object):
|
||||
|
||||
def get_server_info_alt(self, gameid, session_id, client_id):
|
||||
console = 0
|
||||
ipstr = self.session_list[gameid][session_id][client_id]['addr'][0]
|
||||
ipstr = self.session_list[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
|
||||
@@ -253,7 +254,7 @@ class GameSpyNatNegServer(object):
|
||||
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'], self.session_list[gameid][session_id][client_id]['gameid'])._getvalue()
|
||||
serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], self.session_list[session_id][client_id]['gameid'])._getvalue()
|
||||
|
||||
if serveraddr == None:
|
||||
if console != 0:
|
||||
@@ -263,7 +264,7 @@ class GameSpyNatNegServer(object):
|
||||
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'], self.session_list[gameid][session_id][client_id]['gameid'])._getvalue()
|
||||
serveraddr = self.server_manager.find_server_by_local_address(ip, self.session_list[session_id][client_id]['localaddr'], self.session_list[session_id][client_id]['gameid'])._getvalue()
|
||||
|
||||
return serveraddr
|
||||
|
||||
|
||||
@@ -81,233 +81,234 @@ class GameSpyQRServer(object):
|
||||
|
||||
threading.Timer(1, self.keepalive_check).start()
|
||||
|
||||
self.wait_loop()
|
||||
|
||||
def wait_loop(self):
|
||||
while 1:
|
||||
recv_data, address = self.socket.recvfrom(2048)
|
||||
time.sleep(0.05)
|
||||
|
||||
# Tetris DS overlay 10 @ 02144184 - Handle responses back to server
|
||||
# Tetris DS overlay 10 @ 02144184 - Handle responses back to server
|
||||
#
|
||||
# After some more packet inspection, it seems the format goes something like this:
|
||||
# - All server messages seem to always start with \xfe\xfd.
|
||||
# - The first byte from the client (or third byte from the server) is a command.
|
||||
# - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
|
||||
# session-like ID because the number changes between connections. Copying the client's ID might be enough.
|
||||
#
|
||||
# The above was as guessed.
|
||||
# The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
|
||||
# R1 contains the command to be sent to the server.
|
||||
# R2 contains a pointer to some unknown integer that gets written after the command.
|
||||
#
|
||||
# - Commands
|
||||
# Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
|
||||
#
|
||||
# CLIENT:
|
||||
# 0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
|
||||
# Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
|
||||
#
|
||||
# 0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
|
||||
# Data sent:
|
||||
# 1) Loop for each localip available on the system, write as localip%d\x00(local ip)
|
||||
# 2) localport\x00(local port)
|
||||
# 3) natneg (either 0 or 1)
|
||||
# 4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
|
||||
# 5) gamename\x00(game name)
|
||||
# 6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
|
||||
# 7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
|
||||
#
|
||||
# if statechanged != 2:
|
||||
# Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
|
||||
#
|
||||
# 0x07 - Unknown, related to server's 0x06 (returns value sent from server)
|
||||
#
|
||||
# 0x08 - Keep alive? Sent after 0x03
|
||||
#
|
||||
# 0x09 - Availability check
|
||||
#
|
||||
# SERVER:
|
||||
# 0x01 - Unknown
|
||||
# Data sent:
|
||||
# 8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
|
||||
#
|
||||
# 0x06 - Unknown
|
||||
# First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
|
||||
# seeing how the server can send the same IP information many times in a row. If the IP information has
|
||||
# already been parsed then it doesn't waste time handling it.
|
||||
#
|
||||
# After that is a "SBCM" section which is 0x14 bytes in total.
|
||||
# SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
|
||||
# Seems to contain IP address information.
|
||||
#
|
||||
# The SBCM seems to contain a little information that must be parsed before.
|
||||
# After the SBCM:
|
||||
# \x03\x00\x00\x00 - Always the same?
|
||||
# \x01 - Found player?
|
||||
# \x04 - Unknown
|
||||
# (2 bytes) - Unknown. Port?
|
||||
# (4 bytes) - Player's IP
|
||||
# (4 bytes) - Unknown. Some other IP? Remote server IP?
|
||||
# \x00\x00\x00\x00 - Unknown but seems to get checked
|
||||
#
|
||||
# Another SBCM, after a player has been found and attempting to start a game:
|
||||
# \x03\x00\x00\x00 - Always the same?
|
||||
# \x05 - Connecting to player?
|
||||
# \x00 - Unknown
|
||||
# (2 bytes) - Unknown. Port? Same as before.
|
||||
# (4 bytes) - Player's IP
|
||||
# (4 bytes) - Unknown. Some other IP? Remote server IP?
|
||||
#
|
||||
# 0x0a - Response to 0x01
|
||||
# Gets sent after receiving 0x01 from the client. So, server 0x01 -> 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("<I", recv_data[1:5])[0]
|
||||
session_id_raw = recv_data[1:5]
|
||||
if session_id not in self.sessions:
|
||||
# Found a new session, add to session list
|
||||
self.sessions[session_id] = self.Session(address)
|
||||
self.sessions[session_id].session = session_id
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
def handle_packet(self, socket, recv_data, address):
|
||||
# Tetris DS overlay 10 @ 02144184 - Handle responses back to server
|
||||
# Tetris DS overlay 10 @ 02144184 - Handle responses back to server
|
||||
#
|
||||
# After some more packet inspection, it seems the format goes something like this:
|
||||
# - All server messages seem to always start with \xfe\xfd.
|
||||
# - The first byte from the client (or third byte from the server) is a command.
|
||||
# - Bytes 2 - 5 from the client is some kind of ID. This will have to be inspected later. I believe it's a
|
||||
# session-like ID because the number changes between connections. Copying the client's ID might be enough.
|
||||
#
|
||||
# The above was as guessed.
|
||||
# The code in Tetris DS (overlay 10) @ 0216E974 handles the network command creation.
|
||||
# R1 contains the command to be sent to the server.
|
||||
# R2 contains a pointer to some unknown integer that gets written after the command.
|
||||
#
|
||||
# - Commands
|
||||
# Commands range from 0x00 to 0x09 (for client only at least?) (Tetris DS overlay 10 @ 0216DDCC)
|
||||
#
|
||||
# CLIENT:
|
||||
# 0x01 - Response (Tetris DS overlay 10 @ 216DCA4)
|
||||
# Sends back base64 of RC4 encrypted string that was gotten from the server's 0x01.
|
||||
#
|
||||
# 0x03 - Send client state? (Tetris DS overlay 10 @ 216DA30)
|
||||
# Data sent:
|
||||
# 1) Loop for each localip available on the system, write as localip%d\x00(local ip)
|
||||
# 2) localport\x00(local port)
|
||||
# 3) natneg (either 0 or 1)
|
||||
# 4) ONLY IF STATE CHANGED: statechanged\x00(state) (Possible values: 0, 1, 2, 3)
|
||||
# 5) gamename\x00(game name)
|
||||
# 6) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicip\x00(public ip)
|
||||
# 7) ONLY IF PUBLIC IP AND PORT ARE AVAILABLE: publicport\x00(public port)
|
||||
#
|
||||
# if statechanged != 2:
|
||||
# Write various other data described here: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Implementation
|
||||
#
|
||||
# 0x07 - Unknown, related to server's 0x06 (returns value sent from server)
|
||||
#
|
||||
# 0x08 - Keep alive? Sent after 0x03
|
||||
#
|
||||
# 0x09 - Availability check
|
||||
#
|
||||
# SERVER:
|
||||
# 0x01 - Unknown
|
||||
# Data sent:
|
||||
# 8 random ASCII characters (?) followed by the public IP and port of the client as a hex string
|
||||
#
|
||||
# 0x06 - Unknown
|
||||
# First 4 bytes is some kind of id? I believe it's a unique identifier for the data being sent,
|
||||
# seeing how the server can send the same IP information many times in a row. If the IP information has
|
||||
# already been parsed then it doesn't waste time handling it.
|
||||
#
|
||||
# After that is a "SBCM" section which is 0x14 bytes in total.
|
||||
# SBCM information gets parsed at 2141A0C in Tetris DS overlay 10.
|
||||
# Seems to contain IP address information.
|
||||
#
|
||||
# The SBCM seems to contain a little information that must be parsed before.
|
||||
# After the SBCM:
|
||||
# \x03\x00\x00\x00 - Always the same?
|
||||
# \x01 - Found player?
|
||||
# \x04 - Unknown
|
||||
# (2 bytes) - Unknown. Port?
|
||||
# (4 bytes) - Player's IP
|
||||
# (4 bytes) - Unknown. Some other IP? Remote server IP?
|
||||
# \x00\x00\x00\x00 - Unknown but seems to get checked
|
||||
#
|
||||
# Another SBCM, after a player has been found and attempting to start a game:
|
||||
# \x03\x00\x00\x00 - Always the same?
|
||||
# \x05 - Connecting to player?
|
||||
# \x00 - Unknown
|
||||
# (2 bytes) - Unknown. Port? Same as before.
|
||||
# (4 bytes) - Player's IP
|
||||
# (4 bytes) - Unknown. Some other IP? Remote server IP?
|
||||
#
|
||||
# 0x0a - Response to 0x01
|
||||
# Gets sent after receiving 0x01 from the client. So, server 0x01 -> 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("<I", recv_data[1:5])[0]
|
||||
session_id_raw = recv_data[1:5]
|
||||
if session_id not in self.sessions:
|
||||
# Found a new session, add to session list
|
||||
self.sessions[session_id] = self.Session(address)
|
||||
self.sessions[session_id].session = session_id
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
|
||||
elif recv_data[0] == '\x09': # Available
|
||||
# Availability check only sent to *.available.gs.nintendowifi.net
|
||||
self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
|
||||
self.socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)
|
||||
# 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] == '\x0a': # Client Registered
|
||||
# Only sent to client, never received?
|
||||
self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered 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
|
||||
socket.sendto(packet, address)
|
||||
self.log(logging.DEBUG, address, "Sent client registered to %s:%s..." % (address[0], address[1]))
|
||||
else:
|
||||
self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
|
||||
self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
|
||||
# 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')
|
||||
|
||||
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]))
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
|
||||
elif recv_data[0] == '\x09': # Available
|
||||
# Availability check only sent to *.available.gs.nintendowifi.net
|
||||
self.log(logging.DEBUG, address, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
|
||||
socket.sendto(bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address)
|
||||
|
||||
elif recv_data[0] == '\x0a': # Client Registered
|
||||
# Only sent to client, never received?
|
||||
self.log(logging.WARNING, address, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
|
||||
else:
|
||||
self.log(logging.ERROR, address, "Unknown request from %s:%s:" % (address[0], address[1]))
|
||||
self.log(logging.DEBUG, address, utils.pretty_print_hex(recv_data))
|
||||
|
||||
def keepalive_check(self):
|
||||
while 1:
|
||||
|
||||
Reference in New Issue
Block a user