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 import struct from _datetime import datetime from utils.logger import setup_logger from giants.player import Player, PlayerPhases import time from giants import APPLICATION_GUID from .DN_MSG_INTERNAL_SEND_CONNECT_INFO import DN_MSG_INTERNAl_SEND_CONNECT_INFO import asyncio logger = setup_logger(__name__) class Netserver(asyncio.DatagramProtocol): def __init__(self, server): self.remotesocket = None self.statssocket = None self.server = server self.addrs = [] self.guid = uuid.uuid4().bytes self.incoming_packets = [] super().__init__() def connection_made(self, transport): logger.debug("Connection made") self.remotesocket = transport def run(self): logger.debug("Run") return 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) networkthread = threading.Thread(target=self.handle_packets) networkthread.start() if self.server.register_with_ms: ms = MasterServer(self.server) ms.register() while self.server.running: start = datetime.now() self.read_packets() self.server.update() self.send_packets() sleep_ms = (start-datetime.now()).total_seconds()+1/self.server.ticks if sleep_ms > 0: time.sleep(sleep_ms) networkthread.join() def read_packets(self): pass def send_packets(self): pass def datagram_received(self, data, addr): loop = asyncio.get_event_loop() loop.create_task(self.handle_new_packet(addr, data)) async def handle_new_packet(self, addr, bData): pPacket = Packet(bData) first = pPacket.getByte() if len(bData) >= 4 and first & DFrame.PACKET_COMMAND_DATA: await self.handle_new_dframe(addr, pPacket) elif len(bData) >= 12 and first & CFrame.PACKET_COMMAND_FRAME: await self.handle_new_cframe(addr, pPacket) elif first == EnumQuery.LEAD or first == EnumResponse.LEAD: logger.debug("%s:%s > %s (ENUM)", addr[0], addr[1], bData.hex()) await self.handle_new_enum(addr, pPacket) else: logger.error("Unknown frame received") return async 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 er.ApplicationReservedData += struct.pack(" # \x9c\x53\xf4\xdd: Three Way Island - Canyons # \x1e\xe9\x39\xe1: Still Winter # \x9f\xb2\x42\xec: test er.ApplicationReservedData += self.server.currentmap.mapname.encode("ascii")''' appdata = Packet() appdata.putByte(0xff) # map ID appdata.putByte(self.server.game_type) # game type appdata.putByte(self.server.teams) # teams appdata.write(b'\x00') # original: 0x00 - does not seem to affect client appdata.putShort(int(self.server.version * 1000)) appdata.write(b'\x02\x92') # original: 0292 - does not seem to affect client appdata.putShort(self.server.points_per_capture) appdata.putShort(self.server.points_per_kill) appdata.write(b'\x00\x00') # original: 0000 - does not seem to affect client appdata.putShort(self.server.detente_time) appdata.write(self.server.currentmap.checksum) # Seems to be a checksum of current map OR linked to the number of chars in the map name appdata.write(self.server.currentmap.mapname.encode("ascii")) appdata.write(b'\x00' * (32 - len(self.server.currentmap.mapname))) er.ApplicationReservedData = appdata.getvalue() logger.debug("Current map: %s, checksum: %s", self.server.currentmap.mapname, self.server.currentmap.checksum) 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) async def handle_new_cframe(self, addr, data): try: cframe = CFrame(data) session = self.get_session(addr) player = self.get_player(session) if session else None if cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECT: logger.debug("%s:%s > %s (CFRAME CONNECT)", addr[0], addr[1], data.getvalue().hex()) # 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.remotesocket) session.SessID = cframe.SessID session.ip = addr[0] session.port = addr[1] await session.lock.acquire() self.addrs.append(session) session.send_cframe_connected(cframe) player = Player("Unknown", session) def bxor(b1, b2): # use xor for bytes result = bytearray() for b1, b2 in zip(b1, b2): result.append(b1 ^ b2) return result player.id = struct.unpack(">L", bxor(struct.pack(" %s (CFRAME CONNECTED)", addr[0], addr[1], data.getvalue().hex()) # 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() player.phase = PlayerPhases.CFRAME_CONNECTED session.send_dframe_keepalive() elif cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_SACK: logger.debug("%s:%s > %s (CFRAME SACK)", addr[0], addr[1], data.getvalue().hex()) 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 if not session.next_send == cframe.NRecv: logger.error("Received CFRAME (%s) does not have same NRcv (%s). One sent packet might have been lost.", cframe.NRecv, session.next_send) if not sack_sent: session.send_cframe_sack() sack_sent = True # release lock for new packet to be sent if session.lock.locked(): session.lock.release() # 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 async 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 if dframe.Control & DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE: pass #session.send_dframe_keepalive() if dframe.Command & DFrame.PACKET_COMMAND_POLL: #logger.debug("Sending SACK") session.send_cframe_sack() pass if session.next_expected_seq == 255: session.next_expected_seq = 0 else: session.next_expected_seq += 1 if dframe.Payload: await self.handle_game_packet(session, dframe.Payload) else: pass except Exception as e: logger.error("Could not parse DFRAME: ") traceback.print_exc() return async def handle_game_packet(self, session, payload): player = self.get_temp_player(session) logger.debug("%s:%s > %s (GAMEDATA)", session.ip, session.port, payload.hex()) #logger.debug("OPCODE: %s VALUES: %s", struct.pack(" %s (GAMEDATA)", session.ip, session.port, appdata.getvalue().hex()) player.phase = PlayerPhases.DN_SEND_CONNECT_INFO elif payload[0] == 0xc3: player = self.get_temp_player(session) if not player or not player.phase == PlayerPhases.DN_SEND_CONNECT_INFO: return player.phase = PlayerPhases.DN_ACK_CONNECT_INFO await self.server.add_player(player) #player.session.send_cframe_sack() await player.session.send_gamedata(b'\x3c'+struct.pack("