-
Notifications
You must be signed in to change notification settings - Fork 9
Script TXD Issues
This document summarizes the current runtime behavior for texture dictionaries (TXDs) and sprite handling.
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
LOAD_SPRITE 2 {spriteName} "LEFT"
LOAD_SPRITE 3 {spriteName} "UP"
LOAD_SPRITE 4 {spriteName} "RIGHT"
LOAD_SPRITE 13 {spriteName} "UP"
LOAD_SPRITE 14 {spriteName} "DOWN"
LOAD_SPRITE 10 {spriteName} "RIGHT"
LOAD_SPRITE 9 {spriteName} "LEFT"
...
DRAW_SPRITE 1 {offsetLeft} $X {offsetTop} $Y {width} 20@ {height} 19@ {r} 128 {g} 128 {b} 128 {a} 255
...
REMOVE_TEXTURE_DICTIONARY
-
LOAD_TEXTURE_DICTIONARYallocates new TXD object for the script.- previous TXD (if any) is leaked.
-
.cmonly: appends additional record toCTheScripts::MissionCleanUp.- max capacity is 75 records, no bounds checks.
-
LOAD_SPRITEstores texture pointer (shallow copy) in a fixedCTheScripts::ScriptSpritesarray- max capacity is 128 sprites (1-128), no bounds checks.
- Since
CTheScripts::ScriptSpritesis shared, CLEO maintains a copy of the array per script and switches the active copy on script switch.
-
DRAW_SPRITEuses the pointer stored inCTheScripts::ScriptSpritesto render.
-
REMOVE_TEXTURE_DICTIONARYclears the active TXD and sprite slots-
.cmonly: removes all TXD records fromCTheScripts::MissionCleanUp. - it DOES NOT free memory in CLEO4.
-
There is an additional CLEO+ command GET_TEXTURE_FROM_SPRITE which retrieves a pointer to a sprite's underlying texture data and stores that pointer in a variable. The stored value can later be used in rendering calls (for example, DRAW_TEXTURE_PLUS).
Example usage:
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
GET_TEXTURE_FROM_SPRITE 1 0@ // store pointer to sprite 1 in variable 0@
DRAW_TEXTURE_PLUS 0@ ... // render via pointer stored in variable
GET_TEXTURE_FROM_SPRITE performs a shallow copy of the sprite pointer into the destination variable (no ownership transfer)
Use-after-free (UAF): because the pointer is a shallow reference into the active TXD, freeing or removing the TXD invalidates the stored pointer. This creates an easy-to-miss use-after-free when a script caches sprite pointers in variables and later calls REMOVE_TEXTURE_DICTIONARY.
UAF example:
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
GET_TEXTURE_FROM_SPRITE 1 0@
REMOVE_TEXTURE_DICTIONARY // frees/clears TXD and sprite data
DRAW_TEXTURE_PLUS 0@ ... // use-after-free: 0@ now points at freed memory
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
WHITE TRUE
WAIT 0
DRAW_SPRITE 1 ...
END
REMOVE_TEXTURE_DICTIONARY
✅ valid render — TXD and sprite present. Texture memory is freed via REMOVE_TEXTURE_DICTIONARY (in CLEO 5.1)
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
REMOVE_TEXTURE_DICTIONARY
DRAW_SPRITE 1 ...
DRAW_SPRITE references freed memory → garbage rendering. ✅ Does not happen in CLEO 4 since REMOVE_TEXTURE_DICTIONARY does not free memory.
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_SPRITE 1 {spriteName} "DOWN"
GET_TEXTURE_FROM_SPRITE 1 0@
REMOVE_TEXTURE_DICTIONARY // frees/clears TXD and sprite data
DRAW_TEXTURE_PLUS 0@ ... // use-after-free: 0@ now points at freed memory
DRAW_TEXTURE_PLUS references freed memory → garbage rendering. ✅ Does not happen in CLEO 4 since REMOVE_TEXTURE_DICTIONARY does not free memory.
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
LOAD_TEXTURE_DICTIONARY 'LD_BEAT1' // leaks LD_BEAT
LOAD_TEXTURE_DICTIONARY 'LD_BEAT2' // leaks LD_BEAT1
WHILE TRUE
WAIT 0
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
REMOVE_TEXTURE_DICTIONARY
END
LOAD_SPRITE 10000 "DOWN"
WHILE TRUE
WAIT 0
LOAD_TEXTURE_DICTIONARY 'LD_BEAT'
END
CTheScripts::MissionCleanUp which is fixed-size and append-only. If the list is exhausted, the mission has no room for storing more items.
-
LOAD_TEXTURE_DICTIONARYalways allocates a new TXD → previous allocations are leaked if not freed. - Shallow-copy semantics:
LOAD_SPRITE/GET_TEXTURE_FROM_SPRITEare pointers into TXD → freeing the TXD invalidates all copies. - No bounds checks for
CTheScripts::ScriptSpritesindex writes → memory corruption - No duplicate checks for
CTheScripts::MissionCleanUpappends → memory exhaustion. -
REMOVE_TEXTURE_DICTIONARYdoes not free memory in CLEO4 → memory leaks on repeated loads.
LOAD_TEXTURE_DICTIONARY / REMOVE_TEXTURE_DICTIONARY does not modify CTheScripts::MissionCleanUp array for .cm missions - textures unloaded on script/mission end.
Each script maintains its own array of loaded TXDs.
LOAD_TEXTURE_DICTIONARY pushes a new TXD to the array and points global script.txd to it (so that LOAD_SPRITE can work).
REMOVE_TEXTURE_DICTIONARY does not remove the TXD from the array. On script termination, the runtime automatically unloads all TXDs on the array.
- Eliminates double-load leak (no overwrite semantics).
- Guarantees no dangling TXD references when
REMOVE_TEXTURE_DICTIONARYwas never called. - Solves UAF (no frees until script end).
Runtime should validate duplicate loads: if the requested TXD is already on the list of loaded TXDs, skip the load.
Validate sprite indices: reject LOAD_SPRITE if index < 1 or > 128 and return an error. Validating upper bound can be problematic if array size was increased.
- no behavior difference for .cs and .cs4
- legacy scripts that rely on
REMOVE_TEXTURE_DICTIONARYto not free memory will work - allows scripts to load and use multiple TXDs safely
- prevents memory leaks, corruption, and use-after-free bugs.