Compare commits

..

No commits in common. "30b6a71a1ca3c2096b7b8ab3339d892d9edb7164" and "3701bc441c7aebe320b8342db50ac9ad1c2ce979" have entirely different histories.

7 changed files with 43 additions and 159 deletions

View File

@ -7,9 +7,9 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
## TODOs ## TODOs
- [ ] Server core - [ ] Server core
- [x] BEAMP System - [ ] BEAMP System
- [x] Private access without key (Direct connect) - [x] Private access without key
- [x] Server authentication (For public access) - [ ] Server authentication (For public access)
- [X] Player authentication - [X] Player authentication
- [ ] TCP Server part: - [ ] TCP Server part:
- [x] Handle code - [x] Handle code
@ -34,8 +34,6 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
- [x] Create custom events - [x] Create custom events
- [ ] Return from events - [ ] Return from events
- [x] Plugins support - [x] Plugins support
- [x] Load Python plugins
- [ ] Load Lua plugins (Original BEAMP compatibility)
- [x] MultiLanguage (i18n support) - [x] MultiLanguage (i18n support)
- [x] Core - [x] Core
- [x] Console - [x] Console

View File

@ -21,9 +21,6 @@
"GUI_enter_key_message": "Please type your key:", "GUI_enter_key_message": "Please type your key:",
"GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Cannot open browser.\nUse this link: {}",
"": "Web phases",
"web_start": "WebAPI running on {} (Press CTRL+C to quit)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - display the manual page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Display the manual page for COMMAND.", "help_message_man": "Display the manual page for COMMAND.",

View File

@ -1,7 +1,7 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Привет из KuiToi-Server!", "hello": "Привет из KuiToi-Server!",
"config_path": "Используй {} для настройки.", "config_path": "Используя {} для настройки.",
"init_ok": "Инициализация окончена.", "init_ok": "Инициализация окончена.",
"start": "Сервер запущен!", "start": "Сервер запущен!",
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",
@ -21,9 +21,6 @@
"GUI_enter_key_message": "Пожалуйста введите ключ:", "GUI_enter_key_message": "Пожалуйста введите ключ:",
"GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}", "GUI_cannot_open_browser": "Не получилось открыть браузер.\nИспользуй эту ссылку: {}",
"": "Web phases",
"web_start": "WebAPI запустился на {} (CTRL+C для выключения)",
"": "Command: man", "": "Command: man",
"man_message_man": "man - Показывает страничку помощи для COMMAND.\nИспользование: man COMMAND", "man_message_man": "man - Показывает страничку помощи для COMMAND.\nИспользование: man COMMAND",
"help_message_man": "Показывает страничку помощи для COMMAND.", "help_message_man": "Показывает страничку помощи для COMMAND.",

View File

@ -45,7 +45,6 @@ if args.config:
config_path = args.config config_path = args.config
config_provider = ConfigProvider(config_path) config_provider = ConfigProvider(config_path)
config = config_provider.open_config() config = config_provider.open_config()
builtins.config = config
if config.Server['debug'] is True: if config.Server['debug'] is True:
utils.set_debug_status() utils.set_debug_status()
log.info("Debug enabled!") log.info("Debug enabled!")
@ -68,7 +67,8 @@ log.info(i18n.config_path.format(config_path))
log.debug("Initializing BEAMP Server system...") log.debug("Initializing BEAMP Server system...")
# Key handler.. # Key handler..
if not config.Auth['private'] and not config.Auth['key']: private = ((config.Auth['key'] is None or config.Auth['key'] == "") and config.Auth['private'])
if not private:
log.warn(i18n.auth_need_key) log.warn(i18n.auth_need_key)
url = "https://beammp.com/k/keys" url = "https://beammp.com/k/keys"
if shortcuts.yes_no_dialog( if shortcuts.yes_no_dialog(
@ -93,10 +93,11 @@ if not config.Auth['private'] and not config.Auth['key']:
ok_text=i18n.GUI_ok, ok_text=i18n.GUI_ok,
cancel_text=i18n.GUI_cancel).run() cancel_text=i18n.GUI_cancel).run()
config_provider.save_config() config_provider.save_config()
if not config.Auth['private'] and not config.Auth['key']: if not private:
log.error(i18n.auth_empty_key) log.error(i18n.auth_empty_key)
log.info(i18n.stop) log.info(i18n.stop)
exit(1) exit(1)
builtins.config = config
# Console Init # Console Init
log.debug("Initializing console...") log.debug("Initializing console...")
@ -106,6 +107,9 @@ console.builtins_hook()
console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop) console.add_command("stop", console.stop, i18n.man_message_stop, i18n.help_message_stop)
console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit) console.add_command("exit", console.stop, i18n.man_message_exit, i18n.help_message_exit)
if not os.path.exists("mods"):
os.mkdir("mods")
log.debug("Initializing PluginsLoader...") log.debug("Initializing PluginsLoader...")
if not os.path.exists("plugins"): if not os.path.exists("plugins"):
os.mkdir("plugins") os.mkdir("plugins")

