|
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> |
@@ -309,9 +310,16 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
309 | 310 | continue; |
310 | 311 |
|
311 | 312 | RwTexture* pOriginalTexture = (idx < perTxdInfo.replacedOriginals.size()) ? perTxdInfo.replacedOriginals[idx] : nullptr; |
| 313 | + |
| 314 | + if (pOriginalTexture && !SharedUtil::IsReadablePointer(pOriginalTexture, sizeof(RwTexture))) |
| 315 | + pOriginalTexture = nullptr; |
| 316 | + |
312 | 317 | swapMap[pOldTexture] = pOriginalTexture; |
313 | 318 |
|
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)) |
315 | 323 | RwTexDictionaryAddTexture(pInfo->pTxd, pOriginalTexture); |
316 | 324 | } |
317 | 325 |
|
@@ -346,6 +354,9 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
346 | 354 | { |
347 | 355 | if (!pOldTexture) |
348 | 356 | continue; |
| 357 | + |
| 358 | + if (!SharedUtil::IsReadablePointer(pOldTexture, sizeof(RwTexture))) |
| 359 | + continue; |
349 | 360 |
|
350 | 361 | RwTexDictionaryRemoveTexture(pInfo->pTxd, pOldTexture); |
351 | 362 | dassert(!RwTexDictionaryContainsTexture(pInfo->pTxd, pOldTexture)); |
@@ -389,18 +400,85 @@ void CRenderWareSA::ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacemen |
389 | 400 | #ifdef MTA_DEBUG |
390 | 401 | std::vector<RwTexture*> currentTextures; |
391 | 402 | 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()) |
394 | 425 | { |
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 | + } |
399 | 471 | } |
400 | | - assert(currentTextures.empty()); |
401 | 472 |
|
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 | + } |
404 | 482 | #endif |
405 | 483 | // Clear original textures to prevent dangling pointers after TXD ref removal |
406 | 484 | // The textures themselves are owned by the TXD and will be cleaned up when ref count hits zero |
|
0 commit comments