From 31fd8e8a1cebe5e1c63a1da023f37d0c2b7ae181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 23 Oct 2026 19:36:02 +0200 Subject: [PATCH] POPS: Multi-disc on every firmware from 5.01, by leaving the descrambling to the emulator With the converted header's key of zeros, the emulator's descrambling of its disc switching strings only reverses them, so ours go in backwards and its code is left alone. The tables are found by the code that indexes them, which is the same from 3.95 on with only the registers differing. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/FileLoaders/PS1EbootFileLoader.cpp | 4 +-- Core/HLE/PopsBoot.cpp | 34 +++++++++---------------- Core/PSPLoaders.cpp | 15 +---------- docs/POPS.md | 16 ++++++------ 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/Core/FileLoaders/PS1EbootFileLoader.cpp b/Core/FileLoaders/PS1EbootFileLoader.cpp index 2d9efef95a..bbfdbda011 100644 --- a/Core/FileLoaders/PS1EbootFileLoader.cpp +++ b/Core/FileLoaders/PS1EbootFileLoader.cpp @@ -1108,8 +1108,8 @@ void PS1EbootFileLoader::BuildHeaders() { if (multiDisc) { // The fields the emulator reads (pops +1B2F0 in 6.60): the disc offsets, the first disc's ID, and the // title. The end of the last disc and its +0x2D31 are where a STARTDAT would follow, as in a PSISOIMG. - // The MACs a store game's header has, and the key its disc switching strings are descrambled with, - // stay zero: PopsBoot sees to both. + // The MACs a store game's header has stay zero (PopsBoot passes them), and so does the key its disc + // switching strings are descrambled with, which then only reverses PopsBoot's strings. u8 *title = pbp + psarOffset_; memcpy(title, "PSTITLEIMG000000", 16); *(u32_le *)(title + 0x10) = (u32)end; diff --git a/Core/HLE/PopsBoot.cpp b/Core/HLE/PopsBoot.cpp index c65ef33714..867ed12ffd 100644 --- a/Core/HLE/PopsBoot.cpp +++ b/Core/HLE/PopsBoot.cpp @@ -423,8 +423,8 @@ static bool SetsBlockSizeBefore(u32 addr, u32 textAddr) { // The emulator's multi-disc strings, 14 for each of its 12 languages, as {length, UTF-16 pointer}: the HOME // menu's item, then three dialogs of a line each. A store game's PSTITLEIMG header has the key they're -// descrambled with when a game has several discs (pops +29xxx in 6.60). A converted one has none, so the -// descrambling is skipped and they're ours, from our translations. Each dialog's lines are left-aligned and +// descrambled with when a game has several discs (pops +29xxx in 6.60). A converted one's is zeros, which +// only reverses them, and they're ours, from our translations. Each dialog's lines are left-aligned and // centred as a block. Its disc number is drawn over a gap in a line, at a position (one each for the // switch dialog, the disc picker's label and the confirmation) from a table of its own. enum { @@ -629,9 +629,10 @@ static bool InstallDiscSwitchText(u32 stringTable, u32 positionTable, u32 resetT u32 ptr = strings; u32 entries[DISC_STRING_COUNT][2]; for (int i = 0; i < DISC_STRING_COUNT; i++) { + // Backwards: the descrambling reverses them, and with our header's key of zeros does nothing else. const std::u16string &s = text.strings[i]; for (size_t c = 0; c < s.size(); c++) { - Memory::WriteUnchecked_U16(s[c], ptr + (u32)c * 2); + Memory::WriteUnchecked_U16(s[s.size() - 1 - c], ptr + (u32)c * 2); } entries[i][0] = (u32)s.size(); entries[i][1] = s.empty() ? 0 : ptr; @@ -659,22 +660,15 @@ static u32 LuiAddiuAddress(u32 addr) { return (Memory::ReadUnchecked_U32(addr) << 16) + (s16)(Memory::ReadUnchecked_U32(addr + 4) & 0xFFFF); } -// Finds the multi-disc strings' code in the emulator, and skips their descrambling for ours. +// Finds the multi-disc strings' tables in the emulator, and puts ours there. static void PatchDiscSwitchText(u32 textAddr, u32 textSize) { - u32 stringTable = 0, positionTable = 0, resetTable = 0, decode = 0, decodeEnd = 0; + u32 stringTable = 0, positionTable = 0, resetTable = 0; for (u32 addr = textAddr; addr + 0x20 < textAddr + textSize; addr += 4) { auto at = [&](u32 offset) { return Memory::ReadUnchecked_U32(addr + offset); }; - // The language's row: lang * 0x70 + table, into s7. - if (at(0) == 0x000219C0 && at(4) == 0x00021100 && at(8) == 0x00621823 && (at(12) >> 16) == 0x3C02 && (at(16) >> 16) == 0x2442 && at(20) == 0x0062B821) { + // The language's row: lang * 0x70 + table (into s7 in 6.x, k0 in 5.x, the stack in 3.x). + if (!stringTable && at(0) == 0x000219C0 && at(4) == 0x00021100 && at(8) == 0x00621823 && (at(12) >> 16) == 0x3C02 && (at(16) >> 16) == 0x2442 && (at(20) & 0xFFFF07FF) == 0x00620021) { stringTable = LuiAddiuAddress(addr + 12); } - // beq s7, zero, past the text; li s5, 0: then the descrambling, until bne t1, s0; addiu a1, a1, 8. - if (stringTable && !decode && (at(0) >> 16) == 0x12E0 && at(4) == 0x0000A821) { - decode = addr + 8; - } - if (decode && !decodeEnd && addr > decode && (at(0) >> 16) == 0x1530 && at(4) == 0x24A50008) { - decodeEnd = addr + 8; - } // The number position, (lang * 3 + i) * 4 + table. if (at(0) == 0x00031040 && at(4) == 0x00431021 && at(8) == 0x00501021 && (at(12) >> 16) == 0x3C03 && (at(16) >> 16) == 0x2463 && at(20) == 0x00021080) { positionTable = LuiAddiuAddress(addr + 12); @@ -684,17 +678,13 @@ static void PatchDiscSwitchText(u32 textAddr, u32 textSize) { resetTable = LuiAddiuAddress(addr); } } - if (!stringTable || !decode || !decodeEnd || !positionTable || !resetTable || decodeEnd - decode > 0x400) { - WARN_LOG(Log::sceKernel, "POPS: didn't find the disc switching text (%08x %08x-%08x %08x %08x)", stringTable, decode, decodeEnd, positionTable, resetTable); + if (!stringTable || !positionTable || !resetTable) { + WARN_LOG(Log::sceKernel, "POPS: didn't find the disc switching text (%08x %08x %08x)", stringTable, positionTable, resetTable); return; } - if (!InstallDiscSwitchText(stringTable, positionTable, resetTable)) { - return; + if (InstallDiscSwitchText(stringTable, positionTable, resetTable)) { + INFO_LOG(Log::sceKernel, "POPS: disc switching text at %08x, numbers at %08x", stringTable, positionTable); } - Memory::WriteUnchecked_U32(0x10000000 | (((decodeEnd - (decode + 4)) >> 2) & 0xFFFF), decode); // b decodeEnd - Memory::WriteUnchecked_U32(MIPS_MAKE_NOP(), decode + 4); - currentMIPS->InvalidateICacheRangeDeferred(decode, 8); - INFO_LOG(Log::sceKernel, "POPS: disc switching text at %08x, numbers at %08x", stringTable, positionTable); } void __PopsBootPatchEmulator(u32 textAddr, u32 textSize, u32 decodeStub) { diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 01eed5e891..0700708137 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -672,23 +672,10 @@ bool Load_PSP_POPS(FileLoader *fileLoader, std::string *error_string) { return false; } - // Several discs from a playlist need the disc switching text PopsBoot puts in the emulator, which it - // only finds in 6.20 and later. - const PS1EbootFileLoader *discImage = dynamic_cast(fileLoader); - if (discImage && discImage->NumDiscs() > 1) { - InstalledFirmwareInfo info; - ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &info, false); - if (FirmwareVersionToInt(info.version) < 620) { - auto po = GetI18NCategory(I18NCat::POPS); - *error_string = po->T("PS1MultiDiscFirmware", "So far, PS1 games of several discs only run with firmware 6.20 or later. Install one of those from an official PSP update file in Settings > Tools > PSP Firmware."); - return false; - } - } - // Its directory as host0:, as for a homebrew EBOOT. An EBOOT made from a disc image is the only file there. const Path fullPath = fileLoader->GetPath(); std::string gamePath; - if (discImage) { + if (dynamic_cast(fileLoader)) { pspFileSystem.Mount("host0:", std::make_shared(&pspFileSystem, fileLoader, "EBOOT.PBP")); gamePath = "host0:/EBOOT.PBP"; } else { diff --git a/docs/POPS.md b/docs/POPS.md index b24ecac4d3..44954cb696 100644 --- a/docs/POPS.md +++ b/docs/POPS.md @@ -88,13 +88,13 @@ Reset Game change it. A store game's PSTITLEIMG header also has a MAC for each disc header, which the manager's disc header check compares when there are several discs (by calling sceDrmBBMacFinal2 itself, in the export that is 6.60's sceMeAudio_14447BA0), and at +0x28C the key the emulator's disc switching strings are descrambled with. -A converted one has neither: every MAC the manager compares passes, and PopsBoot skips the descrambling and -puts our strings, from our translations, in the emulator's table (14 for each of its 12 languages, as -{length, UTF-16 pointer}). Each dialog's lines are left-aligned and centred as a block, in ltn0.pgf; the -disc number is drawn over a gap, at a position from a table per language (one each for the switch dialog, -the disc picker's label and the confirmation, and the reset dialog's as a move from the label's), which -PopsBoot computes for our text. That code is found by its instructions in 6.20 to 6.61, so several discs -need one of those; earlier versions have the same descrambling, compiled differently. +A converted one has neither: every MAC the manager compares passes, and PopsBoot puts our strings, from our +translations, in the emulator's table (14 for each of its 12 languages, as {length, UTF-16 pointer}). They +go in backwards: with a key of zeros, the descrambling only reverses each string. Each dialog's lines are +left-aligned and centred as a block, in ltn0.pgf; the disc number is drawn over a gap, at a position from a +table per language (one each for the switch dialog, the disc picker's label and the confirmation, and the +reset dialog's as a move from the label's), which PopsBoot computes for our text. The tables are found by +the code that indexes them, which is the same from 3.95 to 6.61. On Android, a game of several files (a cue sheet, `.ccd`, `.mds` or playlist) only loads through a folder PPSSPP has access to, not as a single picked file. @@ -119,6 +119,6 @@ The HOME button opens the emulator's menu. Savestates work. - The anti-LibCrypt word some PAL games need in a converted image (PSISOIMG + 0x12B0). ARK-4's popcorn has a table of them by disc ID, switched off. Converting tools can patch the image instead. - ARK-4's per-game CONFIG.BIN next to the EBOOT, copied over the image's config (PSISOIMG + 0x420). -- Multi-disc games on firmware before 6.20, and booting an image straight from an archive. +- Booting an image straight from an archive. - A byte popcorn changes in reads of converted images (0x41B, from 0x27 to 0x55 when 0x41B-0x41E read 27 19 22 41) without knowing why.