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 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() #ms = MasterServer(self.server) #statsserverthread = threading.Thread(target=ms.register_and_run) #statsserverthread.start() 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' er.ApplicationReservedData += b'\xd9\x05' # game version er.ApplicationReservedData += b'\x02\x92\x05\x00\x01\x00\x00\x00\x00\x00' # Unknown er.ApplicationReservedData += b'\x9c\x53\xf4\xde' # Seems to be a checksum of current map # \x00\x00\x00\x00: [None] # \x00\x00\x00\x01: # \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 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.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 if dframe.Control & DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE: session.send_dframe_keepalive() 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): #logger.debug("Payload: %s", payload) 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(0x14) # max players payload.putLong(0x02) # current players payload.putByte(len((name + "\x00").encode("utf-16-le"))) # player name length payload.write(b'\x01\x00\x00') # unknown - 3585 - or \x16\x01\x00\x00 payload.putLong(len((self.server.name + "\x00").encode("utf-16-le"))) # SERVERNAME LENGTH for _ in range(4): payload.putLong(0) # unknown 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(0x34) # unknown - 52 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') payload.putLong(0x21) # unknown - 33 payload.putLong(0x00) # unknown payload.putLong(0x02) # unknown - 2 payload.putLong(0x00) payload.write(b'\x45\xf5\x52\xe3') payload.putLong(0x00) payload.write(b'\x02\x04\x00\x00') # unknown payload.putLong(0x02) # unknown - 2 payload.putLong(0x00) # unknown payload.putLong(0x07) # unknown - 7 for _ in range(6): payload.putLong(0) # unknown payload.write(b'\x40\xf5\x62\xe1') # unknown - 1089823457 payload.putLong(0x00) # unknown payload.write(b'\x00\x02\x00\x00') # unknown - ? payload.putLong(0x21) # unknown - 33 payload.putLong(0x00) # unknown - 0 payload.putLong(0x07) # unknown - 7 payload.putLong(0xcc) # unknown - 204 payload.putLong(len((name + "\x00").encode("utf-16-le"))) # player name length for _ in range(4): payload.putLong(0) # unknown payload.write((name + "\x00").encode("utf-16-le")) payload.write(b'\xff') # map ID payload.write(b'\x01\x01\x00') payload.write(b'\xd9\x05') # game version payload.write(b'\x02\x92') # unknown payload.write(b'\x05\x00\x01\x00\x00\x00\x00\x00') # Unknown 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 payload.write(self.server.currentmap.mapname.encode("ascii")) payload.write(b'\x00' * (32 - len(self.server.currentmap.mapname))) #payload.putLong(0x00) # ?? payload.write((self.server.name + "\x00").encode("utf-16-le")) r.Payload = payload.getvalue() session.send(r) elif payload[0] == 0xc3: player = self.get_player(session) if not player: return player.session.send_cframe_sack() self.server.broadcast_message("%s: sorry bro, you're fucked." % player.name) 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') """player.session.send_gamedata(b'\x3d\x00\x5b\x53\x65\x72\x76\x65\x72\x5d') # [SERVER] 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 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 """ 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) 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)