Compare commits

...

22 Commits

Author SHA1 Message Date
Lion
cf3985ce00 Add Lua function to get a player's role (#366)
Adds `MP.GetPlayerRole(player_id)` to the Lua API to get a player's role
("USER", "EA", "MDEV", "STAFF", "ET") by their player id.
Currently you can only get someone's role in onPlayerAuth from the
parameters and in onVehicleSpawned and onVehicleEdited from the packet
data, but not in onPlayerJoin for example without storing it.
2024-10-05 16:09:37 +02:00
Lion Kortlepel
b04c5068ea bump version 2024-10-05 16:09:02 +02:00
Lion
077bb6b1cd Add player limit bypass to onPlayerAuth (#372)
With this PR, returning 2 in onPlayerAuth will allow the player to join
without checking if the server is full. This makes it easier for plugin
developers to allow for example their staff to join without having to
change the max player count.
2024-10-05 16:07:53 +02:00
Lion
0850cde1fb Add MP.SendNotification (#373)
Adds MP.SendNotification(message, icon, category (optional) ) to the Lua
api. Uses the newly added "N" packet in the mod.
2024-10-05 16:07:27 +02:00
Lion
611e53b484 Mod hashing + better download (#374) 2024-10-04 23:29:11 +02:00
Tixx
f039f57f11 Fix error messages on sendnotification 2024-10-04 20:24:30 +02:00
Lion Kortlepel
5d34090952 fix stupid read size error leading to corrupt zip 2024-09-29 01:34:38 +02:00
Lion Kortlepel
88ca17236a remove two-socket download 2024-09-29 01:15:48 +02:00
Lion Kortlepel
a4b62d013c implement mod hashing + new download 2024-09-29 00:32:52 +02:00
Tixx
9a0270cb09 Return nil instead of "" when there's no client 2024-09-28 21:05:04 +02:00
Lion
55f1a3c734 Add MP.Get (#369)
Adds `MP.Get(ConfigID)` to the lua api to get the current server
settings.

```lua
lua> print(MP.Get(MP.Settings.Name]))
[LUA] BeamMP Server
lua> MP.Set(MP.Settings.Name, 'Hello World')
[INFO] Set `Name` to Hello World
lua> print(MP.Get(MP.Settings.Name))
[LUA] Hello World
```

Closes #146
2024-09-28 20:30:14 +02:00
Tixx
bb3c762d68 Add player limit bypass to onPlayerAuth 2024-09-28 14:52:04 +02:00
Tixx
3ade7f5743 Add MP.SendNotification 2024-09-28 13:35:25 +02:00
Tixx
9d44c2063c Remove break after return 2024-09-22 15:34:13 +02:00
Tixx
17185da53b Add MP.Get 2024-09-21 23:17:08 +02:00
Tixx
623dfa17d5 Remove expiry check and add braces 2024-09-20 14:45:41 +02:00
Lion
7f69e336a9 Fix exception propagation on packet decompression (#365) 2024-09-20 11:39:36 +02:00
Lion
f08dfc0585 fix MaxPlayers setting using value of MaxCars (#367) 2024-09-20 11:39:22 +02:00
Deer McDurr
a9dee2bec5 fix MaxPlayers using value of MaxCars 2024-09-19 22:15:12 +02:00
Lion Kortlepel
5319c2878a bump version to v3.5.1 2024-09-19 17:24:57 +02:00
Lion Kortlepel
73f494041a fix exception propagation on packet decompression 2024-09-19 16:59:17 +02:00
Tixx
caafb216c9 Add MP.GetPlayerRole(player_id) 2024-09-19 07:51:07 +02:00
15 changed files with 257 additions and 149 deletions

View File

@@ -66,7 +66,6 @@ public:
std::string GetCarData(int Ident);
std::string GetCarPositionRaw(int Ident);
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; }
void SetDownSock(ip::tcp::socket&& CSock) { mDownSocket = std::move(CSock); }
void SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
void Disconnect(std::string_view Reason);
bool IsDisconnected() const { return !mSocket.is_open(); }
@@ -75,8 +74,6 @@ public:
[[nodiscard]] const std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
[[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; }
[[nodiscard]] ip::udp::endpoint& GetUDPAddr() { return mUDPAddress; }
[[nodiscard]] ip::tcp::socket& GetDownSock() { return mDownSocket; }
[[nodiscard]] const ip::tcp::socket& GetDownSock() const { return mDownSocket; }
[[nodiscard]] ip::tcp::socket& GetTCPSock() { return mSocket; }
[[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; }
[[nodiscard]] std::string GetRoles() const { return mRole; }
@@ -122,7 +119,6 @@ private:
SparseArray<std::string> mVehiclePosition;
std::string mName = "Unknown Client";
ip::tcp::socket mSocket;
ip::tcp::socket mDownSocket;
ip::udp::endpoint mUDPAddress {};
int mUnicycleID = -1;
std::string mRole;

View File

@@ -127,7 +127,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 5, 0 };
static inline Version mVersion { 3, 6, 0 };
};
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
@@ -268,3 +268,9 @@ std::vector<uint8_t> DeComp(std::span<const uint8_t> input);
std::string GetPlatformAgnosticErrorString();
#define S_DSN SU_RAW
class InvalidDataError : std::runtime_error {
public:
InvalidDataError() : std::runtime_error("Invalid data") {
}
};

View File

@@ -35,8 +35,10 @@ namespace MP {
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); }
std::pair<bool, std::string> DropPlayer(int ID, std::optional<std::string> MaybeReason);
std::pair<bool, std::string> SendChatMessage(int ID, const std::string& Message);
std::pair<bool, std::string> SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category);
std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue);
TLuaValue Get(int ConfigID);
bool IsPlayerGuest(int ID);
bool IsPlayerConnected(int ID);
void Sleep(size_t Ms);

View File

@@ -263,6 +263,7 @@ private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_GetPlayerIdentifiers(int ID);
std::variant<std::string, sol::nil_t> Lua_GetPlayerRole(int ID);
sol::table Lua_GetPlayers();
std::string Lua_GetPlayerName(int ID);
sol::table Lua_GetPlayerVehicles(int ID);

View File

@@ -58,7 +58,6 @@ private:
std::mutex mOpenIDMutex;
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
void HandleDownload(TConnection&& TCPSock);
void OnConnect(const std::weak_ptr<TClient>& c);
void TCPClient(const std::weak_ptr<TClient>& c);
void Looper(const std::weak_ptr<TClient>& c);
@@ -67,7 +66,7 @@ private:
void Parse(TClient& c, const std::vector<uint8_t>& Packet);
void SendFile(TClient& c, const std::string& Name);
static bool TCPSendRaw(TClient& C, ip::tcp::socket& socket, const uint8_t* Data, size_t Size);
static void SplitLoad(TClient& c, size_t Sent, size_t Size, bool D, const std::string& Name);
static void SendFileToClient(TClient& c, size_t Size, const std::string& Name);
static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
};

View File

@@ -19,6 +19,7 @@
#pragma once
#include "Common.h"
#include <nlohmann/json.hpp>
class TResourceManager {
public:
@@ -30,10 +31,17 @@ public:
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] std::string NewFileList() const;
void RefreshFiles();
private:
size_t mMaxModSize = 0;
std::string mFileSizes;
std::string mFileList;
std::string mTrimmedList;
int mModsLoaded = 0;
std::mutex mModsMutex;
nlohmann::json mMods;
};

