Add secure m2p loader with runtime key enforcement

This commit is contained in:
server
2026-04-14 12:12:23 +02:00
parent 0c2d6c7c9c
commit 229c809b96
8 changed files with 719 additions and 6 deletions

275
src/PackLib/M2Pack.cpp Normal file
View File

@@ -0,0 +1,275 @@
#include "M2Pack.h"
#include "M2PackRuntimeKeyProvider.h"
#include <cstring>
#include <zstd.h>
#include "EterBase/Debug.h"
#include "EterLib/BufferPool.h"
namespace
{
thread_local ZSTD_DCtx* g_m2pack_zstd_dctx = nullptr;
ZSTD_DCtx* GetThreadLocalZstdContext()
{
if (!g_m2pack_zstd_dctx)
{
g_m2pack_zstd_dctx = ZSTD_createDCtx();
}
return g_m2pack_zstd_dctx;
}
template <typename T>
bool ReadPod(const uint8_t* bytes, std::size_t size, std::size_t& offset, T& out)
{
if (offset + sizeof(T) > size)
{
return false;
}
memcpy(&out, bytes + offset, sizeof(T));
offset += sizeof(T);
return true;
}
}
bool CM2Pack::Load(const std::string& path)
{
if (!HasM2PackRuntimeKeysForArchiveLoad())
{
TraceError("CM2Pack::Load: runtime master key required for '%s'", path.c_str());
return false;
}
std::error_code ec;
m_file.map(path, ec);
if (ec)
{
TraceError("CM2Pack::Load: map failed for '%s': %s", path.c_str(), ec.message().c_str());
return false;
}
if (m_file.size() < sizeof(TM2PackHeader))
{
TraceError("CM2Pack::Load: file too small '%s'", path.c_str());
return false;
}
memcpy(&m_header, m_file.data(), sizeof(TM2PackHeader));
static constexpr char kMagic[M2PACK_MAGIC_SIZE] = {'M', '2', 'P', 'A', 'C', 'K', '2', '\0'};
if (memcmp(m_header.magic, kMagic, sizeof(kMagic)) != 0)
{
TraceError("CM2Pack::Load: invalid magic in '%s'", path.c_str());
return false;
}
if (m_header.version != 1)
{
TraceError("CM2Pack::Load: unsupported version %u in '%s'", m_header.version, path.c_str());
return false;
}
if (m_header.manifest_offset + m_header.manifest_size > m_file.size())
{
TraceError("CM2Pack::Load: manifest out of bounds in '%s'", path.c_str());
return false;
}
m_manifest_bytes.assign(
m_file.data() + m_header.manifest_offset,
m_file.data() + m_header.manifest_offset + m_header.manifest_size);
if (!ValidateManifest())
{
TraceError("CM2Pack::Load: manifest validation failed for '%s'", path.c_str());
return false;
}
return true;
}
bool CM2Pack::ValidateManifest()
{
std::array<uint8_t, M2PACK_HASH_SIZE> manifest_hash {};
crypto_generichash(
manifest_hash.data(),
manifest_hash.size(),
m_manifest_bytes.data(),
m_manifest_bytes.size(),
nullptr,
0);
if (memcmp(manifest_hash.data(), m_header.manifest_hash, manifest_hash.size()) != 0)
{
TraceError("CM2Pack::ValidateManifest: manifest hash mismatch");
return false;
}
if (crypto_sign_verify_detached(
m_header.manifest_signature,
m_manifest_bytes.data(),
m_manifest_bytes.size(),
GetM2PackActivePublicKey().data()) != 0)
{
TraceError("CM2Pack::ValidateManifest: manifest signature mismatch");
return false;
}
std::size_t offset = 0;
TM2PackManifestHeader manifest_header {};
if (!ReadPod(m_manifest_bytes.data(), m_manifest_bytes.size(), offset, manifest_header))
{
return false;
}
m_index.clear();
m_index.reserve(manifest_header.entry_count);
for (uint32_t i = 0; i < manifest_header.entry_count; ++i)
{
TM2PackManifestEntryFixed fixed {};
if (!ReadPod(m_manifest_bytes.data(), m_manifest_bytes.size(), offset, fixed))
{
return false;
}
if (offset + fixed.path_size > m_manifest_bytes.size())
{
return false;
}
TM2PackEntry entry;
entry.path.assign(reinterpret_cast<const char*>(m_manifest_bytes.data() + offset), fixed.path_size);
offset += fixed.path_size;
entry.compression = fixed.compression;
entry.data_offset = fixed.data_offset;
entry.original_size = fixed.original_size;
entry.stored_size = fixed.stored_size;
memcpy(entry.nonce.data(), fixed.nonce, entry.nonce.size());
memcpy(entry.plaintext_hash.data(), fixed.plaintext_hash, entry.plaintext_hash.size());
const uint64_t payload_begin = sizeof(TM2PackHeader);
const uint64_t payload_end = m_header.manifest_offset;
const uint64_t begin = payload_begin + entry.data_offset;
const uint64_t end = begin + entry.stored_size;
if (entry.path.empty() || entry.path.find("..") != std::string::npos || entry.path[0] == '/')
{
TraceError("CM2Pack::ValidateManifest: invalid path '%s'", entry.path.c_str());
return false;
}
if (begin > payload_end || end > payload_end || end < begin)
{
TraceError("CM2Pack::ValidateManifest: invalid entry bounds '%s'", entry.path.c_str());
return false;
}
m_index.push_back(std::move(entry));
}
return true;
}
bool CM2Pack::DecryptEntryPayload(const TM2PackEntry& entry, std::vector<uint8_t>& decrypted, CBufferPool* pPool)
{
const uint64_t begin = sizeof(TM2PackHeader) + entry.data_offset;
const auto* ciphertext = reinterpret_cast<const uint8_t*>(m_file.data() + begin);
std::vector<uint8_t> ciphertext_copy;
if (pPool)
{
ciphertext_copy = pPool->Acquire(entry.stored_size);
}
ciphertext_copy.resize(entry.stored_size);
memcpy(ciphertext_copy.data(), ciphertext, entry.stored_size);
decrypted.resize(entry.stored_size);
unsigned long long written = 0;
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
decrypted.data(),
&written,
nullptr,
ciphertext_copy.data(),
ciphertext_copy.size(),
reinterpret_cast<const unsigned char*>(entry.path.data()),
entry.path.size(),
entry.nonce.data(),
GetM2PackActiveMasterKey().data()) != 0)
{
if (pPool)
{
pPool->Release(std::move(ciphertext_copy));
}
return false;
}
decrypted.resize(static_cast<std::size_t>(written));
if (pPool)
{
pPool->Release(std::move(ciphertext_copy));
}
return true;
}
bool CM2Pack::GetFile(const TM2PackEntry& entry, std::vector<uint8_t>& result)
{
return GetFileWithPool(entry, result, nullptr);
}
bool CM2Pack::GetFileWithPool(const TM2PackEntry& entry, std::vector<uint8_t>& result, CBufferPool* pPool)
{
std::vector<uint8_t> compressed;
if (!DecryptEntryPayload(entry, compressed, pPool))
{
TraceError("CM2Pack::GetFileWithPool: decrypt failed for '%s'", entry.path.c_str());
return false;
}
switch (entry.compression)
{
case 0:
result = std::move(compressed);
break;
case 1:
{
result.resize(entry.original_size);
ZSTD_DCtx* dctx = GetThreadLocalZstdContext();
size_t written = ZSTD_decompressDCtx(dctx, result.data(), result.size(), compressed.data(), compressed.size());
if (ZSTD_isError(written) || written != entry.original_size)
{
TraceError("CM2Pack::GetFileWithPool: zstd failed for '%s'", entry.path.c_str());
return false;
}
break;
}
default:
TraceError("CM2Pack::GetFileWithPool: unsupported compression %u for '%s'", entry.compression, entry.path.c_str());
return false;
}
std::array<uint8_t, M2PACK_HASH_SIZE> plain_hash {};
crypto_generichash(
plain_hash.data(),
plain_hash.size(),
result.data(),
result.size(),
nullptr,
0);
if (memcmp(plain_hash.data(), entry.plaintext_hash.data(), plain_hash.size()) != 0)
{
TraceError("CM2Pack::GetFileWithPool: plaintext hash mismatch for '%s'", entry.path.c_str());
return false;
}
return true;
}

