From a4b62d013cda7c9837c84376e8a169ae0b4769fa Mon Sep 17 00:00:00 2001 From: Lion Kortlepel Date: Sun, 29 Sep 2024 00:32:52 +0200 Subject: [PATCH] implement mod hashing + new download --- include/TResourceManager.h | 8 ++++ src/TNetwork.cpp | 8 ++-- src/TResourceManager.cpp | 85 ++++++++++++++++++++++++++++++++++++++ src/main.cpp | 13 +++--- 4 files changed, 104 insertions(+), 10 deletions(-) diff --git a/include/TResourceManager.h b/include/TResourceManager.h index 05f676a..aad951b 100644 --- a/include/TResourceManager.h +++ b/include/TResourceManager.h @@ -19,6 +19,7 @@ #pragma once #include "Common.h" +#include 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; }; diff --git a/src/TNetwork.cpp b/src/TNetwork.cpp index b50af56..f1d5cda 100644 --- a/src/TNetwork.cpp +++ b/src/TNetwork.cpp @@ -758,11 +758,11 @@ void TNetwork::Parse(TClient& c, const std::vector& 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; diff --git a/src/TResourceManager.cpp b/src/TResourceManager.cpp index 5582af3..89d4258 100644 --- a/src/TResourceManager.cpp +++ b/src/TResourceManager.cpp @@ -17,9 +17,14 @@ // along with this program. If not, see . #include "TResourceManager.h" +#include "Common.h" #include #include +#include +#include +#include +#include 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 Data; + while (Read < FileSize) { + Data.resize(size_t(std::min(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", File }, + { "file_size", Read }, + { "hash_algorithm", "sha256" }, + { "hash", result }, + }); + } catch (const std::exception& e) { + beammp_errorf("Sha256 hashing of '{}' failed: {}", File, e.what()); + } + } +} diff --git a/src/main.cpp b/src/main.cpp index 2b36e5a..f28bd5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,19 +36,19 @@ #include 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);