From e8757c33e3336c6663889b585782edefccc09215 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 21 Sep 2026 19:48:28 -0500 Subject: [PATCH] Fix U_IDLE asset error from cutscene item pointer aliasing cutsceneCutsceneResolve and cutsceneSystemLoad could be called with a name/path pointer that aliases into CUTSCENE_SYSTEM.loadedItems (e.g. a CUTSCENE item's own name field). cutsceneLoadParse then overwrites that same buffer while loading the target cutscene, so the subsequent stringCopy into loadedFile read stale, corrupted memory. Copy the name/path into a local buffer before use in both functions. --- src/dusk/rpg/cutscene/cutscenesystem.c | 15 +++++++++++++-- src/dusk/rpg/cutscene/item/cutsceneitem.c | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/dusk/rpg/cutscene/cutscenesystem.c b/src/dusk/rpg/cutscene/cutscenesystem.c index 415e917d..844d4f69 100644 --- a/src/dusk/rpg/cutscene/cutscenesystem.c +++ b/src/dusk/rpg/cutscene/cutscenesystem.c @@ -128,8 +128,19 @@ void cutsceneSystemInsertCutscene(const cutscene_t *cutscene) { void cutsceneSystemLoad(const char_t *file) { assertNotNull(file, "File cannot be NULL"); + // Copy file out before it can be invalidated - every caller today + // passes a string literal, but file could in principle point into + // CUTSCENE_SYSTEM.loadedItems (a running item's own name field, if the + // running cutscene is loadedScene), which both cutsceneLoadParse and + // cutsceneSystemStartCutscene below overwrite/re-enter (see + // cutsceneCutsceneResolve's matching comment for the exact bug this + // guards against - initial.jsonc's closing CUTSCENE item into + // main_menu.jsonc hit it there). + char_t fileCopy[ASSET_FILE_NAME_MAX]; + stringCopy(fileCopy, file, ASSET_FILE_NAME_MAX - 1); + errorret_t result = cutsceneLoadParse( - file, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, + fileCopy, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, CUTSCENE_LOADED_ITEMS_MAX ); if(errorIsNotOk(result)) { @@ -139,7 +150,7 @@ void cutsceneSystemLoad(const char_t *file) { } cutsceneSystemStartCutscene(&CUTSCENE_SYSTEM.loadedScene); - stringCopy(CUTSCENE_SYSTEM.loadedFile, file, ASSET_FILE_NAME_MAX - 1); + stringCopy(CUTSCENE_SYSTEM.loadedFile, fileCopy, ASSET_FILE_NAME_MAX - 1); } void cutsceneRestart(void) { diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 53b39fa3..b6f44ad7 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -430,8 +430,19 @@ bool_t cutsceneCutsceneUpdate( } cutscene_t * cutsceneCutsceneResolve(const char_t *name) { + // Copy name out before it can be invalidated: this is commonly called + // with a pointer into CUTSCENE_SYSTEM.loadedItems itself (a running + // CUTSCENE/INSERT item's own cutsceneRef.name/insert.name field, when + // the running cutscene IS loadedScene, e.g. initial.jsonc's closing + // CUTSCENE item into main_menu.jsonc) - cutsceneLoadParse below parses + // straight into that same shared loadedItems buffer, overwriting the + // very memory name points into with the target cutscene's own first + // item before this function is done reading name. + char_t nameCopy[ASSET_FILE_NAME_MAX]; + stringCopy(nameCopy, name, ASSET_FILE_NAME_MAX - 1); + errorret_t result = cutsceneLoadParse( - name, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, + nameCopy, &CUTSCENE_SYSTEM.loadedScene, CUTSCENE_SYSTEM.loadedItems, CUTSCENE_LOADED_ITEMS_MAX ); if(errorIsNotOk(result)) { @@ -444,7 +455,7 @@ cutscene_t * cutsceneCutsceneResolve(const char_t *name) { // source the same way it does - cutsceneGoTo needs this to reload the // untouched original when jumping to a marker behind the current // position (see cutsceneGoTo's doc comment). - stringCopy(CUTSCENE_SYSTEM.loadedFile, name, ASSET_FILE_NAME_MAX - 1); + stringCopy(CUTSCENE_SYSTEM.loadedFile, nameCopy, ASSET_FILE_NAME_MAX - 1); return &CUTSCENE_SYSTEM.loadedScene; }