Skip to content

Commit eedada1

Browse files
authored
Merge branch 'master' into fix/burn_explosions
2 parents 1834c60 + ceaadd4 commit eedada1

File tree

14 files changed

+335
-81
lines changed

14 files changed

+335
-81
lines changed

Client/core/CSettings.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ void CSettings::CreateGUI()
540540
return;
541541
CVector2D buttonSize;
542542
button->GetSize(buttonSize);
543-
const float bottomPadding = 12.0f;
544-
const float buttonY = std::max(0.0f, tabPanelSize.fY - buttonSize.fY - bottomPadding);
543+
const float bottomPadding = 32.0f;
544+
const float buttonY = std::max(0.0f, tabPanelSize.fY - buttonSize.fY - bottomPadding - 4.0f);
545545
button->SetPosition(CVector2D(std::max(0.0f, tabPanelSize.fX - buttonSize.fX - bottomPadding), buttonY));
546546
};
547547

@@ -1586,7 +1586,7 @@ void CSettings::CreateGUI()
15861586
m_pGridBrowserBlacklist->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 32.0f));
15871587
m_pGridBrowserBlacklist->GetPosition(vecTemp);
15881588
const CVector2D blacklistGridPos = vecTemp;
1589-
const float browserBottomPadding = 12.0f;
1589+
const float browserBottomPadding = 32.0f;
15901590
const float browserButtonSpacing = 5.0f;
15911591
const CVector2D blacklistRemoveSize(140.0f, 22.0f);
15921592
const float blacklistHeightAvailable = tabPanelSize.fY - blacklistGridPos.fY - blacklistRemoveSize.fY - browserButtonSpacing - browserBottomPadding;

Client/game_sa/CColStoreSA.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
#include "StdInc.h"
1212
#include "CColStoreSA.h"
13+
#include "CGameSA.h"
14+
15+
extern CGameSA* pGame;
1316

