Skip to content

Commit 07b774a

Browse files
authored
Merge branch 'master' into setCollide
2 parents 2e9d201 + 3df252e commit 07b774a

File tree

16 files changed

+1517
-121
lines changed

16 files changed

+1517
-121
lines changed

Client/core/CCrashDumpWriter.cpp

Lines changed: 374 additions & 2 deletions
Large diffs are not rendered by default.

Client/core/CrashHandler.cpp

Lines changed: 437 additions & 97 deletions
Large diffs are not rendered by default.

Client/core/Graphics/CPixelsManager.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@
1111
#include "StdInc.h"
1212
#include "CFileFormat.h"
1313
#include "CPixelsManager.h"
14+
#include <CrashTelemetry.h>
15+
16+
namespace
17+
{
18+
const char* PixelsFormatToString(EPixelsFormatType format)
19+
{
20+
switch (format)
21+
{
22+
case EPixelsFormat::PLAIN:
23+
return "plain";
24+
case EPixelsFormat::JPEG:
25+
return "jpeg";
26+
case EPixelsFormat::PNG:
27+
return "png";
28+
case EPixelsFormat::DDS:
29+
return "dds";
30+
case EPixelsFormat::UNKNOWN:
31+
default:
32+
return "unknown";
33+
}
34+
}
35+
}
1436

