Compare commits

...

3 Commits

Author SHA1 Message Date
719e705bab Start adding RCON part! 2023-07-26 05:00:49 +03:00
21dd23cb55 Added Plugins loaders translations. 2023-07-26 04:28:57 +03:00
9295ed2b7a hotfix 2023-07-26 04:25:25 +03:00
14 changed files with 221 additions and 50 deletions

View File

@ -8,4 +8,5 @@ pydantic~=2.0.2
click~=8.1.4
lupa~=2.0
toml~=0.10.2
colorama~=0.4.6
colorama~=0.4.6
cryptography~=41.0.2

View File

@ -561,7 +561,7 @@ class Client:
await ev.call_async_event("onPlayerJoin", player=self)
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(i18n.game_welcome_message.format(self.nick), to_all=True) # Hello message
await self._send(f"J{i18n.game_welcome_message.format(self.nick)}", to_all=True) # Hello message
for client in self.__Core.clients:
if not client:

View File

@ -270,6 +270,9 @@ class Core:
tasks = []
# self.udp.start,
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
if config.RCON['enabled']:
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
f_tasks.append(rcon.start)
for task in f_tasks:
tasks.append(asyncio.create_task(task()))
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)

View File

@ -1,17 +1,14 @@
import secrets
from typing import Dict
class Config:
def __init__(self, auth=None, game=None, server=None, options=None, web=None):
self.Auth = auth or {"key": None, "private": True}
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1}
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
"server_ip": "0.0.0.0", "server_port": 30814}
self.Options = options or {"language": "en", "encoding": "utf8", "speed_limit": 0, "use_queue": False,
"debug": False}
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"secret_key": secrets.token_hex(16)}
Auth: Dict[str, object]
Game: Dict[str, object]
Server: Dict[str, object]
RCON: Dict[str, object]
Options: Dict[str, object]
WebAPI: Dict[str, object]
enc: str | None
def __repr__(self):
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)
class config (Config): ...

View File

@ -12,15 +12,17 @@ import yaml
class Config:
def __init__(self, auth=None, game=None, server=None, options=None, web=None):
def __init__(self, auth=None, game=None, server=None, rcon=None, options=None, web=None):
self.Auth = auth or {"key": None, "private": True}
self.Game = game or {"map": "gridmap_v2", "players": 8, "max_cars": 1}
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!",
"server_ip": "0.0.0.0", "server_port": 30814}
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
"password": secrets.token_hex(16)}
self.Options = options or {"language": "en", "encoding": "utf-8", "speed_limit": 0, "use_queue": False,
"debug": False, "use_lua": False, "log_chat": True}
self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"secret_key": secrets.token_hex(16)}
"access_token": secrets.token_hex(16)}
def __repr__(self):
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server)

View File

@ -0,0 +1,92 @@
import hashlib
import os
from base64 import b64decode, b64encode
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from core import get_logger
"""
shared key: SHA256 of "password"
<header>: "\x00\x00\x00\x00" (Byte order: Little Endian) - like you use
<iv>: A set of random bytes packed in base64 (New for each message)
-> To server
<- From server
Open TCP connection /
| -> "<iv>:hello" Without header, immediately with AES encryption (shared key)
| *Decrypt and some processes*
| Fail /
| | <- ":E:Bad key" | ":E:Error Message" Without header, without AES encryption
| | tcp.close() # End
| Success /
| | <- "<iv>:hello" with header, with AES encryption
| | (Next, everywhere with header, with AES encryption)
| -> "<iv>:<header>Cs:ver"
| <- "<iv>:<header>Os:KuiToi 0.4.3 | "<iv>:<header>Os:BeamMP 3.2.0"
| # Prints server and they version
| -> "<iv>:<header>Cs:commands"
| <- "<iv>:<header>Os:stop,help,plugins" | "<iv>:<header>Os:SKIP" For an autocomplete; "SKIP" For no autocomplete;
| *Ready to handle commands*
| -> "<iv>:<header>C:help"
| <- "<iv>:<header>O:stop: very cool stop\nhelp: Yayayayoy"
| -> "<iv>:<header>C:...."
| <- "<iv>:<header>O:...."
| -> "<iv>:<header>C:exit"
| tcp.close()
Codes:
* "hello" - Hello message
* "E:error_message" - Send RCON error
* "C:command" - Receive command
* "Cs:" - Receive system command
* "O:output" - Send command output
* "Os:" - Send system output
"""
class RCONSystem:
console = None
def __init__(self, key, host, port):
self.log = get_logger("RCON")
self.key = key
self.host = host
self.port = port
def encrypt(self, message, key):
self.log.debug(f"Encrypt message: {message}")
key = hashlib.sha256(key).digest()
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(message.encode('utf-8')) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
encoded_data = b64encode(encrypted_data)
encoded_iv = b64encode(iv)
return encoded_iv + b":" + encoded_data
def decrypt(self, ciphertext, key):
self.log.debug(f"Dencrypt message: {ciphertext}")
key = hashlib.sha256(key).digest()
encoded_iv, encoded_data = ciphertext.split(":")
iv = b64decode(encoded_iv)
encrypted_data = b64decode(encoded_data)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
decryptor = cipher.decryptor()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
return unpadded_data.decode('utf-8')
async def handle_client(self):
pass
async def start(self):
self.log.info("TODO: RCON")
async def stop(self):
pass

