mirror of
https://github.com/solero/houdini.git
synced 2024-11-09 12:48:20 +00:00
Initial support for multiple clients and cross-client play
This commit is contained in:
parent
fedf855f74
commit
4fa0f4dfe0
41
bootstrap.py
41
bootstrap.py
@ -6,6 +6,7 @@ import config
|
||||
from houdini.houdini import Houdini
|
||||
from houdini import ConflictResolution
|
||||
from houdini import Language
|
||||
from houdini import ClientType
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger = logging.getLogger('houdini')
|
||||
@ -81,6 +82,33 @@ if __name__ == '__main__':
|
||||
command_group.add_argument('-ccm', '--command-conflict-mode', action='store', dest='command_conflict_mode',
|
||||
default=config.commands['ConflictMode'].name,
|
||||
help='Command conflict mode', choices=['Silent', 'Append', 'Exception'])
|
||||
|
||||
client_group = parser.add_argument_group('client')
|
||||
client_mode = client_group.add_mutually_exclusive_group()
|
||||
client_mode.add_argument('--multi-client-mode', action='store_true',
|
||||
help='Run server with support for both clients')
|
||||
client_mode.add_argument('--single-client-mode', action='store_true',
|
||||
help='Run server with support for default client only')
|
||||
client_group.add_argument('--legacy-version', action='store',
|
||||
type=int,
|
||||
default=config.client['LegacyVersionChk'],
|
||||
help='Legacy client version to identify legacy clients')
|
||||
client_group.add_argument('--vanilla-version', action='store',
|
||||
type=int,
|
||||
default=config.client['VanillaVersionChk'],
|
||||
help='Vanilla client version to identify vanilla clients')
|
||||
client_group.add_argument('--default-version', action='store',
|
||||
type=int,
|
||||
default=config.client['DefaultVersionChk'],
|
||||
help='Default version to identify clients when multi-client is off')
|
||||
client_group.add_argument('--default-client', action='store',
|
||||
choices=['Legacy', 'Vanilla'],
|
||||
default=config.client['DefaultClientType'].name,
|
||||
help='Default client when multi-client is off')
|
||||
client_group.add_argument('-k', '--auth-key', action='store',
|
||||
default='houdini',
|
||||
help='Static key to use in place of the deprecated random key')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
database = {
|
||||
@ -101,6 +129,18 @@ if __name__ == '__main__':
|
||||
'ConflictMode': getattr(ConflictResolution, args.command_conflict_mode)
|
||||
}
|
||||
|
||||
client = {
|
||||
'MultiClientSupport': True if args.multi_client_mode else False if args.single_client_mode
|
||||
else config.client['MultiClientSupport'],
|
||||
'LegacyVersionChk': args.legacy_version,
|
||||
'VanillaVersionChk': args.vanilla_version,
|
||||
|
||||
'DefaultVersionChk': args.default_version,
|
||||
'DefaultClientType': getattr(ClientType, args.default_client),
|
||||
|
||||
'AuthStaticKey': args.auth_key
|
||||
}
|
||||
|
||||
server = {
|
||||
'Address': args.address or config.servers[args.server]['Address'],
|
||||
'Port': args.port or config.servers[args.server]['Port'],
|
||||
@ -129,6 +169,7 @@ if __name__ == '__main__':
|
||||
database=database,
|
||||
redis=redis,
|
||||
commands=commands,
|
||||
client=client,
|
||||
server=server)
|
||||
try:
|
||||
asyncio.run(factory_instance.start())
|
||||
|
@ -3,6 +3,7 @@ import enum
|
||||
import itertools
|
||||
import importlib
|
||||
import sys
|
||||
import config
|
||||
from types import FunctionType
|
||||
|
||||
from houdini.converters import _listener, _ArgumentDeserializer, get_converter, do_conversion, _ConverterContext
|
||||
@ -51,7 +52,7 @@ class Priority(enum.Enum):
|
||||
|
||||
class _Listener(_ArgumentDeserializer):
|
||||
|
||||
__slots__ = ['priority', 'packet', 'overrides', 'before', 'after']
|
||||
__slots__ = ['priority', 'packet', 'overrides', 'before', 'after', 'client_type']
|
||||
|
||||
def __init__(self, packet, callback, **kwargs):
|
||||
super().__init__(packet.id, callback, **kwargs)
|
||||
@ -60,6 +61,7 @@ class _Listener(_ArgumentDeserializer):
|
||||
self.priority = kwargs.get('priority', Priority.Low)
|
||||
self.before = kwargs.get('before')
|
||||
self.after = kwargs.get('after')
|
||||
self.client_type = kwargs.get('client')
|
||||
|
||||
self.overrides = kwargs.get('overrides', [])
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import config
|
||||
|
||||
from houdini import handlers
|
||||
from houdini import handlers, ClientType
|
||||
from houdini.handlers import XMLPacket
|
||||
from houdini.converters import VersionChkConverter
|
||||
|
||||
@ -10,7 +10,15 @@ from houdini.data.buddy import BuddyList
|
||||
@handlers.handler(XMLPacket('verChk'))
|
||||
@handlers.allow_once
|
||||
async def handle_version_check(p, version: VersionChkConverter):
|
||||
if not version == 153:
|
||||
if config.client['MultiClientSupport']:
|
||||
if config.client['LegacyVersionChk'] == version:
|
||||
p.client_type = ClientType.Legacy
|
||||
elif config.client['VanillaVersionChk'] == version:
|
||||
p.client_type = ClientType.Vanilla
|
||||
elif config.client['DefaultVersionChk'] == version:
|
||||
p.client_type = config.client['DefaultClientType']
|
||||
|
||||
if p.client_type is None:
|
||||
await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}})
|
||||
await p.close()
|
||||
else:
|
||||
@ -20,7 +28,7 @@ async def handle_version_check(p, version: VersionChkConverter):
|
||||
@handlers.handler(XMLPacket('rndK'))
|
||||
@handlers.allow_once
|
||||
async def handle_random_key(p, data):
|
||||
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': 'houdini'})
|
||||
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': config.client['AuthStaticKey']})
|
||||
|
||||
|
||||
async def get_server_presence(p, pid):
|
||||
|
@ -1,4 +1,4 @@
|
||||
from houdini import handlers
|
||||
from houdini import handlers, ClientType
|
||||
from houdini.handlers import XMLPacket, login
|
||||
from houdini.handlers.login import get_server_presence
|
||||
from houdini.converters import Credentials
|
||||
@ -27,7 +27,7 @@ async def handle_login(p, credentials: Credentials):
|
||||
data = await Penguin.query.where(Penguin.username == username).gino.first()
|
||||
|
||||
if data is None:
|
||||
p.logger.info('{} failed to login: penguin does not exist')
|
||||
p.logger.info('{} failed to login: penguin does not exist'.format(username))
|
||||
return await p.send_error_and_disconnect(100)
|
||||
|
||||
password_correct = await loop.run_in_executor(None, bcrypt.checkpw,
|
||||
@ -84,16 +84,19 @@ async def handle_login(p, credentials: Credentials):
|
||||
confirmation_hash = Crypto.hash(os.urandom(24))
|
||||
|
||||
tr = p.server.redis.multi_exec()
|
||||
tr.setex('{}.lkey'.format(data.id), p.server.server_config['KeyTTL'], login_key)
|
||||
tr.setex('{}.ckey'.format(data.id), p.server.server_config['KeyTTL'], confirmation_hash)
|
||||
tr.setex('{}.lkey'.format(data.username), p.server.server_config['KeyTTL'], login_key)
|
||||
tr.setex('{}.ckey'.format(data.username), p.server.server_config['KeyTTL'], confirmation_hash)
|
||||
await tr.execute()
|
||||
|
||||
world_populations, buddy_presence = await get_server_presence(p, data.id)
|
||||
|
||||
raw_login_data = '|'.join([str(data.id), str(data.id), data.username, login_key, str(data.approval),
|
||||
str(data.rejection)])
|
||||
await p.send_xt('l', raw_login_data, confirmation_hash, '', world_populations, buddy_presence,
|
||||
data.email)
|
||||
if p.client_type == ClientType.Vanilla:
|
||||
raw_login_data = '|'.join([str(data.id), str(data.id), data.username, login_key, str(data.approval),
|
||||
str(data.rejection)])
|
||||
await p.send_xt('l', raw_login_data, confirmation_hash, '', world_populations, buddy_presence,
|
||||
data.email)
|
||||
else:
|
||||
await p.send_xt('l', data.id, login_key, world_populations, buddy_presence)
|
||||
|
||||
handle_version_check = login.handle_version_check
|
||||
handle_random_key = login.handle_random_key
|
||||
|
@ -1,8 +1,11 @@
|
||||
from houdini import handlers
|
||||
import config
|
||||
|
||||
from houdini import handlers, ClientType
|
||||
from houdini.handlers import XMLPacket, login
|
||||
from houdini.converters import WorldCredentials
|
||||
from houdini.converters import WorldCredentials, Credentials
|
||||
from houdini.data.penguin import Penguin
|
||||
from houdini.data.moderator import Ban
|
||||
from houdini.crypto import Crypto
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@ -10,20 +13,26 @@ handle_version_check = login.handle_version_check
|
||||
handle_random_key = login.handle_random_key
|
||||
|
||||
|
||||
@handlers.handler(XMLPacket('login'))
|
||||
@handlers.handler(XMLPacket('login'), client=ClientType.Vanilla)
|
||||
@handlers.allow_once
|
||||
@handlers.depends_on_packet(XMLPacket('verChk'), XMLPacket('rndK'))
|
||||
async def handle_login(p, credentials: WorldCredentials):
|
||||
tr = p.server.redis.multi_exec()
|
||||
tr.get('{}.lkey'.format(credentials.id))
|
||||
tr.get('{}.ckey'.format(credentials.id))
|
||||
tr.delete('{}.lkey'.format(credentials.id), '{}.ckey'.format(credentials.id))
|
||||
tr.get('{}.lkey'.format(credentials.username))
|
||||
tr.get('{}.ckey'.format(credentials.username))
|
||||
tr.delete('{}.lkey'.format(credentials.username), '{}.ckey'.format(credentials.username))
|
||||
login_key, confirmation_hash, _ = await tr.execute()
|
||||
|
||||
if login_key is None or confirmation_hash is None:
|
||||
return await p.close()
|
||||
|
||||
if login_key.decode() != credentials.login_key or confirmation_hash.decode() != credentials.confirmation_hash:
|
||||
login_key = login_key.decode()
|
||||
login_hash = Crypto.encrypt_password(login_key + config.client['AuthStaticKey']) + login_key
|
||||
|
||||
if credentials.client_key != login_hash:
|
||||
return await p.close()
|
||||
|
||||
if login_key != credentials.login_key or confirmation_hash.decode() != credentials.confirmation_hash:
|
||||
return await p.close()
|
||||
|
||||
data = await Penguin.get(credentials.id)
|
||||
@ -47,5 +56,5 @@ async def handle_login(p, credentials: WorldCredentials):
|
||||
p.logger.info('{} logged in successfully'.format(data.username))
|
||||
|
||||
p.data = data
|
||||
p.login_key = credentials.login_key
|
||||
p.login_key = login_key
|
||||
await p.send_xt('l')
|
||||
|
@ -58,6 +58,7 @@ class Houdini:
|
||||
self.database_config_override = kwargs.get('database')
|
||||
self.redis_config_override = kwargs.get('redis')
|
||||
self.commands_config_override = kwargs.get('commands')
|
||||
self.client_config_override = kwargs.get('client')
|
||||
self.server_config_override = kwargs.get('server')
|
||||
self.server_config = None
|
||||
|
||||
@ -100,6 +101,7 @@ class Houdini:
|
||||
self.config.database.update(self.database_config_override)
|
||||
self.config.redis.update(self.redis_config_override)
|
||||
self.config.commands.update(self.commands_config_override)
|
||||
self.config.client.update(self.client_config_override)
|
||||
|
||||
general_log_directory = os.path.dirname(self.server_config["Logging"]["General"])
|
||||
errors_log_directory = os.path.dirname(self.server_config["Logging"]["Errors"])
|
||||
@ -215,8 +217,14 @@ class Houdini:
|
||||
self.configure_observers([handlers_path, ListenerFileEventHandler],
|
||||
[plugins_path, PluginFileEventHandler])
|
||||
|
||||
self.logger.info('Multi-client support is {}'.format(
|
||||
'enabled' if self.config.client['MultiClientSupport'] else 'disabled'))
|
||||
self.logger.info('Listening on {}:{}'.format(self.server_config['Address'], self.server_config['Port']))
|
||||
|
||||
if self.config.client['AuthStaticKey'] != 'houdini':
|
||||
self.logger.warning('The static key has been changed from the default, '
|
||||
'this may cause authentication issues!')
|
||||
|
||||
self.plugins.setup(houdini.plugins)
|
||||
|
||||
async with self.server:
|
||||
|
@ -13,7 +13,8 @@ from houdini.cooldown import CooldownError
|
||||
class Spheniscidae:
|
||||
|
||||
__slots__ = ['__reader', '__writer', 'server', 'logger',
|
||||
'peer_name', 'received_packets', 'joined_world']
|
||||
'peer_name', 'received_packets', 'joined_world',
|
||||
'client_type']
|
||||
|
||||
Delimiter = b'\x00'
|
||||
|
||||
@ -28,6 +29,7 @@ class Spheniscidae:
|
||||
self.server.peers_by_ip[self.peer_name] = self
|
||||
|
||||
self.joined_world = False
|
||||
self.client_type = None
|
||||
|
||||
self.received_packets = set()
|
||||
|
||||
@ -93,7 +95,8 @@ class Spheniscidae:
|
||||
packet_data = parsed_data[4:]
|
||||
|
||||
for listener in xt_listeners:
|
||||
await listener(self, packet_data)
|
||||
if listener.client_type is None or listener.client_type == self.client_type:
|
||||
await listener(self, packet_data)
|
||||
self.received_packets.add(packet)
|
||||
else:
|
||||
self.logger.warn('Handler for %s doesn\'t exist!', packet_id)
|
||||
@ -118,7 +121,8 @@ class Spheniscidae:
|
||||
xml_listeners = self.server.xml_listeners[packet]
|
||||
|
||||
for listener in xml_listeners:
|
||||
await listener(self, body_tag)
|
||||
if listener.client_type is None or listener.client_type == self.client_type:
|
||||
await listener(self, body_tag)
|
||||
|
||||
self.received_packets.add(packet)
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user