diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index f3b4b8861f..2be2e597dd 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -363,6 +363,7 @@ void RegisterAllModules() { Register_sceSysEventForKernel(); Register_sceAmctrl_driver(); Register_InitForKernel(); + Register_PopsBootCalls(); // 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 index 9a74238f47..b5d931d13a 100644 --- a/Core/HLE/PopsBoot.cpp +++ b/Core/HLE/PopsBoot.cpp @@ -23,11 +23,14 @@ #include #include +#include "zlib.h" #include "Common/Serialize/Serializer.h" #include "Common/Data/Text/I18n.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/MemBlockInfo.h" +#include "Core/ELF/PBPReader.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/HLE.h" @@ -44,13 +47,17 @@ #include "Core/System.h" static bool g_popsBoot = false; +static bool g_popsConvertedPBP = 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. +// The 6.60 POPS manager (6.61's has the same code), from its base: scePopsManLoadModule, and the +// checks replaced for store and converted games. Its ME loop (+2F88) runs on the emulated ME. enum : u32 { POPSMAN_LOAD_MODULE = 0x1E20, + // Whether data matches its MAC: its own, and the one it exports as sceMeAudio_F6637A72. + POPSMAN_CHECK_MAC = 0x0A28, + POPSMAN_CHECK_MAC_EXPORT = 0x0AB8, }; // What the places used here hold in the 6.60/6.61 manager. Checked when it's loaded. @@ -59,6 +66,8 @@ static const struct { u32 original; } POPSMAN_CODE[] = { { POPSMAN_LOAD_MODULE, 0x27BDFEE0 }, // addiu sp, sp, -0x120 + { POPSMAN_CHECK_MAC, 0x27BDFFC0 }, // addiu sp, sp, -0x40 + { POPSMAN_CHECK_MAC_EXPORT, 0x27BDFFB0 }, }; static const u32 SCE_POPSMAN_LOAD_MODULE_NID = 0x29B3FB24; @@ -69,6 +78,7 @@ void __PopsBootInit() { void __PopsBootShutdown() { g_popsBoot = false; + g_popsConvertedPBP = false; // The kernel objects and memory are already gone with the kernel. if (g_popsInitThread) { g_popsInitThread->Forget(); @@ -105,6 +115,7 @@ void __PopsBootDoState(PointerWrap &p) { return; } Do(p, g_popsBoot); + Do(p, g_popsConvertedPBP); Do(p, g_popsInitPath); DoHelperThread(p, g_popsInitThread); } @@ -114,6 +125,50 @@ static u32 PopsManBase() { return loadModule ? loadModule - POPSMAN_LOAD_MODULE : 0; } +// A converted PBP's disc block, raw DEFLATE. Returns the output capacity - 1, as the emulator's +// own decoder does. +static int __PopsDeflateDecompress(u32 destSize, u32 src, u32 dest) { + if (!Memory::IsValidRange(dest, destSize) || !Memory::IsValidAddress(src)) { + return hleLogError(Log::sceKernel, -1, "bad buffers"); + } + z_stream stream{}; + stream.next_in = (Bytef *)Memory::GetPointerUnchecked(src); + stream.avail_in = (uInt)Memory::ClampValidSizeAt(src, destSize * 2); + stream.next_out = (Bytef *)Memory::GetPointerWriteUnchecked(dest); + stream.avail_out = (uInt)destSize; + if (inflateInit2(&stream, -MAX_WBITS) != Z_OK) { + return hleLogError(Log::sceKernel, -1, "inflateInit2 failed"); + } + const int err = inflate(&stream, Z_FINISH); + inflateEnd(&stream); + if (err != Z_STREAM_END) { + return hleLogError(Log::sceKernel, -1, "inflate failed %d", err); + } + NotifyMemInfo(MemBlockFlags::WRITE, dest, (u32)stream.total_out, "PopsDeflate"); + return hleLogDebug(Log::sceKernel, (int)(destSize - 1)); +} + +// A PS1 PBP's disc image starts after the PSAR's header: 0x400 in, or 0x200 for a multi-disc one. +static bool ReadDiscImage(const PBPReader &pbp, size_t size, u8 *out) { + char psar[8]; + if (!pbp.ReadSubFile(PBP_UNKNOWN_PSAR, 0, sizeof(psar), psar)) { + return false; + } + return pbp.ReadSubFile(PBP_UNKNOWN_PSAR, memcmp(psar, "PSTITLE", 7) == 0 ? 0x200 : 0x400, size, out); +} + +const HLEFunction PopsBootCalls[] = { + { 0x13370006, &WrapI_UUU<__PopsDeflateDecompress>, "__PopsDeflateDecompress", 'i', "xxx" }, +}; + +void Register_PopsBootCalls() { + RegisterHLEModule("PopsBootCalls", ARRAY_SIZE(PopsBootCalls), PopsBootCalls); +} + +static void PatchCall(u32 addr, const char *func) { + Memory::WriteUnchecked_U32(MIPS_MAKE_SYSCALL("PopsBootCalls", func), addr); +} + // The user modules the 6.60 POPS boot configuration (pspbtcnf) starts after the manager. void __PopsBootLoadModules() { static const char *const paths[] = { @@ -134,7 +189,63 @@ void __PopsBootLoadModules() { } } -void __PopsBootPatchEmulator(u32 textAddr, u32 textSize) { +// A store PBP's disc image is a PGD, a converted one's isn't. +static bool IsConvertedPBP(FileLoader *loader) { + PBPReader pbp(loader); + u8 magic[4]; + return pbp.IsValid() && ReadDiscImage(pbp, sizeof(magic), magic) && memcmp(magic, "\0PGD", 4) != 0; +} + +// Sends code at addr to a syscall through a trampoline, then on to resume. The emulator takes +// syscalls in its own text for import stubs, so they can't go in place. +static void PatchTrampoline(u32 addr, const char *func, u32 delaySlotOp, u32 resume) { + u32 size = 16; + const u32 trampoline = kernelMemory.Alloc(size, false, func); + PatchCall(trampoline, func); + Memory::WriteUnchecked_U32(delaySlotOp, trampoline + 4); + Memory::WriteUnchecked_U32(MIPS_MAKE_J(resume), trampoline + 8); + Memory::WriteUnchecked_U32(MIPS_MAKE_NOP(), trampoline + 12); + Memory::WriteUnchecked_U32(MIPS_MAKE_J(trampoline), addr); + currentMIPS->InvalidateICacheRangeDeferred(addr, 8); + currentMIPS->InvalidateICacheRangeDeferred(trampoline, size); +} + +void __PopsBootPatchEmulator(u32 textAddr, u32 textSize, u32 decodeStub) { + if (!g_popsConvertedPBP) { + return; + } + if (!decodeStub) { + ERROR_LOG(Log::sceKernel, "POPS: the emulator doesn't import scePopsMan_0090B2C8, the disc won't read"); + return; + } + // A converted PBP's disc blocks are raw DEFLATE. As CFW's popcorn does, send the decoder calls + // (the jal two words after each "lw a2, 0xC(s3)") to the scePopsMan_0090B2C8 import, which + // inflates. + int sites = 0; + for (u32 addr = textAddr; addr + 8 < textAddr + textSize; addr += 4) { + const u32 op = Memory::ReadUnchecked_U32(addr); + const u32 next = Memory::ReadUnchecked_U32(addr + 4); + if (op == 0x8E66000C) { + Memory::WriteUnchecked_U32(MIPS_MAKE_JAL(decodeStub), addr + 8); + sites++; + } else if ((op == 0x14C00014 && next == 0x24E2FFFF) || (op == 0x14A00014 && next == 0x24C2FFFF)) { + // Also popcorn's: the track index's length doesn't count the CD audio tracks a converted + // image adds, so always take the full-length branch (b +0x14; addiu v0, aN, 0). + Memory::WriteUnchecked_U32(0x10000014, addr); + Memory::WriteUnchecked_U32(next & 0xFFFF0000, addr + 4); + } + } + Memory::WriteUnchecked_U32(MIPS_MAKE_JR_RA(), decodeStub); + PatchCall(decodeStub + 4, "__PopsDeflateDecompress"); + currentMIPS->InvalidateICacheRangeDeferred(textAddr, textSize); + currentMIPS->InvalidateICacheRangeDeferred(decodeStub, 8); + INFO_LOG(Log::sceKernel, "POPS: converted PBP, %d disc decoder calls now inflate", sites); +} + +static void PatchReturn(u32 addr, u32 delaySlotOp) { + Memory::WriteUnchecked_U32(MIPS_MAKE_JR_RA(), addr); + Memory::WriteUnchecked_U32(delaySlotOp, addr + 4); + currentMIPS->InvalidateICacheRangeDeferred(addr, 8); } // The installed firmware's version, from flash0:/vsh/etc/version.txt ("release:6.61:"). Empty if there's @@ -168,7 +279,13 @@ bool __PopsBootStartInit(FileLoader *pbp, const std::string &gamePath, std::stri *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; } + g_popsConvertedPBP = IsConvertedPBP(pbp); const u32 loadModule = base + POPSMAN_LOAD_MODULE; + if (g_popsConvertedPBP) { + // A converted PBP has no MACs. As popcorn does, take its data as authentic. + PatchReturn(base + POPSMAN_CHECK_MAC, 0x24020001); // li v0, 1 + PatchReturn(base + POPSMAN_CHECK_MAC_EXPORT, 0x24020001); + } const u32 pathSize = (u32)gamePath.size() + 1; u32 allocSize = pathSize; diff --git a/Core/HLE/PopsBoot.h b/Core/HLE/PopsBoot.h index a919470eef..bed9c90587 100644 --- a/Core/HLE/PopsBoot.h +++ b/Core/HLE/PopsBoot.h @@ -36,4 +36,7 @@ void __PopsBootLoadModules(); // 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); +void __PopsBootPatchEmulator(u32 textAddr, u32 textSize, u32 decodeStub); + + +void Register_PopsBootCalls(); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 17865523cf..ed534f6590 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2252,7 +2252,13 @@ 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); + u32 decodeStub = 0; + for (const FuncSymbolImport &func : module->importedFuncs) { + if (func.nid == 0x0090B2C8 && !strncmp(func.moduleName, "scePopsMan", KERNELOBJECT_MAX_NAME_LENGTH)) { + decodeStub = func.stubAddr; + } + } + __PopsBootPatchEmulator(module->nm.text_addr, module->nm.text_size, decodeStub); } System_Notify(SystemNotification::SYMBOL_MAP_UPDATED);