Add standalone pack tooling (PackList/PackMake/PackVerify)
Automates the .eix/.epk repacking step that was previously done via the EterNexus.exe GUI, using the real CEterPack class so output is guaranteed read-compatible with the game client. PackList inspects an existing pack's entries, PackMake rebuilds a pack from a source folder, PackVerify byte-compares a pack's contents against source to confirm round-trip correctness.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
// Read-only pack inspector: opens an existing .eix/.epk pair and prints every
|
||||
// entry's stored filename, so we can learn the exact relative-path naming
|
||||
// convention a given pack uses before writing anything back.
|
||||
//
|
||||
// Usage: PackList.exe <pathToPackFolder> <dbname>
|
||||
// e.g. PackList.exe "Y:\Metin2Dev\Exec_Client\pack" root
|
||||
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../EterPack/EterPack.h"
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <packFolder> <dbname>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* pathName = argv[1];
|
||||
const char* dbname = argv[2];
|
||||
|
||||
if (!SetCurrentDirectoryA(pathName))
|
||||
{
|
||||
fprintf(stderr, "FAILED to chdir to '%s'\n", pathName);
|
||||
return 4;
|
||||
}
|
||||
|
||||
CEterFileDict dict;
|
||||
CEterPack pack;
|
||||
|
||||
if (!pack.Create(dict, dbname, "", true))
|
||||
{
|
||||
fprintf(stderr, "FAILED to open pack '%s' in '%s'\n", dbname, pathName);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::vector<std::string> names;
|
||||
if (!pack.GetNames(&names))
|
||||
{
|
||||
fprintf(stderr, "FAILED GetNames\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
printf("Entries: %zu\n", names.size());
|
||||
for (auto& n : names)
|
||||
printf("%s\n", n.c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Write-mode pack builder: recursively packs every file under a source folder
|
||||
// into <dbname>.eix/<dbname>.epk in an output folder, using the same CEterPack
|
||||
// class the game client itself uses to read packs (so the result is guaranteed
|
||||
// compatible). Always rebuilds the pack from scratch (deletes any existing
|
||||
// <dbname>.eix/.epk in the output folder first) so removed source files don't
|
||||
// linger as stale entries.
|
||||
//
|
||||
// Usage: PackMake.exe <sourceFolder> <outputPackFolder> <dbname>
|
||||
// e.g. PackMake.exe "Y:\Metin2Dev\Exec_Client\Eternexus\root" "Y:\scratch\pack" root
|
||||
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../EterPack/EterPack.h"
|
||||
#include "../EterBase/lzo.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
// CLZO is a CSingleton<T> that registers itself via ctor side effect (no
|
||||
// lazy init) - EncryptIndexFile() calls CLZO::Instance() internally, so an
|
||||
// instance must exist before that or Instance() dereferences a null pointer.
|
||||
CLZO lzoInstance;
|
||||
|
||||
if (argc < 4)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <sourceFolder> <outputPackFolder> <dbname>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string sourceFolder = argv[1];
|
||||
const std::string outputFolder = argv[2];
|
||||
const char* dbname = argv[3];
|
||||
|
||||
if (!fs::is_directory(sourceFolder))
|
||||
{
|
||||
fprintf(stderr, "Source folder does not exist: %s\n", sourceFolder.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!fs::is_directory(outputFolder))
|
||||
{
|
||||
fprintf(stderr, "Output pack folder does not exist: %s\n", outputFolder.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (!SetCurrentDirectoryA(outputFolder.c_str()))
|
||||
{
|
||||
fprintf(stderr, "FAILED to chdir to '%s'\n", outputFolder.c_str());
|
||||
return 4;
|
||||
}
|
||||
|
||||
std::string eixPath = std::string(dbname) + ".eix";
|
||||
std::string epkPath = std::string(dbname) + ".epk";
|
||||
|
||||
if (fs::exists(eixPath) || fs::exists(epkPath))
|
||||
{
|
||||
printf("Removing existing %s / %s in output folder for a clean rebuild\n", eixPath.c_str(), epkPath.c_str());
|
||||
fs::remove(eixPath);
|
||||
fs::remove(epkPath);
|
||||
}
|
||||
|
||||
CEterFileDict dict;
|
||||
CEterPack pack;
|
||||
|
||||
if (!pack.Create(dict, dbname, "", /*bReadOnly=*/false))
|
||||
{
|
||||
fprintf(stderr, "FAILED to create pack '%s' in '%s'\n", dbname, outputFolder.c_str());
|
||||
return 3;
|
||||
}
|
||||
|
||||
long fileCount = 0;
|
||||
long failCount = 0;
|
||||
|
||||
for (auto it = fs::recursive_directory_iterator(sourceFolder); it != fs::recursive_directory_iterator(); ++it)
|
||||
{
|
||||
if (!it->is_regular_file())
|
||||
continue;
|
||||
|
||||
std::string relPath = fs::relative(it->path(), sourceFolder).generic_string();
|
||||
std::string srcPath = it->path().string();
|
||||
|
||||
if (!pack.Put(relPath.c_str(), srcPath.c_str(), COMPRESSED_TYPE_NONE, ""))
|
||||
{
|
||||
fprintf(stderr, "FAILED to put '%s' (source: %s)\n", relPath.c_str(), srcPath.c_str());
|
||||
++failCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
++fileCount;
|
||||
}
|
||||
|
||||
if (!pack.EncryptIndexFile())
|
||||
{
|
||||
fprintf(stderr, "FAILED to encrypt index for pack '%s'\n", dbname);
|
||||
return 5;
|
||||
}
|
||||
|
||||
printf("Packed %ld files (%ld failures) into %s / %s\n", fileCount, failCount, eixPath.c_str(), epkPath.c_str());
|
||||
|
||||
return failCount > 0 ? 6 : 0;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Round-trip verification tool: opens a pack read-only, reads every entry via
|
||||
// the same CEterPack::Get() path the game engine uses, and byte-compares it
|
||||
// against the corresponding file under a source folder.
|
||||
//
|
||||
// Usage: PackVerify.exe <packFolder> <dbname> <sourceFolder>
|
||||
// e.g. PackVerify.exe "Y:\scratch\pack" root "Y:\Metin2Dev\Exec_Client\Eternexus\root"
|
||||
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
#include "../EterPack/EterPack.h"
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <packFolder> <dbname> <sourceFolder>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* pathName = argv[1];
|
||||
const char* dbname = argv[2];
|
||||
std::string sourceFolder = argv[3];
|
||||
|
||||
if (!SetCurrentDirectoryA(pathName))
|
||||
{
|
||||
fprintf(stderr, "FAILED to chdir to '%s'\n", pathName);
|
||||
return 4;
|
||||
}
|
||||
|
||||
CEterFileDict dict;
|
||||
CEterPack pack;
|
||||
|
||||
if (!pack.Create(dict, dbname, "", true))
|
||||
{
|
||||
fprintf(stderr, "FAILED to open pack '%s' in '%s'\n", dbname, pathName);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::vector<std::string> names;
|
||||
if (!pack.GetNames(&names))
|
||||
{
|
||||
fprintf(stderr, "FAILED GetNames\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
long okCount = 0, mismatchCount = 0, missingSrcCount = 0, getFailCount = 0;
|
||||
|
||||
for (auto& name : names)
|
||||
{
|
||||
std::string srcPath = sourceFolder + "\\" + name;
|
||||
for (auto& c : srcPath) if (c == '/') c = '\\';
|
||||
|
||||
std::ifstream srcFile(srcPath, std::ios::binary | std::ios::ate);
|
||||
if (!srcFile)
|
||||
{
|
||||
fprintf(stderr, "MISSING SOURCE: %s (expected at %s)\n", name.c_str(), srcPath.c_str());
|
||||
++missingSrcCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::streamsize srcSize = srcFile.tellg();
|
||||
srcFile.seekg(0);
|
||||
std::vector<char> srcData(srcSize);
|
||||
srcFile.read(srcData.data(), srcSize);
|
||||
|
||||
CMappedFile mappedFile;
|
||||
LPCVOID packData = NULL;
|
||||
|
||||
if (!pack.Get(mappedFile, name.c_str(), &packData))
|
||||
{
|
||||
fprintf(stderr, "Get() FAILED: %s\n", name.c_str());
|
||||
++getFailCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD packSize = mappedFile.Size();
|
||||
|
||||
if ((std::streamsize)packSize != srcSize || memcmp(packData, srcData.data(), srcSize) != 0)
|
||||
{
|
||||
fprintf(stderr, "MISMATCH: %s (src %lld bytes, pack %lu bytes)\n", name.c_str(), (long long)srcSize, packSize);
|
||||
++mismatchCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
++okCount;
|
||||
}
|
||||
|
||||
printf("Verified %ld entries: %ld OK, %ld mismatched, %ld missing-source, %ld get-failed\n",
|
||||
(long)names.size(), okCount, mismatchCount, missingSrcCount, getFailCount);
|
||||
|
||||
return (mismatchCount > 0 || getFailCount > 0) ? 5 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user