View File

@ -1,36 +1,34 @@
class Console(object):
from logging import Logger
from typing import AnyStr
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.") -> None: ...
from core import get_logger
def __getitem__(self, item): ...
@property
def alias(self) -> dict: ...
def add(self, key: str, func: function) -> dict: ...
def log(self, s: str, r='\r') -> None: ...
def write(self, s: str, r='\r') -> None: ...
def __lshift__(self, s: AnyStr) -> None: ...
def logger_hook(self) -> None: ...
def builtins_hook(self) -> None: ...
async def start(self) -> None: ...
class console(object):
class RCONSystem:
console = None
def __init__(self, key, host, port):
self.log = get_logger("RCON")
self.key = key
self.host = host
self.port = port
async def start(self): ...
async def stop(self): ...
class console:
rcon: RCONSystem = RCONSystem
@staticmethod
def alias() -> dict: ...
@staticmethod
def add_command(key: str, func: function) -> dict: ...
def add_command(key: str, func, man: str = None, desc: str = None, custom_completer: dict = None) -> dict: ...
@staticmethod
async def start() -> None: ...
@staticmethod
def builtins_hook() -> None: ...
@staticmethod
def logger_hook() -> None: ...
@staticmethod
def log(s: str) -> None: ...
@staticmethod

View File

@ -18,6 +18,7 @@ from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
from prompt_toolkit.patch_stdout import patch_stdout
from core import get_logger
from modules.ConsoleSystem import RCON
class Console:
@ -45,6 +46,9 @@ class Console:
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
custom_completer={"help": {"--raw": None}})
self.completer = NestedCompleter.from_nested_dict(self.__alias)
rcon = RCON
rcon.console = self
self.rcon = rcon
def __debug(self, *x):
self.__logger.debug(f"{x}")

View File

