port 2D step 5b: PackInitialize + pack:// asset_io backend over the ported EterPackManager

Platform UserInterface PackInitialize/PackSingletons (40250 bootstrap, on-disk dbname
resolution), EterBase/FileLoader.cpp mirror, mtpack40250 backend behind asset_io's
pack:// scheme and a Metin2Pack node. port_eterpack_test now registers through
PackInitialize. Nothing reads pack:// at runtime yet; port-map stays TODO.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 01:31:17 +09:00
co-authored by Claude Opus 5
parent 1f1004da79
commit 5a26e93f07
13 changed files with 574 additions and 91 deletions
+1
View File
@@ -489,3 +489,4 @@
{"time":"2026-09-22T15:00:00Z","event":"port_map_rebaseline","scope":"all units","totals":{"logic":3343,"python":5175,"platform":2091,"all":10609,"done":0,"divergent":1},"previous":{"logic":3285,"python":4890,"platform":2229,"all":10404},"changes":["cpp_functions: strip comments, accept indented qualified definitions inside namespace blocks, long parameter/initializer lists (40-line window), all-caps qualified ctors, skip constructor initializer entries","EterBase split by unit: tea/lzo/cipher/Random/Stl/Timer/Poly are logic, CPostIt/CRC32/Debug/FileBase/FileDir/FileLoader/MappedFile/TempFile/Utils/error stay platform; 8 obsolete platform stub files removed","audit/slices regenerated (layer fields; EterLib/Dimm.h now platform)"],"reason":"PORT-PLAN 2A step 6; inventory cross-checked against the clang-derived platform skeleton: every stub is inventoried except 22 declarations 40250 never defines and namespace/typedef spelling variants","tests":["port_map.py check","script/port_gate.sh (macos/android/ios/windows PASS, linux BLOCKED)"]}
{"time":"2026-09-22T16:00:00Z","event":"pack_inventory","scope":"Client/pack (batch 2R)","tool":"tools/epk_scan/epk_scan.py","report":"audit/packs/2R-scan.json","totals":{"registered_packs":136,"entries":54891,"unique_paths":52609,"NONE":20727,"COMPRESS":29812,"SECURITY":4352,"PANAMA":0,"HYBRIDCRYPT":0,"decode_failures":0,"overrides":2282,"overrides_different":2194,"case_collisions":0},"findings":["first-registered pack wins (CEterFileDict multimap, VS2022 STL)","short-layout packer in metin2_patch_{sd,mineral,mundi}: 4 SECURITY files rejected by 40250 CRC check","no server-delivered pack keys needed"]}
{"time":"2026-09-23T10:00:00Z","event":"port_round","unit":"EterPack/*, EterBase/{tea,lzo,Timer,Stl,Random}.cpp + platform EterBase/{CRC32,FileBase,MappedFile,Debug,Utils}","ported":["EterPack/EterPack.cpp","EterPack/EterPackManager.cpp","EterPack/EterPackCursor.cpp","EterPack/EterPackPolicy_CSHybridCrypt.cpp","EterBase/tea.cpp","EterBase/lzo.cpp","EterBase/Timer.cpp","EterBase/Stl.cpp","EterBase/Random.cpp"],"status_note":"mirror copies + platform FileBase/MappedFile/CRC32/Debug/Utils; not runtime-reachable until the asset_io pack backend (PORT-PLAN §5 step 5), so port-map statuses stay TODO","divergent":[],"needs_live":[],"corrections":["2R registered_packs 136 was wrong: 124 Index pairs -> 119 RegisterPack successes, 103 distinct packs + root (PORT-PLAN, memory)"],"findings":["libc++ unordered_multimap also keeps first-registered-wins: 0 wrong winners over 2282 overrides","exactly the 4 short-layout SECURITY files fail CEterPackManager::Get","pack/Index spells Etc for ETC.eix: CEterPack::CreateIndexFile fopens directly, so the bootstrap must resolve the on-disk dbname"],"keys":"EterPack TEA keys generated at configure time into the build tree (EterPackKeys.generated.h), never committed","tests":["extension/tests/port_eterpack_test.cpp PASS (macOS, 52605 files / 2023459178 bytes)","script/port_gate.sh macos/android/ios/windows PASS"]}
{"time":"2026-09-23T12:00:00Z","event":"port_round","unit":"platform UserInterface PackInitialize + EterBase/FileLoader.cpp + pack:// asset_io backend (PORT-PLAN §5 step 5b)","ported":["EterBase/FileLoader.cpp"],"deleted":[],"divergent":[],"needs_live":[],"status_note":"PackInitialize (platform, on-disk dbname resolution, no LogBoxf) + mtpack40250 backend + Metin2Pack node; no runtime reader uses pack:// until the proto/GDScript switch, so port-map statuses stay TODO","tests":["extension/tests/port_eterpack_test.cpp via PackSingletons+PackInitialize(\"pack\"): 103 packs, oracle order, PASS"],"commit":"5b"}
+1
View File
@@ -188,6 +188,7 @@ add_library(mtgodot ${MT_LIB_KIND}
src/texture_util.cpp
src/net/m2_client.cpp
src/proto/proto_node.cpp
src/pack40250_node.cpp
)
target_compile_features(mtgodot PRIVATE cxx_std_20)
target_link_libraries(mtgodot PRIVATE godot::cpp xrender::libgr2 xrender::formats mtnet mtproto port_platform)
+32
View File
@@ -2,19 +2,51 @@
#include <godot_cpp/classes/file_access.hpp>
#include "platform/PackBackend.h"
#include <cstring>
#include <string>
#include <vector>
using namespace godot;
namespace mtgodot {
namespace {
bool pack_name(const String &path, std::string &name) {
if (!path.begins_with(PACK_SCHEME)) {
return false;
}
name = path.substr(String(PACK_SCHEME).length()).utf8().get_data();
return true;
}
} // namespace
PackedByteArray read_file(const String &path) {
if (path.is_empty()) {
return PackedByteArray();
}
std::string name;
if (pack_name(path, name)) {
std::vector<uint8_t> bytes;
PackedByteArray out;
if (mtpack40250::read(name, bytes)) {
out.resize((int64_t)bytes.size());
if (!bytes.empty()) {
std::memcpy(out.ptrw(), bytes.data(), bytes.size());
}
}
return out;
}
// FileAccess::get_file_as_bytes handles res:// / user:// / absolute OS paths.
return FileAccess::get_file_as_bytes(path);
}
bool file_exists(const String &path) {
std::string name;
if (pack_name(path, name)) {
return mtpack40250::exists(name);
}
return !path.is_empty() && FileAccess::file_exists(path);
}
+5
View File
@@ -7,6 +7,9 @@
// - user:// (extracted cache)
// - absolute OS paths (dev: AssetRoot points at mtgodot-poc/assets/)
// godot::FileAccess handles all three transparently.
// - pack://<40250 path> (e.g. pack://locale/en/item_proto, pack://d:/ymir work/pc/...): the 40250 packs,
// read through the ported CEterPackManager once Metin2Pack.initialize(<Client dir>) has run
// (platform/PackBackend.h). The path after pack:// is passed as UTF-8.
#pragma once
#include <godot_cpp/variant/packed_byte_array.hpp>
@@ -20,6 +23,8 @@
namespace mtgodot {
inline constexpr const char *PACK_SCHEME = "pack://";
// Whole-file read. Empty PackedByteArray on failure (path missing / unreadable).
godot::PackedByteArray read_file(const godot::String &path);
bool file_exists(const godot::String &path); // FileAccess::file_exists wrapper
+35
View File
@@ -0,0 +1,35 @@
#include "pack40250_node.h"
#include <godot_cpp/core/class_db.hpp>
#include "asset_io.h"
#include "platform/PackBackend.h"
using namespace godot;
namespace mtgodot {
bool Metin2Pack::initialize(const String &client_dir) {
return mtpack40250::initialize(client_dir.utf8().get_data());
}
bool Metin2Pack::is_ready() {
return mtpack40250::ready();
}
bool Metin2Pack::exists(const String &name) {
return file_exists(String(PACK_SCHEME) + name);
}
PackedByteArray Metin2Pack::get_bytes(const String &name) {
return read_file(String(PACK_SCHEME) + name);
}
void Metin2Pack::_bind_methods() {
ClassDB::bind_static_method("Metin2Pack", D_METHOD("initialize", "client_dir"), &Metin2Pack::initialize);
ClassDB::bind_static_method("Metin2Pack", D_METHOD("is_ready"), &Metin2Pack::is_ready);
ClassDB::bind_static_method("Metin2Pack", D_METHOD("exists", "name"), &Metin2Pack::exists);
ClassDB::bind_static_method("Metin2Pack", D_METHOD("get_bytes", "name"), &Metin2Pack::get_bytes);
}
} // namespace mtgodot
+28
View File
@@ -0,0 +1,28 @@
#pragma once
// Metin2Pack — GDScript access to the 40250 packs (platform/PackBackend.h).
//
// Metin2Pack.initialize(AssetRoot.client_path()) # registers <Client>/pack/Index once
// var b := Metin2Pack.get_bytes("locale/en/item_proto")
//
// Every native reader that takes a path accepts "pack://<40250 path>" through asset_io.
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/variant/packed_byte_array.hpp>
#include <godot_cpp/variant/string.hpp>
namespace mtgodot {
class Metin2Pack : public godot::Object {
GDCLASS(Metin2Pack, godot::Object)
public:
static bool initialize(const godot::String &client_dir);
static bool is_ready();
static bool exists(const godot::String &name);
static godot::PackedByteArray get_bytes(const godot::String &name);
protected:
static void _bind_methods();
};
} // namespace mtgodot
+229 -74
View File
@@ -1,135 +1,290 @@
// Platform skeleton for EterBase/FileLoader.h (40250 EterBase/FileLoader.cpp), generated by platform_stub.py.
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
// Platform implementation of EterBase/FileLoader.h: the 40250 EterBase/FileLoader.cpp, which is portable C/stdio.
// Only change: the CP949 lead-byte test casts to signed char (char is unsigned on Android/Linux arm64).
#include "EterBase/StdAfx.h"
#include "EterBase/FileLoader.h"
#include "../PlatformStub.h"
#include <assert.h>
CMemoryTextFileLoader::CMemoryTextFileLoader()
{
MT_PLATFORM_STUB();
}
CMemoryTextFileLoader::~CMemoryTextFileLoader()
{
MT_PLATFORM_STUB();
}
auto CMemoryTextFileLoader::Bind(int, const void *) -> void
bool CMemoryTextFileLoader::SplitLineByTab(DWORD dwLine, CTokenVector* pstTokenVector)
{
MT_PLATFORM_STUB();
pstTokenVector->reserve(10);
pstTokenVector->clear();
const std::string & c_rstLine = GetLineString(dwLine);
const int c_iLineLength = c_rstLine.length();
if (0 == c_iLineLength)
return false;
int basePos = 0;
do
{
int beginPos = c_rstLine.find_first_of("\t", basePos);
pstTokenVector->push_back(c_rstLine.substr(basePos, beginPos-basePos));
basePos = beginPos+1;
} while (basePos < c_iLineLength && basePos > 0);
return true;
}
auto CMemoryTextFileLoader::GetLineCount() -> DWORD
int CMemoryTextFileLoader::SplitLine2(DWORD dwLine, CTokenVector* pstTokenVector, const char * c_szDelimeter)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<DWORD>();
pstTokenVector->reserve(10);
pstTokenVector->clear();
std::string stToken;
const std::string & c_rstLine = GetLineString(dwLine);
DWORD basePos = 0;
do
{
int beginPos = c_rstLine.find_first_not_of(c_szDelimeter, basePos);
if (beginPos < 0)
return -1;
int endPos;
if (c_rstLine[beginPos] == '"')
{
++beginPos;
endPos = c_rstLine.find_first_of("\"", beginPos);
if (endPos < 0)
return -2;
basePos = endPos + 1;
}
else
{
endPos = c_rstLine.find_first_of(c_szDelimeter, beginPos);
basePos = endPos;
}
pstTokenVector->push_back(c_rstLine.substr(beginPos, endPos - beginPos));
// 추가 코드. 맨뒤에 탭이 있는 경우를 체크한다. - [levites]
if (int(c_rstLine.find_first_not_of(c_szDelimeter, basePos)) < 0)
break;
} while (basePos < c_rstLine.length());
return 0;
}
auto CMemoryTextFileLoader::CheckLineIndex(DWORD) -> bool
bool CMemoryTextFileLoader::SplitLine(DWORD dwLine, CTokenVector* pstTokenVector, const char * c_szDelimeter)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
pstTokenVector->reserve(10);
pstTokenVector->clear();
std::string stToken;
const std::string & c_rstLine = GetLineString(dwLine);
DWORD basePos = 0;
do
{
int beginPos = c_rstLine.find_first_not_of(c_szDelimeter, basePos);
if (beginPos < 0)
return false;
int endPos;
if (c_rstLine[beginPos] == '"')
{
++beginPos;
endPos = c_rstLine.find_first_of("\"", beginPos);
if (endPos < 0)
return false;
basePos = endPos + 1;
}
else
{
endPos = c_rstLine.find_first_of(c_szDelimeter, beginPos);
basePos = endPos;
}
pstTokenVector->push_back(c_rstLine.substr(beginPos, endPos - beginPos));
// 추가 코드. 맨뒤에 탭이 있는 경우를 체크한다. - [levites]
if (int(c_rstLine.find_first_not_of(c_szDelimeter, basePos)) < 0)
break;
} while (basePos < c_rstLine.length());
return true;
}
auto CMemoryTextFileLoader::SplitLine(DWORD, CTokenVector *, const char *) -> bool
DWORD CMemoryTextFileLoader::GetLineCount()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return m_stLineVector.size();
}
auto CMemoryTextFileLoader::SplitLine2(DWORD, CTokenVector *, const char *) -> int
bool CMemoryTextFileLoader::CheckLineIndex(DWORD dwLine)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
if (dwLine >= m_stLineVector.size())
return false;
return true;
}
auto CMemoryTextFileLoader::SplitLineByTab(DWORD, CTokenVector *) -> bool
const std::string & CMemoryTextFileLoader::GetLineString(DWORD dwLine)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
assert(CheckLineIndex(dwLine));
return m_stLineVector[dwLine];
}
auto CMemoryTextFileLoader::GetLineString(DWORD) -> const std::string &
void CMemoryTextFileLoader::Bind(int bufSize, const void* c_pvBuf)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<const std::string &>();
m_stLineVector.reserve(128);
m_stLineVector.clear();
const char * c_pcBuf = (const char *)c_pvBuf;
std::string stLine;
int pos = 0;
while (pos < bufSize)
{
const char c = c_pcBuf[pos++];
if ('\n' == c || '\r' == c)
{
if (pos < bufSize)
if ('\n' == c_pcBuf[pos] || '\r' == c_pcBuf[pos])
++pos;
m_stLineVector.push_back(stLine);
stLine = "";
}
else if (static_cast<signed char>(c) < 0) // PORT: Win32 char is signed
{
stLine.append(c_pcBuf + (pos-1), 2);
++pos;
}
else
{
stLine += c;
}
}
m_stLineVector.push_back(stLine);
}
CMemoryFileLoader::CMemoryFileLoader(int, const void *)
//////////////////////////////////////////////////////////////////////////////////////////////////
int CMemoryFileLoader::GetSize()
{
MT_PLATFORM_STUB();
return m_size;
}
int CMemoryFileLoader::GetPosition()
{
return m_pos;
}
bool CMemoryFileLoader::IsReadableSize(int size)
{
if (m_pos + size > m_size)
return false;
return true;
}
bool CMemoryFileLoader::Read(int size, void* pvDst)
{
if (!IsReadableSize(size))
return false;
memcpy(pvDst, GetCurrentPositionPointer(), size);
m_pos += size;
return true;
}
const char* CMemoryFileLoader::GetCurrentPositionPointer()
{
assert(m_pcBase != NULL);
return (m_pcBase + m_pos);
}
CMemoryFileLoader::CMemoryFileLoader(int size, const void* c_pvMemoryFile)
{
assert(c_pvMemoryFile != NULL);
m_pos = 0;
m_size = size;
m_pcBase = (const char *) c_pvMemoryFile;
}
CMemoryFileLoader::~CMemoryFileLoader()
{
MT_PLATFORM_STUB();
}
auto CMemoryFileLoader::Read(int, void *) -> bool
//////////////////////////////////////////////////////////////////////////////////////////////////
int CDiskFileLoader::GetSize()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
return m_size;
}
auto CMemoryFileLoader::GetPosition() -> int
bool CDiskFileLoader::Read(int size, void* pvDst)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
assert(m_fp != NULL);
int ret = fread(pvDst, size, 1, m_fp);
if (ret <= 0)
return false;
return true;
}
auto CMemoryFileLoader::GetSize() -> int
bool CDiskFileLoader::Open(const char* c_szFileName)
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
Close();
if (!c_szFileName[0])
return false;
m_fp = fopen(c_szFileName, "rb");
if (!m_fp)
return false;
fseek(m_fp, 0, SEEK_END);
m_size = ftell(m_fp);
fseek(m_fp, 0, SEEK_SET);
return true;
}
auto CMemoryFileLoader::IsReadableSize(int) -> bool
void CDiskFileLoader::Close()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
if (m_fp)
fclose(m_fp);
Initialize();
}
auto CMemoryFileLoader::GetCurrentPositionPointer() -> const char *
void CDiskFileLoader::Initialize()
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<const char *>();
m_fp = NULL;
m_size = 0;
}
CDiskFileLoader::CDiskFileLoader()
{
MT_PLATFORM_STUB();
Initialize();
}
CDiskFileLoader::~CDiskFileLoader()
{
MT_PLATFORM_STUB();
}
auto CDiskFileLoader::Close() -> void
{
MT_PLATFORM_STUB();
}
auto CDiskFileLoader::Open(const char *) -> bool
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
}
auto CDiskFileLoader::Read(int, void *) -> bool
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<bool>();
}
auto CDiskFileLoader::GetSize() -> int
{
MT_PLATFORM_STUB();
return mt_platform_stub_return<int>();
}
auto CDiskFileLoader::Initialize() -> void
{
MT_PLATFORM_STUB();
Close();
}
+60
View File
@@ -0,0 +1,60 @@
#include "EterBase/StdAfx.h"
#include "EterBase/MappedFile.h"
#include "EterPack/EterPackManager.h"
#include "UserInterface/UserInterface.h"
#include "PackBackend.h"
#include <mutex>
#if defined(_WIN32)
#include <io.h> // _access; elsewhere the <windows.h> shim declares it
#endif
namespace mtpack40250 {
namespace {
std::mutex g_init_mutex;
bool g_initialized = false;
bool g_ready = false;
}
bool initialize(const std::string& client_dir)
{
std::lock_guard<std::mutex> lock(g_init_mutex);
if (g_initialized)
return g_ready;
const std::string folder = client_dir + "/pack";
if (_access((folder + "/Index").c_str(), 0) != 0)
return false; // nothing registered; a later call with the right directory may still succeed
g_initialized = true;
PackSingletons();
g_ready = PackInitialize(folder.c_str());
return g_ready;
}
bool ready()
{
std::lock_guard<std::mutex> lock(g_init_mutex);
return g_ready;
}
bool read(const std::string& name, std::vector<uint8_t>& out)
{
out.clear();
if (!ready())
return false;
CMappedFile file;
LPCVOID data = nullptr;
if (!CEterPackManager::Instance().Get(file, name.c_str(), &data))
return false;
const BYTE* bytes = static_cast<const BYTE*>(data);
out.assign(bytes, bytes + file.Size());
return true;
}
bool exists(const std::string& name)
{
return ready() && CEterPackManager::Instance().isExist(name.c_str());
}
} // namespace mtpack40250
+23
View File
@@ -0,0 +1,23 @@
#pragma once
// PackBackend — the 40250 pack file system for the extension (docs/PORT-PLAN.md 2R item 6). Registers <client>/pack
// through the platform PackInitialize and reads with the ported CEterPackManager (pack first, then the loose file).
// Names are 40250 paths ("d:/ymir work/pc/warrior/...", "locale/en/item_proto"), normalized by ConvertFileName.
// Standard types only, so Godot-side code (asset_io) includes it without the Win32 shims.
#include <cstdint>
#include <string>
#include <vector>
namespace mtpack40250 {
// Registers <client_dir>/pack/Index once per process; false when <client_dir>/pack/Index is missing.
// Later calls return whether the first one succeeded.
bool initialize(const std::string& client_dir);
bool ready();
// CEterPackManager::Get into `out`; false when the name is in no pack, is rejected (CRC) and has no loose file.
bool read(const std::string& name, std::vector<uint8_t>& out);
// CEterPackManager::isExist.
bool exists(const std::string& name);
} // namespace mtpack40250
@@ -0,0 +1,132 @@
// Platform implementation of the pack bootstrap from 40250 UserInterface/UserInterface.cpp. PackInitialize is the 40250
// function with three platform differences:
// - pack names in Index are matched to the on-disk .eix/.epk case-insensitively ("Etc" is ETC.eix on NTFS, and
// CEterPack::CreateIndexFile fopens the name as given), so the registered dbname carries the on-disk spelling;
// - no LogBoxf (a Win32 message box), only the TraceError next to it;
// - CTextFileLoader::SetCacheMode and CSoundData::SetPackMode are not called: EterLib/TextFileLoader and MilesLib
// are not ported yet, and neither affects which files the pack manager registers or returns.
// Built as in the Distribute configuration (_DISTRIBUTE): pack first.
#include "EterBase/StdAfx.h"
#include "EterBase/FileLoader.h"
#include "EterBase/MappedFile.h"
#include "EterBase/Debug.h"
#include "EterBase/lzo.h"
#include "EterPack/EterPackManager.h"
#include "UserInterface.h"
#include <map>
#include <string>
#if defined(_WIN32)
#include <io.h>
#else
#include <dirent.h>
#endif
namespace
{
// Lowercased file name -> on-disk spelling, for the files directly in `folder`.
std::map<std::string, std::string> list_folder(const std::string& folder)
{
std::map<std::string, std::string> names;
auto add = [&](const std::string& name) {
std::string lower = name;
for (char& c : lower)
c = (char) tolower((unsigned char) c);
names.emplace(lower, name);
};
#if defined(_WIN32)
_finddata_t fd;
intptr_t h = _findfirst((folder + "*").c_str(), &fd);
if (h != -1)
{
do
add(fd.name);
while (_findnext(h, &fd) == 0);
_findclose(h);
}
#else
if (DIR* d = opendir(folder.c_str()))
{
while (dirent* e = readdir(d))
add(e->d_name);
closedir(d);
}
#endif
return names;
}
// <folder><name> with `name` in the spelling of the on-disk <name>.eix, or unchanged when there is none.
std::string on_disk(const std::map<std::string, std::string>& names, const std::string& folder, const std::string& name)
{
std::string lower = name + ".eix";
for (char& c : lower)
c = (char) tolower((unsigned char) c);
auto it = names.find(lower);
if (it == names.end())
return folder + name;
return folder + it->second.substr(0, it->second.size() - 4);
}
}
void PackSingletons()
{
static CLZO lzo;
static CEterPackManager EterPackManager;
}
// 40250: PackInitialize
bool PackInitialize(const char * c_pszFolder)
{
if (_access(c_pszFolder, 0) != 0)
return true;
std::string stFolder(c_pszFolder);
stFolder += "/";
std::string stFileName(stFolder);
stFileName += "Index";
CMappedFile file;
LPCVOID pvData;
if (!file.Create(stFileName.c_str(), &pvData, 0, 0))
{
TraceError("FATAL ERROR! File not exist: %s", stFileName.c_str());
return true;
}
CMemoryTextFileLoader TextLoader;
TextLoader.Bind(file.Size(), pvData);
bool bPackFirst = TRUE;
const std::string& strPackType = TextLoader.GetLineString(0);
if (strPackType.compare("FILE") && strPackType.compare("PACK"))
{
TraceError("Pack/Index has invalid syntax. First line must be 'PACK' or 'FILE'");
return false;
}
CEterPackManager::Instance().SetCacheMode();
CEterPackManager::Instance().SetSearchMode(bPackFirst);
const std::map<std::string, std::string> names = list_folder(stFolder);
std::string strPackName, strTexCachePackName;
for (DWORD i = 1; i < TextLoader.GetLineCount() - 1; i += 2)
{
const std::string & c_rstFolder = TextLoader.GetLineString(i);
const std::string & c_rstName = TextLoader.GetLineString(i + 1);
strPackName = on_disk(names, stFolder, c_rstName);
strTexCachePackName = on_disk(names, stFolder, c_rstName + "_texcache");
CEterPackManager::Instance().RegisterPack(strPackName.c_str(), c_rstFolder.c_str());
CEterPackManager::Instance().RegisterPack(strTexCachePackName.c_str(), c_rstFolder.c_str());
}
CEterPackManager::Instance().RegisterRootPack(on_disk(names, stFolder, "root").c_str());
return true;
}
@@ -0,0 +1,11 @@
#pragma once
// Platform-layer pieces of 40250 UserInterface/UserInterface.cpp (the WinMain unit). 40250 declares these nowhere:
// Main() in the same file calls them. The extension's pack bootstrap calls them instead.
// 40250: PackInitialize. Registers <folder>/Index the way the 40250 client does at startup. The CLZO and
// CEterPackManager singletons it needs must already exist (see PackSingletons).
bool PackInitialize(const char * c_pszFolder);
// The two function-static singletons 40250 Main() creates before PackInitialize("pack")
// (`static CLZO lzo; static CEterPackManager EterPackManager;`), created on first use and alive until exit.
void PackSingletons();
+2
View File
@@ -10,6 +10,7 @@
#include "metin2_world.h"
#include "m2_material.h"
#include "net/m2_client.h"
#include "pack40250_node.h"
#include "proto/proto_node.h"
#include "static_object.h"
#include "terrain_splat.h"
@@ -43,6 +44,7 @@ void initialize_mtgodot_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(mtgodot::Metin2World);
GDREGISTER_CLASS(mtgodot::M2Client);
GDREGISTER_CLASS(mtgodot::Metin2Proto);
GDREGISTER_CLASS(mtgodot::Metin2Pack);
}
void uninitialize_mtgodot_module(ModuleInitializationLevel p_level) {
+15 -17
View File
@@ -1,7 +1,7 @@
// port/EterPack against the real 40250 Client/pack (batch 2D). Registers every pack in pack/Index order the way
// UserInterface.cpp PackInitialize does, then checks the numbers batch 2R measured with tools/epk_scan
// (audit/packs/2R-scan.json): 103 packs + root / 54891 entries / 52609 paths, first-registered pack wins, and exactly
// the 4 short-layout SECURITY files 40250 rejects fail to load.
// port/EterPack against the real 40250 Client/pack (batch 2D). Registers the packs through the platform
// PackInitialize("pack") (40250 UserInterface.cpp), after the singletons Main() creates, then checks the numbers
// batch 2R measured with tools/epk_scan (audit/packs/2R-scan.json): 103 packs + root / 54891 entries / 52609 paths,
// first-registered pack wins, and exactly the 4 short-layout SECURITY files 40250 rejects fail to load.
//
// port_eterpack_test <40250 Client dir>
//
@@ -9,6 +9,7 @@
#include "EterPack/StdAfx.h"
#include "EterPack/EterPackManager.h"
#include "EterBase/CRC32.h"
#include "../src/platform/UserInterface/UserInterface.h"
#include <cstdio>
#include <cstdlib>
@@ -71,27 +72,22 @@ int main(int argc, char** argv)
return is_strict ? 1 : 77;
}
// PackInitialize("pack"): line 0 is PACK, then (folder, name) pairs; every name also gets a _texcache pack.
PackSingletons();
CHECK(PackInitialize("pack"));
CEterPackManager& mgr = CEterPackManager::Instance();
// Expected registration order, from pack/Index independently of PackInitialize: line 0 is PACK, then
// (folder, name) pairs; every name also gets a _texcache pack; a pack is registered where it exists on disk.
const std::vector<std::string> lines = read_lines("pack/Index");
CHECK(!lines.empty() && lines[0] == "PACK");
CEterPackManager manager; // CSingleton: the instance registers itself
CEterPackManager& mgr = CEterPackManager::Instance();
mgr.SetCacheMode();
mgr.SetSearchMode(true);
std::vector<std::string> registered;
for (size_t i = 1; i + 1 < lines.size(); i += 2)
{
const std::string& folder = lines[i];
for (const std::string& name : {lines[i + 1], lines[i + 1] + "_texcache"})
{
const std::string db = "pack/" + on_disk_name("pack", name);
if (mgr.RegisterPack(db.c_str(), folder.c_str()))
if (access((db + ".eix").c_str(), 0) == 0 && access((db + ".epk").c_str(), 0) == 0)
registered.push_back(db);
}
}
mgr.RegisterRootPack("pack/root");
// 124 Index pairs; 119 registrations find a pack (some names are listed twice), 103 distinct packs, + root.
CHECK(registered.size() == 119);
@@ -100,15 +96,17 @@ int main(int argc, char** argv)
// The whole dict (one entry per pack index record, overrides included).
struct Access : CEterPackManager
{
static size_t packs(CEterPackManager& m) { return static_cast<Access&>(m).m_PackMap.size(); }
static const CEterFileDict::TDict& dict(CEterPackManager& m) { return static_cast<Access&>(m).m_FileDict.GetDict(); }
static CEterFileDict::Item* item(CEterPackManager& m, DWORD h, const char* n) { return static_cast<Access&>(m).m_FileDict.GetItem(h, n); }
};
CHECK(Access::packs(mgr) == 103);
const CEterFileDict::TDict& dict = Access::dict(mgr);
CHECK(dict.size() == 54891);
std::map<std::string, int> order;
for (size_t i = 0; i < registered.size(); ++i)
order[registered[i]] = static_cast<int>(i);
order.emplace(registered[i], static_cast<int>(i)); // first registration
// First registration per path, and CRC32 of the name equal to the stored filename_crc.
std::map<std::string, std::string> first;