Skip to content

Fix crashes around imported and freed TXDs - #5478

Open
Flashmyname wants to merge 10 commits into
multitheftauto:masterfrom
Flashmyname:fix/dam-atomic-txd-ref
Open

Flashmyname wants to merge 10 commits into
multitheftauto:masterfrom
Flashmyname:fix/dam-atomic-txd-ref

Conversation

@Flashmyname

@Flashmyname Flashmyname commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A TXD could be destroyed or freed while engineImportTXD textures were still in it, and a TXD lookup could unload a model that was still in use. Both crashed the client in several places.

  • _dam atomics released a TXD ref that SetDamagedAtomic never takes.
  • engineSetModelTXDID moved MTA's own model refs as TXD refs.
  • Freeing a TXD slot left the imported textures in it.
  • The models of a freed slot went to TXD 0, a clothes builder slot GTA unloads regardless of refs, and a model whose stored TXD was the freed slot crashed on engineResetModelTXDID.
  • A slot freed while its TXD was still requested stayed in the streaming list.
  • A CJ clothes rebuild destroys textures imported into model 0, so importing into model 0 now returns false.
  • engineImportTXD right after engineSetModelTXDID on a streamed in vehicle crashed: to load the new TXD it unloaded the model the vehicle was still using. Same after engineFreeTXD puts a model back on its own TXD.
  • engineGetModelTextureNames and engineGetModelTextures did the same.

Motivation

Fixes #4028. Follows #5356, #4043 and #5042.

I also have fixes ready for the usedInModelIds issue Copilot pointed out and for a model ref leak. Both build on this PR, so I'll open them once this one is merged.

Test plan

txd4028.zip

Start the txd4028 resource and run the commands in one session, /rtbq first. On master reconnect after each crash.

Command Master With this fix
/damtxd last barrier turns grey stays green
/rtbv infernus loses its magenta wheels keeps them
/rta crash at 0x731A4E in ~CClientTXD no crash
/rtastop, then quit crash on quit in ~CClientTXD no crash
/rtbs0 bin has no textures textured
/rtbrf crash at 0x731A15 no crash
/rtbq crash at 0x1566544 no crash
/rtbcj, then /rtbcjw crash at 0x5A6C71 no crash
/rtg1 crash at 0x5344E3 no crash

The dump faults one step earlier, at RwTexDictionaryRemoveTexture+0x6, because the dead texture's memory was already released there. With it made unreadable, master gives the dump's stack on quit and the fix exits cleanly.

Steps
crun i=engineLoadIMG(":txd4028/t.img") engineAddImage(i) t=engineLoadTXD(":txd4028/infernus_m.txd") p=engineLoadTXD(":txd4028/infernus_m.txd") S={} M={} return t~=nil and p~=nil
crun for k=1,300 do M[k]=engineRequestModel("object",1337) S[k]=engineRequestTXD("q"..k) engineImageLinkTXD(i,"t.txd",S[k]) engineSetModelTXDID(M[k],S[k]) engineGetModelTextureNames(M[k]) end return #S
crun for k=301,600 do M[k]=engineRequestModel("object",1337) S[k]=engineRequestTXD("q"..k) engineImageLinkTXD(i,"t.txd",S[k]) engineSetModelTXDID(M[k],S[k]) engineGetModelTextureNames(M[k]) end return #S
crun for k=1,40 do engineImportTXD(p,M[k]) end for k=41,600 do engineImportTXD(t,M[k]) end return "imported"
crun for k=1,600 do engineFreeTXD(S[k]) end return "freed"

Then make the freed texture blocks unreadable (VirtualProtectEx, PAGE_NOACCESS; on the fix build skip each block's first and last page) and quit.

The texture lookups need no resource, just runcode. Master crashes at 0x5344E3, with the fix it prints 0 and a table.

crun local x,y,z=getElementPosition(localPlayer) createVehicle(411,x+5,y,z+1) setTimer(function() engineSetModelTXDID(411,engineRequestTXD("t")) outputChatBox(#engineGetModelTextureNames("411")) end,1000,1)
crun local x,y,z=getElementPosition(localPlayer) createVehicle(411,x+5,y,z+1) setTimer(function() engineSetModelTXDID(411,engineRequestTXD("t")) outputChatBox(tostring(engineGetModelTextures(411))) end,1000,1)

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:22
@Flashmyname Flashmyname changed the title Fix _dam models unloading their TXD while still in use Fix crashes around imported and freed TXDs Oct 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unsigned reference-counter underflow and stale model bookkeeping can still cause incorrect TXD releases and prevent re-imports.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Fixes TXD lifetime and reference accounting to prevent crashes when imported textures or custom TXD slots are released.

Changes:

  • Tracks TXD references taken while replacing atomics.
  • Cleans replacement textures before destroying custom TXDs.
  • Restores model TXD mappings and distinguishes internal model references.

Commit note: Include the motivation, reference-lifetime reasoning, and manual test results in the commit message.

File Description
Client/​sdk/​game/​CRenderWare.h Exposes TXD cleanup API.
Client/​mods/​deathmatch/​logic/​CClientTXD.cpp Excludes model 0 from ped TXD imports.
Client/​mods/​deathmatch/​logic/​CClientModel.cpp Cleans and restores models using freed TXDs.
Client/​game_sa/​CTxdPoolSA.cpp Cleans streaming/default state before slot release.
Client/​game_sa/​CRenderWareSA.TextureReplacing.cpp Detaches replacement textures from destroyed TXDs.
Client/​game_sa/​CRenderWareSA.h Declares TXD cleanup implementation.
Client/​game_sa/​CRenderWareSA.cpp Releases only TXD refs actually acquired.
Client/​game_sa/​CModelInfoSA.h Adds internal-reference tracking and cleanup API.
Client/​game_sa/​CModelInfoSA.cpp Adjusts TXD transfers for internally held refs.
Client/​game_sa/​CFileLoaderSA.h Replaces atomic-consumption state with reference state.
Client/​game_sa/​CFileLoaderSA.cpp Records when SetAtomic takes a TXD ref.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Client/game_sa/CModelInfoSA.cpp
Comment thread Client/game_sa/CRenderWareSA.TextureReplacing.cpp
@Flashmyname
Flashmyname force-pushed the fix/dam-atomic-txd-ref branch from 14ef75d to d6800fe Compare October 1, 2026 10:54
@Flashmyname

Flashmyname commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Copilot's wrong on the first one, the wrap is intended and the count stays exact. Changed it to int16 anyway so it doesn't look like a bug.

usedInModelIds isn't a crash and master does the same.

@Flashmyname
Flashmyname marked this pull request as draft October 1, 2026 13:50
@Flashmyname
Flashmyname marked this pull request as ready for review October 1, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hard-hitting crash in CRenderWareSA::RwTexDictionaryRemoveTexture

2 participants