diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index c580468901..411d8e20df 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -542,6 +542,10 @@ u32 KernelFindModuleImport(const char *moduleName, const char *libraryName, u32 // than a game), and reset on the next __KernelLoadExec. See ShouldHLEModuleForLoad below. static bool g_runningVSH = false; +bool __KernelIsRunningVSH() { + return g_runningVSH; +} + // A sceKernelLoadModule whose caller is waiting for the file to be read. The module is only put // in memory when the wait ends - see sceKernelLoadModule. struct PendingModuleLoad { diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index c50c92ca6e..0490a1aa70 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -242,6 +242,8 @@ u32 __KernelGetModuleGP(SceUID module); bool KernelModuleIsKernelMode(SceUID module); bool __KernelLoadGEDump(MIPSState *mips, std::string_view base_filename, std::string *error_string); bool __KernelLoadExec(MIPSState *mips, const char *filename, u32 paramPtr, std::string *error_string); +// The VSH (the XMB) is what's running, not a game it launched. +bool __KernelIsRunningVSH(); // Exposed so sceVshBridge can reuse it directly for vshKernelLoadModuleBufferVSH, matching JPCSP. SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr); bool __KernelLoadExecFromBuffer(MIPSState *mips, const u8 *data, size_t size, u32 paramPtr, std::string *error_string); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index b8ce7dba2b..305ae3461a 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -906,7 +906,7 @@ void UmdReplaceScreen::CreateViews() { rightColumnItems->Add(new Choice(mm->T("Load", "Load...")))->OnClick.Add([&](UI::EventParams &e) { auto mm = GetI18NCategory(I18NCat::MAINMENU); System_BrowseForFile(GetRequesterToken(), mm->T("Load"), BrowseFileType::BOOTABLE, [this](std::string_view value, int) { - __UmdReplace(Path(value)); + UseDisc(Path(value)); TriggerFinish(DR_OK); }); }); @@ -933,10 +933,21 @@ void UmdReplaceScreen::update() { } void UmdReplaceScreen::OnGameSelected(UI::EventParams &e) { - __UmdReplace(Path(e.s)); + UseDisc(Path(e.s)); TriggerFinish(DR_OK); } +void UmdReplaceScreen::UseDisc(const Path &path) { + if (!insert_) { + __UmdReplace(path); + return; + } + std::string error; + if (!__UmdInsert(path, &error)) { + g_OSD.Show(OSDType::MESSAGE_ERROR, error); + } +} + void UmdReplaceScreen::OnGameSettings(UI::EventParams &e) { screenManager()->push(new GameSettingsScreen(Path())); } diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 688edbd029..bf6f6dc4f3 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -103,6 +103,8 @@ private: class UmdReplaceScreen : public UIBaseDialogScreen { public: + // insert: put a disc into the empty drive (the VSH) rather than swap the one a game is running from. + explicit UmdReplaceScreen(bool insert = false) : insert_(insert) {} const char *tag() const override { return "UmdReplace"; } protected: @@ -112,6 +114,9 @@ protected: private: void OnGameSelected(UI::EventParams &e); void OnGameSettings(UI::EventParams &e); + void UseDisc(const Path &path); + + bool insert_; }; class GridSettingsPopupScreen : public UI::PopupScreen { diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index f5d64c3248..fc1c75d7d5 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -46,6 +46,7 @@ #include "Core/RetroAchievements.h" #include "Core/ELF/ParamSFO.h" #include "Core/HLE/sceDisplay.h" +#include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceUmd.h" #include "Core/HLE/sceNet.h" #include "Core/HLE/sceNetInet.h" @@ -712,7 +713,19 @@ void GamePauseScreen::CreateViews() { buttonColumn->Add(rightColumnItems); rightColumnItems->SetSpacing(0.0f); - if (getUMDReplacePermit()) { + if (__KernelIsRunningVSH()) { + // The XMB notices discs coming and going by itself, like a PSP's. + if (__UmdIsInserted()) { + rightColumnItems->Add(new Choice(pa->T("Eject UMD"), ImageID("I_UMD")))->OnClick.Add([this](UI::EventParams &) { + __UmdEject(); + RecreateViews(); + }); + } else { + rightColumnItems->Add(new Choice(pa->T("Insert UMD"), ImageID("I_UMD")))->OnClick.Add([this](UI::EventParams &) { + screenManager()->push(new UmdReplaceScreen(true)); + }); + } + } else if (getUMDReplacePermit()) { rightColumnItems->Add(new Choice(pa->T("Switch UMD"), ImageID("I_UMD")))->OnClick.Add([this](UI::EventParams &) { screenManager()->push(new UmdReplaceScreen()); }); diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 52ce8d0e65..ee3673dbf6 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -1075,8 +1075,10 @@ Cheats = Cheats Continue = Continue Create Game Config = Create game config Delete Game Config = Delete game config +Eject UMD = Eject UMD Exit to menu = Exit to menu Game Settings = Game settings +Insert UMD = Insert UMD Load State = Load state Rewind = Rewind Save State = Save state