proto.cpp reads the 40250 MIPX/MMPT tables with the ported CLZO/TEA (156-byte TItemTable, 255-byte SMobTable), replacing the m2dev XChaCha20 236/335 reader. Names follow CItemData::GetName / GetMonsterName (szLocaleName, cp1252). GDScript callers load AssetRoot.locale_file() = pack://locale/en/<name>, which makes the EterPack chain runtime-reachable; port-map entries for the functions a coverage run hit move to PORTED/ADAPTED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
5.4 KiB
C++
146 lines
5.4 KiB
C++
// item_proto / mob_proto against the real 40250 client: registers <Client>/pack like the client does, reads
|
|
// "locale/en/item_proto" and "locale/en/mob_proto" through CEterPackManager::Get (the path
|
|
// CPythonApplication::Create hands LoadItemTable/LoadNonPlayerData), and parses them in the 40250 format.
|
|
//
|
|
// proto_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 "../src/proto/proto.h"
|
|
#include "../src/platform/PackBackend.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <string>
|
|
|
|
using namespace mtproto;
|
|
|
|
static int g_fail = 0;
|
|
#define CHECK(c, msg) \
|
|
do { \
|
|
if (!(c)) { \
|
|
std::fprintf(stderr, "FAIL: %s\n", msg); \
|
|
++g_fail; \
|
|
} \
|
|
} while (0)
|
|
|
|
static bool load(const char *name, const std::array<uint32_t, 4> &key, Proto &p) {
|
|
std::vector<uint8_t> bytes;
|
|
if (!mtpack40250::read(name, bytes)) {
|
|
std::fprintf(stderr, "FAIL: %s not in the packs\n", name);
|
|
++g_fail;
|
|
return false;
|
|
}
|
|
std::string err;
|
|
const bool ok = load_proto_bytes(bytes, key, p, &err);
|
|
CHECK(ok, (std::string("load ") + name + ": " + err).c_str());
|
|
return ok;
|
|
}
|
|
|
|
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() || !mtpack40250::initialize(client)) {
|
|
std::printf("proto_test: no 40250 Client/pack at '%s'\n", client.c_str());
|
|
return is_strict ? 1 : 77;
|
|
}
|
|
|
|
// CEterPackManager::isExist, what AssetRoot.locale_file() asks before loading.
|
|
CHECK(mtpack40250::exists("locale/en/item_proto"), "isExist locale/en/item_proto");
|
|
CHECK(mtpack40250::exists("LOCALE\\EN\\MOB_PROTO"), "isExist normalizes case and separators");
|
|
CHECK(!mtpack40250::exists("locale/en/no_such_proto"), "isExist rejects a missing name");
|
|
|
|
// --- item_proto ---
|
|
{
|
|
Proto p;
|
|
if (load("locale/en/item_proto", ITEM_PROTO_KEY, p)) {
|
|
CHECK(p.fourcc == 0x5850494Du, "item fourcc MIPX");
|
|
CHECK(p.version == 1, "item version 1");
|
|
CHECK(p.stride == 156, "item stride 156 (== sizeof TItemTable)");
|
|
CHECK(p.elements > 100, "item elements > 100");
|
|
CHECK(p.blob.size() == static_cast<size_t>(p.stride) * p.elements, "item blob size");
|
|
|
|
uint32_t prev = 0;
|
|
int named = 0, mono = 1;
|
|
uint8_t max_spec = 0;
|
|
for (uint32_t i = 0; i < p.elements; ++i) {
|
|
ItemRecord it = parse_item(p.record(i), p.stride);
|
|
if (it.vnum && it.vnum <= prev) {
|
|
mono = 0;
|
|
}
|
|
prev = it.vnum ? it.vnum : prev;
|
|
if (!it.name.empty()) {
|
|
++named;
|
|
}
|
|
if (it.specular > max_spec) {
|
|
max_spec = it.specular;
|
|
}
|
|
}
|
|
CHECK(mono, "item vnums increasing");
|
|
CHECK(named > p.elements * 0.8, "item: >80% have a name");
|
|
std::printf("item_proto: %u items, stride %u, %d named, max bSpecular=%u\n",
|
|
p.elements, p.stride, named, max_spec);
|
|
|
|
// alValues[3] = body-armor shape index; "Monk Plate Armour" 11200..11209 share shape 3 and
|
|
// bSpecular ramps with the refine level.
|
|
bool found_armor = false;
|
|
for (uint32_t i = 0; i < p.elements; ++i) {
|
|
ItemRecord it = parse_item(p.record(i), p.stride);
|
|
if (it.vnum == 11209) {
|
|
found_armor = true;
|
|
std::printf(" 11209 '%s' values=[%d,%d,%d,%d,%d,%d] spec=%u\n", it.locale_name.c_str(),
|
|
it.values[0], it.values[1], it.values[2], it.values[3], it.values[4],
|
|
it.values[5], it.specular);
|
|
CHECK(it.values[3] == 3, "item 11209: values[3] == shape 3");
|
|
CHECK(it.specular == 100, "item 11209 (+9): bSpecular == 100");
|
|
}
|
|
if (it.vnum == 11200) {
|
|
CHECK(it.values[3] == 3 && it.specular == 0, "item 11200 (+0): shape 3, specular 0");
|
|
}
|
|
}
|
|
CHECK(found_armor, "item_proto contains vnum 11209");
|
|
// CItemData::GetName() is szLocaleName.
|
|
ItemRecord yang = parse_item(p.record(0), p.stride);
|
|
std::printf(" [0] vnum=%u locale_name='%s'\n", yang.vnum, yang.locale_name.c_str());
|
|
CHECK(yang.vnum == 1 && yang.locale_name == "Yang", "item[0] = vnum 1 'Yang'");
|
|
}
|
|
}
|
|
|
|
// --- mob_proto ---
|
|
{
|
|
Proto p;
|
|
if (load("locale/en/mob_proto", MOB_PROTO_KEY, p)) {
|
|
CHECK(p.fourcc == 0x54504D4Du, "mob fourcc MMPT");
|
|
CHECK(p.stride == 255, "mob stride 255 (== sizeof TMobTable)");
|
|
CHECK(p.elements > 100, "mob elements > 100");
|
|
|
|
int named = 0, maxlvl = 0, with_folder = 0;
|
|
for (uint32_t i = 0; i < p.elements; ++i) {
|
|
MobRecord m = parse_mob(p.record(i), p.stride);
|
|
named += !m.name.empty();
|
|
with_folder += !m.folder.empty();
|
|
if (m.level > maxlvl) {
|
|
maxlvl = m.level;
|
|
}
|
|
}
|
|
CHECK(named > p.elements * 0.8, "mob: >80% named");
|
|
CHECK(with_folder > p.elements * 0.5, "mob: >50% have a model folder");
|
|
CHECK(maxlvl > 20 && maxlvl < 256, "mob levels in a sane range");
|
|
MobRecord first = parse_mob(p.record(0), p.stride);
|
|
std::printf("mob_proto: %u mobs, stride %u, %d named, maxlvl=%d\n [0] vnum=%u '%s' "
|
|
"type=%u rank=%u level=%u folder='%s'\n",
|
|
p.elements, p.stride, named, maxlvl, first.vnum, first.locale_name.c_str(), first.type,
|
|
first.rank, first.level, first.folder.c_str());
|
|
CHECK(first.vnum > 0 && first.vnum < 60000, "mob[0] vnum sane");
|
|
CHECK(!first.name.empty(), "mob[0] has a name");
|
|
}
|
|
}
|
|
|
|
if (g_fail) {
|
|
std::fprintf(stderr, "%d check(s) failed\n", g_fail);
|
|
return 1;
|
|
}
|
|
std::printf("all checks passed\n");
|
|
return 0;
|
|
}
|