92
src/PackLib/M2Pack.h Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <mio/mmap.hpp>
#include <sodium.h>
class CBufferPool;
constexpr std::size_t M2PACK_MAGIC_SIZE = 8;
constexpr std::size_t M2PACK_HASH_SIZE = 32;
constexpr std::size_t M2PACK_SIGNATURE_SIZE = 64;
constexpr std::size_t M2PACK_KEY_SIZE = crypto_aead_xchacha20poly1305_ietf_KEYBYTES;
constexpr std::size_t M2PACK_NONCE_SIZE = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
constexpr std::size_t M2PACK_PUBLIC_KEY_SIZE = crypto_sign_PUBLICKEYBYTES;
#pragma pack(push, 1)
struct TM2PackHeader
{
char magic[M2PACK_MAGIC_SIZE];
uint32_t version;
uint32_t flags;
uint64_t manifest_offset;
uint64_t manifest_size;
uint8_t manifest_hash[M2PACK_HASH_SIZE];
uint8_t manifest_signature[M2PACK_SIGNATURE_SIZE];
uint8_t reserved[64];
};
struct TM2PackManifestHeader
{
uint32_t entry_count;
uint32_t flags;
};
struct TM2PackManifestEntryFixed
{
uint16_t path_size;
uint8_t compression;
uint8_t flags;
uint64_t data_offset;
uint64_t original_size;
uint64_t stored_size;
uint8_t nonce[M2PACK_NONCE_SIZE];
uint8_t plaintext_hash[M2PACK_HASH_SIZE];
};
#pragma pack(pop)
struct TM2PackEntry
{
std::string path;
uint8_t compression = 0;
uint64_t data_offset = 0;
uint64_t original_size = 0;
uint64_t stored_size = 0;
std::array<uint8_t, M2PACK_NONCE_SIZE> nonce {};
std::array<uint8_t, M2PACK_HASH_SIZE> plaintext_hash {};
};
class CM2Pack : public std::enable_shared_from_this<CM2Pack>
{
public:
CM2Pack() = default;
~CM2Pack() = default;
bool Load(const std::string& path);
const std::vector<TM2PackEntry>& GetIndex() const { return m_index; }
bool GetFile(const TM2PackEntry& entry, std::vector<uint8_t>& result);
bool GetFileWithPool(const TM2PackEntry& entry, std::vector<uint8_t>& result, CBufferPool* pPool);
private:
bool ValidateManifest();
bool DecryptEntryPayload(const TM2PackEntry& entry, std::vector<uint8_t>& decrypted, CBufferPool* pPool);
private:
TM2PackHeader m_header {};
std::vector<uint8_t> m_manifest_bytes;
std::vector<TM2PackEntry> m_index;
mio::mmap_source m_file;
};
using TM2PackFileMapEntry = std::pair<std::shared_ptr<CM2Pack>, TM2PackEntry>;
using TM2PackFileMap = std::unordered_map<std::string, TM2PackFileMapEntry>;
#include "M2PackKeys.h"

