Initial commit
This commit is contained in:
commit
277ada9a79
75
dpnet/CFrame.py
Normal file
75
dpnet/CFrame.py
Normal 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
80
dpnet/DFrame.py
Normal 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
49
dpnet/EnumQuery.py
Normal 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
94
dpnet/EnumResponse.py
Normal 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
0
dpnet/__init__.py
Normal file
18
dpnet/netclient.py
Normal file
18
dpnet/netclient.py
Normal 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
305
dpnet/netserver.py
Normal 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
50
dpnet/packet.py
Normal 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
84
dpnet/session.py
Normal 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
0
giants/__init__.py
Normal file
6
giants/entity.py
Normal file
6
giants/entity.py
Normal 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
11
giants/map.py
Normal 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
45
giants/masterserver.py
Normal 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
10
giants/player.py
Normal 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
9
sendenum.py
Normal 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
63
server.py
Normal 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
0
utils/__init__.py
Normal file
17
utils/logger.py
Normal file
17
utils/logger.py
Normal 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
|
Loading…
Reference in New Issue
Block a user