22 Commits

Author SHA1 Message Date
SantaSpeen f145048cc1 FIX #1 2023-08-16 05:11:48 +03:00
SantaSpeen 68bf7d0d00 More debug info 2023-08-16 05:02:51 +03:00
SantaSpeen e9815cdfcf FIX _get_player #1 2023-08-16 05:02:39 +03:00
SantaSpeen acb2b45626 linux fix 2023-08-16 01:03:02 +03:00
SantaSpeen eb71fda356 0.4.4 -> 0.4.5 2023-08-15 22:56:43 +03:00
SantaSpeen cc400d5a12 0.4.3 -> 0.4.4 2023-08-15 22:53:42 +03:00
SantaSpeen 67b3bd26bb 0.4.3 -> 0.4.4 2023-08-15 22:53:20 +03:00
SantaSpeen 77250561fb Update build 2023-08-15 22:35:42 +03:00
SantaSpeen 70dfb9b40c Move translations 2023-08-15 22:34:27 +03:00
SantaSpeen 3118c74990 Update docs 2023-08-15 22:34:07 +03:00
SantaSpeen cb6adde7c2 prepare for .exe builds 2023-08-15 22:15:19 +03:00
SantaSpeen 422dd35a8f prepare for .exe build 2023-08-15 21:51:57 +03:00
SantaSpeen 2c2bd1cb4a n\f event fix 2023-08-15 19:31:39 +03:00
SantaSpeen 7eba3d5877 mod time fix 2023-08-15 16:56:41 +03:00
SantaSpeen abbd64184e t fix 2023-07-31 22:33:51 +03:00
SantaSpeen e5dd63579b RCON (WIP) 2023-07-31 21:38:36 +03:00
SantaSpeen ef286b7e03 RCON (WIP) 2023-07-31 21:38:08 +03:00
SantaSpeen 3a42fa13e7 Work time 2023-07-31 21:37:48 +03:00
SantaSpeen cdec0b9949 Minor fixes 2023-07-31 21:37:15 +03:00
SantaSpeen de91d075b4 Protocol fixes 2023-07-27 02:05:49 +03:00
SantaSpeen a7c02e0b52 Added kick command 2023-07-26 22:38:50 +03:00
SantaSpeen 6dd3de63a9 Added support async command 2023-07-26 22:37:32 +03:00
27 changed files with 486 additions and 230 deletions
+3
View File
@@ -140,3 +140,6 @@ dmypy.json
logs/ logs/
*.yml *.yml
*.toml *.toml
/win-ver_info.txt
/output/
+4 -3
View File
@@ -75,14 +75,15 @@ I didn't like writing plugins in Lua after using Python; it was very inconvenien
- [ ] Sync with event system - [ ] Sync with event system
- [ ] Add methods... - [ ] Add methods...
- [ ] RCON System: - [ ] RCON System:
- [ ] Serving - [x] Serving
- [ ] Client - [ ] Handle commands
- [x] Client
- [x] AES encryption - [x] AES encryption
- [ ] KuiToi System - [ ] KuiToi System
- [ ] Servers counter - [ ] Servers counter
- [ ] Players counter - [ ] Players counter
- [ ] Etc. - [ ] Etc.
- [ ] [Documentation](./docs/) - [ ] [Documentation](./docs)
## Installation ## Installation
+1 -1
View File
@@ -1,3 +1,3 @@
# MultiLanguage - i18n支持 # MultiLanguage - i18n支持
在 [example.json](./example.json) 中是 [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json) 的副本。如果你想将其翻译成以前未翻译过的语言,或者更新现有的翻译,我将很高兴接受你的拉取请求。 在 [example.json](./example.json) 中是 [src/modules/i18n/files/ru.json](../../../src/translates/ru.json) 的副本。如果你想将其翻译成以前未翻译过的语言,或者更新现有的翻译,我将很高兴接受你的拉取请求。
+1 -1
View File
@@ -1,4 +1,4 @@
# MultiLanguage - i18n Support # MultiLanguage - i18n Support
In [example.json](./example.json) you will find a copy of [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json).\ In [example.json](./example.json) you will find a copy of [src/modules/i18n/files/ru.json](../../../src/translates/ru.json).\
If you want to translate to a language that has not been translated before or update an existing translation, I would be happy to receive your pull requests. If you want to translate to a language that has not been translated before or update an existing translation, I would be happy to receive your pull requests.
+1 -1
View File
@@ -1,4 +1,4 @@
# MultiLanguage - Поддержка i18n # MultiLanguage - Поддержка i18n
В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/modules/i18n/files/ru.json)\ В [example.json](./example.json) это копия [src/modules/i18n/files/ru.json](../../../src/translates/ru.json)\
Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам. Если есть желание перевести на не переведённый ранее язык, или обновить уже существующий перевод буду рад вашим пул реквестам.
+35 -11
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -44,6 +44,10 @@ class Client:
def _writer(self): def _writer(self):
return self.__writer return self.__writer
@property
def alive(self):
return self.__alive
@property @property
def log(self): def log(self):
return self._log return self._log
@@ -185,8 +189,8 @@ class Client:
await writer.drain() await writer.drain()
return True return True
except ConnectionError: except Exception as e:
self.log.debug('[TCP] Disconnected') self.log.debug(f'[TCP] Disconnected: {e}')
self.__alive = False self.__alive = False
await self._remove_me() await self._remove_me()
return False return False
@@ -194,7 +198,13 @@ class Client:
async def _recv(self, one=False): async def _recv(self, one=False):
while self.__alive: while self.__alive:
try: try:
header = await self.__reader.read(4) header = b""
while len(header) < 4 and self.__alive:
h = await self.__reader.read(4)
if not h:
break
else:
header += h
int_header = int.from_bytes(header, byteorder='little', signed=True) int_header = int.from_bytes(header, byteorder='little', signed=True)
@@ -219,9 +229,14 @@ class Client:
self.__packets_queue.append(None) self.__packets_queue.append(None)
continue continue
data = await self.__reader.read(int_header) data = b""
while len(data) < int_header and self.__alive:
buffer = await self.__reader.read(int_header - len(data))
if not buffer:
break
else:
data += buffer
# self.log.debug(f"int_header: {int_header}; data: `{data}`;")
abg = b"ABG:" abg = b"ABG:"
if len(data) > len(abg) and data.startswith(abg): if len(data) > len(abg) and data.startswith(abg):
data = zlib.decompress(data[len(abg):]) data = zlib.decompress(data[len(abg):])
@@ -250,7 +265,7 @@ class Client:
try: try:
writer.write(data) writer.write(data)
await writer.drain() await writer.drain()
self.log.debug(f"[{who}] Sent {len(data)} bytes.") # self.log.debug(f"[{who}] Sent {len(data)} bytes.")
except ConnectionError: except ConnectionError:
self.__alive = False self.__alive = False
self.log.debug(f"[{who}] Disconnected.") self.log.debug(f"[{who}] Disconnected.")
@@ -269,6 +284,9 @@ class Client:
async def _sync_resources(self): async def _sync_resources(self):
while self.__alive: while self.__alive:
data = await self._recv(True) data = await self._recv(True)
if data is None:
await self._remove_me()
break
if data.startswith(b"f"): if data.startswith(b"f"):
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)))
@@ -307,7 +325,7 @@ class Client:
self._split_load(half_size, size, True, file, speed) self._split_load(half_size, size, True, file, speed)
] ]
sl0, sl1 = await asyncio.gather(*uploads) sl0, sl1 = await asyncio.gather(*uploads)
tr = time.monotonic() - t 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))
@@ -347,7 +365,7 @@ class Client:
id_sep = s.find('-') id_sep = s.find('-')
if id_sep == -1: if id_sep == -1:
self.log.debug( self.log.debug(
f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}'") f"Invalid packet: Could not parse pid/vid from packet, as there is no '-' separator: '{data}', {s}")
return -1, -1 return -1, -1
cid = s[:id_sep] cid = s[:id_sep]
vid = s[id_sep + 1:] vid = s[id_sep + 1:]
@@ -497,6 +515,12 @@ class Client:
else: else:
self.log.debug(f"Invalid car: car_id={car_id}") self.log.debug(f"Invalid car: car_id={car_id}")
async def reset_car(self, car_id, x, y, z, rot=None):
# TODO: reset_car
self.log.debug(f"Resetting car from plugin")
if rot is None:
rot = {"y": 0, "w": 0, "x": 0, "z": 0}
async def _reset_car(self, raw_data): async def _reset_car(self, raw_data):
cid, car_id = self._get_cid_vid(raw_data) cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and cid == self.cid and self._cars[car_id]: if car_id != -1 and cid == self.cid and self._cars[car_id]:
@@ -538,7 +562,7 @@ class Client:
case "t": # Broken details case "t": # Broken details
self.log.debug(f"Something changed/broken: {raw_data}") self.log.debug(f"Something changed/broken: {raw_data}")
cid, car_id = self._get_cid_vid(raw_data[5:]) cid, car_id = self._get_cid_vid(raw_data)
if car_id != -1 and cid == self.cid and self._cars[car_id]: if car_id != -1 and cid == self.cid and self._cars[car_id]:
data = raw_data[raw_data.find("{"):] data = raw_data[raw_data.find("{"):]
ev.call_event("onCarChanged", car_id=car_id, data=data) ev.call_event("onCarChanged", car_id=car_id, data=data)
@@ -547,7 +571,7 @@ class Client:
case "m": # Move focus car case "m": # Move focus car
self.log.debug(f"Move focus to: {raw_data}") self.log.debug(f"Move focus to: {raw_data}")
cid, car_id = self._get_cid_vid(raw_data[5:]) cid, car_id = self._get_cid_vid(raw_data[3:])
if car_id != -1 and cid == self.cid and self._cars[car_id]: if car_id != -1 and cid == self.cid and self._cars[car_id]:
self._focus_car = car_id self._focus_car = car_id
data = raw_data[raw_data.find("{"):] data = raw_data[raw_data.find("{"):]
+3 -1
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -42,6 +42,8 @@ class Client:
@property @property
def _writer(self) -> StreamWriter: ... def _writer(self) -> StreamWriter: ...
@property @property
def alive(self) -> bool: ...
@property
def log(self) -> Logger: ... def log(self) -> Logger: ...
@property @property
def addr(self) -> Tuple[str, int]: ... def addr(self) -> Tuple[str, int]: ...
+5 -5
View File
@@ -1,8 +1,8 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.__init__.py # File core.__init__.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.4 # Version 1.5
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
# Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai) # Special thanks to: AI Sage(https://poe.com/Sage), AI falcon-40b-v7(https://OpenBuddy.ai)
@@ -10,8 +10,8 @@
__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.3' __version__ = '0.4.5'
__build__ = 2125 # Я это считаю лог файлами __build__ = 2300 # Я это считаю лог файлами
__author__ = 'SantaSpeen' __author__ = 'SantaSpeen'
__author_email__ = 'admin@kuitoi.su' __author_email__ = 'admin@kuitoi.su'
__license__ = "FPA" __license__ = "FPA"
@@ -30,7 +30,7 @@ from modules import ConfigProvider, EventsSystem
from modules import Console from modules import Console
from modules import MultiLanguage from modules import MultiLanguage
args = parser.parse_args() args, _ = parser.parse_known_args()
if args.version: if args.version:
print(f"{__title__}:\n\tVersion: {__version__}\n\tBuild: {__build__}") print(f"{__title__}:\n\tVersion: {__version__}\n\tBuild: {__build__}")
exit(0) exit(0)
+32 -3
View File
@@ -1,18 +1,20 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.py # File core.core.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version: 0.4.3 # Version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
import math
import os import os
import random import random
import time
from threading import Thread from threading import Thread
import aiohttp import aiohttp
import uvicorn import uvicorn
from core import utils from core import utils, __version__
from core.Client import Client from core.Client import Client
from core.tcp_server import TCPServer from core.tcp_server import TCPServer
from core.udp_server import UDPServer from core.udp_server import UDPServer
@@ -26,6 +28,7 @@ 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.start_time = time.monotonic()
self.run = False self.run = False
self.direct = False self.direct = False
self.clients = [] self.clients = []
@@ -111,6 +114,8 @@ class Core:
if not client.ready: if not client.ready:
client.is_disconnected() client.is_disconnected()
continue continue
if not client.alive:
await client.kick("You are not alive!")
await client._send(ca) await client._send(ca)
except Exception as e: except Exception as e:
self.log.error("Error in check_alive.") self.log.error("Error in check_alive.")
@@ -217,6 +222,22 @@ class Core:
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):
if not len(args) > 0:
return "\nUsage: kick <nick>|:<id> [reason]\nExamples:\n\tkick admin bad boy\n\tkick :0 bad boy"
reason = "kicked by console."
if len(args) > 1:
reason = " ".join(args[1:])
cl = args[0]
if cl.startswith(":") and cl[1:].isdigit():
client = self.get_client(cid=int(cl[1:]))
else:
client = self.get_client(nick=cl)
if client:
await client.kick(reason)
else:
return "Client not found."
async def main(self): async def main(self):
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)
@@ -224,6 +245,7 @@ class Core:
"list", "list",
lambda x: f"Players list: {self.get_clients_list(True)}" lambda x: f"Players list: {self.get_clients_list(True)}"
) )
console.add_command("kick", self.kick_cmd)
pl_dir = "plugins" pl_dir = "plugins"
self.log.debug("Initializing PluginsLoaders...") self.log.debug("Initializing PluginsLoaders...")
@@ -271,6 +293,7 @@ class Core:
# self.udp.start, # self.udp.start,
f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive] f_tasks = [self.tcp.start, self.udp._start, console.start, self.stop_me, self.heartbeat, self.check_alive]
if config.RCON['enabled']: if config.RCON['enabled']:
console.rcon.version = f"KuiToi {__version__}"
rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port']) rcon = console.rcon(config.RCON['password'], config.RCON['server_ip'], config.RCON['server_port'])
f_tasks.append(rcon.start) f_tasks.append(rcon.start)
for task in f_tasks: for task in f_tasks:
@@ -307,6 +330,12 @@ class Core:
ev.call_event("_lua_plugins_unload") ev.call_event("_lua_plugins_unload")
await ev.call_async_event("_plugins_unload") await ev.call_async_event("_plugins_unload")
self.run = False self.run = False
self.log.info(i18n.stop)
if config.WebAPI["enabled"]: if config.WebAPI["enabled"]:
asyncio.run(self.web_stop()) asyncio.run(self.web_stop())
total_time = time.monotonic() - self.start_time
hours = int(total_time // 3600)
minutes = int((total_time % 3600) // 60)
seconds = math.ceil(total_time % 60)
t = f"{'' if not hours else f'{hours} hours, '}{'' if not hours else f'{minutes} min., '}{seconds} sec."
self.log.info(f"Working time: {t}")
self.log.info(i18n.stop)
+4 -1
View File
@@ -1,10 +1,11 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.core.pyi # File core.core.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
import time
from threading import Thread from threading import Thread
from typing import Callable, List, Dict from typing import Callable, List, Dict
@@ -16,6 +17,7 @@ from .udp_server import UDPServer
class Core: class Core:
def __init__(self): def __init__(self):
self.start_time = time.monotonic()
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.run = False
@@ -45,6 +47,7 @@ class Core:
def start_web() -> None: ... def start_web() -> None: ...
def stop_me(self) -> None: ... def stop_me(self) -> None: ...
async def heartbeat(self, test=False) -> None: ... async def heartbeat(self, test=False) -> None: ...
async def kick_cmd(self, args: list) -> None | str: ...
async def main(self) -> None: ... async def main(self) -> None: ...
def start(self) -> None: ... def start(self) -> None: ...
async def stop(self) -> None: ... async def stop(self) -> None: ...
+1 -1
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.py # File core.tcp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
+1 -1
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.tcp_server.pyi # File core.tcp_server.pyi
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
+3 -3
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
@@ -100,8 +100,8 @@ class UDPServer(asyncio.DatagramTransport):
await asyncio.sleep(0.2) await asyncio.sleep(0.2)
except OSError as e: except OSError as e:
self.run = False # self.run = False
self.Core.run = False # self.Core.run = False
self.log.error(f"Cannot bind port or other error: {e}") self.log.error(f"Cannot bind port or other error: {e}")
except Exception as e: except Exception as e:
self.log.error(f"Error: {e}") self.log.error(f"Error: {e}")
+1 -1
View File
@@ -1,7 +1,7 @@
# Developed by KuiToi Dev # Developed by KuiToi Dev
# File core.udp_server.py # File core.udp_server.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import asyncio import asyncio
+1 -1
View File
@@ -2,7 +2,7 @@
# File core.utils.py # File core.utils.py
# Written by: SantaSpeen # Written by: SantaSpeen
# Version 1.1 # Version 1.1
# Core version: 0.4.3 # Core version: 0.4.5
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import datetime import datetime
@@ -25,7 +25,8 @@ class Config:
"access_token": secrets.token_hex(16)} "access_token": secrets.token_hex(16)}
def __repr__(self): def __repr__(self):
return "%s(Auth=%r, Game=%r, Server=%r)" % (self.__class__.__name__, self.Auth, self.Game, self.Server) return f"{self.__class__.__name__}(Auth={self.Auth!r}, Game={self.Game!r}, Server={self.Server!r}, " \
f"RCON={self.RCON!r}, Options={self.Options!r}, WebAPI={self.WebAPI!r})"
class ConfigProvider: class ConfigProvider:
+150 -56
View File
@@ -1,5 +1,8 @@
import asyncio
import binascii
import hashlib import hashlib
import os import os
import zlib
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from cryptography.hazmat.primitives import padding from cryptography.hazmat.primitives import padding
@@ -7,86 +10,177 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from core import get_logger 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: class RCONSystem:
console = None console = None
version = "verError"
def __init__(self, key, host, port): def __init__(self, key, host, port):
self.log = get_logger("RCON") self.log = get_logger("RCON")
self.key = key self.key = hashlib.sha256(key.encode(config.enc)).digest()
self.host = host self.host = host
self.port = port self.port = port
self.run = False
def encrypt(self, message, key): def _encrypt(self, message):
self.log.debug(f"Encrypt message: {message}") self.log.debug(f"Encrypt message: {message}")
key = hashlib.sha256(key).digest()
iv = os.urandom(16) iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
encryptor = cipher.encryptor() encryptor = cipher.encryptor()
padder = padding.PKCS7(algorithms.AES.block_size).padder() padder = padding.PKCS7(algorithms.AES.block_size).padder()
padded_data = padder.update(message.encode('utf-8')) + padder.finalize() padded_data = padder.update(message) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize() encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
encoded_data = b64encode(encrypted_data) encoded_data = b64encode(zlib.compress(encrypted_data, level=zlib.Z_BEST_COMPRESSION))
encoded_iv = b64encode(iv) encoded_iv = b64encode(iv)
return encoded_iv + b":" + encoded_data return encoded_iv + b":" + encoded_data
def decrypt(self, ciphertext, key): def _decrypt(self, ciphertext):
self.log.debug(f"Dencrypt message: {ciphertext}") self.log.debug(f"Decrypt message: {ciphertext}")
key = hashlib.sha256(key).digest() encoded_iv, encoded_data = ciphertext.split(b":", 2)
encoded_iv, encoded_data = ciphertext.split(":")
iv = b64decode(encoded_iv) iv = b64decode(encoded_iv)
encrypted_data = b64decode(encoded_data) encrypted_data = zlib.decompress(b64decode(encoded_data))
cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv))
decryptor = cipher.decryptor() decryptor = cipher.decryptor()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize() decrypted_data = decryptor.update(encrypted_data) + decryptor.finalize()
unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize() unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
return unpadded_data.decode('utf-8') return unpadded_data
async def handle_client(self): async def _recv(self, reader, writer) -> tuple[str, bool]:
try:
header = b""
while len(header) < 4:
h = await reader.read(4 - len(header))
if not h:
break
else:
header += h
header = int.from_bytes(header, byteorder='little', signed=True)
if header <= 0:
self.log.warning("Connection closed!")
writer.close()
encrypted_data = b""
while len(encrypted_data) < header:
buffer = await reader.read(header - len(encrypted_data))
if not buffer:
break
else:
encrypted_data += buffer
try:
data, s = self._decrypt(encrypted_data), True
except binascii.Error:
data, s = encrypted_data, False
except ValueError:
data, s = encrypted_data, False
self.log.debug(f"Received: {data}, {s}")
return data.decode(config.enc), s
except ConnectionResetError:
self.log.warning("Connection reset.")
return "", False
async def _send(self, data, writer, encrypt=True, warn=True):
self.log.debug(f"Sending: \"{data}\"")
if isinstance(data, str):
data = data.encode(config.enc)
if encrypt:
data = self._encrypt(data)
self.log.debug(f"Send encrypted: {data}")
header = len(data).to_bytes(4, "little", signed=True)
try:
writer.write(header + data)
await writer.drain()
return True
except ConnectionError:
self.log.debug("Sending error...")
if encrypt and warn:
self.log.warning("Connection closed!")
return False
async def send_hello(self, writer, work):
while work[0]:
await asyncio.sleep(5)
if not await self._send("Cs:hello", writer, warn=False):
work[0] = False
writer.close()
break
async def while_handle(self, reader, writer):
ver, status = await self._recv(reader, writer)
if ver == "ver" and status:
await self._send(self.version, writer)
cmds, status = await self._recv(reader, writer)
if cmds == "commands" and status:
await self._send("SKIP", writer)
work = [True]
t = asyncio.create_task(self.send_hello(writer, work))
while work[0]:
data, status = await self._recv(reader, writer)
if not status:
work[0] = False
writer.close()
break
code = data[:2]
message = data[data.find(":") + 1:]
match code:
case "Cs":
match message:
case "hello":
await self._send("Os:hello", writer)
case _:
self.log.warning(f"Unknown command: {data}")
case "C:":
self.log.info(f"Called the command: {message}")
if message == "exit":
self.log.info("Connection closed.")
writer.close()
work[0] = False
break
case "Os":
match message:
case "hello":
pass pass
# await self._send("Cs:hello", writer)
case _:
self.log.warning(f"Unknown command: {data}")
case "O:":
pass
case _:
self.log.warning(f"Unknown command: {data}")
await t
async def handle_connect(self, reader, writer):
try:
hello, status = await self._recv(reader, writer)
if hello == "hello" and status:
await self._send("hello", writer)
await self.while_handle(reader, writer)
else:
await self._send("E:Wrong password", writer, False)
writer.close()
except Exception as e:
self.log.error("Error while handling connection...")
self.log.exception(e)
async def start(self): async def start(self):
self.log.info("TODO: RCON") self.run = True
try:
async def stop(self): server = await asyncio.start_server(self.handle_connect, self.host, self.port, backlog=5)
self.log.info(f"RCON server started on {server.sockets[0].getsockname()!r}")
async with server:
await server.serve_forever()
except OSError as e:
self.log.error(i18n.core_bind_failed.format(e))
raise e
except KeyboardInterrupt:
pass pass
except Exception as e:
self.log.error(f"Error: {e}")
raise e
finally:
self.run = False
+12 -4
View File
@@ -7,6 +7,7 @@
# Licence: FPA # Licence: FPA
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import builtins import builtins
import inspect
import logging import logging
from typing import AnyStr from typing import AnyStr
@@ -14,11 +15,14 @@ from prompt_toolkit import PromptSession, print_formatted_text, HTML
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.completion import NestedCompleter from prompt_toolkit.completion import NestedCompleter
from prompt_toolkit.history import FileHistory from prompt_toolkit.history import FileHistory
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError try:
from prompt_toolkit.output.win32 import NoConsoleScreenBufferError
except AssertionError:
class NoConsoleScreenBufferError(Exception): ...
from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.patch_stdout import patch_stdout
from core import get_logger from core import get_logger
from modules.ConsoleSystem import RCON from modules.ConsoleSystem.RCON import RCONSystem
class Console: class Console:
@@ -46,7 +50,7 @@ class Console:
self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help, self.add_command("help", self.__create_help_message, i18n.man_message_help, i18n.help_message_help,
custom_completer={"help": {"--raw": None}}) custom_completer={"help": {"--raw": None}})
self.completer = NestedCompleter.from_nested_dict(self.__alias) self.completer = NestedCompleter.from_nested_dict(self.__alias)
rcon = RCON rcon = RCONSystem
rcon.console = self rcon.console = self
self.rcon = rcon self.rcon = rcon
@@ -242,7 +246,11 @@ class Console:
self.log(text) self.log(text)
command_object = self.__func.get(cmd) command_object = self.__func.get(cmd)
if command_object: if command_object:
out = command_object['f'](cmd_s[1:]) func = command_object['f']
if inspect.iscoroutinefunction(func):
out = await func(cmd_s[1:])
else:
out = func(cmd_s[1:])
if out: if out:
self.log(out) self.log(out)
else: else:
+9 -4
View File
@@ -73,6 +73,11 @@ class EventsSystem:
self.log.debug("used builtins_hook") self.log.debug("used builtins_hook")
builtins.ev = self builtins.ev = self
def is_event(self, event_name):
return (event_name in self.__async_events.keys() or
event_name in self.__events.keys() or
event_name in self.__lua_events.keys())
def register_event(self, event_name, event_func, async_event=False, lua=None): def register_event(self, event_name, event_func, async_event=False, lua=None):
self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', " self.log.debug(f"register_event(event_name='{event_name}', event_func='{event_func}', "
f"async_event={async_event}, lua_event={lua}):") f"async_event={async_event}, lua_event={lua}):")
@@ -112,7 +117,7 @@ class EventsSystem:
except Exception as e: except Exception as e:
self.log.error(i18n.events_calling_error.format(event_name, func.__name__)) self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
self.log.exception(e) self.log.exception(e)
else: elif not self.is_event(event_name):
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_event()")) self.log.warning(i18n.events_not_found.format(event_name, "kt.call_event()"))
return funcs_data return funcs_data
@@ -130,13 +135,13 @@ class EventsSystem:
except Exception as e: except Exception as e:
self.log.error(i18n.events_calling_error.format(event_name, func.__name__)) self.log.error(i18n.events_calling_error.format(event_name, func.__name__))
self.log.exception(e) self.log.exception(e)
else: elif not self.is_event(event_name):
self.log.warning(i18n.events_not_found.format(event_name, "kt.call_async_event()")) self.log.warning(i18n.events_not_found.format(event_name, "kt.call_async_event()"))
return funcs_data return funcs_data
def call_lua_event(self, event_name, *args): def call_lua_event(self, event_name, *args):
self.log.debug(f"Calling lua event: '{event_name}'") self.log.debug(f"Calling lua event: '{event_name} ({args})'")
funcs_data = [] funcs_data = []
if event_name in self.__lua_events.keys(): if event_name in self.__lua_events.keys():
for data in self.__lua_events[event_name]: for data in self.__lua_events[event_name]:
@@ -151,7 +156,7 @@ class EventsSystem:
funcs_data.append(fd) funcs_data.append(fd)
except Exception as e: except Exception as e:
self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}")) self.log.error(i18n.events_lua_calling_error.format(f"{e}", event_name, func_name, f"{args}"))
else: elif not self.is_event(event_name):
self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()")) self.log.warning(i18n.events_not_found.format(event_name, "ev.call_lua_event(), MP.Trigger<>Event()"))
return funcs_data return funcs_data
@@ -110,7 +110,7 @@ class MP:
def TriggerLocalEvent(self, event_name, *args): def TriggerLocalEvent(self, event_name, *args):
self.log.debug("request TriggerLocalEvent()") self.log.debug("request TriggerLocalEvent()")
self.log.debug(f"Calling local lua event: '{event_name}'") self.log.debug(f"Calling local lua event: '{event_name} ({args})'")
funcs_data = [] funcs_data = []
if event_name in self._local_events.keys(): if event_name in self._local_events.keys():
for func_name in self._local_events[event_name]: for func_name in self._local_events[event_name]:
@@ -157,7 +157,7 @@ class MP:
to_all = False to_all = False
if player_id < 0: if player_id < 0:
to_all = True to_all = True
client = client[0] client = client[0] if len(client) > 0 else None
if client and event_name and data: if client and event_name and data:
t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all)) t = self.loop.create_task(client.send_event(event_name, data, to_all=to_all))
self.tasks.append(t) self.tasks.append(t)
@@ -168,7 +168,7 @@ class MP:
return False, "Can't found event_name or data" return False, "Can't found event_name or data"
def TriggerClientEventJson(self, player_id, event_name, data): def TriggerClientEventJson(self, player_id, event_name, data):
self.log.debug("request TriggerClientEventJson()") self.log.debug(f"request TriggerClientEventJson({player_id, event_name, data})")
data = self._lua.globals().Util.JsonEncode(data) data = self._lua.globals().Util.JsonEncode(data)
self.TriggerClientEvent(player_id, event_name, data) self.TriggerClientEvent(player_id, event_name, data)
@@ -232,7 +232,7 @@ class MP:
def GetPlayers(self): def GetPlayers(self):
self.log.debug("request GetPlayers()") self.log.debug("request GetPlayers()")
clients = ev.call_event("_get_players", cid=-1) clients = ev.call_event("_get_player", cid=-1)[0]
return self._lua.table_from(clients) return self._lua.table_from(clients)
def IsPlayerGuest(self, player_id) -> bool: def IsPlayerGuest(self, player_id) -> bool:
@@ -312,12 +312,18 @@ class Util:
return {k: v for k, v in new_dict.items() if v is not None} return {k: v for k, v in new_dict.items() if v is not None}
def JsonEncode(self, table): def JsonEncode(self, table):
data = {}
try:
self.log.debug("requesting JsonEncode()") self.log.debug("requesting JsonEncode()")
if all(isinstance(k, int) for k in table.keys()): if all(isinstance(k, int) for k in table.keys()):
data = self._recursive_list_encode(table) data = self._recursive_list_encode(table)
else: else:
data = self._recursive_dict_encode(table) data = self._recursive_dict_encode(table)
return json.dumps(data) except Exception as e:
self.log.exception(e)
data = json.dumps(data)
self.log.debug(f"Encoded: {data}")
return data
def JsonDecode(self, string): def JsonDecode(self, string):
self.log.debug("requesting JsonDecode()") self.log.debug("requesting JsonDecode()")
+26 -50
View File
@@ -8,6 +8,7 @@
# (c) kuitoi.su 2023 # (c) kuitoi.su 2023
import builtins import builtins
import json import json
import os
from json import JSONDecodeError from json import JSONDecodeError
from core.utils import get_logger from core.utils import get_logger
@@ -25,43 +26,21 @@ class i18n:
class MultiLanguage: class MultiLanguage:
def __init__(self, language: str = None, files_dir="modules/i18n/files/", encoding=None): def __init__(self, language: str = None, files_dir="translates/", encoding=None):
if encoding is None: if encoding is None:
encoding = config.enc encoding = config.enc
if language is None: if language is None:
language = "en" language = "en"
self.__data = {}
self.__i18n = None
self.__encoding = encoding
self.language = language
self.files_dir = files_dir
self.log = get_logger("i18n")
self.set_language(language)
def set_language(self, language):
if language is None:
language = "en"
self.log.debug(f"set_language({language})")
self.language = language
if language != "en":
self.open_file()
else:
# noinspection PyDictDuplicateKeys
self.__data = { self.__data = {
"": "Basic phases",
"hello": "Hello from KuiToi-Server!", "hello": "Hello from KuiToi-Server!",
"config_path": "Use {} to configure.", "config_path": "Use {} to configure.",
"init_ok": "Initialization completed.", "init_ok": "Initialization completed.",
"start": "Server started!", "start": "Server started!",
"stop": "Server stopped!", "stop": "Server stopped!",
"": "Server auth",
"auth_need_key": "BeamMP key is required to run!", "auth_need_key": "BeamMP key is required to run!",
"auth_empty_key": "BeamMP key is empty!", "auth_empty_key": "BeamMP key is empty!",
"auth_cannot_open_browser": "Failed to open browser: {}", "auth_cannot_open_browser": "Failed to open browser: {}",
"auth_use_link": "Use this link: {}", "auth_use_link": "Use this link: {}",
"": "GUI phases",
"GUI_yes": "Yes", "GUI_yes": "Yes",
"GUI_no": "No", "GUI_no": "No",
"GUI_ok": "OK", "GUI_ok": "OK",
@@ -69,11 +48,7 @@ class MultiLanguage:
"GUI_need_key_message": "BeamMP key is required to run!\nDo you want to open the link in your browser to get the key?", "GUI_need_key_message": "BeamMP key is required to run!\nDo you want to open the link in your browser to get the key?",
"GUI_enter_key_message": "Please enter the key:", "GUI_enter_key_message": "Please enter the key:",
"GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}", "GUI_cannot_open_browser": "Failed to open browser.\nUse this link: {}",
"": "Web phases",
"web_start": "WebAPI started on {} (CTRL+C to stop)", "web_start": "WebAPI started on {} (CTRL+C to stop)",
"": "Core phrases",
"core_bind_failed": "Failed to bind port. Error: {}", "core_bind_failed": "Failed to bind port. Error: {}",
"core_direct_mode": "Server started in direct connection mode.", "core_direct_mode": "Server started in direct connection mode.",
"core_auth_server_error": "Received invalid response from BeamMP authentication server.", "core_auth_server_error": "Received invalid response from BeamMP authentication server.",
@@ -92,11 +67,7 @@ class MultiLanguage:
"core_player_kick_server_full": "Server is full.", "core_player_kick_server_full": "Server is full.",
"core_player_set_id": "Player set ID {}", "core_player_set_id": "Player set ID {}",
"core_identifying_okay": "Successful login.", "core_identifying_okay": "Successful login.",
"": "In-game phrases",
"game_welcome_message": "Welcome {}!", "game_welcome_message": "Welcome {}!",
"": "Client class phrases",
"client_mod_request": "Requested mod: {}", "client_mod_request": "Requested mod: {}",
"client_mod_sent": "Mod sent: Size: {}mb, Speed: {}Mb/s ({}sec)", "client_mod_sent": "Mod sent: Size: {}mb, Speed: {}Mb/s ({}sec)",
"client_mod_sent_limit": " (limit {}Mb/s)", "client_mod_sent_limit": " (limit {}Mb/s)",
@@ -105,55 +76,60 @@ class MultiLanguage:
"client_kicked": "Kicked for reason: \"{}\"", "client_kicked": "Kicked for reason: \"{}\"",
"client_event_invalid_data": "Invalid data returned from event: {}", "client_event_invalid_data": "Invalid data returned from event: {}",
"client_player_disconnected": "Left the server. Playtime: {} min", "client_player_disconnected": "Left the server. Playtime: {} min",
"": "Events system",
"events_not_callable": "Unable to add event \"{}\". Use \"{}\" instead. Skipping...", "events_not_callable": "Unable to add event \"{}\". Use \"{}\" instead. Skipping...",
"events_not_found": "Event \"{}\" is not registered. Maybe {}? Skipping...", "events_not_found": "Event \"{}\" is not registered. Maybe {}? Skipping...",
"events_calling_error": "Error calling \"{}\" in function \"{}\".", "events_calling_error": "Error calling \"{}\" in function \"{}\".",
"events_lua_function_not_found": "Unable to call {}lua event - \"{}\" not found.", "events_lua_function_not_found": "Unable to call {}lua event - \"{}\" not found.",
"events_lua_local": "local ", "events_lua_local": "local ",
"events_lua_calling_error": "Error: \"{}\" - calling lua event \"{}\", function: \"{}\", arguments: {}", "events_lua_calling_error": "Error: \"{}\" - calling lua event \"{}\", function: \"{}\", arguments: {}",
"": "Plugins loader",
"plugins_not_found_load": "Function \"def load():\" not found.", "plugins_not_found_load": "Function \"def load():\" not found.",
"plugins_not_found_start": "Function \"def start():\" not found.", "plugins_not_found_start": "Function \"def start():\" not found.",
"plugins_not_found_unload": "Function \"def unload():\" not found.", "plugins_not_found_unload": "Function \"def unload():\" not found.",
"plugins_kt_invalid": "\"kt\" variable does not belong to the KuiToi class.", "plugins_kt_invalid": "\"kt\" variable does not belong to the KuiToi class.",
"plugins_invalid": "Plugin \"{}\" cannot be run in KuiToi.", "plugins_invalid": "Plugin \"{}\" cannot be run in KuiToi.",
"plugins_error_loading": "An error occurred while loading the plugin {}: {}", "plugins_error_loading": "An error occurred while loading the plugin {}: {}",
"": "Lua plugins loader",
"plugins_lua_enabled": "You have enabled Lua plugin support.", "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_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_warning": "Some BeamMP plugins require a properly configured ServerConfig.toml file to function.",
"plugins_lua_legacy_config_create": "Creating it.", "plugins_lua_legacy_config_create": "Creating it.",
"plugins_lua_unload": "Stopping Lua plugin: {}", "plugins_lua_unload": "Stopping Lua plugin: {}",
"": "Command: man",
"man_message_man": "man - Shows the help page for COMMAND.\nUsage: man COMMAND", "man_message_man": "man - Shows the help page for COMMAND.\nUsage: man COMMAND",
"help_message_man": "Shows the help page for COMMAND.", "help_message_man": "Shows the help page for COMMAND.",
"man_for": "Help page for", "man_for": "Help page for",
"man_message_not_found": "man: Help page not found.", "man_message_not_found": "man: Help page not found.",
"man_command_not_found": "man: Command \"{}\" not found!", "man_command_not_found": "man: Command \"{}\" not found!",
"": "Command: help",
"man_message_help": "help - Shows the names and brief descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands, with a brief description for each command.", "man_message_help": "help - Shows the names and brief descriptions of commands.\nUsage: help [--raw]\nThe `help` command displays a list of all available commands, with a brief description for each command.",
"help_message_help": "Shows the names and brief descriptions of commands", "help_message_help": "Shows the names and brief descriptions of commands",
"help_command": "Command", "help_command": "Command",
"help_message": "Text", "help_message": "Text",
"help_message_not_found": "No text found", "help_message_not_found": "No text found",
"": "Command: stop",
"man_message_stop": "stop - Stops the server.\nUsage: stop", "man_message_stop": "stop - Stops the server.\nUsage: stop",
"help_message_stop": "Stops the server.", "help_message_stop": "Stops the server.",
"": "Command: exit",
"man_message_exit": "exit - Stops the server.\nUsage: exit", "man_message_exit": "exit - Stops the server.\nUsage: exit",
"help_message_exit": "Stops the server." "help_message_exit": "Stops the server."
} }
self.__en_data = self.__data.copy()
self.__i18n = None
self.__encoding = encoding
self.language = language
if not os.path.exists(files_dir):
os.makedirs(files_dir)
if not os.path.exists(files_dir + "en.json"):
with open(files_dir + "en.json", "w") as f:
f.write(json.dumps(self.__en_data, indent=2))
self.files_dir = files_dir
self.log = get_logger("i18n")
self.fi = False
self.set_language(language)
def set_language(self, language="en"):
if self.language == language and self.fi:
return
else:
self.fi = True
self.log.debug(f"set_language({language})")
self.language = language
self.open_file()
self.__i18n = i18n(self.__data) self.__i18n = i18n(self.__data)
def open_file(self): def open_file(self):
@@ -165,9 +141,9 @@ class MultiLanguage:
return return
except JSONDecodeError: except JSONDecodeError:
self.log.error( self.log.error(
f"Localisation \"{self.language}.json\" have JsonDecodeError. Using default localisation: en.") f"Localisation \"{file}\" have JsonDecodeError. Using default localisation: en.")
except FileNotFoundError: except FileNotFoundError:
self.log.warning(f"Localisation \"{self.language}.json\" not found; Using default localisation: en.") self.log.warning(f"Localisation \"{file}\" not found; Using default localisation: en.")
self.set_language("en") self.set_language("en")
def builtins_hook(self) -> None: def builtins_hook(self) -> None:
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

