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) {