|
12 | 12 | #include "gamesa_renderware.h" |
13 | 13 |
|
14 | 14 | #include <map> |
| 15 | +#include <sstream> |
15 | 16 | #include <unordered_map> |
16 | 17 | #include <unordered_set> |
17 | 18 | #include <utility> |
@@ -312,9 +313,16 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
312 | 313 | continue; |
313 | 314 |
|
314 | 315 | RwTexture* pOriginalTexture = (idx < perTxdInfo.replacedOriginals.size()) ? perTxdInfo.replacedOriginals[idx] : nullptr; |
| 316 | + |
| 317 | + if (pOriginalTexture && !SharedUtil::IsReadablePointer(pOriginalTexture, sizeof(RwTexture))) |
| 318 | + pOriginalTexture = nullptr; |
| 319 | + |
315 | 320 | swapMap[pOldTexture] = pOriginalTexture; |
316 | 321 |
|
317 | | - if (pOriginalTexture && !RwTexDictionaryContainsTexture(pInfo->pTxd, pOriginalTexture)) |
| 322 | + // Only restore original textures early if this is the last replacement set for this TXD |
| 323 | + // Otherwise, other replacement sets might still be using the TXD |
| 324 | + bool isLastReplacement = (pInfo->usedByReplacements.size() == 1); |
| 325 | + if (pOriginalTexture && isLastReplacement && !RwTexDictionaryContainsTexture(pInfo->pTxd, pOriginalTexture)) |
318 | 326 | RwTexDictionaryAddTexture(pInfo->pTxd, pOriginalTexture); |
319 | 327 | } |
320 | 328 |
|
@@ -349,6 +357,9 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
349 | 357 | { |
350 | 358 | if (!pOldTexture) |
351 | 359 | continue; |
| 360 | + |
| 361 | + if (!SharedUtil::IsReadablePointer(pOldTexture, sizeof(RwTexture))) |
| 362 | + continue; |
352 | 363 |
|
353 | 364 | RwTexDictionaryRemoveTexture(pInfo->pTxd, pOldTexture); |
354 | 365 | dassert(!RwTexDictionaryContainsTexture(pInfo->pTxd, pOldTexture)); |
@@ -392,18 +403,85 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
392 | 403 | #ifdef MTA_DEBUG |
393 | 404 | std::vector<RwTexture*> currentTextures; |
394 | 405 | GetTxdTextures(currentTextures, pInfo->pTxd); |
395 | | - assert(currentTextures.size() == pInfo->originalTextures.size()); |
396 | | - for (RwTexture* pOriginalTexture : pInfo->originalTextures) |
| 406 | + |
| 407 | + auto formatTextures = [](const std::vector<RwTexture*>& textures) -> std::string { |
| 408 | + std::ostringstream result; |
| 409 | + for (size_t i = 0; i < textures.size(); ++i) |
| 410 | + { |
| 411 | + const auto* pTex = textures[i]; |
| 412 | + const bool isValid = pTex && SharedUtil::IsReadablePointer(pTex, sizeof(RwTexture)); |
| 413 | + const bool isLast = (i == textures.size() - 1); |
| 414 | + |
| 415 | + if (isValid) |
| 416 | + result << pTex->name << "[0x" << std::hex << pTex << std::dec << "]"; |
| 417 | + else |
| 418 | + result << "INVALID[0x" << std::hex << pTex << std::dec << "]"; |
| 419 | + |
| 420 | + if (!isLast) |
| 421 | + result << ", "; |
| 422 | + } |
| 423 | + return result.str(); |
| 424 | + }; |
| 425 | + |
| 426 | + // Allow size mismatch in case texture removal was skipped due to invalid pointers |
| 427 | + if (currentTextures.size() != pInfo->originalTextures.size()) |
397 | 428 | { |
398 | | - if (!pOriginalTexture) |
399 | | - continue; |
400 | | - assert(ListContains(currentTextures, pOriginalTexture)); |
401 | | - ListRemove(currentTextures, pOriginalTexture); |
| 429 | + std::ostringstream debugMsg; |
| 430 | + debugMsg << "TXD " << pInfo->usTxdId << ": texture count mismatch (current=" |
| 431 | + << currentTextures.size() << ", expected=" << pInfo->originalTextures.size() << ")\n"; |
| 432 | + debugMsg << " Current textures: " << formatTextures(currentTextures); |
| 433 | + debugMsg << "\n Expected textures: " << formatTextures(pInfo->originalTextures); |
| 434 | + debugMsg << "\n"; |
| 435 | + OutputDebugString(debugMsg.str().c_str()); |
| 436 | + } |
| 437 | + else |
| 438 | + { |
| 439 | + // First pass: validate all original textures are present |
| 440 | + for (const auto* pOriginalTexture : pInfo->originalTextures) |
| 441 | + { |
| 442 | + if (!pOriginalTexture) |
| 443 | + continue; |
| 444 | + if (!ListContains(currentTextures, pOriginalTexture)) |
| 445 | + { |
| 446 | + const char* texName = SharedUtil::IsReadablePointer(pOriginalTexture, sizeof(RwTexture)) |
| 447 | + ? pOriginalTexture->name : "INVALID"; |
| 448 | + std::ostringstream oss; |
| 449 | + oss << "Original texture not found in TXD " << pInfo->usTxdId |
| 450 | + << " - texture '" << texName << "' [0x" << std::hex << pOriginalTexture << std::dec |
| 451 | + << "] was removed or replaced unexpectedly"; |
| 452 | + const std::string assertMsg = oss.str(); |
| 453 | + assert(false && assertMsg.c_str()); |
| 454 | + } |
| 455 | + } |
| 456 | + |
| 457 | + // Second pass: remove original textures from current list to find extras |
| 458 | + for (auto* pOriginalTexture : pInfo->originalTextures) |
| 459 | + { |
| 460 | + if (pOriginalTexture) |
| 461 | + ListRemove(currentTextures, pOriginalTexture); |
| 462 | + } |
| 463 | + |
| 464 | + // Check for leaked textures |
| 465 | + if (!currentTextures.empty()) |
| 466 | + { |
| 467 | + std::ostringstream oss; |
| 468 | + oss << "Extra textures remain in TXD " << pInfo->usTxdId |
| 469 | + << " after removing all originals - indicates texture leak. Remaining: " |
| 470 | + << formatTextures(currentTextures); |
| 471 | + const std::string assertMsg = oss.str(); |
| 472 | + assert(false && assertMsg.c_str()); |
| 473 | + } |
402 | 474 | } |
403 | | - assert(currentTextures.empty()); |
404 | 475 |
|
405 | | - int32_t refsCount = CTxdStore_GetNumRefs(pInfo->usTxdId); |
406 | | - assert(refsCount > 0, "Should have at least one TXD reference here"); |
| 476 | + const int32_t refsCount = CTxdStore_GetNumRefs(pInfo->usTxdId); |
| 477 | + if (refsCount <= 0) |
| 478 | + { |
| 479 | + std::ostringstream oss; |
| 480 | + oss << "TXD " << pInfo->usTxdId << " has invalid ref count " |
| 481 | + << refsCount << " - should be > 0 before cleanup"; |
| 482 | + const std::string assertMsg = oss.str(); |
| 483 | + assert(false && assertMsg.c_str()); |
| 484 | + } |
407 | 485 | #endif |
408 | 486 | // Clear original textures to prevent dangling pointers after TXD ref removal |
409 | 487 | // The textures themselves are owned by the TXD and will be cleaned up when ref count hits zero |
|
0 commit comments