From f5ffd31ad91c83d174294ec4f6a968b0eb158f75 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Fri, 3 May 2024 23:47:35 +0500 Subject: [PATCH] fix(skyrim-platform): fix effectStart/effectFinish crash (#1928) --- .../sp-fix-effectStart-effectFinish-crash.md | 1 + .../skyrim_platform/EventHandler.cpp | 90 ++++++++++++------- 2 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 docs/release/dev/sp-fix-effectStart-effectFinish-crash.md diff --git a/docs/release/dev/sp-fix-effectStart-effectFinish-crash.md b/docs/release/dev/sp-fix-effectStart-effectFinish-crash.md new file mode 100644 index 0000000000..a0547b3520 --- /dev/null +++ b/docs/release/dev/sp-fix-effectStart-effectFinish-crash.md @@ -0,0 +1 @@ +Fixed crash when loading a save with at least one active `effectStart`/`effectFinish` listener. diff --git a/skyrim-platform/src/platform_se/skyrim_platform/EventHandler.cpp b/skyrim-platform/src/platform_se/skyrim_platform/EventHandler.cpp index f46121308c..27023716c1 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/EventHandler.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/EventHandler.cpp @@ -108,45 +108,69 @@ EventResult EventHandler::ProcessEvent( return EventResult::kContinue; } - auto e = CopyEventPtr(event); - - /** - * this is a workaround - * at the moment of writing if you try to create new instance of - * RE::ActiveEffect the game crashes either at instance construction or - * deconstruction, but since we really need copies of those classes here we - * have to manually manage memory to avoid leaks - */ - auto effectList = std::make_shared>>>(); - - for (const auto& eff : - *event->target.get()->As()->GetActiveEffectList()) { - if (eff->usUniqueID != 0) { - auto activeEffect = RE::malloc(); - std::memcpy(activeEffect, eff, sizeof(*eff)); - effectList->emplace_back(activeEffect, - game_type_pointer_deleter()); + struct EffectData + { + uint32_t baseMagicEffectId = 0; + uint32_t casterRefrId = 0; + uint32_t targetRefrId = 0; + bool isApplied = false; + }; + + EffectData effectData; + effectData.casterRefrId = + event->caster.get() ? event->caster.get()->GetFormID() : 0; + effectData.targetRefrId = + event->target.get() ? event->target.get()->GetFormID() : 0; + effectData.isApplied = event->isApplied; + + RE::Actor* targetActor = + event->target.get() ? event->target.get()->As() : nullptr; + + if (targetActor) { + for (RE::ActiveEffect* eff : *targetActor->GetActiveEffectList()) { + if (eff->usUniqueID == event->activeEffectUniqueID) { + auto baseMagicEffect = eff->GetBaseObject(); + effectData.baseMagicEffectId = + baseMagicEffect ? baseMagicEffect->formID : 0; + break; + } } } - SkyrimPlatform::GetSingleton()->AddUpdateTask([e, effectList] { - for (const auto& effect : *effectList.get()) { - if (effect->usUniqueID == e->activeEffectUniqueID) { - auto obj = JsValue::Object(); + if (effectData.baseMagicEffectId == 0) { + return EventResult::kContinue; + } - AddObjProperty(&obj, "effect", effect->GetBaseObject(), "MagicEffect"); - AddObjProperty(&obj, "caster", e->caster.get(), "ObjectReference"); - AddObjProperty(&obj, "target", e->target.get(), "ObjectReference"); + SkyrimPlatform::GetSingleton()->AddUpdateTask([effectData] { + auto obj = JsValue::Object(); - if (e->isApplied) { - SendEvent("effectStart", obj); - } else { - SendEvent("effectFinish", obj); - } + auto baseMagicEffect = + RE::TESForm::LookupByID(effectData.baseMagicEffectId); + auto casterRefr = + RE::TESForm::LookupByID(effectData.casterRefrId); + auto targetRefr = + RE::TESForm::LookupByID(effectData.targetRefrId); - return; - } + if (!baseMagicEffect) { + return; + } + + if (!casterRefr && effectData.casterRefrId != 0) { + return; + } + + if (!targetRefr && effectData.targetRefrId != 0) { + return; + } + + AddObjProperty(&obj, "effect", baseMagicEffect, "MagicEffect"); + AddObjProperty(&obj, "caster", casterRefr, "ObjectReference"); + AddObjProperty(&obj, "target", targetRefr, "ObjectReference"); + + if (effectData.isApplied) { + SendEvent("effectStart", obj); + } else { + SendEvent("effectFinish", obj); } });