/* This file is part of jpcsp. Jpcsp 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, either version 3 of the License, or (at your option) any later version. Jpcsp 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 for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.modules; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CANCELLED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_STATUS_RELEASED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_TIMEOUT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_UMD_NOT_READY; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_UMD; import jpcsp.Emulator; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.PspString; import jpcsp.HLE.TPointer; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import jpcsp.HLE.Modules; import jpcsp.HLE.kernel.types.IAction; import jpcsp.HLE.kernel.types.IWaitStateChecker; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.kernel.types.pspUmdInfo; import jpcsp.filesystems.umdiso.UmdIsoReader; import jpcsp.scheduler.Scheduler; import org.apache.log4j.Logger; public class sceUmdUser extends HLEModule { public static Logger log = Modules.getLogger("sceUmdUser"); private boolean umdAllowReplace; @Override public void start() { // Remember if the UMD was activated even after a call to sceKernelLoadExec() setUmdActivated(); umdDeactivateCalled = false; waitingThreads = new LinkedList<SceKernelThreadInfo>(); umdErrorStat = 0; umdWaitStateChecker = new UmdWaitStateChecker(); setUmdAllowReplace(false); super.start(); } protected class UmdWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { if (checkDriveStat(wait.wantedUmdStat)) { waitingThreads.remove(thread); // Return success thread.cpuContext._v0 = 0; // Do not continue the wait state return false; } return true; } } private static class DelayedUmdSwitch implements IAction { private UmdIsoReader iso; public DelayedUmdSwitch(UmdIsoReader iso) { this.iso = iso; } @Override public void execute() { Modules.sceUmdUserModule.hleDelayedUmdSwitch(iso); } } private static class DelayedUmdRemoved implements IAction { @Override public void execute() { Modules.sceUmdUserModule.hleDelayedUmdSwitch(null); } } protected static final int PSP_UMD_INIT = 0x00; protected static final int PSP_UMD_NOT_PRESENT = 0x01; protected static final int PSP_UMD_PRESENT = 0x02; protected static final int PSP_UMD_CHANGED = 0x04; protected static final int PSP_UMD_NOT_READY = 0x08; protected static final int PSP_UMD_READY = 0x10; protected static final int PSP_UMD_READABLE = 0x20; protected UmdIsoReader iso; protected boolean umdActivated; protected boolean umdDeactivateCalled; protected List<SceKernelThreadInfo> waitingThreads; protected int umdErrorStat; protected UmdWaitStateChecker umdWaitStateChecker; public void setIsoReader(UmdIsoReader iso) { this.iso = iso; setUmdActivated(); } public void setUmdErrorStat(int stat) { umdErrorStat = stat; } public int getUmdErrorStat() { return umdErrorStat; } private void setUmdActivated() { if (iso == null) { umdActivated = false; } else { umdActivated = true; } Modules.IoFileMgrForUserModule.registerUmdIso(); } public boolean isUmdActivated() { return umdActivated; } public int getUmdStat() { int stat; if (iso != null) { stat = PSP_UMD_PRESENT | PSP_UMD_READY; if (umdActivated) { stat |= PSP_UMD_READABLE; } } else { stat = PSP_UMD_NOT_PRESENT; if (umdDeactivateCalled) { stat |= PSP_UMD_NOT_READY; } } return stat; } protected boolean checkDriveStat(int wantedStat) { int currentStat = getUmdStat(); return ((currentStat & wantedStat) != 0); } protected void removeWaitingThread(SceKernelThreadInfo thread) { for (ListIterator<SceKernelThreadInfo> lit = waitingThreads.listIterator(); lit.hasNext();) { SceKernelThreadInfo waitingThread = lit.next(); if (waitingThread.uid == thread.uid) { lit.remove(); break; } } } public void onThreadWaitTimeout(SceKernelThreadInfo thread) { log.info("UMD stat timedout"); removeWaitingThread(thread); // Return WAIT_TIMEOUT thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT; } public void onThreadWaitReleased(SceKernelThreadInfo thread) { log.info("UMD stat released"); removeWaitingThread(thread); // Return ERROR_WAIT_STATUS_RELEASED thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED; } public void onThreadDeleted(SceKernelThreadInfo thread) { if (thread.waitType == JPCSP_WAIT_UMD) { removeWaitingThread(thread); } } protected void checkWaitingThreads() { for (ListIterator<SceKernelThreadInfo> lit = waitingThreads.listIterator(); lit.hasNext();) { SceKernelThreadInfo waitingThread = lit.next(); if (waitingThread.status == SceKernelThreadInfo.PSP_THREAD_WAITING) { int wantedUmdStat = waitingThread.wait.wantedUmdStat; if (waitingThread.waitType == JPCSP_WAIT_UMD && checkDriveStat(wantedUmdStat)) { if (log.isDebugEnabled()) { log.debug("sceUmdUser - checkWaitingThreads waking " + Integer.toHexString(waitingThread.uid) + " thread:'" + waitingThread.name + "'"); } lit.remove(); // Return success waitingThread.cpuContext._v0 = 0; // Wakeup thread Modules.ThreadManForUserModule.hleChangeThreadState(waitingThread, SceKernelThreadInfo.PSP_THREAD_READY); } } } } protected int hleUmdWaitDriveStat(int wantedStat, boolean doCallbacks, boolean doTimeout, int timeout) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; if (!checkDriveStat(wantedStat)) { SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); // Wait on a specific umdStat. currentThread.wait.wantedUmdStat = wantedStat; waitingThreads.add(currentThread); threadMan.hleKernelThreadEnterWaitState(currentThread, JPCSP_WAIT_UMD, -1, umdWaitStateChecker, timeout, !doTimeout, doCallbacks); } threadMan.hleRescheduleCurrentThread(doCallbacks); return 0; } protected int getNotificationArg() { return getNotificationArg(iso != null); } protected int getNotificationArg(boolean umdPresent) { int notifyArg; if (umdPresent) { notifyArg = PSP_UMD_PRESENT | PSP_UMD_READABLE; // The PSP is returning 0x32 instead of 0x22 when // sceKernelSetCompiledSdkVersion() // has been called (i.e. when sceKernelGetCompiledSdkVersion() != 0). if (Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() != 0) { notifyArg |= PSP_UMD_READY; } } else { notifyArg = PSP_UMD_NOT_PRESENT | PSP_UMD_NOT_READY; } return notifyArg; } public boolean isUmdAllowReplace() { return umdAllowReplace; } private void setUmdAllowReplace(boolean umdAllowReplace) { this.umdAllowReplace = umdAllowReplace; // Update the visibility of the "Switch UMD" menu item Emulator.getMainGUI().onUmdChange(); } public void hleUmdSwitch(UmdIsoReader newIso) { Scheduler scheduler = Scheduler.getInstance(); long delayedUmdSwitchSchedule = Scheduler.getNow(); if (iso != null) { // First notify that the UMD has been removed scheduler.addAction(new DelayedUmdRemoved()); // After 100ms delay, notify that a new UMD has been inserted delayedUmdSwitchSchedule += 100 * 1000; } scheduler.addAction(delayedUmdSwitchSchedule, new DelayedUmdSwitch(newIso)); } protected void hleDelayedUmdRemoved() { int notifyArg = getNotificationArg(false); Modules.ThreadManForUserModule.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, notifyArg); } protected void hleDelayedUmdSwitch(UmdIsoReader iso) { Modules.IoFileMgrForUserModule.setIsoReader(iso); setIsoReader(iso); int notifyArg = getNotificationArg() | PSP_UMD_CHANGED; Modules.ThreadManForUserModule.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, notifyArg); } @HLEFunction(nid = 0x46EBB729, version = 150) public boolean sceUmdCheckMedium() { return iso != null; } @HLEFunction(nid = 0xC6183D47, version = 150, checkInsideInterrupt = true) public int sceUmdActivate(int mode, PspString drive) { umdActivated = true; Modules.IoFileMgrForUserModule.registerUmdIso(); // Notify the callback. // The callback will be executed at the next sceXXXXCB() syscall. int notifyArg = getNotificationArg(); Modules.ThreadManForUserModule.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, notifyArg); checkWaitingThreads(); return 0; } @HLEFunction(nid = 0xE83742BA, version = 150, checkInsideInterrupt = true) public int sceUmdDeactivate(int mode, PspString drive) { // Trigger the callback only if the UMD was already activated. // The callback will be executed at the next sceXXXXCB() syscall. boolean triggerCallback = umdActivated; umdActivated = false; Modules.IoFileMgrForUserModule.registerUmdIso(); umdDeactivateCalled = true; if (triggerCallback) { int notifyArg; if (iso != null) { notifyArg = PSP_UMD_PRESENT | PSP_UMD_READY; } else { notifyArg = PSP_UMD_NOT_PRESENT | PSP_UMD_NOT_READY; } Modules.ThreadManForUserModule.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, notifyArg); } checkWaitingThreads(); return 0; } @HLEFunction(nid = 0x8EF08FCE, version = 150, checkInsideInterrupt = true) public int sceUmdWaitDriveStat(int wantedStat) { return hleUmdWaitDriveStat(wantedStat, false, false, 0); } @HLEFunction(nid = 0x56202973, version = 150, checkInsideInterrupt = true) public int sceUmdWaitDriveStatWithTimer(int wantedStat, int timeout) { return hleUmdWaitDriveStat(wantedStat, false, true, timeout); } @HLEFunction(nid = 0x4A9E5E29, version = 150, checkInsideInterrupt = true) public int sceUmdWaitDriveStatCB(int wantedStat, int timeout) { return hleUmdWaitDriveStat(wantedStat, true, true, timeout); } @HLEFunction(nid = 0x6AF9B50A, version = 150) public int sceUmdCancelWaitDriveStat() { ThreadManForUser threadMan = Modules.ThreadManForUserModule; for (ListIterator<SceKernelThreadInfo> lit = waitingThreads.listIterator(); lit.hasNext();) { SceKernelThreadInfo waitingThread = lit.next(); if (!waitingThread.isWaiting() || waitingThread.waitType != JPCSP_WAIT_UMD) { log.warn(String.format("sceUmdCancelWaitDriveStat thread %s not waiting on umd", waitingThread)); } else { if (log.isDebugEnabled()) { log.debug(String.format("sceUmdCancelWaitDriveStat waking thread %s", waitingThread)); } lit.remove(); // Return WAIT_CANCELLED. waitingThread.cpuContext._v0 = ERROR_KERNEL_WAIT_CANCELLED; // Wakeup thread threadMan.hleChangeThreadState(waitingThread, SceKernelThreadInfo.PSP_THREAD_READY); } } return 0; } @HLEFunction(nid = 0x6B4A146C, version = 150) public int sceUmdGetDriveStat() { if (log.isDebugEnabled()) { log.debug(String.format("sceUmdGetDriveStat returning 0x%X", getUmdStat())); } return getUmdStat(); } @HLEFunction(nid = 0x20628E6F, version = 150) public int sceUmdGetErrorStat() { return getUmdErrorStat(); } @HLEFunction(nid = 0x340B7686, version = 150, checkInsideInterrupt = true) public int sceUmdGetDiscInfo(TPointer pspUmdInfoAddr) { pspUmdInfo umdInfo = new pspUmdInfo(); umdInfo.read(pspUmdInfoAddr); if (umdInfo.sizeof() != 8) { return SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT; } umdInfo.type = pspUmdInfo.PSP_UMD_TYPE_GAME; umdInfo.write(pspUmdInfoAddr); return 0; } @HLEFunction(nid = 0xAEE7404D, version = 150, checkInsideInterrupt = true) public int sceUmdRegisterUMDCallBack(int uid) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; if (!threadMan.hleKernelRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, uid)) { return -1; } return 0; } @HLEFunction(nid = 0xBD2BDE07, version = 150) public int sceUmdUnRegisterUMDCallBack(int uid) { if (!Modules.ThreadManForUserModule.hleKernelUnRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_UMD, uid)) { return -1; } return 0; } @HLEFunction(nid = 0x87533940, version = 200) public int sceUmdReplaceProhibit() { if ((getUmdStat() & PSP_UMD_READY) != PSP_UMD_READY || (getUmdStat() & PSP_UMD_READABLE) != PSP_UMD_READABLE) { return ERROR_UMD_NOT_READY; } setUmdAllowReplace(false); return 0; } @HLEFunction(nid = 0xCBE9F02A, version = 200) public int sceUmdReplacePermit() { setUmdAllowReplace(true); return 0; } @HLEUnimplemented @HLEFunction(nid = 0x14C6C45C, version = 660) public int sceUmdUnuseUMDInMsUsbWlan() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xB103FA38, version = 660) public int sceUmdUseUMDInMsUsbWlan() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x816E656B, version = 660) public int sceUmdSetSuspendResumeMode(int mode) { return 0; } }