diff --git a/AGENTS.md b/AGENTS.md index 837278784e8..d82ef7d0d28 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,22 +4,17 @@ GeneralsX is a cross-platform port of Command & Conquer: Generals Zero Hour for **Linux and macOS**, porting legacy Windows DirectX 8 + Miles Sound code to a modern stack (SDL3 + DXVK + OpenAL + 64-bit). This is a **massive C++ game engine** (~500k LOC) preserving retail gameplay while modernizing the platform layer. ## Must-Load Context -<<<<<<< Updated upstream Before starting work, read: - `.github/copilot-instructions.md` – quick reference - `.github/instructions/generalsx.instructions.md` – full architecture - `.github/instructions/git-commit.instructions.md` – commit standards - `.github/instructions/docs.instructions.md` – documentation workflow - `docs/DEV_BLOG/YYYY-MM-DIARY.md` – current development notes -======= -Before work, read: -- `docs/DEV_BLOG/YYYY-MM-DIARY.md` (current month) ## Key Entry Points - `GeneralsMD/Code/Main/WinMain.cpp` - `Generals/Code/Main/WinMain.cpp` - `Core/GameEngineDevice/Source/` ->>>>>>> Stashed changes ## Platform Focus - **Active**: Linux (`linux64-deploy`), macOS (`macos-vulkan`) diff --git a/Core/GameEngine/Include/Common/GameAudio.h b/Core/GameEngine/Include/Common/GameAudio.h index dae9d7cd132..60cb8e214ae 100644 --- a/Core/GameEngine/Include/Common/GameAudio.h +++ b/Core/GameEngine/Include/Common/GameAudio.h @@ -177,11 +177,10 @@ class AudioManager : public SubsystemInterface AsciiString prevTrackName(const AsciiString& currentTrack ); // changing music tracks - virtual void nextMusicTrack() = 0; - virtual void prevMusicTrack() = 0; + virtual AsciiString nextMusicTrack() = 0; + virtual AsciiString prevMusicTrack() = 0; virtual Bool isMusicPlaying() const = 0; virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const = 0; - virtual AsciiString getMusicTrackName() const = 0; virtual void setAudioEventEnabled( AsciiString eventToAffect, Bool enable ); virtual void setAudioEventVolumeOverride( AsciiString eventToAffect, Real newVolume ); diff --git a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp index 7afb66d91d6..db53e049456 100644 --- a/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp +++ b/Core/GameEngine/Source/GameLogic/System/GameLogicDispatch.cpp @@ -281,6 +281,8 @@ void GameLogic::clearGameData( Bool showScoreScreen ) void FixupScoreScreenMovieWindow(); FixupScoreScreenMovieWindow(); + + destroyQuitMenu(); } TheGameEngine->reset(); diff --git a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h index 71707bd3054..2ba3483d23f 100644 --- a/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h +++ b/Core/GameEngineDevice/Include/MilesAudioDevice/MilesAudioManager.h @@ -23,6 +23,7 @@ #include "Common/AsciiString.h" #include "Common/GameAudio.h" #include "mss/mss.h" +#include "mutex.h" class AudioEventRTS; @@ -39,8 +40,8 @@ enum PlayingAudioType CPP_11(: Int) enum PlayingStatus CPP_11(: Int) { PS_Playing, - PS_Stopped, - PS_Paused + PS_Stopping, ///< Is about to be stopped + PS_Stopped, ///< Is about to be released }; enum PlayingWhich CPP_11(: Int) @@ -60,22 +61,26 @@ struct PlayingAudio HSTREAM m_stream; }; - PlayingAudioType m_type; - volatile PlayingStatus m_status; // This member is adjusted by another running thread. AudioEventRTS *m_audioEventRTS; - void *m_file; // The file that was opened to play this - Bool m_requestStop; + void *m_file; // The file that was opened to play this + PlayingAudioType m_type; + volatile PlayingStatus m_status; // This member is adjusted by another running thread. + Short m_framesFaded; + Bool m_fade; Bool m_cleanupAudioEventRTS; - Int m_framesFaded; - - PlayingAudio() : - m_type(PAT_INVALID), - m_audioEventRTS(nullptr), - m_requestStop(false), - m_cleanupAudioEventRTS(true), - m_sample(nullptr), - m_framesFaded(0) - { } + + PlayingAudio() + : m_sample(nullptr) + , m_audioEventRTS(nullptr) + , m_file(nullptr) + , m_type(PAT_INVALID) + , m_status(PS_Playing) + , m_framesFaded(0) + , m_fade(false) + , m_cleanupAudioEventRTS(true) + {} + + static_assert(sizeof(m_status) == sizeof(long), "Must be size of long, because it is used with Interlocked functions"); }; struct ProviderInfo @@ -114,7 +119,7 @@ class AudioFileCache // End Protected by mutex // Note: These functions should be used for informational purposes only. For speed reasons, - // they are not protected by the mutex, so they are not guarenteed to be valid if called from + // they are not protected by the mutex, so they are not guaranteed to be valid if called from // outside the audio cache. They should be used as a rough estimate only. UnsignedInt getCurrentlyUsedSize() const { return m_currentlyUsedSize; } UnsignedInt getMaxSize() const { return m_maxSize; } @@ -150,12 +155,10 @@ class MilesAudioManager : public AudioManager MilesAudioManager(); virtual ~MilesAudioManager() override; - - virtual void nextMusicTrack() override; - virtual void prevMusicTrack() override; + virtual AsciiString nextMusicTrack() override; + virtual AsciiString prevMusicTrack() override; virtual Bool isMusicPlaying() const override; virtual Bool hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const override; - virtual AsciiString getMusicTrackName() const override; virtual void openDevice() override; virtual void closeDevice() override; @@ -172,8 +175,8 @@ class MilesAudioManager : public AudioManager ///< NOTE NOTE NOTE !!DO NOT USE THIS IN FOR GAMELOGIC PURPOSES!! NOTE NOTE NOTE virtual Bool isCurrentlyPlaying( AudioHandle handle ) override; - virtual void notifyOfAudioCompletion( UnsignedInt audioCompleted, UnsignedInt flags ) override; - virtual PlayingAudio *findPlayingAudioFrom( UnsignedInt audioCompleted, UnsignedInt flags ); + virtual void notifyOfAudioCompletion( UnsignedInt handle, UnsignedInt flags ) override; + virtual PlayingAudio *findPlayingAudioFrom( UnsignedInt handle, UnsignedInt flags ); virtual UnsignedInt getProviderCount() const override; virtual AsciiString getProviderName( UnsignedInt providerNum ) const override; @@ -208,7 +211,6 @@ class MilesAudioManager : public AudioManager virtual void processRequestList() override; virtual void processPlayingList(); virtual void processFadingList(); - virtual void processStoppedList(); Bool shouldProcessRequestThisFrame( AudioRequest *req ) const; void adjustRequest( AudioRequest *req ); @@ -243,7 +245,6 @@ class MilesAudioManager : public AudioManager void *playSample( AudioEventRTS *event, HSAMPLE sample ); void *playSample3D( AudioEventRTS *event, H3DSAMPLE sample3D ); - protected: void buildProviderList(); void createListener(); void initDelayFilter(); @@ -261,7 +262,13 @@ class MilesAudioManager : public AudioManager PlayingAudio *allocatePlayingAudio(); void releaseMilesHandles( PlayingAudio *release ); void releasePlayingAudio( PlayingAudio *release ); + void stopPlayingAudio( PlayingAudio *release ); + void fadePlayingAudio( PlayingAudio *playing ); + + PlayingAudio *findActiveMusic( const AsciiString *trackName = nullptr ); + const PlayingAudio *findActiveMusic( const AsciiString* trackName = nullptr ) const; + void releasePlayingAudioInListIfStopped(std::list &list, CriticalSectionClass &cs); void stopAllAudioImmediately(); void freeAllMilesHandles(); @@ -272,7 +279,6 @@ class MilesAudioManager : public AudioManager void stopAllSpeech(); - protected: void initFilters( HSAMPLE sample, AudioEventRTS *eventInfo ); void initFilters3D( H3DSAMPLE sample, AudioEventRTS *eventInfo, const Coord3D *pos ); @@ -298,22 +304,22 @@ class MilesAudioManager : public AudioManager std::list m_availableSamples; std::list m_available3DSamples; - // Currently Playing stuff. Useful if we have to preempt it. + // Currently Playing audio. Useful if we have to preempt it. // This should rarely if ever happen, as we mirror this in Sounds, and attempt to // keep preemption from taking place here. std::list m_playingSounds; std::list m_playing3DSounds; std::list m_playingStreams; - // Currently fading stuff. At this point, we just want to let it finish fading, when it is - // done it should be added to the completed list, then "freed" and the counts should be updated - // on the next update + // Currently fading music. We just let it finish fading, then release it. std::list m_fadingAudio; - // Stuff that is done playing (either because it has finished or because it was killed) - // This stuff should be cleaned up during the next update cycle. This includes updating counts - // in the sound engine - std::list m_stoppedAudio; + // TheSuperHackers @fix Erasing from PlayingAudio lists on main thread is not safe when the MSS Timer thread + // needs to iterate the same lists. The critical sections are used to guard writes that will invalidate iterators. + CriticalSectionClass m_playingSoundsCS; + CriticalSectionClass m_playing3DSoundsCS; + CriticalSectionClass m_playingStreamsCS; + CriticalSectionClass m_fadingAudioCS; AudioFileCache *m_audioCache; PlayingAudio *m_binkHandle; @@ -343,17 +349,16 @@ class MilesAudioManagerDummy : public MilesAudioManager virtual void resumeAudio(AudioAffect which) override {} virtual void pauseAmbient(Bool shouldPause) override {} virtual void killAudioEventImmediately(AudioHandle audioEvent) override {} - virtual void nextMusicTrack() override {} - virtual void prevMusicTrack() override {} + virtual AsciiString nextMusicTrack() override { return AsciiString::TheEmptyString; } + virtual AsciiString prevMusicTrack() override { return AsciiString::TheEmptyString; } virtual Bool isMusicPlaying() const override { return false; } virtual Bool hasMusicTrackCompleted(const AsciiString& trackName, Int numberOfTimes) const override { return false; } - virtual AsciiString getMusicTrackName() const override { return ""; } //virtual void openDevice() override {} //virtual void closeDevice() override {} //virtual void* getDevice() override { return nullptr; } virtual void notifyOfAudioCompletion(UnsignedInt audioCompleted, UnsignedInt flags) override {} virtual UnsignedInt getProviderCount() const override { return 0; }; - virtual AsciiString getProviderName(UnsignedInt providerNum) const override { return ""; } + virtual AsciiString getProviderName(UnsignedInt providerNum) const override { return AsciiString::TheEmptyString; } virtual UnsignedInt getProviderIndex(AsciiString providerName) const override { return 0; } virtual void selectProvider(UnsignedInt providerNdx) override {} virtual void unselectProvider() override {} diff --git a/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h b/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h index 34fd7c61117..95c543ecf96 100644 --- a/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h +++ b/Core/GameEngineDevice/Include/OpenALAudioDevice/OpenALAudioManager.h @@ -81,11 +81,10 @@ friend class OpenALAudioFileCache; OpenALAudioManager(); virtual ~OpenALAudioManager(); - virtual void nextMusicTrack(void); - virtual void prevMusicTrack(void); + virtual AsciiString nextMusicTrack(void); + virtual AsciiString prevMusicTrack(void); virtual Bool isMusicPlaying(void) const; virtual Bool hasMusicTrackCompleted(const AsciiString &trackName, Int numberOfTimes) const; - virtual AsciiString getMusicTrackName(void) const; virtual void openDevice(void); virtual void closeDevice(void); diff --git a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp index 7625b3edfb2..39343fe5810 100644 --- a/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp +++ b/Core/GameEngineDevice/Source/MilesAudioDevice/MilesAudioManager.cpp @@ -70,6 +70,8 @@ #include "Common/file.h" +#include + enum { INFINITE_LOOP_COUNT = 1000000 }; @@ -233,10 +235,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channelCount = TheAudio->getNum2DSamples(); for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (!playing) { - continue; - } - playingArray[(int)AIL_sample_user_data(playing->m_sample, 0)] = playing; } @@ -266,10 +264,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind( '\\' ) + 1; @@ -294,10 +288,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channelCount = TheAudio->getNum3DSamples(); for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (!playing) { - continue; - } - playingArray[(int)AIL_3D_user_data(playing->m_3DSample, 0)] = playing; } @@ -361,10 +351,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; @@ -388,13 +374,9 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL channel = 1; for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (!playing) { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; - // Calculate Sample volume volume = 100.0f; volume *= getEffectiveVolume(playing->m_audioEventRTS); @@ -415,10 +397,6 @@ void MilesAudioManager::audioDebugDisplay(DebugDisplayInterface *dd, void *, FIL for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( !playing ) - { - continue; - } filenameNoSlashes = playing->m_audioEventRTS->getFilename(); filenameNoSlashes = filenameNoSlashes.reverseFind('\\') + 1; @@ -486,7 +464,6 @@ void MilesAudioManager::update() processRequestList(); processPlayingList(); processFadingList(); - processStoppedList(); } //------------------------------------------------------------------------------------------------- @@ -498,49 +475,36 @@ void MilesAudioManager::stopAudio( AudioAffect which ) // 3) Set the status to stopped, so that when we next process the playing list, we will // correctly clean up the sample. - std::list::iterator it; PlayingAudio *playing = nullptr; if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_register_EOS_callback(playing->m_sample, nullptr); - AIL_stop_sample(playing->m_sample); - playing->m_status = PS_Stopped; - } + stopPlayingAudio(playing); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - AIL_stop_3D_sample(playing->m_3DSample); - playing->m_status = PS_Stopped; - } + stopPlayingAudio(playing); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - AIL_register_stream_callback(playing->m_stream, nullptr); - AIL_pause_stream(playing->m_stream, 1); - playing->m_status = PS_Stopped; } + stopPlayingAudio(playing); } } } @@ -554,46 +518,42 @@ void MilesAudioManager::pauseAudio( AudioAffect which ) if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_stop_sample(playing->m_sample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); + AIL_stop_sample(playing->m_sample); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_stop_3D_sample(playing->m_3DSample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("3D Sample is not expected to be null")); + AIL_stop_3D_sample(playing->m_3DSample); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - - AIL_pause_stream(playing->m_stream, 1); } + DEBUG_ASSERTCRASH(playing->m_sample, ("Stream is not expected to be null")); + AIL_pause_stream(playing->m_stream, 1); } } //Get rid of PLAY audio requests when pausing audio. std::list::iterator ait; - for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); /* empty */) + for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ) { AudioRequest *req = (*ait); - if( req && req->m_request == AR_Play ) + if( req->m_request == AR_Play ) { deleteInstance(req); ait = m_audioRequests.erase(ait); @@ -615,36 +575,33 @@ void MilesAudioManager::resumeAudio( AudioAffect which ) if (BitIsSet(which, AudioAffect_Sound)) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_resume_sample(playing->m_sample); - } + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); + AIL_resume_sample(playing->m_sample); } } if (BitIsSet(which, AudioAffect_Sound3D)) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing) { - AIL_resume_3D_sample(playing->m_3DSample); - } + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); + AIL_resume_3D_sample(playing->m_3DSample); } } if (BitIsSet(which, AudioAffect_Speech | AudioAffect_Music)) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing) { - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (!BitIsSet(which, AudioAffect_Music)) { - continue; - } - } else { - if (!BitIsSet(which, AudioAffect_Speech)) { - continue; - } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { + if (!BitIsSet(which, AudioAffect_Music)) { + continue; + } + } else { + if (!BitIsSet(which, AudioAffect_Speech)) { + continue; } - AIL_pause_stream(playing->m_stream, 0); } + DEBUG_ASSERTCRASH(playing->m_stream, ("Stream is not expected to be null")); + AIL_pause_stream(playing->m_stream, 0); } } } @@ -690,15 +647,11 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) if (handleToKill) { for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) { //Release this streaming channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playingStreams.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -723,6 +676,8 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) } AIL_set_stream_volume_pan(stream, getEffectiveVolume(event), 0.5f); playStream(event, stream); + + CriticalSectionClass::LockClass lock(m_playingStreamsCS); m_playingStreams.push_back(audio); audio = nullptr; } @@ -746,15 +701,11 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) { for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill ) { //Release this 3D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playing3DSounds.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -786,7 +737,6 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) audio->m_3DSample = sample3D; audio->m_file = nullptr; audio->m_type = PAT_3DSample; - m_playing3DSounds.push_back(audio); if (sample3D) { audio->m_file = playSample3D(event, sample3D); @@ -795,13 +745,14 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) if( !audio->m_file ) { - m_playing3DSounds.pop_back(); #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Killed (no handles available)")); #endif } else { + CriticalSectionClass::LockClass lock(m_playing3DSoundsCS); + m_playing3DSounds.push_back(audio); audio = nullptr; #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Playing.")); @@ -811,20 +762,16 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) else { // UI sounds are always 2-D. All other sounds should be Positional - // Unit acknowledgement, etc, falls into the UI category of sound. + // Unit acknowledgment, etc, falls into the UI category of sound. Bool foundSoundToReplace = false; if (handleToKill) { for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } if (playing->m_audioEventRTS && playing->m_audioEventRTS->getPlayingHandle() == handleToKill) { //Release this 2D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio(playing); - m_playingSounds.erase(it); + stopPlayingAudio(playing); foundSoundToReplace = true; break; } @@ -857,7 +804,6 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) audio->m_sample = sample; audio->m_file = nullptr; audio->m_type = PAT_Sample; - m_playingSounds.push_back(audio); if (sample) { audio->m_file = playSample(event, sample); @@ -868,14 +814,14 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" Killed (no handles available)")); #endif - m_playingSounds.pop_back(); } else { + CriticalSectionClass::LockClass lock(m_playingSoundsCS); + m_playingSounds.push_back(audio); audio = nullptr; + #ifdef INTENSIVE_AUDIO_DEBUG + DEBUG_LOG((" Playing.")); + #endif } - - #ifdef INTENSIVE_AUDIO_DEBUG - DEBUG_LOG((" Playing.")); - #endif } break; } @@ -884,6 +830,7 @@ void MilesAudioManager::playAudioEvent( AudioEventRTS *event ) // If we were able to successfully play audio, then we set it to null above. (And it will be freed // later. However, if audio is non-null at this point, then it must be freed. if (audio) { + stopPlayingAudio(audio); releasePlayingAudio(audio); } } @@ -897,67 +844,51 @@ void MilesAudioManager::stopAudioEvent( AudioHandle handle ) std::list::iterator it; if ( handle == AHSV_StopTheMusic || handle == AHSV_StopTheMusicFade ) { - // for music, just find the currently playing music stream and kill it. + // for music, find and stop the currently playing music stream(s). for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if( audio->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music ) { if( handle == AHSV_StopTheMusicFade ) { - m_fadingAudio.push_back(audio); + fadePlayingAudio(audio); } else { - //m_stoppedAudio.push_back(audio); - releasePlayingAudio( audio ); + stopPlayingAudio(audio); } - m_playingStreams.erase(it); - break; } } + return; } for ( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { - // found it - audio->m_requestStop = true; - notifyOfAudioCompletion((UnsignedInt)(audio->m_stream), PAT_Stream); + stopPlayingAudio(audio); break; } } for ( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { - audio->m_requestStop = true; + stopPlayingAudio(audio); break; } } for ( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { PlayingAudio *audio = (*it); - if (!audio) { - continue; - } if (audio->m_audioEventRTS->getPlayingHandle() == handle) { #ifdef INTENSIVE_AUDIO_DEBUG DEBUG_LOG((" (%s)", audio->m_audioEventRTS->getEventName())); #endif - audio->m_requestStop = true; + stopPlayingAudio(audio); break; } } @@ -971,7 +902,7 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ait++ ) { AudioRequest *req = (*ait); - if( req && req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) + if( req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent ) { deleteInstance(req); ait = m_audioRequests.erase(ait); @@ -984,15 +915,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playing3DSounds.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1001,15 +927,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playingSounds.begin(); it != m_playingSounds.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playingSounds.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1018,15 +939,10 @@ void MilesAudioManager::killAudioEventImmediately( AudioHandle audioEvent ) for( it = m_playingStreams.begin(); it != m_playingStreams.end(); it++ ) { PlayingAudio *audio = (*it); - if( !audio ) - { - continue; - } if( audio->m_audioEventRTS->getPlayingHandle() == audioEvent ) { - releasePlayingAudio( audio ); - m_playingStreams.erase( it ); + stopPlayingAudio(audio); return; } } @@ -1056,9 +972,7 @@ void MilesAudioManager::closeFile( void *fileRead ) //------------------------------------------------------------------------------------------------- PlayingAudio *MilesAudioManager::allocatePlayingAudio() { - PlayingAudio *aud = NEW PlayingAudio; // poolify - aud->m_status = PS_Playing; - return aud; + return NEW PlayingAudio; // poolify } @@ -1073,6 +987,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) AIL_register_EOS_callback(release->m_sample, nullptr); AIL_stop_sample(release->m_sample); m_availableSamples.push_back(release->m_sample); + release->m_sample = nullptr; } break; } @@ -1082,6 +997,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) AIL_register_3D_EOS_callback(release->m_3DSample, nullptr); AIL_stop_3D_sample(release->m_3DSample); m_available3DSamples.push_back(release->m_3DSample); + release->m_3DSample = nullptr; } break; } @@ -1090,6 +1006,7 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) if (release->m_stream) { AIL_register_stream_callback(release->m_stream, nullptr); AIL_close_stream(release->m_stream); + release->m_stream = nullptr; } break; } @@ -1100,6 +1017,31 @@ void MilesAudioManager::releaseMilesHandles( PlayingAudio *release ) //------------------------------------------------------------------------------------------------- void MilesAudioManager::releasePlayingAudio( PlayingAudio *release ) { + DEBUG_ASSERTCRASH(release->m_status == PS_Stopped, ("PlayingAudio must be stopped by now")); + DEBUG_ASSERTCRASH(release->m_type == PAT_INVALID, ("PlayingAudio must be invalidated by now")); + + if (release->m_file) { + closeFile(release->m_file); + release->m_file = nullptr; + } + + if (release->m_cleanupAudioEventRTS) { + releaseAudioEventRTS(release->m_audioEventRTS); + } + + delete release; +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::stopPlayingAudio( PlayingAudio *release ) +{ + // Advance from Playing to Stopping + InterlockedCompareExchange(reinterpret_cast(&release->m_status), PS_Stopping, PS_Playing); + // Advance from Stopping to Stopped + const long prevStatus = InterlockedCompareExchange(reinterpret_cast(&release->m_status), PS_Stopped, PS_Stopping); + if (prevStatus != PS_Stopping) { + return; + } if (release->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_SoundEffect) { if (release->m_type == PAT_Sample) { if (release->m_sample) { @@ -1111,60 +1053,109 @@ void MilesAudioManager::releasePlayingAudio( PlayingAudio *release ) } } } - releaseMilesHandles(release); // forces stop of this audio - closeFile( release->m_file ); - if (release->m_cleanupAudioEventRTS) { - releaseAudioEventRTS(release->m_audioEventRTS); + releaseMilesHandles(release); +} + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::fadePlayingAudio( PlayingAudio *playing ) +{ + playing->m_fade = true; +} + +//------------------------------------------------------------------------------------------------- +PlayingAudio *MilesAudioManager::findActiveMusic( const AsciiString* trackName ) +{ + std::list::const_iterator it; + PlayingAudio *playing; + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing->m_status != PS_Playing) { + continue; + } + if (playing->m_fade) { + continue; + } + if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType != AT_Music) { + continue; + } + if (trackName && *trackName != playing->m_audioEventRTS->getEventName()) { + continue; + } + return playing; } - delete release; - release = nullptr; + return nullptr; } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::stopAllAudioImmediately() +const PlayingAudio *MilesAudioManager::findActiveMusic( const AsciiString* trackName ) const +{ + return const_cast(this)->findActiveMusic(trackName); +} + + +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::releasePlayingAudioInListIfStopped(std::list &list, CriticalSectionClass &cs) { std::list::iterator it; PlayingAudio *playing; + CriticalSectionClass::LockClass lock(cs); - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { - playing = *it; - if (!playing) { + for (it = list.begin(); it != list.end(); ) + { + playing = (*it); + + if (playing->m_status == PS_Stopped) + { + releasePlayingAudio(playing); + it = list.erase(it); continue; } - releasePlayingAudio(playing); - it = m_playingSounds.erase(it); + ++it; } +} - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { - playing = *it; - if (!playing) { - continue; - } +//------------------------------------------------------------------------------------------------- +void MilesAudioManager::stopAllAudioImmediately() +{ + std::list::iterator it; + PlayingAudio *playing; - releasePlayingAudio(playing); - it = m_playing3DSounds.erase(it); + // + // Stop audio without locking. Calls into Miles. + // + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); } - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } + stopPlayingAudio(playing); + } - releasePlayingAudio(playing); - it = m_playingStreams.erase(it); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); } - for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ) { - playing = (*it); - if (!playing) { - continue; - } + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = (*it); + stopPlayingAudio(playing); + } - releasePlayingAudio(playing); - it = m_fadingAudio.erase(it); - } + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_playingSounds, m_playingSoundsCS); + releasePlayingAudioInListIfStopped(m_playing3DSounds, m_playing3DSoundsCS); + releasePlayingAudioInListIfStopped(m_playingStreams, m_playingStreamsCS); + releasePlayingAudioInListIfStopped(m_fadingAudio, m_fadingAudioCS); + + DEBUG_ASSERTCRASH(m_playingSounds.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_playing3DSounds.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_playingStreams.empty(), ("List is expected to be empty now")); + DEBUG_ASSERTCRASH(m_fadingAudio.empty(), ("List is expected to be empty now")); std::list::iterator hit; for (hit = m_audioForcePlayed.begin(); hit != m_audioForcePlayed.end(); ++hit) { @@ -1185,7 +1176,7 @@ void MilesAudioManager::freeAllMilesHandles() // Walks through the available 2-D and 3-D handles and releases them std::list::iterator it; - for ( it = m_availableSamples.begin(); it != m_availableSamples.end(); /* empty */ ) { + for ( it = m_availableSamples.begin(); it != m_availableSamples.end(); ) { HSAMPLE sample = *it; AIL_release_sample_handle(sample); it = m_availableSamples.erase(it); @@ -1193,7 +1184,7 @@ void MilesAudioManager::freeAllMilesHandles() m_num2DSamples = 0; std::list::iterator it3D; - for ( it3D = m_available3DSamples.begin(); it3D != m_available3DSamples.end(); /* empty */ ) { + for ( it3D = m_available3DSamples.begin(); it3D != m_available3DSamples.end(); ) { H3DSAMPLE sample3D = *it3D; AIL_release_3D_sample_handle(sample3D); it3D = m_available3DSamples.erase(it3D); @@ -1257,17 +1248,10 @@ void MilesAudioManager::stopAllSpeech() { std::list::iterator it; PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) { - continue; - } - if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Streaming) { - releasePlayingAudio(playing); - it = m_playingStreams.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } @@ -1321,16 +1305,12 @@ void MilesAudioManager::initFilters3D( H3DSAMPLE sample, AudioEventRTS *event, c } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::nextMusicTrack() +AsciiString MilesAudioManager::nextMusicTrack() { AsciiString trackName; - std::list::iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - trackName = playing->m_audioEventRTS->getEventName(); - } + + if (PlayingAudio *playing = findActiveMusic()) { + trackName = playing->m_audioEventRTS->getEventName(); } // Stop currently playing music @@ -1339,19 +1319,17 @@ void MilesAudioManager::nextMusicTrack() trackName = nextTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::prevMusicTrack() +AsciiString MilesAudioManager::prevMusicTrack() { AsciiString trackName; - std::list::iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - trackName = playing->m_audioEventRTS->getEventName(); - } + + if (PlayingAudio *playing = findActiveMusic()) { + trackName = playing->m_audioEventRTS->getEventName(); } // Stop currently playing music @@ -1360,73 +1338,27 @@ void MilesAudioManager::prevMusicTrack() trackName = prevTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::isMusicPlaying() const { - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - return TRUE; - } - } - - return FALSE; + return findActiveMusic() != nullptr; } //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::hasMusicTrackCompleted( const AsciiString& trackName, Int numberOfTimes ) const { - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - if (playing->m_audioEventRTS->getEventName() == trackName) { - if (INFINITE_LOOP_COUNT - AIL_stream_loop_count(playing->m_stream) >= numberOfTimes) { - return TRUE; - } - } + if (const PlayingAudio *playing = findActiveMusic(&trackName)) { + if (INFINITE_LOOP_COUNT - AIL_stream_loop_count(playing->m_stream) >= numberOfTimes) { + return TRUE; } } - return FALSE; } -//------------------------------------------------------------------------------------------------- -AsciiString MilesAudioManager::getMusicTrackName() const -{ - // First check the requests. If there's one there, then report that as the currently playing track. - std::list::const_iterator ait; - for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { - if ((*ait)->m_request != AR_Play) { - continue; - } - - if (!(*ait)->m_usePendingEvent) { - continue; - } - - if ((*ait)->m_pendingEvent->getAudioEventInfo()->m_soundType == AT_Music) { - return (*ait)->m_pendingEvent->getEventName(); - } - } - - std::list::const_iterator it; - PlayingAudio *playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { - return playing->m_audioEventRTS->getEventName(); - } - } - - return AsciiString::TheEmptyString; -} - //------------------------------------------------------------------------------------------------- void MilesAudioManager::openDevice() { @@ -1482,21 +1414,21 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getPlayingHandle() == handle) { + if (playing->m_audioEventRTS->getPlayingHandle() == handle) { return true; } } @@ -1506,7 +1438,7 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) AudioRequest *req = nullptr; for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { req = *ait; - if (req && req->m_usePendingEvent && req->m_pendingEvent->getPlayingHandle() == handle) { + if (req->m_usePendingEvent && req->m_pendingEvent->getPlayingHandle() == handle) { return true; } } @@ -1515,9 +1447,9 @@ Bool MilesAudioManager::isCurrentlyPlaying( AudioHandle handle ) } //------------------------------------------------------------------------------------------------- -void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt audioCompleted, UnsignedInt flags ) +void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt handle, UnsignedInt flags ) { - PlayingAudio *playing = findPlayingAudioFrom(audioCompleted, flags); + PlayingAudio *playing = findPlayingAudioFrom(handle, flags); if (!playing) { DEBUG_CRASH(("Audio has completed playing, but we can't seem to find it. - jkmcd")); return; @@ -1568,46 +1500,60 @@ void MilesAudioManager::notifyOfAudioCompletion( UnsignedInt audioCompleted, Uns if (playing->m_type == PAT_Stream) { if (playing->m_audioEventRTS->getAudioEventInfo()->m_soundType == AT_Music) { playStream(playing->m_audioEventRTS, playing->m_stream); - return; } } - playing->m_status = PS_Stopped; // it will be cleaned up on the next frame update + // it will be cleaned up on the next frame update + InterlockedCompareExchange(reinterpret_cast(&playing->m_status), PS_Stopping, PS_Playing); } //------------------------------------------------------------------------------------------------- -PlayingAudio *MilesAudioManager::findPlayingAudioFrom( UnsignedInt audioCompleted, UnsignedInt flags ) +PlayingAudio *MilesAudioManager::findPlayingAudioFrom( UnsignedInt handle, UnsignedInt flags ) { std::list::iterator it; PlayingAudio *playing; if (flags == PAT_Sample) { - HSAMPLE sample = (HSAMPLE) audioCompleted; + HSAMPLE sample = (HSAMPLE) handle; + CriticalSectionClass::LockClass lock(m_playingSoundsCS); for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_sample == sample) { + if (playing->m_sample == sample) { return playing; } } } if (flags == PAT_3DSample) { - H3DSAMPLE sample3D = (H3DSAMPLE) audioCompleted; + H3DSAMPLE sample3D = (H3DSAMPLE) handle; + CriticalSectionClass::LockClass lock(m_playing3DSoundsCS); for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_3DSample == sample3D) { + if (playing->m_3DSample == sample3D) { return playing; } } } if (flags == PAT_Stream) { - HSTREAM stream = (HSTREAM) audioCompleted; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - if (playing && playing->m_stream == stream) { - return playing; + HSTREAM stream = (HSTREAM) handle; + { + CriticalSectionClass::LockClass lock(m_playingStreamsCS); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { + playing = *it; + if (playing->m_stream == stream) { + return playing; + } + } + } + { + CriticalSectionClass::LockClass lock(m_fadingAudioCS); + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = *it; + if (playing->m_stream == stream) { + return playing; + } } } } @@ -1849,9 +1795,6 @@ Bool MilesAudioManager::doesViolateLimit( AudioEventRTS *event ) const std::list::const_iterator arIt; for (arIt = m_audioRequests.begin(); arIt != m_audioRequests.end(); ++arIt) { AudioRequest *req = (*arIt); - if (req == nullptr) { - continue; - } if( req->m_usePendingEvent ) { if( req->m_pendingEvent->getEventName() == event->getEventName() ) @@ -2048,16 +1991,11 @@ Bool MilesAudioManager::killLowestPrioritySoundImmediately( AudioEventRTS *event for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { PlayingAudio *playing = (*it); - if( !playing ) - { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent ) { //Release this 3D sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio( playing ); - m_playing3DSounds.erase( it ); + stopPlayingAudio(playing); return TRUE; } } @@ -2067,16 +2005,11 @@ Bool MilesAudioManager::killLowestPrioritySoundImmediately( AudioEventRTS *event for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { PlayingAudio *playing = (*it); - if( !playing ) - { - continue; - } if( playing->m_audioEventRTS && playing->m_audioEventRTS == lowestPriorityEvent ) { //Release this sound channel immediately because we are going to play another sound in it's place. - releasePlayingAudio( playing ); - m_playingSounds.erase( it ); + stopPlayingAudio(playing); return TRUE; } } @@ -2095,8 +2028,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n PlayingAudio *playing = nullptr; for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_sample, ("Sample is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_sample_volume_pan(playing->m_sample, nullptr, &pan); AIL_set_sample_volume_pan(playing->m_sample, getEffectiveVolume(playing->m_audioEventRTS), pan); @@ -2105,8 +2038,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_set_3D_sample_volume(playing->m_3DSample, getEffectiveVolume(playing->m_audioEventRTS)); } @@ -2114,8 +2047,8 @@ void MilesAudioManager::adjustVolumeOfPlayingAudio(AsciiString eventName, Real n for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = *it; - if (playing && playing->m_audioEventRTS->getEventName() == eventName) { - // Adjust it + if (playing->m_audioEventRTS->getEventName() == eventName) { + DEBUG_ASSERTCRASH(playing->m_stream, ("Stream is not expected to be null")); playing->m_audioEventRTS->setVolume(newVolume); AIL_stream_volume_pan(playing->m_stream, nullptr, &pan); AIL_set_stream_volume_pan(playing->m_stream, getEffectiveVolume(playing->m_audioEventRTS), pan); @@ -2130,45 +2063,30 @@ void MilesAudioManager::removePlayingAudio( AsciiString eventName ) std::list::iterator it; PlayingAudio *playing = nullptr; - for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) - { - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); - } - else + if( playing->m_audioEventRTS->getEventName() == eventName ) { - it++; + stopPlayingAudio(playing); } } - for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) - { - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - } - else + if( playing->m_audioEventRTS->getEventName() == eventName ) { - it++; + stopPlayingAudio(playing); } } - for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getEventName() == eventName ) + if( playing->m_audioEventRTS->getEventName() == eventName ) { - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } } @@ -2179,45 +2097,30 @@ void MilesAudioManager::removeAllDisabledAudio() std::list::iterator it; PlayingAudio *playing = nullptr; - for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ) + for( it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } - for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) + for( it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - } - else - { - it++; + stopPlayingAudio(playing); } } - for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ) + for( it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it ) { playing = *it; - if( playing && playing->m_audioEventRTS->getVolume() == 0.0f ) - { - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); - } - else + if( playing->m_audioEventRTS->getVolume() == 0.0f ) { - it++; + stopPlayingAudio(playing); } } } @@ -2226,12 +2129,8 @@ void MilesAudioManager::removeAllDisabledAudio() void MilesAudioManager::processRequestList() { std::list::iterator it; - for (it = m_audioRequests.begin(); it != m_audioRequests.end(); /* empty */) { + for (it = m_audioRequests.begin(); it != m_audioRequests.end(); ) { AudioRequest *req = (*it); - if (req == nullptr) { - continue; - } - if (!shouldProcessRequestThisFrame(req)) { adjustRequest(req); ++it; @@ -2249,122 +2148,122 @@ void MilesAudioManager::processRequestList() //------------------------------------------------------------------------------------------------- void MilesAudioManager::processPlayingList() { - // There are two types of processing we have to do here. - // 1. Move the item to the stopped list if it has become stopped. - // 2. Update the position of the audio if it is positional std::list::iterator it; PlayingAudio *playing; - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); /* empty */) { + // + // Stop audio without locking. Calls into Miles. + // Update the position of the audio if it is positional. + // + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playingSounds.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) - { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playingSounds.erase(it); + if (playing->m_status == PS_Stopped) { + continue; } - else + + if (m_volumeHasChanged) { - if (m_volumeHasChanged) - { - adjustPlayingVolume(playing); - } - ++it; + adjustPlayingVolume(playing); } } - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) - { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playing3DSounds.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) + if (playing->m_status == PS_Stopped) { + continue; + } + + if (m_volumeHasChanged) { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); + adjustPlayingVolume(playing); } - else + + const Coord3D *pos = getCurrentPositionFromEvent(playing->m_audioEventRTS); + if (pos) { - if (m_volumeHasChanged) + if( playing->m_audioEventRTS->isDead() ) { - adjustPlayingVolume(playing); + stopAudioEvent( playing->m_audioEventRTS->getPlayingHandle() ); } - - const Coord3D *pos = getCurrentPositionFromEvent(playing->m_audioEventRTS); - if (pos) + else { - if( playing->m_audioEventRTS->isDead() ) + Real volForConsideration = getEffectiveVolume(playing->m_audioEventRTS); + volForConsideration /= (m_sound3DVolume > 0.0f ? m_soundVolume : 1.0f); + Bool playAnyways = BitIsSet( playing->m_audioEventRTS->getAudioEventInfo()->m_type, ST_GLOBAL) + || playing->m_audioEventRTS->getAudioEventInfo()->m_priority == AP_CRITICAL; + if( volForConsideration < m_audioSettings->m_minVolume && !playAnyways ) { - stopAudioEvent( playing->m_audioEventRTS->getPlayingHandle() ); - it++; - continue; + stopPlayingAudio(playing); } else { - Real volForConsideration = getEffectiveVolume(playing->m_audioEventRTS); - volForConsideration /= (m_sound3DVolume > 0.0f ? m_soundVolume : 1.0f); - Bool playAnyways = BitIsSet( playing->m_audioEventRTS->getAudioEventInfo()->m_type, ST_GLOBAL) || playing->m_audioEventRTS->getAudioEventInfo()->m_priority == AP_CRITICAL; - if( volForConsideration < m_audioSettings->m_minVolume && !playAnyways ) - { - // don't want to get an additional callback for this sample - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - continue; - } - else - { - Real x = pos->x; - Real y = pos->y; - Real z = pos->z; - AIL_set_3D_position( playing->m_3DSample, x, y, z ); - } + Real x = pos->x; + Real y = pos->y; + Real z = pos->z; + DEBUG_ASSERTCRASH(playing->m_3DSample, ("3D Sample is not expected to be null")); + AIL_set_3D_position( playing->m_3DSample, x, y, z ); } } - else - { - AIL_register_3D_EOS_callback(playing->m_3DSample, nullptr); - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playing3DSounds.erase(it); - continue; - } - - ++it; + } + else + { + stopPlayingAudio(playing); } } - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { playing = (*it); - if (!playing) - { - it = m_playingStreams.erase(it); + + if (playing->m_status == PS_Stopping) { + stopPlayingAudio(playing); continue; } - if (playing->m_status == PS_Stopped) - { - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_playingStreams.erase(it); + if (playing->m_status == PS_Stopped) { + continue; } - else + + if (m_volumeHasChanged) { - if (m_volumeHasChanged) + adjustPlayingVolume(playing); + } + } + + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_playingSounds, m_playingSoundsCS); + releasePlayingAudioInListIfStopped(m_playing3DSounds, m_playing3DSoundsCS); + releasePlayingAudioInListIfStopped(m_playingStreams, m_playingStreamsCS); + + // + // Transfer streams to fade list when signaled. + // + { + CriticalSectionClass::LockClass lock(m_playingStreamsCS); + for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ) { + playing = (*it); + + if (playing->m_fade) { - adjustPlayingVolume(playing); + { + CriticalSectionClass::LockClass lock(m_fadingAudioCS); + m_fadingAudio.push_back(playing); + } + it = m_playingStreams.erase(it); + continue; } ++it; @@ -2395,9 +2294,6 @@ Bool MilesAudioManager::has3DSensitiveStreamsPlaying() const { const PlayingAudio *playing = (*it); - if ( ! playing ) - continue; - if ( playing->m_audioEventRTS->getAudioEventInfo()->m_soundType != AT_Music ) { return TRUE; @@ -2420,18 +2316,18 @@ void MilesAudioManager::processFadingList() std::list::iterator it; PlayingAudio *playing; - for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); /* emtpy */) { - playing = *it; - if (!playing) { + // + // Stop audio without locking. Calls into Miles. + // + for (it = m_fadingAudio.begin(); it != m_fadingAudio.end(); ++it) { + playing = (*it); + + if (playing->m_framesFaded >= getAudioSettings()->m_fadeAudioFrames) { + stopPlayingAudio(playing); continue; } - if (playing->m_framesFaded >= getAudioSettings()->m_fadeAudioFrames) { - playing->m_status = PS_Stopped; - playing->m_requestStop = true; - //m_stoppedAudio.push_back(playing); - releasePlayingAudio( playing ); - it = m_fadingAudio.erase(it); + if (playing->m_status == PS_Stopped) { continue; } @@ -2458,29 +2354,15 @@ void MilesAudioManager::processFadingList() AIL_set_stream_volume_pan(playing->m_stream, volume, 0.5f); break; } - } - - ++it; } -} -//------------------------------------------------------------------------------------------------- -void MilesAudioManager::processStoppedList() -{ - std::list::iterator it; - PlayingAudio *playing; - - for (it = m_stoppedAudio.begin(); it != m_stoppedAudio.end(); /* emtpy */) { - playing = *it; - if (playing) { - releasePlayingAudio(playing); - } - it = m_stoppedAudio.erase(it); - } + // + // Release audio with locking. Must not call into Miles! + // + releasePlayingAudioInListIfStopped(m_fadingAudio, m_fadingAudioCS); } - //------------------------------------------------------------------------------------------------- Bool MilesAudioManager::shouldProcessRequestThisFrame( AudioRequest *req ) const { @@ -2602,31 +2484,19 @@ void MilesAudioManager::closeAnySamplesUsingFile( const void *fileToClose ) std::list::iterator it; PlayingAudio *playing; - for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ) { + for (it = m_playingSounds.begin(); it != m_playingSounds.end(); ++it ) { playing = *it; - if (!playing) { - continue; - } if (playing->m_file == fileToClose) { - releasePlayingAudio(playing); - it = m_playingSounds.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } - for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ) { + for (it = m_playing3DSounds.begin(); it != m_playing3DSounds.end(); ++it ) { playing = *it; - if (!playing) { - continue; - } if (playing->m_file == fileToClose) { - releasePlayingAudio(playing); - it = m_playing3DSounds.erase(it); - } else { - ++it; + stopPlayingAudio(playing); } } } @@ -2736,7 +2606,7 @@ Bool MilesAudioManager::startNextLoop( PlayingAudio *looping ) closeFile(looping->m_file); looping->m_file = nullptr; - if (looping->m_requestStop) { + if (looping->m_status != PS_Playing) { return false; } @@ -2748,9 +2618,7 @@ Bool MilesAudioManager::startNextLoop( PlayingAudio *looping ) // fake it out so that this sound appears done, but also so that it will not // delete the sound on completion (which would suck) looping->m_cleanupAudioEventRTS = false; - looping->m_requestStop = true; - looping->m_status = PS_Stopped; - + InterlockedCompareExchange(reinterpret_cast(&looping->m_status), PS_Stopping, PS_Playing); AudioRequest *req = allocateAudioRequest(true); req->m_pendingEvent = looping->m_audioEventRTS; @@ -2961,6 +2829,7 @@ void *MilesAudioManager::getHandleForBink() aud->m_type = PAT_Sample; if (!aud->m_sample) { + stopPlayingAudio(aud); releasePlayingAudio(aud); return nullptr; } @@ -2977,6 +2846,7 @@ void *MilesAudioManager::getHandleForBink() void MilesAudioManager::releaseHandleForBink() { if (m_binkHandle) { + stopPlayingAudio(m_binkHandle); releasePlayingAudio(m_binkHandle); m_binkHandle = nullptr; } diff --git a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp index 761f4881186..cc888ac738a 100644 --- a/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp +++ b/Core/GameEngineDevice/Source/OpenALAudioDevice/OpenALAudioManager.cpp @@ -1410,7 +1410,7 @@ void OpenALAudioManager::stopAllSpeech(void) //} //------------------------------------------------------------------------------------------------- -void OpenALAudioManager::nextMusicTrack(void) +AsciiString OpenALAudioManager::nextMusicTrack(void) { AsciiString trackName; std::list::iterator it; @@ -1430,10 +1430,12 @@ void OpenALAudioManager::nextMusicTrack(void) trackName = nextTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- -void OpenALAudioManager::prevMusicTrack(void) +AsciiString OpenALAudioManager::prevMusicTrack(void) { AsciiString trackName; std::list::iterator it; @@ -1453,6 +1455,8 @@ void OpenALAudioManager::prevMusicTrack(void) trackName = prevTrackName(trackName); AudioEventRTS newTrack(trackName); TheAudio->addAudioEvent(&newTrack); + + return trackName; } //------------------------------------------------------------------------------------------------- @@ -1493,39 +1497,6 @@ Bool OpenALAudioManager::hasMusicTrackCompleted(const AsciiString& trackName, In return FALSE; } -//------------------------------------------------------------------------------------------------- -AsciiString OpenALAudioManager::getMusicTrackName(void) const -{ - // First check the requests. If there's one there, then report that as the currently playing track. - std::list::const_iterator ait; - for (ait = m_audioRequests.begin(); ait != m_audioRequests.end(); ++ait) { - if ((*ait)->m_request != AR_Play) { - continue; - } - - if (!(*ait)->m_usePendingEvent) { - continue; - } - - if ((*ait)->m_pendingEvent->getAudioEventInfo()->m_soundType == AT_Music) { - return (*ait)->m_pendingEvent->getEventName(); - } - } - - std::list::const_iterator it; - PlayingAudio* playing; - for (it = m_playingStreams.begin(); it != m_playingStreams.end(); ++it) { - playing = *it; - // GeneralsX @bugfix BenderAI 11/03/2026 - guard against null audioEventRTS/info - const AudioEventInfo* info = (playing && playing->m_audioEventRTS) ? playing->m_audioEventRTS->getAudioEventInfo() : nullptr; - if (info && info->m_soundType == AT_Music) { - return playing->m_audioEventRTS->getEventName(); - } - } - - return AsciiString::TheEmptyString; -} - //------------------------------------------------------------------------------------------------- void OpenALAudioManager::openDevice(void) { diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 41a5e929890..ddcf34c2296 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -2182,51 +2182,53 @@ void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real pre const W3DAnimationInfo& animInfo = m_curState->m_animations[m_whichAnimInCurState]; - HAnimClass* animHandle = animInfo.getAnimHandle(); // note that this now returns an ADDREFED handle, which must be released by the caller! - if (m_renderObject && animHandle) + if (m_renderObject) { - Int startFrame = 0; - if (m_curState->m_mode == RenderObjClass::ANIM_MODE_ONCE_BACKWARDS || - m_curState->m_mode == RenderObjClass::ANIM_MODE_LOOP_BACKWARDS) + HAnimClass* animHandle = animInfo.getAnimHandle(); // note that this now returns an ADDREFED handle, which must be released by the caller! + if (animHandle) { - startFrame = animHandle->Get_Num_Frames()-1; - } + Int startFrame = 0; + if (m_curState->m_mode == RenderObjClass::ANIM_MODE_ONCE_BACKWARDS || + m_curState->m_mode == RenderObjClass::ANIM_MODE_LOOP_BACKWARDS) + { + startFrame = animHandle->Get_Num_Frames()-1; + } - if (testFlagBit(m_curState->m_flags, RANDOMIZE_START_FRAME)) - { - startFrame = GameClientRandomValue(0, animHandle->Get_Num_Frames()-1); - } - else if (testFlagBit(m_curState->m_flags, START_FRAME_FIRST)) - { - startFrame = 0; - } - else if (testFlagBit(m_curState->m_flags, START_FRAME_LAST)) - { - startFrame = animHandle->Get_Num_Frames()-1; - } - // order is important here: MAINTAIN_FRAME_ACROSS_STATES is overridden by the other bits, above. - else if (isAnyMaintainFrameFlagSet(m_curState->m_flags) && - prevState && - prevState != m_curState && - isAnyMaintainFrameFlagSet(prevState->m_flags) && - isCommonMaintainFrameFlagSet(m_curState->m_flags, prevState->m_flags) && - prevAnimFraction >= 0.0) - { - startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); - } + if (testFlagBit(m_curState->m_flags, RANDOMIZE_START_FRAME)) + { + startFrame = GameClientRandomValue(0, animHandle->Get_Num_Frames()-1); + } + else if (testFlagBit(m_curState->m_flags, START_FRAME_FIRST)) + { + startFrame = 0; + } + else if (testFlagBit(m_curState->m_flags, START_FRAME_LAST)) + { + startFrame = animHandle->Get_Num_Frames()-1; + } + // order is important here: MAINTAIN_FRAME_ACROSS_STATES is overridden by the other bits, above. + else if (isAnyMaintainFrameFlagSet(m_curState->m_flags) && + prevState && + prevState != m_curState && + isAnyMaintainFrameFlagSet(prevState->m_flags) && + isCommonMaintainFrameFlagSet(m_curState->m_flags, prevState->m_flags) && + prevAnimFraction >= 0.0) + { + startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); + } - m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); - REF_PTR_RELEASE(animHandle); - animHandle = nullptr; + m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); + REF_PTR_RELEASE(animHandle); + animHandle = nullptr; - if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) - { - HLodClass *hlod = (HLodClass*)m_renderObject; - Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); - hlod->Set_Animation_Frame_Rate_Multiplier( factor ); + if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) + { + HLodClass *hlod = (HLodClass*)m_renderObject; + Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); + hlod->Set_Animation_Frame_Rate_Multiplier( factor ); + } } } - } else { diff --git a/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp b/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp index 704898a8875..3bc866b7517 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/hcanim.cpp @@ -293,10 +293,6 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - TimeCodedMotionChannelClass * tc_chan; - AdaptiveDeltaMotionChannelClass * ad_chan; - TimeCodedBitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { @@ -306,7 +302,8 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) switch ( Flavor ) { case ANIM_FLAVOR_TIMECODED: - + { + TimeCodedMotionChannelClass* tc_chan = nullptr; if (!read_channel(cload,&tc_chan)) { goto Error; } @@ -322,8 +319,11 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) } break; + } case ANIM_FLAVOR_ADAPTIVE_DELTA: + { + AdaptiveDeltaMotionChannelClass* ad_chan = nullptr; if (!read_channel(cload,&ad_chan)) { goto Error; } @@ -338,10 +338,13 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) WWDEBUG_SAY(("ERROR! animation %s indexes a bone not present in the model. Please re-export!",Name)); } break; + } } break; case W3D_CHUNK_COMPRESSED_BIT_CHANNEL: + { + TimeCodedBitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan)) { goto Error; } @@ -357,6 +360,7 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) } break; + } default: break; @@ -387,20 +391,32 @@ int HCompressedAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HCompressedAnimClass::read_channel(ChunkLoadClass & cload,TimeCodedMotionChannelClass * * newchan) { - *newchan = W3DNEW TimeCodedMotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + TimeCodedMotionChannelClass* channel = W3DNEW TimeCodedMotionChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } bool HCompressedAnimClass::read_channel(ChunkLoadClass & cload,AdaptiveDeltaMotionChannelClass * * newchan) { - *newchan = W3DNEW AdaptiveDeltaMotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + AdaptiveDeltaMotionChannelClass* channel = W3DNEW AdaptiveDeltaMotionChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } @@ -483,11 +499,17 @@ void HCompressedAnimClass::add_channel(AdaptiveDeltaMotionChannelClass * newchan *=============================================================================================*/ bool HCompressedAnimClass::read_bit_channel(ChunkLoadClass & cload,TimeCodedBitChannelClass * * newchan) { - *newchan = W3DNEW TimeCodedBitChannelClass; - bool result = (*newchan)->Load_W3D(cload); - - return result; - + TimeCodedBitChannelClass* channel = W3DNEW TimeCodedBitChannelClass; + if (channel->Load_W3D(cload)) + { + *newchan = channel; + return true; + } + else + { + delete channel; + return false; + } } diff --git a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp index 681c4c0b0fa..9b5970062ee 100644 --- a/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp +++ b/Core/Libraries/Source/WWVegas/WW3D2/seglinerenderer.cpp @@ -191,7 +191,7 @@ void SegLineRendererClass::Set_Texture_Tile_Factor(float factor) ///@todo: I raised this number and didn't see much difference on our min-spec. -MW const static float MAX_LINE_TILING_FACTOR = 50.0f; if (factor > MAX_LINE_TILING_FACTOR) { - WWDEBUG_SAY(("Texture (%s) Tile Factor (%.2f) too large in SegLineRendererClass!", Get_Texture()->Get_Texture_Name().str(), TextureTileFactor)); + WWDEBUG_SAY(("Texture (%s) Tile Factor (%.2f) too large in SegLineRendererClass!", Peek_Texture()->Get_Texture_Name().str(), TextureTileFactor)); factor = MAX_LINE_TILING_FACTOR; } else { factor = MAX(factor, 0.0f); diff --git a/Dependencies/Utility/CMakeLists.txt b/Dependencies/Utility/CMakeLists.txt index a810813bd62..90acedcaec3 100644 --- a/Dependencies/Utility/CMakeLists.txt +++ b/Dependencies/Utility/CMakeLists.txt @@ -4,6 +4,7 @@ set(UTILITY_SRC Utility/endian_compat.h Utility/fstream_adapter.h Utility/hash_map_adapter.h + Utility/interlocked_adapter.h Utility/intrin_compat.h Utility/iostream_adapter.h Utility/mem_compat.h diff --git a/Dependencies/Utility/Utility/interlocked_adapter.h b/Dependencies/Utility/Utility/interlocked_adapter.h new file mode 100644 index 00000000000..45da6a2f9cb --- /dev/null +++ b/Dependencies/Utility/Utility/interlocked_adapter.h @@ -0,0 +1,28 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#pragma once + +#if defined(_MSC_VER) && _MSC_VER < 1300 + +inline long InterlockedCompareExchange(long volatile *Destination, long Exchange, long Comparand) +{ + return (long)InterlockedCompareExchange((PVOID*)Destination, (PVOID)Exchange, (PVOID)Comparand); +} + +#endif diff --git a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h index 28ecc024578..9bda58f2b07 100644 --- a/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/Generals/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -366,6 +366,10 @@ class MetaEventTranslator : public GameMessageTranslator MetaEventTranslator(); virtual ~MetaEventTranslator() override; virtual GameMessageDisposition translateGameMessage(const GameMessage *msg) override; + +private: + void onMouseEvent(const GameMessage *msg); + void onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp); }; //----------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Include/GameLogic/Object.h b/Generals/Code/GameEngine/Include/GameLogic/Object.h index 25c9590f8a6..dc17b8eae67 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/Object.h +++ b/Generals/Code/GameEngine/Include/GameLogic/Object.h @@ -717,7 +717,7 @@ class Object : public Thing, public Snapshot Object* m_containedBy; /**< an object can only be contained by at most one other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + ObjectID m_containedByID; ///< ID of the object we're contained by; only to be used when m_containedBy cannot be used UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) diff --git a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp index 1d01553023c..7c66f5a2850 100644 --- a/Generals/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/Generals/Code/GameEngine/Source/Common/GameEngine.cpp @@ -285,6 +285,9 @@ GameEngine::~GameEngine() delete TheSubsystemList; TheSubsystemList = nullptr; + delete TheSkirmishGameInfo; + TheSkirmishGameInfo = nullptr; + delete TheNetwork; TheNetwork = nullptr; diff --git a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp index 9ac294382e3..3d3bc513fd7 100644 --- a/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/Generals/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2979,8 +2979,8 @@ void Player::removeUpgrade( const UpgradeTemplate *upgradeTemplate ) if( upgrade->getStatus() == UPGRADE_STATUS_COMPLETE ) onUpgradeRemoved(); + deleteInstance(upgrade); } - } diff --git a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp index dc6cb96aab6..9dd149c1adf 100644 --- a/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp +++ b/Generals/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp @@ -102,6 +102,8 @@ static void embedPristineMap( AsciiString map, Xfer *xfer ) if( file->read( buffer, fileSize ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedPristineMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -159,6 +161,8 @@ static void embedInUseMap( AsciiString map, Xfer *xfer ) if( fread( buffer, 1, fileSize, fp ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -214,6 +218,8 @@ static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer ) if( fwrite( buffer, 1, dataSize, fp ) != dataSize ) { + delete[] buffer; + DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'", mapToSave.str() )); throw SC_INVALID_DATA; diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index b657b769b4d..ae4b136dbb1 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -4521,8 +4521,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_NEXT_TRACK: { - TheAudio->nextMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->nextMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } @@ -4531,8 +4531,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_PREV_TRACK: { - TheAudio->prevMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->prevMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } diff --git a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 5a0aa8e7e0a..7631ccc883d 100644 --- a/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -400,87 +400,191 @@ static Bool isMessageUsable(CommandUsableInType usableIn) GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessage *msg) { GameMessageDisposition disp = KEEP_MESSAGE; - GameMessage::Type t = msg->getType(); + const GameMessage::Type t = msg->getType(); if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + onKeyEvent(msg, disp); + } + else if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + { + onMouseEvent(msg); + } - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; + return disp; +} - if( keyState & KEY_STATE_CONTROL ) +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onMouseEvent(const GameMessage *msg) +{ + Int index = 3; + switch (msg->getType()) + { + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: { - newModState |= CTRL; + --index; + m_mouseDownPosition[index] = msg->getArgument(0)->pixel; + m_nextUpShouldCreateDoubleClick[index] = FALSE; + break; } - if( keyState & KEY_STATE_SHIFT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: { - newModState |= SHIFT; + --index; + m_nextUpShouldCreateDoubleClick[index] = TRUE; + break; } - if( keyState & KEY_STATE_ALT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: { - newModState |= ALT; + --index; + + constexpr const GameMessage::Type SingleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_CLICK, + }; + constexpr const GameMessage::Type DoubleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, + }; + + const ICoord2D location = msg->getArgument(0)->pixel; + const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; + GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); + + IRegion2D pixelRegion; + buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); + if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && + abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) + { + pixelRegion.hi.x = pixelRegion.lo.x; + pixelRegion.hi.y = pixelRegion.lo.y; + } + + newMessage->appendPixelRegionArgument( pixelRegion ); + + // append the modifier keys to the message. + newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); + + // append the time to the message. + //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); + break; } + } +} + +//------------------------------------------------------------------------------------------------- +// GeneralsX @refactor fbraz3 06/05/2026 — adopt upstream's structural split (#2758) +// into a single onKeyEvent() helper while keeping GeneralsX's pre-existing +// input semantics: a "mods-only-changed" branch using m_lastModState and a +// m_lastKeyDown-tracking normal key-transition branch. We do not adopt +// upstream's m_keyDownInfos / KeyDownInfo bit-array (introduced for the +// "ignore order of modifier release" fix in #2577) because that fix is not +// part of GeneralsX's behavior, and the bit-array member is not declared +// in MetaEvent.h. +void MetaEventTranslator::onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp) +{ + const Int systemKey = msg->getArgument(0)->integer; + const Int systemKeyState = msg->getArgument(1)->integer; - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + const MappableKeyType key = (MappableKeyType)systemKey; + + // for our purposes here, we don't care to distinguish between right and left keys, + // so just fudge a little to simplify things. + Int newModState = 0; + + if (systemKeyState & KEY_STATE_CONTROL) + { + newModState |= CTRL; + } + + if (systemKeyState & KEY_STATE_SHIFT) + { + newModState |= SHIFT; + } + + if (systemKeyState & KEY_STATE_ALT) + { + newModState |= ALT; + } + + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + // check for the special case of mods-only-changed. + if ( + map->m_key == MK_NONE && + newModState != m_lastModState && + ( + (map->m_transition == UP && map->m_modState == m_lastModState) || + (map->m_transition == DOWN && map->m_modState == newModState) + ) + ) + { + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; + break; + } + + // ok, now check for "normal" key transitions. + if ( + map->m_key == key && + map->m_modState == newModState && + ( + (map->m_transition == UP && (systemKeyState & KEY_STATE_UP)) || + (map->m_transition == DOWN && (systemKeyState & KEY_STATE_DOWN)) //|| + //(map->m_transition == DOUBLEDOWN && (systemKeyState & KEY_STATE_DOWN) && m_lastKeyDown == key) + ) + ) { - if (!isMessageUsable(map->m_usableIn)) - continue; - - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) + + if (systemKeyState & KEY_STATE_AUTOREPEAT) { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; + // if it's an autorepeat of a "known" key, don't generate the meta-event, + // but DO eat the keystroke so no one else can mess with it + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } - - // ok, now check for "normal" key transitions. - if ( - map->m_key == key && - map->m_modState == newModState && - ( - (map->m_transition == UP && (keyState & KEY_STATE_UP)) || - (map->m_transition == DOWN && (keyState & KEY_STATE_DOWN)) //|| - //(map->m_transition == DOUBLEDOWN && (keyState & KEY_STATE_DOWN) && m_lastKeyDown == key) - ) - ) + else { - if( keyState & KEY_STATE_AUTOREPEAT ) - { - // if it's an autorepeat of a "known" key, don't generate the meta-event, - // but DO eat the keystroke so no one else can mess with it - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - else - { - - // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED - if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) - { + // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED + if (map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) + { #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h - if( TheGlobalData ) + if (TheGlobalData) #else - if( TheGlobalData && TheGameLogic->isInReplayGame()) + if (TheGlobalData && TheGameLogic->isInReplayGame()) #endif { - if ( TheWritableGlobalData ) + if (TheWritableGlobalData) TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; - if ( TheInGameUI ) + if (TheInGameUI) TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") @@ -488,106 +592,23 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys break; - } + } - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - disp = DESTROY_MESSAGE; - break; + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } + disp = DESTROY_MESSAGE; + break; } - - if (t == GameMessage::MSG_RAW_KEY_DOWN) - m_lastKeyDown = key; - m_lastModState = newModState; } - - if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + if (msg->getType() == GameMessage::MSG_RAW_KEY_DOWN) { - Int index = 3; - switch (t) - { - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: - { - --index; - m_mouseDownPosition[index] = msg->getArgument(0)->pixel; - m_nextUpShouldCreateDoubleClick[index] = FALSE; - break; - } - - case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: - { - --index; - m_nextUpShouldCreateDoubleClick[index] = TRUE; - break; - } - - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: - { - --index; - - constexpr const GameMessage::Type SingleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_CLICK, - }; - constexpr const GameMessage::Type DoubleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, - }; - - const ICoord2D location = msg->getArgument(0)->pixel; - const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; - GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); - - IRegion2D pixelRegion; - buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); - if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && - abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) - { - pixelRegion.hi.x = pixelRegion.lo.x; - pixelRegion.hi.y = pixelRegion.lo.y; - } - - newMessage->appendPixelRegionArgument( pixelRegion ); - - // append the modifier keys to the message. - newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); - - // append the time to the message. - //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); - break; - } - - } - + m_lastKeyDown = key; } - return disp; + m_lastModState = newModState; } //------------------------------------------------------------------------------------------------- diff --git a/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp b/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp index c9c98aa8b87..49d3b6f9c5a 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp @@ -535,7 +535,12 @@ void SidesList::prepareForMP_or_Skirmish() break; } } - if (curSide == -1) continue; + if (curSide == -1) + { + deleteInstance(scripts[i]); + scripts[i] = nullptr; + continue; + } deleteInstance(getSkirmishSideInfo(curSide)->getScriptList()); getSkirmishSideInfo(curSide)->setScriptList(scripts[i]); diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index 22ba13446a5..1114aef64d1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -234,6 +234,9 @@ void OpenContain::addOrRemoveObjFromWorld(Object* obj, Bool add) } else { + DEBUG_ASSERTCRASH(!getObject()->isEffectivelyDead() && !getObject()->isDestroyed(), + ("object shouldn't become an occupant of a dead or destroyed container object")); + // remove object from its group (if any) obj->leaveGroup(); @@ -277,11 +280,26 @@ void OpenContain::addToContain( Object *rider ) if( rider == nullptr ) return; +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Caball009 25/05/2026 Ensure the occupant is only added to a non-destroyed + // container to avoid an invalid state and use-after-free bugs when accessing the contained by pointer. + if (getObject()->isDestroyed()) + { + DEBUG_CRASH(("'%s' is about to be added to '%s', which is destroyed", + rider->getTemplate()->getName().str(), getObject()->getTemplate()->getName().str())); + return; + } +#endif + // TheSuperHackers @bugfix Stubbjax 06/02/2026 Ensure the rider is not destroyed to prevent a // likely crash if it enters the container on the same frame. If this occurs with an unpatched // client present in a match, the game has a small chance to mismatch. if (rider->isDestroyed()) + { + DEBUG_CRASH(("'%s', which is destroyed, is about to be added to '%s'", + rider->getTemplate()->getName().str(), getObject()->getTemplate()->getName().str())); return; + } #if defined(RTS_DEBUG) if( !isValidContainerFor( rider, false ) ) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index 7c533936c5b..708807b9f7e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -362,8 +362,19 @@ UpdateSleepTime TransportContain::update() { const TransportContainModuleData *moduleData = getTransportContainModuleData(); - if( m_payloadCreated == FALSE ) + if (m_payloadCreated == FALSE) + { +#if RETAIL_COMPATIBLE_CRC createPayload(); +#else + // TheSuperHackers @bugfix Caball009 25/05/2026 Don't create payload + // for destroyed object to avoid leaving the payload in an invalid state. + if (!getObject()->isDestroyed()) + { + createPayload(); + } +#endif + } if( moduleData && moduleData->m_healthRegen ) { diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 64bb6fe4c6d..4b7fca0ea86 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -174,7 +174,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu m_physics(nullptr), m_geometryInfo(tt->getTemplateGeometryInfo()), m_containedBy(nullptr), - m_xferContainedByID(INVALID_ID), + m_containedByID(INVALID_ID), m_containedByFrame(0), m_behaviors(nullptr), m_body(nullptr), @@ -622,6 +622,22 @@ void Object::onContainedBy( Object *containedBy ) clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); m_containedBy = containedBy; m_containedByFrame = TheGameLogic->getFrame(); + +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + m_containedByID = containedBy->getID(); + } + else + { + m_containedByID = INVALID_ID; + } +#else + DEBUG_ASSERTCRASH(containedBy == nullptr || !containedBy->isDestroyed(), + ("Object::onContainedBy - Adding into a destroyed container")); +#endif } //------------------------------------------------------------------------------------------------- @@ -632,6 +648,10 @@ void Object::onRemovedFrom( Object *removedFrom ) clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); m_containedBy = nullptr; m_containedByFrame = 0; + +#if RETAIL_COMPATIBLE_CRC + m_containedByID = INVALID_ID; +#endif } //------------------------------------------------------------------------------------------------- @@ -682,9 +702,33 @@ void Object::onDestroy() { // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) + if (m_containedBy) { - m_containedBy->getContain()->removeFromContain( this ); +#if RETAIL_COMPATIBLE_CRC + if (m_containedByID == INVALID_ID) + { + // TheSuperHackers @bugfix Caball009 25/05/2026 Due to a potential use-after-free bug that cannot be fixed + // with retail compatibility, the 'contained by' pointer of this object may point to an already destroyed object. + // Avoid removing this object from the contain list, because it could crash the game, + // as the begin / end iterator for STLPort and MSVC std::list implementations depends on dynamically allocated memory. + DEBUG_CRASH(("container object must be valid; this looks like use-after-free")); + } + else + { + DEBUG_ASSERTCRASH(TheGameLogic->findObjectByID(m_containedByID) == m_containedBy, + ("contained by pointer is out of sync with contained by ID")); + + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } + } +#else + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } +#endif } // @@ -3730,16 +3774,18 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr ) - m_xferContainedByID = m_containedBy->getID(); + m_containedByID = m_containedBy->getID(); else - m_xferContainedByID = INVALID_ID; + m_containedByID = INVALID_ID; } +#endif - - xfer->xferObjectID( &m_xferContainedByID ); + xfer->xferObjectID( &m_containedByID ); } // contained by frame @@ -3964,8 +4010,8 @@ void Object::xfer( Xfer *xfer ) //------------------------------------------------------------------------------------------------- void Object::loadPostProcess() { - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + if( m_containedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_containedByID); else m_containedBy = nullptr; diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp index 64da934984c..e088695d72e 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp @@ -164,6 +164,7 @@ BattlePlanUpdate::~BattlePlanUpdate() TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() ); TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() ); + deleteInstance(m_bonuses); } // ------------------------------------------------------------------------------------------------ diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp index e0c835c881c..2d75bd3199f 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp @@ -236,14 +236,13 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - MotionChannelClass * newchan; - BitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { case W3D_CHUNK_ANIMATION_CHANNEL: + { + MotionChannelClass* newchan = nullptr; if (!read_channel(cload,&newchan,pre30)) { goto Error; } @@ -258,8 +257,11 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newchan; } break; + } case W3D_CHUNK_BIT_CHANNEL: + { + BitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan,pre30)) { goto Error; } @@ -274,6 +276,7 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newbitchan; } break; + } default: break; @@ -304,14 +307,22 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HRawAnimClass::read_channel(ChunkLoadClass & cload,MotionChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW MotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); + MotionChannelClass* channel = W3DNEW MotionChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } /*********************************************************************************************** @@ -378,14 +389,22 @@ void HRawAnimClass::add_channel(MotionChannelClass * newchan) *=============================================================================================*/ bool HRawAnimClass::read_bit_channel(ChunkLoadClass & cload,BitChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW BitChannelClass; - bool result = (*newchan)->Load_W3D(cload); + BitChannelClass* channel = W3DNEW BitChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } diff --git a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 100d475c713..0264bf1ae37 100644 --- a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -1421,6 +1421,9 @@ void ScriptDialog::OnLoad() msg += m_readPlayerNames[i].str(); msg += ", discarding scripts for this player."; ::AfxMessageBox(msg); + + deleteInstance(scripts[i]); + scripts[i] = nullptr; continue; } } @@ -1428,31 +1431,34 @@ void ScriptDialog::OnLoad() curSide = 0; ::AfxMessageBox("Imported scripts came from more players than exist in this map. Additional scripts moved to Neutral player."); } - ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); - Script *pScr; - Script *pNextScr; - Int j=0; - for (pScr = scripts[i]->getScript(); pScr; pScr=pNextScr) { - pNextScr=pScr->getNext(); - pScr->setNextScript(nullptr); - pSL->addScript(pScr, j); //unlink it and add. - j++; - } - j=0; - ScriptGroup *pGroup; - ScriptGroup *pNextGroup; - for (pGroup = scripts[i]->getScriptGroup(); pGroup; pGroup=pNextGroup) { - pNextGroup=pGroup->getNext(); - pGroup->setNextGroup(nullptr); - pSL->addGroup(pGroup, j); - j++; - } - scripts[i]->discard(); /* Frees the script list, but none of it's children, as they have been - copied into the current scripts. */ - scripts[i] = nullptr; + ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); if (pSL) { + Script *pScr; + Script *pNextScr; + Int j=0; + for (pScr = scripts[i]->getScript(); pScr; pScr=pNextScr) { + pNextScr=pScr->getNext(); + pScr->setNextScript(nullptr); + pSL->addScript(pScr, j); //unlink it and add. + j++; + } + j=0; + ScriptGroup *pGroup; + ScriptGroup *pNextGroup; + for (pGroup = scripts[i]->getScriptGroup(); pGroup; pGroup=pNextGroup) { + pNextGroup=pGroup->getNext(); + pGroup->setNextGroup(nullptr); + pSL->addGroup(pGroup, j); + j++; + } + scripts[i]->discard(); /* Frees the script list, but none of it's children, as they have been + copied into the current scripts. */ + scripts[i] = nullptr; reloadPlayer(curSide, pSL); + } else { + deleteInstance(scripts[i]); + scripts[i] = nullptr; } } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h index 1bfc1a9ea2b..03205468fbc 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/MetaEvent.h @@ -366,6 +366,10 @@ class MetaEventTranslator : public GameMessageTranslator MetaEventTranslator(); virtual ~MetaEventTranslator() override; virtual GameMessageDisposition translateGameMessage(const GameMessage *msg) override; + +private: + void onMouseEvent(const GameMessage *msg); + void onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp); }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index bfdf333393f..29d391b81a3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -761,7 +761,7 @@ class Object : public Thing, public Snapshot Object* m_containedBy; /**< an object can only be contained by at most one other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + ObjectID m_containedByID; ///< ID of the object we're contained by; only to be used when m_containedBy cannot be used UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp index 6f9ce5a65c7..3df81ea7789 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp @@ -287,6 +287,12 @@ GameEngine::~GameEngine() delete TheSubsystemList; TheSubsystemList = nullptr; + delete TheSkirmishGameInfo; + TheSkirmishGameInfo = nullptr; + + delete TheChallengeGameInfo; + TheChallengeGameInfo = nullptr; + delete TheNetwork; TheNetwork = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index ec5149a081d..1b5a4fa7285 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -3168,13 +3168,13 @@ void Player::removeUpgrade( const UpgradeTemplate *upgradeTemplate ) if( upgrade->getStatus() == UPGRADE_STATUS_COMPLETE ) onUpgradeRemoved(); - if( ThePlayerList->getLocalPlayer() == this ) - { - TheControlBar->markUIDirty(); - } + deleteInstance(upgrade); + if( ThePlayerList->getLocalPlayer() == this ) + { + TheControlBar->markUIDirty(); + } } - } diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp index b2c4d3fba4c..f503e975f8c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/SaveGame/GameStateMap.cpp @@ -104,6 +104,8 @@ static void embedPristineMap( AsciiString map, Xfer *xfer ) if( file->read( buffer, fileSize ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedPristineMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -161,6 +163,8 @@ static void embedInUseMap( AsciiString map, Xfer *xfer ) if( fread( buffer, 1, fileSize, fp ) != fileSize ) { + delete[] buffer; + DEBUG_CRASH(( "embedInUseMap - Error reading from file '%s'", map.str() )); throw SC_INVALID_DATA; @@ -216,6 +220,8 @@ static void extractAndSaveMap( AsciiString mapToSave, Xfer *xfer ) if( fwrite( buffer, 1, dataSize, fp ) != dataSize ) { + delete[] buffer; + DEBUG_CRASH(( "extractAndSaveMap - Error writing to file '%s'", mapToSave.str() )); throw SC_INVALID_DATA; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 9d9d363a0a2..fa221abbb39 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -117,17 +117,13 @@ static_assert(ARRAY_SIZE(TheDrawableIconNames) == MAX_ICONS + 1, "Incorrect arra * * OK, so it's a bit of a hack, but it saves memory in every Drawable */ -static DynamicAudioEventInfo * getNoSoundMarker() -{ - static DynamicAudioEventInfo * marker = nullptr; +class DynamicAudioEventInfoStatic : public DynamicAudioEventInfo +{}; +static DynamicAudioEventInfoStatic s_noSoundMarker; - if ( marker == nullptr ) - { - // Initialize first time function is called - marker = newInstance( DynamicAudioEventInfo ); - } - - return marker; +static DynamicAudioEventInfo* getNoSoundMarker() +{ + return &s_noSoundMarker; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 1f15291995a..3ffdc71758e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -4904,8 +4904,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_NEXT_TRACK: { - TheAudio->nextMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->nextMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } @@ -4914,8 +4914,8 @@ GameMessageDisposition CommandTranslator::translateGameMessage(const GameMessage //----------------------------------------------------------------------------------------- case GameMessage::MSG_META_DEMO_MUSIC_PREV_TRACK: { - TheAudio->prevMusicTrack(); - TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", TheAudio->getMusicTrackName().str()) ); + AsciiString trackName = TheAudio->prevMusicTrack(); + TheInGameUI->message( TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:DebugMusicTrack", L"Playing Track: %hs", trackName.str()) ); disp = DESTROY_MESSAGE; break; } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp index 5f7d773a129..e920d40a8e6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/MetaEvent.cpp @@ -439,88 +439,191 @@ static Bool isMessageUsable(CommandUsableInType usableIn) GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessage *msg) { GameMessageDisposition disp = KEEP_MESSAGE; - GameMessage::Type t = msg->getType(); + const GameMessage::Type t = msg->getType(); if (t == GameMessage::MSG_RAW_KEY_DOWN || t == GameMessage::MSG_RAW_KEY_UP) { - MappableKeyType key = (MappableKeyType)msg->getArgument(0)->integer; - Int keyState = msg->getArgument(1)->integer; + onKeyEvent(msg, disp); + } + else if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) + { + onMouseEvent(msg); + } - // for our purposes here, we don't care to distinguish between right and left keys, - // so just fudge a little to simplify things. - Int newModState = 0; + return disp; +} - if( keyState & KEY_STATE_CONTROL ) +//------------------------------------------------------------------------------------------------- +void MetaEventTranslator::onMouseEvent(const GameMessage *msg) +{ + Int index = 3; + switch (msg->getType()) + { + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: { - newModState |= CTRL; + --index; + m_mouseDownPosition[index] = msg->getArgument(0)->pixel; + m_nextUpShouldCreateDoubleClick[index] = FALSE; + break; } - if( keyState & KEY_STATE_SHIFT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: { - newModState |= SHIFT; + --index; + m_nextUpShouldCreateDoubleClick[index] = TRUE; + break; } - if( keyState & KEY_STATE_ALT ) + case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: + --index; + FALLTHROUGH; + case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: { - newModState |= ALT; + --index; + + constexpr const GameMessage::Type SingleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_CLICK, + }; + constexpr const GameMessage::Type DoubleClickMessages[3] = + { + GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, + GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, + }; + + const ICoord2D location = msg->getArgument(0)->pixel; + const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; + GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); + + IRegion2D pixelRegion; + buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); + if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && + abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) + { + pixelRegion.hi.x = pixelRegion.lo.x; + pixelRegion.hi.y = pixelRegion.lo.y; + } + + newMessage->appendPixelRegionArgument( pixelRegion ); + + // append the modifier keys to the message. + newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); + + // append the time to the message. + //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); + break; } + } +} + +//------------------------------------------------------------------------------------------------- +// GeneralsX @refactor fbraz3 06/05/2026 — adopt upstream's structural split (#2758) +// into a single onKeyEvent() helper while keeping GeneralsX's pre-existing +// input semantics: a "mods-only-changed" branch using m_lastModState and a +// m_lastKeyDown-tracking normal key-transition branch. We do not adopt +// upstream's m_keyDownInfos / KeyDownInfo bit-array (introduced for the +// "ignore order of modifier release" fix in #2577) because that fix is not +// part of GeneralsX's behavior, and the bit-array member is not declared +// in MetaEvent.h. +void MetaEventTranslator::onKeyEvent(const GameMessage *msg, GameMessageDisposition &disp) +{ + const Int systemKey = msg->getArgument(0)->integer; + const Int systemKeyState = msg->getArgument(1)->integer; + + const MappableKeyType key = (MappableKeyType)systemKey; + // for our purposes here, we don't care to distinguish between right and left keys, + // so just fudge a little to simplify things. + Int newModState = 0; + + if (systemKeyState & KEY_STATE_CONTROL) + { + newModState |= CTRL; + } + + if (systemKeyState & KEY_STATE_SHIFT) + { + newModState |= SHIFT; + } + + if (systemKeyState & KEY_STATE_ALT) + { + newModState |= ALT; + } - for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + for (const MetaMapRec *map = TheMetaMap->getFirstMetaMapRec(); map; map = map->m_next) + { + if (!isMessageUsable(map->m_usableIn)) + continue; + + // check for the special case of mods-only-changed. + if ( + map->m_key == MK_NONE && + newModState != m_lastModState && + ( + (map->m_transition == UP && map->m_modState == m_lastModState) || + (map->m_transition == DOWN && map->m_modState == newModState) + ) + ) + { + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + disp = DESTROY_MESSAGE; + break; + } + + // ok, now check for "normal" key transitions. + if ( + map->m_key == key && + map->m_modState == newModState && + ( + (map->m_transition == UP && (systemKeyState & KEY_STATE_UP)) || + (map->m_transition == DOWN && (systemKeyState & KEY_STATE_DOWN)) //|| + //(map->m_transition == DOUBLEDOWN && (systemKeyState & KEY_STATE_DOWN) && m_lastKeyDown == key) + ) + ) { - if (!isMessageUsable(map->m_usableIn)) - continue; - - // check for the special case of mods-only-changed. - if ( - map->m_key == MK_NONE && - newModState != m_lastModState && - ( - (map->m_transition == UP && map->m_modState == m_lastModState) || - (map->m_transition == DOWN && map->m_modState == newModState) - ) - ) + + if (systemKeyState & KEY_STATE_AUTOREPEAT) { - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() Mods-only change: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - disp = DESTROY_MESSAGE; - break; + // if it's an autorepeat of a "known" key, don't generate the meta-event, + // but DO eat the keystroke so no one else can mess with it + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } - - // ok, now check for "normal" key transitions. - if ( - map->m_key == key && - map->m_modState == newModState && - ( - (map->m_transition == UP && (keyState & KEY_STATE_UP)) || - (map->m_transition == DOWN && (keyState & KEY_STATE_DOWN)) //|| - //(map->m_transition == DOUBLEDOWN && (keyState & KEY_STATE_DOWN) && m_lastKeyDown == key) - ) - ) + else { - if( keyState & KEY_STATE_AUTOREPEAT ) - { - // if it's an autorepeat of a "known" key, don't generate the meta-event, - // but DO eat the keystroke so no one else can mess with it - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() auto-repeat: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - else - { - - // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED - if( map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) - { + // THIS IS A GREASY HACK... MESSAGE SHOULD BE HANDLED IN A TRANSLATOR, BUT DURING CINEMATICS THE TRANSLATOR IS DISABLED + if (map->m_meta == GameMessage::MSG_META_TOGGLE_FAST_FORWARD_REPLAY) + { #if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)//may be defined in GameCommon.h - if( TheGlobalData ) + if (TheGlobalData) #else - if( TheGlobalData && TheGameLogic->isInReplayGame()) + if (TheGlobalData && TheGameLogic->isInReplayGame()) #endif { - if ( TheWritableGlobalData ) + if (TheWritableGlobalData) TheWritableGlobalData->m_TiVOFastMode = 1 - TheGlobalData->m_TiVOFastMode; - if ( TheInGameUI ) + if (TheInGameUI) TheInGameUI->messageNoFormat( TheGlobalData->m_TiVOFastMode ? TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_ON", L"Fast Forward is on") : TheGameText->FETCH_OR_SUBSTITUTE("GUI:FF_OFF", L"Fast Forward is off") @@ -528,125 +631,37 @@ GameMessageDisposition MetaEventTranslator::translateGameMessage(const GameMessa } disp = KEEP_MESSAGE; // cause for goodness sake, this key gets used a lot by non-replay hotkeys break; - } + } - /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); - //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); - } - disp = DESTROY_MESSAGE; - break; + /*GameMessage *metaMsg =*/ TheMessageStream->appendMessage(map->m_meta); + //DEBUG_LOG(("Frame %d: MetaEventTranslator::translateGameMessage() normal: %s", TheGameLogic->getFrame(), findGameMessageNameByType(map->m_meta))); } + disp = DESTROY_MESSAGE; + break; } + } - - - if (t == GameMessage::MSG_RAW_KEY_DOWN) - { - m_lastKeyDown = key; + if (msg->getType() == GameMessage::MSG_RAW_KEY_DOWN) + { + m_lastKeyDown = key; #ifdef DUMP_ALL_KEYS_TO_LOG - WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); - UnicodeString uKey; - uKey.set(&Wkey); - AsciiString aKey; - aKey.translate(uKey); + WideChar Wkey = TheKeyboard->getPrintableKey(key, 0); + UnicodeString uKey; + uKey.set(&Wkey); + AsciiString aKey; + aKey.translate(uKey); DEBUG_LOG(("^%s ", aKey.str())); #endif - } - - - - m_lastModState = newModState; - } - + } - if (t > GameMessage::MSG_RAW_MOUSE_BEGIN && t < GameMessage::MSG_RAW_MOUSE_END ) - { - Int index = 3; - switch (t) - { - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_DOWN: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_DOWN: - { - --index; - m_mouseDownPosition[index] = msg->getArgument(0)->pixel; - m_nextUpShouldCreateDoubleClick[index] = FALSE; - break; - } - case GameMessage::MSG_RAW_MOUSE_LEFT_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_DOUBLE_CLICK: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_DOUBLE_CLICK: - { - --index; - m_nextUpShouldCreateDoubleClick[index] = TRUE; - break; - } - case GameMessage::MSG_RAW_MOUSE_LEFT_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_MIDDLE_BUTTON_UP: - --index; - FALLTHROUGH; - case GameMessage::MSG_RAW_MOUSE_RIGHT_BUTTON_UP: - { - --index; - - constexpr const GameMessage::Type SingleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_CLICK, - }; - constexpr const GameMessage::Type DoubleClickMessages[3] = - { - GameMessage::MSG_MOUSE_LEFT_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_MIDDLE_DOUBLE_CLICK, - GameMessage::MSG_MOUSE_RIGHT_DOUBLE_CLICK, - }; - - const ICoord2D location = msg->getArgument(0)->pixel; - const GameMessage::Type messageType = m_nextUpShouldCreateDoubleClick[index] ? DoubleClickMessages[index] : SingleClickMessages[index]; - GameMessage *newMessage = TheMessageStream->insertMessage(messageType, const_cast(msg)); - - IRegion2D pixelRegion; - buildRegion( &m_mouseDownPosition[index], &location, &pixelRegion ); - if (abs(pixelRegion.hi.x - pixelRegion.lo.x) < TheMouse->m_dragTolerance && - abs(pixelRegion.hi.y - pixelRegion.lo.y) < TheMouse->m_dragTolerance) - { - pixelRegion.hi.x = pixelRegion.lo.x; - pixelRegion.hi.y = pixelRegion.lo.y; - } - - newMessage->appendPixelRegionArgument( pixelRegion ); - - // append the modifier keys to the message. - newMessage->appendIntegerArgument( msg->getArgument(1)->integer ); - - // append the time to the message. - //newMessage->appendIntegerArgument( msg->getArgument(2)->integer ); - break; - } - - } - - } - - return disp; + m_lastModState = newModState; } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp index 8794ecc4e57..b8592dc5512 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp @@ -562,7 +562,12 @@ void SidesList::prepareForMP_or_Skirmish() break; } } - if (curSide == -1) continue; + if (curSide == -1) + { + deleteInstance(scripts[i]); + scripts[i] = nullptr; + continue; + } deleteInstance(getSkirmishSideInfo(curSide)->getScriptList()); getSkirmishSideInfo(curSide)->setScriptList(scripts[i]); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index b47881987f7..4cc9a3bae7c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -241,6 +241,9 @@ void OpenContain::addOrRemoveObjFromWorld(Object* obj, Bool add) } else { + DEBUG_ASSERTCRASH(!getObject()->isEffectivelyDead() && !getObject()->isDestroyed(), + ("object shouldn't become an occupant of a dead or destroyed container object")); + // remove object from its group (if any) obj->leaveGroup(); @@ -292,11 +295,26 @@ void OpenContain::addToContain( Object *rider ) if( rider == nullptr ) return; +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @bugfix Caball009 25/05/2026 Ensure the occupant is only added to a non-destroyed + // container to avoid an invalid state and use-after-free bugs when accessing the contained by pointer. + if (getObject()->isDestroyed()) + { + DEBUG_CRASH(("'%s' is about to be added to '%s', which is destroyed", + rider->getTemplate()->getName().str(), getObject()->getTemplate()->getName().str())); + return; + } +#endif + // TheSuperHackers @bugfix Stubbjax 06/02/2026 Ensure the rider is not destroyed to prevent a // likely crash if it enters the container on the same frame. If this occurs with an unpatched // client present in a match, the game has a small chance to mismatch. if (rider->isDestroyed()) + { + DEBUG_CRASH(("'%s', which is destroyed, is about to be added to '%s'", + rider->getTemplate()->getName().str(), getObject()->getTemplate()->getName().str())); return; + } Drawable *riderDraw = rider->getDrawable(); Bool wasSelected = FALSE; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp index fe364eee869..cdadb37d560 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/TransportContain.cpp @@ -469,8 +469,19 @@ UpdateSleepTime TransportContain::update() { const TransportContainModuleData *moduleData = getTransportContainModuleData(); - if( m_payloadCreated == FALSE ) + if (m_payloadCreated == FALSE) + { +#if RETAIL_COMPATIBLE_CRC createPayload(); +#else + // TheSuperHackers @bugfix Caball009 25/05/2026 Don't create payload + // for destroyed object to avoid leaving the payload in an invalid state. + if (!getObject()->isDestroyed()) + { + createPayload(); + } +#endif + } if( moduleData && moduleData->m_healthRegen ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index f88e77b3b5c..0ebb8c06c69 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -183,7 +183,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu m_physics(nullptr), m_geometryInfo(tt->getTemplateGeometryInfo()), m_containedBy(nullptr), - m_xferContainedByID(INVALID_ID), + m_containedByID(INVALID_ID), m_containedByFrame(0), m_behaviors(nullptr), m_body(nullptr), @@ -692,6 +692,22 @@ void Object::onContainedBy( Object *containedBy ) m_containedBy = containedBy; m_containedByFrame = TheGameLogic->getFrame(); +#if RETAIL_COMPATIBLE_CRC + // TheSuperHackers @info Set INVALID_ID if the container object was destroyed + // to indicate that the pointer will become a dangling pointer in the next frame. + if (containedBy && !containedBy->isDestroyed()) + { + m_containedByID = containedBy->getID(); + } + else + { + m_containedByID = INVALID_ID; + } +#else + DEBUG_ASSERTCRASH(containedBy == nullptr || !containedBy->isDestroyed(), + ("Object::onContainedBy - Adding into a destroyed container")); +#endif + handlePartitionCellMaintenance(); // which should unlook me now that I am contained } @@ -705,6 +721,10 @@ void Object::onRemovedFrom( Object *removedFrom ) m_containedBy = nullptr; m_containedByFrame = 0; +#if RETAIL_COMPATIBLE_CRC + m_containedByID = INVALID_ID; +#endif + handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again } @@ -757,9 +777,33 @@ void Object::onDestroy() { // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) + if (m_containedBy) { - m_containedBy->getContain()->removeFromContain( this ); +#if RETAIL_COMPATIBLE_CRC + if (m_containedByID == INVALID_ID) + { + // TheSuperHackers @bugfix Caball009 25/05/2026 Due to a potential use-after-free bug that cannot be fixed + // with retail compatibility, the 'contained by' pointer of this object may point to an already destroyed object. + // Avoid removing this object from the contain list, because it could crash the game, + // as the begin / end iterator for STLPort and MSVC std::list implementations depends on dynamically allocated memory. + DEBUG_CRASH(("container object must be valid; this looks like use-after-free")); + } + else + { + DEBUG_ASSERTCRASH(TheGameLogic->findObjectByID(m_containedByID) == m_containedBy, + ("contained by pointer is out of sync with contained by ID")); + + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } + } +#else + if (ContainModuleInterface* contain = m_containedBy->getContain()) + { + contain->removeFromContain(this); + } +#endif } // @@ -4249,16 +4293,18 @@ void Object::xfer( Xfer *xfer ) // No, the contain module is just going to friend_ reach in and set this for us. // Containers more complicated than Open (like Tunnel) can't do that. Our variable, // our responsibility. +#if !RETAIL_COMPATIBLE_CRC + // TheSuperHackers @tweak Contained by ID is already set with retail compatibility; don't overwrite it. if( xfer->getXferMode() == XFER_SAVE ) { if( m_containedBy != nullptr ) - m_xferContainedByID = m_containedBy->getID(); + m_containedByID = m_containedBy->getID(); else - m_xferContainedByID = INVALID_ID; + m_containedByID = INVALID_ID; } +#endif - - xfer->xferObjectID( &m_xferContainedByID ); + xfer->xferObjectID( &m_containedByID ); } // contained by frame @@ -4483,8 +4529,8 @@ void Object::xfer( Xfer *xfer ) //------------------------------------------------------------------------------------------------- void Object::loadPostProcess() { - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + if( m_containedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_containedByID); else m_containedBy = nullptr; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp index d4d0cead9dd..333d39662c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/BattlePlanUpdate.cpp @@ -164,6 +164,7 @@ BattlePlanUpdate::~BattlePlanUpdate() TheAudio->removeAudioEvent( m_holdTheLineUnpack.getPlayingHandle() ); TheAudio->removeAudioEvent( m_holdTheLinePack.getPlayingHandle() ); + deleteInstance(m_bonuses); } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp b/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp index 4a475b3b16e..71bcb44e26d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/OpenALAudioManager.cpp @@ -430,17 +430,19 @@ Bool OpenALAudioManager::isCurrentlyPlaying(AudioHandle handle) /** * Music playback - next track */ -void OpenALAudioManager::nextMusicTrack(void) +AsciiString OpenALAudioManager::nextMusicTrack(void) { fprintf(stderr, "DEBUG: OpenALAudioManager::nextMusicTrack() - stub\n"); + return AsciiString::TheEmptyString; } /** * Music playback - previous track */ -void OpenALAudioManager::prevMusicTrack(void) +AsciiString OpenALAudioManager::prevMusicTrack(void) { fprintf(stderr, "DEBUG: OpenALAudioManager::prevMusicTrack() - stub\n"); + return AsciiString::TheEmptyString; } /** @@ -460,14 +462,6 @@ Bool OpenALAudioManager::hasMusicTrackCompleted(const AsciiString &trackName, In return false; } -/** - * Get current music track name - */ -AsciiString OpenALAudioManager::getMusicTrackName(void) const -{ - return m_currentMusicTrack; -} - // GeneralsX @build BenderAI 13/02/2026 - Implement remaining pure virtual methods (Phase 2 stubs) // These methods are required by AudioManager interface but will be fully implemented in Phase 2 diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp index 54fdfb732e3..377e813236e 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hrawanim.cpp @@ -236,14 +236,13 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) /* ** Now, read in all of the other chunks (motion channels). */ - MotionChannelClass * newchan; - BitChannelClass * newbitchan; - while (cload.Open_Chunk()) { switch (cload.Cur_Chunk_ID()) { case W3D_CHUNK_ANIMATION_CHANNEL: + { + MotionChannelClass* newchan = nullptr; if (!read_channel(cload,&newchan,pre30)) { goto Error; } @@ -258,8 +257,11 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newchan; } break; + } case W3D_CHUNK_BIT_CHANNEL: + { + BitChannelClass* newbitchan = nullptr; if (!read_bit_channel(cload,&newbitchan,pre30)) { goto Error; } @@ -274,6 +276,7 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) delete newbitchan; } break; + } default: break; @@ -304,15 +307,22 @@ int HRawAnimClass::Load_W3D(ChunkLoadClass & cload) *=============================================================================================*/ bool HRawAnimClass::read_channel(ChunkLoadClass & cload,MotionChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW MotionChannelClass; - bool result = (*newchan)->Load_W3D(cload); + MotionChannelClass* channel = W3DNEW MotionChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->Set_Pivot(channel->Get_Pivot() + 1); + } - if (result && pre30) { -// (*newchan)->PivotIdx += 1; - (*newchan)->Set_Pivot((*newchan)->Get_Pivot()+1); + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } /*********************************************************************************************** @@ -379,14 +389,22 @@ void HRawAnimClass::add_channel(MotionChannelClass * newchan) *=============================================================================================*/ bool HRawAnimClass::read_bit_channel(ChunkLoadClass & cload,BitChannelClass * * newchan,bool pre30) { - *newchan = W3DNEW BitChannelClass; - bool result = (*newchan)->Load_W3D(cload); + BitChannelClass* channel = W3DNEW BitChannelClass; + if (channel->Load_W3D(cload)) + { + if (pre30) + { + channel->PivotIdx += 1; + } - if (result && pre30) { - (*newchan)->PivotIdx += 1; + *newchan = channel; + return true; + } + else + { + delete channel; + return false; } - - return result; } diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index fd36b02b024..27af92ec44a 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -1584,6 +1584,9 @@ void ScriptDialog::OnLoad() msg += m_readPlayerNames[i].str(); msg += ", discarding scripts for this player."; ::AfxMessageBox(msg); + + deleteInstance(scripts[i]); + scripts[i] = nullptr; continue; } } @@ -1591,8 +1594,8 @@ void ScriptDialog::OnLoad() curSide = 0; ::AfxMessageBox("Imported scripts came from more players than exist in this map. Additional scripts moved to Neutral player."); } - ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); + ScriptList *pSL = m_sides.getSideInfo(curSide)->getScriptList(); if (pSL) { Script *pScr; Script *pNextScr; @@ -1616,8 +1619,10 @@ void ScriptDialog::OnLoad() copied into the current scripts. */ scripts[i] = nullptr; //reloadPlayer(curSide, pSL); + } else { + deleteInstance(scripts[i]); + scripts[i] = nullptr; } - } for (i = 0; i < m_sides.getNumSides(); i++) { diff --git a/docs/WORKDIR/planning/SYNC_2026-06-05_TSUPERHACKERS_PLAN.md b/docs/WORKDIR/planning/SYNC_2026-06-05_TSUPERHACKERS_PLAN.md new file mode 100644 index 00000000000..0e2c19ef498 --- /dev/null +++ b/docs/WORKDIR/planning/SYNC_2026-06-05_TSUPERHACKERS_PLAN.md @@ -0,0 +1,101 @@ +# TheSuperHackers Upstream Sync — Conflict Resolution Plan + +**Date:** 2026-06-05 +**Branch:** `thesuperhackers-sync-06-05-2026` +**Sync base:** `ed7e96f8f` (unify(shroud) — common ancestor of `main` and `thesuperhackers/main`) +**Upstream tip:** `f334383ec` (bugfix(contain)) +**Commits imported:** 6 (ahead of our `main`) + +## 1. Scope of the Sync + +Upstream commits pulled in this sync: + +| SHA | Type | Title | Touched areas | +|---|---|---|---| +| `f334383ec` | bugfix | Prevent riders added to destroyed container (#2746) | `Generals{,MD}/.../Object/Contain/OpenContain.cpp`, `TransportContain.cpp` | +| `7c78a5e17` | refactor | Split `MetaEventTranslator::translateGameMessage()` (#2758) | `Generals{,MD}/.../MessageStream/MetaEvent.{h,cpp}` | +| `a7fad3bb2` | fix | Prevent multithread crash in `MilesAudioManager` (#2718) | `Core/.../MilesAudioDevice/MilesAudioManager.{h,cpp}`, `Dependencies/Utility/{CMakeLists.txt,Utility/interlocked_adapter.h}` | +| `b0becc407` | refactor | Clean up and simplify `MilesAudioManager` (#2718) | same + `Core/GameEngine/Include/Common/GameAudio.h` + `Generals{,MD}/.../MessageStream/CommandXlat.cpp` | +| `df2224bf1` | bugfix | Avoid crash with dangling contain in `Object::onDestroy` (#2747) | `Generals{,MD}/.../GameLogic/Object.{h,cpp}` | +| `20f42549c` | fix(memory) | Fix various memory leaks (2) (#2710) | `Generals{,MD}/.../GameEngine.cpp`, `Player.cpp`, `BattlePlanUpdate.cpp`, `SidesList.cpp`, `Drawable.cpp`, `W3DModelDraw.cpp`, `GameLogicDispatch.cpp`, `GameStateMap.cpp` + `Generals{,MD}/.../Libraries/Source/WWVegas/WW3D2/{hcanim,hrawanim,seglinerenderer}.cpp` + `Generals/.../Tools/WorldBuilder/src/ScriptDialog.cpp` | + +## 2. Subsystem Risk Assessment + +| Subsystem | Risk | Notes | +|---|---|---| +| **Input / MetaEvent** | HIGH | GeneralsX has a pre-existing local divergence (mods-only-changed detection + `m_lastKeyDown`/`m_lastModState` members). Upstream refactor (PR #2758) splits `translateGameMessage` into helper functions and uses a new `m_keyDownInfos[KEY_COUNT]` bit-array per key. **Conflict expected.** | +| **MilesAudioManager** | LOW | Source files are listed in `Core/GameEngineDevice/CMakeLists.txt` as a future-only/exploratory comment block — not compiled by any GeneralsX preset. New `interlocked_adapter.h` is small and inert. | +| **Object / Contain** | LOW | Upstream changes are pure additions (defensive null checks, mod-skip). No conflict expected. | +| **Memory leak fixes** | LOW | Additive in upstream. Conflict unlikely. GeneralsX code paths untouched. | +| **CI / workflows / docs** | NONE | None of the upstream commits touch `.github/`, `docs/`, `cmake/`, `scripts/`. We retain all GeneralsX CI infrastructure intact. | +| **Platform abstraction (SDL3/DXVK/OpenAL/FFmpeg)** | NONE | None of the upstream commits touch `Core/GameEngineDevice/` platform paths used by GeneralsX at runtime, or `Core/Libraries/Source/Platform/`. | + +## 3. Conflict Resolution Strategy + +### 3.1 `MetaEvent.cpp` (Generals/ and GeneralsMD/) + +**Conflict source:** PR #2758 refactored `translateGameMessage` into four new helper functions (`onMouseEvent`, `onKeyEvent`, `onKeyModStateRemoved`, `onKeyPressed`, plus `getActionKeyType`/`getKeyModState` statics), and replaced the `m_lastKeyDown`/`m_lastModState` members with a `KeyDownInfo m_keyDownInfos[KEY_COUNT]` bit-array. + +**GeneralsX divergence:** A pre-existing local change had ALREADY simplified the input pipeline (removing the order-of-modifier-release branch that PR #2757 introduced) and added a "mods-only-changed" detection using `m_lastKeyDown` and `m_lastModState`. This GeneralsX behavior must be preserved. + +**Resolution:** Adopt the structural decomposition of the refactor, but with GeneralsX semantics inside the helper. Concretely: + +- Keep the new top-level dispatch: `onMouseEvent(msg)` and `onKeyEvent(msg, disp)`. +- **Inside `onKeyEvent`**, use GeneralsX's logic: + - Compute `newModState` from `systemKeyState` (CTRL/SHIFT/ALT). + - Loop `TheMetaMap` and check BOTH: + 1. The GeneralsX "mods-only-changed" branch (`map->m_key == MK_NONE && newModState != m_lastModState` and the UP/DOWN transitions). + 2. The "normal" key transition branch (`map->m_key == key && map->m_modState == newModState`). + - Fast-forward replay hack stays inline (GeneralsX keeps it here, upstream also keeps it in `onKeyPressed`). + - Update `m_lastKeyDown` on `MSG_RAW_KEY_DOWN`. + - Update `m_lastModState` at the end. +- **Do not** add `m_keyDownInfos`/`KeyDownInfo`, **do not** add `onKeyModStateRemoved`, `onKeyPressed`, `getActionKeyType`, `getKeyModState`. GeneralsX's mods-only-changed is the chosen behavior; introducing upstream's order-independent release would be a behavior change beyond the sync's scope. +- Keep GeneralsX's `MetaEventTranslator()` constructor that initializes `m_lastKeyDown(MK_NONE)` and `m_lastModState(0)`. +- Preserve the `DUMP_ALL_KEYS_TO_LOG` block (GeneralsMD only). + +### 3.2 `MetaEvent.h` (Generals/ and GeneralsMD/) + +The auto-merge produced an inconsistent state: it kept GeneralsX's `m_lastKeyDown`/`m_lastModState` members AND added upstream's helper function declarations, but it did NOT add the `KeyDownInfo` struct or `m_keyDownInfos[KEY_COUNT]` member (because they were on the same line as our existing members and git kept ours). + +**Resolution:** + +- Keep `m_lastKeyDown` and `m_lastModState` (GeneralsX semantics). +- Keep `onMouseEvent` and `onKeyEvent` declarations (upstream structure). +- **Remove** `onKeyModStateRemoved`, `onKeyPressed`, `getActionKeyType`, `getKeyModState` (unused — we won't define them in the `.cpp`). +- Do NOT add `KeyDownInfo` struct or `m_keyDownInfos`. + +### 3.3 `MilesAudioManager.{h,cpp}` and `interlocked_adapter.h` + +- Auto-merge result is acceptable. The `MilesAudioManager` is not compiled by any current GeneralsX preset (verified — the source path appears only in a comment block of `Core/GameEngineDevice/CMakeLists.txt`). +- `Dependencies/Utility/CMakeLists.txt` and `Dependencies/Utility/Utility/interlocked_adapter.h` are additive and safe. +- Will rebuild and re-run smoke to confirm no surprise side-effects. + +### 3.4 All Other Files (auto-merged) + +- Pure additive upstream changes. Trust the auto-merge. +- Sanity-check: re-run the configure flow on macOS (then Linux) and confirm the tree compiles. + +## 4. Special Constraints + +- **Do not** modify `.github/workflows/`, `.github/ISSUE_TEMPLATE/`, `.github/copilot-instructions.md`, or any CI configuration. None of the upstream commits touch these; the instruction is preventative. +- **Do not** introduce `m_keyDownInfos` / `KeyDownInfo` because it is semantically wrong for GeneralsX. Doing so would silently change input behavior beyond the sync's intent. +- **Do not** revert GeneralsX's mods-only-changed detection. It is the established GeneralsX input behavior. + +## 5. Validation Plan + +1. **macOS first** (primary validation target per the project's platform focus): + - `cmake --preset macos-vulkan` — verify configure succeeds with the merged tree. + - `cmake --build build/macos-vulkan --target z_generals` — verify build. +2. **Linux** (secondary, after macOS passes): + - `cmake --preset linux64-deploy` (or `linux64-testing` if dependencies are limited). + - `cmake --build build/linux64-deploy --target z_generals` — verify build. +3. **Smoke test** if binaries produce: run with `-win -logToCon` and confirm clean entry into the main loop. Use `scripts/qa/smoke/docker-smoke-test-zh.sh` if Docker is available, or `run.sh -win` on macOS/Linux host. + +## 6. Steps to Execute + +1. Resolve `MetaEvent.h` (Generals/ and GeneralsMD/) — remove unused upstream helper decls. +2. Resolve `MetaEvent.cpp` (Generals/ and GeneralsMD/) — adopt refactor with GeneralsX semantics. +3. Verify no `<<<<<<<` markers remain. +4. Run macOS configure + build. +5. Run Linux configure + build. +6. Commit, push, and provide final report.