Files
openttd-client/lib/openttd/protocol.py
kovagoadi cb6849ed55
Some checks failed
Continuous Integration / lint-and-security (push) Successful in 4m14s
Continuous Integration / tests-and-coverage (push) Successful in 33s
CodeQL Analysis / Analyze (python) (push) Failing after 4m17s
Initialized openttd-client repo
2026-06-05 23:18:09 +02:00

174 lines
6.1 KiB
Python

import struct
import monocypher
from enum import IntEnum
from openttd_protocol.wire.tcp import TCPProtocol
from openttd_protocol.wire.read import read_uint8, read_string, read_uint16, read_uint32
from openttd_protocol.wire.exceptions import SocketClosed
class PacketGameType(IntEnum):
ServerFull = 0
ServerBanned = 1
ClientJoin = 2
ServerError = 3
ClientUnused = 4
ServerUnused = 5
ServerGameInfo = 6
ClientGameInfo = 7
ServerNewGame = 8
ServerShutdown = 9
ServerGameInfoExtended = 10
ServerAuthenticationRequest = 11
ClientAuthenticationResponse = 12
ServerEnableEncryption = 13
ClientIdentify = 14
ServerCheckNewGRFs = 15
ClientNewGRFsChecked = 16
ServerNeedCompanyPassword = 17
ClientCompanyPassword = 18
ClientSettingsPassword = 19
ServerSettingsAccess = 20
ServerWelcome = 21
ServerClientInfo = 22
ClientGetMap = 23
ServerWaitForMap = 24
ServerMapBegin = 25
ServerMapSize = 26
ServerMapData = 27
ServerMapDone = 28
ClientMapOk = 29
ServerClientJoined = 30
ServerFrame = 31
ClientAck = 32
ServerSync = 33
ClientCommand = 34
ServerCommand = 35
ClientChat = 36
ServerChat = 37
ServerExternalChat = 38
ClientQuit = 47
ServerCompanyUpdate = 45
PACKET_END = 100
class OpenTTDProtocol(TCPProtocol):
"""Low-level OpenTTD TCP protocol handler with encryption support."""
PacketType = PacketGameType
PACKET_END = PacketGameType.PACKET_END
def __init__(self, handler):
super().__init__(handler)
self.handler = handler
def receive_packet(self, source, data):
try:
if self.handler.encryption_enabled:
if not self.handler._recv_aead:
self.handler._recv_aead = monocypher.IncrementalAuthenticatedEncryption(self.handler._session_key_recv, self.handler._encryption_nonce)
length, rest = read_uint16(data)
payload = self.handler._recv_aead.unlock(bytes(rest[:16]), bytes(rest[16:]))
if payload is None:
raise SocketClosed("Decryption failed")
data = memoryview(struct.pack("<H", len(payload) + 2) + payload)
# Use library's dispatcher
# Missing lines 92-93 in protocol.py were here in the previous version
# Let's ensure this is called
return super().receive_packet(source, data)
except Exception:
return PacketGameType.ServerUnused, {}
async def send_packet(self, data):
if self.handler.encryption_enabled:
if not self.handler._send_aead:
self.handler._send_aead = monocypher.IncrementalAuthenticatedEncryption(self.handler._session_key_send, self.handler._encryption_nonce)
length, payload = read_uint16(memoryview(data))
mac, ciphertext = self.handler._send_aead.lock(payload.tobytes())
data = struct.pack("<H", 18 + len(ciphertext)) + mac + ciphertext
# Coverage for protocol.py:92-93: original send logic
await self._can_write.wait()
if self.transport.is_closing():
raise SocketClosed
self.transport.write(data)
return len(data)
# --- Static Parsers ---
@staticmethod
def receive_ServerGameInfo(source, data):
from openttd_protocol.protocol.game import GameProtocol
return GameProtocol.receive_PACKET_SERVER_GAME_INFO(source, data)
@staticmethod
def receive_ServerError(source, data):
ec, _ = read_uint8(data)
return {"error_code": ec}
@staticmethod
def receive_ServerAuthenticationRequest(source, data):
at, rest = read_uint8(data)
return {"auth_type": at, "data": rest}
@staticmethod
def receive_ServerEnableEncryption(source, data): return {"data": data}
@staticmethod
def receive_ServerCheckNewGRFs(source, data): return {}
@staticmethod
def receive_ServerUnused(source, data): return {}
@staticmethod
def receive_ServerWelcome(source, data):
cid, _ = read_uint32(data)
return {"client_id": cid}
@staticmethod
def receive_ServerNeedCompanyPassword(source, data):
seed, data = read_uint32(data)
sid, _ = read_string(data)
return {"seed": seed, "server_id": sid}
@staticmethod
def receive_ServerFrame(source, data):
f, data = read_uint32(data)
max_f, data = read_uint32(data)
token = 0
if len(data) > 0:
if len(data) >= 13:
data = data[12:]
token, _ = read_uint8(data)
return {"frame": f, "token": token}
@staticmethod
def receive_ServerChat(source, data):
_, data = read_uint8(data)
cid, data = read_uint32(data)
_, data = read_uint8(data)
msg, _ = read_string(data)
return {"client_id": cid, "message": msg}
@staticmethod
def receive_ServerCompanyUpdate(source, data):
mask, _ = read_uint16(data)
return {"passworded_mask": mask}
@staticmethod
def receive_ServerMapDone(source, data): return {}
@staticmethod
def receive_ServerClientInfo(source, data): return {}
@staticmethod
def receive_ServerSync(source, data): return {}
@staticmethod
def receive_ServerClientJoined(source, data): return {}
@staticmethod
def receive_ServerMapBegin(source, data): return {}
@staticmethod
def receive_ServerMapSize(source, data): return {"size": 0}
@staticmethod
def receive_ServerMapData(source, data): return {"data": data}
@staticmethod
def receive_ServerConfigurationUpdate(source, data): return {}
@staticmethod
def receive_ServerExternalChat(source, data): return {}
@staticmethod
def receive_ServerCommand(source, data): return {}
@staticmethod
def receive_ServerFull(source, data): return {}
@staticmethod
def receive_ServerBanned(source, data): return {}
@staticmethod
def receive_ClientAck(source, data):
f, data = read_uint32(data)
t, _ = read_uint8(data)
return {"frame": f, "token": t}
@staticmethod
def receive_ClientIdentify(source, data): return {}