diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index 1fcd5be0cd..efbe80c0e8 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include "Common/File/Path.h" @@ -68,6 +69,8 @@ struct CoreParameter { Path mountIso; // If non-empty, and fileToStart is an ELF or PBP, will mount this ISO in the background to umd1:. Path mountRoot; // If non-empty, and fileToStart is an ELF or PBP, mount this as host0:. + // Overrides the VSH's "UMD auto-start" setting (bVshUmdAutoboot) for this boot. + std::optional umdAutoboot; Path nandRoot; // If non-empty, use this directory as the root above flash0:-flash3: (which are hosted in subdirectories flash0-flash3 of it). diff --git a/Core/HLE/sceReg.cpp b/Core/HLE/sceReg.cpp index 5d1b27c7f0..60c3133000 100644 --- a/Core/HLE/sceReg.cpp +++ b/Core/HLE/sceReg.cpp @@ -9,6 +9,7 @@ #include "Common/Serialize/SerializeMap.h" #include "Common/StringUtils.h" #include "Core/Config.h" +#include "Core/System.h" #define SYSTEM_REGISTRY "/system" #define REG_KEYNAME_SIZE 27 @@ -61,9 +62,14 @@ struct KeyValue { const KeyValue *dirContents; // intValue is the count. // If set, an INT key reads this setting (as 0 or 1), even after the guest writes it: the VSH // rewrites every key with its default at each boot, since category_version is below its schema. - const bool *boolSetting = nullptr; + bool (*boolSetting)() = nullptr; }; +static bool UmdAutoboot() { + // Forced on when a UMD Video disc picked from the game list boots the XMB. + return PSP_CoreParameter().umdAutoboot.value_or(g_Config.bVshUmdAutoboot); +} + // Guest-written registry values (sceRegCreateKey / sceRegSetKeyValue), keyed by category path // (no leading slash, e.g. "DATA/FONT") then key name. The static dump above is read-only, but // plugins like CLANNAD's chfont.prx register their font by creating /DATA/FONT/PROPERTY/INFO18 @@ -768,7 +774,7 @@ static const KeyValue tree_CONFIG_SYSTEM_CHARACTER_SET[] = { static const KeyValue tree_CONFIG_SYSTEM[] = { { "owner_name", ValueType::STR, "P939", 5 }, { "backlight_brightness", ValueType::INT, "", (int)0x3 }, // decimal: 3 - { "umd_autoboot", ValueType::INT, "", (int)0x1, nullptr, &g_Config.bVshUmdAutoboot }, // decimal: 1 + { "umd_autoboot", ValueType::INT, "", (int)0x1, nullptr, &UmdAutoboot }, // decimal: 1 { "usb_charge", ValueType::INT, "", (int)0x1 }, // decimal: 1 { "umd_cache", ValueType::INT, "", (int)0x1 }, // decimal: 1 { "usb_auto_connect", ValueType::INT, "", (int)0x1 }, // decimal: 1 @@ -1096,7 +1102,7 @@ static const MergedCategory &GetMergedCategory(const std::string &path) { for (auto &kv : merged.keyvals) { if (kv.boolSetting) { - kv.intValue = *kv.boolSetting ? 1 : 0; + kv.intValue = kv.boolSetting() ? 1 : 0; } } diff --git a/Core/Util/PSARUnpack.cpp b/Core/Util/PSARUnpack.cpp index 15d5d758a8..0fc1526b5d 100644 --- a/Core/Util/PSARUnpack.cpp +++ b/Core/Util/PSARUnpack.cpp @@ -1356,6 +1356,13 @@ bool FirmwareVersionSupportsVSH(std::string_view version) { return FirmwareVersionToInt(version) >= 150; } +bool FirmwareVersionPlaysUmdVideo(std::string_view version) { + // Measured by booting Bourne Supremacy with auto-start on: 2.71 through 6.61 reach its disc menu. + // 1.50, 2.00 and 2.60 crash in video_plugin or paf as soon as the disc is in the drive, and + // 2.50 says the system software (version ".") is too old for it. + return FirmwareVersionSupportsVSH(version) && FirmwareVersionToInt(version) >= 271; +} + std::string BundledUpdateInfo::Describe() const { if (!present) { return std::string(); diff --git a/Core/Util/PSARUnpack.h b/Core/Util/PSARUnpack.h index 9af8392c9d..d19bf69329 100644 --- a/Core/Util/PSARUnpack.h +++ b/Core/Util/PSARUnpack.h @@ -185,6 +185,9 @@ int FirmwareVersionToInt(std::string_view version); // the module patches it needs are version-specific, so it won't get anywhere. bool FirmwareVersionSupportsVSH(std::string_view version); +// Whether that VSH's own player plays UMD Video discs. +bool FirmwareVersionPlaysUmdVideo(std::string_view version); + // Installs a firmware into the NAND directory. Two firmwares can't be merged - a file the new one // doesn't have would linger and still get loaded - so this replaces rather than overlays. // diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 50e1229ce1..5a0df93fe2 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -161,8 +161,8 @@ void EmuScreen::SetPSPAnalog(int iInternalScreenRotation, int stick, float x, fl __CtrlSetAnalogXY(stick, x, y); } -EmuScreen::EmuScreen(const Path &filename, const Path &mountIso) - : gamePath_(filename), mountIso_(mountIso) { +EmuScreen::EmuScreen(const Path &filename, const Path &mountIso, std::optional umdAutoboot) + : gamePath_(filename), mountIso_(mountIso), umdAutoboot_(umdAutoboot) { saveStateSlot_ = SaveState::GetCurrentSlot(); g_controlMapper.AddListener(this); @@ -322,6 +322,7 @@ void EmuScreen::ProcessGameBoot(const Path &filename) { coreParam.enableSound = g_Config.bEnableSound; coreParam.fileToStart = filename; coreParam.mountIso = mountIso_; + coreParam.umdAutoboot = umdAutoboot_; coreParam.mountRoot = g_Config.mountRoot; coreParam.startBreak = !g_Config.bAutoRun; coreParam.headLess = false; @@ -620,6 +621,7 @@ void EmuScreen::sendMessage(UIMessage message, const char *value) { bootIsReset_ = false; gamePath_ = newGamePath; mountIso_.clear(); + umdAutoboot_.reset(); } } else if (message == UIMessage::CONFIG_LOADED) { // In case we need to position touch controls differently. diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 7536e6b49c..8aa1ed8be5 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include @@ -42,7 +43,8 @@ struct ImGuiContext; class EmuScreen : public UIScreen, protected ControlListener { public: // mountIso, if set, is mounted on umd1: when filename is an ELF/PBP (like the VSH). - EmuScreen(const Path &filename, const Path &mountIso = Path()); + // umdAutoboot overrides the VSH's UMD auto-start setting. + EmuScreen(const Path &filename, const Path &mountIso = Path(), std::optional umdAutoboot = std::nullopt); ~EmuScreen(); const char *tag() const override { return "Emu"; } @@ -109,6 +111,7 @@ private: bool bootIsReset_ = false; Path gamePath_; Path mountIso_; + std::optional umdAutoboot_; bool quit_ = false; std::string errorMessage_; diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 6acc975c81..8ab9c69deb 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -367,7 +367,7 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) { LinearLayout *infoLayout = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(10, 200, NONE, NONE)); leftColumn->Add(infoLayout); - if (info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO) { + if (info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO && GetUmdVideoVSHPath().empty()) { auto er = GetI18NCategory(I18NCat::ERRORS); leftColumn->Add(new NoticeView(NoticeLevel::INFO, er->T("PPSSPP doesn't support UMD Video."), "")); leftColumn->Add(new Choice(di->T("More info"), ImageID("I_LINK_OUT_QUESTION"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Add([](UI::EventParams &e) { @@ -553,10 +553,16 @@ void GameScreen::CreateSettingsViews(UI::ViewGroup *rightColumn) { rightColumnItems->SetSpacing(0.0f); rightColumn->Add(rightColumnItems); - if (!inGame_ && FileTypeIsPlayable(info_->fileType)) { + // UMD Video plays in the XMB, with the disc in the drive. + const Path vshPath = info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO ? GetUmdVideoVSHPath() : Path(); + if (!inGame_ && (FileTypeIsPlayable(info_->fileType) || !vshPath.empty())) { Choice *play = rightColumnItems->Add(new Choice(ga->T("Play"), ImageID("I_PLAY"))); - play->OnClick.Add([this](UI::EventParams &e) { - screenManager()->switchScreen(new EmuScreen(gamePath_)); + play->OnClick.Add([this, vshPath](UI::EventParams &e) { + if (vshPath.empty()) { + screenManager()->switchScreen(new EmuScreen(gamePath_)); + } else { + screenManager()->switchScreen(new EmuScreen(vshPath, gamePath_, true)); + } }); // The file is being read, and maybe about to be replaced. play->SetEnabled(!GamePacker::IsPacking(gamePath_)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 305ae3461a..0b7a3f95d4 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -58,9 +58,26 @@ #include "Core/Util/DarwinFileSystemServices.h" // For the browser #include "Core/HLE/sceUmd.h" +#include "Core/Util/PSARUnpack.h" bool MainScreen::showHomebrewTab = false; +Path GetUmdVideoVSHPath() { + InstalledFirmwareInfo info; + ReadInstalledFirmwareInfo(g_Config.nandRootDirectory, &info, false); + if (!info.hasVsh || !FirmwareVersionPlaysUmdVideo(info.version)) { + return Path(); + } + return g_Config.nandRootDirectory / "flash0/vsh/module/vshmain.prx"; +} + +static void LaunchInEmuScreen(ScreenManager *screenManager, Screen *currentScreen, EmuScreen *emuScreen) { + if (currentScreen) { + screenManager->cancelScreensAbove(currentScreen); + } + screenManager->switchScreen(emuScreen); +} + static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, const Path &path) { if (GamePacker::IsPacking(path)) { // Can't play it while it's read (and maybe replaced), so show its progress instead. @@ -87,9 +104,41 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons switch (info->fileType) { case IdentifiedFileType::PSP_UMD_VIDEO_ISO: - // We show info about it. - screenManager->push(new GameScreen(path, false)); + { + // The XMB plays it, if there's one to boot. Otherwise we show info about it. + const Path vshPath = GetUmdVideoVSHPath(); + if (vshPath.empty()) { + screenManager->push(new GameScreen(path, false)); + } else { + LaunchInEmuScreen(screenManager, currentScreen, new EmuScreen(vshPath, path, true)); + } return; + } + case IdentifiedFileType::PSP_ISO: + { + // A disc with both a game and video on it. The XMB would start the game by itself, so for + // the video, it comes up without auto-start and the disc waits in the Video column. + const Path vshPath = GetUmdVideoVSHPath(); + if (vshPath.empty()) { + break; + } + std::unique_ptr fileLoader(ConstructFileLoader(path)); + if (!fileLoader || !(DetectUmdDiscType(fileLoader.get()) & PSP_UMD_TYPE_VIDEO)) { + break; + } + auto ga = GetI18NCategory(I18NCat::GAME); + const std::vector choices = { std::string(ga->T("Play game")), std::string(ga->T("Watch video in the XMB")) }; + UI::ListPopupScreen *popup = new UI::ListPopupScreen(ga->T("This disc has both a game and video"), choices, -1, [=](int choice) { + if (choice == 0) { + LaunchInEmuScreen(screenManager, currentScreen, new EmuScreen(path)); + } else if (choice == 1) { + LaunchInEmuScreen(screenManager, currentScreen, new EmuScreen(vshPath, path, false)); + } + }); + popup->SetChoiceIcons({ { 0, ImageID("I_PLAY") }, { 1, ImageID("I_UMD_VIDEO_ISO") } }); + screenManager->push(popup); + return; + } case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY: { // Show the savedata popup, why not? @@ -114,11 +163,8 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons default: break; } - if (currentScreen) { - screenManager->cancelScreensAbove(currentScreen); - } // Otherwise let the EmuScreen take care of it, including error handling. - screenManager->switchScreen(new EmuScreen(path)); + LaunchInEmuScreen(screenManager, currentScreen, new EmuScreen(path)); } } diff --git a/UI/MainScreen.h b/UI/MainScreen.h index bf6f6dc4f3..3ed60f3094 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -136,3 +136,6 @@ private: }; void LaunchBuyGold(ScreenManager *screenManager); + +// vshmain.prx, if the installed firmware's XMB can play UMD Video discs. Empty otherwise. +Path GetUmdVideoVSHPath(); diff --git a/docs/VSHBootInvestigation.md b/docs/VSHBootInvestigation.md index 8f6f9feadd..d3d44716b1 100644 --- a/docs/VSHBootInvestigation.md +++ b/docs/VSHBootInvestigation.md @@ -54,6 +54,16 @@ Two separate things: and the Spider-Man 2 trailer on the EUR Video Demo Disc play with sound. What that player needed from our Media Engine HLE (per-picture SEI records, keyframe flags, field conversion in `sceMpegBaseCscAvc`) is in pspautotests `video/mpeg/avcdetail` and `video/mpeg/cscavc`. + + Picking a UMD Video disc in the game list boots the XMB with the disc in the drive and UMD + auto-start forced on (`CoreParameter::umdAutoboot`), so it goes straight to the disc's menu. That + needs firmware 2.71 or later: 1.50, 2.00 and 2.60 crash in `video_plugin`/paf as soon as the disc + is in, and 2.50 refuses it, claiming the system software is version "." (all measured with + Bourne Supremacy). A disc with both a game and video asks which one; for video the XMB comes up + without auto-start, since auto-start runs the game. The disc type comes from the fourth field of + `UMD_DATA.BIN` (`G`, `V`, `A`), and `PSP_GAME` without `SYSDIR/EBOOT.BIN` doesn't count: some video + discs (Spider-Man) carry a `PSP_GAME` holding only a firmware update, and reporting those as game + discs made the XMB say "The disc could not be read." - **It then asked whether the region matched and we didn't answer.** `sceIoDevctl` `0x01E18030` is the region check, and unusually it answers through the *return value* (1 matches, 0 doesn't) rather than an output buffer. It was unimplemented, so the VSH read the failure as a mismatch and