From a3f77570416bfb417d2f409e46e59ae0d8131b33 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 13 Mar 2026 16:11:18 +0100 Subject: [PATCH 1/3] Transition: Init only attributes relevant for the current transition --- src/transition.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/transition.cpp b/src/transition.cpp index 2af6f93f45..b0ac138e72 100644 --- a/src/transition.cpp +++ b/src/transition.cpp @@ -124,24 +124,24 @@ void Transition::Init(Type type, Scene *linked_scene, int duration, bool next_er void Transition::SetAttributesTransitions() { int w, h, beg_i, mid_i, end_i, length; - zoom_position = std::vector(2); - random_blocks = std::vector(Player::screen_width * Player::screen_height / (size_random_blocks * size_random_blocks)); - mosaic_random_offset.resize(total_frames); + auto init_random_blocks = [&]() { + random_blocks = std::vector(Player::screen_width * Player::screen_height / (size_random_blocks * size_random_blocks)); + for (uint32_t i = 0; i < random_blocks.size(); i++) { + random_blocks[i] = i; + } - for (uint32_t i = 0; i < random_blocks.size(); i++) { - random_blocks[i] = i; - } + random_block_transition = Bitmap::Create(Player::screen_width, Player::screen_height, true); + current_blocks_print = 0; + }; switch (transition_type) { case TransitionRandomBlocks: - random_block_transition = Bitmap::Create(Player::screen_width, Player::screen_height, true); - current_blocks_print = 0; + init_random_blocks(); std::shuffle(random_blocks.begin(), random_blocks.end(), Rand::GetRNG()); break; case TransitionRandomBlocksDown: case TransitionRandomBlocksUp: - random_block_transition = Bitmap::Create(Player::screen_width, Player::screen_height, true); - current_blocks_print = 0; + init_random_blocks(); if (transition_type == TransitionRandomBlocksUp) { std::reverse(random_blocks.begin(), random_blocks.end()); } w = Player::screen_width / 4; @@ -161,6 +161,8 @@ void Transition::SetAttributesTransitions() { break; case TransitionZoomIn: case TransitionZoomOut: + zoom_position = std::vector(2); + if (scene != nullptr && scene->type == Scene::Map) { auto map = static_cast(scene); @@ -174,6 +176,8 @@ void Transition::SetAttributesTransitions() { break; case TransitionMosaicIn: case TransitionMosaicOut: + mosaic_random_offset.resize(total_frames); + for (int i = 0; i < total_frames; ++i) { // by default i 0..40 for scale 1..41 mosaic_random_offset[i] = Rand::GetRandomNumber(0, i); @@ -204,7 +208,7 @@ void Transition::Draw(Bitmap& dst) { dst.BlendBlit(0, 0, *screen1, screen1->GetRect(), color, 255); return; } - + int tf_off = total_frames - 1; switch (transition_type) { From 876ff5aeb62f43cdcfb30d2d80af2cabb0038297 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 13 Mar 2026 16:19:00 +0100 Subject: [PATCH 2/3] Transition: Fix Random Block transition crashing (Regression) Caused by #3482 --- src/transition.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/transition.cpp b/src/transition.cpp index b0ac138e72..deedb7714d 100644 --- a/src/transition.cpp +++ b/src/transition.cpp @@ -222,7 +222,9 @@ void Transition::Draw(Bitmap& dst) { case TransitionRandomBlocksUp: blocks_to_print = random_blocks.size() * (current_frame + 1) / tf_off; - for (uint32_t i = current_blocks_print; i < blocks_to_print; i++) { + // Off-by-one error? + // i < random_blocks.size() prevents out of bounds access on the final frame + for (uint32_t i = current_blocks_print; i < blocks_to_print && i < random_blocks.size(); i++) { random_block_transition->Blit(random_blocks[i] % (w / size_random_blocks) * size_random_blocks, random_blocks[i] / (w / size_random_blocks) * size_random_blocks, *screen2, Rect(random_blocks[i] % (w / size_random_blocks) * size_random_blocks, random_blocks[i] / (w / size_random_blocks) * size_random_blocks, From 7077f967e66392db7989e0635927bf5c23a268ce Mon Sep 17 00:00:00 2001 From: Ghabry Date: Fri, 13 Mar 2026 16:26:42 +0100 Subject: [PATCH 3/3] dr_wav: Bump to 0.14.5 Fixes CVE-2026-29022 --- src/external/dr_wav.h | 113 +++++++++++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 28 deletions(-) diff --git a/src/external/dr_wav.h b/src/external/dr_wav.h index 3e7770e920..a0a82fe81d 100644 --- a/src/external/dr_wav.h +++ b/src/external/dr_wav.h @@ -1,6 +1,6 @@ /* WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file. -dr_wav - v0.14.0 - 2025-07-23 +dr_wav - v0.14.5 - 2026-03-03 David Reid - mackron@gmail.com @@ -147,7 +147,7 @@ extern "C" { #define DRWAV_VERSION_MAJOR 0 #define DRWAV_VERSION_MINOR 14 -#define DRWAV_VERSION_REVISION 0 +#define DRWAV_VERSION_REVISION 5 #define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION) #include /* For size_t. */ @@ -2193,6 +2193,22 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars if (pMetadata != NULL && bytesJustRead == sizeof(smplHeaderData)) { drwav_uint32 iSampleLoop; + drwav_uint32 loopCount; + drwav_uint32 calculatedLoopCount; + + /* + When we calcualted the amount of memory required for the "smpl" chunk we excluded the chunk entirely + if the loop count in the header did not match with the calculated count based on the size of the + chunk. When this happens, the second stage will still hit this path but the `pMetadata` will be + non-null, but will either be pointing at the very end of the allocation or at the start of another + chunk. We need to check the loop counts for consistency *before* dereferencing the pMetadata object + so it's consistent with how we do it in the first stage. + */ + loopCount = drwav_bytes_to_u32(smplHeaderData + 28); + calculatedLoopCount = (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES; + if (loopCount != calculatedLoopCount) { + return totalBytesRead; + } pMetadata->type = drwav_metadata_type_smpl; pMetadata->data.smpl.manufacturerId = drwav_bytes_to_u32(smplHeaderData + 0); @@ -2209,7 +2225,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars The loop count needs to be validated against the size of the chunk for safety so we don't attempt to read over the boundary of the chunk. */ - if (pMetadata->data.smpl.sampleLoopCount == (pChunkHeader->sizeInBytes - DRWAV_SMPL_BYTES) / DRWAV_SMPL_LOOP_BYTES) { + if (pMetadata->data.smpl.sampleLoopCount == calculatedLoopCount) { pMetadata->data.smpl.pLoops = (drwav_smpl_loop*)drwav__metadata_get_memory(pParser, sizeof(drwav_smpl_loop) * pMetadata->data.smpl.sampleLoopCount, DRWAV_METADATA_ALIGNMENT); for (iSampleLoop = 0; iSampleLoop < pMetadata->data.smpl.sampleLoopCount; ++iSampleLoop) { @@ -2234,6 +2250,15 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars drwav__metadata_parser_read(pParser, pMetadata->data.smpl.pSamplerSpecificData, pMetadata->data.smpl.samplerSpecificDataSizeInBytes, &totalBytesRead); } + } else { + /* + Getting here means the loop count in the header does not match up with the size of the + chunk. Clear out the data to zero just to be safe. + + This should never actually get hit because we check for it above, but keeping this here + for added safety. + */ + DRWAV_ZERO_OBJECT(&pMetadata->data.smpl); } } @@ -5289,7 +5314,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_tell_stdio(void* pUserData, drwav_int64* pC DRWAV_ASSERT(pFileStdio != NULL); DRWAV_ASSERT(pCursor != NULL); -#if defined(_WIN32) +#if defined(_WIN32) && !defined(NXDK) #if defined(_MSC_VER) && _MSC_VER > 1200 result = _ftelli64(pFileStdio); #else @@ -5496,8 +5521,6 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, dr DRWAV_ASSERT(pWav != NULL); - newCursor = pWav->memoryStream.currentReadPos; - if (origin == DRWAV_SEEK_SET) { newCursor = 0; } else if (origin == DRWAV_SEEK_CUR) { @@ -5570,8 +5593,6 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offs DRWAV_ASSERT(pWav != NULL); - newCursor = pWav->memoryStreamWrite.currentWritePos; - if (origin == DRWAV_SEEK_SET) { newCursor = 0; } else if (origin == DRWAV_SEEK_CUR) { @@ -5580,7 +5601,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offs newCursor = (drwav_int64)pWav->memoryStreamWrite.dataSize; } else { DRWAV_ASSERT(!"Invalid seek origin"); - return DRWAV_INVALID_ARGS; + return DRWAV_FALSE; } newCursor += offset; @@ -6296,7 +6317,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav pWav->msadpcm.cachedFrameCount = 2; /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */ - if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table)) { + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= drwav_countof(coeff2Table)) { return totalFramesRead; /* Invalid file. */ } } else { @@ -6323,7 +6344,8 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav pWav->msadpcm.cachedFrameCount = 2; /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */ - if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= drwav_countof(coeff2Table)) { + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= drwav_countof(coeff2Table) || + pWav->msadpcm.predictor[1] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= drwav_countof(coeff2Table)) { return totalFramesRead; /* Invalid file. */ } } @@ -6377,14 +6399,16 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav drwav_int32 newSample0; drwav_int32 newSample1; + /* The predictor is read from the file and then indexed into a table. Check that it's in bounds. */ + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= drwav_countof(coeff2Table)) { + return totalFramesRead; + } + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } + pWav->msadpcm.delta[0] = (drwav_int32)drwav_clamp(((drwav_int64)adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8, 16, 0x7FFFFFFF); pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample0; @@ -6394,15 +6418,11 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav newSample1 += nibble1 * pWav->msadpcm.delta[0]; newSample1 = drwav_clamp(newSample1, -32768, 32767); - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } + pWav->msadpcm.delta[0] = (drwav_int32)drwav_clamp(((drwav_int64)adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8, 16, 0x7FFFFFFF); pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample1; - pWav->msadpcm.cachedFrames[2] = newSample0; pWav->msadpcm.cachedFrames[3] = newSample1; pWav->msadpcm.cachedFrameCount = 2; @@ -6412,28 +6432,30 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav drwav_int32 newSample1; /* Left. */ + if (pWav->msadpcm.predictor[0] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[0] >= drwav_countof(coeff2Table)) { + return totalFramesRead; /* Out of bounds. Invalid file. */ + } + newSample0 = ((pWav->msadpcm.prevFrames[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevFrames[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; newSample0 += nibble0 * pWav->msadpcm.delta[0]; newSample0 = drwav_clamp(newSample0, -32768, 32767); - pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; - if (pWav->msadpcm.delta[0] < 16) { - pWav->msadpcm.delta[0] = 16; - } + pWav->msadpcm.delta[0] = (drwav_int32)drwav_clamp(((drwav_int64)adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8, 16, 0x7FFFFFFF); pWav->msadpcm.prevFrames[0][0] = pWav->msadpcm.prevFrames[0][1]; pWav->msadpcm.prevFrames[0][1] = newSample0; /* Right. */ + if (pWav->msadpcm.predictor[1] >= drwav_countof(coeff1Table) || pWav->msadpcm.predictor[1] >= drwav_countof(coeff2Table)) { + return totalFramesRead; /* Out of bounds. Invalid file. */ + } + newSample1 = ((pWav->msadpcm.prevFrames[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevFrames[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; newSample1 += nibble1 * pWav->msadpcm.delta[1]; newSample1 = drwav_clamp(newSample1, -32768, 32767); - pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; - if (pWav->msadpcm.delta[1] < 16) { - pWav->msadpcm.delta[1] = 16; - } + pWav->msadpcm.delta[1] = (drwav_int32)drwav_clamp(((drwav_int64)adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8, 16, 0x7FFFFFFF); pWav->msadpcm.prevFrames[1][0] = pWav->msadpcm.prevFrames[1][1]; pWav->msadpcm.prevFrames[1][1] = newSample1; @@ -8057,6 +8079,12 @@ DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, uns DRWAV_ASSERT(pWav != NULL); + /* Check for overflow before multiplication. */ + if (pWav->channels == 0 || pWav->totalPCMFrameCount > DRWAV_SIZE_MAX / pWav->channels / sizeof(drwav_int16)) { + drwav_uninit(pWav); + return NULL; /* Overflow or invalid channels. */ + } + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int16); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); @@ -8099,6 +8127,12 @@ DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned DRWAV_ASSERT(pWav != NULL); + /* Check for overflow before multiplication. */ + if (pWav->channels == 0 || pWav->totalPCMFrameCount > DRWAV_SIZE_MAX / pWav->channels / sizeof(float)) { + drwav_uninit(pWav); + return NULL; /* Overflow or invalid channels. */ + } + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(float); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); @@ -8141,6 +8175,12 @@ DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, uns DRWAV_ASSERT(pWav != NULL); + /* Check for overflow before multiplication. */ + if (pWav->channels == 0 || pWav->totalPCMFrameCount > DRWAV_SIZE_MAX / pWav->channels / sizeof(drwav_int32)) { + drwav_uninit(pWav); + return NULL; /* Overflow or invalid channels. */ + } + sampleDataSize = pWav->totalPCMFrameCount * pWav->channels * sizeof(drwav_int32); if (sampleDataSize > DRWAV_SIZE_MAX) { drwav_uninit(pWav); @@ -8521,6 +8561,23 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b) /* REVISION HISTORY ================ +v0.14.5 - 2026-03-03 + - Fix a crash when loading files with a malformed "smpl" chunk. + - Fix a signed overflow bug with the MS-ADPCM decoder. + +v0.14.4 - 2026-01-17 + - Fix some compilation warnings. + +v0.14.3 - 2025-12-14 + - Fix a possible out-of-bounds read when reading from MS-ADPCM encoded files. + - Fix a possible integer overflow error. + +v0.14.2 - 2025-12-02 + - Fix a compilation warning. + +v0.14.1 - 2025-09-10 + - Fix an error with the NXDK build. + v0.14.0 - 2025-07-23 - API CHANGE: Seek origin enums have been renamed to the following: - drwav_seek_origin_start -> DRWAV_SEEK_SET