Compare commits

...

13 Commits

Author SHA1 Message Date
Dennis Felsing 516315e0e2
Merge pull request #8273 from Robyt3/Sound-Various-Fixes
Fix crashes and memory leaks related to editor map sounds and opus file decoding, refactoring
2024-04-27 15:45:55 +00:00
Dennis Felsing a36ae188a8
Merge pull request #8274 from Robyt3/Server-ChangeMap-Argument
Make `change_map` parameter non-optional
2024-04-27 15:45:31 +00:00
Robert Müller 4f94316c72 Make `change_map` parameter non-optional
The command confusingly assumed the empty string if no argument was passed.
2024-04-27 13:45:51 +02:00
Robert Müller 9cf3094934 Also check for incorrect sample index with assertion
Ensure the sample being allocated is not currently used also by checking its next free sample index.
2024-04-27 13:11:35 +02:00
Robert Müller d06f6d3370 Handle failure of `op_pcm_total` function
According to the documentation, this function returns `0` on success and a negative number (error code) otherwise, which would cause an allocation of an invalid size.
2024-04-27 13:11:35 +02:00
Robert Müller d0e27fdcd4 Fix memory leak of opus file structure
We were not calling `op_free` for `OggOpusFile *` after decoding opus files.
2024-04-27 13:04:05 +02:00
Robert Müller cfb5b15222 Log error code if opus file cannot be opened 2024-04-27 13:03:58 +02:00
Robert Müller 51012bcc1b Fix potential out-of-bounds writes on invalid opus files
The third parameter of the `op_read` function specifies the remaining size of the buffer, but we always passed the total size of the buffer without respecting the position at which the data is written into the buffer.
2024-04-27 13:03:18 +02:00
Robert Müller 4d37775c17 Only change sample variables when it was decoded successfully
Avoid changing the sample member variables until the sample was decoded successfully.
2024-04-27 13:02:36 +02:00
Robert Müller 1153507216 Fix double-free when reading opus file fails
Set the data pointer of the sample only when the sample has been loaded successfully, so the invalid sample data is not freed again when decoding fails.
2024-04-27 13:02:23 +02:00
Robert Müller 941a302c4d Ensure editor preview image and sound are unloaded properly
The preview image was previously not unloaded and the preview state was not reset.
2024-04-27 13:01:56 +02:00
Robert Müller 3b0d9cd6c9 Fix editor crash with sound preview when sound is not valid
This happens if a sound could not be loaded successfully.
2024-04-27 13:01:26 +02:00
Robert Müller 690912e209 Fix invalid sound index used when loading external sound fails
This should be highly unlikely because external sound mapres were never supported by the editor.
2024-04-27 13:00:54 +02:00
4 changed files with 44 additions and 29 deletions

View File

@ -292,14 +292,15 @@ CSample *CSound::AllocSample()
return nullptr;
CSample *pSample = &m_aSamples[m_FirstFreeSampleIndex];
m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex;
pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED;
if(pSample->m_pData != nullptr)
if(pSample->m_pData != nullptr || pSample->m_NextFreeSampleIndex == SAMPLE_INDEX_USED)
{
char aError[64];
str_format(aError, sizeof(aError), "Sample was not unloaded (index=%d, duration=%f)", pSample->m_Index, pSample->TotalTime());
char aError[128];
str_format(aError, sizeof(aError), "Sample was not unloaded (index=%d, next=%d, duration=%f, data=%p)",
pSample->m_Index, pSample->m_NextFreeSampleIndex, pSample->TotalTime(), pSample->m_pData);
dbg_assert(false, aError);
}
m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex;
pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED;
return pSample;
}
@ -341,29 +342,36 @@ void CSound::RateConvert(CSample &Sample) const
bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const
{
OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, nullptr);
int OpusError = 0;
OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, &OpusError);
if(pOpusFile)
{
const int NumChannels = op_channel_count(pOpusFile, -1);
const int NumSamples = op_pcm_total(pOpusFile, -1); // per channel!
Sample.m_Channels = NumChannels;
if(Sample.m_Channels > 2)
if(NumChannels > 2)
{
op_free(pOpusFile);
dbg_msg("sound/opus", "file is not mono or stereo.");
return false;
}
Sample.m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
const int NumSamples = op_pcm_total(pOpusFile, -1); // per channel!
if(NumSamples < 0)
{
op_free(pOpusFile);
dbg_msg("sound/opus", "failed to get number of samples, error %d", NumSamples);
return false;
}
short *pSampleData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
int Pos = 0;
while(Pos < NumSamples)
{
const int Read = op_read(pOpusFile, Sample.m_pData + Pos * NumChannels, NumSamples * NumChannels, nullptr);
const int Read = op_read(pOpusFile, pSampleData + Pos * NumChannels, (NumSamples - Pos) * NumChannels, nullptr);
if(Read < 0)
{
free(Sample.m_pData);
free(pSampleData);
op_free(pOpusFile);
dbg_msg("sound/opus", "op_read error %d at %d", Read, Pos);
return false;
}
@ -372,15 +380,19 @@ bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) c
Pos += Read;
}
op_free(pOpusFile);
Sample.m_pData = pSampleData;
Sample.m_NumFrames = Pos;
Sample.m_Rate = 48000;
Sample.m_Channels = NumChannels;
Sample.m_LoopStart = -1;
Sample.m_LoopEnd = -1;
Sample.m_PausedAt = 0;
}
else
{
dbg_msg("sound/opus", "failed to decode sample");
dbg_msg("sound/opus", "failed to decode sample, error %d", OpusError);
return false;
}
@ -459,10 +471,7 @@ bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) con
const unsigned int SampleRate = WavpackGetSampleRate(pContext);
const int NumChannels = WavpackGetNumChannels(pContext);
Sample.m_Channels = NumChannels;
Sample.m_Rate = SampleRate;
if(Sample.m_Channels > 2)
if(NumChannels > 2)
{
dbg_msg("sound/wv", "file is not mono or stereo.");
s_pWVBuffer = nullptr;
@ -498,6 +507,8 @@ bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) con
#endif
Sample.m_NumFrames = NumSamples;
Sample.m_Rate = SampleRate;
Sample.m_Channels = NumChannels;
Sample.m_LoopStart = -1;
Sample.m_LoopEnd = -1;
Sample.m_PausedAt = 0;

