Compare commits

..

17 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
Tixx caafb216c9 Add MP.GetPlayerRole(player_id) 2024-09-19 07:51:07 +02:00
12 changed files with 221 additions and 145 deletions
-4
View File
@@ -66,7 +66,6 @@ public:
std::string GetCarData(int Ident); std::string GetCarData(int Ident);
std::string GetCarPositionRaw(int Ident); std::string GetCarPositionRaw(int Ident);
void SetUDPAddr(const ip::udp::endpoint& Addr) { mUDPAddress = Addr; } 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 SetTCPSock(ip::tcp::socket&& CSock) { mSocket = std::move(CSock); }
void Disconnect(std::string_view Reason); void Disconnect(std::string_view Reason);
bool IsDisconnected() const { return !mSocket.is_open(); } 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 std::unordered_map<std::string, std::string>& GetIdentifiers() const { return mIdentifiers; }
[[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; } [[nodiscard]] const ip::udp::endpoint& GetUDPAddr() const { return mUDPAddress; }
[[nodiscard]] ip::udp::endpoint& GetUDPAddr() { 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]] ip::tcp::socket& GetTCPSock() { return mSocket; }
[[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; } [[nodiscard]] const ip::tcp::socket& GetTCPSock() const { return mSocket; }
[[nodiscard]] std::string GetRoles() const { return mRole; } [[nodiscard]] std::string GetRoles() const { return mRole; }
@@ -122,7 +119,6 @@ private:
SparseArray<std::string> mVehiclePosition; SparseArray<std::string> mVehiclePosition;
std::string mName = "Unknown Client"; std::string mName = "Unknown Client";
ip::tcp::socket mSocket; ip::tcp::socket mSocket;
ip::tcp::socket mDownSocket;
ip::udp::endpoint mUDPAddress {}; ip::udp::endpoint mUDPAddress {};
int mUnicycleID = -1; int mUnicycleID = -1;
std::string mRole; std::string mRole;
+1 -1
View File
@@ -127,7 +127,7 @@ private:
static inline std::mutex mShutdownHandlersMutex {}; static inline std::mutex mShutdownHandlersMutex {};
static inline std::deque<TShutdownHandler> mShutdownHandlers {}; static inline std::deque<TShutdownHandler> mShutdownHandlers {};
static inline Version mVersion { 3, 5, 1 }; static inline Version mVersion { 3, 6, 0 };
}; };
void SplitString(std::string const& str, const char delim, std::vector<std::string>& out); void SplitString(std::string const& str, const char delim, std::vector<std::string>& out);
+2
View File
@@ -35,8 +35,10 @@ namespace MP {
inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); } 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> 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> 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); std::pair<bool, std::string> RemoveVehicle(int PlayerID, int VehicleID);
void Set(int ConfigID, sol::object NewValue); void Set(int ConfigID, sol::object NewValue);
TLuaValue Get(int ConfigID);
bool IsPlayerGuest(int ID); bool IsPlayerGuest(int ID);
bool IsPlayerConnected(int ID); bool IsPlayerConnected(int ID);
void Sleep(size_t Ms); void Sleep(size_t Ms);
+1
View File
@@ -263,6 +263,7 @@ private:
sol::table Lua_TriggerGlobalEvent(const std::string& EventName, sol::variadic_args EventArgs); 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_TriggerLocalEvent(const std::string& EventName, sol::variadic_args EventArgs);
sol::table Lua_GetPlayerIdentifiers(int ID); sol::table Lua_GetPlayerIdentifiers(int ID);
std::variant<std::string, sol::nil_t> Lua_GetPlayerRole(int ID);
sol::table Lua_GetPlayers(); sol::table Lua_GetPlayers();
std::string Lua_GetPlayerName(int ID); std::string Lua_GetPlayerName(int ID);
sol::table Lua_GetPlayerVehicles(int ID); sol::table Lua_GetPlayerVehicles(int ID);
+1 -2
View File
@@ -58,7 +58,6 @@ private:
std::mutex mOpenIDMutex; std::mutex mOpenIDMutex;
std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint); std::vector<uint8_t> UDPRcvFromClient(ip::udp::endpoint& ClientEndpoint);
void HandleDownload(TConnection&& TCPSock);
void OnConnect(const std::weak_ptr<TClient>& c); void OnConnect(const std::weak_ptr<TClient>& c);
void TCPClient(const std::weak_ptr<TClient>& c); void TCPClient(const std::weak_ptr<TClient>& c);
void Looper(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 Parse(TClient& c, const std::vector<uint8_t>& Packet);
void SendFile(TClient& c, const std::string& Name); 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 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); static const uint8_t* SendSplit(TClient& c, ip::tcp::socket& Socket, const uint8_t* DataPtr, size_t Size);
}; };
+8
View File
@@ -19,6 +19,7 @@
#pragma once #pragma once
#include "Common.h" #include "Common.h"
#include <nlohmann/json.hpp>
class TResourceManager { class TResourceManager {
public: public:
@@ -30,10 +31,17 @@ public:
[[nodiscard]] std::string FileSizes() const { return mFileSizes; } [[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; } [[nodiscard]] int ModsLoaded() const { return mModsLoaded; }
[[nodiscard]] std::string NewFileList() const;
void RefreshFiles();
private: private:
size_t mMaxModSize = 0; size_t mMaxModSize = 0;
std::string mFileSizes; std::string mFileSizes;
std::string mFileList; std::string mFileList;
std::string mTrimmedList; std::string mTrimmedList;
int mModsLoaded = 0; int mModsLoaded = 0;
std::mutex mModsMutex;
nlohmann::json mMods;
}; };
-1
View File
@@ -144,7 +144,6 @@ void TClient::EnqueuePacket(const std::vector<uint8_t>& Packet) {
TClient::TClient(TServer& Server, ip::tcp::socket&& Socket) TClient::TClient(TServer& Server, ip::tcp::socket&& Socket)
: mServer(Server) : mServer(Server)
, mSocket(std::move(Socket)) , mSocket(std::move(Socket))
, mDownSocket(ip::tcp::socket(Server.IoCtx()))
, mLastPingTime(std::chrono::high_resolution_clock::now()) { , mLastPingTime(std::chrono::high_resolution_clock::now()) {
} }
+53
View File
@@ -205,6 +205,37 @@ std::pair<bool, std::string> LuaAPI::MP::SendChatMessage(int ID, const std::stri
return Result; 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> LuaAPI::MP::RemoveVehicle(int PID, int VID) {
std::pair<bool, std::string> Result; std::pair<bool, std::string> Result;
auto MaybeClient = GetClient(Engine->Server(), PID); 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) { void LuaAPI::MP::Sleep(size_t Ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(Ms)); std::this_thread::sleep_for(std::chrono::milliseconds(Ms));
} }
+23
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 TLuaEngine::StateThreadData::Lua_GetPlayers() {
sol::table Result = mStateView.create_table(); sol::table Result = mStateView.create_table();
mEngine->Server().ForEachClient([&](std::weak_ptr<TClient> Client) -> bool { 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); return Lua_GetPositionRaw(PID, VID);
}); });
MPTable.set_function("SendChatMessage", &LuaAPI::MP::SendChatMessage); 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 { MPTable.set_function("GetPlayers", [&]() -> sol::table {
return Lua_GetPlayers(); return Lua_GetPlayers();
}); });
@@ -883,6 +902,9 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table { MPTable.set_function("GetPlayerIdentifiers", [&](int ID) -> sol::table {
return Lua_GetPlayerIdentifiers(ID); 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); MPTable.set_function("Sleep", &LuaAPI::MP::Sleep);
// const std::string& EventName, size_t IntervalMS, int strategy // const std::string& EventName, size_t IntervalMS, int strategy
MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) { MPTable.set_function("CreateEventTimer", [&](sol::variadic_args Args) {
@@ -910,6 +932,7 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI
mEngine->CancelEventTimers(EventName, mStateId); mEngine->CancelEventTimers(EventName, mStateId);
}); });
MPTable.set_function("Set", &LuaAPI::MP::Set); MPTable.set_function("Set", &LuaAPI::MP::Set);
MPTable.set_function("Get", &LuaAPI::MP::Get);
auto UtilTable = StateView.create_named_table("Util"); auto UtilTable = StateView.create_named_table("Util");
UtilTable.set_function("LogDebug", [this](sol::variadic_args args) { UtilTable.set_function("LogDebug", [this](sol::variadic_args args) {
+40 -131
View File
@@ -240,7 +240,8 @@ void TNetwork::Identify(TConnection&& RawConnection) {
if (Code == 'C') { if (Code == 'C') {
Client = Authentication(std::move(RawConnection)); Client = Authentication(std::move(RawConnection));
} else if (Code == 'D') { } 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') { } else if (Code == 'P') {
boost::system::error_code ec; boost::system::error_code ec;
write(RawConnection.Socket, buffer("P"), ec); write(RawConnection.Socket, buffer("P"), ec);
@@ -249,7 +250,7 @@ void TNetwork::Identify(TConnection&& RawConnection) {
beammp_errorf("Invalid code got in Identify: '{}'", Code); beammp_errorf("Invalid code got in Identify: '{}'", Code);
} }
} catch (const std::exception& e) { } 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; boost::system::error_code ec;
RawConnection.Socket.shutdown(socket_base::shutdown_both, ec); RawConnection.Socket.shutdown(socket_base::shutdown_both, ec);
if (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::string HashPassword(const std::string& str) {
std::stringstream ret; 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()); auto Futures = LuaAPI::MP::Engine->TriggerEvent("onPlayerAuth", "", Client->GetName(), Client->GetRoles(), Client->IsGuest(), Client->GetIdentifiers());
TLuaEngine::WaitForAll(Futures); TLuaEngine::WaitForAll(Futures);
bool NotAllowed = std::any_of(Futures.begin(), Futures.end(), bool NotAllowed = false;
[](const std::shared_ptr<TLuaResult>& Result) { bool BypassLimit = false;
return !Result->Error && Result->Result.is<int>() && bool(Result->Result.as<int>());
}); 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; std::string Reason;
bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(), bool NotAllowedWithReason = std::any_of(Futures.begin(), Futures.end(),
[&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool { [&Reason](const std::shared_ptr<TLuaResult>& Result) -> bool {
@@ -440,7 +432,7 @@ std::shared_ptr<TClient> TNetwork::Authentication(TConnection&& RawConnection) {
if (!Allowed) { if (!Allowed) {
return {}; 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"); beammp_info("Identification success");
mServer.InsertClient(Client); mServer.InsertClient(Client);
TCPClient(Client); TCPClient(Client);
@@ -758,11 +750,11 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
case 'S': case 'S':
if (SubCode == 'R') { if (SubCode == 'R') {
beammp_debug("Sending Mod Info"); beammp_debug("Sending Mod Info");
std::string ToSend = mResourceManager.FileList() + mResourceManager.FileSizes(); std::string ToSend = mResourceManager.NewFileList();
if (ToSend.empty()) beammp_debugf("Mod Info: {}", ToSend);
ToSend = "-";
if (!TCPSend(c, StringToVector(ToSend))) { if (!TCPSend(c, StringToVector(ToSend))) {
// TODO: error ClientKick(c, "TCP Send 'SY' failed");
return;
} }
} }
return; return;
@@ -772,8 +764,6 @@ void TNetwork::Parse(TClient& c, const std::vector<uint8_t>& Packet) {
} }
void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) { 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 (!fs::path(UnsafeName).has_filename()) {
if (!TCPSend(c, StringToVector("CO"))) { if (!TCPSend(c, StringToVector("CO"))) {
// TODO: handle // TODO: handle
@@ -796,87 +786,9 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
// TODO: handle // 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 Size = size_t(std::filesystem::file_size(FileName));
size_t MSize = Size / 2;
std::thread SplitThreads[2] { SendFileToClient(c, Size, FileName);
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;
}
} }
#if defined(BEAMMP_LINUX) #if defined(BEAMMP_LINUX)
@@ -886,8 +798,8 @@ const uint8_t* /* end ptr */ TNetwork::SendSplit(TClient& c, ip::tcp::socket& So
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#endif #endif
void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const std::string& Name) { void TNetwork::SendFileToClient(TClient& c, size_t Size, const std::string& Name) {
TScopedTimer timer(fmt::format("Download of {}-{} for '{}'", Offset, End, Name)); TScopedTimer timer(fmt::format("Download of '{}' for client {}", Name, c.GetID()));
#if defined(BEAMMP_LINUX) #if defined(BEAMMP_LINUX)
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
// on linux, we can use sendfile(2)! // on linux, we can use sendfile(2)!
@@ -897,11 +809,11 @@ void TNetwork::SplitLoad(TClient& c, size_t Offset, size_t End, bool D, const st
return; return;
} }
// native handle, needed in order to make native syscalls with it // 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; ssize_t ret = 0;
auto ToSendTotal = End - Offset; auto ToSendTotal = Size;
auto Start = Offset; auto Start = 0;
while (ret < ssize_t(ToSendTotal)) { while (ret < ssize_t(ToSendTotal)) {
auto SysOffset = off_t(Start + size_t(ret)); auto SysOffset = off_t(Start + size_t(ret));
ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret)); ret = sendfile(socket, fd, &SysOffset, ToSendTotal - size_t(ret));
@@ -915,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); std::ifstream f(Name.c_str(), std::ios::binary);
uint32_t Split = 125 * MB; uint32_t Split = 125 * MB;
std::vector<uint8_t> Data; std::vector<uint8_t> Data;
if (End > Split) if (Size > Split)
Data.resize(Split); Data.resize(Split);
else else
Data.resize(End); Data.resize(Size);
ip::tcp::socket* TCPSock { nullptr }; ip::tcp::socket* TCPSock = &c.GetTCPSock();
if (D) std::streamsize Sent = 0;
TCPSock = &c.GetDownSock(); while (!c.IsDisconnected() && Sent < Size) {
else size_t Diff = Size - Sent;
TCPSock = &c.GetTCPSock();
while (!c.IsDisconnected() && Offset < End) {
size_t Diff = End - Offset;
if (Diff > Split) { 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); f.read(reinterpret_cast<char*>(Data.data()), Split);
if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) { if (!TCPSendRaw(c, *TCPSock, Data.data(), Split)) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (1)"); c.Disconnect("TCPSendRaw failed in mod download (1)");
break; break;
} }
Offset += Split; Sent += Split;
} else { } else {
f.seekg(Offset, std::ios_base::beg); f.seekg(Sent, std::ios_base::beg);
f.read(reinterpret_cast<char*>(Data.data()), Diff); f.read(reinterpret_cast<char*>(Data.data()), Diff);
if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) { if (!TCPSendRaw(c, *TCPSock, Data.data(), int32_t(Diff))) {
if (!c.IsDisconnected()) if (!c.IsDisconnected())
c.Disconnect("TCPSendRaw failed in mod download (2)"); c.Disconnect("TCPSendRaw failed in mod download (2)");
break; break;
} }
Offset += Diff; Sent += Diff;
} }
} }
#endif #endif
+85
View File
@@ -17,9 +17,14 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "TResourceManager.h" #include "TResourceManager.h"
#include "Common.h"
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <fmt/core.h>
#include <ios>
#include <nlohmann/json.hpp>
#include <openssl/evp.h>
namespace fs = std::filesystem; namespace fs = std::filesystem;
@@ -52,3 +57,83 @@ TResourceManager::TResourceManager() {
Application::SetSubsystemStatus("ResourceManager", Application::Status::Good); 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());
}
}
}
+7 -6
View File
@@ -36,19 +36,19 @@
#include <thread> #include <thread>
static const std::string sCommandlineArguments = R"( static const std::string sCommandlineArguments = R"(
USAGE: USAGE:
BeamMP-Server [arguments] BeamMP-Server [arguments]
ARGUMENTS: ARGUMENTS:
--help --help
Displays this help and exits. Displays this help and exits.
--port=1234 --port=1234
Sets the server's listening TCP and Sets the server's listening TCP and
UDP port. Overrides ENV and ServerConfig. UDP port. Overrides ENV and ServerConfig.
--config=/path/to/ServerConfig.toml --config=/path/to/ServerConfig.toml
Absolute or relative path to the Absolute or relative path to the
Server Config file, including the Server Config file, including the
filename. For paths and filenames with filename. For paths and filenames with
spaces, put quotes around the path. spaces, put quotes around the path.
--working-directory=/path/to/folder --working-directory=/path/to/folder
Sets the working directory of the Server. Sets the working directory of the Server.
@@ -59,7 +59,7 @@ ARGUMENTS:
EXAMPLES: EXAMPLES:
BeamMP-Server --config=../MyWestCoastServerConfig.toml 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 which is one directory above it and is named
'MyWestCoastServerConfig.toml'. 'MyWestCoastServerConfig.toml'.
)"; )";
@@ -190,6 +190,7 @@ int BeamMPServerMain(MainArguments Arguments) {
beammp_trace("Running in debug mode on a debug build"); beammp_trace("Running in debug mode on a debug build");
TResourceManager ResourceManager; TResourceManager ResourceManager;
ResourceManager.RefreshFiles();
TPPSMonitor PPSMonitor(Server); TPPSMonitor PPSMonitor(Server);
THeartbeatThread Heartbeat(ResourceManager, Server); THeartbeatThread Heartbeat(ResourceManager, Server);
TNetwork Network(Server, PPSMonitor, ResourceManager); TNetwork Network(Server, PPSMonitor, ResourceManager);