diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index 3c902b2ffa..7eafa34695 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -588,6 +588,8 @@ add_library(Core STATIC
HLE/sceUtility.h
HLE/sceVaudio.cpp
HLE/sceVaudio.h
+ HLE/sceVshBridge.cpp
+ HLE/sceVshBridge.h
HLE/scePspNpDrm_user.cpp
HLE/scePspNpDrm_user.h
HLE/sceNp.cpp
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 3c7f560423..322278e8fc 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -667,6 +667,7 @@
+
@@ -1176,6 +1177,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index d6790ae2a7..da1dbed56d 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -387,6 +387,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
Dialog
@@ -1704,6 +1707,9 @@
HLE\Libraries
+
+ HLE\Libraries
+
Dialog
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index 373c92236f..679b8ff2cd 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -78,6 +78,7 @@
#include "sceUsbMic.h"
#include "sceUtility.h"
#include "sceVaudio.h"
+#include "sceVshBridge.h"
#include "sceMt19937.h"
#include "sceSha256.h"
#include "sceAdler.h"
@@ -315,6 +316,7 @@ void RegisterAllModules() {
Register_sceImpose_driver();
Register_sceHprm_driver();
Register_sceChkreg();
+ Register_sceVshBridge();
// add new modules here.
// Not ready to enable this due to apparent softlocks in Patapon 3.
diff --git a/Core/HLE/sceCtrl.cpp b/Core/HLE/sceCtrl.cpp
index d9b53ebaff..4384b2b762 100644
--- a/Core/HLE/sceCtrl.cpp
+++ b/Core/HLE/sceCtrl.cpp
@@ -495,7 +495,7 @@ static int sceCtrlGetIdleCancelThreshold(u32 idleResetPtr, u32 idleBackPtr)
return hleLogDebug(Log::sceCtrl, 0);
}
-static int sceCtrlReadBufferPositive(u32 ctrlDataPtr, u32 nBufs)
+int sceCtrlReadBufferPositive(u32 ctrlDataPtr, u32 nBufs)
{
int done = __CtrlReadBuffer(ctrlDataPtr, nBufs, false, false);
hleEatCycles(330);
diff --git a/Core/HLE/sceCtrl.h b/Core/HLE/sceCtrl.h
index 522cc26611..f37325fae6 100644
--- a/Core/HLE/sceCtrl.h
+++ b/Core/HLE/sceCtrl.h
@@ -87,6 +87,9 @@ bool __CtrlGetRapidFire();
// For use by internal UI like MsgDialog
u32 __CtrlPeekButtons();
+
+// Exposed so sceVshBridge can reuse it directly for vshCtrlReadBufferPositive, matching JPCSP.
+int sceCtrlReadBufferPositive(u32 ctrlDataPtr, u32 nBufs);
u32 __CtrlPeekButtonsVisual(); // also incorporates rapid-fire
void __CtrlPeekAnalog(int stick, float *x, float *y);
u32 __CtrlReadLatch();
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index 02a01c5cff..88b2545824 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -1089,6 +1089,64 @@ enum : u32 {
ELF_MAGIC = 0x464c457f,
};
+// Set once we've seen a PSP_MODULE_VSH_MODE module load (i.e. we're booting the VSH rather
+// than a game), and reset on the next __KernelLoadExec. See ShouldHLEModuleForLoad below.
+static bool g_runningVSH = false;
+
+// A few flash0 modules (VSH's own bridge/UI/utility libraries) should only ever be genuinely
+// loaded - rather than faked via any HLE implementation we may have for them - once we know
+// we're actually running the VSH. A regular game never legitimately loads these, so this only
+// matters for VSH boots; mirrors JPCSP's moduleFileNamesVshOnly.
+static bool IsVshOnlyModuleName(std::string_view modname) {
+ static const char *const vshOnlyModules[] = {
+ "sceVshBridge_Driver",
+ "scePaf_Module",
+ "sceVshCommonGui_Module",
+ "sceVshCommonUtil_Module",
+ };
+ for (const char *name : vshOnlyModules) {
+ if (equalsNoCase(modname, name))
+ return true;
+ }
+ return false;
+}
+
+static bool ShouldHLEModuleForLoad(std::string_view modname, bool *wasDisabledManually = nullptr) {
+ if (g_runningVSH && IsVshOnlyModuleName(modname)) {
+ if (wasDisabledManually)
+ *wasDisabledManually = false;
+ return false;
+ }
+ return ShouldHLEModule(modname, wasDisabledManually);
+}
+
+// vsh_module (vshmain.prx) doesn't import sceKernelLoadModule at all - only StartModule/
+// StopModule/UnloadModule - so unlike a game's auxiliary PRXes, it never loads paf.prx/
+// common_gui.prx/common_util.prx/vshbridge.prx itself. On real hardware these are loaded and
+// started as part of the kernel's own boot sequence, before any user module runs (matches
+// JPCSP's moduleFileNamesToBeLoaded/moduleFileNamesVshOnly). We don't emulate that sequence,
+// so approximate it here: load and start them ourselves right before starting the VSH itself.
+static void LoadAndStartVshKernelModules() {
+ static const char *const vshKernelModulePaths[] = {
+ "flash0:/kd/vshbridge.prx",
+ "flash0:/vsh/module/paf.prx",
+ "flash0:/vsh/module/common_gui.prx",
+ "flash0:/vsh/module/common_util.prx",
+ };
+ for (const char *path : vshKernelModulePaths) {
+ std::string error_string;
+ SceUID moduleId = KernelLoadModule(path, &error_string);
+ if (moduleId < 0) {
+ WARN_LOG(Log::sceModule, "LoadAndStartVshKernelModules: failed to load %s: %s", path, error_string.c_str());
+ continue;
+ }
+ int result = __KernelStartModule(moduleId, 0, 0, 0, nullptr, nullptr);
+ if (result < 0) {
+ WARN_LOG(Log::sceModule, "LoadAndStartVshKernelModules: failed to start %s (uid=%d)", path, moduleId);
+ }
+ }
+}
+
// filename is only used for dumping/metadata.
static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAddress, bool fromTop, std::string *error_string, u32 *magic, std::string_view filename, u32 &error) {
// The magic reads below need four bytes, and the ~SCE branch another four after that. Everything
@@ -1135,7 +1193,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
devkitVersion = head->devkitversion;
bool wasDisabled;
- if (ShouldHLEModule(head->modname, &wasDisabled)) {
+ if (ShouldHLEModuleForLoad(head->modname, &wasDisabled)) {
int ver = (head->module_ver_hi << 8) | head->module_ver_lo;
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x, crc %x", head->modname, ver, head->devkitversion, module->crc);
@@ -1362,20 +1420,21 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
modinfo = (const PspModuleInfo *)Memory::GetPointerUnchecked(modinfoaddr);
- // OK, even if it's an ELF module, it might be one we shouldn't fully load and execute!
- // This is seen with mpeg.prx in Tony Hawk's Underground 2, see #20568.
- if (ShouldHLEModule(modinfo->name)) {
- // We load it, but at least we don't run any part of it.
- module->isFake = true;
- }
-
module->nm.nsegment = reader.GetNumSegments();
module->nm.attribute = modinfo->moduleAttrs;
if ((module->nm.attribute & PSP_MODULE_VSH_MODE) != 0) {
// Used by the PSP's Visual Shell (VSH/XMB) and modules it loads, such as vshmain.prx.
- // We don't do anything special with this yet, just recognizing it for now.
+ g_runningVSH = true;
INFO_LOG(Log::sceModule, "VSH mode module detected: %s", modinfo->name);
}
+
+ // OK, even if it's an ELF module, it might be one we shouldn't fully load and execute!
+ // This is seen with mpeg.prx in Tony Hawk's Underground 2, see #20568.
+ if (ShouldHLEModuleForLoad(modinfo->name)) {
+ // We load it, but at least we don't run any part of it.
+ module->isFake = true;
+ }
+
module->nm.version[0] = modinfo->moduleVersion & 0xFF;
module->nm.version[1] = modinfo->moduleVersion >> 8;
module->nm.data_size = 0;
@@ -1697,7 +1756,7 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
delete [] newptr;
- if (!reportedModule && ShouldHLEModule(modinfo->name)) {
+ if (!reportedModule && ShouldHLEModuleForLoad(modinfo->name)) {
INFO_LOG(Log::sceModule, "Loading module %s with version %04x, devkit %08x", modinfo->name, modinfo->moduleVersion, devkitVersion);
if (!strcmp(modinfo->name, "sceMpeg_library")) {
@@ -1837,6 +1896,7 @@ bool KernelModuleIsKernelMode(SceUID uid) {
void __KernelLoadReset() {
// Wipe kernel here, loadexec should reset the entire system
+ g_runningVSH = false;
if (__KernelIsRunning()) {
u32 error;
while (!loadedModules.empty()) {
@@ -1937,6 +1997,10 @@ bool __KernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr, std::
if (module->nm.module_start_thread_stacksize != 0)
option.stacksize = module->nm.module_start_thread_stacksize;
+ if (g_runningVSH) {
+ LoadAndStartVshKernelModules();
+ }
+
INFO_LOG(Log::System, "Starting modules...");
if (paramPtr)
__KernelStartModule(module, param.args, (const char*)param_argp, &option);
diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h
index 59d8fefddf..cc9cfb099a 100644
--- a/Core/HLE/sceKernelModule.h
+++ b/Core/HLE/sceKernelModule.h
@@ -237,6 +237,9 @@ u32 __KernelGetModuleGP(SceUID module);
bool KernelModuleIsKernelMode(SceUID module);
bool __KernelLoadGEDump(std::string_view base_filename, std::string *error_string);
bool __KernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr, std::string *error_string);
+// Exposed so sceVshBridge can reuse it directly for vshKernelLoadModuleBufferVSH, matching JPCSP.
+SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr);
+// Exposed for HLE.cpp's "Unknown syscall" diagnostic - see the definition for details.
bool KernelFindImportByStubAddr(u32 stubAddr, std::string *importModuleName, u32 *nid, std::string *importingModuleName);
// Describes which loaded module (and section within it) an address falls in, e.g. "EBOOT.BIN.text+1234".
// Returns an empty string if the address isn't inside any currently loaded module.
diff --git a/Core/HLE/sceVshBridge.cpp b/Core/HLE/sceVshBridge.cpp
new file mode 100644
index 0000000000..4575f7dc74
--- /dev/null
+++ b/Core/HLE/sceVshBridge.cpp
@@ -0,0 +1,48 @@
+// Copyright (c) 2012- 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/.
+
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/HLE/sceVshBridge.h"
+#include "Core/HLE/sceCtrl.h"
+#include "Core/HLE/sceKernel.h"
+#include "Core/HLE/sceKernelModule.h"
+
+// sceVshBridge is the HLE surface of flash0:/kd/vshbridge.prx, a kernel-mode module that
+// exposes a bunch of otherwise-kernel-only functionality (controller reads, LoadExec
+// variants, audio/ME/display bits, MagicGate memory stick audio, etc.) to the VSH, which
+// mostly runs in user mode.
+//
+// Unlike sceVshCommonUtil/sceVshCommonGui, we intentionally only implement the handful of
+// NIDs that genuinely need to trap into PPSSPP's own host-level code (controller input,
+// process control, our own module-loading machinery) - the same reasoning that applies to
+// every other kernel syscall PPSSPP HLEs for regular games. We now load and start the real
+// vshbridge.prx as part of booting the VSH (see LoadAndStartVshKernelModules in
+// sceKernelModule.cpp), so registering a stub for every other NID here would silently discard
+// vshbridge.prx's real, working exports in favor of a placeholder that just logs an error -
+// Core/HLE/sceKernelModule.cpp's ExportFuncSymbol() ignores a module's real export if we claim
+// to already have HLE for that NID. Found this the hard way with sceVshCommonUtil first.
+
+const HLEFunction sceVshBridge[] = {
+ {0XC9626587, &WrapI_UUUU, "vshKernelLoadModuleBufferVSH", 'i', "xxxx"},
+ {0XC6395C03, &WrapI_UU, "vshCtrlReadBufferPositive", 'i', "xx" },
+ {0X9929DDA5, &WrapV_V, "vshKernelExitVSH", 'v', "" },
+};
+
+void Register_sceVshBridge() {
+ RegisterHLEModule("sceVshBridge", ARRAY_SIZE(sceVshBridge), sceVshBridge);
+}
diff --git a/Core/HLE/sceVshBridge.h b/Core/HLE/sceVshBridge.h
new file mode 100644
index 0000000000..cea6702375
--- /dev/null
+++ b/Core/HLE/sceVshBridge.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012- 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_sceVshBridge();
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 654217ce62..8bd3e6952d 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -781,6 +781,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/sceUsbMic.cpp \
$(SRC)/Core/HLE/sceUtility.cpp \
$(SRC)/Core/HLE/sceVaudio.cpp \
+ $(SRC)/Core/HLE/sceVshBridge.cpp \
$(SRC)/Core/HLE/scePspNpDrm_user.cpp \
$(SRC)/Core/HLE/sceGameUpdate.cpp \
$(SRC)/Core/HLE/sceNp.cpp \
diff --git a/headless/Headless.cpp b/headless/Headless.cpp
index 68e2068e8c..79e9ea0f92 100644
--- a/headless/Headless.cpp
+++ b/headless/Headless.cpp
@@ -137,6 +137,7 @@ std::string NativeLoadSecret(std::string_view nameOfSecret) {
int printUsage(const CommandLineOptions &options, const char *progname, const char *reason) {
options.PrintUsage(progname, reason);
fprintf(stderr, " --ir use ir interpreter\n");
+ fprintf(stderr, " --flash0=DIR use DIR as flash0: instead of the bundled assets (e.g. a real dump)\n");
return 1;
}
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index fdf991d907..c88a3d0235 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -832,6 +832,7 @@ SOURCES_CXX += \
$(COREDIR)/HLE/sceUsbCam.cpp \
$(COREDIR)/HLE/sceUtility.cpp \
$(COREDIR)/HLE/sceVaudio.cpp \
+ $(COREDIR)/HLE/sceVshBridge.cpp \
$(COREDIR)/HLE/scePspNpDrm_user.cpp \
$(COREDIR)/HLE/sceNp.cpp \
$(COREDIR)/HLE/sceNp2.cpp \