View File

@ -1379,12 +1379,17 @@ void CEditor::DoToolbarSounds(CUIRect ToolBar)
if(pSelectedSound->m_SoundId != m_ToolbarPreviewSound && m_ToolbarPreviewSound >= 0 && Sound()->IsPlaying(m_ToolbarPreviewSound))
Sound()->Stop(m_ToolbarPreviewSound);
m_ToolbarPreviewSound = pSelectedSound->m_SoundId;
}
else
{
m_ToolbarPreviewSound = -1;
}
if(m_ToolbarPreviewSound >= 0)
{
static int s_PlayPauseButton, s_StopButton, s_SeekBar = 0;
DoAudioPreview(ToolBarBottom, &s_PlayPauseButton, &s_StopButton, &s_SeekBar, m_ToolbarPreviewSound);
}
else
m_ToolbarPreviewSound = -1;
}
static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation)
@ -8705,11 +8710,10 @@ void CEditor::OnClose()
void CEditor::OnDialogClose()
{
if(m_FilePreviewSound >= 0)
{
Sound()->UnloadSample(m_FilePreviewSound);
m_FilePreviewSound = -1;
}
Graphics()->UnloadTexture(&m_FilePreviewImage);
Sound()->UnloadSample(m_FilePreviewSound);
m_FilePreviewSound = -1;
m_FilePreviewState = PREVIEW_UNLOADED;
}
void CEditor::LoadCurrentMap()

View File

@ -10,7 +10,7 @@ public:
explicit CEditorSound(CEditor *pEditor);
~CEditorSound();
int m_SoundId = 0;
int m_SoundId = -1;
char m_aName[IO_MAX_PATH_LENGTH] = "";
void *m_pData = nullptr;

View File

@ -3068,7 +3068,7 @@ void CGameContext::ConPause(IConsole::IResult *pResult, void *pUserData)
void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *)pUserData;
pSelf->m_pController->ChangeMap(pResult->NumArguments() ? pResult->GetString(0) : "");
pSelf->m_pController->ChangeMap(pResult->GetString(0));
}
void CGameContext::ConRandomMap(IConsole::IResult *pResult, void *pUserData)
@ -3513,7 +3513,7 @@ void CGameContext::OnConsoleInit()
Console()->Register("mapbug", "s[mapbug]", CFGFLAG_SERVER | CFGFLAG_GAME, ConMapbug, this, "Enable map compatibility mode using the specified bug (example: grenade-doubleexplosion@ddnet.tw)");
Console()->Register("switch_open", "i[switch]", CFGFLAG_SERVER | CFGFLAG_GAME, ConSwitchOpen, this, "Whether a switch is deactivated by default (otherwise activated)");
Console()->Register("pause_game", "", CFGFLAG_SERVER, ConPause, this, "Pause/unpause game");
Console()->Register("change_map", "?r[map]", CFGFLAG_SERVER | CFGFLAG_STORE, ConChangeMap, this, "Change map");
Console()->Register("change_map", "r[map]", CFGFLAG_SERVER | CFGFLAG_STORE, ConChangeMap, this, "Change map");
Console()->Register("random_map", "?i[stars]", CFGFLAG_SERVER, ConRandomMap, this, "Random map");
Console()->Register("random_unfinished_map", "?i[stars]", CFGFLAG_SERVER, ConRandomUnfinishedMap, this, "Random unfinished map");
Console()->Register("restart", "?i[seconds]", CFGFLAG_SERVER | CFGFLAG_STORE, ConRestart, this, "Restart in x seconds (0 = abort)");