forked from metin-server/m2dev-client-src
Add secure m2p loader with runtime key enforcement
This commit is contained in:
275
src/PackLib/M2Pack.cpp
Normal file
275
src/PackLib/M2Pack.cpp
Normal 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
92
src/PackLib/M2Pack.h
Normal 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
24
src/PackLib/M2PackKeys.h
Normal 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
|
||||
};
|
||||
249
src/PackLib/M2PackRuntimeKeyProvider.cpp
Normal file
249
src/PackLib/M2PackRuntimeKeyProvider.cpp
Normal 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;
|
||||
}
|
||||
13
src/PackLib/M2PackRuntimeKeyProvider.h
Normal file
13
src/PackLib/M2PackRuntimeKeyProvider.h
Normal 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();
|
||||
@@ -1,7 +1,8 @@
|
||||
#include "PackManager.h"
|
||||
#include "EterLib/BufferPool.h"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include "EterBase/Debug.h"
|
||||
|
||||
CPackManager::CPackManager()
|
||||
@@ -22,6 +23,25 @@ CPackManager::~CPackManager()
|
||||
|
||||
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>();
|
||||
|
||||
if (!pack->Load(path))
|
||||
@@ -51,6 +71,11 @@ bool CPackManager::GetFileWithPool(std::string_view path, TPackFile& result, CBu
|
||||
|
||||
// First try to 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);
|
||||
if (it != m_entries.end()) {
|
||||
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
|
||||
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);
|
||||
if (it != m_entries.end()) {
|
||||
return true;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "EterBase/Singleton.h"
|
||||
#include "M2Pack.h"
|
||||
#include "Pack.h"
|
||||
|
||||
class CBufferPool;
|
||||
@@ -29,6 +30,7 @@ private:
|
||||
private:
|
||||
bool m_load_from_pack = true;
|
||||
TPackFileMap m_entries;
|
||||
TM2PackFileMap m_m2_entries;
|
||||
CBufferPool* m_pBufferPool;
|
||||
mutable std::mutex m_mutex; // Thread safety for parallel pack loading
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "EterBase/lzo.h"
|
||||
|
||||
#include "PackLib/PackManager.h"
|
||||
#include "PackLib/M2PackRuntimeKeyProvider.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
@@ -57,6 +58,33 @@ bool PackInitialize(const char * c_pszFolder)
|
||||
if (_access(c_pszFolder, 0) != 0)
|
||||
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 = {
|
||||
"patch1",
|
||||
"season3_eu",
|
||||
@@ -149,18 +177,16 @@ bool PackInitialize(const char * c_pszFolder)
|
||||
"uiloading",
|
||||
};
|
||||
|
||||
Tracef("PackInitialize: Loading root.pck\n");
|
||||
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;
|
||||
}
|
||||
|
||||
Tracef("PackInitialize: Loading %d pack files...", packFiles.size());
|
||||
for (const std::string& packFileName : packFiles) {
|
||||
Tracef("PackInitialize: Loading %s.pck\n", packFileName.c_str());
|
||||
CPackManager::instance().AddPack(std::format("{}/{}.pck", c_pszFolder, packFileName));
|
||||
AddPreferredPack(packFileName, false);
|
||||
}
|
||||
Tracef("PackInitialize: done. Time taken: %d ms\n", GetTickCount() - dwStartTime);
|
||||
return true;
|
||||
@@ -326,5 +352,7 @@ int Setup(LPSTR lpCmdLine)
|
||||
Callback.Function = nullptr;
|
||||
Callback.UserData = 0;
|
||||
GrannySetLogCallback(&Callback);
|
||||
|
||||
InitializeM2PackRuntimeKeyProvider(lpCmdLine);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user