mirror of
https://github.com/barronwaffles/dwc_network_server_emulator.git
synced 2026-08-27 05:14:31 -05:00
Cleaned: gamespy_qr_server.py
This commit is contained in:
@@ -18,8 +18,10 @@
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Server emulator for *.available.gs.nintendowifi.net and *.master.gs.nintendowifi.net
|
||||
Query and Reporting: http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview
|
||||
Server emulator for *.available.gs.nintendowifi.net and
|
||||
*.master.gs.nintendowifi.net
|
||||
Query and Reporting:
|
||||
http://docs.poweredbygamespy.com/wiki/Query_and_Reporting_Overview
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -43,17 +45,20 @@ logger_output_to_console = True
|
||||
logger_output_to_file = True
|
||||
logger_name = "GameSpyQRServer"
|
||||
logger_filename = "gamespy_qr_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)
|
||||
|
||||
|
||||
class GameSpyServerDatabase(BaseManager):
|
||||
pass
|
||||
|
||||
|
||||
class GameSpyQRServer(object):
|
||||
class Session(object):
|
||||
def __init__(self, address):
|
||||
self.session = ""
|
||||
self.challenge = ""
|
||||
self.secretkey = "" # Parse gslist.cfg later
|
||||
self.secretkey = "" # Parse gslist.cfg later
|
||||
self.sent_challenge = False
|
||||
self.heartbeat_data = None
|
||||
self.address = address
|
||||
@@ -64,47 +69,63 @@ class GameSpyQRServer(object):
|
||||
self.gamename = ""
|
||||
self.keepalive = -1
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.sessions = {}
|
||||
|
||||
# Generate a dictionary "secret_key_list" containing the secret game keys associated with their game IDs.
|
||||
# The dictionary key will be the game's ID, and the value will be the secret key.
|
||||
# Generate a dictionary "secret_key_list" containing the secret game
|
||||
# keys associated with their game IDs. The dictionary key will be the
|
||||
# game's ID, and the value will be the secret key.
|
||||
self.secret_key_list = gs_utils.generate_secret_keys("gslist.cfg")
|
||||
#self.log(logging.DEBUG, address, session_id, "Generated list of secret game keys...")
|
||||
# self.log(logging.DEBUG, address, session_id,
|
||||
# "Generated list of secret game keys...")
|
||||
|
||||
GameSpyServerDatabase.register("update_server_list")
|
||||
GameSpyServerDatabase.register("delete_server")
|
||||
|
||||
def log(self, level, address, session_id, message):
|
||||
def log(self, level, address, session_id, msg, *args, **kwargs):
|
||||
"""TODO: Use logger format"""
|
||||
if address is None:
|
||||
logger.log(level, "%s", message)
|
||||
logger.log(level, msg, *args, **kwargs)
|
||||
else:
|
||||
if session_id is not None:
|
||||
logger.log(level, "[%s:%d %08x] %s", address[0], address[1], session_id, message)
|
||||
logger.log(level, "[%s:%d %08x] " + msg,
|
||||
address[0], address[1], session_id,
|
||||
*args, **kwargs)
|
||||
else:
|
||||
logger.log(level, "[%s:%d] %s", address[0], address[1], message)
|
||||
logger.log(level, "[%s:%d] " + msg,
|
||||
address[0], address[1],
|
||||
*args, **kwargs)
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
manager_address = ("127.0.0.1", 27500)
|
||||
manager_password = ""
|
||||
|
||||
self.server_manager = GameSpyServerDatabase(address = manager_address, authkey= manager_password)
|
||||
self.server_manager = GameSpyServerDatabase(
|
||||
address=manager_address,
|
||||
authkey=manager_password
|
||||
)
|
||||
self.server_manager.connect()
|
||||
|
||||
# Start QR server
|
||||
address = ('0.0.0.0', 27900) # accessible to outside connections (use this if you don't know what you're doing)
|
||||
# Accessible to outside connections (use this if you don't know
|
||||
# what you're doing)
|
||||
address = ('0.0.0.0', 27900)
|
||||
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.socket.bind(address)
|
||||
self.socket.setblocking(0)
|
||||
|
||||
logger.log(logging.INFO, "Server is now listening on %s:%s..." % (address[0], address[1]))
|
||||
logger.log(logging.INFO,
|
||||
"Server is now listening on %s:%s...",
|
||||
address[0], address[1])
|
||||
|
||||
# Dependencies! I don't really like this solution but it's easier than trying to manage it another way.
|
||||
# Dependencies! I don't really like this solution but it's easier
|
||||
# than trying to manage it another way.
|
||||
server_browser_server = GameSpyServerBrowserServer(self)
|
||||
server_browser_server_thread = threading.Thread(target=server_browser_server.start)
|
||||
server_browser_server_thread = threading.Thread(
|
||||
target=server_browser_server.start
|
||||
)
|
||||
server_browser_server_thread.start()
|
||||
|
||||
self.write_queue = Queue.Queue()
|
||||
@@ -120,124 +141,153 @@ class GameSpyQRServer(object):
|
||||
|
||||
self.keepalive_check()
|
||||
except:
|
||||
logger.log(logging.ERROR, "Unknown exception: %s" % traceback.format_exc())
|
||||
logger.log(logging.ERROR,
|
||||
"Unknown exception: %s",
|
||||
traceback.format_exc())
|
||||
|
||||
def write_queue_send(self, data, address):
|
||||
time.sleep(0.05)
|
||||
self.socket.sendto(data, address)
|
||||
|
||||
def write_queue_worker(self):
|
||||
while 1:
|
||||
while True:
|
||||
data, address = self.write_queue.get()
|
||||
threading.Thread(target=self.write_queue_send, args=(data, address)).start()
|
||||
threading.Thread(target=self.write_queue_send,
|
||||
args=(data, address)).start()
|
||||
self.write_queue.task_done()
|
||||
|
||||
def update_server_list(self, session_id, k):
|
||||
if "statechanged" in k and k['statechanged'] == "2": # Close server
|
||||
self.server_manager.delete_server(k['gamename'] , session_id)
|
||||
if "statechanged" in k and k['statechanged'] == "2": # Close server
|
||||
self.server_manager.delete_server(k['gamename'], session_id)
|
||||
|
||||
if session_id in self.sessions:
|
||||
del self.sessions[session_id]
|
||||
if session_id in self.sessions:
|
||||
del self.sessions[session_id]
|
||||
|
||||
else:
|
||||
# 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).
|
||||
# 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()
|
||||
# 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']
|
||||
|
||||
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.
|
||||
"""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.
|
||||
"""
|
||||
session_id = None
|
||||
if recv_data[0] != '\x09':
|
||||
# Don't add a session if the client is trying to check if the game is available or not
|
||||
# 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:
|
||||
@@ -247,78 +297,117 @@ class GameSpyQRServer(object):
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
self.sessions[session_id].disconnected = False
|
||||
|
||||
if session_id in self.sessions and self.sessions[session_id].disconnected == True:
|
||||
if session_id in self.sessions and \
|
||||
self.sessions[session_id].disconnected:
|
||||
return
|
||||
|
||||
if session_id in self.sessions:
|
||||
self.sessions[session_id].keepalive = int(time.time()) # Make sure the server doesn't get removed
|
||||
# Make sure the server doesn't get removed
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
|
||||
# Handle commands
|
||||
if recv_data[0] == '\x00': # Query
|
||||
self.log(logging.DEBUG, address, session_id, "NOT IMPLEMENTED! Received query from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
if recv_data[0] == '\x00': # Query
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"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, session_id, "Received challenge from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
elif recv_data[0] == '\x01': # Challenge
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"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)
|
||||
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
|
||||
# Send client registered command
|
||||
packet = bytearray([0xfe, 0xfd, 0x0a])
|
||||
packet.extend(session_id_raw) # Get the session ID
|
||||
self.write_queue.put((packet, address))
|
||||
self.log(logging.DEBUG, address, session_id, "Sent client registered to %s:%s..." % (address[0], address[1]))
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"Sent client registered to %s:%s...",
|
||||
address[0], address[1])
|
||||
|
||||
if self.sessions[session_id].heartbeat_data is not None:
|
||||
self.update_server_list(session_id, self.sessions[session_id].heartbeat_data)
|
||||
self.update_server_list(
|
||||
session_id,
|
||||
self.sessions[session_id].heartbeat_data
|
||||
)
|
||||
|
||||
else:
|
||||
# Failed the challenge, request another during the next heartbeat
|
||||
# Failed the challenge, request another during the next
|
||||
# heartbeat
|
||||
self.sessions[session_id].sent_challenge = False
|
||||
self.server_manager.delete_server(self.sessions[session_id].gamename, session_id)
|
||||
self.server_manager.delete_server(
|
||||
self.sessions[session_id].gamename,
|
||||
session_id
|
||||
)
|
||||
|
||||
elif recv_data[0] == '\x02': # Echo
|
||||
self.log(logging.DEBUG, address, session_id, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
elif recv_data[0] == '\x02': # Echo
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"NOT IMPLEMENTED! Received echo from %s:%s... %s",
|
||||
address[0], address[1], recv_data[5:])
|
||||
|
||||
elif recv_data[0] == '\x03': # Heartbeat
|
||||
elif recv_data[0] == '\x03': # Heartbeat
|
||||
data = recv_data[5:]
|
||||
self.log(logging.DEBUG, address, session_id, "Received heartbeat from %s:%s... %s" % (address[0], address[1], data))
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"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...
|
||||
# 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, session_id, "%s = %s" % (d[i], d[i+1]))
|
||||
# self.log(logging.DEBUG, address, session_id,
|
||||
# "%s = %s",
|
||||
# d[i], d[i + 1])
|
||||
k[d[i]] = d[i+1]
|
||||
|
||||
if self.sessions[session_id].ingamesn is not None:
|
||||
if "gamename" in k and "dwc_pid" in k:
|
||||
try:
|
||||
profile = self.db.get_profile_from_profileid(k['dwc_pid'])
|
||||
naslogin = self.db.get_nas_login_from_userid(profile['userid'])
|
||||
self.sessions[session_id].ingamesn = str(naslogin['ingamesn']) # convert to string from unicode(which is just a base64 string anyway)
|
||||
except Exception,e:
|
||||
pass # If the game doesn't have, don't worry about it.
|
||||
profile = self.db.get_profile_from_profileid(
|
||||
k['dwc_pid']
|
||||
)
|
||||
naslogin = self.db.get_nas_login_from_userid(
|
||||
profile['userid']
|
||||
)
|
||||
# Convert to string from unicode (which is just a
|
||||
# base64 string anyway)
|
||||
self.sessions[session_id].ingamesn = \
|
||||
str(naslogin['ingamesn'])
|
||||
except Exception, e:
|
||||
# If the game doesn't have, don't worry about it.
|
||||
pass
|
||||
|
||||
if self.sessions[session_id].ingamesn is not None and "ingamesn" not in k:
|
||||
if self.sessions[session_id].ingamesn is not None and \
|
||||
"ingamesn" not in k:
|
||||
k['ingamesn'] = self.sessions[session_id].ingamesn
|
||||
|
||||
if "gamename" in k:
|
||||
if k['gamename'] in self.secret_key_list:
|
||||
self.sessions[session_id].secretkey = self.secret_key_list[k['gamename']]
|
||||
self.sessions[session_id].secretkey = \
|
||||
self.secret_key_list[k['gamename']]
|
||||
else:
|
||||
self.log(logging.INFO, address, session_id, "Connection from unknown game '%s'!" % k['gamename'])
|
||||
self.log(logging.INFO, address, session_id,
|
||||
"Connection from unknown game '%s'!",
|
||||
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.
|
||||
# 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'])
|
||||
|
||||
# Try to detect console without hitting the database first
|
||||
@@ -326,10 +415,16 @@ class GameSpyQRServer(object):
|
||||
if 'gamename' in k:
|
||||
self.sessions[session_id].console = 0
|
||||
|
||||
if k['gamename'].endswith('ds') or k['gamename'].endswith('dsam') or k['gamename'].endswith('dsi') or k['gamename'].endswith('dsiam'):
|
||||
if k['gamename'].endswith('ds') or \
|
||||
k['gamename'].endswith('dsam') or \
|
||||
k['gamename'].endswith('dsi') or \
|
||||
k['gamename'].endswith('dsiam'):
|
||||
self.sessions[session_id].console = 0
|
||||
found_console = True
|
||||
elif k['gamename'].endswith('wii') or k['gamename'].endswith('wiiam') or k['gamename'].endswith('wiiware') or k['gamename'].endswith('wiiwaream'):
|
||||
elif k['gamename'].endswith('wii') or \
|
||||
k['gamename'].endswith('wiiam') or \
|
||||
k['gamename'].endswith('wiiware') or \
|
||||
k['gamename'].endswith('wiiwaream'):
|
||||
self.sessions[session_id].console = 1
|
||||
found_console = True
|
||||
|
||||
@@ -338,92 +433,151 @@ class GameSpyQRServer(object):
|
||||
# Try a 3 times before giving up
|
||||
for i in range(0, 3):
|
||||
try:
|
||||
profile = self.db.get_profile_from_profileid(self.sessions[session_id].playerid)
|
||||
profile = self.db.get_profile_from_profileid(
|
||||
self.sessions[session_id].playerid
|
||||
)
|
||||
|
||||
if "console" in profile:
|
||||
self.sessions[session_id].console = profile['console']
|
||||
self.sessions[session_id].console = \
|
||||
profile['console']
|
||||
|
||||
break
|
||||
except:
|
||||
time.sleep(0.5)
|
||||
|
||||
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 '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
|
||||
be = self.sessions[session_id].console != 0
|
||||
k['publicip'] = str(utils.get_ip(bytearray([int(x) for x in address[0].split('.')]), 0, be))
|
||||
k['publicip'] = str(utils.get_ip(
|
||||
bytearray([int(x) for x in address[0].split('.')]),
|
||||
0,
|
||||
be
|
||||
))
|
||||
|
||||
if 'publicport' in k and 'localport' in k and k['publicport'] != k['localport']:
|
||||
self.log(logging.DEBUG, address, session_id, "publicport %s doesn't match localport %s, so changing publicport to %s..." \
|
||||
% (k['publicport'], k['localport'], str(address[1])))
|
||||
if 'publicport' in k and \
|
||||
'localport' in k and \
|
||||
k['publicport'] != k['localport']:
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"publicport %s doesn't match localport %s,"
|
||||
" so changing publicport to %s...",
|
||||
k['publicport'], k['localport'],
|
||||
str(address[1]))
|
||||
k['publicport'] = str(address[1])
|
||||
|
||||
if self.sessions[session_id].sent_challenge == True:
|
||||
if self.sessions[session_id].sent_challenge:
|
||||
self.update_server_list(session_id, k)
|
||||
else:
|
||||
addr_hex = ''.join(["%02X" % int(x) for x in address[0].split('.')])
|
||||
addr_hex = ''.join(["%02X" % int(x)
|
||||
for x in address[0].split('.')])
|
||||
port_hex = "%04X" % int(address[1])
|
||||
server_challenge = utils.generate_random_str(6) + '00' + addr_hex + port_hex
|
||||
server_challenge = utils.generate_random_str(6) + '00' + \
|
||||
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
|
||||
# Send challenge command
|
||||
packet = bytearray([0xfe, 0xfd, 0x01])
|
||||
# Get the session ID
|
||||
packet.extend(session_id_raw)
|
||||
packet.extend(server_challenge)
|
||||
packet.extend('\x00')
|
||||
|
||||
self.write_queue.put((packet, address))
|
||||
self.log(logging.DEBUG, address, session_id, "Sent challenge to %s:%s..." % (address[0], address[1]))
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"Sent challenge to %s:%s...",
|
||||
address[0], address[1])
|
||||
|
||||
self.sessions[session_id].sent_challenge = True
|
||||
self.sessions[session_id].heartbeat_data = k
|
||||
|
||||
elif recv_data[0] == '\x04': # Add Error
|
||||
self.log(logging.WARNING, address, session_id, "NOT IMPLEMENTED! Received add error from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
elif recv_data[0] == '\x04': # Add Error
|
||||
self.log(logging.WARNING, address, session_id,
|
||||
"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, session_id, "NOT IMPLEMENTED! Received echo response from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
elif recv_data[0] == '\x05': # Echo Response
|
||||
self.log(logging.WARNING, address, session_id,
|
||||
"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, session_id, "NOT IMPLEMENTED! Received echo from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
elif recv_data[0] == '\x06': # Client Message
|
||||
self.log(logging.WARNING, address, session_id,
|
||||
"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, session_id, "NOT IMPLEMENTED! Received client message ack from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
self.log(logging.DEBUG, address, session_id, "Received client message ack from %s:%s..." % (address[0], address[1]))
|
||||
elif recv_data[0] == '\x07': # Client Message Ack
|
||||
# self.log(logging.WARNING, address, session_id,
|
||||
# "NOT IMPLEMENTED! Received client message ack"
|
||||
# " from %s:%s... %s",
|
||||
# address[0], address[1], recv_data[5:])
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"Received client message ack from %s:%s...",
|
||||
address[0], address[1])
|
||||
|
||||
elif recv_data[0] == '\x08': # Keep Alive
|
||||
self.log(logging.DEBUG, address, session_id, "Received keep alive from %s:%s..." % (address[0], address[1]))
|
||||
elif recv_data[0] == '\x08': # Keep Alive
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"Received keep alive from %s:%s...",
|
||||
address[0], address[1])
|
||||
self.sessions[session_id].keepalive = int(time.time())
|
||||
|
||||
elif recv_data[0] == '\x09': # Available
|
||||
elif recv_data[0] == '\x09': # Available
|
||||
# Availability check only sent to *.available.gs.nintendowifi.net
|
||||
self.log(logging.DEBUG, address, session_id, "Received availability request for '%s' from %s:%s..." % (recv_data[5: -1], address[0], address[1]))
|
||||
self.write_queue.put((bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]), address))
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"Received availability request for '%s' from %s:%s...",
|
||||
recv_data[5: -1], address[0], address[1])
|
||||
self.write_queue.put((
|
||||
bytearray([0xfe, 0xfd, 0x09, 0x00, 0x00, 0x00, 0x00]),
|
||||
address
|
||||
))
|
||||
|
||||
elif recv_data[0] == '\x0a': # Client Registered
|
||||
elif recv_data[0] == '\x0a': # Client Registered
|
||||
# Only sent to client, never received?
|
||||
self.log(logging.WARNING, address, session_id, "NOT IMPLEMENTED! Received client registered from %s:%s... %s" % (address[0], address[1], recv_data[5:]))
|
||||
self.log(logging.WARNING, address, session_id,
|
||||
"NOT IMPLEMENTED! Received client registered"
|
||||
" from %s:%s... %s",
|
||||
address[0], address[1], recv_data[5:])
|
||||
|
||||
else:
|
||||
self.log(logging.ERROR, address, session_id, "Unknown request from %s:%s:" % (address[0], address[1]))
|
||||
self.log(logging.DEBUG, address, session_id, utils.pretty_print_hex(recv_data))
|
||||
self.log(logging.ERROR, address, session_id,
|
||||
"Unknown request from %s:%s:",
|
||||
address[0], address[1])
|
||||
self.log(logging.DEBUG, address, session_id,
|
||||
"%s",
|
||||
utils.pretty_print_hex(recv_data))
|
||||
|
||||
def keepalive_check(self):
|
||||
#self.log(logging.DEBUG, None, session_id, "Keep alive check on %d sessions" % (len(self.sessions)))
|
||||
# self.log(logging.DEBUG, None, session_id,
|
||||
# "Keep alive check on %d sessions",
|
||||
# len(self.sessions))
|
||||
|
||||
pruned = []
|
||||
now = int(time.time())
|
||||
|
||||
for session_id in self.sessions:
|
||||
delta = now - self.sessions[session_id].keepalive
|
||||
timeout = 61 # Remove clients that haven't responded in x seconds
|
||||
timeout = 61 # Remove clients that haven't responded in x seconds
|
||||
|
||||
if delta < 0 or delta >= timeout:
|
||||
pruned.append(session_id)
|
||||
self.server_manager.delete_server(self.sessions[session_id].gamename, self.sessions[session_id].session)
|
||||
self.log(logging.DEBUG, None, session_id, "Keep alive check removed %s:%s for game %s. Client hasn't responded in %d seconds." % (self.sessions[session_id].address[0], self.sessions[session_id].address[1], self.sessions[session_id].gamename, delta))
|
||||
self.server_manager.delete_server(
|
||||
self.sessions[session_id].gamename,
|
||||
self.sessions[session_id].session
|
||||
)
|
||||
self.log(logging.DEBUG, None, session_id,
|
||||
"Keep alive check removed %s:%s for game %s."
|
||||
" Client hasn't responded in %d seconds.",
|
||||
self.sessions[session_id].address[0],
|
||||
self.sessions[session_id].address[1],
|
||||
self.sessions[session_id].gamename,
|
||||
delta)
|
||||
|
||||
for session_id in pruned:
|
||||
del self.sessions[session_id]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
qr_server = GameSpyQRServer()
|
||||
qr_server.start()
|
||||
|
||||
Reference in New Issue
Block a user