From 00770764a73bd50df0e8edef6d2d74777c9bc155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 17:42:01 -0600 Subject: [PATCH] Run the real mpeg.prx in place of our sceMpeg HLE Add flash0:/kd/mpeg.prx to the sceUtility module swap, hung off av_mpegbase, so it loads when DisableHLEFlags::sceMpeg is set. Games that ship their own sceMpeg_library on the disc never get here and just use theirs. The piece that was missing to make it work: the access unit address mpeg.prx passes to sceVideocodecDecode is in Media Engine space, which we can't read. On hardware sceMpegBasePESpacketCopy DMA'd the payload there first. That copy is ours, so it now gathers the blocks and sceVideocodec decodes from those, falling back to main memory for any caller that points at it directly. The gather is keyed by destination, because that call carries the audio payload too - video to an ME address, audio to main memory - and handing an ATRAC packet to the H.264 decoder gets you nothing. With that, video/mpeg/basic gets a 144x80 frame out of real Sony code driving our sceVideocodec through ffmpeg. The test still fails overall, as it did before this branch - it is in tests_next. Co-Authored-By: Claude Opus 5 (1M context) --- Core/HLE/sceMpeg.cpp | 10 ++++++++++ Core/HLE/sceMpegbase.cpp | 40 +++++++++++++++++++++++++++++++++++++- Core/HLE/sceMpegbase.h | 9 +++++++++ Core/HLE/sceUtility.cpp | 19 +++++++++++++++--- Core/HLE/sceVideocodec.cpp | 24 +++++++++++++++++++---- 5 files changed, 94 insertions(+), 8 deletions(-) diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 9ca6ef58c9..a31617efe0 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -26,6 +26,7 @@ #include "Core/HLE/sceMpegbase.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelThread.h" +#include "Core/Config.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/ErrorCodes.h" @@ -348,6 +349,15 @@ private: }; void __MpegInit() { + // We used to load flash0's mpeg.prx here, before the game module, on the theory that a game + // calling sceMpegInit without asking sceUtility for the AV module first would otherwise find + // nothing to resolve against. In practice a game either ships its own sceMpeg_library on the + // disc (Death Jr., Pursuit Force) or asks sceUtility (Thrillville), and both of those paths + // arrive in time on their own - while loading it here costs 33KB at the top of user memory + // for nothing. Stacks allocate from the top, so that pushed Pursuit Force's main thread stack + // down by exactly that much and left the largest free block just under the 256KB it wanted, + // which failed the boot outright. If a game turns up that really does need it early, make the + // load conditional on the game not providing its own rather than bringing this back. __MpegBaseInit(); isMpegInit = false; mpegLibVersion = 0x010A; diff --git a/Core/HLE/sceMpegbase.cpp b/Core/HLE/sceMpegbase.cpp index 7fe02ae97d..cfcac524b3 100644 --- a/Core/HLE/sceMpegbase.cpp +++ b/Core/HLE/sceMpegbase.cpp @@ -20,6 +20,7 @@ // mpeg.prx drives these directly, so they have to be real for the firmware module to run in place // of our sceMpeg HLE. +#include #include #include "Common/Swap.h" @@ -31,12 +32,18 @@ #include "Core/MemMapHelpers.h" #include "GPU/ge_constants.h" +// The PES payloads gathered by sceMpegBasePESpacketCopy, keyed by the destination each was +// copied to. It carries audio as well as video - the destination is what tells them apart - so +// sceVideocodec has to ask for the one matching the address it was handed. +static std::map> g_pesPackets; + // Set by sceMpegBaseCscInit / sceMpegBaseCscSetPixelMode, and used when the caller passes 0. static int g_mpegBaseBufferWidth = 512; static int g_mpegBasePixelMode = GE_CMODE_32BIT_ABGR8888; void __MpegBaseInit() { // None of this survives a boot on hardware. + g_pesPackets.clear(); g_mpegBaseBufferWidth = 512; g_mpegBasePixelMode = GE_CMODE_32BIT_ABGR8888; } @@ -56,10 +63,41 @@ static u32 sceMpegBasePESpacketCopy(u32 p) } MpegSetPmpVideoSource(p, nBlocks); - DEBUG_LOG(Log::Mpeg, "sceMpegBasePESpacketCopy(%08x), received %d block(s)", p, nBlocks); + // On hardware this is the DMA that moves the PES payload into the Media Engine's own memory, + // after which mpeg.prx hands sceVideocodecDecode an ME-side address we have no way to read. + // Since the copy is ours, gather the blocks here instead and let sceVideocodec decode from + // this - see MpegBaseGetPESPacket. + lli = PSPPointer::Create(p); + u32 dest = 0; + std::vector gathered; + for (int i = 0; i < nBlocks && lli.IsValid(); i++) { + if (i == 0) { + dest = lli->pDst; + } + // The list is game-supplied, so check the span before taking a pointer to it: the range + // accessors raise a memory exception rather than returning null, and a malformed block + // shouldn't fault the game when the intent here is to skip it. + const u8 *src = (lli->iSize > 0 && Memory::IsValidRange(lli->pSrc, lli->iSize)) + ? Memory::GetTypedPointerRange(lli->pSrc, lli->iSize) : nullptr; + if (src) { + gathered.insert(gathered.end(), src, src + lli->iSize); + } + ++lli; + } + if (dest != 0) { + g_pesPackets[dest] = std::move(gathered); + } + + DEBUG_LOG(Log::Mpeg, "sceMpegBasePESpacketCopy(%08x), %d block(s) -> %08x, %d bytes", + p, nBlocks, dest, dest ? (int)g_pesPackets[dest].size() : 0); return 0; } +const std::vector *MpegBaseGetPESPacket(u32 dest) { + auto it = g_pesPackets.find(dest); + return it == g_pesPackets.end() ? nullptr : &it->second; +} + // --- sceMpegbase colour conversion --------------------------------------------------------- // diff --git a/Core/HLE/sceMpegbase.h b/Core/HLE/sceMpegbase.h index 98fbad71bd..50334d5d77 100644 --- a/Core/HLE/sceMpegbase.h +++ b/Core/HLE/sceMpegbase.h @@ -17,7 +17,16 @@ #pragma once +#include + +#include "Common/CommonTypes.h" + void Register_sceMpegbase(); // Called per boot, from __MpegInit. void __MpegBaseInit(); + +// The PES payload sceMpegBasePESpacketCopy gathered for a given destination. On hardware that copy +// lands in Media Engine memory, which sceVideocodec would then read back; we keep it here instead. +// Returns nullptr if nothing was copied to that address. +const std::vector *MpegBaseGetPESPacket(u32 dest); diff --git a/Core/HLE/sceUtility.cpp b/Core/HLE/sceUtility.cpp index 3f16d40a3d..21be540051 100644 --- a/Core/HLE/sceUtility.cpp +++ b/Core/HLE/sceUtility.cpp @@ -137,11 +137,23 @@ static void LoadFirmwareModules(const char *library, const FirmwareModule *modul } } -// libmp3.prx imports nothing but the kernel and sceAudiocodec, which we have. -static void NotifyLoadStatusMp3(int state, u32 loadAddr, u32 totalSize) { +// mpeg.prx needs sceVideocodec, sceMpegbase and sceAudiocodec from us, all of which we implement, +// so the module itself is the only thing that has to come from somewhere real. +static void NotifyLoadStatusMpegBase(int state, u32 loadAddr, u32 totalSize) { // The effective flags, not the raw setting: those also account for the compat flags, for a // firmware dump that isn't there, and for the boundary a savestate restored - resolving // imports one way and loading modules the other is how a game ends up with neither. + if (state != 1 || !(GetEffectiveDisableHLEFlags() & DisableHLEFlags::sceMpeg)) { + return; + } + static const FirmwareModule modules[] = { + { "flash0:/kd/mpeg.prx", "sceMpeg_library" }, + }; + LoadFirmwareModules("sceMpeg", modules, ARRAY_SIZE(modules)); +} + +// libmp3.prx imports nothing but the kernel and sceAudiocodec, which we have. +static void NotifyLoadStatusMp3(int state, u32 loadAddr, u32 totalSize) { if (state != 1 || !(GetEffectiveDisableHLEFlags() & DisableHLEFlags::sceMp3)) { return; } @@ -203,6 +215,7 @@ static void NotifyLoadStatusAtrac(int state, u32 loadAddr, u32 totalSize) { static DisableHLEFlags UtilityModuleLibraryFlag(u32 module) { switch (module) { case 0x302: return DisableHLEFlags::sceAtrac; // av_atrac3plus + case 0x303: return DisableHLEFlags::sceMpeg; // av_mpegbase case 0x304: return DisableHLEFlags::sceMp3; // av_mp3 case 0x308: return DisableHLEFlags::sceMp4; // av_mp4 default: return (DisableHLEFlags)0; @@ -235,7 +248,7 @@ static const ModuleLoadInfo moduleLoadInfo[] = { // The size varies a bit per version, from about 0x3C00 to 0x4500 bytes. We could make a lookup table... // Changing this breaks some bad cheats though.. ModuleLoadInfo(0x302, 0x00008000, "av_atrac3plus", atrac3PlusModuleDeps, &NotifyLoadStatusAtrac), - ModuleLoadInfo(0x303, 0x0000c000, "av_mpegbase", mpegBaseModuleDeps), + ModuleLoadInfo(0x303, 0x0000c000, "av_mpegbase", mpegBaseModuleDeps, &NotifyLoadStatusMpegBase), ModuleLoadInfo(0x304, 0x00004000, "av_mp3", &NotifyLoadStatusMp3), ModuleLoadInfo(0x305, 0x0000a300, "av_vaudio"), ModuleLoadInfo(0x306, 0x00004000, "av_aac"), diff --git a/Core/HLE/sceVideocodec.cpp b/Core/HLE/sceVideocodec.cpp index dfcfa120d7..15c83f5e08 100644 --- a/Core/HLE/sceVideocodec.cpp +++ b/Core/HLE/sceVideocodec.cpp @@ -35,6 +35,8 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceVideocodec.h" +#include "Core/HLE/sceMpeg.h" +#include "Core/HLE/sceMpegbase.h" #include "Core/Util/BlockAllocator.h" #include "Core/HW/AvcDecoder.h" #include "Core/MemMap.h" @@ -473,13 +475,27 @@ static int sceVideocodecDecode(u32 ctxAddr, int type) { return hleLogError(Log::ME, -1, "bad output descriptor"); } + // The access unit address mpeg.prx passes is in Media Engine space, which we can't read - + // on hardware sceMpegBasePESpacketCopy DMA'd the data there. That copy is ours, so use what + // it gathered instead, and fall back to main memory for any caller that points at it + // directly. bool gotFrame = false; + const u8 *au = nullptr; + int auBytes = 0; if (auSize > 0 && Memory::IsValidRange(auAddr, auSize)) { - const u8 *au = Memory::GetTypedPointerRange(auAddr, auSize); - if (au) { - gotFrame = vctx.decoder->Decode(au, auSize); + au = Memory::GetTypedPointerRange(auAddr, auSize); + auBytes = auSize; + } else { + // Ask for the payload copied to this exact address - the same call carries audio too. + const std::vector *pes = MpegBaseGetPESPacket(auAddr); + if (pes && !pes->empty()) { + au = pes->data(); + auBytes = (int)pes->size(); } } + if (au && auBytes > 0) { + gotFrame = vctx.decoder->Decode(au, auBytes); + } const int width = gotFrame ? vctx.decoder->Width() : 0; const int height = gotFrame ? vctx.decoder->Height() : 0; @@ -533,7 +549,7 @@ static int sceVideocodecDecode(u32 ctxAddr, int type) { } return hleLogDebug(Log::ME, 0, "type %d, %d bytes -> %s %dx%d", - type, auSize, gotFrame ? "frame" : "no frame yet", width, height); + type, auBytes, gotFrame ? "frame" : "no frame yet", width, height); } static int sceVideocodecStop(u32 ctxAddr, int type) {