layout: restructure into src/ tests/ android/ scripts/ tools/
- extension/src/{port,platform,codepage} -> src/; native_render -> src/host
(+ dxt, shaders/); libgr2 -> src/gr2; extension/third_party -> third_party
- extension/tests -> tests/port, libgr2/tests -> tests/gr2
- android-native -> android (build.sh, push-client.sh moved in)
- script -> scripts; tools/40250 -> tools/server; oracle -> tools/granny-oracle;
perf tools -> tools/perf
- all build trees under build/ (native, release, android, port-gate)
- xrender:: CMake aliases -> mt::; port-map ledger impl paths rewritten
No code changes. ctest 15/15, port_gate macos+android PASS, port_map check 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
70710477cf
commit
a46093104c
@@ -0,0 +1,186 @@
|
||||
// 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>
|
||||
//
|
||||
// Exit 77 (ctest SKIP) when the client is missing, unless MT_ASSETS_STRICT=1, which turns that into a failure.
|
||||
#include "EterPack/StdAfx.h"
|
||||
#include "EterPack/EterPackManager.h"
|
||||
#include "EterBase/CRC32.h"
|
||||
#include "EterBase/lzo.h"
|
||||
#include "../../src/platform/UserInterface/UserInterface.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_failures = 0;
|
||||
#define CHECK(cond) \
|
||||
do { \
|
||||
if (!(cond)) { \
|
||||
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
|
||||
++g_failures; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Index names are spelled as on the NTFS original ("Etc" for ETC.eix); find the on-disk spelling.
|
||||
static std::string on_disk_name(const std::string& dir, const std::string& name)
|
||||
{
|
||||
std::string found = name;
|
||||
if (DIR* d = opendir(dir.c_str()))
|
||||
{
|
||||
const std::string want = name + ".eix";
|
||||
while (dirent* e = readdir(d))
|
||||
if (strcasecmp(e->d_name, want.c_str()) == 0)
|
||||
found = std::string(e->d_name, want.size() - 4);
|
||||
closedir(d);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
static std::vector<std::string> read_lines(const std::string& path)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
std::string line;
|
||||
while (std::getline(in, line))
|
||||
{
|
||||
while (!line.empty() && (line.back() == '\r' || line.back() == ' ' || line.back() == '\t'))
|
||||
line.pop_back();
|
||||
lines.push_back(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const char* strict = std::getenv("MT_ASSETS_STRICT");
|
||||
const bool is_strict = strict && std::string(strict) == "1";
|
||||
const std::string client = argc > 1 ? argv[1] : "";
|
||||
if (client.empty() || chdir(client.c_str()) != 0 || access("pack/Index", 0) != 0)
|
||||
{
|
||||
std::fprintf(stderr, "port_eterpack_test: no 40250 Client/pack at '%s'\n", client.c_str());
|
||||
return is_strict ? 1 : 77;
|
||||
}
|
||||
|
||||
PackSingletons();
|
||||
// A 32-bit pack header must round-trip through liblzo's native-width output parameter.
|
||||
const char sample[] = "40250 pack LZO length on a 64-bit host";
|
||||
CLZObject compressed;
|
||||
compressed.BeginCompress(sample, sizeof(sample));
|
||||
CHECK(compressed.Compress());
|
||||
CLZObject restored;
|
||||
CHECK(restored.BeginDecompress(compressed.GetBuffer()));
|
||||
CHECK(restored.Decompress());
|
||||
CHECK(restored.GetSize() == sizeof(sample));
|
||||
CHECK(std::memcmp(restored.GetBuffer(), sample, sizeof(sample)) == 0);
|
||||
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");
|
||||
std::vector<std::string> registered;
|
||||
for (size_t i = 1; i + 1 < lines.size(); i += 2)
|
||||
for (const std::string& name : {lines[i + 1], lines[i + 1] + "_texcache"})
|
||||
{
|
||||
const std::string db = "pack/" + on_disk_name("pack", name);
|
||||
if (access((db + ".eix").c_str(), 0) == 0 && access((db + ".epk").c_str(), 0) == 0)
|
||||
registered.push_back(db);
|
||||
}
|
||||
|
||||
// 124 Index pairs; 119 registrations find a pack (some names are listed twice), 103 distinct packs, + root.
|
||||
CHECK(registered.size() == 119);
|
||||
CHECK(std::set<std::string>(registered.begin(), registered.end()).size() == 103);
|
||||
|
||||
// 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.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;
|
||||
for (const auto& kv : dict)
|
||||
{
|
||||
const std::string name = kv.second.pkInfo->filename;
|
||||
const std::string pack = kv.second.pkPack->GetDBName();
|
||||
CHECK(GetCRC32(name.c_str(), name.size()) == kv.first);
|
||||
auto it = first.find(name);
|
||||
if (it == first.end() || order[pack] < order[it->second])
|
||||
first[name] = pack;
|
||||
}
|
||||
CHECK(first.size() == 52609);
|
||||
|
||||
size_t overridden = 0, wrong_winner = 0;
|
||||
std::map<std::string, int> per_name;
|
||||
for (const auto& kv : dict)
|
||||
++per_name[kv.second.pkInfo->filename];
|
||||
for (const auto& [name, count] : per_name)
|
||||
{
|
||||
if (count < 2)
|
||||
continue;
|
||||
overridden += count - 1;
|
||||
CEterFileDict::Item* item = Access::item(mgr, GetCRC32(name.c_str(), name.size()), name.c_str());
|
||||
if (!item || first[name] != item->pkPack->GetDBName())
|
||||
{
|
||||
if (wrong_winner < 5)
|
||||
std::fprintf(stderr, "winner %s: %s, expected %s\n", name.c_str(),
|
||||
item ? item->pkPack->GetDBName() : "(none)", first[name].c_str());
|
||||
++wrong_winner;
|
||||
}
|
||||
}
|
||||
CHECK(overridden == 2282);
|
||||
CHECK(wrong_winner == 0);
|
||||
|
||||
// Every path loads through CEterPackManager::Get, except the 4 short-layout SECURITY files.
|
||||
const std::set<std::string> rejected_expected = {
|
||||
"d:/ymir work/npc/mineral2/wait.msa",
|
||||
"d:/ymir work/effect/background/metinstone_loop_redblack.mse",
|
||||
"d:/ymir work/monster2/spider_spawn/wait.msa",
|
||||
"d:/ymir work/monster2/spider_spawn/line1.mse",
|
||||
};
|
||||
std::set<std::string> rejected;
|
||||
unsigned long long bytes = 0;
|
||||
for (const auto& [name, pack] : first)
|
||||
{
|
||||
CMappedFile file;
|
||||
LPCVOID data = nullptr;
|
||||
if (mgr.Get(file, name.c_str(), &data))
|
||||
bytes += file.Size();
|
||||
else
|
||||
rejected.insert(name);
|
||||
}
|
||||
for (const std::string& name : rejected)
|
||||
if (!rejected_expected.count(name))
|
||||
std::fprintf(stderr, "unexpected load failure: %s\n", name.c_str());
|
||||
CHECK(rejected == rejected_expected);
|
||||
|
||||
std::printf("packs %zu, entries %zu, paths %zu, overrides %zu, loaded %zu (%llu bytes), rejected %zu\n",
|
||||
registered.size(), dict.size(), first.size(), overridden, first.size() - rejected.size(), bytes,
|
||||
rejected.size());
|
||||
if (g_failures)
|
||||
std::fprintf(stderr, "%d failure(s)\n", g_failures);
|
||||
return g_failures ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user