From 0b1e1a9bb301667c25efcc33e4d8f0dc628d5453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Oct 2026 12:46:09 +0200 Subject: [PATCH] POPS: Boot PS1 EBOOTs through the firmware's POPS manager A PS1 EBOOT now boots the installed firmware's POPS manager (flash0:/kd/popsman.prx), which loads its PS1 emulator (pops_0Xg.prx) for the game. Load_PSP_POPS stands in for the kernel's reboot into its POPS configuration: it loads the manager and that configuration's user modules, and a helper thread makes the kernel init's scePopsManLoadModule(path, 0) call. InitForKernel reports a POPS boot from the memory stick. The emulator plays its sound on the Media Engine, so a PS1 boot always gets the emulated ME, whatever the homebrew setting says, and is refused on platforms that can't emulate it. The manager boots the ME with its own code (POPS's reboot has no me_wrapper, so Sony's ME firmware isn't booted for it), and its ME loop runs there unchanged, playing through the audio block. Everything in POPS that reaches hardware registers is kernel code, so a POPS boot leaves the ME's kernel mode stopgap (g_kernelModeAnywhere) off: 7% fewer host instructions in Crash Bandicoot. The manager also drives the GE directly: its stall register and FINISH status are an MMIO device that only answers during a POPS boot, and stall writes extend the HLE GPU's current list. The 6.60 and 6.61 popsman.prx and pops_01g/02g.prx have the same code. The manager's is checked when it's loaded, and any other one fails the boot with a message. Savestates include the boot's threads. Reading a game's disc image needs the next two changes. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/CMakeLists.txt | 4 + Core/Core.vcxproj | 4 + Core/Core.vcxproj.filters | 8 ++ Core/Dialog/PSPDialog.cpp | 7 +- Core/HLE/HLE.cpp | 11 ++ Core/HLE/HLE.h | 1 + Core/HLE/HLEHelperThread.h | 3 + Core/HLE/HLETables.cpp | 2 + Core/HLE/PopsBoot.cpp | 189 ++++++++++++++++++++++++++++ Core/HLE/PopsBoot.h | 39 ++++++ Core/HLE/sceKernel.cpp | 39 ++++++ Core/HLE/sceKernel.h | 1 + Core/HLE/sceKernelHeap.cpp | 12 ++ Core/HLE/sceKernelModule.cpp | 22 ++++ Core/HLE/sceKernelModule.h | 3 + Core/HW/MMIO.cpp | 4 + Core/HW/PopsMMIO.cpp | 102 +++++++++++++++ Core/HW/PopsMMIO.h | 34 +++++ Core/MIPS/MeCore.cpp | 17 ++- Core/MIPS/MeCore.h | 4 +- Core/PSPLoaders.cpp | 29 +++++ Core/PSPLoaders.h | 1 + Core/System.cpp | 18 ++- UWP/CoreUWP/CoreUWP.vcxproj | 4 + UWP/CoreUWP/CoreUWP.vcxproj.filters | 4 + android/jni/Android.mk | 2 + libretro/Makefile.common | 2 + 27 files changed, 556 insertions(+), 10 deletions(-) create mode 100644 Core/HLE/PopsBoot.cpp create mode 100644 Core/HLE/PopsBoot.h create mode 100644 Core/HW/PopsMMIO.cpp create mode 100644 Core/HW/PopsMMIO.h 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 \