diff --git a/CMakeLists.txt b/CMakeLists.txt index 462a693..7337e8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,12 @@ find_package(TBB CONFIG ) find_package(xbyak REQUIRED CONFIG) +find_package(Boost + CONFIG + REQUIRED + COMPONENTS + stacktrace_windbg +) include(cmake/sourcelist.cmake) @@ -101,6 +107,7 @@ target_link_libraries( TBB::tbb TBB::tbbmalloc xbyak::xbyak + Boost::stacktrace_windbg ) if (MSVC) diff --git a/src/patches/formcaching.cpp b/src/patches/formcaching.cpp index 1816845..d541aca 100644 --- a/src/patches/formcaching.cpp +++ b/src/patches/formcaching.cpp @@ -38,6 +38,7 @@ namespace patches RE::TESForm* hk_GetFormByID(std::uint32_t FormId) { RE::TESForm* formPointer = nullptr; + bool wrongPtrInCache = false; const std::uint8_t masterId = (FormId & 0xFF000000) >> 24; const std::uint32_t baseId = (FormId & 0x00FFFFFF); @@ -50,10 +51,28 @@ namespace patches formPointer = accessor->second; if (formPointer->GetFormID() != FormId) { - logger::trace("debug hk_GetFormByID FormId = {:08X}, but formPointer->GetFormID() = {:08X}"sv, FormId, formPointer->GetFormID()); + logger::trace("debug hk_GetFormByID FormId = {:08X}, but formPointer->GetFormID() = {:08X}:{}"sv, FormId, formPointer->GetFormID(), RE::FormTypeToString(formPointer->GetFormType())); + formPointer = nullptr; + wrongPtrInCache = true; } else { + // TODO: temporary Debug + GlobalFormTableLock->LockForRead(); + RE::TESForm* formPointerInGlobalFormTable = nullptr; + if (*GlobalFormTable) + { + auto iter = (*GlobalFormTable)->find(FormId); + formPointerInGlobalFormTable = (iter != (*GlobalFormTable)->end()) ? iter->second : nullptr; + } + GlobalFormTableLock->UnlockForRead(); + if (formPointerInGlobalFormTable != formPointer) + { + logger::trace("debug hk_GetFormByID FormId = {:08X} found in cache, but Ptr in Cache {:08X} and GlobalFormTable {:08X} not same"sv, FormId, reinterpret_cast(formPointer), reinterpret_cast(formPointerInGlobalFormTable)); + formPointer = nullptr; + wrongPtrInCache = true; + } + return formPointer; } } @@ -70,7 +89,7 @@ namespace patches GlobalFormTableLock->UnlockForRead(); - if (formPointer) + if (formPointer || wrongPtrInCache) UpdateFormCache(FormId, formPointer, false); return formPointer; diff --git a/src/patches/memorymanager.cpp b/src/patches/memorymanager.cpp index 035c7e4..34c9396 100644 --- a/src/patches/memorymanager.cpp +++ b/src/patches/memorymanager.cpp @@ -1,6 +1,10 @@ #include "version.h" #include "offsets.h" +#include +#include +#include +#pragma comment(lib, "dbghelp.lib") namespace { @@ -78,34 +82,234 @@ namespace } } + namespace MemoryManagerStats + { + + struct PointerStats + { + bool allocated = false; + }; + + tbb::concurrent_hash_map allocatedPointers; + + RE::TESForm* ctd; + + inline void StackDump() + { + auto st = boost::stacktrace::stacktrace(); + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); + + for (size_t i = 0; i < st.size(); i++) + { + auto frame = st[i]; + const void* address = frame.address(); + DWORD64 displacement = 0; + + // Get module info + IMAGEHLP_MODULE64 moduleInfo; + ZeroMemory(&moduleInfo, sizeof(moduleInfo)); + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + BOOL hasModule = SymGetModuleInfo64(process, (DWORD64)address, &moduleInfo); + + // Get symbol info + char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbolBuffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; + BOOL hasSymbol = SymFromAddr(process, (DWORD64)address, &displacement, symbol); + + // Get instruction address + CONTEXT context; + RtlCaptureContext(&context); + + char* dllName = nullptr; + if (hasModule) + + { + dllName = strrchr(moduleInfo.ImageName, '\\'); + } + + logger::error("Stack trace: [{}] DLL: {} | Offset: {:X} | Instruction: {:X} | Function: {}", i, (hasModule ? (dllName ? dllName + 1 : moduleInfo.ImageName) : "unknown"), displacement, context.Rip, (hasSymbol ? symbol->Name : "unknown")); + } + + SymCleanup(process); + } + + bool Allocate(void* mem) + { + decltype(allocatedPointers)::accessor accessor; + + uintptr_t addr = reinterpret_cast(mem); + + if (allocatedPointers.find(accessor, addr)) + { + PointerStats* stats = accessor->second; + if (stats->allocated) + { + logger::trace("MemoryManagerStats::Allocate error 101: already allocated for {}.", addr); + return false; + } + else + { + stats->allocated = true; + //logger::trace("MemoryManagerStats::Allocate success: allocated for {}.", addr); + return true; + } + } + else + { + PointerStats* stats = new PointerStats(); + stats->allocated = true; + allocatedPointers.emplace(addr, stats); + //logger::trace("MemoryManagerStats::Allocate success: allocated for {}.", addr); + return true; + } + } + + bool Deallocate(void* mem) + { + decltype(allocatedPointers)::accessor accessor; + + uintptr_t addr = reinterpret_cast(mem); + + if (allocatedPointers.find(accessor, addr)) + { + PointerStats* stats = accessor->second; + if (!stats->allocated) + { + logger::error("MemoryManagerStats::Deallocate error 202: already deallocated for {}.", addr); + StackDump(); + return false; + + } + else { + stats->allocated = false; + //logger::trace("MemoryManagerStats::Deallocate success: allocated for {}.", addr); + return true; + } + } + else + { + logger::trace("MemoryManagerStats::Deallocate error 203: not allocated for {}.", addr); + return false; + } + } + + bool Reallocate(void* oldmem, void* newmem) + { + decltype(allocatedPointers)::accessor accessor; + + uintptr_t old_addr = reinterpret_cast(oldmem); + uintptr_t new_addr = reinterpret_cast(newmem); + + if (old_addr == new_addr) { + return true; + } + + bool oldSuccess = true; + bool newSuccess = true; + + if (allocatedPointers.find(accessor, old_addr)) + { + PointerStats* stats = accessor->second; + if (!stats->allocated) + { + logger::trace("MemoryManagerStats::Reallocate old_addr error 302: already deallocated for {}.", old_addr); + oldSuccess = false; + } + else + { + //logger::trace("MemoryManagerStats::Reallocate old_addr success: allocated for {}.", old_addr); + stats->allocated = false; + } + } + else + { + logger::trace("MemoryManagerStats::Reallocate old_addr error 303: not allocated for {}.", old_addr); + oldSuccess = false; + } + + if (allocatedPointers.find(accessor, new_addr)) + { + PointerStats* stats = accessor->second; + if (stats->allocated) + { + logger::trace("MemoryManagerStats::Reallocate new_addr error 301: already allocated for {}.", new_addr); + newSuccess = false; + } + else + { + //logger::trace("MemoryManagerStats::Reallocate new_addr success: allocated for {}.", new_addr); + stats->allocated = true; + } + } + else + { + PointerStats* stats = new PointerStats(); + stats->allocated = true; + allocatedPointers.emplace(new_addr, stats); + //logger::trace("MemoryManagerStats::Reallocate new_addr success: allocated for {}.", new_addr); + } + + return oldSuccess && newSuccess; + } + } + namespace MemoryManager { void* Allocate(RE::MemoryManager*, std::size_t a_size, std::uint32_t a_alignment, bool a_alignmentRequired) { + void* ret = g_trash; if (a_size > 0) - return a_alignmentRequired ? - scalable_aligned_malloc(a_size, a_alignment) : - scalable_malloc(a_size); - else - return g_trash; + { + ret = a_alignmentRequired ? + scalable_aligned_malloc(a_size, a_alignment) : + scalable_malloc(a_size); + if (ret == nullptr) + { + logger::trace("MemoryManager::Allocate error {} ", errno); + } + else { + MemoryManagerStats::Allocate(ret); + } + } + return ret; } void Deallocate(RE::MemoryManager*, void* a_mem, bool a_alignmentRequired) { - if (a_mem != g_trash) - a_alignmentRequired ? - scalable_aligned_free(a_mem) : - scalable_free(a_mem); + if (a_mem != g_trash && a_mem != nullptr) + { + if (MemoryManagerStats::Deallocate(a_mem)) + { + a_alignmentRequired ? + scalable_aligned_free(a_mem) : + scalable_free(a_mem); + } + } } void* Reallocate(RE::MemoryManager* a_self, void* a_oldMem, std::size_t a_newSize, std::uint32_t a_alignment, bool a_alignmentRequired) { + void* ret = g_trash; if (a_oldMem == g_trash) - return Allocate(a_self, a_newSize, a_alignment, a_alignmentRequired); + ret = Allocate(a_self, a_newSize, a_alignment, a_alignmentRequired); else - return a_alignmentRequired ? + { + ret = a_alignmentRequired ? scalable_aligned_realloc(a_oldMem, a_newSize, a_alignment) : scalable_realloc(a_oldMem, a_newSize); + + if (ret == nullptr) + { + logger::trace("MemoryManager::Reallocate error {} ", errno); + } + else { + MemoryManagerStats::Reallocate(a_oldMem, ret); + } + } + return ret; } void ReplaceAllocRoutines() @@ -158,9 +362,19 @@ namespace { void* Allocate(RE::ScrapHeap*, std::size_t a_size, std::size_t a_alignment) { - return a_size > 0 ? - scalable_aligned_malloc(a_size, a_alignment) : - g_trash; + void* ret = g_trash; + if (a_size > 0) + { + ret = scalable_aligned_malloc(a_size, a_alignment); + if (ret == nullptr) + { + logger::trace("ScrapHeap::Allocate error {} ", errno); + } + else { + MemoryManagerStats::Allocate(ret); + } + } + return ret; } RE::ScrapHeap* Ctor(RE::ScrapHeap* a_this) @@ -172,8 +386,13 @@ namespace void Deallocate(RE::ScrapHeap*, void* a_mem) { - if (a_mem != g_trash) - scalable_aligned_free(a_mem); + if (a_mem != g_trash && a_mem != nullptr) + { + if (MemoryManagerStats::Deallocate(a_mem)) + { + scalable_aligned_free(a_mem); + } + } } void WriteHooks() diff --git a/src/patches/treelodreferencecaching.cpp b/src/patches/treelodreferencecaching.cpp index 10cc317..52e82b0 100644 --- a/src/patches/treelodreferencecaching.cpp +++ b/src/patches/treelodreferencecaching.cpp @@ -31,8 +31,10 @@ namespace patches decltype(referencesFormCache)::accessor accessor; if (referencesFormCache.find(accessor, maskedFormId)) + { refrObject = accessor->second; - else + } + if (refrObject == nullptr) { // Find first valid tree object by ESP/ESM load order auto dataHandler = RE::TESDataHandler::GetSingleton(); @@ -59,6 +61,35 @@ namespace patches // Insert even if it's a null pointer referencesFormCache.insert(std::make_pair(maskedFormId, refrObject)); } + else + { + RE::TESObjectREFR* refrObject2 = nullptr; + // Find first valid tree object by ESP/ESM load order + auto dataHandler = RE::TESDataHandler::GetSingleton(); + for (std::uint32_t i = 0; i < dataHandler->compiledFileCollection.files.size(); i++) + { + RE::TESForm* form = LookupFormByID((i << 24) | maskedFormId); + if (form) + refrObject2 = form->AsReference(); + if (refrObject2) + { + auto baseObj = refrObject2->GetBaseObject(); + if (baseObj) + { + using STATFlags = RE::TESObjectSTAT::RecordFlags; + // Checks if the form type is TREE (TESObjectTREE) or if it has the kHasTreeLOD flag (TESObjectSTAT) + if (baseObj->formFlags & STATFlags::kHasTreeLOD || baseObj->Is(RE::FormType::Tree)) + break; + } + } + + refrObject2 = nullptr; + } + if (refrObject2 != refrObject) + { + logger::trace("debug hk_UpdateBlockVisibility maskedFormId = {:08X} found in cache, but Ptr in Cache {:08X} and refrObject2 {:08X} not same"sv, maskedFormId, reinterpret_cast(refrObject), reinterpret_cast(refrObject2)); + } + } bool fullyHidden = false; float alpha = 1.0f; diff --git a/vcpkg.json b/vcpkg.json index 2a81169..8efadf5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -12,7 +12,8 @@ "spdlog", "tbb", "xbyak", - "rsm-binary-io" + "rsm-binary-io", + "boost-stacktrace" ], "builtin-baseline": "d9ccd77bb554e67137f3f754a2e2f11f4188c82c", "overrides": [