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 PacketAdminType(IntEnum): AdminJoin = 0 AdminQuit = 1 AdminUpdateFrequency = 2 AdminPoll = 3 AdminChat = 4 AdminRcon = 5 AdminGamescript = 6 AdminPing = 7 AdminExternalChat = 8 AdminJoinSecure = 9 AdminAuthResponse = 10 ServerFull = 100 ServerBanned = 101 ServerError = 102 ServerProtocol = 103 ServerWelcome = 104 ServerNewGame = 105 ServerShutdown = 106 ServerDate = 107 ServerClientJoin = 108 ServerClientInfo = 109 ServerClientUpdate = 110 ServerClientQuit = 111 ServerClientError = 112 ServerCompanyNew = 113 ServerCompanyInfo = 114 ServerCompanyUpdate = 115 ServerCompanyRemove = 116 ServerCompanyEconomy = 117 ServerCompanyStats = 118 ServerChat = 119 ServerRcon = 120 ServerConsole = 121 ServerCmdNames = 122 ServerCmdLoggingOld = 123 ServerGamescript = 124 ServerRconEnd = 125 ServerPong = 126 ServerCmdLogging = 127 ServerAuthRequest = 128 ServerEnableEncryption = 129 PACKET_END = 130 class AdminUpdateFrequency(IntEnum): Poll = 1 << 0 Daily = 1 << 1 Weekly = 1 << 2 Monthly = 1 << 3 Quarterly = 1 << 4 Annually = 1 << 5 Automatic = 1 << 6 class AdminUpdateType(IntEnum): Date = 0 ClientInfo = 1 CompanyInfo = 2 CompanyEconomy = 3 CompanyStats = 4 Chat = 5 Console = 6 CmdNames = 7 CmdLogging = 8 Gamescript = 9 End = 10 class NetworkAuthenticationMethod(IntEnum): X25519_KeyExchangeOnly = 0 X25519_PAKE = 1 X25519_AuthorizedKey = 2 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(" 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 {} class OpenTTDAdminProtocol(OpenTTDProtocol): """Low-level OpenTTD Admin TCP protocol handler.""" PacketType = PacketAdminType PACKET_END = PacketAdminType.PACKET_END @staticmethod def receive_ServerProtocol(source, data): v, data = read_uint8(data) updates = {} while True: has_more, data = read_uint8(data) if not has_more: break ut, data = read_uint16(data) freqs, data = read_uint16(data) updates[ut] = freqs return {"version": v, "updates": updates} @staticmethod def receive_ServerWelcome(source, data): name, data = read_string(data) ver, data = read_string(data) dedi, data = read_uint8(data) map_name, data = read_string(data) seed, data = read_uint32(data) land, data = read_uint8(data) date, data = read_uint32(data) width, data = read_uint16(data) height, _ = read_uint16(data) return { "server_name": name, "openttd_version": ver, "dedicated": bool(dedi), "map_name": map_name, "generation_seed": seed, "landscape": land, "start_date": date, "map_width": width, "map_height": height } @staticmethod def receive_ServerDate(source, data): d, _ = read_uint32(data) return {"date": d} @staticmethod def receive_ServerChat(source, data): action, data = read_uint8(data) dest_type, data = read_uint8(data) cid, data = read_uint32(data) msg, data = read_string(data) money = 0 if len(data) >= 8: import struct money = struct.unpack("