[~] 0.4.5 > 0.4.6

[+] Recreate config (if empty)
[+] Hidden config
[!] FIX rl
[!] Update getting loop
[-] core_player_set_id
[~] Update copyrights
[~] Minor
This commit is contained in:
SantaSpeen committed 2024-07-16 17:18:49 +03:00
1 parent 2eb7e8801b
commit a923dbea1f
13 files changed
+136 -103

No files matched your search

+31 -34
View File
@@ -19,7 +19,7 @@ class Client:
def __init__(self, reader, writer, core): def __init__(self, reader, writer, core):
self.__reader = reader self.__reader = reader
self.__writer = writer self.__writer = writer
self.__Core = core self._core = core
self.__alive = True self.__alive = True
self.__packets_queue = [] self.__packets_queue = []
self.__tasks = [] self.__tasks = []
@@ -38,7 +38,7 @@ class Client:
self._identifiers = [] self._identifiers = []
self._cars = [None] * 21 # Max 20 cars per player + 1 snowman self._cars = [None] * 21 # Max 20 cars per player + 1 snowman
self._focus_car = -1 self._focus_car = -1
self._snowman = {"id": -1, "packet": ""} self._unicycle = {"id": -1, "packet": ""}
self._connect_time = 0 self._connect_time = 0
self._last_position = {} self._last_position = {}
self._lock = Lock() self._lock = Lock()
@@ -155,7 +155,7 @@ class Client:
if to_all: if to_all:
code = chr(data[0]) code = chr(data[0])
for client in self.__Core.clients: for client in self._core.clients:
if not client or (client is self and not to_self): if not client or (client is self and not to_self):
continue continue
if not to_udp or code in ['V', 'W', 'Y', 'E']: if not to_udp or code in ['V', 'W', 'Y', 'E']:
@@ -174,8 +174,7 @@ class Client:
data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION) data = b"ABG:" + zlib.compress(data, level=zlib.Z_BEST_COMPRESSION)
if to_udp: if to_udp:
udp_sock = self._udp_sock[0] udp_sock, udp_addr = self._udp_sock
udp_addr = self._udp_sock[1]
# self.log.debug(f'[UDP] len: {len(data)}; send: {data!r}') # self.log.debug(f'[UDP] len: {len(data)}; send: {data!r}')
if udp_sock and udp_addr: if udp_sock and udp_addr:
try: try:
@@ -297,7 +296,7 @@ class Client:
file = data[1:].decode(config.enc) file = data[1:].decode(config.enc)
self.log.info(i18n.client_mod_request.format(repr(file))) self.log.info(i18n.client_mod_request.format(repr(file)))
size = -1 size = -1
for mod in self.__Core.mods_list: for mod in self._core.mods_list:
if type(mod) == int: if type(mod) == int:
continue continue
if mod.get('path') == file: if mod.get('path') == file:
@@ -318,9 +317,9 @@ class Client:
await self.kick("Missing download socket") await self.kick("Missing download socket")
return return
if config.Options['use_queue']: if config.Options['use_queue']:
while self.__Core.lock_upload: while self._core.lock_upload:
await asyncio.sleep(.2) await asyncio.sleep(.2)
self.__Core.lock_upload = True self._core.lock_upload = True
speed = config.Options["speed_limit"] speed = config.Options["speed_limit"]
if speed: if speed:
speed = speed / 2 speed = speed / 2
@@ -332,8 +331,8 @@ class Client:
] ]
sl0, sl1 = await asyncio.gather(*uploads) sl0, sl1 = await asyncio.gather(*uploads)
tr = (time.monotonic() - t) or 0.0001 tr = (time.monotonic() - t) or 0.0001
if self.__Core.lock_upload: if self._core.lock_upload:
self.__Core.lock_upload = False self._core.lock_upload = False
msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr)) msg = i18n.client_mod_sent.format(round(size / MB, 3), math.ceil(size / tr / MB), int(tr))
if speed: if speed:
msg += i18n.client_mod_sent_limit.format(int(speed * 2)) msg += i18n.client_mod_sent_limit.format(int(speed * 2))
@@ -349,7 +348,7 @@ class Client:
elif data.startswith(b"SR"): elif data.startswith(b"SR"):
path_list = '' path_list = ''
size_list = '' size_list = ''
for mod in self.__Core.mods_list: for mod in self._core.mods_list:
if type(mod) == int: if type(mod) == int:
continue continue
path_list += f"{mod['path']};" path_list += f"{mod['path']};"
@@ -391,7 +390,7 @@ class Client:
car_data = data[2:] car_data = data[2:]
car_id = next((i for i, car in enumerate(self._cars) if car is None), len(self._cars)) car_id = next((i for i, car in enumerate(self._cars) if car is None), len(self._cars))
cars_count = len(self._cars) - self._cars.count(None) cars_count = len(self._cars) - self._cars.count(None)
if self._snowman['id'] != -1: if self._unicycle['id'] != -1:
cars_count -= 1 # -1 for unicycle cars_count -= 1 # -1 for unicycle
self.log.debug(f"car_id={car_id}, cars_count={cars_count}") self.log.debug(f"car_id={car_id}, cars_count={cars_count}")
car_json = {} car_json = {}
@@ -416,12 +415,12 @@ class Client:
snowman = car_json.get("jbm") == "unicycle" snowman = car_json.get("jbm") == "unicycle"
if allow and config.Game['cars'] > cars_count or (snowman and allow_snowman) or over_spawn: if allow and config.Game['cars'] > cars_count or (snowman and allow_snowman) or over_spawn:
if snowman: if snowman:
unicycle_id = self._snowman['id'] unicycle_id = self._unicycle['id']
if unicycle_id != -1: if unicycle_id != -1:
self.log.debug(f"Delete old unicycle: unicycle_id={unicycle_id}") self.log.debug(f"Delete old unicycle: car_id={unicycle_id}")
self._cars[unicycle_id] = None self._cars[unicycle_id] = None
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True) await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
self._snowman = {"id": car_id, "packet": pkt} self._unicycle = {"id": car_id, "packet": pkt}
self.log.debug(f"Unicycle spawn accepted: car_id={car_id}") self.log.debug(f"Unicycle spawn accepted: car_id={car_id}")
else: else:
self.log.debug(f"Car spawn accepted: car_id={car_id}") self.log.debug(f"Car spawn accepted: car_id={car_id}")
@@ -469,8 +468,8 @@ class Client:
car = self._cars[car_id] car = self._cars[car_id]
if car['snowman']: if car['snowman']:
self.log.debug(f"Snowman found") self.log.debug(f"Snowman found")
unicycle_id = self._snowman['id'] unicycle_id = self._unicycle['id']
self._snowman['id'] = -1 self._unicycle['id'] = -1
self._cars[unicycle_id] = None self._cars[unicycle_id] = None
self._cars[car_id] = None self._cars[car_id] = None
await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True) await self._send(f"Od:{self.cid}-{car_id}", to_all=True, to_self=True)
@@ -482,7 +481,7 @@ class Client:
async def _edit_car(self, raw_data, data): async def _edit_car(self, raw_data, data):
cid, car_id = self._get_cid_vid(raw_data) cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and self._cars[car_id]: if car_id != -1 and self._cars[car_id]:
client = self.__Core.get_client(cid=cid) client = self._core.get_client(cid=cid)
if client: if client:
car = client._cars[car_id] car = client._cars[car_id]
new_car_json = {} new_car_json = {}
@@ -506,8 +505,8 @@ class Client:
if cid == self.cid or allow or admin_allow: if cid == self.cid or allow or admin_allow:
if car['snowman']: if car['snowman']:
unicycle_id = self._snowman['id'] unicycle_id = self._unicycle['id']
self._snowman['id'] = -1 self._unicycle['id'] = -1
self.log.debug(f"Delete snowman") self.log.debug(f"Delete snowman")
await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True) await self._send(f"Od:{self.cid}-{unicycle_id}", to_all=True, to_self=True)
self._cars[unicycle_id] = None self._cars[unicycle_id] = None
@@ -593,7 +592,7 @@ class Client:
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(f"J{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: for client in self._core.clients:
if not client: if not client:
continue continue
for car in client._cars: for car in client._cars:
@@ -656,20 +655,18 @@ class Client:
self.__alive = False self.__alive = False
return return
# Codes: V W X Y
if 89 >= data[0] >= 86:
await self._send(data, to_all=True, to_self=False)
return
_bytes = False _bytes = False
try: try:
data = data.decode() data = data.decode()
except UnicodeDecodeError: except UnicodeDecodeError:
_bytes = True _bytes = True
self.log.error(f"UnicodeDecodeError: {data}") self.log.error(f"UnicodeDecodeError: {data}")
self.log.info("Some things are skipping...")
# Codes: p, Z in udp_server.py if data[0] in ['V', 'W', 'Y', 'E', 'N']:
await self._send(data, to_all=True, to_self=False)
return
# Codes: p, Z, X in udp_server.py
match data[0]: # At data[0] code match data[0]: # At data[0] code
case "H": # Map load, client ready case "H": # Map load, client ready
await self._connected_handler() await self._connected_handler()
@@ -700,8 +697,8 @@ class Client:
ev.call_lua_event(event_name, self.cid, even_data) ev.call_lua_event(event_name, self.cid, even_data)
ev.call_event(event_name, data=even_data, player=self) ev.call_event(event_name, data=even_data, player=self)
await ev.call_async_event(event_name, data=even_data, player=self) await ev.call_async_event(event_name, data=even_data, player=self)
case "N": case _:
await self._send(data, to_all=True, to_self=False) self.log.warning(f"TCP [{self.cid}] Unknown code: {data[0]}; {data}")
async def _looper(self): async def _looper(self):
ev.call_lua_event("onPlayerConnecting", self.cid) ev.call_lua_event("onPlayerConnecting", self.cid)
@@ -728,7 +725,7 @@ class Client:
await asyncio.sleep(0.3) await asyncio.sleep(0.3)
self.__alive = False self.__alive = False
if (self.cid > 0 or self.nick is not None) and \ if (self.cid > 0 or self.nick is not None) and \
self.__Core.clients_by_nick.get(self.nick): self._core.clients_by_nick.get(self.nick):
for i, car in enumerate(self._cars): for i, car in enumerate(self._cars):
if not car: if not car:
continue continue
@@ -746,9 +743,9 @@ class Client:
round((time.monotonic() - self._connect_time) / 60, 2) round((time.monotonic() - self._connect_time) / 60, 2)
) )
) )
self.__Core.clients[self.cid] = None self._core.clients[self.cid] = None
del self.__Core.clients_by_id[self.cid] del self._core.clients_by_id[self.cid]
del self.__Core.clients_by_nick[self.nick] del self._core.clients_by_nick[self.nick]
else: else:
self.log.debug(f"Removing client; Closing connection...") self.log.debug(f"Removing client; Closing connection...")
try: try:
+2 -2
View File
@@ -25,7 +25,7 @@ class Client:
self._log = utils.get_logger("client(id: )") self._log = utils.get_logger("client(id: )")
self._addr: Tuple[str, int] = writer.get_extra_info("sockname") self._addr: Tuple[str, int] = writer.get_extra_info("sockname")
self._loop = asyncio.get_event_loop() self._loop = asyncio.get_event_loop()
self.__Core: Core = core self._core: Core = core
self._cid: int = -1 self._cid: int = -1
self._key: str = None self._key: str = None
self.nick: str = None self.nick: str = None
@@ -37,7 +37,7 @@ class Client:
self._focus_car = -1 self._focus_car = -1
self._identifiers = [] self._identifiers = []
self._cars: List[Union[Dict[str, Union[str, bool, Dict[str, Union[str, List[int], float]]]], None]] = [] self._cars: List[Union[Dict[str, Union[str, bool, Dict[str, Union[str, List[int], float]]]], None]] = []
self._snowman: Dict[str, Union[int, str]] = {"id": -1, "packet": ""} self._unicycle: Dict[str, Union[int, str]] = {"id": -1, "packet": ""}
self._last_position = {} self._last_position = {}
self._lock = Lock() self._lock = Lock()
+7 -6
View File
@@ -9,12 +9,12 @@
__title__ = 'KuiToi-Server' __title__ = 'KuiToi-Server'
__description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.' __description__ = 'BeamingDrive Multiplayer server compatible with BeamMP clients.'
__url__ = 'https://github.com/kuitoi/kuitoi-Server' __url__ = 'https://github.com/kuitoi/kuitoi-Server'
__version__ = '0.4.5' __version__ = '0.4.6'
__build__ = 2303 # Я это считаю лог файлами __build__ = 2421 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"
__copyright__ = 'Copyright 2023 © SantaSpeen (Maxim Khomutov)' __copyright__ = 'Copyright 2024 © SantaSpeen (Maxim Khomutov)'
import asyncio import asyncio
import builtins import builtins
@@ -43,7 +43,7 @@ config_path = "kuitoi.yml"
if args.config: 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.read()
builtins.config = config builtins.config = config
config.enc = config.Options['encoding'] config.enc = config.Options['encoding']
if config.Options['debug'] is True: if config.Options['debug'] is True:
@@ -60,6 +60,7 @@ ml.builtins_hook()
log.debug("Initializing EventsSystem...") log.debug("Initializing EventsSystem...")
ev = EventsSystem() ev = EventsSystem()
ev.builtins_hook() ev.builtins_hook()
ev.register("get_version", lambda _: {"version": __version__, "build": __build__})
log.info(i18n.hello) log.info(i18n.hello)
log.info(i18n.config_path.format(config_path)) log.info(i18n.config_path.format(config_path))
@@ -68,7 +69,7 @@ log.debug("Initializing BeamMP Server system...")
# Key handler.. # Key handler..
if not config.Auth['private'] and not config.Auth['key']: if not config.Auth['private'] and not config.Auth['key']:
log.warn(i18n.auth_need_key) log.warn(i18n.auth_need_key)
url = "https://beammp.com/k/keys" url = "https://keymaster.beammp.com/login"
if shortcuts.yes_no_dialog( if shortcuts.yes_no_dialog(
title='BeamMP Server Key', title='BeamMP Server Key',
text=i18n.GUI_need_key_message, text=i18n.GUI_need_key_message,
@@ -90,7 +91,7 @@ if not config.Auth['private'] and not config.Auth['key']:
text=i18n.GUI_enter_key_message, text=i18n.GUI_enter_key_message,
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()
if not config.Auth['private'] and not config.Auth['key']: 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)
+48 -30
View File
@@ -27,7 +27,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.new_event_loop()
asyncio.set_event_loop(self.loop)
self.start_time = time.monotonic() self.start_time = time.monotonic()
self.run = False self.run = False
self.direct = False self.direct = False
@@ -47,7 +48,7 @@ class Core:
self.lock_upload = False self.lock_upload = False
self.client_major_version = "2.0" self.client_major_version = "2.0"
self.BeamMP_version = "3.4.1" # 20.07.2023 self.BeamMP_version = "3.4.1" # 16.07.2024
ev.register("_get_BeamMP_version", lambda x: tuple([int(i) for i in self.BeamMP_version.split(".")])) ev.register("_get_BeamMP_version", lambda x: tuple([int(i) for i in self.BeamMP_version.split(".")]))
ev.register("_get_player", lambda x: self.get_client(**x['kwargs'])) ev.register("_get_player", lambda x: self.get_client(**x['kwargs']))
@@ -103,6 +104,7 @@ class Core:
return out return out
async def check_alive(self): async def check_alive(self):
self.log.debug("Starting alive checker.")
maxp = config.Game['players'] maxp = config.Game['players']
try: try:
while self.run: while self.run:
@@ -138,6 +140,8 @@ class Core:
uvserver.run() uvserver.run()
async def stop_me(self): async def stop_me(self):
if not config.WebAPI['enabled']:
return
while webapp.data_run[0]: while webapp.data_run[0]:
await asyncio.sleep(1) await asyncio.sleep(1)
self.run = False self.run = False
@@ -145,31 +149,44 @@ class Core:
# noinspection SpellCheckingInspection,PyPep8Naming # noinspection SpellCheckingInspection,PyPep8Naming
async def heartbeat(self, test=False): async def heartbeat(self, test=False):
if config.Auth["private"] or self.direct: try:
if test: self.log.debug("Starting heartbeat.")
self.log.info(i18n.core_direct_mode) if config.Auth["private"] or self.direct:
self.direct = True if test:
return self.log.info(i18n.core_direct_mode)
self.direct = True
return
BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"] BEAM_backend = ["backend.beammp.com", "backup1.beammp.com", "backup2.beammp.com"]
modlist = "" _map = config.Game['map'] if "/" in config.Game['map'] else f"/levels/{config.Game['map']}/info.json"
for mod in self.mods_list: tags = config.Server['tags'].replace(", ", ";").replace(",", ";")
if type(mod) == int: if tags and tags[-1:] != ";":
continue tags += ";"
modlist += f"/{os.path.basename(mod['path'])};" modlist = "".join(f"/{os.path.basename(mod['path'])};" for mod in self.mods_list[1:])
modstotalsize = self.mods_list[0] modstotalsize = self.mods_list[0]
modstotal = len(self.mods_list) - 1 modstotal = len(self.mods_list) - 1
while self.run: while self.run:
try: playerslist = "".join(f"{client.nick};" for client in self.clients if client and client.alive)
data = {"uuid": config.Auth["key"], "players": len(self.clients_by_id), data = {
"maxplayers": config.Game["players"], "port": config.Server["server_port"], "uuid": config.Auth["key"],
"map": f"/levels/{config.Game['map']}/info.json", "private": config.Auth['private'], "players": len(self.clients_by_id),
"version": self.BeamMP_version, "clientversion": self.client_major_version, "maxplayers": config.Game["players"],
"name": config.Server["name"], "modlist": modlist, "modstotalsize": modstotalsize, "port": config.Server["server_port"],
"modstotal": modstotal, "playerslist": "", "desc": config.Server['description'], "pass": False} "map": _map,
"private": config.Auth['private'],
"version": self.BeamMP_version,
"clientversion": self.client_major_version,
"name": config.Server["name"],
"tags": tags,
"guests": not config.Auth["private"],
"modlist": modlist,
"modstotalsize": modstotalsize,
"modstotal": modstotal,
"playerslist": playerslist,
"desc": config.Server['description'],
"pass": False
}
# Sentry?
ok = False
body = {} body = {}
for server_url in BEAM_backend: for server_url in BEAM_backend:
url = "https://" + server_url + "/heartbeat" url = "https://" + server_url + "/heartbeat"
@@ -177,14 +194,15 @@ class Core:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.post(url, data=data, headers={"api-v": "2"}) as response: async with session.post(url, data=data, headers={"api-v": "2"}) as response:
code = response.status code = response.status
# text = await response.text()
# self.log.debug(f"[HB] res={text}")
body = await response.json() body = await response.json()
ok = True
break break
except Exception as e: except Exception as e:
self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`") self.log.debug(f"Auth: Error `{e}` while auth with `{server_url}`")
continue continue
if ok: if body:
if not (body.get("status") is not None and if not (body.get("status") is not None and
body.get("code") is not None and body.get("code") is not None and
body.get("msg") is not None): body.get("msg") is not None):
@@ -216,11 +234,11 @@ class Core:
# raise KeyboardInterrupt # raise KeyboardInterrupt
if test: if test:
return ok return bool(body)
await asyncio.sleep(5) await asyncio.sleep(5)
except Exception as e: except Exception as e:
self.log.error(f"Error in heartbeat: {e}") self.log.error(f"Error in heartbeat: {e}")
async def kick_cmd(self, args): async def kick_cmd(self, args):
if not len(args) > 0: if not len(args) > 0:
+3 -3
View File
@@ -94,9 +94,8 @@ class TCPServer:
await client.kick(i18n.core_player_kick_server_full) await client.kick(i18n.core_player_kick_server_full)
return False, client return False, client
else: else:
self.log.info(i18n.core_identifying_okay)
await self.Core.insert_client(client) await self.Core.insert_client(client)
client.log.info(i18n.core_player_set_id.format(client.pid)) client.log.info(i18n.core_identifying_okay)
return True, client return True, client
@@ -137,7 +136,8 @@ class TCPServer:
try: try:
ip = writer.get_extra_info('peername')[0] ip = writer.get_extra_info('peername')[0]
if self.rl.is_banned(ip): if self.rl.is_banned(ip):
self.rl.notify(ip, writer) await self.rl.notify(ip, writer)
writer.close()
break break
data = await reader.read(1) data = await reader.read(1)
if not data: if not data:
+17 -13
View File
@@ -18,7 +18,7 @@ class UDPServer(asyncio.DatagramTransport):
super().__init__() super().__init__()
self.log = utils.get_logger("UDPServer") self.log = utils.get_logger("UDPServer")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.Core = core self._core = core
self.host = host self.host = host
self.port = port self.port = port
self.run = False self.run = False
@@ -33,8 +33,10 @@ class UDPServer(asyncio.DatagramTransport):
code = data[2:3].decode() code = data[2:3].decode()
data = data[2:].decode() data = data[2:].decode()
client = self.Core.get_client(cid=cid) client = self._core.get_client(cid=cid)
if client: if client:
if not client.alive:
self.log.debug(f"{client.nick}:{cid} still sending UDP data: {data}")
match code: match code:
case "p": # Ping packet case "p": # Ping packet
ev.call_event("onSentPing") ev.call_event("onSentPing")
@@ -45,24 +47,26 @@ class UDPServer(asyncio.DatagramTransport):
self.log.debug(f"Set UDP Sock for CID: {cid}") self.log.debug(f"Set UDP Sock for CID: {cid}")
ev.call_event("onChangePosition", data=data) ev.call_event("onChangePosition", data=data)
sub = data.find("{", 1) sub = data.find("{", 1)
last_pos_data = data[sub:] last_pos = data[sub:]
try: try:
last_pos = json.loads(last_pos_data)
client._last_position = last_pos
_, car_id = client._get_cid_vid(data) _, car_id = client._get_cid_vid(data)
client._cars[car_id]['pos'] = last_pos if client._cars[car_id]:
last_pos = json.loads(last_pos)
client._last_position = last_pos
client._cars[car_id]['pos'] = last_pos
except Exception as e: except Exception as e:
self.log.debug(f"Cannot parse position packet: {e}") self.log.warning(f"Cannot parse position packet: {e}")
self.log.debug(f"data: {data}, sup: {sub}") self.log.debug(f"data: '{data}', sup: {sub}")
self.log.debug(f"last_pos_data: {last_pos_data}") self.log.debug(f"last_pos ({type(last_pos)}): {last_pos}")
await client._send(data, to_all=True, to_self=False, to_udp=True)
case "X":
await client._send(data, to_all=True, to_self=False, to_udp=True) await client._send(data, to_all=True, to_self=False, to_udp=True)
case _: case _:
self.log.debug(f"[{cid}] Unknown code: {code}") self.log.warning(f" UDP [{cid}] Unknown code: {code}; {data}")
else: else:
self.log.debug(f"[{cid}] Client not found.") self.log.debug(f"[{cid}] Client not found.")
except Exception as e: except Exception as e:
self.log.error(f"Error handle_datagram: {e}") self.log.error(f"Error handle_datagram: {e}")
def datagram_received(self, *args, **kwargs): def datagram_received(self, *args, **kwargs):
@@ -81,14 +85,14 @@ class UDPServer(asyncio.DatagramTransport):
async def _start(self): async def _start(self):
self.log.debug("Starting UDP server.") self.log.debug("Starting UDP server.")
while self.Core.run: while self._core.run:
try: try:
await asyncio.sleep(0.2) await asyncio.sleep(0.2)
d = UDPServer d = UDPServer
self.transport, p = await self.loop.create_datagram_endpoint( self.transport, p = await self.loop.create_datagram_endpoint(
lambda: d(self.Core), lambda: d(self._core),
local_addr=(self.host, self.port) local_addr=(self.host, self.port)
) )
d.transport = self.transport d.transport = self.transport
+1 -1
View File
@@ -18,7 +18,7 @@ class UDPServer(asyncio.DatagramTransport):
def __init__(self, core: Core, host=None, port=None, transport=None): def __init__(self, core: Core, host=None, port=None, transport=None):
self.log = utils.get_logger("UDPServer") self.log = utils.get_logger("UDPServer")
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.Core = core self._core = core
self.host = host self.host = host
self.port = port self.port = port
self.run = False self.run = False
+25 -8
View File
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import copy
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File modules.ConfigProvider # File modules.ConfigProvider
# Written by: SantaSpeen # Written by: SantaSpeen
@@ -16,12 +16,12 @@ class Config:
def __init__(self, auth=None, game=None, server=None, rcon=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.Auth = auth or {"key": None, "private": True}
self.Game = game or {"map": "gridmap_v2", "players": 8, "cars": 1} self.Game = game or {"map": "gridmap_v2", "players": 8, "cars": 1}
self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", self.Server = server or {"name": "KuiToi-Server", "description": "Welcome to KuiToi Server!", "tags": "Freroam",
"server_ip": "0.0.0.0", "server_port": 30814} "server_ip": "0.0.0.0", "server_port": 30814}
self.Options = options or {"language": "en", "speed_limit": 0, "use_queue": False,
"use_lua": False, "log_chat": True}
self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383, self.RCON = rcon or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 10383,
"password": secrets.token_hex(16)} "password": secrets.token_hex(6)}
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, self.WebAPI = web or {"enabled": False, "server_ip": "127.0.0.1", "server_port": 8433,
"access_token": secrets.token_hex(16)} "access_token": secrets.token_hex(16)}
@@ -36,7 +36,7 @@ class ConfigProvider:
self.config_path = config_path self.config_path = config_path
self.config = Config() self.config = Config()
def open_config(self): def read(self, _again=False):
if not os.path.exists(self.config_path): if not os.path.exists(self.config_path):
with open(self.config_path, "w", encoding="utf-8") as f: with open(self.config_path, "w", encoding="utf-8") as f:
yaml.dump(self.config, f) yaml.dump(self.config, f)
@@ -47,9 +47,26 @@ class ConfigProvider:
print("You have errors in the YAML syntax.") print("You have errors in the YAML syntax.")
print("Stopping server.") print("Stopping server.")
exit(1) exit(1)
if not self.config:
if _again:
print("Error: empty configuration.")
exit(1)
print("Empty config?..")
os.remove(self.config_path)
self.config = Config()
return self.read(True)
if not self.config.Options.get("debug"):
self.config.Options['debug'] = False
if not self.config.Options.get("encoding"):
self.config.Options['encoding'] = "utf-8"
return self.config return self.config
def save_config(self): def save(self):
_config = copy.deepcopy(self.config)
del _config.enc
del _config.Options['debug']
del _config.Options['encoding']
with open(self.config_path, "w", encoding="utf-8") as f: with open(self.config_path, "w", encoding="utf-8") as f:
yaml.dump(self.config, f) yaml.dump(_config, f)
+2 -1
View File
@@ -40,6 +40,7 @@ class RateLimiter:
if len(x) == 2: if len(x) == 2:
ip = x[1] ip = x[1]
if ip in _banned_ips: if ip in _banned_ips:
self._notified[ip] = False
self._calls[ip].clear() self._calls[ip].clear()
self._banned_until[ip] = datetime.now() self._banned_until[ip] = datetime.now()
return f"{ip} removed from banlist." return f"{ip} removed from banlist."
@@ -52,6 +53,7 @@ class RateLimiter:
sec = x[2] sec = x[2]
if not sec.isdigit(): if not sec.isdigit():
return f"{sec!r} is not digit." return f"{sec!r} is not digit."
self._notified[ip] = False
self._calls[ip].clear() self._calls[ip].clear()
self._banned_until[ip] = datetime.now() + timedelta(seconds=int(sec)) self._banned_until[ip] = datetime.now() + timedelta(seconds=int(sec))
return f"{ip} banned until {self._banned_until[ip]}" return f"{ip} banned until {self._banned_until[ip]}"
@@ -69,7 +71,6 @@ class RateLimiter:
try: try:
writer.write(b'\x0b\x00\x00\x00Eip banned.') writer.write(b'\x0b\x00\x00\x00Eip banned.')
await writer.drain() await writer.drain()
writer.close()
except Exception: except Exception:
pass pass
-2
View File
@@ -45,7 +45,6 @@ class i18n:
core_player_kick_stale: str core_player_kick_stale: str
core_player_kick_no_allowed_default_reason: str core_player_kick_no_allowed_default_reason: str
core_player_kick_server_full: str core_player_kick_server_full: str
core_player_set_id: str
core_identifying_okay: str core_identifying_okay: str
# In-game phrases # In-game phrases
@@ -110,5 +109,4 @@ class i18n:
# Command: exit # Command: exit
man_message_exit: str man_message_exit: str
help_message_exit: str help_message_exit: str
``` ```
-1
View File
@@ -41,7 +41,6 @@
"core_player_kick_stale": "过时的客户端。(由新连接替换)", "core_player_kick_stale": "过时的客户端。(由新连接替换)",
"core_player_kick_no_allowed_default_reason": "您不受欢迎。拒绝访问。", "core_player_kick_no_allowed_default_reason": "您不受欢迎。拒绝访问。",
"core_player_kick_server_full": "服务器已满。", "core_player_kick_server_full": "服务器已满。",
"core_player_set_id": "玩家设置ID {}",
"core_identifying_okay": "成功登录。", "core_identifying_okay": "成功登录。",
"": "游戏内短语", "": "游戏内短语",
-1
View File
@@ -41,7 +41,6 @@
"core_player_kick_stale": "Stale client. (Replaced by new connection)", "core_player_kick_stale": "Stale client. (Replaced by new connection)",
"core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.", "core_player_kick_no_allowed_default_reason": "You are not welcome on this server. Access denied.",
"core_player_kick_server_full": "Server is full.", "core_player_kick_server_full": "Server is full.",
"core_player_set_id": "Player set ID {}",
"core_identifying_okay": "Successful login.", "core_identifying_okay": "Successful login.",
"": "In-game phrases", "": "In-game phrases",
-1
View File
@@ -41,7 +41,6 @@
"core_player_kick_stale": "Устаревший клиент. (Заменено новым подключением)", "core_player_kick_stale": "Устаревший клиент. (Заменено новым подключением)",
"core_player_kick_no_allowed_default_reason": "Вам не рады на этом сервере. Вход запрещён.", "core_player_kick_no_allowed_default_reason": "Вам не рады на этом сервере. Вход запрещён.",
"core_player_kick_server_full": "Сервер полон.", "core_player_kick_server_full": "Сервер полон.",
"core_player_set_id": "Игрок получил ID {}",
"core_identifying_okay": "Успешный вход.", "core_identifying_okay": "Успешный вход.",
"": "In-game phrases", "": "In-game phrases",