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) {