commit 277ada9a79ea124c6bbd49664178dbc5a9d26f75 Author: HipsterCat Date: Tue Jan 22 01:30:42 2019 +0100 Initial commit diff --git a/dpnet/CFrame.py b/dpnet/CFrame.py new file mode 100644 index 0000000..d653781 --- /dev/null +++ b/dpnet/CFrame.py @@ -0,0 +1,75 @@ +from .packet import Packet +import random + + +class CFrame: + PACKET_COMMAND_FRAME = 0x80 + PACKET_COMMAND_POLL = 0x08 + FRAME_EXOPCODE_CONNECT = 0x01 + FRAME_EXOPCODE_CONNECTED = 0x02 + FRAME_EXOPCODE_CONNECTED_SIGNED = 0x03 + FRAME_EXOPCODE_HARD_DISCONNECT = 0x04 + FRAME_EXOPCODE_SACK = 0x06 + + SACK_FLAGS_RESPONSE = 0x01 + SACK_FLAGS_SACK_MASK1 = 0x02 + SACK_FLAGS_SACK_MASK2 = 0x04 + SACK_FLAGS_SEND_MASK1 = 0x08 + SACK_FLAGS_SEND_MASK2 = 0x10 + + def __init__(self, packet=None): + self.Command = CFrame.PACKET_COMMAND_FRAME | CFrame.PACKET_COMMAND_POLL + self.ExtOpCode = CFrame.FRAME_EXOPCODE_CONNECT + self.MsgID = 0 + self.RspId = 0 + self.Flags = 1 + self.Retry = 0 + self.NSeq = 0 + self.NRecv = 0 + self.CurrentProtocolVersion = 0x00010006 + self.SessID = random.getrandbits(32) + self.Timestamp = random.getrandbits(32) + + if packet: + self.parse(packet) + + def parse(self, packet): + packet.seek(0) + self.Command = packet.getByte() + self.ExtOpCode = packet.getByte() + if self.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECTED_SIGNED: + raise Exception("CONNECTED_SIGNED is not implemented") + if self.ExtOpCode == CFrame.FRAME_EXOPCODE_SACK: + self.Flags = packet.getByte() + self.Retry = packet.getByte() + self.NSeq = packet.getByte() + self.NRecv = packet.getByte() + packet.getByte() # padding + packet.getByte() # padding + self.Timestamp = packet.getULong() + else: + self.MsgID = packet.getByte() + self.RspId = packet.getByte() + self.CurrentProtocolVersion = packet.getLong() + self.SessID = packet.getLong() + self.Timestamp = packet.getULong() + + def to_packet(self): + packet = Packet() + packet.putByte(self.Command) + packet.putByte(self.ExtOpCode) + if self.ExtOpCode == CFrame.FRAME_EXOPCODE_SACK: + packet.putByte(self.Flags) + packet.putByte(self.Retry) + packet.putByte(self.NSeq) + packet.putByte(self.NRecv) + packet.putByte(0) # padding + packet.putByte(0) # padding + packet.putULong(self.Timestamp) + else: + packet.putByte(self.MsgID) + packet.putByte(self.RspId) + packet.putLong(self.CurrentProtocolVersion) + packet.putLong(self.SessID) + packet.putULong(self.Timestamp) + return packet diff --git a/dpnet/DFrame.py b/dpnet/DFrame.py new file mode 100644 index 0000000..77f8cb0 --- /dev/null +++ b/dpnet/DFrame.py @@ -0,0 +1,80 @@ +from .packet import Packet + + +class DFrame: + PACKET_COMMAND_DATA = 0x01 + PACKET_COMMAND_RELIABLE = 0x02 + PACKET_COMMAND_SEQUENTIAL = 0x04 + PACKET_COMMAND_POLL = 0x08 + PACKET_COMMAND_NEW_MSG = 0x10 + PACKET_COMMAND_END_MSG = 0x20 + PACKET_COMMAND_USER_1 = 0x40 + PACKET_COMMAND_USER_2 = 0x80 + + PACKET_CONTROL_RETRY = 0x01 + PACKET_CONTROL_KEEPALIVE_OR_CORRELATE = 0x02 + PACKET_CONTROL_COALESCE = 0x04 + PACKET_CONTROL_END_STREAM = 0x08 + PACKET_CONTROL_SACK1 = 0x10 + PACKET_CONTROL_SACK2 = 0x20 + PACKET_CONTROL_SEND1 = 0x40 + PACKET_CONTROL_SEND2 = 0x80 + + def __init__(self, packet=None): + self.Command = DFrame.PACKET_COMMAND_DATA + self.Control = 0x00 + self.Seq = 0 + self.NRcv = 0 + self.SACKMask1 = b'' + self.SACKMask2 = b'' + self.SendMask1 = b'' + self.SendMask2 = b'' + self.Signature = b'' + self.SessID = b'' + self.Payload = b'' + + if packet: + self.parse(packet) + + def parse(self, packet): + packet.seek(0) + self.Command = packet.getByte() + self.Control = packet.getByte() + self.Seq = packet.getByte() + self.NRcv = packet.getByte() + if self.Control & DFrame.PACKET_CONTROL_SACK1: + self.SACKMask1 = packet.getLong() + if self.Control & DFrame.PACKET_CONTROL_SACK2: + self.SACKMask2 = packet.getLong() + if self.Control & DFrame.PACKET_CONTROL_SEND1: + self.SendMask1 = packet.getLong() + if self.Control & DFrame.PACKET_CONTROL_SEND2: + self.SendMask2 = packet.getLong() + + # TODO: handle signature + + if self.Control & DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE: + self.SessID = packet.getLong() + self.Payload = packet.read() + + def to_packet(self): + packet = Packet() + packet.putByte(self.Command) + packet.putByte(self.Control) + packet.putByte(self.Seq) + packet.putByte(self.NRcv) + if self.Control & DFrame.PACKET_CONTROL_SACK1: + packet.putLong(self.SACKMask1) + if self.Control & DFrame.PACKET_CONTROL_SACK2: + packet.putLong(self.SACKMask2) + if self.Control & DFrame.PACKET_CONTROL_SEND1: + packet.putLong(self.SendMask1) + if self.Control & DFrame.PACKET_CONTROL_SEND2: + packet.putLong(self.SendMask2) + if self.Signature: + packet.putLongLong(self.Signature) + if self.Control & DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE: + packet.putLong(self.SessID) + packet.putBytes(self.Payload) + + return packet diff --git a/dpnet/EnumQuery.py b/dpnet/EnumQuery.py new file mode 100644 index 0000000..19600e1 --- /dev/null +++ b/dpnet/EnumQuery.py @@ -0,0 +1,49 @@ +from .packet import Packet +import random + + +class EnumQuery: + LEAD = 0x00 + COMMAND = 0x02 + NO_APPLICATION_GUID = 0x02 + HAS_APPLICATION_GUID = 0x01 + + def __init__(self, packet=None): + self.Lead = EnumQuery.LEAD + self.Command = EnumQuery.COMMAND + self.Payload = random.getrandbits(16) + self.Type = None + self.ApplicationGUID = None + self.ApplicationPayload = None + + if packet: + self.parse(packet) + + def parse(self, packet): + self.Lead = EnumQuery.LEAD + self.Command = EnumQuery.COMMAND + self.Payload = packet.getShort() + self.Type = packet.getByte() + if self.Type == EnumQuery.HAS_APPLICATION_GUID: + self.ApplicationGUID = packet.getBytes(16) + self.ApplicationPayload = packet.read() + elif self.Type == EnumQuery.NO_APPLICATION_GUID: + self.ApplicationPayload = packet.read() + else: + raise Exception("EnumQuery type was incorrect: "+self.Type) + + def to_packet(self): + packet = Packet() + packet.putByte(EnumQuery.LEAD) + packet.putByte(EnumQuery.COMMAND) + packet.putShort(self.Payload) + if self.ApplicationGUID: + packet.putByte(EnumQuery.HAS_APPLICATION_GUID) + packet.putBytes(self.ApplicationGUID) + else: + packet.putByte(EnumQuery.NO_APPLICATION_GUID) + + if self.ApplicationPayload: + packet.putBytes(self.ApplicationPayload) + + return packet diff --git a/dpnet/EnumResponse.py b/dpnet/EnumResponse.py new file mode 100644 index 0000000..308fa56 --- /dev/null +++ b/dpnet/EnumResponse.py @@ -0,0 +1,94 @@ +from .packet import Packet + + +class EnumResponse: + LEAD = 0x00 + COMMAND = 0x03 + + def __init__(self, packet=None): + self.Lead = EnumResponse.LEAD + self.Command = EnumResponse.COMMAND + self.Payload = b'' + self.ReplyOffset = b'' + self.ReplySize = b'' + self.ApplicationDescSize = b'' + self.ApplicationDescFlags = b'' + self.MaxPlayers = b'' + self.CurrentPlayers = b'' + self.SessionNameOffset = b'' + self.SessionNameSize = b'' + self.PasswordOffset = b'' + self.PasswordSize = b'' + self.ReservedDataOffset = b'' + self.ReservedDataSize = b'' + self.ApplicationReservedDataOffset = b'' + self.ApplicationReservedDataSize = b'' + self.ApplicationInstanceGUID = b'' + self.ApplicationGUID = b'' + self.SessionName = b'' + self.Password = b'' + self.ReservedData = b'' + self.ApplicationReservedData = b'' + self.ApplicationData = b'' + + if packet: + self.parse(packet) + + def parse(self, packet): + self.Lead = EnumResponse.LEAD + self.Command = EnumResponse.COMMAND + self.Payload = packet.getShort() + + def to_packet(self): + varpos = 88 + self.SessionNameSize = len(self.SessionName.encode("utf-16")) + self.SessionNameOffset = varpos if self.SessionName else 0 + varpos += self.SessionNameSize if self.SessionName else 0 + + self.PasswordSize = len(self.Password) + self.PasswordOffset = varpos if self.Password else 0 + varpos += self.PasswordSize if self.Password else 0 + + self.ReservedDataSize = len(self.ReservedData) + self.ReservedDataOffset = varpos if self.ReservedData else 0 + varpos += self.ReservedDataSize if self.ReservedData else 0 + + self.ApplicationReservedDataSize = len(self.ApplicationReservedData) + self.ApplicationReservedDataOffset = varpos if self.ApplicationReservedData else 0 + varpos += self.ApplicationReservedDataSize if self.ApplicationReservedData else 0 + + self.ReplySize = len(self.ApplicationData) + self.ReplyOffset = varpos if self.ApplicationData else 0 + varpos += self.ReplySize if self.ApplicationData else 0 + + packet = Packet() + packet.putByte(EnumResponse.LEAD) + packet.putByte(EnumResponse.COMMAND) + packet.putShort(self.Payload) + packet.putLong(self.ReplyOffset) + packet.putLong(self.ReplySize) + packet.putLong(self.ApplicationDescSize) + packet.putLong(self.ApplicationDescFlags) + packet.putLong(self.MaxPlayers) + packet.putLong(self.CurrentPlayers) + packet.putLong(self.SessionNameOffset) + packet.putLong(self.SessionNameSize) + packet.putLong(self.PasswordOffset) + packet.putLong(self.PasswordSize) + packet.putLong(self.ReservedDataOffset) + packet.putLong(self.ReservedDataSize) + packet.putLong(self.ApplicationReservedDataOffset) + packet.putLong(self.ApplicationReservedDataSize) + packet.putBytes(self.ApplicationInstanceGUID) + packet.putBytes(self.ApplicationGUID) + if self.SessionName: + packet.putBytes((self.SessionName+'\x00').encode("utf-16-le")) + if self.Password: + packet.putBytes(self.Password) + if self.ReservedData: + packet.putBytes(self.ReservedData) + if self.ApplicationReservedData: + packet.putBytes(self.ApplicationReservedData) + if self.ApplicationData: + packet.putBytes(self.ApplicationData) + return packet diff --git a/dpnet/__init__.py b/dpnet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dpnet/netclient.py b/dpnet/netclient.py new file mode 100644 index 0000000..da9c2fc --- /dev/null +++ b/dpnet/netclient.py @@ -0,0 +1,18 @@ +from .packet import Packet +from .EnumQuery import EnumQuery +import socket +import threading + + +class Netclient: + def __init__(self, ip, port): + self.ip = ip + self.port = port + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP + + def send(self, packet): + self.socket.sendto(packet.getvalue(), (self.ip, self.port)) + print("R>", packet.getvalue().hex()) + + def receive(self): + return self.socket.recv(4096) \ No newline at end of file diff --git a/dpnet/netserver.py b/dpnet/netserver.py new file mode 100644 index 0000000..4c588a7 --- /dev/null +++ b/dpnet/netserver.py @@ -0,0 +1,305 @@ +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\xdd' # 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.CFRAME_NRcv == 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 + + 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.write(b'\x0e\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(0xda) # unknown - 218 - or 0xe2 + 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(0x0e) # unknown - 6 + 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'\x00\x04\x00') + payload.write(b'\xd9\x05') # game version + payload.write(b'\x02\x92\x05\x00\x01\x00\x00\x00\x00\x00') # Unknown + payload.write(b'\x9c\x53\xf4\xde') # Seems to be a checksum of current map + payload.write(self.server.currentmap.mapname.encode("ascii")) + payload.putLong(0x00) # ?? + payload.write((self.server.name + "\x00").encode("utf-16-le")) + + r.Payload = payload.getvalue() + session.send(r) + + if payload[0] == 0xc3: + self.server.broadcast_message(name + " is a dick.") + + + 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) diff --git a/dpnet/packet.py b/dpnet/packet.py new file mode 100644 index 0000000..1bd83f1 --- /dev/null +++ b/dpnet/packet.py @@ -0,0 +1,50 @@ +import io +import struct + + +class Packet(io.BytesIO): + def putByte(self, val): + self.write(struct.pack('= 4 and rawbytes[0] & DFrame.PACKET_COMMAND_DATA: + self.next_send += 1 + + def setup_Connect_Retry_Timer(self): + time = 0.2 * pow(2, self.connect_retry_timer_num) + if time > 5: + return False + t = Timer(time, self._send_connected) + t.start() + return t + + def cancel_Connect_Retry_Timer(self): + if self.connect_retry_timer: + self.connect_retry_timer.cancel() + self.connect_retry_timer_num = 0 + + def _send_connected(self): + response = CFrame() + response.ExtOpCode = CFrame.FRAME_EXOPCODE_CONNECTED + response.RspId = self.LastMsgID + response.SessID = self.SessID + logger.debug("Timer sending CONNECTED") + self.send(response) + self.connect_retry_timer = self.setup_Connect_Retry_Timer() + + def send_gamedata(self, bPayload): + dframe = DFrame() + dframe.Command = DFrame.PACKET_COMMAND_DATA | DFrame.PACKET_COMMAND_POLL | DFrame.PACKET_COMMAND_RELIABLE | DFrame.PACKET_COMMAND_SEQUENTIAL | DFrame.PACKET_COMMAND_NEW_MSG | DFrame.PACKET_COMMAND_END_MSG + dframe.Control = 0x00 + dframe.Seq = self.next_send + dframe.NRcv = self.next_expected_seq + dframe.Payload = bPayload + self.send(dframe) + + def send_cframe_connected(self, connect): + response = CFrame() + response.ExtOpCode = CFrame.FRAME_EXOPCODE_CONNECTED + response.RspId = connect.MsgID + response.SessID = connect.SessID + self.send(response) + + def send_cframe_sack(self): + r = CFrame() + r.Command = CFrame.PACKET_COMMAND_FRAME + r.ExtOpCode = CFrame.FRAME_EXOPCODE_SACK + r.Flags = CFrame.SACK_FLAGS_RESPONSE + r.NRecv = self.next_expected_seq + r.NSeq = self.next_send + self.send(r) + + def send_dframe_keepalive(self): + dframe = DFrame() + dframe.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.Control = DFrame.PACKET_CONTROL_KEEPALIVE_OR_CORRELATE + dframe.Seq = self.next_send + dframe.NRcv = self.next_expected_seq + dframe.SessID = self.SessID + self.send(dframe) diff --git a/giants/__init__.py b/giants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/giants/entity.py b/giants/entity.py new file mode 100644 index 0000000..226bb25 --- /dev/null +++ b/giants/entity.py @@ -0,0 +1,6 @@ +class Entity: + def __init__(self): + self.x = 0 + self.y = 0 + self.z = 0 + self.o = 0 diff --git a/giants/map.py b/giants/map.py new file mode 100644 index 0000000..0380516 --- /dev/null +++ b/giants/map.py @@ -0,0 +1,11 @@ +class Map: + def __init__(self, mapname="Unknown mapname"): + self.mapname = mapname + self.checksum = None + + def load_map(self, mapname): + self.checksum = Map.checksum(mapname) + + @staticmethod + def checksum(mapname): + return 1 diff --git a/giants/masterserver.py b/giants/masterserver.py new file mode 100644 index 0000000..ac99863 --- /dev/null +++ b/giants/masterserver.py @@ -0,0 +1,45 @@ +import socket +from dpnet.packet import Packet + + +class MasterServer: + def __init__(self, server): + self.server = server + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.masterserverip = "gckms.no-ip.org" + self.masterserverport = 27900 + + def register_and_run(self): + self.socket.bind((self.server.listen_ip, 8911)) + print("Listening to " + self.server.listen_ip + ":" + str(8911)) + print("Registering to Master Server") + self.register() + self.keepalive() + self.handle_packets() + + def register(self): + packet = Packet() + packet.write("019711".encode("ascii")) + self.socket.sendto(packet.getvalue(), (self.masterserverip, self.masterserverport)) + + def keepalive(self): + packet = Packet() + packet.write("119711".encode("ascii")) + self.socket.sendto(packet.getvalue(), (self.masterserverip, self.masterserverport)) + + def handle_packets(self): + while True: + data, addr = self.socket.recvfrom(1024) + print(addr[0], ":", addr[1], ">", data.hex()) + data = Packet(data) + command = data.read().decode("ascii") + print("Received command:", command) + if command == "\\status\\": + resp = Packet() + respstr = "\\gamename\\giants\\gamever\\1.497\\hostname\\" + self.server.name + "\\hostport\\" + str( + self.server.listen_port) + "\\mapname\\" + self.server.currentmap.mapname + "\\gametype\\Capture Smartie with full base\\numplayers\\" + str( + len(self.server.players)) + "\\maxplayers\\" + str( + self.server.maxplayers) + "\\gamemode\\openplaying\\timelimit\\60\\fraglimit\\0\\teamfraglimit\\0\\firstbasecomplete\\0\\player_0\\Amazed\\frags_0\\0\\deaths_0\\0\\ping_0\\0\\team_0\\Green\\final\\\\queryid\\2.1" + resp.putBytes(respstr.encode("ascii")) + self.socket.sendto(resp.getvalue(), addr) + print(addr[0], ":", addr[1], "<", resp.getvalue().hex()) \ No newline at end of file diff --git a/giants/player.py b/giants/player.py new file mode 100644 index 0000000..1a45479 --- /dev/null +++ b/giants/player.py @@ -0,0 +1,10 @@ +from .entity import Entity + + +class Player(Entity): + def __init__(self, name, session): + super().__init__() + self.name = name + self.session = session + self.team = 0 + self.score = 0 diff --git a/sendenum.py b/sendenum.py new file mode 100644 index 0000000..d348a9e --- /dev/null +++ b/sendenum.py @@ -0,0 +1,9 @@ +from dpnet.EnumQuery import EnumQuery +from dpnet.netclient import Netclient + +client = Netclient("163.158.182.243", 19711) +eq = EnumQuery() +eq.ApplicationGUID = b"\x10\x5e\x62\xa7\x96\x1a\xd2\x11\x9a\xfc\x00\x60\x08\x45\xe5\x71" +client.send(eq.to_packet()) +data = client.receive() +print("