24
src/PackLib/M2PackKeys.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <array>
#include <cstdint>
// Generated by m2pack export-client-config.
// Do not edit manually.
// Runtime master key delivery is required for .m2p loading.
constexpr bool M2PACK_RUNTIME_MASTER_KEY_REQUIRED = true;
constexpr std::array<uint8_t, M2PACK_KEY_SIZE> M2PACK_MASTER_KEY = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
constexpr std::array<uint8_t, M2PACK_PUBLIC_KEY_SIZE> M2PACK_SIGN_PUBLIC_KEY = {
0x22, 0x69, 0x26, 0xd0, 0xa9, 0xa5, 0x53, 0x4c,
0x95, 0x45, 0xec, 0xba, 0xe9, 0x32, 0x46, 0xc9,
0x43, 0x80, 0x5c, 0x1a, 0x2c, 0x57, 0xc0, 0x03,
0xd9, 0x72, 0x41, 0x19, 0xea, 0x0b, 0xc6, 0xa4
};

View File

@@ -0,0 +1,249 @@
#include "M2PackRuntimeKeyProvider.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <windows.h>
#include "EterBase/Debug.h"
#include "EterBase/Utils.h"
namespace
{
constexpr char M2PACK_SHARED_KEY_MAGIC[8] = {'M', '2', 'K', 'E', 'Y', 'S', '1', '\0'};
constexpr const char* M2PACK_DEFAULT_MAP_NAME = "Local\\M2PackSharedKeys";
constexpr const char* M2PACK_ENV_MASTER_KEY = "M2PACK_MASTER_KEY_HEX";
constexpr const char* M2PACK_ENV_PUBLIC_KEY = "M2PACK_SIGN_PUBKEY_HEX";
constexpr const char* M2PACK_ENV_MAP_NAME = "M2PACK_KEY_MAP";
#pragma pack(push, 1)
struct M2PackSharedKeys
{
char magic[8];
uint32_t version;
uint32_t flags;
uint8_t master_key[M2PACK_KEY_SIZE];
uint8_t sign_public_key[M2PACK_PUBLIC_KEY_SIZE];
};
#pragma pack(pop)
struct RuntimeKeyState
{
std::array<uint8_t, M2PACK_KEY_SIZE> master_key {};
std::array<uint8_t, M2PACK_PUBLIC_KEY_SIZE> public_key = M2PACK_SIGN_PUBLIC_KEY;
bool runtime_master_key = false;
bool runtime_public_key = false;
bool initialized = false;
};
RuntimeKeyState g_state;
uint8_t HexNibble(char ch)
{
if (ch >= '0' && ch <= '9')
return static_cast<uint8_t>(ch - '0');
if (ch >= 'a' && ch <= 'f')
return static_cast<uint8_t>(10 + ch - 'a');
if (ch >= 'A' && ch <= 'F')
return static_cast<uint8_t>(10 + ch - 'A');
return 0xff;
}
template <std::size_t N>
bool ParseHexInto(std::string value, std::array<uint8_t, N>& out)
{
value.erase(std::remove_if(value.begin(), value.end(), [](unsigned char ch) {
return std::isspace(ch) != 0;
}), value.end());
if (value.size() != N * 2)
return false;
for (std::size_t i = 0; i < N; ++i)
{
const uint8_t hi = HexNibble(value[i * 2]);
const uint8_t lo = HexNibble(value[i * 2 + 1]);
if (hi == 0xff || lo == 0xff)
return false;
out[i] = static_cast<uint8_t>((hi << 4) | lo);
}
return true;
}
std::string GetEnvString(const char* name)
{
char buffer[512];
const DWORD len = GetEnvironmentVariableA(name, buffer, sizeof(buffer));
if (len == 0 || len >= sizeof(buffer))
return {};
return std::string(buffer, buffer + len);
}
bool LoadFromSharedMapping(const std::string& mappingName)
{
const HANDLE mapping = OpenFileMappingA(FILE_MAP_READ, FALSE, mappingName.c_str());
if (!mapping)
return false;
void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, sizeof(M2PackSharedKeys));
if (!view)
{
CloseHandle(mapping);
return false;
}
M2PackSharedKeys blob {};
memcpy(&blob, view, sizeof(blob));
UnmapViewOfFile(view);
CloseHandle(mapping);
if (memcmp(blob.magic, M2PACK_SHARED_KEY_MAGIC, sizeof(blob.magic)) != 0 || blob.version != 1)
{
TraceError("M2Pack key mapping exists but has invalid header");
return false;
}
memcpy(g_state.master_key.data(), blob.master_key, g_state.master_key.size());
memcpy(g_state.public_key.data(), blob.sign_public_key, g_state.public_key.size());
g_state.runtime_master_key = true;
g_state.runtime_public_key = true;
return true;
}
void ApplyCommandLineOption(const std::string& key, const std::string& value)
{
if (key == "--m2pack-key-hex")
{
if (ParseHexInto(value, g_state.master_key))
{
g_state.runtime_master_key = true;
}
else
{
TraceError("Invalid value for --m2pack-key-hex");
}
return;
}
if (key == "--m2pack-pubkey-hex")
{
if (ParseHexInto(value, g_state.public_key))
{
g_state.runtime_public_key = true;
}
else
{
TraceError("Invalid value for --m2pack-pubkey-hex");
}
return;
}
if (key == "--m2pack-key-map")
{
LoadFromSharedMapping(value);
return;
}
}
} // namespace
bool InitializeM2PackRuntimeKeyProvider(const char* commandLine)
{
if (g_state.initialized)
return true;
g_state = RuntimeKeyState {};
const std::string mapName = GetEnvString(M2PACK_ENV_MAP_NAME);
if (!mapName.empty())
{
LoadFromSharedMapping(mapName);
}
else
{
LoadFromSharedMapping(M2PACK_DEFAULT_MAP_NAME);
}
const std::string envMaster = GetEnvString(M2PACK_ENV_MASTER_KEY);
if (!envMaster.empty())
{
if (ParseHexInto(envMaster, g_state.master_key))
g_state.runtime_master_key = true;
else
TraceError("Invalid M2PACK_MASTER_KEY_HEX value");
}
const std::string envPublic = GetEnvString(M2PACK_ENV_PUBLIC_KEY);
if (!envPublic.empty())
{
if (ParseHexInto(envPublic, g_state.public_key))
g_state.runtime_public_key = true;
else
TraceError("Invalid M2PACK_SIGN_PUBKEY_HEX value");
}
int argc = 0;
PCHAR* argv = CommandLineToArgv(const_cast<PCHAR>(commandLine ? commandLine : ""), &argc);
if (argv)
{
for (int i = 0; i < argc; ++i)
{
const std::string key = argv[i];
if ((key == "--m2pack-key-hex" || key == "--m2pack-pubkey-hex" || key == "--m2pack-key-map") && i + 1 < argc)
{
ApplyCommandLineOption(key, argv[i + 1]);
++i;
}
}
SAFE_FREE_GLOBAL(argv);
}
if (g_state.runtime_master_key || g_state.runtime_public_key)
{
Tracef("M2Pack runtime key provider: override active (master=%d public=%d)\n",
g_state.runtime_master_key ? 1 : 0,
g_state.runtime_public_key ? 1 : 0);
}
else
{
Tracef("M2Pack runtime key provider: no runtime master key available; .m2p loading will be denied\n");
}
g_state.initialized = true;
return true;
}
const std::array<uint8_t, M2PACK_KEY_SIZE>& GetM2PackActiveMasterKey()
{
return g_state.master_key;
}
const std::array<uint8_t, M2PACK_PUBLIC_KEY_SIZE>& GetM2PackActivePublicKey()
{
return g_state.public_key;
}
bool HasM2PackRuntimeMasterKey()
{
return g_state.runtime_master_key;
}
bool HasM2PackRuntimeKeysForArchiveLoad()
{
return g_state.runtime_master_key;
}
bool IsM2PackUsingRuntimeMasterKey()
{
return g_state.runtime_master_key;
}
bool IsM2PackUsingRuntimePublicKey()
{
return g_state.runtime_public_key;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <array>
#include "M2Pack.h"
bool InitializeM2PackRuntimeKeyProvider(const char* commandLine);
const std::array<uint8_t, M2PACK_KEY_SIZE>& GetM2PackActiveMasterKey();
const std::array<uint8_t, M2PACK_PUBLIC_KEY_SIZE>& GetM2PackActivePublicKey();
bool HasM2PackRuntimeMasterKey();
bool HasM2PackRuntimeKeysForArchiveLoad();
bool IsM2PackUsingRuntimeMasterKey();
bool IsM2PackUsingRuntimePublicKey();

View File

@@ -1,7 +1,8 @@
#include "PackManager.h" #include "PackManager.h"
#include "EterLib/BufferPool.h" #include "EterLib/BufferPool.h"
#include <fstream>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <memory>
#include "EterBase/Debug.h" #include "EterBase/Debug.h"
CPackManager::CPackManager() CPackManager::CPackManager()
@@ -22,6 +23,25 @@ CPackManager::~CPackManager()
bool CPackManager::AddPack(const std::string& path) bool CPackManager::AddPack(const std::string& path)
{ {
const std::filesystem::path packPath(path);
if (packPath.extension() == ".m2p")
{
std::shared_ptr<CM2Pack> pack = std::make_shared<CM2Pack>();
if (!pack->Load(path))
{
return false;
}
std::lock_guard<std::mutex> lock(m_mutex);
const auto& index = pack->GetIndex();
for (const auto& entry : index)
{
m_m2_entries[entry.path] = std::make_pair(pack, entry);
}
return true;
}
std::shared_ptr<CPack> pack = std::make_shared<CPack>(); std::shared_ptr<CPack> pack = std::make_shared<CPack>();
if (!pack->Load(path)) if (!pack->Load(path))
@@ -51,6 +71,11 @@ bool CPackManager::GetFileWithPool(std::string_view path, TPackFile& result, CBu
// First try to load from pack // First try to load from pack
if (m_load_from_pack) { if (m_load_from_pack) {
auto m2it = m_m2_entries.find(buf);
if (m2it != m_m2_entries.end()) {
return m2it->second.first->GetFileWithPool(m2it->second.second, result, pPool);
}
auto it = m_entries.find(buf); auto it = m_entries.find(buf);
if (it != m_entries.end()) { if (it != m_entries.end()) {
return it->second.first->GetFileWithPool(it->second.second, result, pPool); return it->second.first->GetFileWithPool(it->second.second, result, pPool);
@@ -86,6 +111,11 @@ bool CPackManager::IsExist(std::string_view path) const
// First check in pack entries // First check in pack entries
if (m_load_from_pack) { if (m_load_from_pack) {
auto m2it = m_m2_entries.find(buf);
if (m2it != m_m2_entries.end()) {
return true;
}
auto it = m_entries.find(buf); auto it = m_entries.find(buf);
if (it != m_entries.end()) { if (it != m_entries.end()) {
return true; return true;

View File

@@ -3,6 +3,7 @@
#include <mutex> #include <mutex>
#include "EterBase/Singleton.h" #include "EterBase/Singleton.h"
#include "M2Pack.h"
#include "Pack.h" #include "Pack.h"
class CBufferPool; class CBufferPool;
@@ -29,6 +30,7 @@ private:
private: private:
bool m_load_from_pack = true; bool m_load_from_pack = true;
TPackFileMap m_entries; TPackFileMap m_entries;
TM2PackFileMap m_m2_entries;
CBufferPool* m_pBufferPool; CBufferPool* m_pBufferPool;
mutable std::mutex m_mutex; // Thread safety for parallel pack loading mutable std::mutex m_mutex; // Thread safety for parallel pack loading
}; };

View File

@@ -14,6 +14,7 @@
#include "EterBase/lzo.h" #include "EterBase/lzo.h"
#include "PackLib/PackManager.h" #include "PackLib/PackManager.h"
#include "PackLib/M2PackRuntimeKeyProvider.h"
#include <filesystem> #include <filesystem>
#include <format> #include <format>
@@ -57,6 +58,33 @@ bool PackInitialize(const char * c_pszFolder)
if (_access(c_pszFolder, 0) != 0) if (_access(c_pszFolder, 0) != 0)
return false; return false;
auto AddPreferredPack = [c_pszFolder](const std::string& packName, bool required) -> bool
{
const std::string m2Path = std::format("{}/{}.m2p", c_pszFolder, packName);
const std::string legacyPath = std::format("{}/{}.pck", c_pszFolder, packName);
if (_access(m2Path.c_str(), 0) == 0)
{
Tracef("PackInitialize: Loading %s\n", m2Path.c_str());
if (CPackManager::instance().AddPack(m2Path))
return true;
TraceError("PackInitialize: Failed to load %s", m2Path.c_str());
return false;
}
if (_access(legacyPath.c_str(), 0) == 0)
{
Tracef("PackInitialize: Loading %s\n", legacyPath.c_str());
if (CPackManager::instance().AddPack(legacyPath))
return true;
TraceError("PackInitialize: Failed to load %s", legacyPath.c_str());
}
return !required;
};
std::vector<std::string> packFiles = { std::vector<std::string> packFiles = {
"patch1", "patch1",
"season3_eu", "season3_eu",
@@ -149,18 +177,16 @@ bool PackInitialize(const char * c_pszFolder)
"uiloading", "uiloading",
}; };
Tracef("PackInitialize: Loading root.pck\n");
DWORD dwStartTime = GetTickCount(); DWORD dwStartTime = GetTickCount();
if (!CPackManager::instance().AddPack(std::format("{}/root.pck", c_pszFolder))) if (!AddPreferredPack("root", true))
{ {
TraceError("Failed to load root.pck"); TraceError("Failed to load root pack");
return false; return false;
} }
Tracef("PackInitialize: Loading %d pack files...", packFiles.size()); Tracef("PackInitialize: Loading %d pack files...", packFiles.size());
for (const std::string& packFileName : packFiles) { for (const std::string& packFileName : packFiles) {
Tracef("PackInitialize: Loading %s.pck\n", packFileName.c_str()); AddPreferredPack(packFileName, false);
CPackManager::instance().AddPack(std::format("{}/{}.pck", c_pszFolder, packFileName));
} }
Tracef("PackInitialize: done. Time taken: %d ms\n", GetTickCount() - dwStartTime); Tracef("PackInitialize: done. Time taken: %d ms\n", GetTickCount() - dwStartTime);
return true; return true;
@@ -326,5 +352,7 @@ int Setup(LPSTR lpCmdLine)
Callback.Function = nullptr; Callback.Function = nullptr;
Callback.UserData = 0; Callback.UserData = 0;
GrannySetLogCallback(&Callback); GrannySetLogCallback(&Callback);
InitializeM2PackRuntimeKeyProvider(lpCmdLine);
return 1; return 1;
} }