Initial Release of IGNCore version 2.5

This commit is contained in:
2021-08-09 13:18:56 +02:00
commit a83d98c47e
910 changed files with 224171 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
#
# Base class of all modules...
# makes accessing fields which exist in all modules easier
#
class BaseModule:
module_name = ""
module_dir = ""
# noinspection DuplicatedCode
def inject(self, registry):
pass
def pre_start(self):
pass
def start(self):
pass
+117
View File
@@ -0,0 +1,117 @@
import select
import socket
import struct
from core.aochat.client_packets import LoginRequest, LoginSelect
from core.aochat.crypt import generate_login_key
from core.aochat.server_packets import ServerPacket, LoginOK, LoginError, LoginCharacterList
from core.logger import Logger
class Bot:
def __init__(self):
self.socket = None
self.char_id = None
self.char_name = None
self.logger = Logger(__name__)
def connect(self, host, port):
self.logger.info("Connecting to '%s:%d'" % (host, port))
self.socket = socket.create_connection((host, port), 10)
def disconnect(self):
if self.socket:
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()
self.socket = None
def login(self, username, password, character):
character = character.capitalize()
# read seed packet
self.logger.info("Logging in as '%s'" % character)
seed_packet = self.read_packet(10)
seed = seed_packet.seed
# send back challenge
key = generate_login_key(seed, username, password)
login_request_packet = LoginRequest(0, username, key)
self.send_packet(login_request_packet)
# read character list
character_list_packet: LoginCharacterList = self.read_packet()
if isinstance(character_list_packet, LoginError):
self.logger.error("Error logging in: %s" % character_list_packet.message)
return False
if character not in character_list_packet.names:
self.logger.error("Character '%s' does not exist on this account" % character)
return False
index = character_list_packet.names.index(character)
# select character
self.char_id = character_list_packet.char_ids[index]
self.char_name = character_list_packet.names[index]
login_select_packet = LoginSelect(self.char_id)
self.send_packet(login_select_packet)
# wait for OK
packet = self.read_packet()
if packet.id == LoginOK.id:
self.logger.info("Connected!")
return packet
else:
self.logger.error("Error logging in: %s" % packet.message)
return False
def read_packet(self, max_delay_time=1):
"""
Wait for packet from server.
"""
read, write, error = select.select([self.socket], [], [], max_delay_time)
if not read:
return None
else:
# Read data from server
head = self.read_bytes(4)
packet_type, packet_length = struct.unpack(">2H", head)
data = self.read_bytes(packet_length)
try:
return ServerPacket.get_instance(packet_type, data)
except Exception as e:
self.logger.error(f"Error parsing packet parameters for packet_type {packet_type} and payload: {data}",
e)
return None
def send_packet(self, packet):
data = packet.to_bytes()
data = struct.pack(">2H", packet.id, len(data)) + data
self.write_bytes(data)
def read_bytes(self, num_bytes):
data = bytes()
while num_bytes > 0:
chunk = self.socket.recv(num_bytes)
if len(chunk) == 0:
raise EOFError
num_bytes -= len(chunk)
data = data + chunk
return data
def write_bytes(self, data):
num_bytes = len(data)
while num_bytes > 0:
sent = self.socket.send(data)
if sent == 0:
raise EOFError
data = data[sent:]
num_bytes -= sent
+266
View File
@@ -0,0 +1,266 @@
from core.aochat.packets import *
class ClientPacket(Packet):
def __init__(self, packet_id, types, args):
self.id = packet_id
self.types = types
self.args = args
def to_bytes(self):
return encode_args(self.types, self.args)
def __str__(self):
return "ClientPacket(%d): %s" % (self.id, self.args)
@classmethod
def get_instance(cls, packet_id, data):
if packet_id == LoginRequest.id:
LoginRequest.from_bytes(data)
elif packet_id == LoginSelect.id:
LoginSelect.from_bytes(data)
elif packet_id == CharacterLookup.id:
CharacterLookup.from_bytes(data)
elif packet_id == PrivateMessage.id:
PrivateMessage.from_bytes(data)
elif packet_id == BuddyAdd.id:
BuddyAdd.from_bytes(data)
elif packet_id == BuddyRemove.id:
BuddyRemove.from_bytes(data)
elif packet_id == PrivateChannelInvite.id:
PrivateChannelInvite.from_bytes(data)
elif packet_id == PrivateChannelKick.id:
PrivateChannelKick.from_bytes(data)
elif packet_id == PrivateChannelJoin.id:
PrivateChannelJoin.from_bytes(data)
elif packet_id == PrivateChannelLeave.id:
PrivateChannelLeave.from_bytes(data)
elif packet_id == PrivateChannelKickAll.id:
PrivateChannelKickAll.from_bytes(data)
elif packet_id == PrivateChannelMessage.id:
PrivateChannelMessage.from_bytes(data)
elif packet_id == PublicChannelMessage.id:
PublicChannelMessage.from_bytes(data)
elif packet_id == Ping.id:
Ping.from_bytes(data)
elif packet_id == ChatCommand.id:
ChatCommand.from_bytes(data)
else:
return None
class LoginRequest(ClientPacket):
id = 2
types = "ISS"
def __init__(self, unknown, username, key):
self.unknown = unknown
self.username = username
self.key = key
super().__init__(self.id, self.types, [self.unknown, self.username, self.key])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class LoginSelect(ClientPacket):
id = 3
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class CharacterLookup(ClientPacket):
id = 21
types = "S"
def __init__(self, name):
self.name = name
super().__init__(self.id, self.types, [self.name])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateMessage(ClientPacket):
id = 30
types = "ISS"
def __init__(self, char_id, message, blob):
self.char_id = char_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.char_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class BuddyAdd(ClientPacket):
id = 40
types = "IS"
def __init__(self, char_id, status):
self.char_id = char_id
self.status = status
super().__init__(self.id, self.types, [self.char_id, self.status])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class BuddyRemove(ClientPacket):
id = 41
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelInvite(ClientPacket):
id = 50
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelKick(ClientPacket):
id = 51
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelJoin(ClientPacket):
id = 52
types = "I"
def __init__(self, private_channel_id):
self.private_channel_id = private_channel_id
super().__init__(self.id, self.types, [self.private_channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelLeave(ClientPacket):
id = 53
types = "I"
def __init__(self, private_channel_id):
self.private_channel_id = private_channel_id
super().__init__(self.id, self.types, [self.private_channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelKickAll(ClientPacket):
id = 54
types = ""
def __init__(self):
super().__init__(self.id, self.types, [])
@classmethod
def from_bytes(cls, data):
return cls()
class PrivateChannelMessage(ClientPacket):
id = 57
types = "ISS"
def __init__(self, private_channel_id, message, blob):
self.private_channel_id = private_channel_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.private_channel_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PublicChannelMessage(ClientPacket):
id = 65
types = "GSS"
def __init__(self, channel_id, message, blob):
self.channel_id = channel_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.channel_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class Ping(ClientPacket):
id = 100
types = "S"
def __init__(self, blob):
self.blob = blob
super().__init__(self.id, self.types, [self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class ChatCommand(ClientPacket):
id = 120
types = "s"
def __init__(self, commands):
self.commands = commands
super().__init__(self.id, self.types, [self.commands])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
+110
View File
@@ -0,0 +1,110 @@
# Relevant parts of original copyright notice of AOChat.php:
# Copyright (C) 2005 by Jürgen A. Erhard
# Copyright (C) 2002-2004 Oskari Saarenmaa <auno@auno.org>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA
import random
import socket
import struct
# This is 'half' Diffie-Hellman key exchange.
# 'Half' as in we already have the server's key ($dhY)
# $dhN is a prime and $dhG is generator for it.
#
# http://en.wikipedia.org/wiki/Diffie-Hellman_key_exchange
# noinspection LongLine
def generate_login_key(server_key, username, password):
dhY = 0x9c32cc23d559ca90fc31be72df817d0e124769e809f936bc14360ff4bed758f260a0d596584eacbbc2b88bdd410416163e11dbf62173393fbc0c6fefb2d855f1a03dec8e9f105bbad91b3437d8eb73fe2f44159597aa4053cf788d2f9d7012fb8d7c4ce3876f7d6cd5d0c31754f4cd96166708641958de54a6def5657b9f2e92
dhN = 0xeca2e8c85d863dcdc26a429a71a9815ad052f6139669dd659f98ae159d313d13c6bf2838e10a69b6478b64a24bd054ba8248e8fa778703b418408249440b2c1edd28853e240d8a7e49540b76d120d3b1ad2878b1b99490eb4a2a5e84caa8a91cecbdb1aa7c816e8be343246f80c637abc653b893fd91686cf8d32d6cfe5f2a6f
dhG = 0x5
dhx = random.randrange(0, 2 ** 256)
dhX = pow(dhG, dhx, dhN)
dhK = pow(dhY, dhx, dhN)
dhK = "%x" % dhK
if len(dhK) > 32:
dhK = dhK[:32]
dhK = eval("0x" + dhK)
challenge = "%s|%s|%s" % (username, server_key, password)
# prefix is an 8 bytes of randomness
prefix_bytes = random.randrange(0, 2 ** 64)
prefix = struct.pack(">Q", prefix_bytes)
length = 8 + 4 + len(challenge) # prefix, int, ...
pad = " " * ((8 - length % 8) % 8)
challenge_len = struct.pack(">I", len(challenge))
plain = prefix + challenge_len + challenge.encode('ascii') + pad.encode('ascii')
crypted = aochat_crypt(dhK, plain)
if not crypted:
raise Exception("panic")
return ("%0x" % dhX) + "-" + crypted
def aochat_crypt(key, data):
if len(data) % 8 != 0:
return None
cycle = [0, 0]
result = [0, 0]
ret = ""
key_arr = [socket.ntohl(int(s, 16)) for s in
struct.unpack("8s" * (len("%s" % key) // 8), ("%x" % key).encode('ascii'))]
data_arr = struct.unpack("I" * (len(data) // 4), data)
i = 0
while i < len(data_arr):
cycle[0] = data_arr[i] ^ result[0]
cycle[1] = data_arr[i + 1] ^ result[1]
result = aochat_tea_encrypt(cycle, key_arr)
p = "%08x%08x" % (socket.htonl(result[0]) & 0xffffffff, socket.htonl(result[1]) & 0xffffffff)
ret += p
i += 2
return ret
def aochat_tea_encrypt(cycle, key):
a, b = cycle
total = 0
delta = 0x9e3779b9
i = 32
while i:
total = (total + delta) & 0xffffffff
a += (((b << 4 & 0xfffffff0) + key[0]) ^ (b + total) ^ ((b >> 5 & 0x7ffffff) + key[1])) & 0xffffffff
a &= 0xffffffff
b += (((a << 4 & 0xfffffff0) + key[2]) ^ (a + total) ^ ((a >> 5 & 0x7ffffff) + key[3])) & 0xffffffff
b &= 0xffffffff
i -= 1
return a, b
+34
View File
@@ -0,0 +1,34 @@
import time
class DelayQueue:
def __init__(self, recovery: int, burst=0):
self.recovery = recovery
self.burst = burst
self.items = []
self.next_packet = 0
def enqueue(self, item):
self.items.insert(0, item)
def dequeue(self):
if self.items:
t = time.time()
time_with_burst = t - (self.burst * self.recovery)
if self.next_packet < time_with_burst:
self.next_packet = time_with_burst
if t >= self.next_packet:
self.next_packet += self.recovery
return self.items.pop()
else:
return None
def __len__(self):
return len(self.items)
def clear(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
+20
View File
@@ -0,0 +1,20 @@
class ExtendedMessage:
def __init__(self, category_id, instance_id, template, params):
self.category_id = category_id
self.instance_id = instance_id
self.template = template
self.params = params
def get_message(self):
try:
return self.template % tuple(self.params)
except TypeError:
# sometimes params are sent even tho the template does not include param placeholders
# ex: ExtendedMessage:
# [20000, 134870373,
# 'Your ability to send private messages has been revoked temporarily with a GM gag.',
# [1000]]
return self.template
def __str__(self):
return str([self.category_id, self.instance_id, self.template, self.params, self.get_message()])
+137
View File
@@ -0,0 +1,137 @@
import struct
from core.logger import Logger
class MMDBParser:
def __init__(self, filename):
self.filename = filename
self.logger = Logger(__name__)
def get_message_string(self, category_id, instance_id):
with open(self.filename, mode="rb") as file:
categories = self.get_categories(file)
try:
category = next(categories)
while category["id"] != category_id:
category = next(categories)
next_category = next(categories)
except StopIteration:
return None
instance = self.find_entry(file, instance_id, category["offset"], next_category["offset"])
if instance:
file.seek(instance["offset"])
return self.read_string(file)
else:
return None
def get_all_message_strings(self):
with open(self.filename, mode="rb") as file:
categories = iter(list(self.get_categories(file)))
next_category = next(categories)
while True:
try:
category = next_category
next_category = next(categories)
except StopIteration:
break
max_offset = next_category["offset"]
file.seek(category["offset"])
instances = []
while file.tell() < max_offset:
entry = self.read_entry(file)
instances.append(entry)
for instance in instances:
file.seek(instance["offset"])
message_string = self.read_string(file)
print([category["id"], instance["id"], message_string])
def find_entry(self, file, entry_id, min_offset, max_offset):
file.seek(min_offset)
entry = self.read_entry(file)
while file.tell() <= max_offset:
if entry["id"] == entry_id:
return entry
entry = self.read_entry(file)
return None
def get_categories(self, file):
file.seek(4)
num_categories = self.read_int(file)
for i in range(0, num_categories):
yield self.read_entry(file)
def read_entry(self, file):
return {"id": self.read_int(file), "offset": self.read_int(file)}
def read_int(self, file):
return int.from_bytes(file.read(4), byteorder="little")
def read_string(self, file):
message = bytearray()
char = file.read(1)
i = 0
while char and char != b'\x00':
i += 1
message.append(ord(char))
char = file.read(1)
return message.decode("utf-8")
def read_base_85(self, num_str):
n = 0
for i in range(0, 5):
n = n * 85 + num_str[i] - 33
return n
def parse_params(self, param_arr):
args = []
while len(param_arr) > 0:
data_type = chr(param_arr[0])
param_arr = param_arr[1:]
if data_type == "S":
size = param_arr[0] * 256 + param_arr[1]
args.append(param_arr[2:2 + size].decode("utf-8"))
param_arr = param_arr[2 + size:]
elif data_type == "s":
size = param_arr[0] - 1 # size is 1 less than indicated
args.append(param_arr[1:1 + size].decode("utf-8"))
param_arr = param_arr[1 + size:]
elif data_type == "I":
args.append(struct.unpack(">I", param_arr[:4])[0])
param_arr = param_arr[4:]
elif data_type == "i" or data_type == "u":
args.append(self.read_base_85(param_arr[:5]))
param_arr = param_arr[5:]
elif data_type == "R":
category_id = self.read_base_85(param_arr[:5])
instance_id = self.read_base_85(param_arr[5:10])
message = self.get_message_string(category_id, instance_id)
if not message:
raise Exception(f"Could not find message string for category "
f"'{category_id}' and instance '{instance_id}'")
args.append(message)
param_arr = param_arr[10:]
elif data_type == "l":
category_id = 20000
instance_id = struct.unpack(">I", param_arr[:4])[0]
message = self.get_message_string(category_id, instance_id)
if not message:
raise Exception(f"Could not find message string for category "
f"'{category_id}' and instance '{instance_id}'")
args.append(message)
param_arr = param_arr[4:]
elif data_type == "~":
break
else:
raise Exception("Unknown argument type '%s'" % data_type)
return args
+95
View File
@@ -0,0 +1,95 @@
import struct
class UnknownArgumentType(Exception):
pass
class PacketMissingArgument(Exception):
pass
def decode_args(types, data):
args = []
for argtype in types:
if argtype == "I":
elem, data = data[:4], data[4:]
result = struct.unpack(">I", elem)[0]
elif argtype == "S":
length = struct.unpack(">H", data[:2])[0]
result = data[2:2 + length].decode("utf-8", "ignore")
data = data[2 + length:]
elif argtype == "B":
length = struct.unpack(">H", data[:2])[0]
result = data[2:2 + length]
data = data[2 + length:]
elif argtype == "G":
result, data = data[:5], data[5:]
# Convert result (5 bytes) to a long. Can't use
# struct.unpack(">Q", "\x00"*3 + result), since we
# can't rely on "long long" being available.
high, low = struct.unpack(">BI", result)
result = (high << 32) + low
elif argtype == "i":
length = struct.unpack(">H", data[:2])[0]
result = struct.unpack(">%sI" % length, data[2:2 + 4 * length])
data = data[2 + 4 * length:]
elif argtype == "s":
length = struct.unpack(">H", data[:2])[0]
data = data[2:]
result = []
while length:
slength = struct.unpack(">H", data[:2])[0]
result.append(data[2:2 + slength].decode("utf-8"))
data = data[2 + slength:]
length -= 1
else:
raise UnknownArgumentType(argtype)
args.append(result)
return args
def encode_args(types, args):
data = b""
for argtype in types:
if not args:
raise PacketMissingArgument
it = args[0]
del args[0]
if argtype == "I":
data += struct.pack(">I", it)
elif argtype == "S":
encoded = it.encode("utf-8")
data += struct.pack(">H", len(encoded))
data += encoded
elif argtype == "G":
data += struct.pack(">BI", it >> 32, it & 0xffffffff)
elif argtype == "s":
data += struct.pack(">H", len(it))
for it_elem in it:
encoded = it_elem.encode("utf-8")
data += struct.pack(">H", len(encoded))
data += encoded
else:
raise UnknownArgumentType(argtype)
return data
class Packet:
pass
+456
View File
@@ -0,0 +1,456 @@
from core.aochat.extended_message import ExtendedMessage
from core.aochat.packets import *
class ServerPacket(Packet):
def __init__(self, packet_id, types, args):
self.id = packet_id
self.types = types
self.args = args
def to_bytes(self):
return encode_args(self.types, self.args)
def __str__(self):
return "ServerPacket(%d): %s" % (self.id, self.args)
@classmethod
def get_instance(cls, packet_id, data):
if packet_id == LoginSeed.id:
return LoginSeed.from_bytes(data)
elif packet_id == LoginOK.id:
return LoginOK.from_bytes(data)
elif packet_id == LoginError.id:
return LoginError.from_bytes(data)
elif packet_id == LoginCharacterList.id:
return LoginCharacterList.from_bytes(data)
elif packet_id == CharacterUnknown.id:
return CharacterUnknown.from_bytes(data)
elif packet_id == CharacterName.id:
return CharacterName.from_bytes(data)
elif packet_id == CharacterLookup.id:
return CharacterLookup.from_bytes(data)
elif packet_id == PrivateMessage.id:
return PrivateMessage.from_bytes(data)
elif packet_id == VicinityMessage.id:
return VicinityMessage.from_bytes(data)
elif packet_id == BroadcastMessage.id:
return BroadcastMessage.from_bytes(data)
elif packet_id == SimpleSystemMessage.id:
return SimpleSystemMessage.from_bytes(data)
elif packet_id == SystemMessage.id:
return SystemMessage.from_bytes(data)
elif packet_id == BuddyAdded.id:
return BuddyAdded.from_bytes(data)
elif packet_id == BuddyRemoved.id:
return BuddyRemoved.from_bytes(data)
elif packet_id == PrivateChannelInvited.id:
return PrivateChannelInvited.from_bytes(data)
elif packet_id == PrivateChannelKicked.id:
return PrivateChannelKicked.from_bytes(data)
elif packet_id == PrivateChannelLeft.id:
return PrivateChannelLeft.from_bytes(data)
elif packet_id == PrivateChannelClientJoined.id:
return PrivateChannelClientJoined.from_bytes(data)
elif packet_id == PrivateChannelClientLeft.id:
return PrivateChannelClientLeft.from_bytes(data)
elif packet_id == PrivateChannelMessage.id:
return PrivateChannelMessage.from_bytes(data)
elif packet_id == PrivateChannelInviteRefused.id:
return PrivateChannelInviteRefused.from_bytes(data)
elif packet_id == PublicChannelJoined.id:
return PublicChannelJoined.from_bytes(data)
elif packet_id == PublicChannelLeft.id:
return PublicChannelLeft.from_bytes(data)
elif packet_id == PublicChannelMessage.id:
return PublicChannelMessage.from_bytes(data)
elif packet_id == Pong.id:
return Pong.from_bytes(data)
else:
return None
class LoginSeed(ServerPacket):
id = 0
types = "S"
def __init__(self, seed):
self.seed = seed
super().__init__(self.id, self.types, [self.seed])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class LoginOK(ServerPacket):
id = 5
types = ""
def __init__(self):
super().__init__(self.id, self.types, [])
@classmethod
def from_bytes(cls, data):
return cls()
class LoginError(ServerPacket):
id = 6
types = "S"
def __init__(self, message):
self.message = message
super().__init__(self.id, self.types, [self.message])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class LoginCharacterList(ServerPacket):
id = 7
types = "isii"
def __init__(self, char_ids, names, levels, online_statuses):
self.char_ids = char_ids
self.names = names
self.levels = levels
self.online_statuses = online_statuses
super().__init__(self.id, self.types, [self.char_ids, self.names, self.levels, self.online_statuses])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class CharacterUnknown(ServerPacket):
id = 10
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class CharacterName(ServerPacket):
id = 20
types = "IS"
def __init__(self, char_id, name):
self.char_id = char_id
self.name = name
super().__init__(self.id, self.types, [self.char_id, self.name])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class CharacterLookup(ServerPacket):
id = 21
types = "IS"
def __init__(self, char_id, name):
self.char_id = char_id
self.name = name
super().__init__(self.id, self.types, [self.char_id, self.name])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateMessage(ServerPacket):
id = 30
types = "ISS"
def __init__(self, char_id, message, blob):
self.char_id = char_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.char_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class VicinityMessage(ServerPacket):
id = 34
types = "ISS"
def __init__(self, char_id, message, blob):
self.char_id = char_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.char_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class BroadcastMessage(ServerPacket):
id = 35
types = "SSS"
def __init__(self, text, message, blob):
self.text = text
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.text, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class SimpleSystemMessage(ServerPacket):
id = 36
types = "S"
def __init__(self, message):
self.message = message
super().__init__(self.id, self.types, [self.message])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class SystemMessage(ServerPacket):
id = 37
types = "IIIB"
def __init__(self, client_id, window_id, message_id, message_args):
self.client_id = client_id
self.window_id = window_id
self.message_id = message_id
self.message_args = message_args
# noinspection PyTypeChecker
self.extended_message: ExtendedMessage = None
super().__init__(self.id, self.types, [self.client_id, self.window_id, self.message_id, self.message_args])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
def __str__(self):
return super().__str__() + ", ExtendedMessage: %s" % self.extended_message
class BuddyAdded(ServerPacket):
id = 40
types = "IIS"
def __init__(self, char_id, online, status):
self.char_id = char_id
self.online = online
self.status = status
super().__init__(self.id, self.types, [self.char_id, self.online, self.status])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class BuddyRemoved(ServerPacket):
id = 41
types = "I"
def __init__(self, char_id):
self.char_id = char_id
super().__init__(self.id, self.types, [self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelInvited(ServerPacket):
id = 50
types = "I"
def __init__(self, private_channel_id):
self.private_channel_id = private_channel_id
super().__init__(self.id, self.types, [self.private_channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelKicked(ServerPacket):
id = 51
types = "I"
def __init__(self, private_channel_id):
self.private_channel_id = private_channel_id
super().__init__(self.id, self.types, [self.private_channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelLeft(ServerPacket):
id = 53
types = "I"
def __init__(self, private_channel_id):
self.private_channel_id = private_channel_id
super().__init__(self.id, self.types, [self.private_channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelClientJoined(ServerPacket):
id = 55
types = "II"
def __init__(self, private_channel_id, char_id):
self.private_channel_id = private_channel_id
self.char_id = char_id
super().__init__(self.id, self.types, [self.private_channel_id, self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelClientLeft(ServerPacket):
id = 56
types = "II"
def __init__(self, private_channel_id, char_id):
self.private_channel_id = private_channel_id
self.char_id = char_id
super().__init__(self.id, self.types, [self.private_channel_id, self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelMessage(ServerPacket):
id = 57
types = "IISS"
def __init__(self, private_channel_id, char_id, message, blob):
self.private_channel_id = private_channel_id
self.char_id = char_id
self.message = message
self.blob = blob
super().__init__(self.id, self.types, [self.private_channel_id, self.char_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PrivateChannelInviteRefused(ServerPacket):
id = 58
types = "II"
def __init__(self, private_channel_id, char_id):
self.private_channel_id = private_channel_id
self.char_id = char_id
super().__init__(self.id, self.types, [self.private_channel_id, self.char_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PublicChannelJoined(ServerPacket):
id = 60
types = "GSIS"
def __init__(self, channel_id, name, unknown, flags):
self.channel_id = channel_id
self.name = name
self.unknown = unknown
self.flags = flags
super().__init__(self.id, self.types, [self.channel_id, self.name, self.unknown, self.flags])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PublicChannelLeft(ServerPacket):
id = 61
types = "G"
def __init__(self, channel_id):
self.channel_id = channel_id
super().__init__(self.id, self.types, [self.channel_id])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
class PublicChannelMessage(ServerPacket):
id = 65
types = "GISS"
def __init__(self, channel_id, char_id, message, blob):
self.channel_id = channel_id
self.char_id = char_id
self.message = message
self.blob = blob
# noinspection PyTypeChecker
self.extended_message: ExtendedMessage = None
super().__init__(self.id, self.types, [self.channel_id, self.char_id, self.message, self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)
def __str__(self):
return super().__str__() + ", ExtendedMessage: %s" % self.extended_message
class Pong(ServerPacket):
id = 100
types = "S"
def __init__(self, blob):
self.blob = blob
super().__init__(self.id, self.types, [self.blob])
@classmethod
def from_bytes(cls, data):
args = decode_args(cls.types, data)
return cls(*args)