forked from hipstercat/giantsd
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