From 6e70f2b19309e5dc404b25bd742eb55542cd36f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 10 Nov 2026 16:57:12 +0100 Subject: [PATCH] sceVideocodec: Fail decoding an empty access unit, as the PSP does sceMpegAvcDecode called again on an access unit it has already decoded passes the ME an empty one, and on a PSP that fails with 0x80628002 (pspautotests video/mpeg/decoderepeat). We returned success with no picture. A movie player that retries the same access unit until a picture comes back then never got out of its loop, and as the decode took no time, it starved the lower-priority thread that feeds the ringbuffer. Co-Authored-By: Claude Opus 5.5 --- Core/HLE/sceVideocodec.cpp | 7 +++++++ Core/MIPS/Interpreter.cpp | 2 +- pspautotests | 2 +- test.py | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Core/HLE/sceVideocodec.cpp b/Core/HLE/sceVideocodec.cpp index b9023c319a..a43be1f080 100644 --- a/Core/HLE/sceVideocodec.cpp +++ b/Core/HLE/sceVideocodec.cpp @@ -759,6 +759,13 @@ static int sceVideocodecDecode(u32 ctxAddr, int type) { // is about to read. Nor is a picture announced until the buffers holding it have been // published: saying "one image decoded" and then failing to allocate would have mpeg.prx // convert from whatever the descriptor pointed at last. + if (auSize <= 0) { + // An empty access unit is an error, and the stream carries on normally after it + // (pspautotests video/mpeg/decoderepeat). sceMpegAvcDecode passes one when called again + // on a unit it already decoded, which some movie players do until a picture comes back. + // The error is what ends their loop. + return hleLogDebug(Log::ME, SCE_MPEG_ERROR_AVC_DECODE_FATAL, "type 0, no access unit"); + } int pictures = 0; if (au && auBytes > 0) { if (vctx.decoder->DecodePictures(au, auBytes) < 0) { diff --git a/Core/MIPS/Interpreter.cpp b/Core/MIPS/Interpreter.cpp index 595bec640c..2449ae82f0 100644 --- a/Core/MIPS/Interpreter.cpp +++ b/Core/MIPS/Interpreter.cpp @@ -89,7 +89,7 @@ static inline void DelaySlotJumpTo(MIPSState *mips, u32 where) { if (!InJumpDelaySlot(mips)) { return; } - if (!mips->space->IsValidAddress(where) || (where & 3) != 0) { + if (!mips->space->IsValid4AlignedAddress(where)) { Core_ExecException(where, PC, ExecExceptionType::JUMP); } mips->nextPC = where; diff --git a/pspautotests b/pspautotests index fcd60cb905..ab2245ec6f 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit fcd60cb9055c233f50f20e010e65a5ff30ac8539 +Subproject commit ab2245ec6fe733b7a0c9214b75538983455d3fdd diff --git a/test.py b/test.py index 5a4c567cf5..aac6c9f46d 100755 --- a/test.py +++ b/test.py @@ -795,6 +795,7 @@ tests_next = [ "video/mp4/avchold", # needs Sony's mp4 and mpeg modules: --disable-hle=0x110, see docs/pspautotests.md "video/mpeg/basic", "video/mpeg/decodeend", # needs Sony's mpeg module, as avchold + "video/mpeg/decoderepeat", # needs Sony's mpeg module, as avchold "video/mpeg/deletetiming", "video/pmf/pmf", "video/pmf_simple/pmf_simple",