diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 191efb53..c3eae601 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -16,12 +16,12 @@ jobs: os: [ windows-2022, ubuntu-22.04, macos-13 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2 with: submodules: true - name: Add msbuild to PATH (Windows only) if: ${{ matrix.os == 'windows-2022' }} - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330 with: vs-version: '[17,18)' msbuild-architecture: x64 @@ -30,12 +30,16 @@ jobs: run: | brew install openssl@3 sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer + - name: Get CMake 3.x + uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c + with: + cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version - name: cmake - uses: lukka/run-cmake@v10 + uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38 with: workflowPreset: "ci-${{matrix.os}}" - name: artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47 with: name: build-${{matrix.os}} path: | diff --git a/README.md b/README.md index 642bb9dc..7fce47ee 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer ### Windows packages Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed. -You'll also need to download and install [CMake](https://cmake.org/download/) (version **CMake version 3.25** or later!). +You'll also need to download and install [CMake](https://cmake.org/download/) (**version 3.25** up to **version 3.31**!). ### MacOS packages Ensure you have [brew](https://brew.sh) installed. @@ -100,7 +100,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve ``` #### Required CMake version -This project uses **CMake version 3.25** or higher and as such you will need to ensure you have this version installed. +This project uses **CMake version 3.25** up to **version 3.31** and as such you will need to ensure you have this version installed. You can check your CMake version by using the following command in a terminal. ```bash cmake --version diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index 59427ee5..bc4fbf35 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -6,6 +6,9 @@ #include "zlib.h" +constexpr uint32_t CRC32_INIT = 0xFFFFFFFF; +constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4}; + AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::is_directory(path)) { throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory."); @@ -18,12 +21,20 @@ AssetManager::AssetManager(const std::filesystem::path& path) { m_RootPath = m_Path; m_ResPath = (m_Path / "client" / "res"); - } else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) { + } else if (std::filesystem::exists(m_Path / "res" / "pack")) { + if (!std::filesystem::exists(m_Path / ".." / "versions")) { + throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); + } + m_AssetBundleType = eAssetBundleType::Packed; m_RootPath = (m_Path / ".."); m_ResPath = (m_Path / "res"); - } else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) { + } else if (std::filesystem::exists(m_Path / "pack")) { + if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) { + throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); + } + m_AssetBundleType = eAssetBundleType::Packed; m_RootPath = (m_Path / ".." / ".."); @@ -48,6 +59,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) { break; } case eAssetBundleType::None: + [[fallthrough]]; case eAssetBundleType::Unpacked: { break; } @@ -55,19 +67,10 @@ AssetManager::AssetManager(const std::filesystem::path& path) { } void AssetManager::LoadPackIndex() { - m_PackIndex = new PackIndex(m_RootPath); + m_PackIndex = PackIndex(m_RootPath); } -std::filesystem::path AssetManager::GetResPath() { - return m_ResPath; -} - -eAssetBundleType AssetManager::GetAssetBundleType() { - return m_AssetBundleType; -} - -bool AssetManager::HasFile(const char* name) { - auto fixedName = std::string(name); +bool AssetManager::HasFile(std::string fixedName) const { std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); }); // Special case for unpacked client have BrickModels in upper case @@ -81,8 +84,7 @@ bool AssetManager::HasFile(const char* name) { std::replace(fixedName.begin(), fixedName.end(), '/', '\\'); if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName; - uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast(const_cast(fixedName.c_str())), fixedName.size()); - crc = crc32b(crc, reinterpret_cast(const_cast("\0\0\0\0")), 4); + const auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR); for (const auto& item : this->m_PackIndex->GetPackFileIndices()) { if (item.m_Crc == crc) { @@ -93,8 +95,7 @@ bool AssetManager::HasFile(const char* name) { return false; } -bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) { - auto fixedName = std::string(name); +bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) const { std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); }); std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes @@ -129,8 +130,7 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) { fixedName = "client\\res\\" + fixedName; } int32_t packIndex = -1; - uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast(const_cast(fixedName.c_str())), fixedName.size()); - crc = crc32b(crc, reinterpret_cast(const_cast("\0\0\0\0")), 4); + auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR); for (const auto& item : this->m_PackIndex->GetPackFileIndices()) { if (item.m_Crc == crc) { @@ -144,15 +144,13 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) { return false; } - auto packs = this->m_PackIndex->GetPacks(); - auto* pack = packs.at(packIndex); - - bool success = pack->ReadFileFromPack(crc, data, len); + const auto& pack = this->m_PackIndex->GetPacks().at(packIndex); + const bool success = pack.ReadFileFromPack(crc, data, len); return success; } -AssetStream AssetManager::GetFile(const char* name) { +AssetStream AssetManager::GetFile(const char* name) const { char* buf; uint32_t len; bool success = this->GetFile(name, &buf, &len); @@ -160,23 +158,15 @@ AssetStream AssetManager::GetFile(const char* name) { return AssetStream(buf, len, success); } -uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) { - size_t i, j; - uint32_t crc, msb; - - crc = base; - for (i = 0; i < l; i++) { +uint32_t AssetManager::crc32b(uint32_t crc, const std::string_view message) { + for (const auto byte : message) { // xor next byte to upper bits of crc - crc ^= (static_cast(message[i]) << 24); - for (j = 0; j < 8; j++) { // Do eight times. - msb = crc >> 31; + crc ^= (static_cast(std::bit_cast(byte)) << 24); + for (size_t _ = 0; _ < 8; _++) { // Do eight times. + const uint32_t msb = crc >> 31; crc <<= 1; crc ^= (0 - msb) & 0x04C11DB7; } } return crc; // don't complement crc on output } - -AssetManager::~AssetManager() { - delete m_PackIndex; -} diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index 2116f223..a63ffcf6 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -61,23 +61,32 @@ struct AssetStream : std::istream { class AssetManager { public: AssetManager(const std::filesystem::path& path); - ~AssetManager(); - std::filesystem::path GetResPath(); - eAssetBundleType GetAssetBundleType(); + [[nodiscard]] + const std::filesystem::path& GetResPath() const { + return m_ResPath; + } + + [[nodiscard]] + eAssetBundleType GetAssetBundleType() const { + return m_AssetBundleType; + } - bool HasFile(const char* name); - bool GetFile(const char* name, char** data, uint32_t* len); - AssetStream GetFile(const char* name); + [[nodiscard]] + bool HasFile(std::string name) const; + + [[nodiscard]] + bool GetFile(std::string name, char** data, uint32_t* len) const; + + [[nodiscard]] + AssetStream GetFile(const char* name) const; private: void LoadPackIndex(); // Modified crc algorithm (mpeg2) // Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2 - inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l); - - bool m_SuccessfullyLoaded; + static inline uint32_t crc32b(uint32_t crc, std::string_view message); std::filesystem::path m_Path; std::filesystem::path m_RootPath; @@ -85,5 +94,5 @@ private: eAssetBundleType m_AssetBundleType = eAssetBundleType::None; - PackIndex* m_PackIndex; + std::optional m_PackIndex; }; diff --git a/dCommon/dClient/Pack.cpp b/dCommon/dClient/Pack.cpp index cf32a53f..04d07f12 100644 --- a/dCommon/dClient/Pack.cpp +++ b/dCommon/dClient/Pack.cpp @@ -21,19 +21,20 @@ Pack::Pack(const std::filesystem::path& filePath) { m_FileStream.seekg(recordCountPos, std::ios::beg); - BinaryIO::BinaryRead(m_FileStream, m_RecordCount); + uint32_t recordCount = 0; + BinaryIO::BinaryRead(m_FileStream, recordCount); - for (int i = 0; i < m_RecordCount; i++) { + m_Records.reserve(recordCount); + std::generate_n(std::back_inserter(m_Records), recordCount, [&] { PackRecord record; BinaryIO::BinaryRead(m_FileStream, record); - - m_Records.push_back(record); - } + return record; + }); m_FileStream.close(); } -bool Pack::HasFile(uint32_t crc) { +bool Pack::HasFile(const uint32_t crc) const { for (const auto& record : m_Records) { if (record.m_Crc == crc) { return true; @@ -43,7 +44,7 @@ bool Pack::HasFile(uint32_t crc) { return false; } -bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) { +bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const { // Time for some wacky C file reading for speed reasons PackRecord pkRecord{}; diff --git a/dCommon/dClient/Pack.h b/dCommon/dClient/Pack.h index 8113ed55..406f18e0 100644 --- a/dCommon/dClient/Pack.h +++ b/dCommon/dClient/Pack.h @@ -24,16 +24,17 @@ struct PackRecord { class Pack { public: Pack(const std::filesystem::path& filePath); - ~Pack() = default; - bool HasFile(uint32_t crc); - bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len); + [[nodiscard]] + bool HasFile(uint32_t crc) const; + + [[nodiscard]] + bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) const; private: std::ifstream m_FileStream; std::filesystem::path m_FilePath; char m_Version[7]; - uint32_t m_RecordCount; std::vector m_Records; }; diff --git a/dCommon/dClient/PackIndex.cpp b/dCommon/dClient/PackIndex.cpp index 0d7a7fe9..256890a0 100644 --- a/dCommon/dClient/PackIndex.cpp +++ b/dCommon/dClient/PackIndex.cpp @@ -6,38 +6,32 @@ PackIndex::PackIndex(const std::filesystem::path& filePath) { m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary); + uint32_t packPathCount = 0; BinaryIO::BinaryRead(m_FileStream, m_Version); - BinaryIO::BinaryRead(m_FileStream, m_PackPathCount); + BinaryIO::BinaryRead(m_FileStream, packPathCount); - m_PackPaths.resize(m_PackPathCount); + m_PackPaths.resize(packPathCount); for (auto& item : m_PackPaths) { BinaryIO::ReadString(m_FileStream, item, BinaryIO::ReadType::String); } - BinaryIO::BinaryRead(m_FileStream, m_PackFileIndexCount); + uint32_t packFileIndexCount = 0; + BinaryIO::BinaryRead(m_FileStream, packFileIndexCount); - for (int i = 0; i < m_PackFileIndexCount; i++) { + m_PackFileIndices.reserve(packFileIndexCount); + std::generate_n(std::back_inserter(m_PackFileIndices), packFileIndexCount, [&] { PackFileIndex packFileIndex; BinaryIO::BinaryRead(m_FileStream, packFileIndex); - - m_PackFileIndices.push_back(packFileIndex); - } + return packFileIndex; + }); LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size()); + m_Packs.reserve(m_PackPaths.size()); for (auto& item : m_PackPaths) { std::replace(item.begin(), item.end(), '\\', '/'); - - auto* pack = new Pack(filePath / item); - - m_Packs.push_back(pack); + m_Packs.emplace_back(filePath / item); } m_FileStream.close(); } - -PackIndex::~PackIndex() { - for (const auto* item : m_Packs) { - delete item; - } -} diff --git a/dCommon/dClient/PackIndex.h b/dCommon/dClient/PackIndex.h index bf10b809..c25691ca 100644 --- a/dCommon/dClient/PackIndex.h +++ b/dCommon/dClient/PackIndex.h @@ -21,20 +21,23 @@ struct PackFileIndex { class PackIndex { public: PackIndex(const std::filesystem::path& filePath); - ~PackIndex(); - const std::vector& GetPackPaths() { return m_PackPaths; } - const std::vector& GetPackFileIndices() { return m_PackFileIndices; } - const std::vector& GetPacks() { return m_Packs; } + [[nodiscard]] + const std::vector& GetPackPaths() const { return m_PackPaths; } + + [[nodiscard]] + const std::vector& GetPackFileIndices() const { return m_PackFileIndices; } + + [[nodiscard]] + const std::vector& GetPacks() const { return m_Packs; } private: std::ifstream m_FileStream; uint32_t m_Version; - uint32_t m_PackPathCount; std::vector m_PackPaths; - uint32_t m_PackFileIndexCount; + std::vector m_PackFileIndices; - std::vector m_Packs; + std::vector m_Packs; }; diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index ea03d0e0..e430aefc 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -512,7 +512,7 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(*doneM); + mission->LoadFromXmlDone(*doneM); doneM = doneM->NextSiblingElement(); @@ -527,9 +527,9 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { currentM->QueryAttribute("id", &missionId); - auto* mission = new Mission(this, missionId); + auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId); - mission->LoadFromXml(*currentM); + mission->LoadFromXmlCur(*currentM); if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { mission->SetUniqueMissionOrderID(missionOrder); @@ -565,20 +565,23 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto* mission = pair.second; if (mission) { - const auto complete = mission->IsComplete(); + const auto completions = mission->GetCompletions(); auto* m = doc.NewElement("m"); - if (complete) { - mission->UpdateXml(*m); + if (completions > 0) { + mission->UpdateXmlDone(*m); done->LinkEndChild(m); - continue; + if (mission->IsComplete()) continue; + + m = doc.NewElement("m"); } + if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID()); - mission->UpdateXml(*m); + mission->UpdateXmlCur(*m); cur->LinkEndChild(m); } diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 2a841e39..f1f97eab 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -65,7 +65,7 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { } } -void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { +void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) { // Start custom XML if (element.Attribute("state") != nullptr) { m_State = static_cast(std::stoul(element.Attribute("state"))); @@ -76,11 +76,15 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { m_Completions = std::stoul(element.Attribute("cct")); m_Timestamp = std::stoul(element.Attribute("cts")); - - if (IsComplete()) { - return; - } } +} + +void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { + // Start custom XML + if (element.Attribute("state") != nullptr) { + m_State = static_cast(std::stoul(element.Attribute("state"))); + } + // End custom XML auto* task = element.FirstChildElement(); @@ -132,7 +136,7 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { } } -void Mission::UpdateXml(tinyxml2::XMLElement& element) { +void Mission::UpdateXmlDone(tinyxml2::XMLElement& element) { // Start custom XML element.SetAttribute("state", static_cast(m_State)); // End custom XML @@ -141,15 +145,21 @@ void Mission::UpdateXml(tinyxml2::XMLElement& element) { element.SetAttribute("id", static_cast(info.id)); - if (m_Completions > 0) { - element.SetAttribute("cct", static_cast(m_Completions)); + element.SetAttribute("cct", static_cast(m_Completions)); - element.SetAttribute("cts", static_cast(m_Timestamp)); + element.SetAttribute("cts", static_cast(m_Timestamp)); +} - if (IsComplete()) { - return; - } - } +void Mission::UpdateXmlCur(tinyxml2::XMLElement& element) { + // Start custom XML + element.SetAttribute("state", static_cast(m_State)); + // End custom XML + + element.DeleteChildren(); + + element.SetAttribute("id", static_cast(info.id)); + + if (IsComplete()) return; for (auto* task : m_Tasks) { if (task->GetType() == eMissionTaskType::COLLECTION || diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index 74b8d352..77f24181 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -28,8 +28,13 @@ public: Mission(MissionComponent* missionComponent, uint32_t missionId); ~Mission(); - void LoadFromXml(const tinyxml2::XMLElement& element); - void UpdateXml(tinyxml2::XMLElement& element); + // XML functions to load and save completed mission state to xml + void LoadFromXmlDone(const tinyxml2::XMLElement& element); + void UpdateXmlDone(tinyxml2::XMLElement& element); + + // XML functions to load and save current mission state and task data to xml + void LoadFromXmlCur(const tinyxml2::XMLElement& element); + void UpdateXmlCur(tinyxml2::XMLElement& element); /** * Returns the ID of this mission diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index d00d47e8..d3275b31 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -181,6 +181,7 @@ namespace Mail { void AttachmentCollectRequest::Handle() { AttachmentCollectResponse response; + response.mailID = mailID; auto inv = player->GetComponent(); if (mailID > 0 && playerID == player->GetObjectID() && inv) {