Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions Code/client/CrashHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "CrashHandler.h"
#include <DbgHelp.h>
#include <Windows.h>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <iomanip>
Expand All @@ -23,16 +24,12 @@ std::string SerializeTimePoint(const time_point& time, const std::string& format

LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
static int alreadyCrashed = 0;
static std::atomic_bool s_alreadyCrashed = false;
auto retval = EXCEPTION_CONTINUE_SEARCH;

// Serialize
static std::mutex singleThreaded;
const std::lock_guard lock{singleThreaded};

// Check for severe, not continuable and not software-originated exception
// Handle only the first access violation. Later or nested ones pass through instead of blocking.
if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION &&
alreadyCrashed++ == 0)
!s_alreadyCrashed.exchange(true))
{
spdlog::critical (__FUNCTION__ ": crash occurred!");

Expand Down Expand Up @@ -115,13 +112,13 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
spdlog::critical(__FUNCTION__ ": UnhandledExceptionFilter() workaround triggered.");

singleThreaded.unlock(); // Might reenter, but is safe at this point.
if ((*pCurrentUnhandledExceptionFilter)(pExceptionInfo) == EXCEPTION_CONTINUE_EXECUTION)
retval = EXCEPTION_CONTINUE_EXECUTION;
singleThreaded.lock();
}

spdlog::shutdown();
// The exception may still be handled, and logging after spdlog::shutdown() crashes
if (auto pLogger = spdlog::default_logger())
pLogger->flush();
}
return retval;
}
Expand Down
16 changes: 16 additions & 0 deletions Code/client/Services/Generic/CharacterService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,13 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage)
return;
}

// Enabling and killing it here would run its death scripts again, e.g. repeating quest stages and rewards
if (acMessage.IsDead && pActor->IsDisabled() && pActor->GetExtension()->Reconciliation != ActorExtension::ReconciliationStage::WaitingForDisable)
{
spdlog::info("Skipped spawn for dead actor {:X} that is disabled locally, server id: {:X}", cActorId, acMessage.ServerId);
return;
}

const auto view = m_world.view<FormIdComponent>();
const auto itor = std::find_if(std::begin(view), std::end(view), [cActorId, view](entt::entity entity) { return view.get<FormIdComponent>(entity).Id == cActorId; });

Expand Down Expand Up @@ -1286,6 +1293,15 @@ void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpac
{
const uint32_t cWorldSpaceId = m_world.GetModSystem().GetGameId(acWorldSpaceId);
TESWorldSpace* const pWorldSpace = Cast<TESWorldSpace>(TESForm::GetById(cWorldSpaceId));

// Loading a cell outside the player's worldspace can crash the reference promote tasks and strands the actor
// there in our save. It's placed when it spawns instead.
if (pWorldSpace && pWorldSpace != PlayerCharacter::Get()->GetWorldSpace())
{
spdlog::info(__FUNCTION__ ": skipped moving actor {:X} to worldspace {:X}, the player isn't in it", apActor->formID, cWorldSpaceId);
return;
}

if (pWorldSpace)
{
GridCellCoords coordinates = GridCellCoords::CalculateGridCellCoords(acPosition);
Expand Down
8 changes: 8 additions & 0 deletions Code/client/Services/Generic/QuestService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,14 @@ void QuestService::OnQuestUpdate(const NotifyQuestUpdate& aUpdate) noexcept
return;
}

// Like the game, never restart a run once quest that's already done here, which would hand out its rewards again
const bool cIsStartOrStage = aUpdate.Status == NotifyQuestUpdate::Started || aUpdate.Status == NotifyQuestUpdate::StageUpdate;
if (cIsStartOrStage && (pQuest->flags & TESQuest::Flags::RunOnce) && (pQuest->flags & TESQuest::Flags::Completed))
{
spdlog::info("Remote quest {:X} is a completed run once quest here, ignoring stage {}", formId, aUpdate.Stage);
return;
}

if (aUpdate.Status == NotifyQuestUpdate::StageUpdate && pQuest->IsStageReached(aUpdate.Stage))
{
spdlog::info("Remote quest {:X} already reached stage {}, current stage: {}", formId, aUpdate.Stage, pQuest->currentStage);
Expand Down