From 7c6f9d18b8514d347d268912b53f5d7a44196ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 7 Sep 2026 16:41:46 -0600 Subject: [PATCH] sceMpegbase: implement the colour conversion functions Five of the six functions in this module were registered as nullptr, so any caller reaching them got nothing. mpeg.prx needs all of them, and this is the first of the pieces required before the real module can run in place of our sceMpeg HLE. The module gets its own file at the same time. It was a tail of sceMpeg.cpp, which is long enough already, and everything below is new code for it. sceMpegBaseCscAvc and sceMpegBaseCscAvcRange convert the ME's decoded output to RGB. The 48-byte descriptor holds the frame size in macroblocks at 0x10 and 0x14 - it appears at 0x00/0x04 too, but scaled differently depending on which path built it, so the second pair is the one every caller agrees on - and the four luma buffers at 0x20. What they point at is not planar: luma arrives as 16-pixel-wide vertical strips alternating between buffers on every other line, and chroma as Cb/Cr pairs interleaved across four more. All eight are untangled into planes once per frame before converting, which keeps the pixel loop readable. sceMpegBaseCscInit carries the default buffer width for callers that pass zero. sceMpegBaseCscSetPixelMode (0x0530BE4E - the official name isn't known) carries the output format, in mpegbase's own numbering rather than the GE's: mpeg.prx gets it by indexing a table of {1, 2, 3, 0} with the pixel mode the game gave sceMpegAvcDecodeMode, making 0 ABGR8888 and 2 ABGR5551. Read as a GE mode it turned Thrillville's black into blue. sceMpegBaseYCrCbCopy copies the descriptor but not the buffers it points at, and says so in its log - nothing seen so far needs more, and inventing the buffer copy without something to test it against seemed worse than a note. Behaviour is cross-checked against JPCSP, which implements all of these. Co-Authored-By: Claude Opus 5 (1M context) --- Core/CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/HLE/HLETables.cpp | 1 + Core/HLE/sceMpeg.cpp | 47 +--- Core/HLE/sceMpeg.h | 14 +- Core/HLE/sceMpegbase.cpp | 343 ++++++++++++++++++++++++++++ Core/HLE/sceMpegbase.h | 23 ++ UWP/CoreUWP/CoreUWP.vcxproj | 2 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 2 + android/jni/Android.mk | 1 + libretro/Makefile.common | 1 + 12 files changed, 402 insertions(+), 42 deletions(-) create mode 100644 Core/HLE/sceMpegbase.cpp create mode 100644 Core/HLE/sceMpegbase.h diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index a283e7c935..c6cf1f5a80 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -496,6 +496,8 @@ add_library(Core STATIC HLE/sceKernelVTimer.h HLE/sceMpeg.cpp HLE/sceMpeg.h + HLE/sceMpegbase.cpp + HLE/sceMpegbase.h HLE/sceNet.cpp HLE/sceNet.h HLE/sceNet_lib.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index b1cbe3f35e..bdda9d35a3 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -638,6 +638,7 @@ + @@ -1151,6 +1152,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 8bb8462455..5133900c30 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -258,6 +258,9 @@ HLE\Libraries + + HLE\Libraries + HLE\Libraries @@ -1581,6 +1584,9 @@ HLE\Libraries + + HLE\Libraries + HLE\Libraries diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index 94e15495e9..39df6a3f78 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -57,6 +57,7 @@ #include "sceNetAdhocMatching.h" #include "sceNp.h" #include "sceMpeg.h" +#include "sceMpegbase.h" #include "sceOpenPSID.h" #include "sceResmgr.h" #include "sceP3da.h" diff --git a/Core/HLE/sceMpeg.cpp b/Core/HLE/sceMpeg.cpp index 3418883434..9ca6ef58c9 100644 --- a/Core/HLE/sceMpeg.cpp +++ b/Core/HLE/sceMpeg.cpp @@ -23,6 +23,7 @@ #include "Common/Serialize/SerializeMap.h" #include "Common/Swap.h" #include "Core/HLE/sceMpeg.h" +#include "Core/HLE/sceMpegbase.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/HLE.h" @@ -121,13 +122,10 @@ static AVPixelFormat pmp_want_pix_fmt; #endif -struct SceMpegLLI -{ - u32 pSrc; - u32 pDst; - u32 Next; - int iSize; -}; +void MpegSetPmpVideoSource(u32 addr, int blocks) { + pmp_videoSource = addr; + pmp_nBlocks = blocks; +} void SceMpegAu::read(u32 addr) { Memory::Memcpy(this, addr, sizeof(*this), "SceMpegAu"); @@ -350,6 +348,7 @@ private: }; void __MpegInit() { + __MpegBaseInit(); isMpegInit = false; mpegLibVersion = 0x010A; streamIdGen = 1; @@ -2322,37 +2321,3 @@ void Register_sceMpeg() } // This function is currently only been used for PMP videos -// p pointing to a SceMpegLLI structure consists of video frame blocks. -static u32 sceMpegBasePESpacketCopy(u32 p) -{ - pmp_videoSource = p; - pmp_nBlocks = 0; - - auto lli = PSPPointer::Create(p); - while (lli.IsValid()) { - pmp_nBlocks++; - // lli.Next ==0 for last block - if (lli->Next == 0){ - break; - } - ++lli; - } - - DEBUG_LOG(Log::Mpeg, "sceMpegBasePESpacketCopy(%08x), received %d block(s)", pmp_videoSource, pmp_nBlocks); - return 0; -} - -const HLEFunction sceMpegbase[] = -{ - {0XBEA18F91, &WrapU_U, "sceMpegBasePESpacketCopy", 'x', "x" }, - {0X492B5E4B, nullptr, "sceMpegBaseCscInit", '?', "" }, - {0X0530BE4E, nullptr, "sceMpegbase_0530BE4E", '?', "" }, - {0X91929A21, nullptr, "sceMpegBaseCscAvc", '?', "" }, - {0X304882E1, nullptr, "sceMpegBaseCscAvcRange", '?', "" }, - {0X7AC0321A, nullptr, "sceMpegBaseYCrCbCopy", '?', "" } -}; - -void Register_sceMpegbase() -{ - RegisterHLEModule("sceMpegbase", ARRAY_SIZE(sceMpegbase), sceMpegbase); -}; diff --git a/Core/HLE/sceMpeg.h b/Core/HLE/sceMpeg.h index db969b6292..9692a92176 100644 --- a/Core/HLE/sceMpeg.h +++ b/Core/HLE/sceMpeg.h @@ -139,7 +139,19 @@ void __MpegLoadModule(int version, u32 crc); void Register_sceMpeg(); -void Register_sceMpegbase(); +// One block of the scatter-gather list sceMpegBasePESpacketCopy walks. Shared because the PMP +// video path reads the same list back. +struct SceMpegLLI { + u32 pSrc; + u32 pDst; + u32 Next; + int iSize; +}; + +// sceMpegBasePESpacketCopy hands the list it just walked to the PMP path, which decodes from it. +// The two values live in sceMpeg.cpp rather than with the copy itself because __VideoPmpDoState +// serializes them as part of sceMpeg's savestate section. +void MpegSetPmpVideoSource(u32 addr, int blocks); void __VideoPmpInit(); void __VideoPmpDoState(PointerWrap &p); diff --git a/Core/HLE/sceMpegbase.cpp b/Core/HLE/sceMpegbase.cpp new file mode 100644 index 0000000000..9c048172e6 --- /dev/null +++ b/Core/HLE/sceMpegbase.cpp @@ -0,0 +1,343 @@ +// Copyright (c) 2026- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +// sceMpegbase - the Media Engine's colour space conversion, and the DMA that feeds it. +// +// 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 "Common/Swap.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/HLE/sceMpeg.h" +#include "Core/HLE/sceMpegbase.h" +#include "Core/MemMapHelpers.h" +#include "GPU/ge_constants.h" + +// 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_mpegBaseBufferWidth = 512; + g_mpegBasePixelMode = GE_CMODE_32BIT_ABGR8888; +} + +// p pointing to a SceMpegLLI structure consists of video frame blocks. +static u32 sceMpegBasePESpacketCopy(u32 p) +{ + int nBlocks = 0; + auto lli = PSPPointer::Create(p); + while (lli.IsValid()) { + nBlocks++; + // lli.Next ==0 for last block + if (lli->Next == 0){ + break; + } + ++lli; + } + MpegSetPmpVideoSource(p, nBlocks); + + DEBUG_LOG(Log::Mpeg, "sceMpegBasePESpacketCopy(%08x), received %d block(s)", p, nBlocks); + return 0; +} + + +// --- sceMpegbase colour conversion --------------------------------------------------------- +// +// mpeg.prx hands the ME's decoded output to these to be converted to RGB. The descriptor it +// passes is 48 bytes - which is exactly the range mpegbase.prx bounds-checks before using it. +// +// The dimensions appear twice. Death Jr. has the plain macroblock counts in both pairs, but +// Thrillville's psmfplayer path has them shifted up by 8 in the first pair and plain in the +// second, so the second is the one to trust. The four luma buffers are at 0x20; the chroma ones +// aren't in here at all (see the recovery in MpegBaseCscRange). +struct SceMp4AvcCscStruct { + s32_le scaledHeight; // 0x00 height << 8 on some paths, macroblocks on others - unused + s32_le scaledWidth; // 0x04 + s32_le mode0; // 0x08 + s32_le mode1; // 0x0c + s32_le height; // 0x10 in macroblocks + s32_le width; // 0x14 in macroblocks + s32_le unk18; // 0x18 + s32_le unk1c; // 0x1c + u32_le buffer[4]; // 0x20 luma only +}; +static_assert(sizeof(SceMp4AvcCscStruct) == 0x30); + + +// The ME doesn't write planar YCbCr. The layout below was established by analysing +// sceMpegBaseYCrCbCopy output on a real PSP (documented in JPCSP's sceVideocodec), and it uses +// all eight buffers in the descriptor: +// +// The image is divided into vertical bands 32 pixels wide, each split into two 16-pixel halves. +// Luma is one byte per pixel, and which buffer a row lands in depends on whether it is even or +// odd: +// buffer0: left half, even rows buffer1: right half, even rows +// buffer2: left half, odd rows buffer3: right half, odd rows +// Within a buffer, rows are stored 16 bytes at a time, band by band, top to bottom. +// +// Chroma is one (Cb,Cr) byte pair per 2x2 pixel square, so in chroma coordinates the bands are +// 16 wide with 8-pixel halves, and the same even/odd split applies: +// buffer4: left half, even chroma rows buffer5: left half, odd chroma rows +// buffer6: right half, even chroma rows buffer7: right half, odd chroma rows +// +// Untangling it into plain planes costs one pass per frame, which keeps the conversion below +// readable and is not where the time goes. +static bool ReadTiledYCbCr(const u32 *buffers, int width, int height, + std::vector &luma, std::vector &cb, std::vector &cr) { + const int width2 = width >> 1; + const int height2 = height >> 1; + + // buffer0/2 take the odd band out when the width isn't a multiple of 32. + const int lumaSizeLeft = ((width + 16) >> 5) * (height >> 1) * 16; + const int lumaSizeRight = (width >> 5) * (height >> 1) * 16; + const int chromaSizeLeft = lumaSizeLeft >> 1; + const int chromaSizeRight = lumaSizeRight >> 1; + + const u8 *y[4] = {}; + const u8 *c[4] = {}; + const int ySize[4] = { lumaSizeLeft, lumaSizeRight, lumaSizeLeft, lumaSizeRight }; + const int cSize[4] = { chromaSizeLeft, chromaSizeLeft, chromaSizeRight, chromaSizeRight }; + for (int i = 0; i < 4; i++) { + if (ySize[i] > 0) { + if (!Memory::IsValidRange(buffers[i], ySize[i])) { + return false; + } + y[i] = Memory::GetTypedPointerRange(buffers[i], ySize[i]); + } + if (cSize[i] > 0) { + if (!Memory::IsValidRange(buffers[4 + i], cSize[i])) { + return false; + } + c[i] = Memory::GetTypedPointerRange(buffers[4 + i], cSize[i]); + } + if ((ySize[i] > 0 && !y[i]) || (cSize[i] > 0 && !c[i])) { + return false; + } + } + + luma.assign((size_t)width * height, 0); + cb.assign((size_t)width2 * height2, 128); + cr.assign((size_t)width2 * height2, 128); + + // Luma: four buffers, keyed by (left/right half of the band, even/odd row). + for (int b = 0; b < 4; b++) { + if (!y[b]) { + continue; + } + const int xOffset = (b & 1) ? 16 : 0; + const int yStart = (b >> 1) ? 1 : 0; + int j = 0; + for (int bandX = xOffset; bandX < width; bandX += 32) { + const int run = std::min(16, width - bandX); + for (int row = yStart; row < height; row += 2, j += 16) { + if (run <= 0 || j + run > ySize[b]) { + continue; + } + memcpy(&luma[(size_t)row * width + bandX], y[b] + j, run); + } + } + } + + // Chroma: same shape in half-resolution coordinates, with interleaved Cb/Cr pairs. + for (int b = 0; b < 4; b++) { + if (!c[b]) { + continue; + } + const int xOffset = (b >> 1) ? 8 : 0; + const int yStart = (b & 1) ? 1 : 0; + int j = 0; + for (int bandX = xOffset; bandX < width2; bandX += 16) { + for (int row = yStart; row < height2; row += 2) { + for (int k = 0; k < 8; k++, j += 2) { + const int x = bandX + k; + if (x >= width2 || j + 1 >= cSize[b]) { + continue; + } + const size_t i = (size_t)row * width2 + x; + cb[i] = c[b][j]; + cr[i] = c[b][j + 1]; + } + } + } + } + return true; +} + +static u32 YCbCrToPixel(int y, int cbv, int crv, int pixelMode) { + const int c = y - 16, d = cbv - 128, e = crv - 128; + int r = (298 * c + 409 * e + 128) >> 8; + int g = (298 * c - 100 * d - 208 * e + 128) >> 8; + int b = (298 * c + 516 * d + 128) >> 8; + r = std::min(255, std::max(0, r)); + g = std::min(255, std::max(0, g)); + b = std::min(255, std::max(0, b)); + switch (pixelMode) { + case GE_CMODE_16BIT_BGR5650: + return ((b >> 3) << 11) | ((g >> 2) << 5) | (r >> 3); + case GE_CMODE_16BIT_ABGR5551: + return (1 << 15) | ((b >> 3) << 10) | ((g >> 3) << 5) | (r >> 3); + case GE_CMODE_16BIT_ABGR4444: + return (0xF << 12) | ((b >> 4) << 8) | ((g >> 4) << 4) | (r >> 4); + default: + return 0xFF000000 | (b << 16) | (g << 8) | r; + } +} + +// The shared body of sceMpegBaseCscAvc and sceMpegBaseCscAvcRange - the former is just the +// latter over the whole frame. +static int MpegBaseCscRange(u32 bufferRGB, u32 cscAddr, int bufferWidth, + int rangeX, int rangeY, int rangeWidth, int rangeHeight) { + auto csc = PSPPointer::Create(cscAddr); + if (!csc.IsValid()) { + return hleLogError(Log::Mpeg, -1, "bad csc struct pointer"); + } + if (bufferWidth == 0) { + bufferWidth = g_mpegBaseBufferWidth; + } + + const int width = csc->width << 4; + const int height = csc->height << 4; + if (width <= 0 || height <= 0 || width > 1024 || height > 1024) { + return hleLogError(Log::Mpeg, -1, "unreasonable frame size %dx%d", width, height); + } + if (rangeWidth <= 0 || rangeHeight <= 0) { + return hleLogDebug(Log::Mpeg, 0, "empty range"); + } + rangeWidth = std::min(rangeWidth, width - rangeX); + rangeHeight = std::min(rangeHeight, height - rangeY); + // The output buffer is sized from the stride below, so a row can't be wider than one. Every + // game seen so far passes a stride comfortably wider than the frame (512 for 480), but nothing + // guarantees it, and writing a wider row than we measured would run off the end of the buffer. + rangeWidth = std::min(rangeWidth, bufferWidth); + if (rangeX < 0 || rangeY < 0 || rangeWidth <= 0 || rangeHeight <= 0) { + return hleLogError(Log::Mpeg, -1, "range outside the frame"); + } + + u32 buffers[8]{}; + for (int i = 0; i < 4; i++) { + buffers[i] = csc->buffer[i]; + } + + std::vector luma, cb, cr; + if (!ReadTiledYCbCr(buffers, width, height, luma, cb, cr)) { + return hleLogError(Log::Mpeg, -1, "YCbCr buffers not readable"); + } + + const int bpp = g_mpegBasePixelMode == GE_CMODE_32BIT_ABGR8888 ? 4 : 2; + const u32 destSize = (u32)(rangeHeight * bufferWidth * bpp); + if (!Memory::IsValidRange(bufferRGB, destSize)) { + return hleLogError(Log::Mpeg, -1, "output buffer not writable"); + } + u8 *dest = Memory::GetTypedPointerWriteRange(bufferRGB, destSize); + if (!dest) { + return hleLogError(Log::Mpeg, -1, "output buffer not writable"); + } + + const int width2 = width >> 1; + for (int y = 0; y < rangeHeight; y++) { + const int sy = rangeY + y; + for (int x = 0; x < rangeWidth; x++) { + const int sx = rangeX + x; + const int ci = (sy >> 1) * width2 + (sx >> 1); + const u32 pixel = YCbCrToPixel(luma[sy * width + sx], cb[ci], cr[ci], g_mpegBasePixelMode); + if (bpp == 4) { + memcpy(dest + (y * bufferWidth + x) * 4, &pixel, 4); + } else { + const u16 p16 = (u16)pixel; + memcpy(dest + (y * bufferWidth + x) * 2, &p16, 2); + } + } + } + NotifyMemInfo(MemBlockFlags::WRITE, bufferRGB, destSize, "MpegBaseCsc"); + return hleLogDebug(Log::Mpeg, 0, "%dx%d at %d,%d -> %08x stride %d", + rangeWidth, rangeHeight, rangeX, rangeY, bufferRGB, bufferWidth); +} + +static int sceMpegBaseCscInit(int bufferWidth) { + g_mpegBaseBufferWidth = bufferWidth ? bufferWidth : 512; + return hleLogInfo(Log::Mpeg, 0, "bufferWidth %d", bufferWidth); +} + +// Sets the output pixel format for the conversions below. The official name isn't known - this one +// says what it does. Inside mpegbase.prx it stores the value and hands it to the DMACPLUS colour +// conversion hardware, and mpeg.prx gets it by indexing a table of {1, 2, 3, 0} with the pixel mode +// the game passed to sceMpegAvcDecodeMode. So the numbering is its own, not the GE's. +static int sceMpegBaseCscSetPixelMode(int pixelMode) { + static const int toGeMode[4] = { + GE_CMODE_32BIT_ABGR8888, + GE_CMODE_16BIT_BGR5650, + GE_CMODE_16BIT_ABGR5551, + GE_CMODE_16BIT_ABGR4444, + }; + if (pixelMode < 0 || pixelMode >= (int)ARRAY_SIZE(toGeMode)) { + return hleLogError(Log::Mpeg, -1, "bad pixel mode %d", pixelMode); + } + g_mpegBasePixelMode = toGeMode[pixelMode]; + return hleLogInfo(Log::Mpeg, 0, "pixel mode %d -> GE mode %d", pixelMode, g_mpegBasePixelMode); +} + +static int sceMpegBaseCscAvc(u32 bufferRGB, u32 unknown, int bufferWidth, u32 cscAddr) { + auto csc = PSPPointer::Create(cscAddr); + if (!csc.IsValid()) { + return hleLogError(Log::Mpeg, -1, "bad csc struct pointer"); + } + return MpegBaseCscRange(bufferRGB, cscAddr, bufferWidth, 0, 0, csc->width << 4, csc->height << 4); +} + +static u32 sceMpegBaseCscAvcRange(u32 bufferRGB, u32 unknown, u32 rangeAddr, int bufferWidth, u32 cscAddr) { + if (!Memory::IsValidRange(rangeAddr, 16)) { + return hleLogError(Log::Mpeg, -1, "bad range pointer"); + } + // Also in macroblocks. + const int rangeX = Memory::ReadUnchecked_U32(rangeAddr + 0) << 4; + const int rangeY = Memory::ReadUnchecked_U32(rangeAddr + 4) << 4; + const int rangeWidth = Memory::ReadUnchecked_U32(rangeAddr + 8) << 4; + const int rangeHeight = Memory::ReadUnchecked_U32(rangeAddr + 12) << 4; + return MpegBaseCscRange(bufferRGB, cscAddr, bufferWidth, rangeX, rangeY, rangeWidth, rangeHeight); +} + +// Copies one 48-byte YCrCb descriptor over another. The hardware copies the buffers themselves +// when told to; games seen so far only use it to move the descriptor around, so that is all we +// do until something needs more. +static int sceMpegBaseYCrCbCopy(u32 dstAddr, u32 srcAddr, int flags) { + if (!Memory::IsValidRange(dstAddr, sizeof(SceMp4AvcCscStruct)) || + !Memory::IsValidRange(srcAddr, sizeof(SceMp4AvcCscStruct))) { + return hleLogError(Log::Mpeg, -1, "bad descriptor pointer"); + } + Memory::Memcpy(dstAddr, srcAddr, (u32)sizeof(SceMp4AvcCscStruct), "MpegBaseYCrCbCopy"); + return hleLogDebug(Log::Mpeg, 0, "flags %08x - descriptor copied, buffers left alone", flags); +} + +const HLEFunction sceMpegbase[] = +{ + {0XBEA18F91, &WrapU_U, "sceMpegBasePESpacketCopy", 'x', "x" }, + {0X492B5E4B, &WrapI_I, "sceMpegBaseCscInit", 'i', "i" }, + {0X0530BE4E, &WrapI_I, "sceMpegbase_0530BE4E", 'i', "i" }, + {0X91929A21, &WrapI_UUIU, "sceMpegBaseCscAvc", 'i', "xxix" }, + {0X304882E1, &WrapU_UUUIU, "sceMpegBaseCscAvcRange", 'x', "xxxix" }, + {0X7AC0321A, &WrapI_UUI, "sceMpegBaseYCrCbCopy", 'i', "xxi" } +}; + +void Register_sceMpegbase() +{ + RegisterHLEModule("sceMpegbase", ARRAY_SIZE(sceMpegbase), sceMpegbase); +}; diff --git a/Core/HLE/sceMpegbase.h b/Core/HLE/sceMpegbase.h new file mode 100644 index 0000000000..98fbad71bd --- /dev/null +++ b/Core/HLE/sceMpegbase.h @@ -0,0 +1,23 @@ +// Copyright (c) 2026- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +void Register_sceMpegbase(); + +// Called per boot, from __MpegInit. +void __MpegBaseInit(); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index b94187585b..9bda778ca2 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -220,6 +220,7 @@ + @@ -503,6 +504,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 05b1873f8f..555c850fcd 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -128,6 +128,7 @@ + @@ -522,6 +523,7 @@ + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index d7c5912df0..1a0d219d2c 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -742,6 +742,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/sceKernelTime.cpp \ $(SRC)/Core/HLE/sceKernelVTimer.cpp \ $(SRC)/Core/HLE/sceMpeg.cpp \ + $(SRC)/Core/HLE/sceMpegbase.cpp \ $(SRC)/Core/HLE/sceMd5.cpp \ $(SRC)/Core/HLE/sceMp4.cpp \ $(SRC)/Core/HLE/sceAac.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index c0af28cc5b..a47b086c88 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -802,6 +802,7 @@ SOURCES_CXX += \ $(COREDIR)/HLE/sceKernelTime.cpp \ $(COREDIR)/HLE/sceKernelVTimer.cpp \ $(COREDIR)/HLE/sceMpeg.cpp \ + $(COREDIR)/HLE/sceMpegbase.cpp \ $(COREDIR)/HLE/sceNet.cpp \ $(COREDIR)/HLE/sceNet_lib.cpp \ $(COREDIR)/HLE/NetAdhocCommon.cpp \