View File

@@ -144,7 +144,6 @@ void TClient::EnqueuePacket(const std::vector<uint8_t>& Packet) {
TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
: mServer(Server)
, mSocket(std::move(Socket))
, mDownSocket(ip::tcp::socket(Server.IoCtx()))
, mLastPingTime(std::chrono::high_resolution_clock::now()) {
}

View File

@@ -418,7 +418,11 @@ std::vector<uint8_t> DeComp(std::span<const uint8_t> input) {
output_size = output_buffer.size();
} else if (res != Z_OK) {
beammp_error("zlib uncompress() failed: " + std::to_string(res));
throw std::runtime_error("zlib uncompress() failed");
if (res == Z_DATA_ERROR) {
throw InvalidDataError {};
} else {
throw std::runtime_error("zlib uncompress() failed");
}
} else if (res == Z_OK) {
break;
}

View File

@@ -205,6 +205,37 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
return Result;
}
std::pair<bool, std::string> LuaAPI::MP::SendNotification(int ID, const std::string& Message, const std::string& Icon, const std::string& Category) {
std::pair<bool, std::string> Result;
std::string Packet = "N" + Category + ":" + Icon + ":" + Message;
if (ID == -1) {
Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true);
Result.first = true;
} else {
auto MaybeClient = GetClient(Engine->Server(), ID);
if (MaybeClient) {
auto c = MaybeClient.value().lock();
if (!c->IsSynced()) {
Result.first = false;
Result.second = "Player is not synced yet";
return Result;
}
if (!Engine->Network().Respond(*c, StringToVector(Packet), true)) {
beammp_errorf("Failed to send notification to player (id {}) - did the player disconnect?", ID);
Result.first = false;
Result.second = "Failed to send packet";
}
Result.first = true;
} else {
beammp_lua_error("SendNotification invalid argument [1] invalid ID");
Result.first = false;
Result.second = "Invalid Player ID";
}
return Result;
}
return Result;
}
std::pair<bool, std::string> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
std::pair<bool, std::string> Result;
auto MaybeClient = GetClient(Engine->Server(), PID);
@@ -292,6 +323,28 @@ void LuaAPI::MP::Set(int ConfigID, sol::object NewValue) {
}
}
TLuaValue LuaAPI::MP::Get(int ConfigID) {
switch (ConfigID) {
case 0: // debug
return Application::Settings.getAsBool(Settings::Key::General_Debug);
case 1: // private
return Application::Settings.getAsBool(Settings::Key::General_Private);
case 2: // max cars
return Application::Settings.getAsInt(Settings::Key::General_MaxCars);
case 3: // max players
return Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
case 4: // Map
return Application::Settings.getAsString(Settings::Key::General_Map);
case 5: // Name
return Application::Settings.getAsString(Settings::Key::General_Name);
case 6: // Desc
return Application::Settings.getAsString(Settings::Key::General_Description);
default:
beammp_warn("Invalid config ID \"" + std::to_string(ConfigID) + "\". Use `MP.Settings.*` enum for this.");
return 0;
}
}
void LuaAPI::MP::Sleep(size_t Ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
}

