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 \