From 23ab908366d547ef6802259d62cc7d4606fbd371 Mon Sep 17 00:00:00 2001 From: dongresource Date: Thu, 22 Jun 2023 02:27:28 +0200 Subject: [PATCH] Refuse to start if there are invalid NPC types in the JSONs This fixes an issue where there server would start up fine even if NPC types from a later build were found in the gruntwork file. Because of the semantics of the C++ array indexing operator, the index into NPCData in loadGruntworkPost() would silently create extra entries in the nlohmann::json array, which would break future NPC type limit checks and subsequently crash the server at the next invocation of /summonW, and likely other places. We fix this by refusing to start the server if any invalid NPC types are found, because simply skipping them in the gruntwork file would silently omit them from further writes to the gruntwork file, which would be undesirable data loss. --- src/TableData.cpp | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/TableData.cpp b/src/TableData.cpp index 2b87de5..a685cee 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -37,6 +37,22 @@ public: const char *what() const throw() { return msg.c_str(); } }; +/* + * We must refuse to run if an invalid NPC type is found in the JSONs, especially + * the gruntwork file. If we were to just skip loading invalid NPCs, they would get + * silently dropped from the gruntwork file, which would be confusing in situations + * where a gruntwork file for the wrong game build was accidentally loaded. + */ +static void ensureValidNPCType(int type, std::string filename) { + // last known NPC type + int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"]; + + if (type > npcLimit) { + std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl; + exit(1); + } +} + /* * Create a full and properly-paced path by interpolating between keyframes. */ @@ -766,6 +782,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { int id = (*nextId)--; uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"]; + ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON); + if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) { npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"], NPCManager::NPCData[(int)mob["iNPCType"]], id); @@ -785,6 +803,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { auto groups = gruntwork["groups"]; for (auto _group = groups.begin(); _group != groups.end(); _group++) { auto leader = _group.value(); + + ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON); + auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; @@ -805,6 +826,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) { int followerCount = 0; for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { auto follower = _fol.value(); + + ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON); + auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId); @@ -861,10 +885,9 @@ static void loadNPCs(json& npcData) { npcID += NPC_ID_OFFSET; int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; int type = (int)npc["iNPCType"]; - if (NPCManager::NPCData[type].is_null()) { - std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl; - continue; - } + + ensureValidNPCType(type, settings::NPCJSON); + #ifdef ACADEMY // do not spawn NPCs in the future if (npc["iX"] > 512000 && npc["iY"] < 256000) @@ -906,10 +929,9 @@ static void loadMobs(json& npcData, int32_t* nextId) { int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer npcID += MOB_ID_OFFSET; int type = (int)npc["iNPCType"]; - if (NPCManager::NPCData[type].is_null()) { - std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl; - continue; - } + + ensureValidNPCType(type, settings::MOBJSON); + auto td = NPCManager::NPCData[type]; uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; @@ -937,7 +959,10 @@ static void loadMobs(json& npcData, int32_t* nextId) { for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) { auto leader = _group.value(); int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer + leadID += MOB_GROUP_ID_OFFSET; + ensureValidNPCType(leader["iNPCType"], settings::MOBJSON); + auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; auto followers = leader["aFollowers"]; @@ -967,6 +992,9 @@ static void loadMobs(json& npcData, int32_t* nextId) { int followerCount = 0; for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { auto follower = _fol.value(); + + ensureValidNPCType(follower["iNPCType"], settings::MOBJSON); + auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);