Add PackManagerSim: full-manager pack verification tool

PackVerify only checks a single pack's own round-trip, which missed a
real bug: uiscript.eix entries were built without their required
"uiscript/" path prefix, so the standalone tool "round-tripped" fine
(source-relative paths matched its own wrongly-shifted reference) while
the actual game's CEterPackManager - which merges all packs into one
shared dict keyed by full path - failed to resolve any uiscript file.

PackManagerSim replicates the real client's PackInitialize(): it parses
the pack folder's Index manifest, registers every pack (~103) into one
real CEterPackManager exactly like the game does, and can verify every
entry of a given pack resolves through that full lookup path.
This commit is contained in:
Andreas Lierschaft
2026-08-18 00:24:41 +02:00
parent 08481e3906
commit a75dc404cd
+141
View File
@@ -0,0 +1,141 @@
// Reproduces the exact pack-registration flow the real client performs in
// PackInitialize() (UserInterface.cpp): parses the pack folder's "Index"
// manifest and registers every listed pack (plus root) into one real
// CEterPackManager, exactly like the running game does. This is the only
// reliable way to verify a pack, because single-pack tools (PackVerify) can't
// catch bugs in how a file's stored path lines up with what the manager's
// merged, folder-prefixed lookup actually queries for.
//
// Usage:
// PackManagerSim.exe <packFolder> <fileNameToLookup>
// Looks up one file through the full manager, e.g.:
// PackManagerSim.exe "Y:\Metin2Dev\Exec_Client\pack" "UIScript/PopupDialog.py"
//
// PackManagerSim.exe <packFolder> --verify-all <dbname>
// Registers all packs, then checks that every entry of <dbname> (read
// directly from its own .eix) resolves through the full manager, e.g.:
// PackManagerSim.exe "Y:\Metin2Dev\Exec_Client\pack" --verify-all uiscript
#include <windows.h>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include "../EterPack/EterPackManager.h"
#include "../EterBase/lzo.h"
int main(int argc, char** argv)
{
CLZO lzoInstance;
if (argc < 3)
{
fprintf(stderr, "Usage: %s <packFolder> <fileNameToLookup>\n", argv[0]);
fprintf(stderr, " or: %s <packFolder> --verify-all <dbname>\n", argv[0]);
return 1;
}
std::string packFolder = argv[1];
std::string lookupName = argv[2];
std::string verifyDbname = (argc >= 4) ? argv[3] : "";
std::string stFolder = packFolder + "/";
std::string indexPath = stFolder + "Index";
std::ifstream indexFile(indexPath);
if (!indexFile)
{
fprintf(stderr, "Cannot open Index file: %s\n", indexPath.c_str());
return 2;
}
std::vector<std::string> lines;
std::string line;
while (std::getline(indexFile, line))
{
while (!line.empty() && (line.back() == '\r' || line.back() == '\n'))
line.pop_back();
lines.push_back(line);
}
if (lines.empty() || (lines[0] != "FILE" && lines[0] != "PACK"))
{
fprintf(stderr, "Invalid Index syntax\n");
return 3;
}
CEterPackManager manager;
manager.SetSearchMode(true); // _DISTRIBUTE build: pack-first
int registered = 0, failed = 0;
for (size_t i = 1; i + 1 < lines.size(); i += 2)
{
const std::string& rstFolder = lines[i];
const std::string& rstName = lines[i + 1];
std::string packName = stFolder + rstName;
std::string texCacheName = packName + "_texcache";
if (manager.RegisterPack(packName.c_str(), rstFolder.c_str()))
++registered;
else
++failed;
manager.RegisterPack(texCacheName.c_str(), rstFolder.c_str());
}
manager.RegisterRootPack((stFolder + "root").c_str());
printf("Registered %d packs (%d failed to open) from Index\n", registered, failed);
if (lookupName == "--verify-all")
{
if (verifyDbname.empty())
{
fprintf(stderr, "--verify-all requires a <dbname> argument\n");
return 12;
}
std::string dbname = stFolder + verifyDbname;
CEterFileDict rawDict;
CEterPack rawPack;
if (!rawPack.Create(rawDict, dbname.c_str(), "", true))
{
fprintf(stderr, "Cannot open raw pack '%s'\n", dbname.c_str());
return 20;
}
std::vector<std::string> names;
rawPack.GetNames(&names);
int ok = 0, bad = 0;
for (auto& n : names)
{
CMappedFile mf;
LPCVOID d = NULL;
if (manager.Get(mf, n.c_str(), &d))
++ok;
else
{
++bad;
printf(" MISSING VIA MANAGER: %s\n", n.c_str());
}
}
printf("verify-all '%s': %d/%zu OK via manager, %d missing\n", verifyDbname.c_str(), ok, names.size(), bad);
return bad > 0 ? 11 : 0;
}
CMappedFile mappedFile;
LPCVOID data = NULL;
bool ok = manager.Get(mappedFile, lookupName.c_str(), &data);
printf("Get(\"%s\") => %s", lookupName.c_str(), ok ? "FOUND" : "NOT FOUND");
if (ok)
printf(" (%lu bytes)", mappedFile.Size());
printf("\n");
return ok ? 0 : 10;
}