From 7a439bb5b92b2e02be90b1fb577741c538cfd086 Mon Sep 17 00:00:00 2001 From: Tixx <83774803+WiserTixx@users.noreply.github.com> Date: Mon, 31 Mar 2025 08:04:15 +0200 Subject: [PATCH] Add mod hash caching and mod protection --- include/TResourceManager.h | 1 + src/TNetwork.cpp | 5 ++++ src/TResourceManager.cpp | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/include/TResourceManager.h b/include/TResourceManager.h index 5cdc498..c1ae46c 100644 --- a/include/TResourceManager.h +++ b/include/TResourceManager.h @@ -30,6 +30,7 @@ public: [[nodiscard]] std::string TrimmedList() const { return mTrimmedList; } [[nodiscard]] std::string FileSizes() const { return mFileSizes; } [[nodiscard]] int ModsLoaded() const { return mModsLoaded; } + [[nodiscard]] nlohmann::json GetMods() const { return mMods; } [[nodiscard]] std::string NewFileList() const; diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index f7a818d..06263f5 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -810,6 +810,11 @@ void TNetwork::SendFile(TClient& c, const std::string& UnsafeName) { auto FileName = fs::path(UnsafeName).filename().string(); FileName = Application::Settings.getAsString(Settings::Key::General_ResourceFolder) + "/Client/" + FileName; + for (auto mod : mResourceManager.GetMods()) { + if (mod["filename"] == FileName && mod["protected"] == true) + return; + } + if (!std::filesystem::exists(FileName)) { if (!TCPSend(c, StringToVector("CO"))) { // TODO: handle diff --git a/src/TResourceManager.cpp b/src/TResourceManager.cpp index 7a3639b..478cb26 100644 --- a/src/TResourceManager.cpp +++ b/src/TResourceManager.cpp @@ -66,14 +66,52 @@ void TResourceManager::RefreshFiles() { 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()) { + beammp_infof("File '{}' has been modified, rehashing", File); + } else { + 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; @@ -133,9 +171,28 @@ 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 } + }; + } catch (const std::exception& e) { beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what()); } } + + 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())); + } }