Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/TConsole.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class TConsole {
void Command_Settings(const std::string& cmd, const std::vector<std::string>& args);
void Command_Clear(const std::string&, const std::vector<std::string>& args);
void Command_Version(const std::string& cmd, const std::vector<std::string>& args);
void Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args);
void Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args);

void Command_Say(const std::string& FullCommand);
bool EnsureArgsCount(const std::vector<std::string>& args, size_t n);
Expand All @@ -77,6 +79,8 @@ class TConsole {
{ "clear", [this](const auto& a, const auto& b) { Command_Clear(a, b); } },
{ "say", [this](const auto&, const auto&) { Command_Say(""); } }, // shouldn't actually be called
{ "version", [this](const auto& a, const auto& b) { Command_Version(a, b); } },
{ "protectmod", [this](const auto& a, const auto& b) { Command_ProtectMod(a, b); } },
{ "reloadmods", [this](const auto& a, const auto& b) { Command_ReloadMods(a, b); } },
};

std::unique_ptr<Commandline> mCommandline { nullptr };
Expand Down
2 changes: 2 additions & 0 deletions include/TNetwork.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class TNetwork {
void SendToAll(TClient* c, const std::vector<uint8_t>& Data, bool Self, bool Rel);
void UpdatePlayer(TClient& Client);

TResourceManager& ResourceManager() const { return mResourceManager; }

private:
void UDPServerMain();
void TCPServerMain();
Expand Down
4 changes: 2 additions & 2 deletions include/TResourceManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ class TResourceManager {
[[nodiscard]] std::string TrimmedList() const { return mTrimmedList; }
[[nodiscard]] std::string FileSizes() const { return mFileSizes; }
[[nodiscard]] int ModsLoaded() const { return mModsLoaded; }

[[nodiscard]] std::string NewFileList() const;
[[nodiscard]] nlohmann::json GetMods() const { return mMods; }

void RefreshFiles();
void SetProtected(const std::string& ModName, bool Protected);

private:
size_t mMaxModSize = 0;
Expand Down
48 changes: 38 additions & 10 deletions src/TConsole.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,16 +208,18 @@ void TConsole::Command_Help(const std::string&, const std::vector<std::string>&
}
static constexpr const char* sHelpString = R"(
Commands:
help displays this help
exit shuts down the server
kick <name> [reason] kicks specified player with an optional reason
list lists all players and info about them
say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua
settings [command] sets or gets settings for the server, run `settings help` for more info
status how the server is doing and what it's up to
clear clears the console window
version displays the server version)";
help displays this help
exit shuts down the server
kick <name> [reason] kicks specified player with an optional reason
list lists all players and info about them
say <message> sends the message to all players in chat
lua [state id] switches to lua, optionally into a specific state id's lua
settings [command] sets or gets settings for the server, run `settings help` for more info
status how the server is doing and what it's up to
clear clears the console window
version displays the server version
protectmod <name> <value> sets whether a mod is protected, value can be true or false
reloadmods reloads all mods from the Resources Client folder)";
Application::Console().WriteRaw("BeamMP-Server Console: " + std::string(sHelpString));
}

Expand Down Expand Up @@ -262,6 +264,32 @@ void TConsole::Command_Version(const std::string& cmd, const std::vector<std::st
std::string openssl_version = fmt::format("OpenSSL: v{}.{}.{}", OPENSSL_VERSION_MAJOR, OPENSSL_VERSION_MINOR, OPENSSL_VERSION_PATCH);
Application::Console().WriteRaw(openssl_version);
}
void TConsole::Command_ProtectMod(const std::string& cmd, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 2)) {
return;
}

const auto& ModName = args.at(0);
const auto& Protect = args.at(1);

for (auto mod : mLuaEngine->Network().ResourceManager().GetMods()) {
if (mod["file_name"].get<std::string>() == ModName) {
mLuaEngine->Network().ResourceManager().SetProtected(ModName, Protect == "true");
Application::Console().WriteRaw("Mod " + ModName + " is now " + (Protect == "true" ? "protected" : "unprotected"));
return;
}
}

Application::Console().WriteRaw("Mod " + ModName + " not found.");
}
void TConsole::Command_ReloadMods(const std::string& cmd, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 0)) {
return;
}

mLuaEngine->Network().ResourceManager().RefreshFiles();
Application::Console().WriteRaw("Mods reloaded.");
}

