// item_proto / mob_proto reader — against the real locale files if present. // Set M2_ASSETS to the Metin2 assets dir, else falls back to a repo-relative // guess; skips (passes) if the files are not found. #include "../src/proto/proto.h" #include #include #include 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 std::string assets_root() { if (const char *e = std::getenv("M2_ASSETS")) { return e; } return "../../assets"; // ctest cwd = build/extension -> /assets } static bool exists(const std::string &p) { FILE *f = std::fopen(p.c_str(), "rb"); if (f) { std::fclose(f); return true; } return false; } int main() { const std::string base = assets_root() + "/locale/locale/en"; const std::string ipath = base + "/item_proto"; const std::string mpath = base + "/mob_proto"; if (!exists(ipath) || !exists(mpath)) { std::printf("skipped (no locale proto files at %s)\n", base.c_str()); return 0; } // --- item_proto --- { Proto p; std::string err; bool ok = load_proto(ipath, ITEM_PROTO_KEY, p, &err); CHECK(ok, ("load item_proto: " + err).c_str()); if (ok) { CHECK(p.fourcc == 0x5850494Du, "item fourcc MIPX"); CHECK(p.version == 1, "item version 1"); CHECK(p.stride == 236, "item stride 236 (== pack(1) sizeof TItemTable)"); CHECK(p.elements > 100, "item elements > 100"); CHECK(p.blob.size() == static_cast(p.stride) * p.elements, "item blob size"); // every record: vnum monotonically increasing, name printable ascii 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 non-decreasing"); 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); ItemRecord first = parse_item(p.record(0), p.stride); std::printf(" [0] vnum=%u '%s' type=%u sub=%u weight=%u\n", first.vnum, first.name.c_str(), first.type, first.sub_type, first.weight); CHECK(first.vnum > 0 && first.vnum < 100000, "item[0] vnum sane"); CHECK(!first.name.empty(), "item[0] has a name"); // alValues[3] = body-armor shape index (anchored by bSpecular @234). // "Monk Plate Armour" 11200..11209 all share shape 3; specular ramps // with the refine level (0 -> 100). 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 values=[%d,%d,%d,%d,%d,%d] spec=%u\n", 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"); } } // --- mob_proto --- { Proto p; std::string err; bool ok = load_proto(mpath, MOB_PROTO_KEY, p, &err); CHECK(ok, ("load mob_proto: " + err).c_str()); if (ok) { CHECK(p.fourcc == 0x54504D4Du, "mob fourcc MMPT"); CHECK(p.stride == 335, "mob stride 335 (derived from realSize/elements)"); CHECK(p.elements > 100, "mob elements > 100"); int named = 0, maxlvl = 0; for (uint32_t i = 0; i < p.elements; ++i) { MobRecord m = parse_mob(p.record(i), p.stride); if (!m.name.empty()) { ++named; } if (m.level > maxlvl) { maxlvl = m.level; } } CHECK(named > p.elements * 0.8, "mob: >80% named"); 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\n", p.elements, p.stride, named, maxlvl, first.vnum, first.name.c_str(), first.type, first.rank, first.level); 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; }