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:
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user