1417
void CColStoreSA::Initialise()
1518
{
@@ -55,6 +58,22 @@ void CColStoreSA::AddCollisionNeededAtPosition(const CVector& position)
5558

5659
void CColStoreSA::EnsureCollisionIsInMemory(const CVector& position)
5760
{
61+
// Wait for GTA to complete initialization before calling collision functions
62+
// Race condition: MTA can trigger streaming/collision operations before GTA completes initialization.
63+
// GTA calls CTimer::Initialise at 0x53BDE6 during startup, which sets _timerFunction at 0x56189E.
64+
// If called before GTA reaches GS_INIT_PLAYING_GAME, the timer isn't initialized > crash at 0x5619E9 (CTimer::Suspend)
65+
66+
if (!pGame || pGame->GetSystemState() < SystemState::GS_INIT_PLAYING_GAME)
67+
return; // GTA not ready yet - skip (will retry on next streaming update)
68+
69+
// Just in case
70+
constexpr auto ADDR_timerFunction = 0xB7CB28;
71+
const auto timerFunction = *reinterpret_cast<void* const*>(ADDR_timerFunction);
72+
if (!timerFunction)
73+
return; // Timer not initialized yet - skip
74+
75+
// SA function signature: void __cdecl CColStore::EnsureCollisionIsInMemory(const CVector2D&)
76+
// CVector implicitly converts to CVector2D (uses x, y components only)
5877
using Signature = void(__cdecl*)(const CVector&);
5978
const auto function = reinterpret_cast<Signature>(0x410AD0);
6079
function(position);

Client/game_sa/CGameSA.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -898,12 +898,13 @@ void CGameSA::SetIgnoreFireStateEnabled(bool isEnabled)
898898

899899
if (isEnabled)
900900
{
901-
MemSet((void*)0x6511B9, 0x90, 10); // CCarEnterExit::IsVehicleStealable
902-
MemSet((void*)0x643A95, 0x90, 14); // CTaskComplexEnterCar::CreateFirstSubTask
903-
MemSet((void*)0x6900B5, 0x90, 14); // CTaskComplexCopInCar::ControlSubTask
904-
MemSet((void*)0x64F3DB, 0x90, 14); // CCarEnterExit::IsPlayerToQuitCarEnter
905-
906-
MemSet((void*)0x685A7F, 0x90, 14); // CTaskSimplePlayerOnFoot::ProcessPlayerWeapon
901+
// All these patches disable fire state checks (m_pFire != nullptr)
902+
// Related crash protection is handled by checks in TaskSA.cpp and CTaskManagementSystemSA.cpp
903+
MemSet((void*)0x6511B9, 0x90, 10); // CCarEnterExit::IsVehicleStealable - fire check
904+
MemSet((void*)0x643A95, 0x90, 14); // CTaskComplexEnterCar::CreateFirstSubTask - fire check
905+
MemSet((void*)0x6900B5, 0x90, 14); // CTaskComplexCopInCar::ControlSubTask - fire check
906+
MemSet((void*)0x64F3DB, 0x90, 14); // CCarEnterExit::IsPlayerToQuitCarEnter - fire check
907+
MemSet((void*)0x685A7F, 0x90, 14); // CTaskSimplePlayerOnFoot::ProcessPlayerWeapon - fire check
907908

908909
MemSet((void*)0x53A899, 0x90, 5); // CFire::ProcessFire
909910
MemSet((void*)0x53A990, 0x90, 5); // CFire::ProcessFire

Client/game_sa/CRenderWareSA.TextureReplacing.cpp

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "gamesa_renderware.h"
1313

1414
#include <map>
15+
#include <sstream>
1516
#include <unordered_map>
1617
#include <unordered_set>
1718
#include <utility>
@@ -309,9 +310,16 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
309310
continue;
310311

311312
RwTexture* pOriginalTexture = (idx < perTxdInfo.replacedOriginals.size()) ? perTxdInfo.replacedOriginals[idx] : nullptr;
313+
314+
if (pOriginalTexture && !SharedUtil::IsReadablePointer(pOriginalTexture, sizeof(RwTexture)))
315+
pOriginalTexture = nullptr;
316+
312317
swapMap[pOldTexture] = pOriginalTexture;
313318

314-
if (pOriginalTexture && !RwTexDictionaryContainsTexture(pInfo->pTxd, pOriginalTexture))
319+
// Only restore original textures early if this is the last replacement set for this TXD
320+
// Otherwise, other replacement sets might still be using the TXD
321+
bool isLastReplacement = (pInfo->usedByReplacements.size() == 1);
322+
if (pOriginalTexture && isLastReplacement && !RwTexDictionaryContainsTexture(pInfo->pTxd, pOriginalTexture))
315323
RwTexDictionaryAddTexture(pInfo->pTxd, pOriginalTexture);
316324
}
317325

@@ -346,6 +354,9 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
346354
{
347355
if (!pOldTexture)
348356
continue;
357+
358+
if (!SharedUtil::IsReadablePointer(pOldTexture, sizeof(RwTexture)))
359+
continue;
349360

350361
RwTexDictionaryRemoveTexture(pInfo->pTxd, pOldTexture);
351362
dassert(!RwTexDictionaryContainsTexture(pInfo->pTxd, pOldTexture));
@@ -389,18 +400,85 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen
389400
#ifdef MTA_DEBUG
390401
std::vector<RwTexture*> currentTextures;
391402
GetTxdTextures(currentTextures, pInfo->pTxd);
392-
assert(currentTextures.size() == pInfo->originalTextures.size());
393-
for (RwTexture* pOriginalTexture : pInfo->originalTextures)
403+
404+
auto formatTextures = [](const std::vector<RwTexture*>& textures) -> std::string {
405+
std::ostringstream result;
406+
for (size_t i = 0; i < textures.size(); ++i)
407+
{
408+
const auto* pTex = textures[i];
409+
const bool isValid = pTex && SharedUtil::IsReadablePointer(pTex, sizeof(RwTexture));
410+
const bool isLast = (i == textures.size() - 1);
411+
412+
if (isValid)
413+
result << pTex->name << "[0x" << std::hex << pTex << std::dec << "]";
414+
else
415+
result << "INVALID[0x" << std::hex << pTex << std::dec << "]";
416+
417+
if (!isLast)
418+
result << ", ";
419+
}
420+
return result.str();
421+
};
422+
423+
// Allow size mismatch in case texture removal was skipped due to invalid pointers
424+
if (currentTextures.size() != pInfo->originalTextures.size())
394425
{
395-
if (!pOriginalTexture)
396-
continue;
397-
assert(ListContains(currentTextures, pOriginalTexture));
398-
ListRemove(currentTextures, pOriginalTexture);
426+
std::ostringstream debugMsg;
427+
debugMsg << "TXD " << pInfo->usTxdId << ": texture count mismatch (current="
428+
<< currentTextures.size() << ", expected=" << pInfo->originalTextures.size() << ")\n";
429+
debugMsg << " Current textures: " << formatTextures(currentTextures);
430+
debugMsg << "\n Expected textures: " << formatTextures(pInfo->originalTextures);
431+
debugMsg << "\n";
432+
OutputDebugString(debugMsg.str().c_str());
433+
}
434+
else
435+
{
436+
// First pass: validate all original textures are present
437+
for (const auto* pOriginalTexture : pInfo->originalTextures)
438+
{
439+
if (!pOriginalTexture)
440+
continue;
441+
if (!ListContains(currentTextures, pOriginalTexture))
442+
{
443+
const char* texName = SharedUtil::IsReadablePointer(pOriginalTexture, sizeof(RwTexture))
444+
? pOriginalTexture->name : "INVALID";
445+
std::ostringstream oss;
446+
oss << "Original texture not found in TXD " << pInfo->usTxdId
447+
<< " - texture '" << texName << "' [0x" << std::hex << pOriginalTexture << std::dec
448+
<< "] was removed or replaced unexpectedly";
449+
const std::string assertMsg = oss.str();
450+
assert(false && assertMsg.c_str());
451+
}
452+
}
453+
454+
// Second pass: remove original textures from current list to find extras
455+
for (auto* pOriginalTexture : pInfo->originalTextures)
456+
{
457+
if (pOriginalTexture)
458+
ListRemove(currentTextures, pOriginalTexture);
459+
}
460+
461+
// Check for leaked textures
462+
if (!currentTextures.empty())
463+
{
464+
std::ostringstream oss;
465+
oss << "Extra textures remain in TXD " << pInfo->usTxdId
466+
<< " after removing all originals - indicates texture leak. Remaining: "
467+
<< formatTextures(currentTextures);
468+
const std::string assertMsg = oss.str();
469+
assert(false && assertMsg.c_str());
470+
}
399471
}
400-
assert(currentTextures.empty());
401472

402-
int32_t refsCount = CTxdStore_GetNumRefs(pInfo->usTxdId);
403-
assert(refsCount > 0 && "Should have at least one TXD reference here");
473+
const int32_t refsCount = CTxdStore_GetNumRefs(pInfo->usTxdId);
474+
if (refsCount <= 0)
475+
{
476+
std::ostringstream oss;
477+
oss << "TXD " << pInfo->usTxdId << " has invalid ref count "
478+
<< refsCount << " - should be > 0 before cleanup";
479+
const std::string assertMsg = oss.str();
480+
assert(false && assertMsg.c_str());
481+
}
404482
#endif
405483
// Clear original textures to prevent dangling pointers after TXD ref removal
406484
// The textures themselves are owned by the TXD and will be cleaned up when ref count hits zero

Client/game_sa/CRenderWareSA.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,9 +755,16 @@ short CRenderWareSA::CTxdStore_GetTxdRefcount(unsigned short usTxdID)
755755

756756
bool CRenderWareSA::RwTexDictionaryContainsTexture(RwTexDictionary* pTXD, RwTexture* pTex)
757757
{
758-
// Avoid crashes with freed/invalid textures
758+
// Avoid crashes with freed/invalid textures and TXDs
759759
if (!pTex || !pTXD)
760760
return false;
761+
762+
// Prevent crash when texture/TXD has been freed but pointer still exists
763+
if (!SharedUtil::IsReadablePointer(pTex, sizeof(RwTexture)))
764+
return false;
765+
766+
if (!SharedUtil::IsReadablePointer(pTXD, sizeof(RwTexDictionary)))
767+
return false;
761768

762769
return pTex->txd == pTXD;
763770
}

Client/game_sa/CStreamingSA.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "CModelInfoSA.h"
1616
#include "Fileapi.h"
1717
#include "processthreadsapi.h"
18+
#include "CGameSA.h"
1819

1920
extern CCoreInterface* g_pCore;
2021

@@ -334,6 +335,14 @@ void CStreamingSA::SetStreamingInfo(uint modelid, unsigned char usStreamID, uint
334335
{
335336
CStreamingInfo* pItemInfo = GetStreamingInfo(modelid);
336337

338+
// We remove the existing RwObject because, after switching the archive, the streamer will load a new one.
339+
// ReInit doesn't delete all RwObjects unless certain conditions are met.
340+
// In this case, we must force-remove the RwObject from memory, because it is no longer used,
341+
// and due to the archive change the streamer no longer detects it and therefore won't delete it.
342+
// As a result, a memory leak occurs after every call to engineImageLinkDFF.
343+
if (CModelInfo* modelInfo = g_pCore->GetGame()->GetModelInfo(modelid); modelInfo->GetRwObject())
344+
RemoveModel(modelid);
345+
337346
// Change nextInImg field for prev model
338347
for (CStreamingInfo& info : ms_aInfoForModel)
339348
{

Client/game_sa/CTaskManagementSystemSA.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,21 @@ void CTaskManagementSystemSA::RemoveTask(CTaskSAInterface* pTaskInterface)
102102
CTaskSA* CTaskManagementSystemSA::GetTask(CTaskSAInterface* pTaskInterface)
103103
{
104104
// Return NULL if we got passed NULL
105-
if (pTaskInterface == 0)
106-
return NULL;
105+
if (!pTaskInterface)
106+
return nullptr;
107+
108+
// Check vtable pointer to prevent crash from corrupted task objects
109+
TaskVTBL* pVTBL = pTaskInterface->VTBL;
110+
if (!pVTBL)
111+
return nullptr;
112+
113+
// Vtable should be in executable memory range (.text/.rdata sections)
114+
// GTA SA base is around 0x400000-0x900000 range
115+
constexpr DWORD GTA_BASE_MIN = 0x400000;
116+
constexpr DWORD GTA_BASE_MAX = 0x900000;
117+
DWORD dwVTableAddr = reinterpret_cast<DWORD>(pVTBL);
118+
if (dwVTableAddr < GTA_BASE_MIN || dwVTableAddr > GTA_BASE_MAX)
119+
return nullptr;
107120

108121
// Find it in our list
109122
STaskListItem* pListItem;
@@ -122,7 +135,7 @@ CTaskSA* CTaskManagementSystemSA::GetTask(CTaskSAInterface* pTaskInterface)
122135
// its not existed before, lets create the task
123136
// First, we create a temp task
124137
int iTaskType = 9999;
125-
DWORD dwFunc = pTaskInterface->VTBL->GetTaskType;
138+
DWORD dwFunc = pVTBL->GetTaskType;
126139
if (dwFunc && dwFunc != 0x82263A)
127140
{
128141
__asm

0 commit comments

Comments
 (0)