mirror of
https://github.com/kuitoi/kuitoi-Server.git
synced 2025-08-17 16:25:36 +00:00
Compare commits
5 Commits
3701bc441c
...
30b6a71a1c
Author | SHA1 | Date | |
---|---|---|---|
30b6a71a1c | |||
b321595e7e | |||
10e03b2582 | |||
a9774a63cd | |||
eb28a4d5cf |
@ -7,9 +7,9 @@ BeamingDrive Multiplayer (BeamMP) server compatible with BeamMP clients.
|
|||||||
## TODOs
|
## TODOs
|
||||||
|
|
||||||
- [ ] Server core
|
- [ ] Server core
|
||||||
- [ ] BEAMP System
|
- [x] BEAMP System
|
||||||
- [x] Private access without key
|
- [x] Private access without key (Direct connect)
|
||||||
- [ ] Server authentication (For public access)
|
- [x] Server authentication (For public access)
|
||||||
- [X] Player authentication
|
- [X] Player authentication
|
||||||
- [ ] TCP Server part:
|
- [ ] TCP Server part:
|
||||||
- [x] Handle code
|
- [x] Handle code
|
||||||
@ -34,6 +34,8 @@ 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
|
||||||
|
@ -21,6 +21,9 @@
|
|||||||
"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.",
|
||||||
|
@ -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,6 +21,9 @@
|
|||||||
"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.",
|
||||||
|
@ -45,6 +45,7 @@ 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!")
|
||||||
@ -67,8 +68,7 @@ log.info(i18n.config_path.format(config_path))
|
|||||||
|
|
||||||
log.debug("Initializing BEAMP Server system...")
|
log.debug("Initializing BEAMP Server system...")
|
||||||
# Key handler..
|
# Key handler..
|
||||||
private = ((config.Auth['key'] is None or config.Auth['key'] == "") and config.Auth['private'])
|
if not config.Auth['private'] and not config.Auth['key']:
|
||||||
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,11 +93,10 @@ if not private:
|
|||||||
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 private:
|
if not config.Auth['private'] and not config.Auth['key']:
|
||||||
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...")
|
||||||
@ -107,9 +106,6 @@ 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")
|
||||||
|
137
src/core/core.py
137
src/core/core.py
@ -5,16 +5,19 @@
|
|||||||
# 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:
|
||||||
@ -133,7 +136,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)
|
||||||
@ -144,8 +147,12 @@ 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
|
||||||
@ -154,6 +161,9 @@ 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)
|
||||||
@ -197,37 +207,132 @@ 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.run = True
|
||||||
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
self.tcp = self.tcp(self, self.server_ip, self.server_port)
|
||||||
self.udp = self.udp(self, self.server_ip, self.server_port)
|
self.udp = self.udp(self, self.server_ip, self.server_port)
|
||||||
tasks = [self.tcp.start(), self.udp.start(), console.start(), self.stop_me()] # self.check_alive()
|
|
||||||
t = asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
|
# WebApi Start
|
||||||
if config.WebAPI["enabled"]:
|
if config.WebAPI["enabled"]:
|
||||||
self.log.debug("Initializing WebAPI...")
|
self.log.debug("Initializing WebAPI...")
|
||||||
web_thread = Thread(target=self.start_web)
|
web_thread = Thread(target=self.start_web)
|
||||||
web_thread.start()
|
web_thread.start()
|
||||||
self.web_thread = web_thread
|
self.web_thread = web_thread
|
||||||
self.web_stop = webapp._stop
|
self.web_stop = webapp._stop
|
||||||
|
|
||||||
|
# Mods handler
|
||||||
|
self.log.debug("Listing mods..")
|
||||||
|
if not os.path.exists(self.mods_dir):
|
||||||
|
os.mkdir(self.mods_dir)
|
||||||
|
for file in os.listdir(self.mods_dir):
|
||||||
|
path = os.path.join(self.mods_dir, file).replace("\\", "/")
|
||||||
|
if os.path.isfile(path) and path.endswith(".zip"):
|
||||||
|
size = os.path.getsize(path)
|
||||||
|
self.mods_list.append({"path": path, "size": size})
|
||||||
|
self.mods_list[0] += size
|
||||||
|
self.log.debug(f"mods_list: {self.mods_list}")
|
||||||
|
lmods = len(self.mods_list) - 1
|
||||||
|
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)
|
self.log.info(i18n.start)
|
||||||
# TODO: Server auth
|
|
||||||
ev.call_event("on_started")
|
ev.call_event("on_started")
|
||||||
await t
|
await t
|
||||||
# while True:
|
# Wait the end.
|
||||||
# try:
|
except Exception as e:
|
||||||
# tasks = [console.start(), self.tcp.start(), self.udp.start()] # self.check_alive()
|
self.log.error(f"Exception: {e}")
|
||||||
# await asyncio.wait(tasks, return_when=asyncio.FIRST_EXCEPTION)
|
traceback.print_exc()
|
||||||
# except Exception as e:
|
except KeyboardInterrupt:
|
||||||
# await asyncio.sleep(1)
|
pass
|
||||||
# print("Error: " + str(e))
|
finally:
|
||||||
# traceback.print_exc()
|
self.run = False
|
||||||
# break
|
|
||||||
# except KeyboardInterrupt:
|
|
||||||
# raise KeyboardInterrupt
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -40,23 +40,30 @@ class Client:
|
|||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.clients_counter: int = 0
|
|
||||||
self.log = utils.get_logger("core")
|
self.log = utils.get_logger("core")
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
self.run = False
|
||||||
|
self.direct = False
|
||||||
self.clients = dict()
|
self.clients = dict()
|
||||||
|
self.clients_counter: int = 0
|
||||||
|
self.mods_dir: str = "mods"
|
||||||
|
self.mods_list: list = []
|
||||||
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(self) -> None: ...
|
def stop_me() -> 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: ...
|
||||||
|
@ -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": "Сервер остановлен!",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user