View File

@@ -139,7 +139,7 @@ void TConfig::FlushToFile() {
SetComment(data["General"][StrTags.data()].comments(), " Add custom identifying tags to your server to make it easier to find. Format should be TagA,TagB,TagC. Note the comma seperation.");
data["General"][StrTags.data()] = Application::Settings.getAsString(Settings::Key::General_Tags);
data["General"][StrMaxCars.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxCars);
data["General"][StrMaxPlayers.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxCars);
data["General"][StrMaxPlayers.data()] = Application::Settings.getAsInt(Settings::Key::General_MaxPlayers);
data["General"][StrMap.data()] = Application::Settings.getAsString(Settings::Key::General_Map);
data["General"][StrDescription.data()] = Application::Settings.getAsString(Settings::Key::General_Description);
data["General"][StrResourceFolder.data()] = Application::Settings.getAsString(Settings::Key::General_ResourceFolder);

View File

@@ -595,6 +595,16 @@ sol::table TLuaEngine::StateThreadData::Lua_GetPlayerIdentifiers(int ID) {
}
}
std::variant<std::string, sol::nil_t> TLuaEngine::StateThreadData::Lua_GetPlayerRole(int ID) {
auto MaybeClient = GetClient(mEngine->Server(), ID);
if (MaybeClient) {
return MaybeClient.value().lock()->GetRoles();
} else {
return sol::nil;
}
}
sol::table TLuaEngine::StateThreadData::Lua_GetPlayers() {
sol::table Result = mStateView.create_table();
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool {
@@ -869,6 +879,15 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
return Lua_GetPositionRaw(PID, VID);
});
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage);
MPTable.set_function("SendNotification", [&](sol::variadic_args Args) {
if (Args.size() == 3) {
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(1));
} else if (Args.size() == 4) {
LuaAPI::MP::SendNotification(Args.get<int>(0), Args.get<std::string>(1), Args.get<std::string>(2), Args.get<std::string>(3));
} else {
beammp_lua_error("SendNotification expects 2 or 3 arguments.");
}
});
MPTable.set_function("GetPlayers", [&]() -> sol::table {
return Lua_GetPlayers();
});
@@ -883,6 +902,9 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
return Lua_GetPlayerIdentifiers(ID);
});
MPTable.set_function("GetPlayerRole", [&](int ID) -> std::variant<std::string, sol::nil_t> {
return Lua_GetPlayerRole(ID);
});
MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
// const std::string& EventName, size_t IntervalMS, int strategy
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
@@ -910,6 +932,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
mEngine->CancelEventTimers(EventName, mStateId);
});
MPTable.set_function("Set", &LuaAPI::MP::Set);
MPTable.set_function("Get", &LuaAPI::MP::Get);
auto UtilTable = StateView.create_named_table("Util");
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {

View File

@@ -240,7 +240,8 @@ void TNetwork::Identify(TConnection&& RawConnection) {
if (Code == 'C') {
Client = Authentication(std::move(RawConnection));
} else if (Code == 'D') {
HandleDownload(std::move(RawConnection));
beammp_errorf("Old download packet detected - the client is wildly out of date, this will be ignored");
return;
} else if (Code == 'P') {
boost::system::error_code ec;
write(RawConnection.Socket, buffer("P"), ec);
@@ -249,7 +250,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
beammp_errorf("Invalid code got in Identify: '{}'", Code);
}
} catch (const std::exception& e) {
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket", Code);
beammp_errorf("Error during handling of code {} - client left in invalid state, closing socket: {}", Code, e.what());
boost::system::error_code ec;
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
if (ec) {
@@ -262,27 +263,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
}
}
void TNetwork::HandleDownload(TConnection&& Conn) {
char D;
boost::system::error_code ec;
read(Conn.Socket, buffer(&D, 1), ec);
if (ec) {
Conn.Socket.shutdown(socket_base::shutdown_both, ec);
// ignore ec
return;
}
auto ID = uint8_t(D);
mServer.ForEachClient([&](const std::weak_ptr<TClient>& ClientPtr) -> bool {
ReadLock Lock(mServer.GetClientMutex());
if (!ClientPtr.expired()) {
auto c = ClientPtr.lock();
if (c->GetID() == ID) {
c->SetDownSock(std::move(Conn.Socket));
}
}
return true;
});
}
std::string HashPassword(const std::string& str) {
std::stringstream ret;
@@ -404,10 +385,21 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
TLuaEngine::WaitForAll(Futures);
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(),
[](const std::shared_ptr<TLuaResult>& Result) {
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
});
bool NotAllowed = false;
bool BypassLimit = false;
for (const auto& Result : Futures) {
if (!Result->Error && Result->Result.is<int>()) {
auto Res = Result->Result.as<int>();
if (Res == 1) {
NotAllowed = true;
break;
} else if (Res == 2) {
BypassLimit = true;
}
}
}
std::string Reason;
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
@@ -440,7 +432,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
if (!Allowed) {
return {};
} else if (mServer.ClientCount() < size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers))) {
} else if (mServer.ClientCount() < size_t(Application::Settings.getAsInt(Settings::Key::General_MaxPlayers)) || BypassLimit) {
beammp_info("Identification success");
mServer.InsertClient(Client);
TCPClient(Client);
@@ -546,7 +538,17 @@ std::vector<uint8_t> TNetwork::TCPRcv(TClient& c) {
constexpr std::string_view ABG = "ABG:";
if (Data.size() >= ABG.size() && std::equal(Data.begin(), Data.begin() + ABG.size(), ABG.begin(), ABG.end())) {
Data.erase(Data.begin(), Data.begin() + ABG.size());
return DeComp(Data);
try {
return DeComp(Data);
} catch (const InvalidDataError& ) {
beammp_errorf("Failed to decompress packet from a client. The receive failed and the client may be disconnected as a result");
// return empty -> error
return std::vector<uint8_t>();
} catch (const std::runtime_error& e) {
beammp_errorf("Failed to decompress packet from a client: {}. The server may be out of RAM! The receive failed and the client may be disconnected as a result", e.what());
// return empty -> error
return std::vector<uint8_t>();
}
} else {
return Data;
}
@@ -748,11 +750,11 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
case 'S':
if (SubCode == 'R') {
beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes();
if (ToSend.empty())
ToSend = "-";
std::string ToSend = mResourceManager.NewFileList();
beammp_debugf("Mod Info: {}", ToSend);
if (!TCPSend(c, StringToVector(ToSend))) {
// TODO: error
ClientKick(c, "TCP Send 'SY' failed");
return;
}
}
return;
@@ -762,8 +764,6 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
}
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
beammp_info(c.GetName() + " requesting : " + UnsafeName.substr(UnsafeName.find_last_of('/')));
if (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, StringToVector("CO"))) {
// TODO: handle
@@ -786,87 +786,9 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
// TODO: handle
}
/// Wait for connections
int T = 0;
while (!c.GetDownSock().is_open() && T < 50) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
T++;
}
if (!c.GetDownSock().is_open()) {
beammp_error("Client doesn't have a download socket!");
if (!c.IsDisconnected())
c.Disconnect("Missing download socket");
return;
}
size_t Size = size_t(std::filesystem::file_size(FileName));
size_t MSize = Size / 2;
std::thread SplitThreads[2] {
std::thread([&] {
RegisterThread("SplitLoad_0");
SplitLoad(c, 0, MSize, false, FileName);
}),
std::thread([&] {
RegisterThread("SplitLoad_1");
SplitLoad(c, MSize, Size, true, FileName);
})
};
for (auto& SplitThread : SplitThreads) {
if (SplitThread.joinable()) {
SplitThread.join();
}
}
}
static std::pair<size_t /* count */, size_t /* last chunk */> SplitIntoChunks(size_t FullSize, size_t ChunkSize) {
if (FullSize < ChunkSize) {
return { 0, FullSize };
}
size_t Count = FullSize / (FullSize / ChunkSize);
size_t LastChunkSize = FullSize - (Count * ChunkSize);
return { Count, LastChunkSize };
}
TEST_CASE("SplitIntoChunks") {
size_t FullSize;
size_t ChunkSize;
SUBCASE("Normal case") {
FullSize = 1234567;
ChunkSize = 1234;
}
SUBCASE("Zero original size") {
FullSize = 0;
ChunkSize = 100;
}
SUBCASE("Equal full size and chunk size") {
FullSize = 125;
ChunkSize = 125;
}
SUBCASE("Even split") {
FullSize = 10000;
ChunkSize = 100;
}
SUBCASE("Odd split") {
FullSize = 13;
ChunkSize = 2;
}
SUBCASE("Large sizes") {
FullSize = 10 * GB;
ChunkSize = 125 * MB;
}
auto [Count, LastSize] = SplitIntoChunks(FullSize, ChunkSize);
CHECK((Count * ChunkSize) + LastSize == FullSize);
}
const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size) {
if (TCPSendRaw(c, Socket, DataPtr, Size)) {
return DataPtr + Size;
} else {
return nullptr;
}
SendFileToClient(c, Size, FileName);
}
#if defined(BEAMMP_LINUX)
@@ -876,8 +798,8 @@ const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& So
#include <unistd.h>
#include <signal.h>
#endif
void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const std::string& Name) {
TScopedTimer timer(fmt::format("Download of {}-{} for '{}'", Offset, End, Name));
void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) {
TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID()));
#if defined(BEAMMP_LINUX)
signal(SIGPIPE, SIG_IGN);
// on linux, we can use sendfile(2)!
@@ -887,11 +809,11 @@ void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const st
return;
}
// native handle, needed in order to make native syscalls with it
int socket = D ? c.GetDownSock().native_handle() : c.GetTCPSock().native_handle();
int socket = c.GetTCPSock().native_handle();
ssize_t ret = 0;
auto ToSendTotal = End - Offset;
auto Start = Offset;
auto ToSendTotal = Size;
auto Start = 0;
while (ret < ssize_t(ToSendTotal)) {
auto SysOffset = off_t(Start + size_t(ret));
ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
@@ -905,35 +827,32 @@ void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const st
std::ifstream f(Name.c_str(), std::ios::binary);
uint32_t Split = 125 * MB;
std::vector<uint8_t> Data;
if (End > Split)
if (Size > Split)
Data.resize(Split);
else
Data.resize(End);
ip::tcp::socket* TCPSock { nullptr };
if (D)
TCPSock = &c.GetDownSock();
else
TCPSock = &c.GetTCPSock();
while (!c.IsDisconnected() && Offset < End) {
size_t Diff = End - Offset;
Data.resize(Size);
ip::tcp::socket* TCPSock = &c.GetTCPSock();
std::streamsize Sent = 0;
while (!c.IsDisconnected() && Sent < Size) {
size_t Diff = Size - Sent;
if (Diff > Split) {
f.seekg(Offset, std::ios_base::beg);
f.seekg(Sent, std::ios_base::beg);
f.read(reinterpret_cast<char*>(Data.data()), Split);
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (1)");
break;
}
Offset += Split;
Sent += Split;
} else {
f.seekg(Offset, std::ios_base::beg);
f.seekg(Sent, std::ios_base::beg);
f.read(reinterpret_cast<char*>(Data.data()), Diff);
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (2)");
break;
}
Offset += Diff;
Sent += Diff;
}
}
#endif

