Files
ppsspp/Core/HLE/sceUmd.cpp
Henrik RydgårdandClaude Opus 5.5 b547e01c9d VSH: Boot games the XMB starts like any other game launch
A disc game or memory stick EBOOT started from the XMB used to be swapped in under the XMB's session,
so it ran without its PARAM.SFO, compat flags, game config, window title, savestates and so on.
Much of that is decided once, at boot (the memory map, the GPU backend, the shader cache), so
sceKernelLoadExec now asks the frontend for a reboot into the disc image or EBOOT (PSP_RequestBoot)
and stops. The app and headless boot it like a game from the game list.

The game keeps the XMB's launch args (CoreParameter::launchedFromVSH), so exiting boots the XMB
again, with the disc that's in the drive and those args, and the XMB comes back where it was without
auto-starting the disc. Homebrew keeps the disc that was in the drive. A disc whose executables are
all zeroes (the EUR Video Demo Disc's "game") still loads in place and fails back to the XMB.

In headless, the run carries on through the reboot, starting stopped under the debugger like the
first boot, since the reboot loses a pending cpu.runUntilTime.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 13:36:28 +02:00

670 lines
21 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <vector>
#include "Common/System/System.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/Serialize/SerializeMap.h"
#include "Core/Loaders.h"
#include "Core/MemMap.h"
#include "Core/System.h"
#include "Core/CoreTiming.h"
#include "Core/Reporting.h"
#include "Core/MIPS/MIPS.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceUmd.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/KernelWaitHelpers.h"
#include "Core/RetroAchievements.h"
#include "Core/FileSystems/BlockDevices.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/FileSystems/ISOFileSystem.h"
#include "Core/FileSystems/VirtualDiscFileSystem.h"
static constexpr u64 MICRO_DELAY_ACTIVATE = 4000;
// Does not include PSP_UMD_CHANGED.
static constexpr uint32_t UMD_STAT_ALLOW_WAIT = PSP_UMD_NOT_PRESENT | PSP_UMD_PRESENT | PSP_UMD_NOT_READY | PSP_UMD_READY | PSP_UMD_READABLE;
static bool umdActivated = true;
static u32 umdStatus = 0;
static u32 umdErrorStat = 0;
static int driveCBId = 0;
static int umdStatTimeoutEvent = -1;
static int umdStatChangeEvent = -1;
static int umdInsertChangeEvent = -1;
static std::vector<SceUID> umdWaitingThreads;
static std::map<SceUID, u64> umdPausedWaits;
bool g_UMDReplacePermit = false;
bool UMDInserted = true;
// The image a disc swap or insert put in the drive. Empty if it's still the one we booted with.
static Path g_umdPath;
struct PspUmdInfo {
u32_le size;
u32_le type;
};
static void __UmdStatTimeout(u64 userdata, int cyclesLate);
static void __UmdStatChange(u64 userdata, int cyclesLate);
static void __UmdInsertChange(u64 userdata, int cyclesLate);
static void __UmdBeginCallback(SceUID threadID, SceUID prevCallbackId);
static void __UmdEndCallback(SceUID threadID, SceUID prevCallbackId);
void __UmdInit()
{
umdStatTimeoutEvent = CoreTiming::RegisterEvent("UmdTimeout", __UmdStatTimeout);
umdStatChangeEvent = CoreTiming::RegisterEvent("UmdChange", __UmdStatChange);
umdInsertChangeEvent = CoreTiming::RegisterEvent("UmdInsertChange", __UmdInsertChange);
umdActivated = true;
umdStatus = 0;
umdErrorStat = 0;
driveCBId = 0;
umdWaitingThreads.clear();
umdPausedWaits.clear();
g_UMDReplacePermit = false;
g_umdPath.clear();
__KernelRegisterWaitTypeFuncs(WAITTYPE_UMD, __UmdBeginCallback, __UmdEndCallback);
}
void __UmdDoState(PointerWrap &p)
{
auto s = p.Section("sceUmd", 1, 3);
if (!s)
return;
u8 activatedByte = umdActivated ? 1 : 0;
Do(p, activatedByte);
umdActivated = activatedByte != 0;
Do(p, umdStatus);
Do(p, umdErrorStat);
Do(p, driveCBId);
Do(p, umdStatTimeoutEvent);
CoreTiming::RestoreRegisterEvent(umdStatTimeoutEvent, "UmdTimeout", __UmdStatTimeout);
Do(p, umdStatChangeEvent);
CoreTiming::RestoreRegisterEvent(umdStatChangeEvent, "UmdChange", __UmdStatChange);
Do(p, umdWaitingThreads);
Do(p, umdPausedWaits);
if (s > 1) {
Do(p, g_UMDReplacePermit);
if (g_UMDReplacePermit && p.mode == p.MODE_READ) {
System_Notify(SystemNotification::UI);
}
} else if (p.mode == p.MODE_READ) {
g_UMDReplacePermit = false;
}
if (s > 2) {
Do(p, umdInsertChangeEvent);
Do(p, UMDInserted);
} else {
umdInsertChangeEvent = -1;
UMDInserted = true;
}
CoreTiming::RestoreRegisterEvent(umdInsertChangeEvent, "UmdInsertChange", __UmdInsertChange);
}
static u8 __KernelUmdGetState() {
if (!UMDInserted) {
return PSP_UMD_NOT_PRESENT;
}
// Most games seem to expect the disc to be ready early on, active or not.
// It seems like the PSP sets this state when the disc is "ready".
u8 state = PSP_UMD_PRESENT | PSP_UMD_READY;
if (umdActivated) {
state |= PSP_UMD_READABLE;
}
return state;
}
static void UmdWakeThreads() {
// Wake anyone waiting on this.
for (size_t i = 0; i < umdWaitingThreads.size(); ++i) {
const SceUID threadID = umdWaitingThreads[i];
u32 error;
u32 stat = __KernelGetWaitValue(threadID, error);
bool keep = false;
if (HLEKernel::VerifyWait(threadID, WAITTYPE_UMD, 1)) {
// Only if they are still waiting do we keep them in the list.
keep = (stat & __KernelUmdGetState()) == 0;
if (!keep) {
__KernelResumeThreadFromWait(threadID, 0);
}
}
if (!keep) {
umdWaitingThreads.erase(umdWaitingThreads.begin() + i--);
}
}
}
static void __UmdStatChange(u64 userdata, int cyclesLate) {
umdActivated = userdata != 0;
UmdWakeThreads();
}
// Callback notifications don't queue: a second one before the callback runs replaces the first's
// argument. So a disc that goes in before the shell has run the callback for the one that came out
// (both done from the pause menu) would look to it like nothing changed.
static bool DriveCallbackPending() {
u32 error;
const PSPCallback *cb = driveCBId != 0 ? kernelObjects.Get<PSPCallback>(driveCBId, error) : nullptr;
return cb && cb->nc.notifyCount != 0;
}
// userdata 1: an insert waiting for the drive callback to see the drive empty, which then gets
// the callback itself. 0: the second half of a disc swap.
static void __UmdInsertChange(u64 userdata, int cyclesLate) {
if (userdata == 1 && DriveCallbackPending()) {
CoreTiming::ScheduleEvent(usToCycles(100 * 1000), umdInsertChangeEvent, 1);
return;
}
UMDInserted = true;
UmdWakeThreads();
if (userdata == 1 && driveCBId != 0) {
__KernelNotifyCallback(driveCBId, PSP_UMD_PRESENT | PSP_UMD_READABLE | PSP_UMD_CHANGED);
}
}
static void __KernelUmdActivate()
{
u32 notifyArg = PSP_UMD_PRESENT | PSP_UMD_READABLE;
// PSP_UMD_READY will be returned when sceKernelGetCompiledSdkVersion() != 0
if (sceKernelGetCompiledSdkVersion() != 0) {
notifyArg |= PSP_UMD_READY;
}
// An empty drive (the XMB's) says so: the shell otherwise asks for the disc and reports it unreadable.
if (!UMDInserted) {
notifyArg = PSP_UMD_NOT_PRESENT;
}
if (driveCBId != 0)
__KernelNotifyCallback(driveCBId, notifyArg);
// Don't activate immediately, take time to "spin up."
CoreTiming::RemoveEvent(umdStatChangeEvent);
CoreTiming::ScheduleEvent(usToCycles(MICRO_DELAY_ACTIVATE), umdStatChangeEvent, 1);
}
static void __KernelUmdDeactivate()
{
u32 notifyArg = UMDInserted ? PSP_UMD_PRESENT | PSP_UMD_READY : PSP_UMD_NOT_PRESENT;
if (driveCBId != 0)
__KernelNotifyCallback(driveCBId, notifyArg);
CoreTiming::RemoveEvent(umdStatChangeEvent);
__UmdStatChange(0, 0);
}
static void __UmdBeginCallback(SceUID threadID, SceUID prevCallbackId)
{
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
if (HLEKernel::VerifyWait(threadID, WAITTYPE_UMD, 1))
{
// Shouldn't happen: each nesting level pauses under its own key, and on hardware a callback can
// nest only one level (a CB wait that would go deeper never returns.)
if (umdPausedWaits.find(pauseKey) != umdPausedWaits.end())
return;
_dbg_assert_msg_(umdStatTimeoutEvent != -1, "Must have a umd timer");
s64 cyclesLeft = CoreTiming::UnscheduleEvent(umdStatTimeoutEvent, threadID);
if (cyclesLeft != 0)
umdPausedWaits[pauseKey] = CoreTiming::GetTicks(currentMIPS) + cyclesLeft;
else
umdPausedWaits[pauseKey] = 0;
HLEKernel::RemoveWaitingThread(umdWaitingThreads, threadID);
DEBUG_LOG(Log::sceIo, "sceUmdWaitDriveStatCB: Suspending lock wait for callback");
}
else
WARN_LOG_REPORT(Log::sceIo, "sceUmdWaitDriveStatCB: beginning callback with bad wait id?");
}
static void __UmdEndCallback(SceUID threadID, SceUID prevCallbackId)
{
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
u32 error;
u32 stat = __KernelGetWaitValue(threadID, error);
if (umdPausedWaits.find(pauseKey) == umdPausedWaits.end())
{
WARN_LOG_REPORT(Log::sceIo, "__UmdEndCallback(): UMD paused wait missing");
__KernelResumeThreadFromWait(threadID, 0);
return;
}
u64 waitDeadline = umdPausedWaits[pauseKey];
umdPausedWaits.erase(pauseKey);
// TODO: Don't wake up if __KernelCurHasReadyCallbacks()?
if ((stat & __KernelUmdGetState()) != 0)
{
__KernelResumeThreadFromWait(threadID, 0);
return;
}
s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks(currentMIPS);
if (cyclesLeft < 0 && waitDeadline != 0)
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
else
{
_dbg_assert_msg_(umdStatTimeoutEvent != -1, "Must have a umd timer");
// A deadline of 0 means the wait has no timeout.
if (waitDeadline != 0)
CoreTiming::ScheduleEvent(cyclesLeft, umdStatTimeoutEvent, __KernelGetCurThread());
umdWaitingThreads.push_back(threadID);
DEBUG_LOG(Log::sceIo, "sceUmdWaitDriveStatCB: Resuming lock wait for callback");
}
}
static int sceUmdCheckMedium() {
int retVal = 0;
if (UMDInserted) {
retVal = 1; //non-zero: disc in drive
}
return hleLogDebug(Log::sceKernel, retVal);
}
// A user mode caller can't pass a kernel address. (mediaman.prx checks these with k1.)
static bool IsUserAddress(u32 addr) {
return (addr & 0x80000000) == 0;
}
static u32 sceUmdGetDiscInfo(u32 infoAddr) {
DEBUG_LOG(Log::sceIo, "sceUmdGetDiscInfo(%08x)", infoAddr);
if (Memory::IsValidRange(infoAddr, 8) && IsUserAddress(infoAddr) && IsUserAddress(infoAddr + 4)) {
auto info = PSPPointer<PspUmdInfo>::Create(infoAddr);
if (info->size != 8)
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT);
// Games see a game disc, whatever else is on it.
const u32 type = __UmdDiscType();
info->type = (type & PSP_UMD_TYPE_GAME) || type == 0 ? PSP_UMD_TYPE_GAME : type;
return hleLogDebug(Log::sceIo, 0);
} else {
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT);
}
}
static int sceUmdActivate(u32 mode, u32 namePtr) {
if (mode < 1 || mode > 2)
return hleLogWarning(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT);
// The firmware compares the name before checking the pointer, so a bad one crashes there.
const char *name = namePtr != 0 && Memory::IsValidAddress(namePtr) ? Memory::GetCharPointer(namePtr) : nullptr;
if (!name || strncmp(name, "disc0:", 7) != 0 || !IsUserAddress(namePtr))
return hleLogWarning(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad name");
__KernelUmdActivate();
if (mode != 1) {
return hleLogError(Log::sceIo, 0, "UNTESTED");
}
return hleLogDebug(Log::sceIo, 0);
}
static int sceUmdDeactivate(u32 mode, u32 namePtr)
{
// Why 18? No idea.
if (mode > 18)
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT);
// Unlike sceUmdActivate(), the name isn't compared, and only mode 2 requires one.
if ((mode == 2 && namePtr == 0) || !IsUserAddress(namePtr))
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad name");
__KernelUmdDeactivate();
if (mode == 1) {
return hleLogDebug(Log::sceIo, 0);
} else {
return hleLogError(Log::sceIo, 0, "UNTESTED mode != 1");
}
}
static u32 sceUmdRegisterUMDCallBack(u32 cbId)
{
int retVal = 0;
if (!kernelObjects.Is<PSPCallback>(cbId)) {
retVal = SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
} else {
// There's only ever one.
driveCBId = cbId;
}
return hleLogDebug(Log::sceIo, retVal);
}
static int sceUmdUnRegisterUMDCallBack(int cbId)
{
if (cbId != driveCBId) {
return hleLogError(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT);
} else {
int retVal;
if (sceKernelGetCompiledSdkVersion() > 0x3000000) {
retVal = 0;
} else {
retVal = cbId;
}
driveCBId = 0;
return hleLogDebug(Log::sceIo, retVal);
}
}
static u32 sceUmdGetDriveStat() {
if (!UMDInserted) {
return hleLogWarning(Log::sceIo, PSP_UMD_NOT_PRESENT, "sceUmdGetDriveStat: UMD is taking out for switch UMD");
}
//u32 retVal = PSP_UMD_INITED | PSP_UMD_READY | PSP_UMD_PRESENT;
u32 retVal = __KernelUmdGetState();
// This one can be very spammy.
return hleLogVerbose(Log::sceIo, retVal, "0x%02x=sceUmdGetDriveStat()", retVal);
}
static void __UmdStatTimeout(u64 userdata, int cyclesLate)
{
SceUID threadID = (SceUID)userdata;
u32 error;
SceUID waitID = __KernelGetWaitID(threadID, WAITTYPE_UMD, error);
// Assuming it's still waiting.
if (waitID == 1)
__KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT);
HLEKernel::RemoveWaitingThread(umdWaitingThreads, threadID);
}
// The firmware waits on an event flag holding the drive state (mediaman.prx), and passes no
// timeout at all for 0, so that waits forever.
static void __UmdWaitStat(u32 timeout, bool callbacks)
{
if (timeout == 0)
return;
// Measured on hardware. Oddly, the CB version doesn't have the shortest step.
if (timeout <= 1 && !callbacks)
timeout = 25;
else if (timeout <= 209)
timeout = 240;
CoreTiming::ScheduleEvent(usToCycles((int) timeout), umdStatTimeoutEvent, __KernelGetCurThread());
}
/**
* Wait for a drive to reach a certain state
*
* @param stat - The drive stat to wait for.
* @return < 0 on error
*
*/
static int sceUmdWaitDriveStat(u32 stat) {
if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
}
if (!__KernelIsDispatchEnabled()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
}
if (__IsInInterrupt()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
}
hleEatCycles(520);
if ((stat & __KernelUmdGetState()) == 0) {
DEBUG_LOG(Log::sceIo, "sceUmdWaitDriveStat(stat = %08x): waiting", stat);
umdWaitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, 0, "umd stat waited");
return hleLogDebug(Log::sceIo, 0);
}
return hleLogDebug(Log::sceIo, 0);
}
static int sceUmdWaitDriveStatWithTimer(u32 stat, u32 timeout) {
if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
}
if (!__KernelIsDispatchEnabled()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
}
if (__IsInInterrupt()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
}
hleEatCycles(520);
if ((stat & __KernelUmdGetState()) == 0) {
__UmdWaitStat(timeout, false);
umdWaitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, false, "umd stat waited with timer");
return hleLogDebug(Log::sceIo, 0, "waiting");
} else {
hleReSchedule("umd stat checked");
}
return hleLogDebug(Log::sceIo, 0);
}
static int sceUmdWaitDriveStatCB(u32 stat, u32 timeout) {
if ((stat & UMD_STAT_ALLOW_WAIT) == 0) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT, "bad status");
}
if (!__KernelIsDispatchEnabled()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");
}
if (__IsInInterrupt()) {
return hleLogDebug(Log::sceIo, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "inside interrupt");
}
hleEatCycles(520);
hleCheckCurrentCallbacks();
if ((stat & __KernelUmdGetState()) == 0) {
__UmdWaitStat(timeout, true);
umdWaitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_UMD, 1, stat, 0, true, "umd stat waited");
return hleLogDebug(Log::sceIo, 0, "waiting");
} else {
hleReSchedule("umd stat waited");
}
return hleLogDebug(Log::sceIo, 0);
}
static u32 sceUmdCancelWaitDriveStat() {
for (size_t i = 0; i < umdWaitingThreads.size(); ++i) {
const SceUID threadID = umdWaitingThreads[i];
CoreTiming::UnscheduleEvent(umdStatTimeoutEvent, threadID);
HLEKernel::ResumeFromWait(threadID, WAITTYPE_UMD, 1, (int)SCE_KERNEL_ERROR_WAIT_CANCEL);
}
umdWaitingThreads.clear();
return hleLogDebug(Log::sceIo, 0);
}
static u32 sceUmdGetErrorStat() {
return hleLogDebug(Log::sceIo, umdErrorStat);
}
void __UmdReplace(const Path &filepath) {
std::string error = "";
FileLoader *fileLoader;
if (!UmdReplace(filepath, &fileLoader, error)) {
ERROR_LOG(Log::sceIo, "UMD Replace failed: %s", error.c_str());
return;
}
Achievements::ChangeUMD(filepath, fileLoader);
g_umdPath = filepath;
UMDInserted = false;
// Wake any threads waiting for the disc to be removed.
UmdWakeThreads();
CoreTiming::ScheduleEvent(usToCycles(200*1000), umdInsertChangeEvent, 0); // Wait sceUmdCheckMedium call
// TODO Is this always correct if UMD was not activated?
u32 notifyArg = PSP_UMD_PRESENT | PSP_UMD_READABLE | PSP_UMD_CHANGED;
if (driveCBId != 0)
__KernelNotifyCallback(driveCBId, notifyArg);
}
bool __UmdInsert(const Path &filepath, std::string *error) {
if (__UmdIsInserted()) {
*error = "a disc is already in the drive";
return false;
}
FileLoader *fileLoader;
if (!UmdMount(filepath, &fileLoader, *error)) {
return false;
}
Achievements::ChangeUMD(filepath, fileLoader);
g_umdPath = filepath;
CoreTiming::UnscheduleEvent(umdInsertChangeEvent, 1);
if (DriveCallbackPending()) {
CoreTiming::ScheduleEvent(usToCycles(100 * 1000), umdInsertChangeEvent, 1);
return true;
}
UMDInserted = true;
UmdWakeThreads();
if (driveCBId != 0)
__KernelNotifyCallback(driveCBId, PSP_UMD_PRESENT | PSP_UMD_READABLE | PSP_UMD_CHANGED);
return true;
}
void __UmdEject() {
// A disc still waiting to go in comes back out without the shell ever hearing of it.
const bool insertPending = CoreTiming::UnscheduleEvent(umdInsertChangeEvent, 1) != 0;
if (!UMDInserted && !insertPending) {
return;
}
pspFileSystem.Unmount("umd0:");
pspFileSystem.Unmount("umd1:");
pspFileSystem.Unmount("umd:");
pspFileSystem.Unmount("disc0:");
if (!UMDInserted) {
return;
}
UMDInserted = false;
UmdWakeThreads();
if (driveCBId != 0)
__KernelNotifyCallback(driveCBId, PSP_UMD_NOT_PRESENT | PSP_UMD_CHANGED);
}
Path __UmdGetPath() {
if (!UMDInserted) {
return Path();
}
if (!g_umdPath.empty()) {
return g_umdPath;
}
const CoreParameter &coreParam = PSP_CoreParameter();
if (!coreParam.mountIso.empty()) {
return coreParam.mountIso;
}
switch (coreParam.fileType) {
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
case IdentifiedFileType::PSP_DISC_DIRECTORY:
return coreParam.fileToStart;
default:
// A homebrew or PS1 EBOOT, with no disc behind disc0:.
return Path();
}
}
bool __UmdIsInserted() {
// Counting a disc that's on its way in, which can't finish while emulation is paused.
return UMDInserted || CoreTiming::IsScheduled(umdInsertChangeEvent);
}
u32 __UmdDiscType() {
if (!UMDInserted) {
return 0;
}
const u32 type = DetectUmdDiscType(&pspFileSystem, "disc0:");
// A loose game directory or an ISO without the usual layout still boots as a game.
return type != 0 ? type : PSP_UMD_TYPE_GAME;
}
bool getUMDReplacePermit() {
return g_UMDReplacePermit;
}
static u32 sceUmdReplaceProhibit() {
if (g_UMDReplacePermit) {
g_UMDReplacePermit = false;
System_Notify(SystemNotification::SWITCH_UMD_UPDATED);
return hleLogDebug(Log::sceIo, 0, "switched state to prohibited");
} else {
return hleLogDebug(Log::sceIo, 0);
}
}
static u32 sceUmdReplacePermit() {
if (!g_UMDReplacePermit) {
//INFO_LOG(Log::sceIo, "sceUmdReplacePermit() - permitted");
g_UMDReplacePermit = true;
System_Notify(SystemNotification::SWITCH_UMD_UPDATED);
return hleLogDebug(Log::sceIo, 0, "switched state to permitted");
} else {
return hleLogDebug(Log::sceIo, 0);
}
}
const HLEFunction sceUmdUser[] =
{
{0XC6183D47, &WrapI_UU<sceUmdActivate>, "sceUmdActivate", 'i', "is"},
{0X6B4A146C, &WrapU_V<sceUmdGetDriveStat>, "sceUmdGetDriveStat", 'x', "" },
{0X46EBB729, &WrapI_V<sceUmdCheckMedium>, "sceUmdCheckMedium", 'i', "" },
{0XE83742BA, &WrapI_UU<sceUmdDeactivate>, "sceUmdDeactivate", 'i', "xs"},
{0X8EF08FCE, &WrapI_U<sceUmdWaitDriveStat>, "sceUmdWaitDriveStat", 'i', "x" },
{0X56202973, &WrapI_UU<sceUmdWaitDriveStatWithTimer>, "sceUmdWaitDriveStatWithTimer", 'i', "xx"},
{0X4A9E5E29, &WrapI_UU<sceUmdWaitDriveStatCB>, "sceUmdWaitDriveStatCB", 'i', "xx"},
{0X6AF9B50A, &WrapU_V<sceUmdCancelWaitDriveStat>, "sceUmdCancelWaitDriveStat", 'x', "" },
{0X20628E6F, &WrapU_V<sceUmdGetErrorStat>, "sceUmdGetErrorStat", 'x', "" },
{0X340B7686, &WrapU_U<sceUmdGetDiscInfo>, "sceUmdGetDiscInfo", 'x', "x" },
{0XAEE7404D, &WrapU_U<sceUmdRegisterUMDCallBack>, "sceUmdRegisterUMDCallBack", 'x', "x" },
{0XBD2BDE07, &WrapI_I<sceUmdUnRegisterUMDCallBack>, "sceUmdUnRegisterUMDCallBack", 'i', "i" },
{0X87533940, &WrapU_V<sceUmdReplaceProhibit>, "sceUmdReplaceProhibit", 'x', "" },
{0XCBE9F02A, &WrapU_V<sceUmdReplacePermit>, "sceUmdReplacePermit", 'x', "" },
{0X14C6C45C, nullptr, "sceUmdUnuseUMDInMsUsbWlan", '?', "" },
{0XB103FA38, nullptr, "sceUmdUseUMDInMsUsbWlan", '?', "" },
{0X1A2485D2, nullptr, "sceUmdUseUMDDetectIntr", '?', "" },
{0X3BA4EC53, nullptr, "sceUmdUnuseUMDDetectIntr", '?', "" },
};
void Register_sceUmdUser()
{
RegisterHLEModule("sceUmdUser", ARRAY_SIZE(sceUmdUser), sceUmdUser);
}