forked from hipstercat/giantsd
666 lines
32 KiB
Python
666 lines
32 KiB
Python
import os
|
|
|
|
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
|
|
import struct
|
|
from _datetime import datetime
|
|
from utils.logger import setup_logger
|
|
from giants.player import Player, PlayerPhases
|
|
import time
|
|
from giants import APPLICATION_GUID
|
|
import random
|
|
from .DN_MSG_INTERNAL_SEND_CONNECT_INFO import DN_MSG_INTERNAl_SEND_CONNECT_INFO
|
|
import asyncio
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
|
|
class Netserver(asyncio.DatagramProtocol):
|
|
def __init__(self, server):
|
|
self.remotesocket = None
|
|
self.statssocket = None
|
|
self.server = server
|
|
self.addrs = []
|
|
self.guid = uuid.uuid4().bytes
|
|
self.incoming_packets = []
|
|
super().__init__()
|
|
|
|
def connection_made(self, transport):
|
|
logger.debug("Connection made")
|
|
self.remotesocket = transport
|
|
|
|
def run(self):
|
|
logger.debug("Run")
|
|
return
|
|
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)
|
|
networkthread = threading.Thread(target=self.handle_packets)
|
|
networkthread.start()
|
|
|
|
if self.server.register_with_ms:
|
|
ms = MasterServer(self.server)
|
|
ms.register()
|
|
|
|
while self.server.running:
|
|
start = datetime.now()
|
|
self.read_packets()
|
|
self.server.update()
|
|
self.send_packets()
|
|
sleep_ms = (start-datetime.now()).total_seconds()+1/self.server.ticks
|
|
if sleep_ms > 0:
|
|
time.sleep(sleep_ms)
|
|
|
|
networkthread.join()
|
|
|
|
def read_packets(self):
|
|
pass
|
|
|
|
def send_packets(self):
|
|
pass
|
|
|
|
def datagram_received(self, data, addr):
|
|
loop = asyncio.get_event_loop()
|
|
loop.create_task(self.handle_new_packet(addr, data))
|
|
|
|
async def handle_new_packet(self, addr, bData):
|
|
pPacket = Packet(bData)
|
|
first = pPacket.getByte()
|
|
if len(bData) >= 4 and first & DFrame.PACKET_COMMAND_DATA:
|
|
await self.handle_new_dframe(addr, pPacket)
|
|
elif len(bData) >= 12 and first & CFrame.PACKET_COMMAND_FRAME:
|
|
await self.handle_new_cframe(addr, pPacket)
|
|
elif first == EnumQuery.LEAD or first == EnumResponse.LEAD:
|
|
logger.debug("%s:%s > %s (ENUM)", addr[0], addr[1], bData.hex())
|
|
await self.handle_new_enum(addr, pPacket)
|
|
else:
|
|
logger.error("Unknown frame received")
|
|
return
|
|
|
|
async 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' # game type and teams
|
|
er.ApplicationReservedData += struct.pack("<L", int(self.server.version*1000)) # 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
|
|
# \x9f\xb2\x42\xec: test
|
|
er.ApplicationReservedData += self.server.currentmap.mapname.encode("ascii")'''
|
|
|
|
appdata = Packet()
|
|
appdata.putByte(0xff) # map ID
|
|
appdata.putByte(self.server.game_type) # game type
|
|
appdata.putByte(self.server.teams) # teams
|
|
appdata.write(b'\x00') # original: 0x00 - does not seem to affect client
|
|
appdata.putShort(int(self.server.version * 1000))
|
|
appdata.write(b'\x02\x92') # original: 0292 - does not seem to affect client
|
|
appdata.putShort(self.server.points_per_capture)
|
|
appdata.putShort(self.server.points_per_kill)
|
|
appdata.write(b'\x00\x00') # original: 0000 - does not seem to affect client
|
|
appdata.putShort(self.server.detente_time)
|
|
appdata.write(self.server.currentmap.checksum) # Seems to be a checksum of current map OR linked to the number of chars in the map name
|
|
appdata.write(self.server.currentmap.mapname.encode("ascii"))
|
|
appdata.write(b'\x00' * (32 - len(self.server.currentmap.mapname)))
|
|
er.ApplicationReservedData = appdata.getvalue()
|
|
|
|
logger.debug("Current map: %s, checksum: %s", self.server.currentmap.mapname, self.server.currentmap.checksum)
|
|
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)
|
|
|
|
async def handle_new_cframe(self, addr, data):
|
|
try:
|
|
cframe = CFrame(data)
|
|
session = self.get_session(addr)
|
|
player = self.get_player(session) if session else None
|
|
if cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECT:
|
|
logger.debug("%s:%s > %s (CFRAME CONNECT)", addr[0], addr[1], data.getvalue().hex())
|
|
# CONNECT CFRAME
|
|
if session and session.Full:
|
|
if False:
|
|
logger.error("Session %s:%s was already fully connected. Ignoring.", session.ip, session.port)
|
|
return
|
|
else:
|
|
logger.error("Session %s:%s was already fully connected. Kicking the old one.", session.ip, session.port)
|
|
player = self.get_player(session)
|
|
if player:
|
|
self.server.remove_player(self.get_player(session))
|
|
self.addrs.remove(session)
|
|
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.server, self.remotesocket)
|
|
session.SessID = cframe.SessID
|
|
session.ip = addr[0]
|
|
session.port = addr[1]
|
|
await session.lock.acquire()
|
|
self.addrs.append(session)
|
|
session.send_cframe_connected(cframe)
|
|
player = Player("Unknown", session)
|
|
|
|
def bxor(b1, b2): # use xor for bytes
|
|
result = bytearray()
|
|
for b1, b2 in zip(b1, b2):
|
|
result.append(b1 ^ b2)
|
|
return result
|
|
#player.id = struct.unpack(">L", bxor(struct.pack("<L", len(self.server.players)), self.guid[0:4]))[0]
|
|
logger.debug("Value for connected player: %s", player.id)
|
|
player.phase = PlayerPhases.CFRAME_CONNECT
|
|
self.server.create_temp_player(player)
|
|
#session.setup_Connect_Retry_Timer()
|
|
elif cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_CONNECTED:
|
|
logger.debug("%s:%s > %s (CFRAME CONNECTED)", addr[0], addr[1], data.getvalue().hex())
|
|
# 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()
|
|
player.phase = PlayerPhases.CFRAME_CONNECTED
|
|
session.send_dframe_keepalive()
|
|
|
|
elif cframe.ExtOpCode == CFrame.FRAME_EXOPCODE_SACK:
|
|
logger.debug("%s:%s > %s (CFRAME SACK)", addr[0], addr[1], data.getvalue().hex())
|
|
if not session or not session.Full:
|
|
logger.error("Received a SACK packet for a non fully connected session")
|
|
return
|
|
|
|
sack_sent = False
|
|
if not cframe.NSeq == session.next_expected_seq:
|
|
logger.error("Received CFRAME (%s) does not have the same NSeq (%s). Did we miss a packet?", cframe.NSeq, session.next_expected_seq)
|
|
if not sack_sent:
|
|
session.send_cframe_sack()
|
|
sack_sent = True
|
|
|
|
if not session.next_send == cframe.NRecv:
|
|
logger.error("Received CFRAME (%s) does not have same NRcv (%s). One sent packet might have been lost.", cframe.NRecv, session.next_send)
|
|
if not sack_sent:
|
|
session.send_cframe_sack()
|
|
sack_sent = True
|
|
|
|
# release lock for new packet to be sent
|
|
if session.lock.locked():
|
|
session.lock.release()
|
|
|
|
# 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
|
|
|
|
async 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 and not dframe.Control & DFrame.PACKET_CONTROL_END_STREAM:
|
|
logger.debug("%s sent a DFRAME without having sent a CONNECT before. GTFO.", addr)
|
|
logger.debug("%s:%s > %s (DFRAME)", addr[0], addr[1], data.getvalue().hex())
|
|
return
|
|
|
|
if dframe.Control & DFrame.PACKET_CONTROL_END_STREAM:
|
|
logger.debug("%s:%s > %s (END_STREAM)", addr[0], addr[1], data.getvalue().hex())
|
|
if not session:
|
|
logger.error("Received a END STREAM packet for a non fully connected session")
|
|
session = Session(self.server, self.remotesocket)
|
|
session.ip = addr[0]
|
|
session.port = addr[1]
|
|
session.SessID = dframe.SessID
|
|
session.next_send = dframe.NRcv
|
|
session.next_expected_seq = dframe.Seq+1
|
|
|
|
session.send_cframe_sack()
|
|
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
|
|
logger.debug(" %s:%s < %s (END_STREAM)", session.ip, session.port, resp.to_packet().getvalue().hex())
|
|
session.send(resp)
|
|
|
|
# TODO: broadcast session has disconnected
|
|
if self.get_session(addr):
|
|
self.addrs.remove(session)
|
|
|
|
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_KEEPALIVE_OR_CORRELATE:
|
|
pass
|
|
#session.send_dframe_keepalive()
|
|
|
|
if dframe.Command & DFrame.PACKET_COMMAND_POLL:
|
|
#logger.debug("Sending SACK")
|
|
session.send_cframe_sack()
|
|
pass
|
|
|
|
if session.next_expected_seq == 255:
|
|
session.next_expected_seq = 0
|
|
else:
|
|
session.next_expected_seq += 1
|
|
if dframe.Payload:
|
|
await self.handle_game_packet(session, dframe.Payload)
|
|
else:
|
|
pass
|
|
except Exception as e:
|
|
logger.error("Could not parse DFRAME: ")
|
|
traceback.print_exc()
|
|
return
|
|
|
|
async def handle_game_packet(self, session, payload):
|
|
player = self.get_temp_player(session)
|
|
logger.debug("%s:%s > %s (GAMEDATA)", session.ip, session.port, payload.hex())
|
|
#logger.debug("OPCODE: %s VALUES: %s", struct.pack("<B", payload[0]).hex(), payload[1:].hex())
|
|
#await self.server.broadcast_message("SENT OPCODE: %s VALUES: %s" % (struct.pack("<B", payload[0]).hex(), payload[1:].hex()))
|
|
|
|
if payload[0] == 0xc1:
|
|
if not player or not player.phase == PlayerPhases.CFRAME_CONNECTED:
|
|
logger.error("Fuck you. Playerphase was %s", player.phase)
|
|
return
|
|
# DN_INTERNAL_MESSAGE_PLAYER_CONNECT_INFO_EX
|
|
ppayload = Packet(payload)
|
|
dwPacketType = ppayload.getULong()
|
|
dwFlags = ppayload.getULong()
|
|
dwDNETVersion = ppayload.getULong()
|
|
dwNameOffset = ppayload.getULong()
|
|
dwNameSize = ppayload.getULong()
|
|
dwDataOffset = ppayload.getULong()
|
|
dwDataSize = ppayload.getULong()
|
|
dwPasswordOffset = ppayload.getULong()
|
|
dwPasswordSize = ppayload.getULong()
|
|
dwConnectDataOffset = ppayload.getULong()
|
|
dwConnectDataSize = ppayload.getULong()
|
|
dwURLOffset = ppayload.getULong()
|
|
dwURLSize = ppayload.getULong()
|
|
guidInstance = payload[52:68]
|
|
guidApplication = payload[68:84]
|
|
name = payload[dwNameOffset+4:dwNameOffset+2+dwNameSize]
|
|
name = name.decode("utf-16-le")
|
|
logger.debug("%s connected!", name)
|
|
player.name = name
|
|
#session.send_dframe_keepalive()
|
|
|
|
# Response: DN_MSG_INTERNAL_SEND_CONNECT_INFO
|
|
d = DN_MSG_INTERNAl_SEND_CONNECT_INFO(self, player)
|
|
d.Seq = session.next_send
|
|
d.NRcv = session.next_expected_seq
|
|
|
|
appdata = Packet()
|
|
appdata.putByte(0xff) # map ID
|
|
appdata.putByte(self.server.game_type) # game type
|
|
appdata.putByte(self.server.teams) # teams
|
|
appdata.write(b'\x00') # original: 0x00 - does not seem to affect client
|
|
appdata.putShort(int(self.server.version * 1000))
|
|
appdata.write(b'\x03\x90') # original: 0292 - does not seem to affect client
|
|
appdata.putShort(self.server.points_per_capture)
|
|
appdata.putShort(self.server.points_per_kill)
|
|
appdata.write(b'\x00\x00') # original: 0000 - does not seem to affect client
|
|
appdata.putShort(self.server.detente_time)
|
|
appdata.write(self.server.currentmap.checksum) # Seems to be a checksum of current map OR linked to the number of chars in the map name
|
|
appdata.write(self.server.currentmap.mapname.encode("ascii"))
|
|
appdata.write(b'\x00' * (32 - len(self.server.currentmap.mapname)))
|
|
d.ApplicationReservedData = appdata.getvalue()
|
|
session.send(d)
|
|
logger.debug("%s:%s > %s (GAMEDATA)", session.ip, session.port, appdata.getvalue().hex())
|
|
player.phase = PlayerPhases.DN_SEND_CONNECT_INFO
|
|
|
|
elif payload[0] == 0xc3:
|
|
player = self.get_temp_player(session)
|
|
if not player or not player.phase == PlayerPhases.DN_SEND_CONNECT_INFO:
|
|
return
|
|
|
|
player.phase = PlayerPhases.DN_ACK_CONNECT_INFO
|
|
await self.server.add_player(player)
|
|
await player.session.send_gamedata(b'\x3c'+struct.pack("<L", player.id)+b"\x00", acknow=True)
|
|
|
|
await player.session.send_gamedata(self.build_players_dpid(player))
|
|
await self.server.broadcast_gamedata_except(player, self.build_players_dpid())
|
|
|
|
plid=0
|
|
for pplayer in self.server.players:
|
|
await self.server.broadcast_gamedata(b'\x3d'+struct.pack("<B", plid)+pplayer.name.encode("ascii")+b"\x00"*(33-len(pplayer.name.encode("ascii")))) # playername
|
|
plid+=1
|
|
elif payload[0] == 0x0f:
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
inc = Packet(payload)
|
|
opcode = inc.getByte()
|
|
playerid = inc.getULong()
|
|
status = inc.getULong()
|
|
padding = inc.getShort()
|
|
|
|
out = Packet()
|
|
out.putByte(opcode)
|
|
out.putULong(playerid)
|
|
out.putULong(status)
|
|
out.putShort(padding)
|
|
await player.session.send_gamedata(out.getvalue(), acknow=False)
|
|
|
|
elif payload[0] == 0x2c: # CMSG_CHANGE_TEAM: team (byte), player id (int), unknown (byte)
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
|
|
p = Packet(payload)
|
|
opcode = p.getByte()
|
|
team = p.getByte()
|
|
playerid = p.getULong()
|
|
unknown = p.getByte()
|
|
|
|
if player.team > 3 >= team % 128:
|
|
# ignore
|
|
logger.info("%s tried to change team to %s but failed" % (player.name, team))
|
|
else:
|
|
await player.change_team(team % 128)
|
|
elif payload[0] == 0x35: # CMSG_SEND_CHAT_MESSAGE: messagetype? (byte), team? (byte), message (string)
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
|
|
p = Packet(payload)
|
|
opcode = p.getByte()
|
|
type = p.getByte()
|
|
team = p.getByte()
|
|
message = p.read().decode("utf8").replace("\x00", "")
|
|
await self.server.broadcast_event("on_player_chat", player, type, team, message)
|
|
|
|
elif payload[0] == 0x2a:
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
# winning condition time limit
|
|
time_limit_minutes = 60 # 1 hour
|
|
await player.session.send_gamedata(b"\x29"+struct.pack("<H", time_limit_minutes)+b"\x00\x80"+(b"\x00"*17), acknow=True)
|
|
|
|
await player.session.send_gamedata(b"\x39"+(b"\x00"*21), acknow=False) # acknow=True
|
|
await player.session.send_gamedata(b"\x39\x01\x01\x01\x01"+(b"\x00"*17), acknow=True)
|
|
elif payload[0] == 0x0c:
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
playerping = 1
|
|
await player.session.send_gamedata(b"\x2f\x02\x00\x00" + struct.pack("<H", playerping) + b"\x00", acknow=False) # acknow=True
|
|
for i in range(0x06):
|
|
ack = (i % 2 == 0)
|
|
await player.session.send_gamedata(b"\x04" + struct.pack("<B", i) + b"\x00\x00\x00\x00" + struct.pack("<B", i+0x07) + b"\x00\x00\x00",acknow=ack)
|
|
|
|
# needed to avoid crash after Loading textures
|
|
await player.session.send_gamedata(b"\x1a\xee\x02\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x83\x5f\xa4\x44\x00\xf9\xa3\xc2\x00\x00\x00\x00\xff\xff\x33\x43\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x9e\xdc\x07\x00", acknow=True)
|
|
|
|
# has something to do with the base build
|
|
await player.session.send_gamedata(b"\x19\x96\x03\x00\x10\x00\x00\x04\x00\x0d\x00\x00\x83\x5f\xa4\x44\x00\xf9\xa3\xc2\x00\x00\x00\x00\xff\xff\x33\x43\x00\x00\x7a\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00", acknow=False)
|
|
|
|
# useless?
|
|
await player.session.send_gamedata(b"\x39"+b"\x00"*21, acknow=True)
|
|
|
|
# useless? can be game status
|
|
await player.session.send_gamedata(b"\x39"+b"\x01"*4+b"\x00"*17, acknow=False)
|
|
|
|
# useless? can be game status
|
|
await player.session.send_gamedata(b"\x2e"+b"\x00"*9, acknow=True)
|
|
|
|
# useless? can be game status
|
|
await player.session.send_gamedata(b"\x3b\x00\x04\x00\x00\x00\x04\x00\x00\x00", acknow=False)
|
|
|
|
# useless?
|
|
await player.session.send_gamedata(b"\x3e\x00\x00\x00\x00\x00", acknow=True)
|
|
|
|
# useless?
|
|
await player.session.send_gamedata(b"\x3f\x00\x00", acknow=False)
|
|
|
|
# stuck in awaiting snapshot if not sent (could be the packet to go ingame?)
|
|
await player.session.send_gamedata(b"\x0e\x00", acknow=True)
|
|
|
|
# change team packet
|
|
await player.session.send_gamedata(b"\x10" + struct.pack("<B", player.team) + struct.pack("<L", player.id) + b"\x00\x00", acknow=False)
|
|
|
|
# spawn server as mecc
|
|
await player.session.send_gamedata(bytes.fromhex("05007300000200000000"), acknow=False)
|
|
await player.session.send_gamedata(bytes.fromhex("0a320000000073000001020000000000000000000000000000000000000000000000000000000000000000000000000000000a120000000073000003020000000000000000"), acknow=False)
|
|
|
|
# spawn Bot 1 as kab
|
|
#await player.session.send_gamedata(bytes.fromhex("05017400000100000000"), acknow=False)
|
|
#await player.session.send_gamedata(bytes.fromhex("0a320000000174000001010000000000000000000000000000000000000000000000000000000000000000000000000000000a120000000174000003020000000000000000"), acknow=False)
|
|
|
|
# spawn Bot 2 as reap
|
|
await player.session.send_gamedata(bytes.fromhex("05027500000300000000"), acknow=False)
|
|
await player.session.send_gamedata(bytes.fromhex("0a320000000275000001030000000000000000000000000000000000000000000000000000000000000000000000000000000a120000000275000003020000000000000000"), acknow=False)
|
|
|
|
# spawn Bot 3 as <id>
|
|
objid = "75"
|
|
plix = "01"
|
|
model = "4b"
|
|
#async def spawn(objid, plix, model):
|
|
# await player.session.send_gamedata(bytes.fromhex("05"+plix+objid+"0000"+model+"00000000"), acknow=False)
|
|
# await player.session.send_gamedata(bytes.fromhex("0a32000000"+plix+objid+"000001"+model+"0000000000000000000000000000000000000000000000000000000000000000000000000000000a12000000"+plix+objid+"0000"+model+"020000000000000000"), acknow=False)
|
|
#await spawn(objid, plix, model)
|
|
|
|
# makes one player change team
|
|
#await player.session.send_gamedata(bytes.fromhex("0f1c49e2450e0000000000"), acknow=False)
|
|
#await player.session.send_gamedata(bytes.fromhex("0f1e"+struct.pack("<L", player.id)+"0000000000"), acknow=False)
|
|
#await player.session.send_gamedata(bytes.fromhex("0f1e123456780000000000"), acknow=False)
|
|
|
|
await player.session.send_gamedata(bytes.fromhex("090f000000015500000a02000000ff090f000000015500000a0200000001090f000000015500000a020000000300"), acknow=False)
|
|
await player.session.send_gamedata(bytes.fromhex("2b0000000066fe954400"), acknow=False)
|
|
|
|
player.phase = PlayerPhases.INGAME
|
|
|
|
elif payload[0] == 0x0a and payload[1] == 0x32:
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
p = Packet(payload)
|
|
opcode = p.getByte()
|
|
length = p.getByte() # length?
|
|
unknown1 = p.getByte()
|
|
unknown2 = p.getByte()
|
|
unknown3 = p.getByte()
|
|
playerindex = p.getByte()
|
|
objid = p.getShort()
|
|
unknown5 = p.getByte()
|
|
unknown6 = p.getByte()
|
|
model = p.getULong()
|
|
playerx = p.getFloat()
|
|
playery = p.getFloat()
|
|
playerz = p.getFloat()
|
|
unknown4 = p.getULong()
|
|
unknown5 = p.getULong()
|
|
unknown6 = p.getULong()
|
|
unknown7 = p.getULong()
|
|
orientation = p.getULong() # orientation
|
|
unknown9 = p.getULong()
|
|
|
|
player.x = playerx
|
|
player.y = playery
|
|
player.z = playerz
|
|
player.oid = objid
|
|
player.o = orientation
|
|
|
|
#logger.info("%s is now at coords (%s,%s,%s)", player.name, player.x, player.y, player.z)
|
|
if player.opts["debug"]:
|
|
await player.send_message("%s is now at coords (%s,%s,%s, %s). Model: %s" % (player.name, player.x, player.y, player.z, player.o, model))
|
|
#await self.server.broadcast_message("(%s,%s,%s, %s,%s)" % (unknown4, unknown5, unknown6, unknown7, unknown9))
|
|
|
|
elif payload[0] == 0x3a:
|
|
# CLIENT EXITED
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
|
|
await self.server.remove_player(player)
|
|
await self.server.broadcast_gamedata(self.build_players_dpid())
|
|
|
|
plid = 0
|
|
for pplayer in self.server.players:
|
|
await self.server.broadcast_gamedata(b'\x3d' + struct.pack("<B", plid) + pplayer.name.encode("ascii") + b"\x00" * (33 - len(pplayer.name.encode("ascii"))), acknow=False) # playername
|
|
plid += 1
|
|
await self.server.broadcast_event("on_player_left", player)
|
|
elif payload[0] == 0x4d:
|
|
# CLIENT DOWNLOAD MAP
|
|
player = self.get_player(session)
|
|
if not player:
|
|
logger.debug("Fuck that no player wtf man")
|
|
return
|
|
|
|
payload = Packet()
|
|
payload.putByte(0x4e) # packet opcode
|
|
#payload.putByte(0x9d) # packet opcode?
|
|
|
|
mapsize = os.stat("maps/"+self.server.currentmap.mappath).st_size
|
|
payload.putULong(mapsize) # map size
|
|
payload.putBytes((self.server.currentmap.mapname+"\x00"*(128-len(self.server.currentmap.mapname))).encode("ascii"))
|
|
|
|
await player.session.send_gamedata(payload.getvalue())
|
|
|
|
with open("maps/"+self.server.currentmap.mappath, "rb") as fh:
|
|
data = fh.read(512)
|
|
while data != b"":
|
|
if len(data) == 512:
|
|
# full packet
|
|
mapdl = Packet()
|
|
mapdl.putByte(0x4f) # opcode
|
|
mapdl.putByte(0x00) # unknown
|
|
mapdl.putByte(0x02) # unknown
|
|
mapdl.putByte(0x00) # unknown
|
|
mapdl.putByte(0x00) # unknown
|
|
mapdl.putBytes(data)
|
|
mapdl.putByte(0x00) # ending
|
|
await player.session.send_gamedata(mapdl.getvalue(), acknow=False) # map chunk
|
|
else:
|
|
# last packet
|
|
mapdl = Packet()
|
|
mapdl.putByte(0x4f) # opcode
|
|
mapdl.putULong(len(data)) # map end length
|
|
mapdl.putBytes(data)
|
|
# fill with crap at the end
|
|
mapdl.putBytes(b"\x00"*(512-len(data)))
|
|
mapdl.putByte(0x00) # ending
|
|
await player.session.send_gamedata(mapdl.getvalue(), acknow=False) # map chunk
|
|
data = fh.read(512)
|
|
|
|
|
|
def build_players_dpid(self, exceptplayer=False):
|
|
p1 = b'\x01' + struct.pack("<B", len(self.server.players))
|
|
for pplayer in self.server.players:
|
|
p1 += struct.pack("<L", pplayer.id)
|
|
for pplayer in self.server.players:
|
|
if pplayer.name == "[Server]":
|
|
continue
|
|
p1 += b'\x0e'
|
|
p1 += b"\x01\x00"
|
|
for pplayer in self.server.players:
|
|
if pplayer == exceptplayer or pplayer.name == "[Server]":
|
|
continue
|
|
p1 += struct.pack("<B", pplayer.team)
|
|
p1 += b'\x00\x00'
|
|
return p1
|
|
|
|
def send_packet(self, addr, packet):
|
|
self.remotesocket.sendto(packet.getvalue(), addr)
|
|
#logger.debug("%s:%s < %s", addr[0], addr[1], packet.getvalue().hex())
|
|
|
|
def get_session(self, iporaddr, port=None):
|
|
if not port:
|
|
return next((x for x in self.addrs if x.ip == iporaddr[0] and x.port == iporaddr[1]), None)
|
|
else:
|
|
return next((x for x in self.addrs if x.ip == iporaddr and x.port == port), None)
|
|
|
|
def get_player(self, iporaddr, port=None):
|
|
if type(iporaddr) == Session:
|
|
a = next((x for x in self.server.players if x.session.ip == iporaddr.ip and x.session.port == iporaddr.port), None)
|
|
if a:
|
|
return a
|
|
else:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr.ip and x.session.port == iporaddr.port),None)
|
|
if not port:
|
|
a = next((x for x in self.server.players if x.session.ip == iporaddr[0] and x.session.port == iporaddr[1]), None)
|
|
if a:
|
|
return a
|
|
else:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr[0] and x.session.port == iporaddr[1]),None)
|
|
else:
|
|
a = next((x for x in self.server.players if x.session.ip == iporaddr and x.session.port == port), None)
|
|
if a:
|
|
return a
|
|
else:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr and x.session.port == port), None)
|
|
|
|
def get_temp_player(self, iporaddr, port=None):
|
|
if type(iporaddr) == Session:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr.ip and x.session.port == iporaddr.port), None)
|
|
if not port:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr[0] and x.session.port == iporaddr[1]), None)
|
|
else:
|
|
return next((x for x in self.server.tempplayers if x.session.ip == iporaddr and x.session.port == port), None) |