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.
This commit is contained in:
dongresource 2023-06-22 02:27:28 +02:00
parent be6a4c0a5d
commit 23ab908366

View File

@ -37,6 +37,22 @@ public:
const char *what() const throw() { return msg.c_str(); } 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. * 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)--; int id = (*nextId)--;
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"]; 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) { if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"], npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
NPCManager::NPCData[(int)mob["iNPCType"]], id); NPCManager::NPCData[(int)mob["iNPCType"]], id);
@ -785,6 +803,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
auto groups = gruntwork["groups"]; auto groups = gruntwork["groups"];
for (auto _group = groups.begin(); _group != groups.end(); _group++) { for (auto _group = groups.begin(); _group != groups.end(); _group++) {
auto leader = _group.value(); auto leader = _group.value();
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; 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; int followerCount = 0;
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
auto follower = _fol.value(); auto follower = _fol.value();
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; 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); 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; npcID += NPC_ID_OFFSET;
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
int type = (int)npc["iNPCType"]; 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; ensureValidNPCType(type, settings::NPCJSON);
continue;
}
#ifdef ACADEMY #ifdef ACADEMY
// do not spawn NPCs in the future // do not spawn NPCs in the future
if (npc["iX"] > 512000 && npc["iY"] < 256000) 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 int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
npcID += MOB_ID_OFFSET; npcID += MOB_ID_OFFSET;
int type = (int)npc["iNPCType"]; 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; ensureValidNPCType(type, settings::MOBJSON);
continue;
}
auto td = NPCManager::NPCData[type]; auto td = NPCManager::NPCData[type];
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"]; 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++) { for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
auto leader = _group.value(); auto leader = _group.value();
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
leadID += MOB_GROUP_ID_OFFSET; leadID += MOB_GROUP_ID_OFFSET;
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
auto td = NPCManager::NPCData[(int)leader["iNPCType"]]; auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"]; uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
auto followers = leader["aFollowers"]; auto followers = leader["aFollowers"];
@ -967,6 +992,9 @@ static void loadMobs(json& npcData, int32_t* nextId) {
int followerCount = 0; int followerCount = 0;
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) { for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
auto follower = _fol.value(); auto follower = _fol.value();
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]]; 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); 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);