diff --git a/r5dev/bsplib/bsplib.cpp b/r5dev/bsplib/bsplib.cpp index 9c69c323..a220d392 100644 --- a/r5dev/bsplib/bsplib.cpp +++ b/r5dev/bsplib/bsplib.cpp @@ -2,6 +2,7 @@ #include "tier1/cvar.h" #include "datacache/mdlcache.h" #include "common/pseudodefs.h" +#include "materialsystem/cmaterialsystem.h" #include "materialsystem/cmaterialglue.h" #include "engine/host_state.h" #include "engine/modelloader.h" @@ -339,7 +340,7 @@ //} //----------------------------------------------------------------------------- -// Purpose: calculates the view frustum culling data per static prop +// Purpose: calculates the view frustum culling data foreach static prop //----------------------------------------------------------------------------- void* __fastcall BuildPropStaticFrustumCullMap(int64_t a1, int64_t a2, unsigned int a3, unsigned int a4, int64_t a5, int64_t a6, int64_t a7) { @@ -396,11 +397,14 @@ void* __fastcall BuildPropStaticFrustumCullMap(int64_t a1, int64_t a2, unsigned ++v64; v67 += 92i64; - if (reinterpret_cast(v68) < g_GameDll.m_RunTimeData.m_pSectionBase || // Check bounds (data could only be within the '.data' segment. + if (reinterpret_cast(v68) < g_GameDll.m_RunTimeData.m_pSectionBase || // Check bounds (data is mostly within the '.data' segment. reinterpret_cast(v68) > g_GameDll.m_ExceptionTable.m_pSectionBase || error) { - error = true; - continue; + if (!IsMaterialVFTable(reinterpret_cast(v68))) // Last chance. + { + error = true; + continue; + } } } while (v64 < *((int*)v65 + 19)); } diff --git a/r5dev/materialsystem/cmaterialsystem.cpp b/r5dev/materialsystem/cmaterialsystem.cpp index d7e0dcff..0a102776 100644 --- a/r5dev/materialsystem/cmaterialsystem.cpp +++ b/r5dev/materialsystem/cmaterialsystem.cpp @@ -80,6 +80,34 @@ void* __fastcall DispatchDrawCall(int64_t a1, uint64_t a2, int a3, int a4, int64 #endif } +//----------------------------------------------------------------------------- +// Purpose: checks if ptr is valid, and checks for equality against CMaterial vftable +// Input : **pCandidate - +// Output : true if valid and material, false otherwise +//----------------------------------------------------------------------------- +bool IsMaterialVFTable(void** pCandidate) +{ + // NOTE: this is a dirty fix, but for running technically broken BSP's, this is the only fix + // besides going bare metal inline assembly (which on its own isn't directly the problem, but + // portability wise it will be a problem as the majority of the code in r5apex.exe is declared inline). + // In the future, do not fix anything like this unless there is absolutely no other choice! + // The context of the problem is that we fix the missing models defined in the game_lump of a + // BSP by swapping missing models out for existing models, which will in many cases, end up with + // 2 or more model name duplicates within a single BSP's game_lump, which is illegal and causes + // unpredictable behavior, which in this case causes a register to be assigned to an invalid CMaterial + // address. The pointer can still be dereferenced in many cases, which is why we do an equality test. + // The first member of the CMaterial data structure should be its VFTable pointer, anything else is invalid. + __try + { + if (*pCandidate == g_pMaterialVFTable) + return true; + } + __except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) + { + return false; + } +} + /////////////////////////////////////////////////////////////////////////////// void CMaterialSystem_Attach() { diff --git a/r5dev/materialsystem/cmaterialsystem.h b/r5dev/materialsystem/cmaterialsystem.h index 7bad2b5e..aeee8eba 100644 --- a/r5dev/materialsystem/cmaterialsystem.h +++ b/r5dev/materialsystem/cmaterialsystem.h @@ -7,6 +7,7 @@ inline CMemory p_CMaterialSystem__Init; inline auto CMaterialSystem__Init = p_CMaterialSystem__Init.RCast(); inline void* g_pMaterialSystem = nullptr; +inline void* g_pMaterialVFTable = nullptr; #ifndef DEDICATED #if defined (GAMEDLL_S0) || defined (GAMEDLL_S1) inline CMemory p_DispatchDrawCall; @@ -25,6 +26,7 @@ inline int* g_nUnfreeStreamingTextureMemory = nullptr; inline int* g_nUnusableStreamingTextureMemory = nullptr; #endif // !DEDICATED +bool IsMaterialVFTable(void** pCandidate); void CMaterialSystem_Attach(); void CMaterialSystem_Detach(); /////////////////////////////////////////////////////////////////////////////// @@ -36,14 +38,13 @@ class VMaterialSystem : public IDetour #ifndef DEDICATED spdlog::debug("| FUN: CMaterialSystem::DispatchDrawCall : {:#18x} |\n", p_DispatchDrawCall.GetPtr()); spdlog::debug("| FUN: CMaterialSystem::DrawStreamOverlay : {:#18x} |\n", p_DrawStreamOverlay.GetPtr()); - spdlog::debug("| VAR: s_pRenderContext : {:#18x} |\n", s_pRenderContext.GetPtr()); -#endif // !DEDICATED - spdlog::debug("| VAR: g_pMaterialSystem : {:#18x} |\n", reinterpret_cast(g_pMaterialSystem)); -#ifndef DEDICATED spdlog::debug("| VAR: g_nTotalStreamingTextureMemory : {:#18x} |\n", reinterpret_cast(g_nTotalStreamingTextureMemory)); spdlog::debug("| VAR: g_nUnfreeStreamingTextureMemory : {:#18x} |\n", reinterpret_cast(g_nUnfreeStreamingTextureMemory)); spdlog::debug("| VAR: g_nUnusableStreamingTextureMemory : {:#18x} |\n", reinterpret_cast(g_nUnusableStreamingTextureMemory)); + spdlog::debug("| VAR: s_pRenderContext : {:#18x} |\n", s_pRenderContext.GetPtr()); #endif // !DEDICATED + spdlog::debug("| VAR: g_pMaterialSystem : {:#18x} |\n", reinterpret_cast(g_pMaterialSystem)); + spdlog::debug("| CON: g_pMaterialVFTable : {:#18x} |\n", reinterpret_cast(g_pMaterialVFTable)); spdlog::debug("+----------------------------------------------------------------+\n"); } virtual void GetFun(void) const @@ -64,17 +65,20 @@ class VMaterialSystem : public IDetour } virtual void GetVar(void) const { - g_pMaterialSystem = g_GameDll.FindPatternSIMD(reinterpret_cast( - "\x48\x8B\x0D\x00\x00\x00\x00\x48\x85\xC9\x74\x11\x48\x8B\x01\x48\x8D\x15\x00\x00\x00\x00"), "xxx????xxxxxxxxxxx????").ResolveRelativeAddressSelf(0x3, 0x7).RCast(); #ifndef DEDICATED - s_pRenderContext = p_DispatchDrawCall.FindPattern("48 8B ?? ?? ?? ?? 01").ResolveRelativeAddressSelf(0x3, 0x7); - g_nTotalStreamingTextureMemory = p_DrawStreamOverlay.Offset(0x0).FindPatternSelf("48 8B 05", CMemory::Direction::DOWN).ResolveRelativeAddressSelf(0x3, 0x7).RCast(); g_nUnfreeStreamingTextureMemory = p_DrawStreamOverlay.Offset(0x20).FindPatternSelf("48 8B 05", CMemory::Direction::DOWN).ResolveRelativeAddressSelf(0x3, 0x7).RCast(); g_nUnusableStreamingTextureMemory = p_DrawStreamOverlay.Offset(0x50).FindPatternSelf("48 8B 05", CMemory::Direction::DOWN).ResolveRelativeAddressSelf(0x3, 0x7).RCast(); + + s_pRenderContext = p_DispatchDrawCall.FindPattern("48 8B ?? ?? ?? ?? 01").ResolveRelativeAddressSelf(0x3, 0x7); #endif // !DEDICATED + g_pMaterialSystem = g_GameDll.FindPatternSIMD(reinterpret_cast( + "\x48\x8B\x0D\x00\x00\x00\x00\x48\x85\xC9\x74\x11\x48\x8B\x01\x48\x8D\x15\x00\x00\x00\x00"), "xxx????xxxxxxxxxxx????").ResolveRelativeAddressSelf(0x3, 0x7).RCast(); + } + virtual void GetCon(void) const + { + g_pMaterialVFTable = g_GameDll.GetVirtualMethodTable(".?AVCMaterial@@").RCast(); } - virtual void GetCon(void) const { } virtual void Attach(void) const { } virtual void Detach(void) const { } };