more hardening of code in Network/ (#6650)

also unique_ptr
This commit is contained in:
Philip Dubé
2026-06-05 06:16:11 +00:00
committed by GitHub
parent b8a3998c51
commit b61db77020
11 changed files with 113 additions and 67 deletions
@@ -191,6 +191,9 @@ inline void from_json(const json& j, SaveContext& saveContext) {
j.at("swordHealth").get_to(saveContext.swordHealth);
std::vector<u32> sceneFlagsArray;
j.at("sceneFlags").get_to(sceneFlagsArray);
if (sceneFlagsArray.size() < 124 * 4) {
sceneFlagsArray.resize(124 * 4, 0);
}
for (int i = 0; i < 124; i++) {
saveContext.sceneFlags[i].chest = sceneFlagsArray[i * 4];
saveContext.sceneFlags[i].swch = sceneFlagsArray[i * 4 + 1];
@@ -3,6 +3,7 @@
#include <libultraship/libultraship.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/OTRGlobals.h"
#include "soh/Enhancements/randomizer/randomizerEnums/RandomizerCheck.h"
static bool isResultOfHandling = false;
@@ -39,6 +40,10 @@ void Anchor::HandlePacket_SetCheckStatus(nlohmann::json payload) {
auto randoContext = Rando::Context::GetInstance();
RandomizerCheck rc = payload["rc"].get<RandomizerCheck>();
if (rc < 0 || rc >= RC_MAX) {
SPDLOG_ERROR("[Anchor] SET_CHECK_STATUS: rc {} out of range", (int)rc);
return;
}
RandomizerCheckStatus status = payload["status"].get<RandomizerCheckStatus>();
bool skipped = payload["skipped"].get<bool>();
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_SetFlag(nlohmann::json payload) {
s16 flagType = payload["flagType"].get<s16>();
s16 flag = payload["flag"].get<s16>();
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
// values would index gSaveContext.sceneFlags out of bounds.
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
SPDLOG_ERROR("[Anchor] SET_FLAG: sceneNum {} out of range", sceneNum);
return;
}
if (sceneNum == SCENE_ID_MAX) {
auto effect = new GameInteractionEffect::SetFlag();
effect->parameters[0] = flagType;
@@ -39,6 +39,12 @@ void Anchor::HandlePacket_TeleportTo(nlohmann::json payload) {
s32 entranceIndex = payload["entranceIndex"].get<s32>();
s8 roomIndex = payload["roomIndex"].get<s8>();
if (entranceIndex < 0 || roomIndex < 0) {
SPDLOG_ERROR("[Anchor] TELEPORT_TO: invalid entranceIndex {} or roomIndex {}", entranceIndex, (int)roomIndex);
return;
}
PosRot posRot = payload["posRot"].get<PosRot>();
gPlayState->nextEntranceIndex = entranceIndex;
@@ -41,6 +41,13 @@ void Anchor::HandlePacket_UnsetFlag(nlohmann::json payload) {
s16 flagType = payload["flagType"].get<s16>();
s16 flag = payload["flag"].get<s16>();
// sceneNum == SCENE_ID_MAX is a sentinel meaning "global flag" (handled below); only larger
// values would index gSaveContext.sceneFlags out of bounds.
if (sceneNum < 0 || sceneNum > SCENE_ID_MAX) {
SPDLOG_ERROR("[Anchor] UNSET_FLAG: sceneNum {} out of range", sceneNum);
return;
}
if (sceneNum == SCENE_ID_MAX) {
auto effect = new GameInteractionEffect::UnsetFlag();
effect->parameters[0] = flagType;
@@ -1,6 +1,7 @@
#include "soh/Network/Anchor/Anchor.h"
#include <nlohmann/json.hpp>
#include <libultraship/libultraship.h>
#include <spdlog/spdlog.h>
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/OTRGlobals.h"
@@ -33,6 +34,12 @@ void Anchor::HandlePacket_UpdateDungeonItems(nlohmann::json payload) {
}
u16 mapIndex = payload["mapIndex"].get<u16>();
// dungeonKeys is shorter than dungeonItems (19 vs 20), so bound by the smaller of the two.
if (mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonItems) ||
mapIndex >= ARRAY_COUNT(gSaveContext.inventory.dungeonKeys)) {
SPDLOG_ERROR("[Anchor] UPDATE_DUNGEON_ITEMS: mapIndex {} out of range", mapIndex);
return;
}
gSaveContext.inventory.dungeonItems[mapIndex] = payload["dungeonItems"].get<u8>();
gSaveContext.inventory.dungeonKeys[mapIndex] = payload["dungeonKeys"].get<s8>();
}
+21 -14
View File
@@ -29,19 +29,19 @@ void CrowdControl::OnDisconnected() {
}
void CrowdControl::OnIncomingJson(nlohmann::json payload) {
Effect* incomingEffect = ParseMessage(payload);
std::unique_ptr<Effect> incomingEffect = ParseMessage(payload);
if (!incomingEffect) {
return;
}
// If effect is not a timed effect, execute and return result.
if (!incomingEffect->timeRemaining) {
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect);
EffectResult result = CrowdControl::ExecuteEffect(incomingEffect.get());
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
} else {
// If another timed effect is already active that conflicts with the incoming effect.
bool isConflictingEffectActive = false;
for (Effect* effect : activeEffects) {
for (const auto& effect : activeEffects) {
if (effect != incomingEffect && effect->category == incomingEffect->category &&
effect->id < incomingEffect->id) {
isConflictingEffectActive = true;
@@ -52,14 +52,14 @@ void CrowdControl::OnIncomingJson(nlohmann::json payload) {
if (!isConflictingEffectActive) {
// Check if effect can be applied, if it can't, let CC know.
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect);
EffectResult result = CrowdControl::CanApplyEffect(incomingEffect.get());
if (result == EffectResult::Retry || result == EffectResult::Failure) {
EmitMessage(incomingEffect->id, incomingEffect->timeRemaining, result);
return;
}
activeEffectsMutex.lock();
activeEffects.push_back(incomingEffect);
activeEffects.push_back(std::move(incomingEffect));
activeEffectsMutex.unlock();
}
}
@@ -74,17 +74,15 @@ void CrowdControl::ProcessActiveEffects() {
auto it = activeEffects.begin();
while (it != activeEffects.end()) {
Effect* effect = *it;
Effect* effect = it->get();
EffectResult result = CrowdControl::ExecuteEffect(effect);
if (result == EffectResult::Success) {
// If time remaining has reached 0, we have finished the effect.
if (effect->timeRemaining <= 0) {
it = activeEffects.erase(std::remove(activeEffects.begin(), activeEffects.end(), effect),
activeEffects.end());
GameInteractor::RemoveEffect(
*dynamic_cast<RemovableGameInteractionEffect*>(effect->giEffect.get()));
delete effect;
it = activeEffects.erase(it);
} else {
// If we have a success after previously being paused, tell CC to resume timer.
if (effect->isPaused) {
@@ -168,7 +166,7 @@ CrowdControl::EffectResult CrowdControl::TranslateGiEnum(GameInteractionEffectQu
return result;
}
CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
std::unique_ptr<CrowdControl::Effect> CrowdControl::ParseMessage(nlohmann::json dataReceived) {
if (!dataReceived.contains("id") || !dataReceived.contains("type")) {
SPDLOG_ERROR("[CrowdControl] Invalid payload received:\n{}", dataReceived.dump());
return nullptr;
@@ -176,13 +174,16 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
SPDLOG_INFO("[CrowdControl] Received payload:\n{}", dataReceived.dump());
if (!dataReceived.contains("code")) {
// "parameters" is intentionally not required: most effects (spawn enemies, teleports, status
// effects, etc.) carry no parameters. Its absence is handled safely below, and any type error
// is caught by the guard in Network::HandleRemoteJson.
if (!dataReceived.contains("code") || !dataReceived.contains("viewer")) {
// This seems to happen when the CC session ends
SPDLOG_ERROR("[CrowdControl] Payload does not contain code, ignoring.");
SPDLOG_ERROR("[CrowdControl] Payload does not contain code or viewer, ignoring.");
return nullptr;
}
Effect* effect = new Effect();
auto effect = std::make_unique<Effect>();
effect->lastExecutionResult = EffectResult::Initiate;
effect->id = dataReceived["id"];
effect->viewerName = dataReceived["viewer"];
@@ -194,9 +195,15 @@ CrowdControl::Effect* CrowdControl::ParseMessage(nlohmann::json dataReceived) {
receivedParameter = dataReceived["parameters"][0];
}
auto it = effectStringToEnum.find(effectName);
if (it == effectStringToEnum.end()) {
SPDLOG_ERROR("[CrowdControl] Unknown effect code: {}", effectName);
return nullptr;
}
// Assign GameInteractionEffect + values to CC effect.
// Categories are mostly used for checking for conflicting timed effects.
switch (effectStringToEnum[effectName]) {
switch (it->second) {
// Spawn Enemies and Objects
case kEffectSpawnCuccoStorm:
+2 -2
View File
@@ -64,14 +64,14 @@ class CrowdControl : public Network {
std::thread ccThreadProcess;
std::vector<Effect*> activeEffects;
std::vector<std::unique_ptr<Effect>> activeEffects;
std::mutex activeEffectsMutex;
void HandleRemoteData(nlohmann::json payload);
void ProcessActiveEffects();
void EmitMessage(uint32_t eventId, long timeRemaining, EffectResult status);
Effect* ParseMessage(nlohmann::json payload);
std::unique_ptr<Effect> ParseMessage(nlohmann::json payload);
EffectResult ExecuteEffect(Effect* effect);
EffectResult CanApplyEffect(Effect* effect);
EffectResult TranslateGiEnum(GameInteractionEffectQueryResult giResult);
+5 -1
View File
@@ -157,5 +157,9 @@ void Network::HandleRemoteJson(std::string payload) {
return;
}
OnIncomingJson(jsonPayload);
try {
OnIncomingJson(jsonPayload);
} catch (const std::exception& e) {
SPDLOG_ERROR("[Network] Exception handling incoming JSON: {}", e.what());
} catch (...) { SPDLOG_ERROR("[Network] Unknown exception handling incoming JSON"); }
}
+47 -49
View File
@@ -2,8 +2,6 @@
#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include <nlohmann/json.hpp>
#include "soh/OTRGlobals.h"
#include "soh/util.h"
template <class DstType, class SrcType> bool IsType(const SrcType* src) {
return dynamic_cast<const DstType*>(src) != nullptr;
@@ -98,12 +96,12 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
return;
}
GameInteractionEffectBase* giEffect = EffectFromJson(payload["effect"]);
auto giEffect = EffectFromJson(payload["effect"]);
if (giEffect) {
GameInteractionEffectQueryResult result;
if (effectType == "remove") {
if (IsType<RemovableGameInteractionEffect>(giEffect)) {
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect)->Remove();
if (IsType<RemovableGameInteractionEffect>(giEffect.get())) {
result = dynamic_cast<RemovableGameInteractionEffect*>(giEffect.get())->Remove();
} else {
result = GameInteractionEffectQueryResult::NotPossible;
}
@@ -133,7 +131,7 @@ void Sail::OnIncomingJson(nlohmann::json payload) {
} catch (...) { SPDLOG_ERROR("[Sail] Unknown exception handling remote JSON"); }
}
GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
std::unique_ptr<GameInteractionEffectBase> Sail::EffectFromJson(nlohmann::json payload) {
if (!payload.contains("name")) {
return nullptr;
}
@@ -141,7 +139,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
std::string name = payload["name"].get<std::string>();
if (name == "SetSceneFlag") {
auto effect = new GameInteractionEffect::SetSceneFlag();
auto effect = std::make_unique<GameInteractionEffect::SetSceneFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
@@ -149,7 +147,7 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
}
return effect;
} else if (name == "UnsetSceneFlag") {
auto effect = new GameInteractionEffect::UnsetSceneFlag();
auto effect = std::make_unique<GameInteractionEffect::UnsetSceneFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
@@ -157,171 +155,171 @@ GameInteractionEffectBase* Sail::EffectFromJson(nlohmann::json payload) {
}
return effect;
} else if (name == "SetFlag") {
auto effect = new GameInteractionEffect::SetFlag();
auto effect = std::make_unique<GameInteractionEffect::SetFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "UnsetFlag") {
auto effect = new GameInteractionEffect::UnsetFlag();
auto effect = std::make_unique<GameInteractionEffect::UnsetFlag>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "ModifyHeartContainers") {
auto effect = new GameInteractionEffect::ModifyHeartContainers();
auto effect = std::make_unique<GameInteractionEffect::ModifyHeartContainers>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FillMagic") {
return new GameInteractionEffect::FillMagic();
return std::make_unique<GameInteractionEffect::FillMagic>();
} else if (name == "EmptyMagic") {
return new GameInteractionEffect::EmptyMagic();
return std::make_unique<GameInteractionEffect::EmptyMagic>();
} else if (name == "ModifyRupees") {
auto effect = new GameInteractionEffect::ModifyRupees();
auto effect = std::make_unique<GameInteractionEffect::ModifyRupees>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "NoUI") {
return new GameInteractionEffect::NoUI();
return std::make_unique<GameInteractionEffect::NoUI>();
} else if (name == "ModifyGravity") {
auto effect = new GameInteractionEffect::ModifyGravity();
auto effect = std::make_unique<GameInteractionEffect::ModifyGravity>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyHealth") {
auto effect = new GameInteractionEffect::ModifyHealth();
auto effect = std::make_unique<GameInteractionEffect::ModifyHealth>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetPlayerHealth") {
auto effect = new GameInteractionEffect::SetPlayerHealth();
auto effect = std::make_unique<GameInteractionEffect::SetPlayerHealth>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "FreezePlayer") {
return new GameInteractionEffect::FreezePlayer();
return std::make_unique<GameInteractionEffect::FreezePlayer>();
} else if (name == "BurnPlayer") {
return new GameInteractionEffect::BurnPlayer();
return std::make_unique<GameInteractionEffect::BurnPlayer>();
} else if (name == "ElectrocutePlayer") {
return new GameInteractionEffect::ElectrocutePlayer();
return std::make_unique<GameInteractionEffect::ElectrocutePlayer>();
} else if (name == "KnockbackPlayer") {
auto effect = new GameInteractionEffect::KnockbackPlayer();
auto effect = std::make_unique<GameInteractionEffect::KnockbackPlayer>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyLinkSize") {
auto effect = new GameInteractionEffect::ModifyLinkSize();
auto effect = std::make_unique<GameInteractionEffect::ModifyLinkSize>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "InvisibleLink") {
return new GameInteractionEffect::InvisibleLink();
return std::make_unique<GameInteractionEffect::InvisibleLink>();
} else if (name == "PacifistMode") {
return new GameInteractionEffect::PacifistMode();
return std::make_unique<GameInteractionEffect::PacifistMode>();
} else if (name == "DisableZTargeting") {
return new GameInteractionEffect::DisableZTargeting();
return std::make_unique<GameInteractionEffect::DisableZTargeting>();
} else if (name == "WeatherRainstorm") {
return new GameInteractionEffect::WeatherRainstorm();
return std::make_unique<GameInteractionEffect::WeatherRainstorm>();
} else if (name == "ReverseControls") {
return new GameInteractionEffect::ReverseControls();
return std::make_unique<GameInteractionEffect::ReverseControls>();
} else if (name == "ForceEquipBoots") {
auto effect = new GameInteractionEffect::ForceEquipBoots();
auto effect = std::make_unique<GameInteractionEffect::ForceEquipBoots>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ModifyMovementSpeedMultiplier") {
auto effect = new GameInteractionEffect::ModifyMovementSpeedMultiplier();
auto effect = std::make_unique<GameInteractionEffect::ModifyMovementSpeedMultiplier>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "OneHitKO") {
return new GameInteractionEffect::OneHitKO();
return std::make_unique<GameInteractionEffect::OneHitKO>();
} else if (name == "ModifyDefenseModifier") {
auto effect = new GameInteractionEffect::ModifyDefenseModifier();
auto effect = std::make_unique<GameInteractionEffect::ModifyDefenseModifier>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "GiveOrTakeShield") {
auto effect = new GameInteractionEffect::GiveOrTakeShield();
auto effect = std::make_unique<GameInteractionEffect::GiveOrTakeShield>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "TeleportPlayer") {
auto effect = new GameInteractionEffect::TeleportPlayer();
auto effect = std::make_unique<GameInteractionEffect::TeleportPlayer>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "ClearAssignedButtons") {
auto effect = new GameInteractionEffect::ClearAssignedButtons();
auto effect = std::make_unique<GameInteractionEffect::ClearAssignedButtons>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetTimeOfDay") {
auto effect = new GameInteractionEffect::SetTimeOfDay();
auto effect = std::make_unique<GameInteractionEffect::SetTimeOfDay>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "SetCollisionViewer") {
return new GameInteractionEffect::SetCollisionViewer();
return std::make_unique<GameInteractionEffect::SetCollisionViewer>();
} else if (name == "RandomizeCosmetics") {
return new GameInteractionEffect::RandomizeCosmetics();
return std::make_unique<GameInteractionEffect::RandomizeCosmetics>();
} else if (name == "PressButton") {
auto effect = new GameInteractionEffect::PressButton();
auto effect = std::make_unique<GameInteractionEffect::PressButton>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "PressRandomButton") {
auto effect = new GameInteractionEffect::PressRandomButton();
auto effect = std::make_unique<GameInteractionEffect::PressRandomButton>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
}
return effect;
} else if (name == "AddOrTakeAmmo") {
auto effect = new GameInteractionEffect::AddOrTakeAmmo();
auto effect = std::make_unique<GameInteractionEffect::AddOrTakeAmmo>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "RandomBombFuseTimer") {
return new GameInteractionEffect::RandomBombFuseTimer();
return std::make_unique<GameInteractionEffect::RandomBombFuseTimer>();
} else if (name == "DisableLedgeGrabs") {
return new GameInteractionEffect::DisableLedgeGrabs();
return std::make_unique<GameInteractionEffect::DisableLedgeGrabs>();
} else if (name == "RandomWind") {
return new GameInteractionEffect::RandomWind();
return std::make_unique<GameInteractionEffect::RandomWind>();
} else if (name == "RandomBonks") {
return new GameInteractionEffect::RandomBonks();
return std::make_unique<GameInteractionEffect::RandomBonks>();
} else if (name == "PlayerInvincibility") {
return new GameInteractionEffect::PlayerInvincibility();
return std::make_unique<GameInteractionEffect::PlayerInvincibility>();
} else if (name == "SlipperyFloor") {
return new GameInteractionEffect::SlipperyFloor();
return std::make_unique<GameInteractionEffect::SlipperyFloor>();
} else if (name == "SpawnEnemyWithOffset") {
auto effect = new GameInteractionEffect::SpawnEnemyWithOffset();
auto effect = std::make_unique<GameInteractionEffect::SpawnEnemyWithOffset>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
}
return effect;
} else if (name == "SpawnActor") {
auto effect = new GameInteractionEffect::SpawnActor();
auto effect = std::make_unique<GameInteractionEffect::SpawnActor>();
if (payload.contains("parameters")) {
effect->parameters[0] = payload["parameters"][0].get<int32_t>();
effect->parameters[1] = payload["parameters"][1].get<int32_t>();
+3 -1
View File
@@ -2,12 +2,14 @@
#define NETWORK_SAIL_H
#ifdef __cplusplus
#include <memory>
#include "soh/Network/Network.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
class Sail : public Network {
private:
GameInteractionEffectBase* EffectFromJson(nlohmann::json payload);
std::unique_ptr<GameInteractionEffectBase> EffectFromJson(nlohmann::json payload);
void RegisterHooks();
public: