diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 24b7c17d4d..c42305224a 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -494,6 +494,8 @@ add_library(Core STATIC
HLE/sceKernelTime.h
HLE/sceKernelVTimer.cpp
HLE/sceKernelVTimer.h
+ HLE/sceVideocodec.cpp
+ HLE/sceVideocodec.h
HLE/sceMpeg.cpp
HLE/sceMpeg.h
HLE/sceMpegbase.cpp
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index b051ba598a..c39cf6b058 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -637,6 +637,7 @@
+
@@ -1152,6 +1153,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 16ebfaddd9..b0e07da334 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -255,6 +255,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
HLE\Libraries
@@ -1587,6 +1590,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
HLE\Libraries
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index 39df6a3f78..44d672aebe 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -91,6 +91,7 @@
#include "sceNetResolver.h"
// #include "sceNp2.h"
#include "sceNet_lib.h"
+#include "sceVideocodec.h"
static const HLEFunction FakeSysCalls[] = {
{NID_THREADRETURN, __KernelReturnFromThread, "__KernelReturnFromThread", 'x', ""},
@@ -329,6 +330,8 @@ void RegisterAllModules() {
Register_sceVshBridge();
Register_sceResmgr();
+ Register_sceVideocodec();
+
// add new modules here.
// Not ready to enable this due to apparent softlocks in Patapon 3.
diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp
index 623b412289..aeb6fdb928 100644
--- a/Core/HLE/sceKernel.cpp
+++ b/Core/HLE/sceKernel.cpp
@@ -45,6 +45,7 @@
#include "sceAtrac.h"
#include "sceAudio.h"
#include "sceAudiocodec.h"
+#include "sceVideocodec.h"
#include "sceCcc.h"
#include "sceCtrl.h"
#include "sceDisplay.h"
@@ -158,6 +159,7 @@ void __KernelInit()
__HeapInit();
__DmacInit();
__AudioCodecInit();
+ __VideocodecInit();
__VideoPmpInit();
__UsbGpsInit();
__UsbCamInit();
@@ -197,6 +199,7 @@ void __KernelShutdown()
__UsbGpsShutdown();
__AudioCodecShutdown();
+ __VideocodecShutdown();
__VideoPmpShutdown();
__AACShutdown();
__NetAdhocShutdown();
@@ -284,6 +287,7 @@ void __KernelDoState(PointerWrap &p)
__JpegDoState(p);
__Mp3DoState(p);
__MpegDoState(p);
+ __VideocodecDoState(p);
__NetDoState(p);
__NetAdhocDoState(p);
__PowerDoState(p);
diff --git a/Core/HLE/sceMpegbase.cpp b/Core/HLE/sceMpegbase.cpp
index 9c048172e6..b55a31f507 100644
--- a/Core/HLE/sceMpegbase.cpp
+++ b/Core/HLE/sceMpegbase.cpp
@@ -27,6 +27,7 @@
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceMpeg.h"
#include "Core/HLE/sceMpegbase.h"
+#include "Core/HLE/sceVideocodec.h"
#include "Core/MemMapHelpers.h"
#include "GPU/ge_constants.h"
@@ -116,21 +117,21 @@ static bool ReadTiledYCbCr(const u32 *buffers, int width, int height,
const u8 *c[4] = {};
const int ySize[4] = { lumaSizeLeft, lumaSizeRight, lumaSizeLeft, lumaSizeRight };
const int cSize[4] = { chromaSizeLeft, chromaSizeLeft, chromaSizeRight, chromaSizeRight };
+ // These are addresses in the Media Engine's memory, not in PSP RAM, so they resolve through
+ // sceVideocodec rather than through Memory::. A descriptor that was never filled in holds
+ // small integers instead, and those simply aren't in the ME's range.
for (int i = 0; i < 4; i++) {
if (ySize[i] > 0) {
- if (!Memory::IsValidRange(buffers[i], ySize[i])) {
+ y[i] = VideocodecMEPointer(buffers[i], ySize[i]);
+ if (!y[i]) {
return false;
}
- y[i] = Memory::GetTypedPointerRange(buffers[i], ySize[i]);
}
if (cSize[i] > 0) {
- if (!Memory::IsValidRange(buffers[4 + i], cSize[i])) {
+ c[i] = VideocodecMEPointer(buffers[4 + i], cSize[i]);
+ if (!c[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;
}
}
diff --git a/Core/HLE/sceVideocodec.cpp b/Core/HLE/sceVideocodec.cpp
new file mode 100644
index 0000000000..f4ec3b3150
--- /dev/null
+++ b/Core/HLE/sceVideocodec.cpp
@@ -0,0 +1,581 @@
+// 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/.
+
+// sceVideocodec - the H.264 decoding interface the Media Engine exposes.
+//
+// This exists so that flash0:/kd/mpeg.prx can be run in place of our sceMpeg HLE: mpeg.prx needs
+// only sceVideocodec, sceMpegbase and sceAudiocodec from us, and the other two we already have.
+// The point is to have a reference to compare the HLE against, so it aims to behave like the
+// hardware rather than to be the fastest way to get pixels on screen.
+//
+// Behaviour cross-checked against JPCSP, whose description of the buffer layout was established
+// by looking at sceMpegBaseYCrCbCopy output on a real PSP.
+
+#include
+#include