+19
View File
@@ -0,0 +1,19 @@
# pip install pyinstaller-versionfile
# create-version-file win-metadata.yml --outfile win-ver_info.txt
Version: 0.4.5
CompanyName: KuiToi
FileDescription: KuiToi Server
InternalName: KuiToi Server
LegalCopyright: © Maxim Khomutov
OriginalFilename: KuiToi-Server.exe
ProductName: KuiToi Server
Translation:
# ru-RU
- langID: 1049
charsetID: 1251
# en-US
- langID: 1033
charsetID: 1251
# zh-CN
- langID: 2052
charsetID: 950
+85
View File
@@ -0,0 +1,85 @@
{
"version": "auto-py-to-exe-configuration_v1",
"pyinstallerOptions": [
{
"optionDest": "noconfirm",
"value": true
},
{
"optionDest": "filenames",
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/src/main.py"
},
{
"optionDest": "onefile",
"value": true
},
{
"optionDest": "console",
"value": true
},
{
"optionDest": "icon_file",
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/win-logo.ico"
},
{
"optionDest": "name",
"value": "KuiToi-Server"
},
{
"optionDest": "ascii",
"value": true
},
{
"optionDest": "clean_build",
"value": false
},
{
"optionDest": "strip",
"value": false
},
{
"optionDest": "noupx",
"value": false
},
{
"optionDest": "disable_windowed_traceback",
"value": false
},
{
"optionDest": "version_file",
"value": "C:/Users/Santa/PycharmProjects/kuitoi-Server/win-ver_info.txt"
},
{
"optionDest": "embed_manifest",
"value": true
},
{
"optionDest": "uac_admin",
"value": false
},
{
"optionDest": "uac_uiaccess",
"value": false
},
{
"optionDest": "win_private_assemblies",
"value": false
},
{
"optionDest": "win_no_prefer_redirects",
"value": false
},
{
"optionDest": "bootloader_ignore_signals",
"value": false
},
{
"optionDest": "argv_emulation",
"value": false
}
],
"nonPyinstallerOptions": {
"increaseRecursionLimit": true,
"manualArguments": ""
}
}