diff --git a/code/gamesnd/gamesnd.cpp b/code/gamesnd/gamesnd.cpp index cc3320a20a..ca8f981bd6 100644 --- a/code/gamesnd/gamesnd.cpp +++ b/code/gamesnd/gamesnd.cpp @@ -601,6 +601,7 @@ void gamesnd_unload_gameplay_sounds() } } } + snd_unload_cleanup(); } /** @@ -636,6 +637,7 @@ void gamesnd_unload_interface_sounds() } } } + snd_unload_cleanup(); } void parse_gamesnd_old(game_snd* gs) diff --git a/code/mission/missionmessage.cpp b/code/mission/missionmessage.cpp index 70892eb0de..700f2ce484 100644 --- a/code/mission/missionmessage.cpp +++ b/code/mission/missionmessage.cpp @@ -1007,6 +1007,7 @@ void message_mission_shutdown() snd_unload( Message_waves[i].num ); } } + snd_unload_cleanup(); fsspeech_stop(); diff --git a/code/scripting/api/objs/sound.cpp b/code/scripting/api/objs/sound.cpp index b30510079a..fd13f9115a 100644 --- a/code/scripting/api/objs/sound.cpp +++ b/code/scripting/api/objs/sound.cpp @@ -481,6 +481,7 @@ ADE_FUNC(unload, l_Soundfile, nullptr, return ade_set_error(L, "b", false); auto result = snd_unload(handle->idx); + snd_unload_cleanup(); if (result != 0) { // Invalidate this handle so that the script cannot do something bad with it diff --git a/code/sound/sound.cpp b/code/sound/sound.cpp index 017e4f3b69..ae48ab9c81 100644 --- a/code/sound/sound.cpp +++ b/code/sound/sound.cpp @@ -481,6 +481,8 @@ sound_load_id snd_load(game_snd_entry* entry, int *flags, int /*allow_hardware_l return sound_load_id(static_cast(n)); } +static SCP_set Unloaded_sound_ids; + // --------------------------------------------------------------------------------------- // snd_unload() // @@ -504,6 +506,8 @@ int snd_unload(sound_load_id n) ds_unload_buffer(snd.sid); + Unloaded_sound_ids.emplace(n); + if (snd.sid != -1) { Snd_sram -= snd.uncompressed_size; } @@ -530,6 +534,29 @@ void snd_unload_all() while ( !Sounds.empty() ) { snd_unload(sound_load_id((int)(Sounds.size() - 1))); } + snd_unload_cleanup(); +} + +// If two tabled sounds share a sound file, unloading one will not correctly invalidate the index of the other, +// causing a CTD when trying to play the other sound. +// To fix this, every time we unload ANY sound, we must iterate over all tabled sounds, and invalidate their indices +// if the given sound ID has been unloaded. +void snd_unload_cleanup() { + for (auto& snd : Snds) { + for (auto& entry : snd.sound_entries) { + if (Unloaded_sound_ids.contains(entry.id)) { + entry.id = sound_load_id::invalid(); + } + } + } + for (auto& snd : Snds_iface) { + for (auto& entry : snd.sound_entries) { + if (Unloaded_sound_ids.contains(entry.id)) { + entry.id = sound_load_id::invalid(); + } + } + } + Unloaded_sound_ids.clear(); } // --------------------------------------------------------------------------------------- diff --git a/code/sound/sound.h b/code/sound/sound.h index 18712092bd..567908e0d8 100644 --- a/code/sound/sound.h +++ b/code/sound/sound.h @@ -136,6 +136,7 @@ sound_load_id snd_load(game_snd_entry* entry, int* flags, int allow_hardware_loa int snd_unload(sound_load_id sndnum); void snd_unload_all(); +void snd_unload_cleanup(); // Plays a sound with volume between 0 and 1.0, where 0 is the // inaudible and 1.0 is the loudest sound in the game.