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