diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt
index fcb2f3c3e1..9a585939ae 100644
--- a/Core/CMakeLists.txt
+++ b/Core/CMakeLists.txt
@@ -391,6 +391,8 @@ add_library(Core STATIC
HLE/sceSysreg.cpp
HLE/Plugins.h
HLE/Plugins.cpp
+ HLE/PopsBoot.cpp
+ HLE/PopsBoot.h
HLE/ThreadQueueList.h
HLE/__sceAudio.cpp
HLE/__sceAudio.h
@@ -586,6 +588,8 @@ add_library(Core STATIC
HW/MeMMIO.h
HW/MMIO.cpp
HW/MMIO.h
+ HW/PopsMMIO.cpp
+ HW/PopsMMIO.h
HW/AvcDecoder.cpp
HW/AvcDecoder.h
HW/MediaEngine.cpp
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index 1c07a66c9a..4098967edf 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -477,6 +477,7 @@
+
@@ -496,6 +497,7 @@
+
@@ -853,6 +855,7 @@
+
@@ -872,6 +875,7 @@
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index cd7094d8b3..a0b8550a09 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -772,6 +772,9 @@
HLE
+
+ HLE
+
Ext\Snappy
@@ -1184,6 +1187,7 @@
HW
+
HW
@@ -2118,6 +2122,9 @@
HLE
+
+ HLE
+
Ext\gason
@@ -2200,6 +2207,7 @@
HW
+
HW
diff --git a/Core/Dialog/PSPDialog.cpp b/Core/Dialog/PSPDialog.cpp
index 2b87ad511d..b9fb4cb3a2 100644
--- a/Core/Dialog/PSPDialog.cpp
+++ b/Core/Dialog/PSPDialog.cpp
@@ -26,6 +26,7 @@
#include "Core/System.h"
#include "Core/CoreTiming.h"
#include "Core/Dialog/PSPDialog.h"
+#include "Core/HLE/PopsBoot.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/scePower.h"
@@ -170,8 +171,10 @@ void PSPDialog::FinishVolatile() {
if (KernelVolatileMemUnlock(0) == 0) {
volatileLocked_ = false;
- // Simulate modifications to volatile memory.
- Memory::Memset(PSP_GetVolatileMemoryStart(), 0, PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart());
+ // Simulate modifications to volatile memory. Not under the PS1 emulator, which keeps its heap there.
+ if (!PopsBootActive()) {
+ Memory::Memset(PSP_GetVolatileMemoryStart(), 0, PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart());
+ }
}
}
diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp
index e5adbd149f..11f6510ca4 100644
--- a/Core/HLE/HLE.cpp
+++ b/Core/HLE/HLE.cpp
@@ -792,6 +792,17 @@ u64 hleDelayResult(u64 result, const char *reason, int usec) {
return result;
}
+// Ends a thread's 32-bit hleDelayResult() early. Returns whether it was in one.
+bool hleWakeDelayedThread(int threadID) {
+ u32 error;
+ if (__KernelGetWaitID(threadID, WAITTYPE_HLEDELAY, error) != 1 || error != 0) {
+ return false;
+ }
+ CoreTiming::UnscheduleEvent(delayedResultEvent, (u64)(u32)threadID);
+ __KernelResumeThreadFromWait(threadID, __KernelGetWaitValue(threadID, error));
+ return true;
+}
+
void hleEatCycles(int cycles) {
if (hleAfterSyscall & HLE_SPLIT_SYSCALL_OVER_GE) {
splitSyscallEatCycles = cycles;
diff --git a/Core/HLE/HLE.h b/Core/HLE/HLE.h
index c4b5874f36..75cd9caf8c 100644
--- a/Core/HLE/HLE.h
+++ b/Core/HLE/HLE.h
@@ -159,6 +159,7 @@ void hleEnqueueCall(u32 func, int argc, const u32 *argv, PSPAction *afterAction
// Delays the result for usec microseconds, allowing other threads to run during this time.
u32 hleDelayResult(u32 result, const char *reason, int usec);
u64 hleDelayResult(u64 result, const char *reason, int usec);
+bool hleWakeDelayedThread(int threadID);
void hleEatCycles(int cycles);
void hleEatMicro(int usec);
diff --git a/Core/HLE/HLEHelperThread.h b/Core/HLE/HLEHelperThread.h
index e5d3d2e889..134ef6b6dd 100644
--- a/Core/HLE/HLEHelperThread.h
+++ b/Core/HLE/HLEHelperThread.h
@@ -42,6 +42,9 @@ public:
u32 Entry() {
return entry_;
}
+ SceUID Id() const {
+ return id_;
+ }
private:
void AllocEntry(u32 size);
diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp
index b3a58074b7..f3b4b8861f 100644
--- a/Core/HLE/HLETables.cpp
+++ b/Core/HLE/HLETables.cpp
@@ -87,6 +87,7 @@
#include "sceSfmt19937.h"
#include "sceG729.h"
#include "KUBridge.h"
+#include "PopsBoot.h"
#include "sceAmctrl.h"
#include "sceNetInet.h"
#include "sceNetResolver.h"
@@ -361,6 +362,7 @@ void RegisterAllModules() {
Register_sceSysreg_driver();
Register_sceSysEventForKernel();
Register_sceAmctrl_driver();
+ Register_InitForKernel();
// Not ready to enable this due to apparent softlocks in Patapon 3.
// Register_sceNpMatching2();
diff --git a/Core/HLE/PopsBoot.cpp b/Core/HLE/PopsBoot.cpp
new file mode 100644
index 0000000000..9a74238f47
--- /dev/null
+++ b/Core/HLE/PopsBoot.cpp
@@ -0,0 +1,189 @@
+// 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/.
+
+// PS1 EBOOTs, run by the firmware's own POPS manager (popsman.prx) and emulator (pops_0Xg.prx).
+// This stands in for the parts of the POPS boot PPSSPP doesn't run: the kernel's init, and the
+// Media Engine the manager's ME loop runs on. See docs/POPS.md.
+
+#include
+#include
+#include
+
+
+#include "Common/Serialize/Serializer.h"
+#include "Common/Data/Text/I18n.h"
+#include "Common/Serialize/SerializeFuncs.h"
+#include "Core/CoreTiming.h"
+#include "Core/FileSystems/MetaFileSystem.h"
+#include "Core/HLE/FunctionWrappers.h"
+#include "Core/HLE/HLE.h"
+#include "Core/HLE/HLEHelperThread.h"
+#include "Core/HLE/PopsBoot.h"
+#include "Core/HLE/sceKernelMemory.h"
+#include "Core/HLE/sceKernelModule.h"
+#include "Core/HLE/sceKernelThread.h"
+#include "Core/HW/PopsMMIO.h"
+#include "Core/MIPS/MIPS.h"
+#include "Core/MIPS/MIPSCodeUtils.h"
+#include "Core/MemMap.h"
+#include "Core/MemMapHelpers.h"
+#include "Core/System.h"
+
+static bool g_popsBoot = false;
+static std::unique_ptr g_popsInitThread;
+static u32 g_popsInitPath = 0;
+
+// The 6.60 POPS manager (6.61's has the same code), from its base: scePopsManLoadModule. Its ME loop
+// (+2F88) runs on the emulated ME.
+enum : u32 {
+ POPSMAN_LOAD_MODULE = 0x1E20,
+};
+
+// What the places used here hold in the 6.60/6.61 manager. Checked when it's loaded.
+static const struct {
+ u32 offset;
+ u32 original;
+} POPSMAN_CODE[] = {
+ { POPSMAN_LOAD_MODULE, 0x27BDFEE0 }, // addiu sp, sp, -0x120
+};
+
+static const u32 SCE_POPSMAN_LOAD_MODULE_NID = 0x29B3FB24;
+
+void __PopsBootInit() {
+ g_popsBoot = true;
+}
+
+void __PopsBootShutdown() {
+ g_popsBoot = false;
+ // The kernel objects and memory are already gone with the kernel.
+ if (g_popsInitThread) {
+ g_popsInitThread->Forget();
+ }
+ g_popsInitThread.reset();
+ g_popsInitPath = 0;
+}
+
+bool PopsBootActive() {
+ return g_popsBoot;
+}
+
+static void DoHelperThread(PointerWrap &p, std::unique_ptr &thread) {
+ bool has = thread != nullptr;
+ Do(p, has);
+ if (p.mode == PointerWrap::MODE_READ) {
+ // The kernel objects come back with the kernel's own state.
+ if (thread) {
+ thread->Forget();
+ }
+ thread.reset(has ? new HLEHelperThread() : nullptr);
+ }
+ if (thread) {
+ thread->DoState(p);
+ }
+}
+
+void __PopsBootDoState(PointerWrap &p) {
+ auto s = p.Section("PopsBoot", 0, 1);
+ if (!s) {
+ if (p.mode == PointerWrap::MODE_READ) {
+ __PopsBootShutdown();
+ }
+ return;
+ }
+ Do(p, g_popsBoot);
+ Do(p, g_popsInitPath);
+ DoHelperThread(p, g_popsInitThread);
+}
+
+static u32 PopsManBase() {
+ const u32 loadModule = KernelFindModuleExport("scePopsMan", SCE_POPSMAN_LOAD_MODULE_NID);
+ return loadModule ? loadModule - POPSMAN_LOAD_MODULE : 0;
+}
+
+// The user modules the 6.60 POPS boot configuration (pspbtcnf) starts after the manager.
+void __PopsBootLoadModules() {
+ static const char *const paths[] = {
+ "flash0:/vsh/module/libfont_hv.prx",
+ "flash0:/vsh/module/pafmini.prx",
+ "flash0:/vsh/module/libpspvmc.prx",
+ };
+ for (const char *path : paths) {
+ std::string error;
+ const SceUID moduleId = KernelLoadModule(path, &error);
+ if (moduleId < 0) {
+ WARN_LOG(Log::sceKernel, "POPS: failed to load %s: %s", path, error.c_str());
+ continue;
+ }
+ if (__KernelStartModule(moduleId, 0, 0, 0, nullptr, nullptr) < 0) {
+ WARN_LOG(Log::sceKernel, "POPS: failed to start %s", path);
+ }
+ }
+}
+
+void __PopsBootPatchEmulator(u32 textAddr, u32 textSize) {
+}
+
+// The installed firmware's version, from flash0:/vsh/etc/version.txt ("release:6.61:"). Empty if there's
+// none, as in a partial install.
+static std::string FirmwareVersion() {
+ std::vector data;
+ if (pspFileSystem.ReadEntireFile("flash0:/vsh/etc/version.txt", data, true) < 0)
+ return "";
+ const std::string text(data.begin(), data.end());
+ const size_t start = text.find("release:");
+ if (start == std::string::npos)
+ return "";
+ const size_t end = text.find(':', start + 8);
+ return end == std::string::npos ? "" : text.substr(start + 8, end - start - 8);
+}
+
+bool __PopsBootStartInit(FileLoader *pbp, const std::string &gamePath, std::string *error) {
+ const u32 base = PopsManBase();
+ bool known = base != 0;
+ for (const auto &code : POPSMAN_CODE) {
+ known = known && Memory::ReadUnchecked_U32(base + code.offset) == code.original;
+ }
+ // 6.35-6.39's manager has the same code at those places, but imports kernel functions by other
+ // NIDs.
+ const std::string version = FirmwareVersion();
+ if (!version.empty() && version != "6.60" && version != "6.61") {
+ known = false;
+ }
+ if (!known) {
+ auto er = GetI18NCategory(I18NCat::ERRORS);
+ *error = er->T("PS1FirmwareVersion", "So far, PS1 games only run with firmware 6.60 or 6.61, and a different version is installed. Install one of those from an official PSP update file in Settings > Tools > PSP Firmware.");
+ return false;
+ }
+ const u32 loadModule = base + POPSMAN_LOAD_MODULE;
+
+ const u32 pathSize = (u32)gamePath.size() + 1;
+ u32 allocSize = pathSize;
+ g_popsInitPath = kernelMemory.Alloc(allocSize, false, "PopsInitPath");
+ Memory::Memcpy(g_popsInitPath, gamePath.c_str(), pathSize, "PopsInitPath");
+
+ // The kernel's init: scePopsManLoadModule(path, 0). Starts with a1 = its copy of the path, below
+ // the manager's priority so it runs once module_start is done.
+ const u32 code[] = {
+ 0x00A02021, // move a0, a1
+ MIPS_MAKE_J(loadModule), // returns to the thread's end
+ 0x24050000, // li a1, 0
+ };
+ g_popsInitThread.reset(new HLEHelperThread("ScePopsInit", code, (u32)ARRAY_SIZE(code), 0x40, 0x4000));
+ g_popsInitThread->Start(pathSize, g_popsInitPath);
+ INFO_LOG(Log::sceKernel, "POPS: init will call scePopsManLoadModule(%s, 0)", gamePath.c_str());
+ return true;
+}
diff --git a/Core/HLE/PopsBoot.h b/Core/HLE/PopsBoot.h
new file mode 100644
index 0000000000..a919470eef
--- /dev/null
+++ b/Core/HLE/PopsBoot.h
@@ -0,0 +1,39 @@
+// 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
+
+#include
+
+#include "Common/CommonTypes.h"
+#include "Core/HLE/sceKernel.h"
+
+class FileLoader;
+class PointerWrap;
+
+void __PopsBootInit();
+void __PopsBootShutdown();
+void __PopsBootDoState(PointerWrap &p);
+bool PopsBootActive();
+
+// After the POPS manager is loaded: the user modules of the POPS boot configuration.
+void __PopsBootLoadModules();
+// After the POPS manager is loaded: have it load the emulator for the game, as the kernel's init does.
+// Fails for a manager other than 6.60's.
+bool __PopsBootStartInit(FileLoader *pbp, const std::string &gamePath, std::string *error);
+// The emulator module is loaded, not yet started.
+void __PopsBootPatchEmulator(u32 textAddr, u32 textSize);
diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp
index f9a5c939c6..fc82813739 100644
--- a/Core/HLE/sceKernel.cpp
+++ b/Core/HLE/sceKernel.cpp
@@ -61,6 +61,7 @@
#include "sceKernelHeap.h"
#include "sceKernelInterrupt.h"
#include "sceKernelThread.h"
+#include "PopsBoot.h"
#include "sceKernelMemory.h"
#include "sceKernelModule.h"
#include "sceKernelMutex.h"
@@ -199,6 +200,7 @@ void __KernelShutdown()
kernelObjects.Clear();
__RegShutdown();
+ __PopsBootShutdown();
__HttpShutdown();
__OpenPSIDShutdown();
__UsbCamShutdown();
@@ -327,6 +329,7 @@ void __KernelDoState(PointerWrap &p)
__DmacDoState(p);
__UsbCamDoState(p);
__NpDrmDoState(p);
+ __PopsBootDoState(p);
// IMPORTANT! Add new sections last!
}
@@ -1780,3 +1783,39 @@ const char *KernelErrorToString(u32 err) {
return nullptr;
}
}
+
+enum : u32 {
+ PSP_BOOT_DISC = 0x20,
+ PSP_BOOT_MS = 0x40,
+ PSP_INIT_KEYCONFIG_GAME = 0x200,
+ PSP_INIT_KEYCONFIG_POPS = 0x300,
+};
+
+// The POPS manager stays resident only when booted from the memory stick.
+static u32 sceKernelBootFrom() {
+ return hleLogInfo(Log::sceKernel, PopsBootActive() ? PSP_BOOT_MS : PSP_BOOT_DISC);
+}
+
+// The key configuration the kernel was booted with. Also known as sceKernelInitKeyConfig.
+static u32 sceKernelApplicationType() {
+ return hleLogInfo(Log::sceKernel, PopsBootActive() ? PSP_INIT_KEYCONFIG_POPS : PSP_INIT_KEYCONFIG_GAME);
+}
+
+static int sceKernelGetChunk(int chunkId) {
+ return hleLogWarning(Log::sceKernel, -1, "UNIMPL chunk %d", chunkId);
+}
+
+static int InitForKernel_9D33A110() {
+ return hleLogWarning(Log::sceKernel, 0, "UNIMPL");
+}
+
+const HLEFunction InitForKernel[] = {
+ {0x27932388, &WrapU_V, "sceKernelBootFrom", 'x', "", HLE_KERNEL_SYSCALL },
+ {0x7233B5BC, &WrapU_V, "sceKernelApplicationType", 'x', "", HLE_KERNEL_SYSCALL },
+ {0x2C6E9FE9, &WrapI_I, "sceKernelGetChunk", 'i', "i", HLE_KERNEL_SYSCALL },
+ {0x9D33A110, &WrapI_V, "InitForKernel_9D33A110", 'i', "", HLE_KERNEL_SYSCALL },
+};
+
+void Register_InitForKernel() {
+ RegisterHLEModule("InitForKernel", ARRAY_SIZE(InitForKernel), InitForKernel);
+}
diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h
index 10c8a2db08..4c37a9ede3 100644
--- a/Core/HLE/sceKernel.h
+++ b/Core/HLE/sceKernel.h
@@ -340,6 +340,7 @@ void Register_LoadExecForUser();
void Register_LoadExecForKernel();
void Register_ExceptionManagerForKernel();
void Register_UtilsForKernel();
+void Register_InitForKernel();
// returns nullptr if not found.
const char *KernelErrorToString(u32 err);
diff --git a/Core/HLE/sceKernelHeap.cpp b/Core/HLE/sceKernelHeap.cpp
index b1f5f163a1..6cf2d18e96 100644
--- a/Core/HLE/sceKernelHeap.cpp
+++ b/Core/HLE/sceKernelHeap.cpp
@@ -6,6 +6,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HLE/ErrorCodes.h"
#include "Core/HLE/FunctionWrappers.h"
+#include "Core/HLE/PopsBoot.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelHeap.h"
#include "Core/HLE/sceKernelMemory.h"
@@ -216,6 +217,15 @@ static int sceKernelSetUmdCacheOn(int on) {
return hleLogWarning(Log::sceKernel, 0, "UNIMPL");
}
+// A PS1 EBOOT declares none, and the POPS manager breaks unless it reads at least 3.00.
+static int sceKernelGetCompiledSdkVersionForKernel() {
+ int version = sceKernelGetCompiledSdkVersion();
+ if (version == 0 && PopsBootActive()) {
+ version = 0x06060010;
+ }
+ return hleLogDebug(Log::sceKernel, version);
+}
+
static u32 sceKernelMemset(u32 addr, int value, u32 size) {
if (Memory::IsValidRange(addr, size)) {
Memory::Memset(addr, (u8)value, size, "KernelMemset");
@@ -257,6 +267,8 @@ const HLEFunction SysMemForKernel[] = {
{ 0xF91FE6AA, &WrapI_V, "sceKernelSetParamSfo", 'i', "", HLE_KERNEL_SYSCALL },
{ 0xC2460DD5, &WrapI_V, "sceKernelSetParamSfo", 'i', "", HLE_KERNEL_SYSCALL },
{ 0x807179E7, &WrapI_V, "sceKernelSetParamSfo", 'i', "", HLE_KERNEL_SYSCALL },
+ // 6.60's NID.
+ { 0xB4F00CB5, &WrapI_V, "sceKernelGetCompiledSdkVersion", 'x', "", HLE_KERNEL_SYSCALL },
{ 0x8AE776AF, &WrapU_UIU, "sceKernelMemset", 'x', "xix", HLE_KERNEL_SYSCALL },
{ 0xC90B0992, &WrapI_IU, "sceKernelGetUIDcontrolBlock", 'i', "ix", HLE_KERNEL_SYSCALL },
};
diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp
index 14054404d3..17865523cf 100644
--- a/Core/HLE/sceKernelModule.cpp
+++ b/Core/HLE/sceKernelModule.cpp
@@ -67,6 +67,7 @@
#include "Core/HLE/sceKernelModule.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
+#include "Core/HLE/PopsBoot.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceMpeg.h"
#include "Core/HLE/scePsmf.h"
@@ -503,6 +504,23 @@ struct SceKernelSMOption {
static int actionAfterModule;
static std::set loadedModules;
+
+u32 KernelFindModuleExport(const char *libraryName, u32 nid) {
+ u32 error;
+ for (SceUID moduleId : loadedModules) {
+ PSPModule *module = kernelObjects.Get(moduleId, error);
+ if (!module) {
+ continue;
+ }
+ for (const FuncSymbolExport &func : module->exportedFuncs) {
+ if (func.nid == nid && !strncmp(func.moduleName, libraryName, KERNELOBJECT_MAX_NAME_LENGTH)) {
+ return func.symAddr;
+ }
+ }
+ }
+ return 0;
+}
+
// 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;
@@ -2233,6 +2251,10 @@ static PSPModule *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 load
}
}
+ if (PopsBootActive() && !strcmp(modinfo->name, "pops")) {
+ __PopsBootPatchEmulator(module->nm.text_addr, module->nm.text_size);
+ }
+
System_Notify(SystemNotification::SYMBOL_MAP_UPDATED);
u32 moduleSize = sizeof(module->nm);
diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h
index 9fe57c7973..1e649941cc 100644
--- a/Core/HLE/sceKernelModule.h
+++ b/Core/HLE/sceKernelModule.h
@@ -280,3 +280,6 @@ void Register_SystemCtrlForUser();
// Expose for use by KUBridge.
u32 sceKernelLoadModule(MIPSState *mips, const char *name, u32 flags, u32 optionAddr);
u32 sceKernelStartModule(u32 moduleId, u32 argsize, u32 argAddr, u32 returnValueAddr, u32 optionAddr);
+
+// Address of a function a loaded module exports, or 0.
+u32 KernelFindModuleExport(const char *libraryName, u32 nid);
diff --git a/Core/HW/MMIO.cpp b/Core/HW/MMIO.cpp
index d2e06770a1..d28d87e844 100644
--- a/Core/HW/MMIO.cpp
+++ b/Core/HW/MMIO.cpp
@@ -25,6 +25,7 @@
#include "Core/HW/GpioMMIO.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/MeMMIO.h"
+#include "Core/HW/PopsMMIO.h"
#include "Core/MemMap.h"
#include "Core/MIPS/MIPS.h"
@@ -145,6 +146,9 @@ void Init() {
MeMMIO::AudioDevice(),
&g_kernelUncached,
};
+ for (MMIODevice *device : PopsMMIO::Devices()) {
+ g_devices.push_back(device);
+ }
for (MMIODevice *device : g_devices) {
device->Reset();
}
diff --git a/Core/HW/PopsMMIO.cpp b/Core/HW/PopsMMIO.cpp
new file mode 100644
index 0000000000..22600077b2
--- /dev/null
+++ b/Core/HW/PopsMMIO.cpp
@@ -0,0 +1,102 @@
+// 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
+
+#include "Common/Common.h"
+#include "Common/Log.h"
+#include "Common/Serialize/Serializer.h"
+#include "Common/Serialize/SerializeFuncs.h"
+#include "Core/HLE/PopsBoot.h"
+#include "Core/HW/MMIO.h"
+#include "Core/HW/PopsMMIO.h"
+#include "GPU/GPU.h"
+#include "GPU/GPUCommon.h"
+
+namespace PopsMMIO {
+
+namespace {
+
+// The manager extends the emulator's stalled display list each frame: a FINISH, then the stall.
+class GeDevice : public MMIODevice {
+public:
+ GeDevice() : MMIODevice("GE", 0xBD400000, 0x400) {}
+
+ bool Read(MIPSState *mips, u32 offset, int size, u32 *value) override {
+ if (!PopsBootActive() || size != 4) {
+ return false;
+ }
+ DisplayList list;
+ const bool running = gpu && gpu->GetCurrentDisplayList(list);
+ if (offset == REG_STALL) {
+ *value = running ? list.stall : 0;
+ return true;
+ }
+ if (offset == REG_INTR_STATUS) {
+ // The HLE GPU runs up to the stall right away, so the FINISH before it is done.
+ const bool atStall = running && list.stall != 0 && (list.pc & 0x0FFFFFFF) == (list.stall & 0x0FFFFFFF);
+ *value = atStall ? INTR_FINISH : 0;
+ return true;
+ }
+ return false;
+ }
+
+ bool Write(MIPSState *mips, u32 offset, int size, u32 value) override {
+ if (!PopsBootActive() || size != 4) {
+ return false;
+ }
+ if (offset == REG_INTR_ACK) {
+ return true;
+ }
+ if (offset != REG_STALL) {
+ return false;
+ }
+ DisplayList list;
+ if (!gpu || !gpu->GetCurrentDisplayList(list)) {
+ WARN_LOG(Log::G3D, "GE stall register write %08x with no list running", value);
+ return true;
+ }
+ bool runList = false;
+ const u32 result = gpu->UpdateStall(list.id, value, &runList);
+ if (result & 0x80000000) {
+ WARN_LOG(Log::G3D, "GE stall register write %08x: list %d refused it (%08x)", value, list.id, result);
+ } else if (runList) {
+ gpu->ProcessDLQueue();
+ }
+ return true;
+ }
+
+ void Reset() override {}
+ void DoState(PointerWrap &p) override {}
+
+private:
+ static constexpr u32 REG_STALL = 0x10C;
+ static constexpr u32 REG_INTR_STATUS = 0x304;
+ static constexpr u32 REG_INTR_ACK = 0x310;
+ static constexpr u32 INTR_FINISH = 4;
+};
+
+GeDevice g_ge;
+
+} // namespace
+
+const std::vector &Devices() {
+ static const std::vector devices = { &g_ge };
+ return devices;
+}
+
+} // namespace PopsMMIO
diff --git a/Core/HW/PopsMMIO.h b/Core/HW/PopsMMIO.h
new file mode 100644
index 0000000000..b5b68d7981
--- /dev/null
+++ b/Core/HW/PopsMMIO.h
@@ -0,0 +1,34 @@
+// 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/.
+
+// Hardware the firmware's POPS manager (flash0:/kd/popsman.prx) drives directly during a PS1 boot
+// (Core/HLE/PopsBoot.cpp), besides the Media Engine's: the GE's stall and interrupt registers. The
+// devices only answer during that boot.
+
+#pragma once
+
+#include
+
+#include "Common/CommonTypes.h"
+
+class MMIODevice;
+
+namespace PopsMMIO {
+
+const std::vector &Devices();
+
+} // namespace PopsMMIO
diff --git a/Core/MIPS/MeCore.cpp b/Core/MIPS/MeCore.cpp
index 367dd299a5..e3cf554aab 100644
--- a/Core/MIPS/MeCore.cpp
+++ b/Core/MIPS/MeCore.cpp
@@ -28,6 +28,7 @@
#include "Core/ELF/PrxDecrypter.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/HLE/HLE.h"
+#include "Core/HLE/PopsBoot.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HW/MeMMIO.h"
@@ -69,9 +70,10 @@ enum : u32 {
MEState *g_meState = nullptr;
static int meIntrEvent = -1;
-// Booting the VSH counts as homebrew: vshmain.prx loads as an ELF.
-bool Wanted(bool homebrew) {
- return homebrew && g_Config.bEmulateMediaEngine;
+// Booting the VSH counts as homebrew: vshmain.prx loads as an ELF. A PS1 game always gets the ME:
+// the firmware's PS1 emulator plays its sound on it.
+bool Wanted(bool homebrew, bool pops) {
+ return pops || (homebrew && g_Config.bEmulateMediaEngine);
}
static void MeIntrCallback(u64 userdata, int cyclesLate) {
@@ -124,7 +126,9 @@ bool Init() {
g_meState->tickBase = CoreTiming::GetTicks(&mipsr4k);
PowerOn(g_meState);
meIntrEvent = CoreTiming::RegisterEvent("MeIntr", &MeIntrCallback);
- Memory::g_kernelModeAnywhere = true;
+ // Not for POPS, whose code that reaches hardware is all kernel code anyway. A savestate's POPS boot
+ // is known by now; a new one turns it off in KernelInit().
+ Memory::g_kernelModeAnywhere = !PopsBootActive();
INFO_LOG(Log::ME, "Media Engine created, %d KB eDRAM", space->edramSize / 1024);
return true;
}
@@ -260,6 +264,11 @@ static u32 CreateSysEventHandler(const char *name) {
void KernelInit() {
if (!g_meState)
return;
+ // The POPS manager boots the ME with its own code (POPS's reboot has no me_wrapper).
+ if (PopsBootActive()) {
+ Memory::g_kernelModeAnywhere = false;
+ return;
+ }
if (const u32 data = LoadFirmwareImage()) {
BootFirmware(g_meState, data);
g_meState->booting = true;
diff --git a/Core/MIPS/MeCore.h b/Core/MIPS/MeCore.h
index 7fa72971ae..0221822a7d 100644
--- a/Core/MIPS/MeCore.h
+++ b/Core/MIPS/MeCore.h
@@ -104,8 +104,8 @@ struct MEState {
// Null when the ME isn't emulated.
extern MEState *g_meState;
-// Whether the config and this boot (homebrew) want the ME.
-bool Wanted(bool homebrew);
+// Whether this boot wants the ME: homebrew with the setting on, or a PS1 game.
+bool Wanted(bool homebrew, bool pops);
// Creates the ME, in reset. Fails on platforms without an ME address space.
bool Init();
void Shutdown();
diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp
index 4b9f88d2a9..21a940e29d 100644
--- a/Core/PSPLoaders.cpp
+++ b/Core/PSPLoaders.cpp
@@ -54,6 +54,7 @@
#include "Core/System.h"
#include "Core/PSPLoaders.h"
#include "Core/HLE/sceKernelModule.h"
+#include "Core/HLE/PopsBoot.h"
static void UseLargeMem(int memsize) {
if (memsize != 1) {
@@ -658,6 +659,34 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string_view discId, bool load
return __KernelLoadExec(currentMIPS, finalName.c_str(), 0, error_string);
}
+// A PS1 EBOOT, run by the firmware's POPS manager, which loads its PS1 emulator for the game.
+bool Load_PSP_POPS(FileLoader *fileLoader, std::string *error_string) {
+ static const char *const popsmanPath = "flash0:/kd/popsman.prx";
+ if (!pspFileSystem.GetFileInfo(popsmanPath).exists) {
+ auto er = GetI18NCategory(I18NCat::ERRORS);
+ *error_string = er->T("PS1NoFirmware", "PS1 games need the PS1 emulator in the PSP's firmware, and no firmware is installed. Install firmware 6.60 or 6.61 from an official PSP update file in Settings > Tools > PSP Firmware. Starting a PSP game whose disc has an updater on it also installs its firmware.");
+ return false;
+ }
+
+ // Its directory as host0:, as for a homebrew EBOOT.
+ const Path fullPath = fileLoader->GetPath();
+ auto fs = std::make_shared(&pspFileSystem, fullPath.NavigateUp(), FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
+ pspFileSystem.Mount("host0:", fs);
+ pspFileSystem.SetStartingDirectory("host0:/");
+
+ __PopsBootInit();
+ const std::string gamePath = "host0:/" + fullPath.GetFilename();
+ INFO_LOG(Log::Loader, "POPS: booting %s through %s", gamePath.c_str(), popsmanPath);
+ if (!__KernelLoadExec(currentMIPS, popsmanPath, 0, error_string)) {
+ return false;
+ }
+ __PopsBootLoadModules();
+ if (!__PopsBootStartInit(fileLoader, gamePath, error_string)) {
+ return false;
+ }
+ return true;
+}
+
bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
auto umd = std::make_shared(&pspFileSystem, fileLoader, "data.ppdmp");
pspFileSystem.Mount("disc0:", umd);
diff --git a/Core/PSPLoaders.h b/Core/PSPLoaders.h
index e3b47f3a9a..7592b43427 100644
--- a/Core/PSPLoaders.h
+++ b/Core/PSPLoaders.h
@@ -26,6 +26,7 @@ class BlockDevice;
bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string);
bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string_view discId, bool loadGameConfigs, std::string *error_string);
bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string);
+bool Load_PSP_POPS(FileLoader *fileLoader, std::string *error_string);
bool MountGameISO(FileLoader *fileLoader, std::string *errorString);
bool LoadParamSFOFromDisc();
diff --git a/Core/System.cpp b/Core/System.cpp
index 289a2897d3..8760cf1d32 100644
--- a/Core/System.cpp
+++ b/Core/System.cpp
@@ -462,6 +462,10 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
gameTitle = g_CoreParameter.fileToStart.GetFilename();
}
break;
+ case IdentifiedFileType::PSP_PS1_PBP:
+ // Title and disc ID; the PS1 image itself is read by the firmware's POPS manager.
+ LoadParamSFOFromPBP(fileLoader);
+ break;
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
{
ERROR_LOG(Log::Loader, "PPSSPP doesn't support UMD Video.");
@@ -557,8 +561,12 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
CoreTiming::Init(&mipsr4k);
MMIO::Init();
- if (MeCore::Wanted(homebrew)) {
- MeCore::Init();
+ const bool pops = type == IdentifiedFileType::PSP_PS1_PBP;
+ if (MeCore::Wanted(homebrew, pops) && !MeCore::Init() && pops) {
+ // The firmware's PS1 emulator plays its sound on the ME.
+ auto er = GetI18NCategory(I18NCat::ERRORS);
+ *errorString = er->T("PS1NoMediaEngine", "PS1 games need the PSP's Media Engine, which PPSSPP can't emulate on this platform yet.");
+ return false;
}
DisplayHWInit();
@@ -636,6 +644,12 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin
}
break;
+ case IdentifiedFileType::PSP_PS1_PBP:
+ if (!Load_PSP_POPS(fileLoader, errorString)) {
+ return false;
+ }
+ break;
+
default:
GetBootError(type, errorString);
g_CoreParameter.fileToStart.clear();
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj
index ec65fce10b..1d33f307f1 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj
+++ b/UWP/CoreUWP/CoreUWP.vcxproj
@@ -180,6 +180,7 @@
+
@@ -263,6 +264,7 @@
+
@@ -481,6 +483,7 @@
+
@@ -563,6 +566,7 @@
+
diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters
index 1213e96991..fd6c82b8f7 100644
--- a/UWP/CoreUWP/CoreUWP.vcxproj.filters
+++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters
@@ -88,6 +88,7 @@
+
@@ -172,6 +173,7 @@
+
@@ -507,6 +509,7 @@
+
@@ -593,6 +596,7 @@
+
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index 9b670d77c3..b49ecf991c 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -658,6 +658,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HW/GpioMMIO.cpp \
$(SRC)/Core/HW/MMIO.cpp \
$(SRC)/Core/HW/MeMMIO.cpp \
+ $(SRC)/Core/HW/PopsMMIO.cpp \
$(SRC)/Core/HW/MemoryStick.cpp \
$(SRC)/Core/HW/MpegDemux.cpp.arm \
$(SRC)/Core/HW/AvcDecoder.cpp.arm \
@@ -752,6 +753,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HLE/NetInetConstants.cpp \
$(SRC)/Core/HLE/SocketManager.cpp \
$(SRC)/Core/HLE/Plugins.cpp \
+ $(SRC)/Core/HLE/PopsBoot.cpp \
$(SRC)/Core/HLE/sceAdler.cpp \
$(SRC)/Core/HLE/sceAmctrl.cpp \
$(SRC)/Core/HLE/sceAtrac.cpp \
diff --git a/libretro/Makefile.common b/libretro/Makefile.common
index 793294ec1c..a9eef287aa 100644
--- a/libretro/Makefile.common
+++ b/libretro/Makefile.common
@@ -759,6 +759,7 @@ SOURCES_CXX += \
$(COREDIR)/HLE/NetInetConstants.cpp \
$(COREDIR)/HLE/SocketManager.cpp \
$(COREDIR)/HLE/Plugins.cpp \
+ $(COREDIR)/HLE/PopsBoot.cpp \
$(COREDIR)/HLE/sceSha256.cpp \
$(COREDIR)/HLE/sceSircs.cpp \
$(COREDIR)/HLE/sceG729.cpp \
@@ -852,6 +853,7 @@ SOURCES_CXX += \
$(COREDIR)/HW/GpioMMIO.cpp \
$(COREDIR)/HW/MMIO.cpp \
$(COREDIR)/HW/MeMMIO.cpp \
+ $(COREDIR)/HW/PopsMMIO.cpp \
$(COREDIR)/HW/SimpleAudioDec.cpp \
$(COREDIR)/HW/Atrac3Standalone.cpp \
$(COREDIR)/HW/AsyncIOManager.cpp \