View File

@ -5,19 +5,16 @@
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
import os
import time import time
import traceback
import zlib import zlib
from threading import Thread from threading import Thread
import aiohttp
import uvicorn import uvicorn
from core import utils from core import utils
from modules.WebAPISystem import app as webapp
from core.tcp_server import TCPServer from core.tcp_server import TCPServer
from core.udp_server import UDPServer from core.udp_server import UDPServer
from modules.WebAPISystem import app as webapp
class Client: class Client:
@ -136,7 +133,7 @@ class Client:
match code: match code:
case "H": case "H":
# Client connected # Client connected
await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8")) await self.tcp_send(b"Sn"+bytes(self.nick, "utf-8"))
case "C": case "C":
# Chat # Chat
await self.tcp_send(data) await self.tcp_send(data)
@ -147,12 +144,8 @@ class Core:
def __init__(self): def __init__(self):
self.log = utils.get_logger("core") self.log = utils.get_logger("core")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.run = False
self.direct = False
self.clients = {} self.clients = {}
self.clients_counter = 0 self.clients_counter = 0
self.mods_dir = "./mods"
self.mods_list = [0, ]
self.server_ip = config.Server["server_ip"] self.server_ip = config.Server["server_ip"]
self.server_port = config.Server["server_port"] self.server_port = config.Server["server_port"]
self.tcp = TCPServer self.tcp = TCPServer
@ -161,9 +154,6 @@ class Core:
self.web_pool = webapp.data_pool self.web_pool = webapp.data_pool
self.web_stop = None self.web_stop = None
self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0"
def get_client(self, sock=None, cid=None): def get_client(self, sock=None, cid=None):
if cid: if cid:
return self.clients.get(cid) return self.clients.get(cid)
@ -207,132 +197,37 @@ class Core:
await asyncio.sleep(1) await asyncio.sleep(1)
raise KeyboardInterrupt raise KeyboardInterrupt
# noinspection SpellCheckingInspection,PyPep8Naming
async def authenticate(self, test=False):
if config.Auth["private"] or self.direct:
if test:
self.log.info(f"Server runnig in Direct connect mode.")
self.direct = True
return
BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"]
modlist = ""
for mod in self.mods_list:
if type(mod) == int:
continue
modlist += f"/{os.path.basename(mod['path'])};"
modstotalsize = self.mods_list[0]
modstotal = len(self.mods_list) - 1
while self.run:
data = {"uuid": config.Auth["key"], "players": len(self.clients), "maxplayers": config.Game["players"],
"port": config.Server["server_port"], "map": f"/levels/{config.Game['map']}/info.json",
"private": config.Auth['private'], "version": self.BEAMP_version, "clientversion": self.client_major_version,
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize,
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False}
self.log.debug(f"Auth: data {data}")
# Sentry?
ok = False
body = {}
code = 0
for server_url in BEAM_backend:
url = "https://" + server_url + "/heartbeat"
try:
async with aiohttp.ClientSession() as session:
async with session.post(url, data=data, headers={"api-v": "2"}) as response:
code = response.status
body = await response.json()
self.log.debug(f"Auth: code {code}, body {body}")
ok = True
break
except Exception as e:
self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`")
continue
if ok:
if not (body.get("status") is not None and
body.get("code") is not None and
body.get("msg") is not None):
self.log.error("Missing/invalid json members in backend response")
raise KeyboardInterrupt
if test:
status = body.get("status")
msg = body.get("msg")
if status == "2000":
self.log.info(f"Authenticated! {msg}")
elif status == "200":
self.log.info(f"Resumed authenticated session. {msg}")
else:
self.log.error(f"Backend REFUSED the auth key. Reason: "
f"{msg or 'Backend did not provide a reason'}")
self.log.info(f"Server still runnig, but only in Direct connect mode.")
self.direct = True
else:
self.direct = True
if test:
self.log.error("Cannot auth...")
if not config.Auth['private']:
raise KeyboardInterrupt
if test:
self.log.info(f"Server still runnig, but only in Direct connect mode.")
if test:
return ok
await asyncio.sleep(5)
async def main(self): async def main(self):
try: self.tcp = self.tcp(self, self.server_ip, self.server_port)
self.run = True self.udp = self.udp(self, self.server_ip, self.server_port)
self.tcp = self.tcp(self, self.server_ip, self.server_port) tasks = [self.tcp.start(), self.udp.start(), console.start(), self.stop_me()] # self.check_alive()
self.udp = self.udp(self, self.server_ip, self.server_port) t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
if config.WebAPI["enabled"]:
# WebApi Start self.log.debug("Initializing WebAPI...")
if config.WebAPI["enabled"]: web_thread = Thread(target=self.start_web)
self.log.debug("Initializing WebAPI...") web_thread.start()
web_thread = Thread(target=self.start_web) self.web_thread = web_thread
web_thread.start() self.web_stop = webapp._stop
self.web_thread = web_thread self.log.info(i18n.start)
self.web_stop = webapp._stop # TODO: Server auth
ev.call_event("on_started")
# Mods handler await t
self.log.debug("Listing mods..") # while True:
if not os.path.exists(self.mods_dir): # try:
os.mkdir(self.mods_dir) # tasks = [console.start(), self.tcp.start(), self.udp.start()] # self.check_alive()
for file in os.listdir(self.mods_dir): # await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
path = os.path.join(self.mods_dir, file).replace("\\", "/") # except Exception as e:
if os.path.isfile(path) and path.endswith(".zip"): # await asyncio.sleep(1)
size = os.path.getsize(path) # print("Error: " + str(e))
self.mods_list.append({"path": path, "size": size}) # traceback.print_exc()
self.mods_list[0] += size # break
self.log.debug(f"mods_list: {self.mods_list}") # except KeyboardInterrupt:
lmods = len(self.mods_list) - 1 # raise KeyboardInterrupt
if lmods > 0:
self.log.info(f"Loaded {lmods} mods: {round(self.mods_list[0] / MB, 2)}mb")
await self.authenticate(True)
tasks = [self.tcp.start(), self.udp.start(), console.start(),
self.stop_me(), self.authenticate(),] # self.check_alive()
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
self.log.info(i18n.start)
ev.call_event("on_started")
await t
# Wait the end.
except Exception as e:
self.log.error(f"Exception: {e}")
traceback.print_exc()
except KeyboardInterrupt:
pass
finally:
self.run = False
def start(self): def start(self):
asyncio.run(self.main()) asyncio.run(self.main())
def stop(self): def stop(self):
self.run = False
self.log.info(i18n.stop) self.log.info(i18n.stop)
asyncio.run(self.web_stop()) asyncio.run(self.web_stop())
exit(0) exit(0)

View File

@ -40,30 +40,23 @@ class Client:
class Core: class Core:
def __init__(self): def __init__(self):
self.log = utils.get_logger("core")
self.loop = asyncio.get_event_loop()
self.run = False
self.direct = False
self.clients = dict()
self.clients_counter: int = 0 self.clients_counter: int = 0
self.mods_dir: str = "mods" self.log = utils.get_logger("core")
self.mods_list: list = [] self.clients = dict()
self.server_ip = config.Server["server_ip"] self.server_ip = config.Server["server_ip"]
self.server_port = config.Server["server_port"] self.server_port = config.Server["server_port"]
self.loop = asyncio.get_event_loop()
self.tcp = TCPServer self.tcp = TCPServer
self.udp = UDPServer self.udp = UDPServer
self.web_thread: Thread = None self.web_thread: Thread = None
self.web_stop: Callable = lambda: None self.web_stop: Callable = lambda: None
self.client_major_version = "2.0"
self.BEAMP_version = "3.2.0"
def insert_client(self, client: Client) -> None: ... def insert_client(self, client: Client) -> None: ...
def create_client(self, *args, **kwargs) -> Client: ... def create_client(self, *args, **kwargs) -> Client: ...
async def check_alive(self) -> None: ... async def check_alive(self) -> None: ...
@staticmethod @staticmethod
def start_web() -> None: ... def start_web() -> None: ...
@staticmethod @staticmethod
def stop_me() -> None: ... def stop_me(self) -> None: ...
async def authenticate(self, test=False) -> None: ...
async def main(self) -> None: ... async def main(self) -> None: ...
def start(self) -> None: ... def start(self) -> None: ...
def stop(self) -> None: ... def stop(self) -> None: ...

View File

@ -1,7 +1,7 @@
{ {
"": "Basic phases", "": "Basic phases",
"hello": "Привет из KuiToi-Server!", "hello": "Привет из KuiToi-Server!",
"config_path": "Используй {} для настройки.", "config_path": "Используя {} для настройки.",
"init_ok": "Инициализация окончена.", "init_ok": "Инициализация окончена.",
"start": "Сервер запущен!", "start": "Сервер запущен!",
"stop": "Сервер остановлен!", "stop": "Сервер остановлен!",