1537
///////////////////////////////////////////////////////////////
1638
// Object creation
@@ -750,6 +772,22 @@ bool CPixelsManager::ChangePixelsFormat(const CPixels& oldPixels, CPixels& newPi
750772
if (oldFormat == EPixelsFormat::UNKNOWN || newFormat == EPixelsFormat::UNKNOWN)
751773
return false;
752774

775+
const uint sourceBytes = oldPixels.GetSize();
776+
uint width = 0;
777+
uint height = 0;
778+
GetPixelsSize(oldPixels, width, height);
779+
780+
SString telemetryDetail;
781+
telemetryDetail.Format("%s->%s q=%d bytes=%u dims=%ux%u",
782+
PixelsFormatToString(oldFormat),
783+
PixelsFormatToString(newFormat),
784+
uiQuality,
785+
sourceBytes,
786+
width,
787+
height);
788+
// Tag conversions here so crashes show which pixel formats/dimensions were being processed.
789+
CrashTelemetry::Scope conversionScope(sourceBytes, oldPixels.GetData(), "Pixels::ChangeFormat", telemetryDetail.c_str());
790+
753791
if (oldFormat == newFormat)
754792
{
755793
// No change

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: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,16 @@ void CStreamingSA::SetStreamingInfo(uint modelid, unsigned char usStreamID, uint
340340
// In this case, we must force-remove the RwObject from memory, because it is no longer used,
341341
// and due to the archive change the streamer no longer detects it and therefore won't delete it.
342342
// 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);
343+
const auto baseTxdId = g_pCore->GetGame()->GetBaseIDforTXD();
344+
if (modelid < static_cast<uint>(baseTxdId))
345+
{
346+
if (CModelInfo* modelInfo = g_pCore->GetGame()->GetModelInfo(modelid, true))
347+
{
348+
// Only DFF models got RwObjects, TXDs don't, so we only flush those here (or crash)
349+
if (modelInfo->GetRwObject())
350+
RemoveModel(modelid);
351+
}
352+
}
345353

346354
// Change nextInImg field for prev model
347355
for (CStreamingInfo& info : ms_aInfoForModel)

Client/mods/deathmatch/StdInc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#define MTA_CLIENT
55
#define SHARED_UTIL_WITH_FAST_HASH_MAP
66
#include "SharedUtil.h"
7+
#include <CrashTelemetry.h>
78

89
#include <string.h>
910
#include <stdio.h>

Client/mods/deathmatch/logic/CClientEntity.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,14 @@ bool CClientEntity::CallEvent(const char* szName, const CLuaArguments& Arguments
747747
if (!g_pClientGame->GetDebugHookManager()->OnPreEvent(szName, Arguments, this, NULL))
748748
return false;
749749

750+
const SString& thisTypeName = GetTypeName();
751+
const char* thisTypeNameCStr = !thisTypeName.empty() ? thisTypeName.c_str() : "<unknown>";
752+
const ElementID thisId = GetID();
753+
SString telemetryDetail;
754+
telemetryDetail.Format("%s %s(%u)", szName, thisTypeNameCStr, thisId.Value());
755+
// Capture the element+event context so any crash (even core.dll faults) reports the last event being dispatched.
756+
CrashTelemetry::Scope entityScope(0, this, "Entity::CallEvent", telemetryDetail.c_str());
757+
750758
TIMEUS startTime = GetTimeUs();
751759

752760
CEvents* pEvents = g_pClientGame->GetEvents();

Client/mods/deathmatch/logic/CMapEventManager.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,32 @@ bool CMapEventManager::Call(const char* szName, const CLuaArguments& Arguments,
187187
if (!g_pClientGame->GetDebugHookManager()->OnPreEventFunction(szName, Arguments, pSource, nullptr, pMapEvent))
188188
continue;
189189

190+
const char* scriptName = pMapEvent->GetVM()->GetScriptName();
191+
CResource* pEventResource = pMapEvent->GetVM()->GetResource();
192+
const char* resourceName = pEventResource ? pEventResource->GetName() : "<no-resource>";
193+
194+
SString sourceTag = "<null>";
195+
if (pSource)
196+
{
197+
const SString& sourceTypeName = pSource->GetTypeName();
198+
const char* sourceTypeNameCStr = !sourceTypeName.empty() ? sourceTypeName.c_str() : "<unknown>";
199+
sourceTag.Format("%s(%u)", sourceTypeNameCStr, pSource->GetID().Value());
200+
}
201+
202+
SString thisTag = "<null>";
203+
if (pThis)
204+
{
205+
const SString& thisTypeName = pThis->GetTypeName();
206+
const char* thisTypeNameCStr = !thisTypeName.empty() ? thisTypeName.c_str() : "<unknown>";
207+
thisTag.Format("%s(%u)", thisTypeNameCStr, pThis->GetID().Value());
208+
}
209+
210+
SString telemetryDetail;
211+
telemetryDetail.Format("%s::%s src=%s this=%s", scriptName, resourceName, sourceTag.c_str(), thisTag.c_str());
212+
// Tag each Lua event dispatch so crash dumps show which
213+
// resource/event/element executed last
214+
CrashTelemetry::Scope eventScope(0, pThis, "LuaEvent::Call", telemetryDetail.c_str());
215+
190216
// Store the current values of the globals
191217
lua_getglobal(pState, "source");
192218
CLuaArgument OldSource(pState, -1);

Client/mods/deathmatch/logic/lua/CLuaArguments.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ bool CLuaArguments::Call(CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction
202202
assert(pLuaMain);
203203
TIMEUS startTime = GetTimeUs();
204204

205+
const SString& functionTag = pLuaMain->GetFunctionTag(iLuaFunction.ToInt());
206+
CrashTelemetry::Scope telemetryScope(0, pLuaMain, "Lua::Call", functionTag.c_str());
207+
205208
// Add the function name to the stack and get the event from the table
206209
lua_State* luaVM = pLuaMain->GetVirtualMachine();
207210
assert(luaVM);
@@ -244,7 +247,7 @@ bool CLuaArguments::Call(CLuaMain* pLuaMain, const CLuaFunctionRef& iLuaFunction
244247
lua_pop(luaVM, 1);
245248
}
246249

247-
CClientPerfStatLuaTiming::GetSingleton()->UpdateLuaTiming(pLuaMain, pLuaMain->GetFunctionTag(iLuaFunction.ToInt()), GetTimeUs() - startTime);
250+
CClientPerfStatLuaTiming::GetSingleton()->UpdateLuaTiming(pLuaMain, functionTag, GetTimeUs() - startTime);
248251
return true;
249252
}
250253

@@ -254,6 +257,8 @@ bool CLuaArguments::CallGlobal(CLuaMain* pLuaMain, const char* szFunction, CLuaA
254257
assert(szFunction);
255258
TIMEUS startTime = GetTimeUs();
256259

260+
CrashTelemetry::Scope telemetryScope(0, pLuaMain, "Lua::CallGlobal", szFunction);
261+
257262
// Add the function name to the stack and get the event from the table
258263
lua_State* luaVM = pLuaMain->GetVirtualMachine();
259264
assert(luaVM);

0 commit comments

Comments
 (0)