void TConsole::Command_Kick(const std::string&, const std::vector<std::string>& args) {
if (!EnsureArgsCount(args, 1, size_t(-1))) {
Expand Down
11 changes: 10 additions & 1 deletion src/TNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ 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.NewFileList();
std::string ToSend = mResourceManager.GetMods().dump();
beammp_debugf("Mod Info: {}", ToSend);
if (!TCPSend(c, StringToVector(ToSend))) {
ClientKick(c, "TCP Send 'SY' failed");
Expand All @@ -808,6 +808,15 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) {
return;
}
auto FileName = fs::path(UnsafeName).filename().string();

for (auto mod : mResourceManager.GetMods()) {
if (mod["file_name"].get<std::string>() == FileName && mod["protected"] == true) {
beammp_warn("Client tried to access protected file " + UnsafeName);
c.Disconnect("Mod is protected thus cannot be downloaded");
return;
}
}

FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName;

if (!std::filesystem::exists(FileName)) {
Expand Down
108 changes: 104 additions & 4 deletions src/TResourceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,58 @@ 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";

nlohmann::json modsDB;

if (std::filesystem::exists(Path + "/mods.json")) {
try {
std::ifstream stream(Path + "/mods.json");

stream >> modsDB;

stream.close();
} catch (const std::exception& e) {
beammp_errorf("Failed to load mods.json: {}", e.what());
}
}

for (const auto& entry : fs::directory_iterator(Path)) {
std::string File(entry.path().string());

if (entry.path().filename().string() == "mods.json") {
continue;
}

if (entry.path().extension() != ".zip" || std::filesystem::is_directory(entry.path())) {
beammp_warnf("'{}' is not a ZIP file and will be ignored", File);
continue;
}

if (modsDB.contains(entry.path().filename().string())) {
auto& dbEntry = modsDB[entry.path().filename().string()];
if (entry.last_write_time().time_since_epoch().count() > dbEntry["lastwrite"] || std::filesystem::file_size(File) != dbEntry["filesize"].get<size_t>()) {
beammp_infof("File '{}' has been modified, rehashing", File);
} else {
dbEntry["exists"] = true;

mMods.push_back(nlohmann::json {
{ "file_name", std::filesystem::path(File).filename() },
{ "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" },
{ "hash", dbEntry["hash"] },
{ "protected", dbEntry["protected"] } });

beammp_debugf("Mod '{}' loaded from cache", File);

continue;
}
}

try {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
Expand Down Expand Up @@ -133,9 +169,73 @@ void TResourceManager::RefreshFiles() {
{ "file_size", std::filesystem::file_size(File) },
{ "hash_algorithm", "sha256" },
{ "hash", result },
});
{ "protected", false } });

modsDB[std::filesystem::path(File).filename().string()] = {
{ "lastwrite", entry.last_write_time().time_since_epoch().count() },
{ "hash", result },
{ "filesize", std::filesystem::file_size(File) },
{ "protected", false },
{ "exists", true }
};

} catch (const std::exception& e) {
beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what());
}
}

for (auto it = modsDB.begin(); it != modsDB.end();) {
if (!it.value().contains("exists")) {
it = modsDB.erase(it);
} else {
it.value().erase("exists");
++it;
}
}

try {
std::ofstream stream(Path + "/mods.json");

stream << modsDB.dump(4);

stream.close();
} catch (std::exception& e) {
beammp_error("Failed to update mod DB: " + std::string(e.what()));
}
}

void TResourceManager::SetProtected(const std::string& ModName, bool Protected) {
std::unique_lock Lock(mModsMutex);

for (auto& mod : mMods) {
if (mod["file_name"].get<std::string>() == ModName) {
mod["protected"] = Protected;
break;
}
}

auto modsDBPath = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/mods.json";

if (std::filesystem::exists(modsDBPath)) {
try {
nlohmann::json modsDB;

std::fstream stream(modsDBPath);

stream >> modsDB;

if (modsDB.contains(ModName)) {
modsDB[ModName]["protected"] = Protected;
}

stream.clear();
stream.seekp(0, std::ios::beg);

stream << modsDB.dump(4);

stream.close();
} catch (const std::exception& e) {
beammp_errorf("Failed to update mods.json: {}", e.what());
}
}
}
Loading