View File

@@ -17,9 +17,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "TResourceManager.h"
#include "Common.h"
#include <algorithm>
#include <filesystem>
#include <fmt/core.h>
#include <ios>
#include <nlohmann/json.hpp>
#include <openssl/evp.h>
namespace fs = std::filesystem;
@@ -52,3 +57,83 @@ TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good);
}
std::string TResourceManager::NewFileList() const {
return mMods.dump();
}
void TResourceManager::RefreshFiles() {
mMods.clear();
std::unique_lock Lock(mModsMutex);
std::string Path = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client";
for (const auto& entry : fs::directory_iterator(Path)) {
std::string File(entry.path().string());
if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
continue;
}
try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
uint8_t sha256_value[EVP_MAX_MD_SIZE];
md = EVP_sha256();
if (md == nullptr) {
throw std::runtime_error("EVP_sha256() failed");
}
mdctx = EVP_MD_CTX_new();
if (mdctx == nullptr) {
throw std::runtime_error("EVP_MD_CTX_new() failed");
}
if (!EVP_DigestInit_ex2(mdctx, md, NULL)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestInit_ex2() failed");
}
std::ifstream stream(File, std::ios::binary);
const size_t FileSize = std::filesystem::file_size(File);
size_t Read = 0;
std::vector<char> Data;
while (Read < FileSize) {
Data.resize(size_t(std::min<size_t>(FileSize - Read, 4096)));
size_t RealDataSize = Data.size();
stream.read(Data.data(), std::streamsize(Data.size()));
if (stream.eof() || stream.fail()) {
RealDataSize = size_t(stream.gcount());
}
Data.resize(RealDataSize);
if (RealDataSize == 0) {
break;
}
if (RealDataSize > 0 && !EVP_DigestUpdate(mdctx, Data.data(), Data.size())) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestUpdate() failed");
}
Read += RealDataSize;
}
unsigned int sha256_len = 0;
if (!EVP_DigestFinal_ex(mdctx, sha256_value, &sha256_len)) {
EVP_MD_CTX_free(mdctx);
throw std::runtime_error("EVP_DigestFinal_ex() failed");
}
EVP_MD_CTX_free(mdctx);
std::string result;
for (size_t i = 0; i < sha256_len; i++) {
result += fmt::format("{:02x}", sha256_value[i]);
}
beammp_debugf("sha256('{}'): {}", File, result);
mMods.push_back(nlohmann::json {
{ "file_name", std::filesystem::path(File).filename() },
{ "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" },
{ "hash", result },
});
} catch (const std::exception& e) {
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what());
}
}
}

