giantsd/dpnet/netserver.py

352 lines
19 KiB
Python
Raw Normal View History

2019-01-22 01:30:42 +01:00
from .packet import Packet
from .EnumQuery import EnumQuery
from .EnumResponse import EnumResponse
from .CFrame import CFrame
from .DFrame import DFrame
from giants.masterserver import MasterServer
from .session import Session
import socket
import threading
import traceback
import uuid
2019-01-23 10:54:51 +01:00
import struct
2019-01-22 01:30:42 +01:00
from utils.logger import setup_logger
from giants.player import Player
logger = setup_logger(__name__)
class Netserver:
def __init__(self, server):
self.socket = None
self.statssocket = None
self.server = server
self.addrs = []
self.guid = uuid.uuid4().bytes
def run(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.bind((self.server.listen_ip, self.server.listen_port))
logger.debug("Listening to %s:%s", self.server.listen_ip, self.server.listen_port)
gameserverthread = threading.Thread(target=self.handle_packets)
gameserverthread.start()
if self.server.register_with_ms:
ms = MasterServer(self.server)
ms.register()
2019-01-22 01:30:42 +01:00
gameserverthread.join()
#statsserverthread.join()
def handle_packets(self):
while True:
data, addr = self.socket.recvfrom(1024)
logger.debug("%s:%s > %s", addr[0], addr[1], data.hex())
self.handle_new_packet(addr, data)
def handle_new_packet(self, addr, bData):
pPacket = Packet(bData)
first = pPacket.getByte()
if len(bData) >= 4 and first & DFrame.PACKET_COMMAND_DATA:
self.handle_new_dframe(addr, pPacket)
elif len(bData) >= 12 and first & CFrame.PACKET_COMMAND_FRAME:
self.handle_new_cframe(addr, pPacket)
elif first == EnumQuery.LEAD or first == EnumResponse.LEAD:
self.handle_new_enum(addr, pPacket)
else:
logger.error("Unknown frame received")
return
def handle_new_enum(self, addr, data):
# EnumQuery or EnumResponse
second = data.getByte()
if second == EnumQuery.COMMAND:
# EnumQuery
try:
eq = EnumQuery(data)
logger.debug("Got EnumQuery, sending EnumResponse")
er = EnumResponse()
er.Payload = eq.Payload
er.ApplicationDescSize = 0x50
er.ApplicationDescFlags = 0x41
er.MaxPlayers = self.server.maxplayers
er.CurrentPlayers = len(self.server.players)
er.SessionName = self.server.name
er.ApplicationInstanceGUID = self.guid
er.ApplicationGUID = eq.ApplicationGUID
er.ApplicationReservedData = b'\xff' # Map ID
er.ApplicationReservedData += b'\x00\x04\x00' # game type and teams
2019-01-23 10:54:51 +01:00
er.ApplicationReservedData += struct.pack("<L", int(self.server.version*1000)) # game version
2019-01-22 01:30:42 +01:00
er.ApplicationReservedData += b'\x02\x92\x05\x00\x01\x00\x00\x00\x00\x00' # Unknown
2019-01-22 10:35:15 +01:00
er.ApplicationReservedData += b'\x9c\x53\xf4\xde' # Seems to be a checksum of current map
2019-01-22 01:30:42 +01:00
# \x00\x00\x00\x00: [None]
# \x00\x00\x00\x01: <custom map>
# \x9c\x53\xf4\xdd: Three Way Island - Canyons
# \x1e\xe9\x39\xe1: Still Winter
er.ApplicationReservedData += self.server.currentmap.mapname.encode("ascii")
er.ApplicationReservedData += b'\x00' * (32 - len(self.server.currentmap.mapname))
self.send_packet(addr, er.to_packet())
except Exception:
logger.error("Could not parse EnumQuery or forge EnumResponse: ")
traceback.print_exc()
return
elif second == EnumResponse.COMMAND:
# wait what? ignore that shit
return
else:
logger.error("Unknown DPLHP command: %s", second)
def handle_new_cframe(self, addr, data):
try:
cframe = CFrame(data)
session = self.get_session(addr)
if cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECT:
# CONNECT CFRAME
if session and session.Full:
logger.error("Session %s:%s was already fully connected. Ignoring.", session.ip, session.port)
return
elif session and not session.Full and session.SessID == cframe.SessID:
# send CONNECTED
logger.debug("Already partially established connection. Sending a CFRAME CONNECTED again.")
session.send_cframe_connected(cframe)
#session.setup_Connect_Retry_Timer()
return
if not self.server.accept_new_players:
# ignore new players
return
else:
logger.debug("New connection. Sending a CFRAME CONNECTED.")
session = Session(self.socket)
session.SessID = cframe.SessID
session.ip = addr[0]
session.port = addr[1]
self.addrs.append(session)
session.send_cframe_connected(cframe)
#session.setup_Connect_Retry_Timer()
elif cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECTED:
# CONNECTED CFRAME
# check if already sent a CONNECT packet
logger.debug("%s:%s sent back a CONNECTED CFrame.", session.ip, session.port)
if not session:
logger.error("%s sent a CONNECTED opcode without having sent a CONNECT before. GTFO.", addr)
return
if not cframe.SessID == session.SessID or cframe.Command & CFrame.PACKET_COMMAND_POLL:
logger.error("Sent a CONNECTED packet with incorrect SessID or had COMMAND_POLL")
return
session.Full = True # fully connected
session.cancel_Connect_Retry_Timer()
elif cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_SACK:
if not session or not session.Full:
logger.error("Received a SACK packet for a non fully connected session")
return
sack_sent = False
if not cframe.NSeq == session.next_expected_seq:
logger.error("Received CFRAME (%s) does not have the same NSeq (%s). Did we miss a packet?", cframe.NSeq, session.next_expected_seq)
if not sack_sent:
session.send_cframe_sack()
sack_sent = True
2019-01-22 10:35:15 +01:00
if not session.next_send == cframe.NRecv:
2019-01-22 01:30:42 +01:00
logger.error("Received CFRAME (%s) does not have same NRcv (%s). One sent packet might have been lost.", cframe.NRecv, session.CFRAME_NRcv)
if not sack_sent:
session.send_cframe_sack()
sack_sent = True
# WIP: The bNSeq, bNRcv, optional selective acknowledgment (SACK), and optional send mask fields are then processed by using the standard rules in sections 3.1.5.2.1 through 3.1.5.2.4
# TODO: A successfully validated SACK packet SHOULD count as a valid receive and thus restart the KeepAlive timer
if cframe.Command & CFrame.PACKET_COMMAND_POLL:
if not session:
logger.error("Received a POLL packet for a non fully connected session")
return
logger.debug("Got a CFrame POLL. Replying with a SACK.")
# must send back a ACK
session.send_cframe_sack()
except Exception:
logger.error("Should have been a CFRAME but could not parse it")
traceback.print_exc()
return
def handle_new_dframe(self, addr, data):
try:
dframe = DFrame(data)
logger.debug("Received DFRAME")
session = next((x for x in self.addrs if x.ip == addr[0] and x.port == addr[1]), None)
if not session:
logger.debug("%s sent a DFRAME without having sent a CONNECT before. GTFO.", addr)
logger.debug(self.addrs)
return
if not dframe.Seq == session.next_expected_seq:
logger.error("%s unexpected SEQ. Got %s, expected %s", addr, dframe.Seq, session.next_expected_seq)
#return
if dframe.Control & DFrame.PACKET_CONTROL_END_STREAM:
if not session:
logger.error("Received a END STREAM packet for a non fully connected session")
return
# disconnect
resp = DFrame()
resp.Command = DFrame.PACKET_COMMAND_DATA | DFrame.PACKET_COMMAND_NEW_MSG | DFrame.PACKET_COMMAND_END_MSG | DFrame.PACKET_COMMAND_RELIABLE | DFrame.PACKET_COMMAND_SEQUENTIAL
resp.Control = DFrame.PACKET_CONTROL_END_STREAM
resp.Seq = dframe.NRcv
resp.NRcv = session.next_send
session.send(resp)
session.send_cframe_sack()
# TODO: broadcast session has disconnected
self.addrs.remove(session)
return
2019-01-22 10:35:15 +01:00
if dframe.Control & DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE:
session.send_dframe_keepalive()
2019-01-22 01:30:42 +01:00
session.next_expected_seq += 1
if dframe.Payload:
self.handle_game_packet(session, dframe.Payload)
else:
pass
except Exception as e:
logger.error("Could not parse DFRAME: ")
traceback.print_exc()
return
def handle_game_packet(self, session, payload):
2019-01-22 15:11:47 +01:00
#logger.debug("Payload: %s", payload)
2019-01-22 01:30:42 +01:00
if payload[0] == 0xc1:
# connect frame?
_ = payload[0:4]
_ = payload[4:8]
_ = payload[8:12]
_ = payload[12:16]
_ = payload[16:20]
_ = payload[20:52]
instanceguid = payload[52:68]
gameguid = payload[68:84]
_ = payload[84:88]
_ = payload[88:92]
name = payload[92:]
name = name.decode("utf-16-le")[0:-1]
logger.debug("%s connected!", name)
player = Player(name, session)
self.server.new_player(player)
session.send_dframe_keepalive()
r = DFrame()
r.Command = DFrame.PACKET_COMMAND_DATA | DFrame.PACKET_COMMAND_RELIABLE | DFrame.PACKET_COMMAND_SEQUENTIAL | DFrame.PACKET_COMMAND_POLL | DFrame.PACKET_COMMAND_NEW_MSG | DFrame.PACKET_COMMAND_END_MSG | DFrame.PACKET_COMMAND_USER_1
r.Control = 0x00
r.Seq = session.next_send
r.NRcv = session.next_expected_seq
payload = Packet()
payload.putLong(0xc2) # C2 = response to a C1 connection?
payload.putLong(0x00) # unknown
payload.putLong(0x00) # unknown
payload.putLong(0x50) # unknown - 80
payload.putLong(0x41) # unknown - 65
payload.putLong(self.server.maxplayers) # max players
payload.putLong(len(self.server.players)) # current players
2019-01-22 15:25:09 +01:00
payload.putByte(len((name + "\x00").encode("utf-16-le"))) # player name length
payload.write(b'\x01\x00\x00') # original: 010000 - client does not connect if wrong
2019-01-22 01:30:42 +01:00
payload.putLong(len((self.server.name + "\x00").encode("utf-16-le"))) # SERVERNAME LENGTH
for _ in range(4):
payload.putLong(0) # unknown
2019-01-22 15:25:09 +01:00
payload.putLong(204+len((name+"\x00").encode("utf-16-le"))) # 204+playerlen that's an offset FUN FACT: setting it to a bad value makes the client crash on connect
payload.putLong(0x12) # original: 0x34 - does not seem to affect the client
2019-01-22 01:30:42 +01:00
payload.write(self.guid) # instance guid
payload.write(
b"\x10\x5e\x62\xa7\x96\x1a\xd2\x11\x9a\xfc\x00\x60\x08\x45\xe5\x71") # app guid
payload.write(b'\x40\xf5\x62\xe1') # original: 40f562e1 - client does not connect if wrong
payload.putLong(0x10) # original: 0x21 - does not seem to affect the client
2019-01-22 01:30:42 +01:00
payload.putLong(0x00) # unknown
payload.putLong(0x02) # unknown - 2
payload.putLong(0x00) # original: 0x00
payload.write(b'\x01\x01\x01\x01') # original: 45f552e3 - does not seem to affect the client
2019-01-22 01:30:42 +01:00
payload.putLong(0x00)
payload.write(b'\x02\x04\x00\x00') # original: 02040000 - client does not connect if wrong
payload.putLong(0x02) # original: 0x02 - client does not connect if wrong
2019-01-22 01:30:42 +01:00
payload.putLong(0x00) # unknown
payload.putLong(0x07) # original: 0x07 unknown - 7
2019-01-22 01:30:42 +01:00
for _ in range(6):
payload.putLong(0) # unknown
payload.write(b'\x40\xf5\x62\xe1') # original: 40f562e1 - client does not connect if wrong
2019-01-22 01:30:42 +01:00
payload.putLong(0x00) # unknown
payload.write(b'\x00\x02\x00\x00') # unknown - ?
payload.putLong(0x21) # original: 0x21 unknown - 33
2019-01-22 01:30:42 +01:00
payload.putLong(0x00) # unknown - 0
payload.putLong(0x07) # original: 0x07 unknown - 7
payload.putLong(0xcc) # original: 0xcc - does not seem to affect client
2019-01-22 15:11:47 +01:00
payload.putLong(len((name + "\x00").encode("utf-16-le"))) # player name length
2019-01-22 01:30:42 +01:00
for _ in range(4):
payload.putLong(0) # unknown
payload.write((name + "\x00").encode("utf-16-le"))
payload.write(b'\xff') # map ID
2019-01-23 10:54:51 +01:00
payload.putByte(self.server.game_type) # game type
payload.putByte(self.server.teams) # teams
payload.write(b'\xcc') # original: 0x00 - does not seem to affect client
2019-01-23 10:54:51 +01:00
payload.putShort(int(self.server.version*1000))
payload.write(b'\x03\x90') # original: 0292 - does not seem to affect client
2019-01-23 10:54:51 +01:00
payload.putShort(self.server.points_per_capture)
payload.putShort(self.server.points_per_kill)
payload.write(b'\xff\xff') # original: 0000 - does not seem to affect client
2019-01-23 10:54:51 +01:00
payload.putShort(self.server.detente_time)
2019-01-22 10:35:15 +01:00
payload.write(b'\x9c\x53\xf4\xdf') # Seems to be a checksum of current map OR linked to the number of chars in the map name
2019-01-22 01:30:42 +01:00
payload.write(self.server.currentmap.mapname.encode("ascii"))
2019-01-22 10:35:15 +01:00
payload.write(b'\x00' * (32 - len(self.server.currentmap.mapname)))
2019-01-22 01:30:42 +01:00
payload.write((self.server.name + "\x00").encode("utf-16-le"))
r.Payload = payload.getvalue()
session.send(r)
2019-01-22 10:35:15 +01:00
elif payload[0] == 0xc3:
player = self.get_player(session)
2019-01-22 15:11:47 +01:00
if not player:
return
player.session.send_cframe_sack()
self.server.broadcast_message("%s: sorry bro, you're fucked." % player.name)
2019-01-22 10:35:15 +01:00
player.session.send_gamedata(b'\x3c\x56\xab\x31\x96\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
player.session.send_gamedata(b'\x01\x02\x57\xab\xa1\x96\x56\xab\x31\x96\x0e\x01\x00\x00\x00')
2019-01-22 15:11:47 +01:00
"""player.session.send_gamedata(b'\x3d\x00\x5b\x53\x65\x72\x76\x65\x72\x5d') # [SERVER]
2019-01-22 10:35:15 +01:00
player.session.send_gamedata(b'\x3d\x01'+player.name.encode("ascii")+b"\x00") # playername
player.session.send_gamedata(b'\x0f\x56\xab\x31\x96\x06\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x10\x02\x56\xab\x31\x96\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x29\x28\x00\x00\x80\x32\x00\x00\x80\x05\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x39\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x39\x01\x02\x01\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x0f\x56\xab\x31\x96\x07\x00\x00\x00\x00\x00\x00\x00\x00') # unknown
player.session.send_gamedata(b'\x0a\x12\x00\x00\x00\x00\x28\x10\x03\x10\x12\x00\x00\x00\xc1\xff\x41\xff\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x0f\x56\xab\x31\x96\x08\x00\x00\x00\x00\x00\x00\x00\x00', acknow=True) # unknown
2019-01-22 15:11:47 +01:00
player.session.send_gamedata(b'\x0a\x12\x00\x00\x00\x00\x2c\x10\x03\x10\x12\x00\x00\x00\x08\x00\xb6\x01\x0a\x14\x00\x00\x00\x00\x2d\x10\x03\x01\x12\x00\x00\x00\xd4\xfc\x9b\xfd\x00\x00\x00', acknow=True) # unknown
#\x00
player.session.send_gamedata(b'\x2f\x02\x00\x00\xbe\x00\x00\x00\x00\x00\x00\x00\x00\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x0a\x12\x00\x00\x00\x00\x2e\x10\x03\x10\x12\x00\x00\x00\xa8\xfd\x2d\x03\x0a\x14\x00\x00\x00\x00\x2e\x10\x03\x01\x12\x00\x00\x00\x60\xfd\x14\x03\x00\x00\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x0a\x14\x00\x00\x00\x00\x28\x10\x03\x01\x12\x00\x00\x00\xbf\xff\x3f\xff\x00\x00\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x0a\x12\x00\x00\x00\x00\x2a\x10\x03\x10\x12\x00\x00\x00\x73\x01\x42\x02\x0a\x14\x00\x00\x00\x00\x2a\x10\x03\x01\x12\x00\x00\x00\xbe\x01\x8c\x02\x00\x00\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x2f\x02\x00\x00\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00',acknow=True) # unknown
player.session.send_gamedata(b'\x0a\x14\x00\x00\x00\x00\x2c\x10\x03\x01\x12\x00\x00\x00\x06\x00\xb5\x01\x00\x00\x00', acknow=True) # unknown
player.session.send_gamedata(b'\x0a\x12\x00\x00\x00\x00\x2e\x10\x03\x10\x12\x00\x00\x00\xa4\xfd\x2c\x03\x00',acknow=True) # unknown
player.session.send_gamedata(b'\x2f\x02\x00\x00\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00', acknow=True) # unknown
"""
2019-01-22 01:30:42 +01:00
def send_packet(self, addr, packet):
self.socket.sendto(packet.getvalue(), addr)
logger.debug("%s:%s < %s", addr[0], addr[1], packet.getvalue().hex())
def get_session(self, iporaddr, port=None):
if not port:
return next((x for x in self.addrs if x.ip == iporaddr[0] and x.port == iporaddr[1]), None)
else:
return next((x for x in self.addrs if x.ip == iporaddr and x.port == port), None)
2019-01-22 10:35:15 +01:00
def get_player(self, iporaddr, port=None):
if type(iporaddr) == Session:
return next((x for x in self.server.players if x.session.ip == iporaddr.ip and x.session.port == iporaddr.port), None)
if not port:
return next((x for x in self.server.players if x.session.ip == iporaddr[0] and x.session.port == iporaddr[1]), None)
else:
return next((x for x in self.server.players if x.session.ip == iporaddr and x.session.port == port), None)