#include "proto.h" #include "EterBase/StdAfx.h" #include "EterBase/lzo.h" #include "GameLib/StdAfx.h" #include "GameLib/ItemData.h" #include "../platform/UserInterface/UserInterface.h" #include #include namespace mtproto { namespace { // CPythonNonPlayer::SMobTable (UserInterface/PythonNonPlayer.h), the 40250 ILP32 layout. The mirror of that header // needs UserInterface/StdAfx.h, which includes the Python headers (after batch 2P); until then the table is here. #pragma pack(push, 1) struct SMobSkillLevel { DWORD dwVnum; BYTE bLevel; }; struct SMobTable { DWORD dwVnum; char szName[24 + 1]; // CHARACTER_NAME_MAX_LEN (UserInterface/StdAfx.h) char szLocaleName[24 + 1]; BYTE bType; BYTE bRank; BYTE bBattleType; BYTE bLevel; BYTE bSize; DWORD dwGoldMin; DWORD dwGoldMax; DWORD dwExp; DWORD dwMaxHP; BYTE bRegenCycle; BYTE bRegenPercent; WORD wDef; DWORD dwAIFlag; DWORD dwRaceFlag; DWORD dwImmuneFlag; BYTE bStr, bDex, bCon, bInt; DWORD dwDamageRange[2]; short sAttackSpeed; short sMovingSpeed; BYTE bAggresiveHPPct; WORD wAggressiveSight; WORD wAttackRange; char cEnchants[6]; // MOB_ENCHANTS_MAX_NUM char cResists[11]; // MOB_RESISTS_MAX_NUM DWORD dwResurrectionVnum; DWORD dwDropItemVnum; BYTE bMountCapacity; BYTE bOnClickType; BYTE bEmpire; char szFolder[64 + 1]; float fDamMultiply; DWORD dwSummonVnum; DWORD dwDrainSP; DWORD dwMonsterColor; DWORD dwPolymorphItemVnum; SMobSkillLevel Skills[5]; // MOB_SKILL_MAX_NUM BYTE bBerserkPoint; BYTE bStoneSkinPoint; BYTE bGodSpeedPoint; BYTE bDeathBlowPoint; BYTE bRevivePoint; }; #pragma pack(pop) static_assert(sizeof(SMobTable) == 255, "mob_proto record"); using TItemTable = CItemData::TItemTable; uint32_t rd_u32(const uint8_t *p) { uint32_t v; std::memcpy(&v, p, 4); return v; } std::string cstr(const char *p, size_t maxn) { size_t n = 0; while (n < maxn && p[n]) { ++n; } return std::string(p, n); } } // namespace bool load_proto(const std::string &path, const std::array &key, Proto &out, std::string *err) { std::ifstream f(path, std::ios::binary); if (!f) { if (err) *err = "cannot open " + path; return false; } std::vector file((std::istreambuf_iterator(f)), std::istreambuf_iterator()); return load_proto_bytes(file, key, out, err); } // The header checks of CItemManager::LoadItemTable / CPythonNonPlayer::LoadNonPlayerData, then CLZO::Decompress. bool load_proto_bytes(const std::vector &file, const std::array &key, Proto &out, std::string *err) { out = Proto{}; if (file.size() < 12) { if (err) *err = "file too small"; return false; } size_t pos = 0; auto read_u32 = [&](uint32_t &v) { if (pos + 4 > file.size()) { return false; } v = rd_u32(file.data() + pos); pos += 4; return true; }; read_u32(out.fourcc); if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'X')) { if (!read_u32(out.version) || !read_u32(out.stride)) { if (err) *err = "truncated MIPX header"; return false; } if (out.version != 1) { if (err) *err = "invalid item_proto VERSION " + std::to_string(out.version); return false; } if (out.stride != sizeof(TItemTable)) { if (err) *err = "invalid item_proto STRIDE " + std::to_string(out.stride) + " != sizeof(SItemTable)"; return false; } } else if (out.fourcc == MAKEFOURCC('M', 'I', 'P', 'T')) { out.stride = sizeof(TItemTable); } else if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T')) { out.stride = sizeof(SMobTable); } else { if (err) *err = "invalid proto type"; return false; } uint32_t data_size = 0; if (!read_u32(out.elements) || !read_u32(data_size)) { if (err) *err = "truncated proto header"; return false; } // 40250 reads dwDataSize bytes without checking; CLZO then reads its own header lengths. if (pos + data_size > file.size() || data_size < sizeof(CLZObject::THeader)) { if (err) *err = "declared data_size past end of file"; return false; } PackSingletons(); // CLZO, as 40250 Main() creates it DWORD dwKey[4] = {key[0], key[1], key[2], key[3]}; CLZObject zObj; if (!CLZO::Instance().Decompress(zObj, file.data() + pos, dwKey)) { if (err) *err = "CLZO::Decompress failed (wrong key or corrupt data)"; return false; } if (out.fourcc == MAKEFOURCC('M', 'M', 'P', 'T') && (zObj.GetSize() % sizeof(SMobTable)) != 0) { if (err) *err = "invalid mob_proto size " + std::to_string(zObj.GetSize()) + ", check data format"; return false; } // 40250 walks dwElements records from the buffer without comparing against its size. if (static_cast(out.stride) * out.elements > zObj.GetSize()) { if (err) *err = "elements * stride past decompressed size"; return false; } const BYTE *buf = zObj.GetBuffer(); out.blob.assign(buf, buf + static_cast(out.stride) * out.elements); return true; } ItemRecord parse_item(const uint8_t *r, uint32_t stride) { ItemRecord it; if (!r || stride != sizeof(TItemTable)) { return it; } TItemTable t; std::memcpy(&t, r, sizeof(t)); it.vnum = t.dwVnum; it.vnum_range = t.dwVnumRange; it.name = cstr(t.szName, sizeof(t.szName)); it.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName)); it.type = t.bType; it.sub_type = t.bSubType; it.weight = t.bWeight; it.size = t.bSize; it.anti_flags = t.dwAntiFlags; it.flags = t.dwFlags; it.wear_flags = t.dwWearFlags; it.immune_flag = t.dwImmuneFlag; it.buy_price = t.dwIBuyItemPrice; it.sell_price = t.dwISellItemPrice; for (int i = 0; i < CItemData::ITEM_LIMIT_MAX_NUM; ++i) { it.limits[i] = {t.aLimits[i].bType, t.aLimits[i].lValue}; } for (int i = 0; i < CItemData::ITEM_APPLY_MAX_NUM; ++i) { it.applies[i] = {t.aApplies[i].bType, t.aApplies[i].lValue}; } for (int i = 0; i < CItemData::ITEM_VALUES_MAX_NUM; ++i) { it.values[i] = t.alValues[i]; } for (int i = 0; i < CItemData::ITEM_SOCKET_MAX_NUM; ++i) { it.sockets[i] = t.alSockets[i]; } it.refined_vnum = t.dwRefinedVnum; it.refine_set = t.wRefineSet; it.alter_to_magic_pct = t.bAlterToMagicItemPct; it.specular = t.bSpecular; it.gain_socket_pct = t.bGainSocketPct; return it; } MobRecord parse_mob(const uint8_t *r, uint32_t stride) { MobRecord m; if (!r || stride != sizeof(SMobTable)) { return m; } SMobTable t; std::memcpy(&t, r, sizeof(t)); m.vnum = t.dwVnum; m.name = cstr(t.szName, sizeof(t.szName)); m.locale_name = cstr(t.szLocaleName, sizeof(t.szLocaleName)); m.type = t.bType; m.rank = t.bRank; m.battle_type = t.bBattleType; m.level = t.bLevel; m.size = t.bSize; m.attack_speed = t.sAttackSpeed; m.moving_speed = t.sMovingSpeed; m.on_click_type = t.bOnClickType; m.empire = t.bEmpire; m.folder = cstr(t.szFolder, sizeof(t.szFolder)); m.monster_color = t.dwMonsterColor; return m; } } // namespace mtproto