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
+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();