@ -580,13 +580,10 @@ class LuaPluginsLoader:
def load(self):
self.log.debug("Loading Lua plugins...")
# TODO: i18n
self.log.info("You have enabled support for Lua plugins.")
self.log.warning("There are some nuances to working with KuiToi. "
"If you have a proposal for their solution, and it is related to KuiToi, "
"please contact the developer.")
self.log.warning("Some BeamMP plugins require a correctly configured ServerConfig.toml file to function.")
self.log.info("Creating it.")
self.log.info(i18n.plugins_lua_enabled)
self.log.warning(i18n.plugins_lua_nuances_warning)
self.log.warning(i18n.plugins_lua_legacy_config_create_warning)
self.log.info(i18n.plugins_lua_legacy_config_create)
data = {
"info": "ServerConfig.toml is created solely for backward compatibility support. "
"This file will be updated every time the program is launched.",
@ -674,6 +671,6 @@ class LuaPluginsLoader:
self.log.debug("Unloading lua plugins")
for name, data in self.lua_plugins.items():
if data['ok']:
self.log.info(f"Unloading lua plugin: {name}")
self.log.info(i18n.plugins_lua_unload.format(name))
for _, timer in data['lua'].globals().MP._event_timers.items():
timer.stop()

View File

@ -131,21 +131,21 @@ class PluginsLoader:
try:
is_func = inspect.isfunction
if not is_func(plugin.load):
self.log.error('Function "def load():" not found.')
self.log.error(i18n.plugins_not_found_load)
ok = False
if not is_func(plugin.start):
self.log.error('Function "def start():" not found.')
self.log.error(i18n.plugins_not_found_start)
ok = False
if not is_func(plugin.unload):
self.log.error('Function "def unload():" not found.')
self.log.error(i18n.plugins_not_found_unload)
ok = False
if type(plugin.kt) != KuiToi:
self.log.error(f'Attribute "kt" isn\'t KuiToi class. Plugin file: "{file_path}"')
self.log.error(i18n.plugins_kt_invalid)
ok = False
except AttributeError:
ok = False
if not ok:
self.log.error(f'Plugin file: "{file_path}" is not a valid KuiToi plugin.')
self.log.error(i18n.plugins_invalid.format(file_path))
return
pl_name = plugin.kt.name
@ -185,9 +185,8 @@ class PluginsLoader:
self.loaded_str += f"{pl_name}:ok, "
self.log.debug(f"Plugin loaded: {file}. Settings: {self.plugins[pl_name]}")
except Exception as e:
# TODO: i18n
self.loaded_str += f"{file}:no, "
self.log.error(f"Error while loading plugin: {file}; Error: {e}")
self.log.error(i18n.plugins_error_loading.format(file, f"{e}"))
self.log.exception(e)
async def start(self, _):

View File

@ -57,6 +57,25 @@
"client_event_invalid_data": "从事件返回的数据无效:{}",
"client_player_disconnected": "离开服务器。游戏时间:{}分钟。",
"": "Events system",
"": "插件加载器",
"plugins_not_found_load": "未找到\"def load():\"函数。",
"plugins_not_found_start": "未找到\"def start():\"函数。",
"plugins_not_found_unload": "未找到\"def unload():\"函数。",
"plugins_kt_invalid": "“kt”变量不属于KuiToi类。",
"plugins_invalid": "无法在KuiToi中运行插件\"{}\"。",
"plugins_error_loading": "加载插件{}时出错:{}",
"": "Lua插件加载器",
"plugins_lua_enabled": "您已启用Lua插件支持。",
"plugins_lua_nuances_warning": "在使用KuiToi时有一些细微差别。如果您有关于解决方案的建议并且它与KuiToi相关请联系开发人员。",
"plugins_lua_legacy_config_create_warning": "一些BeamMP插件需要一个正确配置的ServerConfig.toml文件才能正常运行。",
"plugins_lua_legacy_config_create": "正在创建。",
"plugins_lua_unload": "停止Lua插件{}",
"": "命令man",
"man_message_man": "man - 显示COMMAND的帮助页面。\n用法man COMMAND",
"help_message_man": "显示COMMAND的帮助页面。",

View File

@ -57,6 +57,25 @@
"client_event_invalid_data": "Invalid data returned from event: {}",
"client_player_disconnected": "Left the server. Playtime: {} min",
"": "Events system",
"": "Plugins loader",
"plugins_not_found_load": "Function \"def load():\" not found.",
"plugins_not_found_start": "Function \"def start():\" not found.",
"plugins_not_found_unload": "Function \"def unload():\" not found.",
"plugins_kt_invalid": "\"kt\" variable does not belong to the KuiToi class.",
"plugins_invalid": "Plugin \"{}\" cannot be run in KuiToi.",
"plugins_error_loading": "An error occurred while loading the plugin {}: {}",
"": "Lua plugins loader",
"plugins_lua_enabled": "You have enabled Lua plugin support.",
"plugins_lua_nuances_warning": "There are some nuances when working with Kuiti. If you have a suggestion for their solution, and it is related to KuiToi, please contact the developer.",
"plugins_lua_legacy_config_create_warning": "Some BeamMP plugins require a properly configured ServerConfig.toml file to function.",
"plugins_lua_legacy_config_create": "Creating it.",
"plugins_lua_unload": "Stopping Lua plugin: {}",
"": "Command: man",
"man_message_man": "man - Shows the help page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Shows the help page for COMMAND.",

View File

@ -57,6 +57,25 @@
"client_event_invalid_data": "Из ивента вернулись не верные данные: {}",
"client_player_disconnected": "Вышел с сервера. Время игры: {} мин",
"": "Events system",
"": "Plugins loader",
"plugins_not_found_load": "Функция \"def load():\" не найдена.",
"plugins_not_found_start": "Функция \"def start():\" не найдена.",
"plugins_not_found_unload": "Функция \"def unload():\" не найдена.",
"plugins_kt_invalid": "Переменная \"kt\" не принадлежит классу KuiToi.",
"plugins_invalid": "Плагин: \"{}\" - не может быть запущен в KuiToi.",
"plugins_error_loading": "Произошла ошибка при загрузке плагина {}: {}",
"": "Lua plugins loader",
"plugins_lua_enabled": "Вы включили поддержку плагинов Lua.",
"plugins_lua_nuances_warning": "В работе с Kuiti есть некоторые нюансы. Если у вас есть предложение по их решению, и оно связано с KuiToi, пожалуйста, свяжитесь с разработчиком.",
"plugins_lua_legacy_config_create_warning": "Для работы некоторых плагинов BeamMP требуется правильно настроенный файл ServerConfig.toml.",
"plugins_lua_legacy_config_create": "Создаю его.",
"plugins_lua_unload": "Останавливаю Lua плагин: {}",
"": "Command: man",
"man_message_man": "man - Показывает страничку помощи для COMMAND.\nИспользование: man COMMAND",
"help_message_man": "Показывает страничку помощи для COMMAND.",

View File

@ -60,6 +60,27 @@ class i18n:
client_event_invalid_data: str
client_player_disconnected: str
# Events system
# Plugins loader
plugins_not_found_load: str
plugins_not_found_start: str
plugins_not_found_unload: str
plugins_kt_invalid: str
plugins_invalid: str
plugins_error_loading: str
# Lua plugins loader
plugins_lua_enabled: str
plugins_lua_nuances_warning: str
plugins_lua_legacy_config_create_warning: str
plugins_lua_legacy_config_create: str
plugins_lua_unload: str
# Command: man
man_message_man: str
help_message_man: str