VSH: An empty UMD drive, and inserting a disc over the WebSocket debugger

The XMB, booted without a game, now starts with an empty drive: when
vsh_module loads with nothing at disc0:, the drive is marked empty, and
activating it tells the shell's drive callback (SceVshMediaDetectUMD)
there's no disc. Activation used to report a readable disc regardless,
so the shell asked for its type and showed "The disc could not be read."
The disc type devctl (0x01F20001) answers 0 without a disc, as JPCSP
does.

game.umd.insert (path) mounts a disc image into the empty drive and
calls the drive callback as inserting one does; game.umd.eject takes it
out. UmdMount() is UmdReplace() without its need for a disc already in.

Inserted into the 6.61 XMB, Daxter shows in the Game column with its
icon, background and animated preview, and launching it boots the game
to its title menu.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-21 17:32:43 +02:00
1 parent b225c33639
commit eee153b220
10 files changed
+124 -10

No files matched your search

@@ -40,6 +40,7 @@
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/HLE/sceDisplay.h"
#include "Core/HLE/sceUmd.h"
#include "Core/RetroAchievements.h"
#include "Core/SaveState.h"
#include "Core/System.h"
@@ -245,6 +246,8 @@ DebuggerSubscriber *WebSocketGameInit(DebuggerEventHandlerMap &map) {
map["game.state.verify"] = [p](DebuggerRequest &req) { p->StateOperation(req, StateOp::VERIFY); };
map["game.speed.get"] = &WebSocketGameSpeedGet;
map["game.speed.set"] = &WebSocketGameSpeedSet;
map["game.umd.insert"] = &WebSocketGameUmdInsert;
map["game.umd.eject"] = &WebSocketGameUmdEject;
map["version"] = &WebSocketVersion;
return p;
@@ -468,3 +471,52 @@ void WebSocketVersion(DebuggerRequest &req) {
else
json.writeString("path", path);
}
// Put a disc in the drive (game.umd.insert)
//
// For the XMB, booted with --vsh and no game, whose drive starts out empty. The shell gets the
// drive's callback, as on a PSP. To swap discs in a running game, use the Switch UMD menu.
//
// Parameters:
// - path: string, the disc image (ISO, CSO, ...) or extracted disc directory to insert.
//
// Response (same event name) with no extra data, or error if a disc is already in the drive or the
// image can't be mounted.
void WebSocketGameUmdInsert(DebuggerRequest &req) {
std::string path;
if (!req.ParamString("path", &path))
return;
bool running = false;
bool inserted = false;
std::string error;
Core_RunOnCPUThread([&] {
running = PSP_GetBootState() == BootState::Complete;
if (running)
inserted = __UmdInsert(Path(path), &error);
});
if (!running)
return req.Fail("Game not running");
if (!inserted)
return req.Fail("Couldn't insert the disc: " + error);
req.Respond();
}
// Take the disc out of the drive (game.umd.eject)
//
// No parameters.
//
// Response (same event name) with no extra data. Ejecting an empty drive does nothing.
void WebSocketGameUmdEject(DebuggerRequest &req) {
bool running = false;
Core_RunOnCPUThread([&] {
running = PSP_GetBootState() == BootState::Complete;
if (running)
__UmdEject();
});
if (!running)
return req.Fail("Game not running");
req.Respond();
}