From 9134189e57bccb60747b453d4b96099a20b27b23 Mon Sep 17 00:00:00 2001 From: plenarius Date: Sat, 2 May 2026 15:58:44 -0400 Subject: [PATCH] SkillRanks: fix UB in GetAreaModifier dereferencing empty std::optional `pArea->nwnxGet(...)` returns `std::optional` and is empty when the area has no modifier set for the requested skill (i.e. SetAreaModifier was never called for that combination). Dereferencing it via `*` was undefined behaviour. Use `.value_or(0)` to return the natural identity for an unset modifier, matching how the internal skill resolution at line 675-677 already treats an empty optional (it skips the `+= *areaMod` accumulation). Fixes #1855 --- CHANGELOG.md | 1 + Plugins/SkillRanks/SkillRanks.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cb568a8ddb..f0966171af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ https://github.com/nwnxee/unified/compare/build8193.37.13...HEAD - MaxLevel: Fixed returning an invalid number of known spells in some cases. - Fixed `NWNX_TWEAKS_RESIST_ENERGY_STACKS_WITH_EPIC_ENERGY_RESISTANCE` not working correctly when the character has more than one resist energy feat. - Fixed `NWNX_TWEAKS_SNEAK_ATTACK_IGNORE_CRIT_IMMUNITY` only considering 3 classes for determining the level difference of attacker and defender. +- SkillRanks: Fixed `GetAreaModifier()` dereferencing an empty `std::optional` (undefined behavior) when the queried area had no modifier set for the skill. Now returns `0` in that case. ## 8193.37.13 https://github.com/nwnxee/unified/compare/build8193.36.10...build8193.37.13 diff --git a/Plugins/SkillRanks/SkillRanks.cpp b/Plugins/SkillRanks/SkillRanks.cpp index f0d5a63f8e2..ac1aea3f3b4 100644 --- a/Plugins/SkillRanks/SkillRanks.cpp +++ b/Plugins/SkillRanks/SkillRanks.cpp @@ -890,7 +890,7 @@ ArgumentStack SkillRanks::GetAreaModifier(ArgumentStack&& args) ASSERT_OR_THROW(skillId >= Constants::Skill::MIN); ASSERT_OR_THROW(skillId < Globals::Rules()->m_nNumSkills); - int32_t retVal = *pArea->nwnxGet(areaModPOSKey + std::to_string(skillId)); + int32_t retVal = pArea->nwnxGet(areaModPOSKey + std::to_string(skillId)).value_or(0); return ScriptAPI::Arguments(retVal); }