View File

@@ -165,7 +165,19 @@ void TServer::GlobalParser(const std::weak_ptr<TClient>& Client, std::vector<uin
constexpr std::string_view ABG = "ABG:";
if (Packet.size() >= ABG.size() && std::equal(Packet.begin(), Packet.begin() + ABG.size(), ABG.begin(), ABG.end())) {
Packet.erase(Packet.begin(), Packet.begin() + ABG.size());
Packet = DeComp(Packet);
try {
Packet = DeComp(Packet);
} catch (const InvalidDataError& ) {
auto LockedClient = Client.lock();
beammp_errorf("Failed to decompress packet from client {}. The client sent invalid data and will now be disconnected.", LockedClient->GetID());
Network.ClientKick(*LockedClient, "Sent invalid compressed packet (this is likely a bug on your end)");
return;
} catch (const std::runtime_error& e) {
auto LockedClient = Client.lock();
beammp_errorf("Failed to decompress packet from client {}: {}. The server might be out of RAM! The client will now be disconnected.", LockedClient->GetID(), e.what());
Network.ClientKick(*LockedClient, "Decompression failed (likely a server-side problem)");
return;
}
}
if (Packet.empty()) {
return;

View File

@@ -36,19 +36,19 @@
#include <thread>
static const std::string sCommandlineArguments = R"(
USAGE:
USAGE:
BeamMP-Server [arguments]
ARGUMENTS:
--help
--help
Displays this help and exits.
--port=1234
Sets the server's listening TCP and
UDP port. Overrides ENV and ServerConfig.
--config=/path/to/ServerConfig.toml
Absolute or relative path to the
Absolute or relative path to the
Server Config file, including the
filename. For paths and filenames with
filename. For paths and filenames with
spaces, put quotes around the path.
--working-directory=/path/to/folder
Sets the working directory of the Server.
@@ -59,7 +59,7 @@ ARGUMENTS:
EXAMPLES:
BeamMP-Server --config=../MyWestCoastServerConfig.toml
Runs the BeamMP-Server and uses the server config file
Runs the BeamMP-Server and uses the server config file
which is one directory above it and is named
'MyWestCoastServerConfig.toml'.
)";
@@ -190,6 +190,7 @@ int BeamMPServerMain(MainArguments Arguments) {
beammp_trace("Running in debug mode on a debug build");
TResourceManager ResourceManager;
ResourceManager.RefreshFiles();
TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager);