Initial commit

This commit is contained in:
Amazed 2019-01-22 01:30:42 +01:00
commit 277ada9a79
18 changed files with 916 additions and 0 deletions

75
dpnet/CFrame.py Normal file
View File

@ -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

80
dpnet/DFrame.py Normal file
View File

@ -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

49
dpnet/EnumQuery.py Normal file
View File

@ -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

94
dpnet/EnumResponse.py Normal file
View File

@ -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

0
dpnet/__init__.py Normal file
View File

18
dpnet/netclient.py Normal file
View File

@ -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)

305
dpnet/netserver.py Normal file
View File

@ -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: <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
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)

50
dpnet/packet.py Normal file
View File

@ -0,0 +1,50 @@
import io
import struct
class Packet(io.BytesIO):
def putByte(self, val):
self.write(struct.pack('<B', val))
def getByte(self):
return struct.unpack('<B', self.read(1))[0]
def putBytes(self, bytes):
for byte in bytes:
self.write(struct.pack('<B', byte))
def getBytes(self, num):
return bytes(struct.unpack('<'+str(num)+'B', self.read(num)))
def putShort(self, val):
self.write(struct.pack('<H', val))
def getShort(self):
return struct.unpack('<H', self.read(2))[0]
def putLong(self, val):
self.write(struct.pack('<l', val))
def putULong(self, val):
self.write(struct.pack('<L', val))
def getLong(self):
return struct.unpack('<l', self.read(4))[0]
def getULong(self):
return struct.unpack('<L', self.read(4))[0]
def putLongLong(self, val):
self.write(struct.pack('<Q', val))
def getLongLong(self):
return struct.unpack('<Q', self.read(8))[0]
def putFloat(self, val):
self.write(struct.pack('<f', val))
def getFloat(self):
return struct.unpack('<f', self.read(4))[0]
def putString(self, val):
self.write(val + '\x00')

84
dpnet/session.py Normal file
View File

@ -0,0 +1,84 @@
from threading import Timer
from utils.logger import setup_logger
from dpnet.CFrame import CFrame
from dpnet.DFrame import DFrame
logger = setup_logger(__name__)
class Session:
def __init__(self, serversock):
self.serversock = serversock
self.ip = ""
self.port = 0
self.SessID = 0
self.next_expected_seq = 0
self.next_send = 0
self.Full = False
self.LastMsgID = 0
self.connect_retry_timer_num = 0
self.connect_retry_timer = False
def send(self, packet):
rawpacket = packet.to_packet()
rawbytes = rawpacket.getvalue()
self.serversock.sendto(rawbytes, (self.ip, self.port))
logger.debug("%s:%s < %s", self.ip, self.port, rawbytes.hex())
if len(rawbytes) >= 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)

0
giants/__init__.py Normal file
View File

6
giants/entity.py Normal file
View File

@ -0,0 +1,6 @@
class Entity:
def __init__(self):
self.x = 0
self.y = 0
self.z = 0
self.o = 0

11
giants/map.py Normal file
View File

@ -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

45
giants/masterserver.py Normal file
View File

@ -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())

10
giants/player.py Normal file
View File

@ -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

9
sendenum.py Normal file
View File

@ -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("<R", data.hex())

63
server.py Normal file
View File

@ -0,0 +1,63 @@
from giants.map import Map
from giants.player import Player
from dpnet.netserver import Netserver
from dpnet.session import Session
from dpnet.DFrame import DFrame
import socket
class Server:
def __init__(self, **kwargs):
self.listen_ip = kwargs.get("ip", "0.0.0.0")
self.listen_port = kwargs.get("port", 19711)
fake_session = Session(socket.socket(socket.AF_INET, socket.SOCK_DGRAM))
fake_session.ip = "127.0.0.1"
fake_session.port = 3333
self.players = [Player("Amazed4", fake_session)]
self.currentmap = kwargs.get("map", Map("wow rly"))
self.maxplayers = kwargs.get("maxplayers", 20)
self.name = kwargs.get("name", "Default Server Name")
self.accept_new_players = True
# events
self._on_new_player = []
self._on_new_map = []
def new_player(self, player):
self.players.append(player)
for func in self._on_new_player:
func(player)
def change_map(self, mapname):
for func in self._on_new_map:
func(mapname)
def on_new_player(self, func):
self._on_new_player.append(func)
return func
def broadcast_message(self, text):
for player in self.players:
player.session.send_gamedata(b'\x35\x80\x81'+text.encode("ascii")+b'\x00\x00')
def on_new_map(self, func):
self._on_new_map.append(func)
return func
def run(self):
return Netserver(self).run()
if __name__ == '__main__':
server = Server(name="giantsd", map=Map("MvMvMvM - Infinity7 - 4Teams"), maxplayers=10)
@server.on_new_player
def new_player(player):
#print("A NEW PLAYER HAS JOINED: %s" % player)
pass
@server.on_new_map
def new_map(mapname):
pass
server.run() # blocking

0
utils/__init__.py Normal file
View File

17
utils/logger.py Normal file
View File

@ -0,0 +1,17 @@
import logging
def setup_logger(name):
#_fh = logging.FileHandler("cerqual-deploy.log")
#_fh.setLevel("INFO")
_ch = logging.StreamHandler()
_ch.setLevel("DEBUG")
_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
_ch.setFormatter(_formatter)
#_fh.setFormatter(_formatter)
logger = logging.getLogger(name)
logger.addHandler(_ch)
#logger.addHandler(_fh)
logger.setLevel("DEBUG")
logger.debug("Logger initialized")
return logger