/*
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.Allegrex.Common._s0;
import static jpcsp.HLE.VFS.local.LocalVirtualFileSystem.getMsFileName;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ERRNO_DEVICE_NOT_FOUND;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ERRNO_FILE_ALREADY_EXISTS;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ERRNO_FILE_NOT_FOUND;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_ERRNO_READ_ONLY;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_INVALID_ARGUMENT;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ASYNC_BUSY;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_FILE_READ_ERROR;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOCWD;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NO_ASYNC_OP;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NO_SUCH_DEVICE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_TOO_MANY_OPEN_FILES;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_UNSUPPORTED_OPERATION;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_IO;
import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_READY;
import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.THREAD_CALLBACK_MEMORYSTICK_FAT;
import static jpcsp.util.Utilities.readStringNZ;
import static jpcsp.util.Utilities.readStringZ;
import jpcsp.HLE.CanBeNull;
import jpcsp.HLE.HLEFunction;
import jpcsp.HLE.HLELogging;
import jpcsp.HLE.HLEModule;
import jpcsp.HLE.HLEUnimplemented;
import jpcsp.HLE.PspString;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
import jpcsp.HLE.TPointer64;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.MemoryMap;
import jpcsp.Processor;
import jpcsp.Allegrex.CpuState;
import jpcsp.Allegrex.compiler.RuntimeContext;
import jpcsp.HLE.Modules;
import jpcsp.HLE.VFS.IVirtualFile;
import jpcsp.HLE.VFS.IVirtualFileSystem;
import jpcsp.HLE.VFS.SeekableDataInputVirtualFile;
import jpcsp.HLE.VFS.VirtualFileSystemManager;
import jpcsp.HLE.VFS.crypto.PGDVirtualFile;
import jpcsp.HLE.VFS.emulator.EmulatorVirtualFileSystem;
import jpcsp.HLE.VFS.iso.UmdIsoVirtualFile;
import jpcsp.HLE.VFS.iso.UmdIsoVirtualFileSystem;
import jpcsp.HLE.VFS.local.LocalVirtualFileSystem;
import jpcsp.HLE.VFS.memoryStick.MemoryStickStorageVirtualFileSystem;
import jpcsp.HLE.VFS.memoryStick.MemoryStickVirtualFileSystem;
import jpcsp.HLE.kernel.Managers;
import jpcsp.HLE.kernel.managers.MsgPipeManager;
import jpcsp.HLE.kernel.managers.SceUidManager;
import jpcsp.HLE.kernel.types.IAction;
import jpcsp.HLE.kernel.types.IWaitStateChecker;
import jpcsp.HLE.kernel.types.SceIoDirent;
import jpcsp.HLE.kernel.types.SceIoStat;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.kernel.types.SceKernelMppInfo;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.ScePspDateTime;
import jpcsp.HLE.kernel.types.ThreadWaitInfo;
import jpcsp.HLE.kernel.types.pspIoDrv;
import jpcsp.filesystems.SeekableDataInput;
import jpcsp.filesystems.SeekableRandomFile;
import jpcsp.filesystems.umdiso.UmdIsoFile;
import jpcsp.filesystems.umdiso.UmdIsoReader;
import jpcsp.hardware.MemoryStick;
import jpcsp.memory.IMemoryWriter;
import jpcsp.memory.MemoryWriter;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.util.Utilities;
import org.apache.log4j.Logger;
public class IoFileMgrForUser extends HLEModule {
public static Logger log = Modules.getLogger("IoFileMgrForUser");
private static Logger stdout = Logger.getLogger("stdout");
private static Logger stderr = Logger.getLogger("stderr");
public final static int PSP_O_RDONLY = 0x0001;
public final static int PSP_O_WRONLY = 0x0002;
public final static int PSP_O_RDWR = (PSP_O_RDONLY | PSP_O_WRONLY);
public final static int PSP_O_NBLOCK = 0x0004;
public final static int PSP_O_DIROPEN = 0x0008;
public final static int PSP_O_APPEND = 0x0100;
public final static int PSP_O_CREAT = 0x0200;
public final static int PSP_O_TRUNC = 0x0400;
public final static int PSP_O_EXCL = 0x0800;
public final static int PSP_O_NBUF = 0x4000; // Used on the PSP to bypass the internal disc cache (commonly seen in media files that need to maintain a fixed bitrate).
public final static int PSP_O_NOWAIT = 0x8000;
public final static int PSP_O_PLOCK = 0x2000000; // Used on the PSP to open the file inside a power lock (safe).
public final static int PSP_O_FGAMEDATA = 0x40000000; // Used on the PSP to handle encrypted data (used by NPDRM module).
//Every flag seems to be ORed with a retry count.
//In Activision Hits Remixed, an error is produced after
//the retry count (0xf0000/15) is over.
public final static int PSP_O_RETRY_0 = 0x00000;
public final static int PSP_O_RETRY_1 = 0x10000;
public final static int PSP_O_RETRY_2 = 0x20000;
public final static int PSP_O_RETRY_3 = 0x30000;
public final static int PSP_O_RETRY_4 = 0x40000;
public final static int PSP_O_RETRY_5 = 0x50000;
public final static int PSP_O_RETRY_6 = 0x60000;
public final static int PSP_O_RETRY_7 = 0x70000;
public final static int PSP_O_RETRY_8 = 0x80000;
public final static int PSP_O_RETRY_9 = 0x90000;
public final static int PSP_O_RETRY_10 = 0xa0000;
public final static int PSP_O_RETRY_11 = 0xb0000;
public final static int PSP_O_RETRY_12 = 0xc0000;
public final static int PSP_O_RETRY_13 = 0xd0000;
public final static int PSP_O_RETRY_14 = 0xe0000;
public final static int PSP_O_RETRY_15 = 0xf0000;
public final static int PSP_SEEK_SET = 0;
public final static int PSP_SEEK_CUR = 1;
public final static int PSP_SEEK_END = 2;
// Type of device used (abstract).
// Can symbolize physical character or block devices, logical filesystem devices
// or devices represented by an alias or even mount point devices also represented by an alias.
public final static int PSP_DEV_TYPE_NONE = 0x0;
public final static int PSP_DEV_TYPE_CHARACTER = 0x1;
public final static int PSP_DEV_TYPE_BLOCK = 0x4;
public final static int PSP_DEV_TYPE_FILESYSTEM = 0x10;
public final static int PSP_DEV_TYPE_ALIAS = 0x20;
public final static int PSP_DEV_TYPE_MOUNT = 0x40;
// PSP opens STDIN, STDOUT, STDERR in this order:
public final static int STDIN_ID = 0;
public final static int STDOUT_ID = 1;
public final static int STDERR_ID = 2;
protected SceKernelMppInfo[] stdRedirects;
private final static int MIN_ID = 3;
private final static int MAX_ID = 63;
private final static String idPurpose = "IOFileManager-File";
private final static boolean useVirtualFileSystem = false;
protected VirtualFileSystemManager vfsManager;
protected Map<String, String> assignedDevices;
public static class IoOperationTiming {
private int delayMillis;
private int sizeUnit;
public IoOperationTiming() {
this.delayMillis = 0;
}
public IoOperationTiming(int delayMillis) {
this.delayMillis = delayMillis;
this.sizeUnit = 0;
}
public IoOperationTiming(int delayMillis, int sizeUnit) {
this.delayMillis = delayMillis;
this.sizeUnit = sizeUnit;
}
/**
* Return a delay in milliseconds for the IoOperation.
*
* @return the delay in milliseconds
*/
int getDelayMillis() {
return delayMillis;
}
/**
* Return a delay in milliseconds based on the size of the
* processed data.
*
* @param size size of the processed data.
* 0 if no size is available.
* @return the delay in milliseconds
*/
int getDelayMillis(int size) {
if (sizeUnit == 0 || size <= 0) {
return getDelayMillis();
}
// Return a delay based on the given size.
// Return at least the delayMillis.
return Math.max((int) (((long) delayMillis) * size / sizeUnit), delayMillis);
}
public void setDelayMillis(int delayMillis) {
this.delayMillis = delayMillis;
}
}
public static enum IoOperation {
open, close, seek, ioctl, remove, rename, mkdir, dread, iodevctl, read, write
}
public static final Map<IoOperation, IoOperationTiming> defaultTimings = new HashMap<IoFileMgrForUser.IoOperation, IoFileMgrForUser.IoOperationTiming>();
public static final Map<IoOperation, IoOperationTiming> noDelayTimings = new HashMap<IoFileMgrForUser.IoOperation, IoFileMgrForUser.IoOperationTiming>();
// modeStrings indexed by [0, PSP_O_RDONLY, PSP_O_WRONLY, PSP_O_RDWR]
// SeekableRandomFile doesn't support write only: take "rw",
private final static String[] modeStrings = {"r", "r", "rw", "rw"};
public HashMap<Integer, IoInfo> fileIds;
public HashMap<Integer, IoInfo> fileUids;
public HashMap<Integer, IoDirInfo> dirIds;
private String filepath; // current working directory on PC
private UmdIsoReader iso;
private IoWaitStateChecker ioWaitStateChecker;
private String host0Path;
private int previousFatMsState;
private int defaultAsyncPriority;
private final static int asyncThreadRegisterArgument = _s0; // $s0 is preserved across calls
private boolean noDelayIoOperation;
private boolean allowExtractPGD;
// Implement the list of IIoListener as an array to improve the performance
// when iterating over all the entries (most common action).
private IIoListener[] ioListeners;
public class IoInfo {
// PSP settings
public final int flags;
public final int permissions;
// Internal settings
public final String filename;
public final SeekableRandomFile msFile; // on memory stick, should either be identical to readOnlyFile or null
public SeekableDataInput readOnlyFile; // on memory stick or umd
public IVirtualFile vFile;
public final String mode;
public long position; // virtual position, beyond the end is allowed, before the start is an error
public boolean sectorBlockMode;
public final int id;
public final int uid;
public long result; // The return value from the last operation on this file, used by sceIoWaitAsync
public boolean closePending = false; // sceIoCloseAsync has been called on this file
public boolean asyncPending; // Thread has not switched since an async operation was called on this file
public boolean asyncResultPending; // Async IO result is available and has not yet been retrieved
public long asyncDoneMillis; // When the async operation can be completed
public int asyncThreadPriority = defaultAsyncPriority;
public SceKernelThreadInfo asyncThread;
public IAction asyncAction;
private boolean truncateAtNextWrite;
// Async callback
public int cbid = -1;
public int notifyArg = 0;
/** Memory stick version */
public IoInfo(String filename, SeekableRandomFile f, String mode, int flags, int permissions) {
vFile = null;
this.filename = filename;
msFile = f;
readOnlyFile = f;
this.mode = mode;
this.flags = flags;
this.permissions = permissions;
sectorBlockMode = false;
id = getNewId();
if (isValidId()) {
uid = getNewUid();
fileIds.put(id, this);
fileUids.put(uid, this);
} else {
uid = -1;
}
}
/** UMD version (read only) */
public IoInfo(String filename, SeekableDataInput f, String mode, int flags, int permissions) {
vFile = null;
this.filename = filename;
msFile = null;
readOnlyFile = f;
this.mode = mode;
this.flags = flags;
this.permissions = permissions;
sectorBlockMode = false;
id = getNewId();
if (isValidId()) {
uid = getNewUid();
fileIds.put(id, this);
fileUids.put(uid, this);
} else {
uid = -1;
}
}
/** VirtualFile version */
public IoInfo(String filename, IVirtualFile f, String mode, int flags, int permissions) {
vFile = f;
this.filename = filename;
msFile = null;
readOnlyFile = null;
this.mode = mode;
this.flags = flags;
this.permissions = permissions;
sectorBlockMode = false;
id = getNewId();
if (isValidId()) {
uid = getNewUid();
fileIds.put(id, this);
fileUids.put(uid, this);
} else {
uid = -1;
}
}
public boolean isValidId() {
return id != SceUidManager.INVALID_ID;
}
public boolean isUmdFile() {
return (msFile == null);
}
public int getAsyncRestMillis() {
long now = Emulator.getClock().currentTimeMillis();
if (now >= asyncDoneMillis) {
return 0;
}
return (int) (asyncDoneMillis - now);
}
public void truncate(int length) {
try {
// Only valid for msFile.
if (msFile != null) {
msFile.setLength(length);
}
} catch (IOException ioe) {
log.debug("truncate", ioe);
}
}
public IoInfo close() {
IoInfo info = fileIds.remove(id);
if (info != null) {
fileUids.remove(uid);
releaseId(id);
releaseUid(uid);
}
return info;
}
public boolean isTruncateAtNextWrite() {
return truncateAtNextWrite;
}
public void setTruncateAtNextWrite(boolean truncateAtNextWrite) {
this.truncateAtNextWrite = truncateAtNextWrite;
}
@Override
public String toString() {
return String.format("id=0x%X, fileName='%s'", id, filename);
}
}
public class IoDirInfo {
final String path;
final String[] filenames;
int position;
int printableposition;
final int id;
final IVirtualFileSystem vfs;
String fileNameFilter;
public IoDirInfo(String path, String[] filenames) {
vfs = null;
id = getNewId();
// iso reader doesn't like path//filename, so trim trailing /
// (it's like doing cd somedir/ instead of cd somedir, makes little difference)
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
this.path = path;
this.filenames = filenames;
init();
}
public IoDirInfo(String path, String[] filenames, IVirtualFileSystem vfs) {
this.vfs = vfs;
id = getNewId();
this.path = path;
this.filenames = filenames;
init();
}
private void init() {
position = 0;
printableposition = 0;
// Hide iso special files
if (filenames != null) {
if (filenames.length > position && filenames[position].equals(".")) {
position++;
}
if (filenames.length > position && filenames[position].equals("\01")) {
position++;
}
}
dirIds.put(id, this);
}
public boolean hasNext() {
return (position < filenames.length);
}
public String next() {
String filename = null;
if (position < filenames.length) {
filename = filenames[position];
position++;
printableposition++;
}
return filename;
}
public IoDirInfo close() {
IoDirInfo info = dirIds.remove(id);
if (info != null) {
releaseId(id);
}
return info;
}
}
private static class PatternFilter implements FilenameFilter {
private Pattern pattern;
public PatternFilter(String pattern) {
this.pattern = Pattern.compile(pattern);
}
@Override
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
};
public static interface IIoListener {
void sceIoSync(int result, int device_addr, String device, int unknown);
void sceIoPollAsync(int result, int uid, int res_addr);
void sceIoWaitAsync(int result, int uid, int res_addr);
void sceIoOpen(int result, int filename_addr, String filename, int flags, int permissions, String mode);
void sceIoClose(int result, int uid);
void sceIoWrite(int result, int uid, int data_addr, int size, int bytesWritten);
void sceIoRead(int result, int uid, int data_addr, int size, int bytesRead, long position, SeekableDataInput dataInput, IVirtualFile vFile);
void sceIoCancel(int result, int uid);
void sceIoSeek32(int result, int uid, int offset, int whence);
void sceIoSeek64(long result, int uid, long offset, int whence);
void sceIoMkdir(int result, int path_addr, String path, int permissions);
void sceIoRmdir(int result, int path_addr, String path);
void sceIoChdir(int result, int path_addr, String path);
void sceIoDopen(int result, int path_addr, String path);
void sceIoDread(int result, int uid, int dirent_addr);
void sceIoDclose(int result, int uid);
void sceIoDevctl(int result, int device_addr, String device, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen);
void sceIoIoctl(int result, int uid, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen);
void sceIoAssign(int result, int dev1_addr, String dev1, int dev2_addr, String dev2, int dev3_addr, String dev3, int mode, int unk1, int unk2);
void sceIoGetStat(int result, int path_addr, String path, int stat_addr);
void sceIoRemove(int result, int path_addr, String path);
void sceIoChstat(int result, int path_addr, String path, int stat_addr, int bits);
void sceIoRename(int result, int path_addr, String path, int new_path_addr, String newpath);
}
private class IoWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
IoInfo info = fileIds.get(wait.Io_id);
if (info == null) {
return false;
}
if (!info.asyncPending) {
// Async IO is already completed
if (info.asyncResultPending) {
if (Memory.isAddressGood(wait.Io_resultAddr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("IoWaitStateChecker - async completed, writing pending result 0x%X", info.result));
}
Memory.getInstance().write64(wait.Io_resultAddr, info.result);
}
info.asyncResultPending = false;
info.result = ERROR_KERNEL_NO_ASYNC_OP;
}
return false;
}
return true;
}
}
private class IOAsyncReadAction implements IAction {
private IoInfo info;
private int address;
private int size;
private int requestedSize;
public IOAsyncReadAction(IoInfo info, int address, int requestedSize, int size) {
this.info = info;
this.address = address;
this.requestedSize = requestedSize;
this.size = size;
}
@Override
public void execute() {
long position = info.position;
int result = 0;
if (info.vFile != null) {
result = info.vFile.ioRead(new TPointer(Memory.getInstance(), address), size);
if (result >= 0) {
info.position += result;
size = result;
if (info.sectorBlockMode) {
result /= UmdIsoFile.sectorLength;
}
} else {
size = 0;
}
} else {
try {
Utilities.readFully(info.readOnlyFile, address, size);
info.position += size;
result = size;
if (info.sectorBlockMode) {
result /= UmdIsoFile.sectorLength;
}
} catch (IOException e) {
log.error(e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
}
info.result = result;
// Invalidate any compiled code in the read range
RuntimeContext.invalidateRange(address, size);
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoRead(result, info.id, address, requestedSize, size, position, info.readOnlyFile, info.vFile);
}
}
}
private class ExtractPGDSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setAllowExtractPGDStatus(value);
}
}
public void registerUmdIso() {
if (vfsManager != null && useVirtualFileSystem) {
if (iso != null && Modules.sceUmdUserModule.isUmdActivated()) {
IVirtualFileSystem vfsIso = new UmdIsoVirtualFileSystem(iso);
vfsManager.register("disc0", vfsIso);
vfsManager.register("umd0", vfsIso);
vfsManager.register("umd1", vfsIso);
vfsManager.register("umd", vfsIso);
vfsManager.register("isofs", vfsIso);
} else {
vfsManager.unregister("disc0");
vfsManager.unregister("umd0");
vfsManager.unregister("umd1");
vfsManager.unregister("umd");
vfsManager.unregister("isofs");
// Register the local path if the application has been loaded as a file (and not as an UMD).
if (filepath != null) {
int colon = filepath.indexOf(':');
if (colon >= 0) {
String device = filepath.substring(0, colon);
device = device.toLowerCase();
vfsManager.register(device, new LocalVirtualFileSystem(device + ":\\", false));
}
}
}
}
}
private void registerVfsMs0() {
if (vfsManager == null) {
vfsManager = new VirtualFileSystemManager();
}
vfsManager.register("ms0", new LocalVirtualFileSystem("ms0/", true));
vfsManager.register("fatms0", new LocalVirtualFileSystem("ms0/", true));
vfsManager.register("flash0", new LocalVirtualFileSystem("flash0/", false));
vfsManager.register("flash1", new LocalVirtualFileSystem("flash1/", false));
vfsManager.register("exdata0", new LocalVirtualFileSystem("exdata0/", false));
vfsManager.register("mscmhc0", new MemoryStickVirtualFileSystem());
vfsManager.register("msstor0p1", new MemoryStickStorageVirtualFileSystem());
vfsManager.register("msstor0", new MemoryStickStorageVirtualFileSystem());
}
@Override
public void start() {
if (fileIds != null) {
// Close open files
for (Iterator<IoInfo> it = fileIds.values().iterator(); it.hasNext();) {
IoInfo info = it.next();
try {
info.readOnlyFile.close();
} catch (IOException e) {
log.error("pspiofilemgr - error closing file: " + e.getMessage());
}
}
}
fileIds = new HashMap<Integer, IoInfo>();
fileUids = new HashMap<Integer, IoInfo>();
dirIds = new HashMap<Integer, IoDirInfo>();
MemoryStick.setStateMs(MemoryStick.PSP_MEMORYSTICK_STATE_DRIVER_READY);
defaultAsyncPriority = -1;
if (ioListeners == null) {
ioListeners = new IIoListener[0];
}
ioWaitStateChecker = new IoWaitStateChecker();
host0Path = null;
noDelayIoOperation = false;
stdRedirects = new SceKernelMppInfo[3];
previousFatMsState = MemoryStick.PSP_FAT_MEMORYSTICK_STATE_UNASSIGNED;
vfsManager = new VirtualFileSystemManager();
vfsManager.register("emulator", new EmulatorVirtualFileSystem());
vfsManager.register("kemulator", new EmulatorVirtualFileSystem());
if (useVirtualFileSystem) {
registerVfsMs0();
registerUmdIso();
}
assignedDevices = new HashMap<String, String>();
setSettingsListener("emu.extractPGD", new ExtractPGDSettingsListerner());
defaultTimings.put(IoOperation.open, new IoFileMgrForUser.IoOperationTiming(5));
defaultTimings.put(IoOperation.close, new IoFileMgrForUser.IoOperationTiming(1));
defaultTimings.put(IoOperation.seek, new IoFileMgrForUser.IoOperationTiming());
defaultTimings.put(IoOperation.ioctl, new IoFileMgrForUser.IoOperationTiming(20));
defaultTimings.put(IoOperation.remove, new IoFileMgrForUser.IoOperationTiming());
defaultTimings.put(IoOperation.rename, new IoFileMgrForUser.IoOperationTiming());
defaultTimings.put(IoOperation.mkdir, new IoFileMgrForUser.IoOperationTiming());
defaultTimings.put(IoOperation.dread, new IoFileMgrForUser.IoOperationTiming());
defaultTimings.put(IoOperation.iodevctl, new IoFileMgrForUser.IoOperationTiming(2));
// Duration of read operation: approx. 7 ms per 0x10000 bytes (tested on real PSP)
defaultTimings.put(IoOperation.read, new IoFileMgrForUser.IoOperationTiming(7, 0x10000));
// Duration of write operation: approx. 5 ms per 0x10000 bytes
defaultTimings.put(IoOperation.write, new IoFileMgrForUser.IoOperationTiming(5, 0x10000));
noDelayTimings.put(IoOperation.open, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.close, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.seek, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.ioctl, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.remove, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.rename, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.mkdir, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.dread, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.iodevctl, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.read, new IoFileMgrForUser.IoOperationTiming());
noDelayTimings.put(IoOperation.write, new IoFileMgrForUser.IoOperationTiming());
super.start();
}
public void setHost0Path(String path) {
host0Path = path;
}
public void setAllowExtractPGDStatus(boolean status) {
allowExtractPGD = status;
}
public boolean getAllowExtractPGDStatus() {
return allowExtractPGD;
}
public IoInfo getFileIoInfo(int id) {
return fileIds.get(id);
}
private static int getNewUid() {
return SceUidManager.getNewUid(idPurpose);
}
private static void releaseUid(int uid) {
SceUidManager.releaseUid(uid, idPurpose);
}
private static int getNewId() {
return SceUidManager.getNewId(idPurpose, MIN_ID, MAX_ID);
}
private static void releaseId(int id) {
SceUidManager.releaseId(id, idPurpose);
}
/**
* Resolve and remove the "/.." in file names.
* E.g.:
* disc0:/PSP_GAME/USRDIR/A/../B
* transformed into
* disc0:/PSP_GAME/USRDIR/B
*
* @param fileName File name, possibly containing "/.."
* @return File name without "/.."
*/
private String removeDotDotInFilename(String fileName) {
while (true) {
int dotDotIndex = fileName.indexOf("/..");
if (dotDotIndex < 0) {
break;
}
int parentIndex = fileName.substring(0, dotDotIndex).lastIndexOf("/");
if (parentIndex < 0) {
break;
}
fileName = fileName.substring(0, parentIndex) + fileName.substring(dotDotIndex + 3);
}
return fileName;
}
private String getAbsoluteFileName(String fileName) {
if (filepath == null || fileName.contains(":")) {
return fileName;
}
String absoluteFileName = filepath;
if (!absoluteFileName.endsWith("/") && !fileName.startsWith("/")) {
absoluteFileName += "/";
}
absoluteFileName += fileName;
absoluteFileName = absoluteFileName.replaceFirst("^disc0/", "disc0:");
absoluteFileName = absoluteFileName.replaceFirst("^ms0/", "ms0:");
return absoluteFileName;
}
/*
* Local file handling functions.
*/
public String getDeviceFilePath(String pspfilename) {
pspfilename = pspfilename.replaceAll("\\\\", "/");
String device = null;
String cwd = "";
String filename = null;
if (pspfilename.startsWith("flash0:")) {
if (pspfilename.startsWith("flash0:/")) {
return pspfilename.replace("flash0:", "flash0");
}
return pspfilename.replace("flash0:", "flash0/");
}
if (pspfilename.startsWith("exdata0:")) {
if (pspfilename.startsWith("exdata0:/")) {
return pspfilename.replace("exdata0:", "exdata0");
}
return pspfilename.replace("exdata0:", "exdata0/");
}
if (host0Path != null && pspfilename.startsWith("host0:") && !pspfilename.startsWith("host0:/")) {
pspfilename = pspfilename.replace("host0:", host0Path);
pspfilename = removeDotDotInFilename(pspfilename);
}
if (filepath == null) {
return pspfilename;
}
int findcolon = pspfilename.indexOf(":");
if (findcolon != -1) {
device = pspfilename.substring(0, findcolon);
pspfilename = pspfilename.substring(findcolon + 1);
} else {
int findslash = filepath.indexOf("/");
if (findslash != -1) {
device = filepath.substring(0, findslash);
cwd = filepath.substring(findslash + 1);
if (cwd.startsWith("/")) {
cwd = cwd.substring(1);
}
if (cwd.endsWith("/")) {
cwd = cwd.substring(0, cwd.length() - 1);
}
} else {
device = filepath;
}
}
// Map assigned devices, e.g.
// Fire Up:
// sceIoAssign alias=0x0898EFC0('pfat0:'), physicalDev=0x0898F000('msstor0p1:/'), filesystemDev=0x0898F00C('fatms0:'), mode=0x0, arg_addr=0x0, argSize=0x0
// sceIoOpen filename='pfat0:PSP/SAVEDATA/PPCD00001DLS001/DATA2.BIN'
if (assignedDevices != null && assignedDevices.containsKey(device)) {
device = assignedDevices.get(device);
}
// remap host0
// - Bliss Island - ULES00616
if (device.equals("host0")) {
if (iso != null) {
device = "disc0";
} else {
device = "ms0";
}
}
// remap fatms0
// - Wipeout Pure - UCUS98612
if (device.equals("fatms0")) {
device = "ms0";
}
// Ignore the filename in "umd0:xxx".
// Using umd0: is always opening the whole UMD in sector block mode,
// ignoring the file name specified after the colon.
if (device.startsWith("umd")) {
pspfilename = "";
}
// strip leading and trailing slash from supplied path
// this step is common to absolute and relative paths
if (pspfilename.startsWith("/")) {
pspfilename = pspfilename.substring(1);
}
if (pspfilename.endsWith("/")) {
pspfilename = pspfilename.substring(0, pspfilename.length() - 1);
}
// assemble final path
// convert device to lower case here for case sensitive file systems (linux) and also for isUmdPath and trimUmdPrefix regex
// - GTA: LCS uses upper case device DISC0
// - The Fast and the Furious uses upper case device DISC0
filename = device.toLowerCase();
if (cwd.length() > 0) {
filename += "/" + cwd;
}
if (pspfilename.length() > 0) {
filename += "/" + pspfilename;
}
return filename;
}
private static final String[] umdPrefixes = new String[] {
"disc[0-9]+", "umd[0-9]+", "umd", "isofs"
};
private boolean isUmdPath(String deviceFilePath) {
for (String umdPrefix : umdPrefixes) {
if (deviceFilePath.matches(umdPrefix)) {
return true;
} else if (deviceFilePath.matches(umdPrefix + "/.*")) {
return true;
}
}
return false;
}
private String trimUmdPrefix(String pcfilename) {
// Assume the device name is always lower case (ensured by getDeviceFilePath)
// Handle case where file path is blank so there is no slash after the device name
for (String umdPrefix : umdPrefixes) {
if (pcfilename.matches(umdPrefix)) {
return "";
} else if (pcfilename.matches(umdPrefix + "/.*")) {
return pcfilename.substring(pcfilename.indexOf("/") + 1);
}
}
return pcfilename;
}
public void mkdirs(String dir) {
String pcfilename = getDeviceFilePath(dir);
if (pcfilename != null) {
File f = new File(pcfilename);
f.mkdirs();
}
}
private boolean rmdir(File f, boolean recursive) {
boolean subDirResult = true;
if (recursive && f.isDirectory()) {
File[] subFiles = f.listFiles();
for (int i = 0; subFiles != null && i < subFiles.length; i++) {
if (!rmdir(subFiles[i], recursive)) {
subDirResult = false;
}
}
}
return f.delete() && subDirResult;
}
public boolean rmdir(String dir, boolean recursive) {
String pcfilename = getDeviceFilePath(dir);
if (pcfilename == null) {
return false;
}
File f = new File(pcfilename);
return rmdir(f, recursive);
}
public boolean deleteFile(String pspfilename) {
String pcfilename = getDeviceFilePath(pspfilename);
if (pcfilename == null) {
return false;
}
String absoluteFileName = getAbsoluteFileName(pspfilename);
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
boolean fileDeleted;
if (vfs != null) {
int result = vfs.ioRemove(localFileName.toString());
fileDeleted = result >= 0;
} else {
File f = new File(pcfilename);
fileDeleted = f.delete();
}
return fileDeleted;
}
public String[] listFiles(String dir, String pattern) {
String pcfilename = getDeviceFilePath(dir);
if (pcfilename == null) {
return null;
}
File f = new File(pcfilename);
return pattern == null ? f.list() : f.list(new PatternFilter(pattern));
}
public SceIoStat statFile(String pspfilename) {
String pcfilename = getDeviceFilePath(pspfilename);
if (pcfilename == null) {
return null;
}
SceIoStat stat = null;
String absoluteFileName = getAbsoluteFileName(pspfilename);
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
stat = new SceIoStat();
int result = vfs.ioGetstat(localFileName.toString(), stat);
if (result < 0) {
stat = null;
}
} else {
stat = stat(pcfilename);
}
return stat;
}
/**
* @param pcfilename can be null for convenience
* @returns null on error
*/
private SceIoStat stat(String pcfilename) {
SceIoStat stat = null;
if (pcfilename != null) {
if (isUmdPath(pcfilename)) {
// check umd is mounted
if (iso == null) {
log.error("stat - no umd mounted");
Emulator.getProcessor().cpu._v0 = ERROR_ERRNO_DEVICE_NOT_FOUND;
// check umd is activated
} else if (!Modules.sceUmdUserModule.isUmdActivated()) {
log.warn("stat - umd mounted but not activated");
Emulator.getProcessor().cpu._v0 = ERROR_KERNEL_NO_SUCH_DEVICE;
} else {
String isofilename = trimUmdPrefix(pcfilename);
int mode = 4; // 4 = readable
int attr = 0;
long size = 0;
long timestamp = 0;
int startSector = 0;
try {
// Check for files first.
UmdIsoFile file = iso.getFile(isofilename);
attr = 0x20;
size = file.length();
timestamp = file.getTimestamp().getTime();
startSector = file.getStartSector();
// Octal extend into user and group
mode = mode + mode * 8 + mode * 64;
// Copy attr into mode
mode |= attr << 8;
stat = new SceIoStat(mode, attr, size,
ScePspDateTime.fromUnixTime(timestamp),
ScePspDateTime.fromUnixTime(0),
ScePspDateTime.fromUnixTime(timestamp));
if (startSector > 0) {
stat.setStartSector(startSector);
}
} catch (FileNotFoundException fnfe) {
// If file wasn't found, try looking for a directory.
try {
if (iso.isDirectory(isofilename)) {
attr |= 0x10;
mode |= 1; // 1 = executable
}
// Octal extend into user and group
mode = mode + mode * 8 + mode * 64;
// Copy attr into mode
mode |= attr << 8;
stat = new SceIoStat(mode, attr, size,
ScePspDateTime.fromUnixTime(timestamp),
ScePspDateTime.fromUnixTime(0),
ScePspDateTime.fromUnixTime(timestamp));
if (startSector > 0) {
stat.setStartSector(startSector);
}
} catch (FileNotFoundException dnfe) {
log.warn("stat - '" + isofilename + "' umd file/dir not found");
} catch (IOException e) {
log.warn("stat - umd io error: " + e.getMessage());
}
} catch (IOException e) {
log.warn("stat - umd io error: " + e.getMessage());
}
}
} else {
File file = new File(pcfilename);
if (file.exists()) {
int mode = (file.canRead() ? 4 : 0) + (file.canWrite() ? 2 : 0) + (file.canExecute() ? 1 : 0);
int attr = 0;
long size = file.length();
long mtime = file.lastModified();
// Octal extend into user and group
mode = mode + mode * 8 + mode * 64;
// Set attr (dir/file) and copy into mode
if (file.isDirectory()) {
attr |= 0x10;
}
if (file.isFile()) {
attr |= 0x20;
}
mode |= attr << 8;
// Java can't see file create/access time
stat = new SceIoStat(mode, attr, size,
ScePspDateTime.fromUnixTime(mtime),
ScePspDateTime.fromUnixTime(0),
ScePspDateTime.fromUnixTime(mtime));
}
}
}
return stat;
}
public IVirtualFile getVirtualFile(String filename, int flags, int permissions) {
String absoluteFileName = getAbsoluteFileName(filename);
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
return vfs.ioOpen(localFileName.toString(), flags, permissions);
}
return null;
}
public SeekableDataInput getFile(String filename, int flags) {
SeekableDataInput resultFile = null;
String pcfilename = getDeviceFilePath(filename);
if (pcfilename != null) {
if (isUmdPath(pcfilename)) {
// check umd is mounted
if (iso == null) {
log.error("getFile - no umd mounted");
return resultFile;
// check flags are valid
} else if ((flags & PSP_O_WRONLY) == PSP_O_WRONLY ||
(flags & PSP_O_CREAT) == PSP_O_CREAT ||
(flags & PSP_O_TRUNC) == PSP_O_TRUNC) {
log.error("getFile - refusing to open umd media for write");
return resultFile;
} else {
// open file
try {
UmdIsoFile file = iso.getFile(trimUmdPrefix(pcfilename));
resultFile = file;
} catch (FileNotFoundException e) {
if (log.isDebugEnabled()) {
log.debug("getFile - umd file not found '" + pcfilename + "' (ok to ignore this message, debug purpose only)");
}
} catch (IOException e) {
log.error("getFile - error opening umd media: " + e.getMessage());
}
}
} else {
// First check if the file already exists
File file = new File(pcfilename);
if (file.exists() &&
(flags & PSP_O_CREAT) == PSP_O_CREAT &&
(flags & PSP_O_EXCL) == PSP_O_EXCL) {
if (log.isDebugEnabled()) {
log.debug("getFile - file already exists (PSP_O_CREAT + PSP_O_EXCL)");
}
} else {
if (file.exists() &&
(flags & PSP_O_TRUNC) == PSP_O_TRUNC) {
log.warn("getFile - file already exists, deleting UNIMPLEMENT (PSP_O_TRUNC)");
}
String mode = getMode(flags);
try {
resultFile = new SeekableRandomFile(pcfilename, mode);
} catch (FileNotFoundException e) {
if (log.isDebugEnabled()) {
log.debug("getFile - file not found '" + pcfilename + "' (ok to ignore this message, debug purpose only)");
}
}
}
}
}
return resultFile;
}
public SeekableDataInput getFile(int id) {
IoInfo info = fileIds.get(id);
if (info == null) {
return null;
}
return info.readOnlyFile;
}
public String getFileFilename(int id) {
IoInfo info = fileIds.get(id);
if (info == null) {
return null;
}
return info.filename;
}
private String getMode(int flags) {
return modeStrings[flags & PSP_O_RDWR];
}
private long updateResult(IoInfo info, long result, boolean async, boolean resultIs64bit, IoOperationTiming ioOperationTiming) {
return updateResult(info, result, async, resultIs64bit, ioOperationTiming, null, 0);
}
// Handle returning/storing result for sync/async operations
private long updateResult(IoInfo info, long result, boolean async, boolean resultIs64bit, IoOperationTiming ioOperationTiming, IAction asyncAction, int size) {
// No async IO is started when returning error code ERROR_KERNEL_ASYNC_BUSY
if (info != null && result != ERROR_KERNEL_ASYNC_BUSY) {
if (async) {
if (!info.asyncPending) {
result = startIoAsync(info, result, ioOperationTiming, asyncAction, size);
}
} else {
info.result = ERROR_KERNEL_NO_ASYNC_OP;
}
}
return result;
}
public static String getWhenceName(int whence) {
switch (whence) {
case PSP_SEEK_SET:
return "PSP_SEEK_SET";
case PSP_SEEK_CUR:
return "PSP_SEEK_CUR";
case PSP_SEEK_END:
return "PSP_SEEK_END";
default:
return "UNHANDLED " + whence;
}
}
public void setfilepath(String filepath) {
filepath = filepath.replaceAll("\\\\", "/");
if (log.isDebugEnabled()) {
log.debug(String.format("filepath set to '%s'", filepath));
}
this.filepath = filepath;
}
public void exit() {
closeIsoReader();
}
private void closeIsoReader() {
if (iso != null) {
try {
iso.close();
} catch (IOException e) {
log.error("Error closing ISO reader", e);
}
iso = null;
}
}
public void setIsoReader(UmdIsoReader iso) {
closeIsoReader();
this.iso = iso;
registerUmdIso();
}
public UmdIsoReader getIsoReader() {
return iso;
}
protected void delayIoOperation(IoOperationTiming ioOperationTiming) {
if (!noDelayIoOperation && ioOperationTiming.delayMillis > 0) {
Modules.ThreadManForUserModule.hleKernelDelayThread(ioOperationTiming.delayMillis * 1000, false);
}
}
public void hleSetNoDelayIoOperation(boolean noDelayIoOperation) {
this.noDelayIoOperation = noDelayIoOperation;
}
/*
* Async thread functions.
*/
public void hleAsyncThread(Processor processor) {
CpuState cpu = processor.cpu;
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
int uid = cpu.getRegister(asyncThreadRegisterArgument);
IoInfo info = fileUids.get(uid);
if (info == null) {
if (log.isDebugEnabled()) {
log.debug(String.format("hleAsyncThread non-existing uid=%x", uid));
}
cpu._v0 = 0; // Exit status
// Exit and delete the thread to free its resources (e.g. its stack)
threadMan.hleKernelExitDeleteThread();
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("hleAsyncThread id=%x", info.id));
}
boolean asyncCompleted = doStepAsync(info);
if (threadMan.getCurrentThread() == info.asyncThread) {
if (asyncCompleted) {
if (log.isDebugEnabled()) {
log.debug(String.format("Async IO completed"));
}
// Wait for a new Async IO... wakeup is done by triggerAsyncThread()
threadMan.hleKernelSleepThread(false);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Async IO not yet completed"));
}
// Wait for the Async IO to complete...
threadMan.hleKernelDelayThread(info.getAsyncRestMillis() * 1000, false);
}
}
}
}
/**
* Trigger the activation of the async thread if one is defined.
*
* @param info the file info
*/
private void triggerAsyncThread(IoInfo info) {
if (info.asyncThread != null) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
threadMan.hleKernelWakeupThread(info.asyncThread);
}
}
/**
* Start the async IO thread if not yet started.
*
* @param info the file
* @param result the result the async IO should return
*/
private int startIoAsync(IoInfo info, long result, IoOperationTiming ioOperationTiming, IAction asyncAction, int size) {
int startResult = 0;
if (info == null) {
return startResult;
}
info.asyncPending = true;
info.asyncResultPending = false;
long now = Emulator.getClock().currentTimeMillis();
info.asyncDoneMillis = now + ioOperationTiming.getDelayMillis(size);
info.asyncAction = asyncAction;
info.result = result;
if (info.asyncThread == null) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
// Inherit priority from current thread if no default priority set
int asyncPriority = info.asyncThreadPriority;
if (asyncPriority < 0) {
// Take the priority of the thread executing the first async operation.
asyncPriority = threadMan.getCurrentThread().currentPriority;
}
int stackSize = 0x2000;
// On FW 1.50, the stack size for the async thread is 0x2000,
// on FW 5.00, the stack size is 0x800.
// When did it change?
if (Emulator.getInstance().getFirmwareVersion() > 150) {
stackSize = 0x800;
}
// The stack of the async thread is always allocated in the kernel partition
info.asyncThread = threadMan.hleKernelCreateThread("SceIofileAsync",
ThreadManForUser.ASYNC_LOOP_ADDRESS, asyncPriority, stackSize,
threadMan.getCurrentThread().attr, 0, SysMemUserForUser.KERNEL_PARTITION_ID);
if (info.asyncThread.getStackAddr() == 0) {
log.warn(String.format("Cannot start the Async IO thread, not enough memory to create its stack"));
threadMan.hleDeleteThread(info.asyncThread);
info.asyncThread = null;
startResult = SceKernelErrors.ERROR_KERNEL_NO_MEMORY;
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Starting Async IO thread %s", info.asyncThread));
}
// This must be the last action of the hleIoXXX call because it can context-switch
// Inherit $gp from this process ($gp can be used by interrupts)
threadMan.hleKernelStartThread(info.asyncThread, 0, 0, info.asyncThread.gpReg_addr);
// Copy uid to Async Thread argument register after starting the thread
// (all registers are reset when starting the thread).
info.asyncThread.cpuContext.setRegister(asyncThreadRegisterArgument, info.uid);
}
} else {
triggerAsyncThread(info);
}
return startResult;
}
private boolean doStepAsync(IoInfo info) {
boolean done = true;
if (info.asyncPending) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (info.getAsyncRestMillis() > 0) {
done = false;
} else {
// Execute any pending async action and remove it.
// Execute the action only when the async operation can be completed
// as its execution can be time consuming (e.g. code block cache invalidation).
if (info.asyncAction != null) {
IAction asyncAction = info.asyncAction;
info.asyncAction = null;
asyncAction.execute();
}
info.asyncPending = false;
info.asyncResultPending = true;
if (info.cbid >= 0) {
// Trigger Async callback.
threadMan.hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_IO, info.cbid, info.notifyArg);
}
// Find threads waiting on this id and wake them up.
for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext();) {
SceKernelThreadInfo thread = it.next();
if (thread.waitType == JPCSP_WAIT_IO &&
thread.wait.Io_id == info.id) {
if (log.isDebugEnabled()) {
log.debug("IoFileMgrForUser.doStepAsync - onContextSwitch waking " + Integer.toHexString(thread.uid) + " thread:'" + thread.name + "'");
}
// Write result
Memory mem = Memory.getInstance();
if (Memory.isAddressGood(thread.wait.Io_resultAddr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("IoFileMgrForUser.doStepAsync - storing result 0x%X", info.result));
}
mem.write64(thread.wait.Io_resultAddr, info.result);
}
// Return error at next call to sceIoWaitAsync
info.result = ERROR_KERNEL_NO_ASYNC_OP;
info.asyncResultPending = false;
// Return success
thread.cpuContext._v0 = 0;
// Wakeup
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
}
}
}
}
return done;
}
public void unregisterIoListener(IIoListener ioListener) {
if (ioListeners != null) {
for (int i = 0; i < ioListeners.length; i++) {
if (ioListeners[i] == ioListener) {
IIoListener[] newIoListeners = new IIoListener[ioListeners.length - 1];
System.arraycopy(ioListeners, 0, newIoListeners, 0, i);
System.arraycopy(ioListeners, i + 1, newIoListeners, i, ioListeners.length - i - 1);
ioListeners = newIoListeners;
break;
}
}
}
}
public void registerIoListener(IIoListener ioListener) {
if (ioListeners == null) {
ioListeners = new IIoListener[1];
ioListeners[0] = ioListener;
} else {
for (int i = 0; i < ioListeners.length; i++) {
if (ioListeners[i] == ioListener) {
// The listener is already registered
return;
}
}
IIoListener[] newIoListeners = new IIoListener[ioListeners.length + 1];
System.arraycopy(ioListeners, 0, newIoListeners, 0, ioListeners.length);
newIoListeners[ioListeners.length] = ioListener;
ioListeners = newIoListeners;
}
}
private IoInfo getInfo(IVirtualFile vFile) {
for (IoInfo info : fileIds.values()) {
if (info.vFile == vFile) {
return info;
}
}
return null;
}
public long getPosition(IVirtualFile vFile) {
IoInfo info = getInfo(vFile);
if (info == null) {
return -1;
}
return info.position;
}
public void setPosition(IVirtualFile vFile, long position) {
IoInfo info = getInfo(vFile);
if (info != null) {
info.position = position;
}
}
public VirtualFileSystemManager getVirtualFileSystemManager() {
return vfsManager;
}
public IVirtualFileSystem getVirtualFileSystem(String pspfilename, StringBuilder localFileName) {
boolean umdRegistered = false;
boolean msRegistered = false;
// This call wants to use the VFS.
// If the UMD has not been registered, register it just for this call
if (!useVirtualFileSystem) {
if (iso != null && Modules.sceUmdUserModule.isUmdActivated()) {
IVirtualFileSystem vfsIso = new UmdIsoVirtualFileSystem(iso);
vfsManager.register("disc0", vfsIso);
vfsManager.register("umd0", vfsIso);
vfsManager.register("umd1", vfsIso);
vfsManager.register("umd", vfsIso);
vfsManager.register("isofs", vfsIso);
umdRegistered = true;
}
registerVfsMs0();
msRegistered = true;
}
String absoluteFileName = getAbsoluteFileName(pspfilename);
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (umdRegistered) {
vfsManager.unregister("disc0");
vfsManager.unregister("umd0");
vfsManager.unregister("umd1");
vfsManager.unregister("umd");
vfsManager.unregister("isofs");
}
if (msRegistered) {
vfsManager.unregister("ms0");
vfsManager.unregister("fatms0");
vfsManager.unregister("flash0");
vfsManager.unregister("flash1");
vfsManager.unregister("exdata0");
vfsManager.unregister("mscmhc0");
vfsManager.unregister("msstor0");
vfsManager.unregister("msstor0");
}
return vfs;
}
/*
* HLE functions.
*/
public int hleIoWaitAsync(int id, TPointer64 resAddr, boolean wait, boolean callbacks) {
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoWaitAsync id=0x%X, res=%s, wait=%b, callbacks=%b", id, resAddr, wait, callbacks));
}
IoInfo info = fileIds.get(id);
if (info == null) {
if (id == 0) {
// Avoid WARN spam messages
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoWaitAsync - unknown id 0x%X", id));
}
} else {
log.warn(String.format("hleIoWaitAsync - unknown id 0x%X", id));
}
return ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
if (info.result == ERROR_KERNEL_NO_ASYNC_OP || info.asyncThread == null) {
log.debug("hleIoWaitAsync - PSP_ERROR_NO_ASYNC_OP");
return ERROR_KERNEL_NO_ASYNC_OP;
}
if (info.asyncPending && !wait) {
// Polling returns 1 when async is busy.
log.debug("hleIoWaitAsync - poll return = 1(busy)");
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoWaitAsync info.result=0x%X", info.result));
}
return 1;
}
boolean waitForAsync = false;
// Check for the waiting condition first.
if (wait) {
waitForAsync = true;
}
if (!info.asyncPending) {
log.debug("hleIoWaitAsync - async already completed, not waiting");
waitForAsync = false;
}
// The file was marked as closePending, so close it right away to avoid delays.
if (info.closePending) {
log.debug("hleIoWaitAsync - file marked with closePending, calling hleIoClose, not waiting");
info.asyncPending = false;
info.asyncResultPending = false;
hleIoClose(info.id, false);
waitForAsync = false;
}
// The file was not found at sceIoOpenAsync.
if (info.result == ERROR_ERRNO_FILE_NOT_FOUND) {
log.debug("hleIoWaitAsync - file not found, not waiting");
info.close();
triggerAsyncThread(info);
waitForAsync = false;
}
if (waitForAsync) {
// Call the ioListeners.
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoWaitAsync(0, id, resAddr.getAddress());
}
// Start the waiting mode.
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
currentThread.wait.Io_id = info.id;
currentThread.wait.Io_resultAddr = resAddr.getAddress();
threadMan.hleKernelThreadEnterWaitState(JPCSP_WAIT_IO, info.id, ioWaitStateChecker, callbacks);
} else {
// Store the result
if (resAddr.isNotNull()) {
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoWaitAsync - storing result 0x%X", info.result));
}
resAddr.setValue(info.result);
}
// Async result can only be retrieved once
info.asyncResultPending = false;
info.result = ERROR_KERNEL_NO_ASYNC_OP;
// For sceIoPollAsync, only call the ioListeners.
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoPollAsync(0, id, resAddr.getAddress());
}
}
return 0;
}
public int hleIoOpen(PspString filename, int flags, int permissions, boolean async) {
return hleIoOpen(filename.getAddress(), filename.getString(), flags, permissions, async);
}
public int hleIoOpen(int filename_addr, String filename, int flags, int permissions, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
if (log.isInfoEnabled()) {
log.info("hleIoOpen filename = " + filename + " flags = " + Integer.toHexString(flags) + " permissions = 0" + Integer.toOctalString(permissions));
}
if (log.isDebugEnabled()) {
if ((flags & PSP_O_RDONLY) == PSP_O_RDONLY) {
log.debug("PSP_O_RDONLY");
}
if ((flags & PSP_O_WRONLY) == PSP_O_WRONLY) {
log.debug("PSP_O_WRONLY");
}
if ((flags & PSP_O_NBLOCK) == PSP_O_NBLOCK) {
log.debug("PSP_O_NBLOCK");
}
if ((flags & PSP_O_DIROPEN) == PSP_O_DIROPEN) {
log.debug("PSP_O_DIROPEN");
}
if ((flags & PSP_O_APPEND) == PSP_O_APPEND) {
log.debug("PSP_O_APPEND");
}
if ((flags & PSP_O_CREAT) == PSP_O_CREAT) {
log.debug("PSP_O_CREAT");
}
if ((flags & PSP_O_TRUNC) == PSP_O_TRUNC) {
log.debug("PSP_O_TRUNC");
}
if ((flags & PSP_O_EXCL) == PSP_O_EXCL) {
log.debug("PSP_O_EXCL");
}
if ((flags & PSP_O_NBUF) == PSP_O_NBUF) {
log.debug("PSP_O_NBUF");
}
if ((flags & PSP_O_NOWAIT) == PSP_O_NOWAIT) {
log.debug("PSP_O_NOWAIT");
}
if ((flags & PSP_O_PLOCK) == PSP_O_PLOCK) {
log.debug("PSP_O_PLOCK");
}
if ((flags & PSP_O_FGAMEDATA) == PSP_O_FGAMEDATA) {
log.debug("PSP_O_FGAMEDATA");
}
}
String mode = getMode(flags);
if (mode == null) {
log.error("hleIoOpen - unhandled flags " + Integer.toHexString(flags));
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoOpen(-1, filename_addr, filename, flags, permissions, mode);
}
return -1;
}
//Retry count.
int retry = (flags >> 16) & 0x000F;
if (retry != 0) {
log.info("hleIoOpen - retry count is " + retry);
}
if ((flags & PSP_O_RDONLY) == PSP_O_RDONLY &&
(flags & PSP_O_APPEND) == PSP_O_APPEND) {
log.warn("hleIoOpen - read and append flags both set!");
}
IoInfo info = null;
int result;
try {
String pcfilename = getDeviceFilePath(filename);
String absoluteFileName = getAbsoluteFileName(filename);
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
timings = vfs.getTimings();
IVirtualFile vFile = vfs.ioOpen(localFileName.toString(), flags, permissions);
if (vFile == null) {
result = ERROR_ERRNO_FILE_NOT_FOUND;
} else {
info = new IoInfo(filename, vFile, mode, flags, permissions);
info.sectorBlockMode = vFile.isSectorBlockMode();
info.result = ERROR_KERNEL_NO_ASYNC_OP;
result = info.id;
if (log.isDebugEnabled()) {
log.debug("hleIoOpen assigned id = 0x" + Integer.toHexString(info.id));
}
}
} else if (useVirtualFileSystem) {
log.error(String.format("hleIoOpen - device not found '%s'", filename));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (pcfilename != null) {
if (log.isDebugEnabled()) {
log.debug("hleIoOpen - opening file " + pcfilename);
}
if (isUmdPath(pcfilename)) {
// Check umd is mounted.
if (iso == null) {
log.error("hleIoOpen - no umd mounted");
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
// Check umd is activated.
} else if (!Modules.sceUmdUserModule.isUmdActivated()) {
log.warn("hleIoOpen - umd mounted but not activated");
result = ERROR_KERNEL_NO_SUCH_DEVICE;
// Check flags are valid.
} else if ((flags & PSP_O_WRONLY) == PSP_O_WRONLY ||
(flags & PSP_O_CREAT) == PSP_O_CREAT ||
(flags & PSP_O_TRUNC) == PSP_O_TRUNC) {
log.error("hleIoOpen - refusing to open umd media for write");
result = ERROR_ERRNO_READ_ONLY;
} else {
// Open file.
try {
String trimmedFileName = trimUmdPrefix(pcfilename);
// Opening an empty file name with no current working directory set
// should return ERROR_ERRNO_FILE_NOT_FOUND
if (trimmedFileName != null && trimmedFileName.length() == 0 && filename.length() == 0) {
throw new FileNotFoundException(filename);
}
UmdIsoFile file = iso.getFile(trimmedFileName);
info = new IoInfo(filename, file, mode, flags, permissions);
if (!info.isValidId()) {
// Too many open files...
log.warn(String.format("hleIoOpen - too many open files"));
result = ERROR_KERNEL_TOO_MANY_OPEN_FILES;
// Return immediately the error, even in async mode
async = false;
} else {
if (trimmedFileName != null && trimmedFileName.length() == 0) {
// Opening "umd0:" is allowing to read the whole UMD per sectors.
info.sectorBlockMode = true;
}
info.result = ERROR_KERNEL_NO_ASYNC_OP;
result = info.id;
if (log.isDebugEnabled()) {
log.debug("hleIoOpen assigned id = 0x" + Integer.toHexString(info.id));
}
}
} catch (FileNotFoundException e) {
if (log.isDebugEnabled()) {
log.debug("hleIoOpen - umd file not found (ok to ignore this message, debug purpose only)");
}
result = ERROR_ERRNO_FILE_NOT_FOUND;
} catch (IOException e) {
log.error("hleIoOpen - error opening umd media: " + e.getMessage());
result = -1;
}
}
} else {
// First check if the file already exists
File file = new File(pcfilename);
if (file.exists() && (flags & PSP_O_CREAT) == PSP_O_CREAT &&
(flags & PSP_O_EXCL) == PSP_O_EXCL) {
if (log.isDebugEnabled()) {
log.debug("hleIoOpen - file already exists (PSP_O_CREAT + PSP_O_EXCL)");
}
result = ERROR_ERRNO_FILE_ALREADY_EXISTS;
} else {
// When PSP_O_CREAT is specified, create the parent directories
// if they do not yet exist.
if (!file.exists() && ((flags & PSP_O_CREAT) == PSP_O_CREAT)) {
String parentDir = new File(pcfilename).getParent();
new File(parentDir).mkdirs();
}
SeekableRandomFile raf = new SeekableRandomFile(pcfilename, mode);
info = new IoInfo(filename, raf, mode, flags, permissions);
if ((flags & PSP_O_WRONLY) == PSP_O_WRONLY &&
(flags & PSP_O_TRUNC) == PSP_O_TRUNC) {
// When writing, PSP_O_TRUNC truncates the file at the position of the first write.
// E.g.:
// open(PSP_O_TRUNC)
// seek(0x1000)
// write() -> truncates the file at the position 0x1000 before writing
info.setTruncateAtNextWrite(true);
}
info.result = ERROR_KERNEL_NO_ASYNC_OP; // sceIoOpenAsync will set this properly
result = info.id;
if (log.isDebugEnabled()) {
log.debug("hleIoOpen assigned id = 0x" + Integer.toHexString(info.id));
}
}
}
} else {
result = -1;
}
} catch (FileNotFoundException e) {
// To be expected under mode="r" and file doesn't exist
if (log.isDebugEnabled()) {
log.debug("hleIoOpen - file not found (ok to ignore this message, debug purpose only)");
}
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
if (result == ERROR_ERRNO_FILE_NOT_FOUND && filepath.equals("disc0/") && !filename.contains(":") && !filename.contains("/")) {
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoOpen - no current directory set filepath='%s', filename='%s'", filepath, filename));
}
result = ERROR_KERNEL_NOCWD;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoOpen(result, filename_addr, filename, flags, permissions, mode);
}
if (async) {
int realResult = result;
if (info == null) {
log.debug("sceIoOpenAsync - file not found (ok to ignore this message, debug purpose only)");
// For async we still need to make and return a file handle even if we couldn't open the file,
// this is so the game can query on the handle (wait/async stat/io callback).
info = new IoInfo(readStringZ(filename_addr), (SeekableDataInput) null, null, flags, permissions);
result = info.id;
}
int startResult = startIoAsync(info, realResult, timings.get(IoOperation.open), null, 0);
if (startResult < 0) {
result = startResult;
}
} else {
delayIoOperation(timings.get(IoOperation.open));
}
return result;
}
private int hleIoClose(int id, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
int result;
IoInfo info = fileIds.get(id);
if (id == STDIN_ID || id == STDOUT_ID || id == STDERR_ID) {
// Cannot close stdin, stdout, stderr
result = SceKernelErrors.ERROR_KERNEL_ILLEGAL_PERMISSION;
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoClose id=0x%X returning ERROR_KERNEL_ILLEGAL_PERMISSION(0x%08X)", id, result));
}
} else if (async) {
if (info != null) {
if (info.asyncPending || info.asyncResultPending) {
result = ERROR_KERNEL_ASYNC_BUSY;
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoClose id=0x%X returning ERROR_KERNEL_ASYNC_BUSY(0x%08X)", id, result));
}
} else {
info.closePending = true;
result = (int) updateResult(info, 0, true, false, timings.get(IoOperation.close));
}
} else {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoClose id=0x%X returning ERROR_KERNEL_BAD_FILE_DESCRIPTOR(0x%08X)", id, result));
}
}
} else {
try {
if (info == null) {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoClose id=0x%X returning ERROR_KERNEL_BAD_FILE_DESCRIPTOR(0x%08X)", id, result));
}
} else if (info.asyncPending || info.asyncResultPending) {
// Cannot close while an async operation is running
result = ERROR_KERNEL_ASYNC_BUSY;
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoClose id=0x%X returning ERROR_KERNEL_ASYNC_BUSY(0x%08X)", id, result));
}
} else {
if (info.vFile != null) {
timings = info.vFile.getTimings();
info.vFile.ioClose();
} else if (info.readOnlyFile != null) {
// Can be just closing an empty handle, because hleIoOpen(async==true)
// generates a dummy IoInfo when the file could not be opened.
info.readOnlyFile.close();
}
info.close();
triggerAsyncThread(info);
info.result = 0;
result = 0;
}
} catch (IOException e) {
log.error("pspiofilemgr - error closing file: " + e.getMessage());
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoClose(result, id);
}
}
if (!async) {
delayIoOperation(timings.get(IoOperation.close));
}
return result;
}
private int hleIoWrite(int id, TPointer dataAddr, int size, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoInfo info = null;
int result;
if (id == STDOUT_ID) {
// stdout
String message = Utilities.stripNL(readStringNZ(dataAddr.getAddress(), size));
stdout.info(message);
if (stdRedirects[id] != null) {
Managers.msgPipes.hleKernelSendMsgPipe(stdRedirects[id].uid, dataAddr, size, MsgPipeManager.PSP_MPP_WAIT_MODE_COMPLETE, TPointer32.NULL, TPointer32.NULL, false, false);
}
result = size;
} else if (id == STDERR_ID) {
// stderr
String message = Utilities.stripNL(readStringNZ(dataAddr.getAddress(), size));
stderr.info(message);
result = size;
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoWrite(id=0x%X, data=%s, size=0x%X) async=%b", id, dataAddr, size, async));
if (log.isTraceEnabled()) {
log.trace(String.format("hleIoWrite: %s", Utilities.getMemoryDump(dataAddr.getAddress(), Math.min(size, 32))));
}
}
try {
info = fileIds.get(id);
if (info == null) {
log.warn("hleIoWrite - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.asyncPending || info.asyncResultPending) {
log.warn("hleIoWrite - id " + Integer.toHexString(id) + " PSP_ERROR_ASYNC_BUSY");
result = ERROR_KERNEL_ASYNC_BUSY;
} else if ((dataAddr.getAddress() < MemoryMap.START_RAM) && (dataAddr.getAddress() + size > MemoryMap.END_RAM)) {
log.warn("hleIoWrite - id " + Integer.toHexString(id) + " data is outside of ram 0x" + Integer.toHexString(dataAddr.getAddress()) + " - 0x" + Integer.toHexString(dataAddr.getAddress() + size));
result = -1;
} else if ((info.flags & PSP_O_RDWR) == PSP_O_RDONLY) {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.vFile != null) {
timings = info.vFile.getTimings();
if ((info.flags & PSP_O_APPEND) == PSP_O_APPEND) {
info.vFile.ioLseek(info.vFile.length());
info.position = info.vFile.length();
}
if (info.position > info.vFile.length()) {
int towrite = (int) (info.position - info.vFile.length());
info.vFile.ioLseek(info.vFile.length());
while (towrite > 0) {
result = info.vFile.ioWrite(dataAddr, 1);
if (result < 0) {
break;
}
towrite -= result;
}
}
result = info.vFile.ioWrite(dataAddr, size);
if (result > 0) {
info.position += result;
}
} else {
if ((info.flags & PSP_O_APPEND) == PSP_O_APPEND) {
info.msFile.seek(info.msFile.length());
info.position = info.msFile.length();
}
if (info.position > info.readOnlyFile.length()) {
byte[] junk = new byte[512];
int towrite = (int) (info.position - info.readOnlyFile.length());
info.msFile.seek(info.msFile.length());
while (towrite >= 512) {
info.msFile.write(junk, 0, 512);
towrite -= 512;
}
if (towrite > 0) {
info.msFile.write(junk, 0, towrite);
}
}
if (info.isTruncateAtNextWrite()) {
// The file was open with PSP_O_TRUNC: truncate the file at the first write
if (info.position < info.readOnlyFile.length()) {
info.truncate((int) info.position);
}
info.setTruncateAtNextWrite(false);
}
info.position += size;
Utilities.write(info.msFile, dataAddr.getAddress(), size);
result = size;
}
} catch (IOException e) {
e.printStackTrace();
result = -1;
}
}
result = (int) updateResult(info, result, async, false, timings.get(IoOperation.write), null, size);
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoWrite(result, id, dataAddr.getAddress(), size, size);
}
if (!async) {
// Do not delay output on stdout/stderr
if (id != STDOUT_ID && id != STDERR_ID) {
delayIoOperation(timings.get(IoOperation.write));
}
}
return result;
}
public int hleIoRead(int id, int data_addr, int size, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoInfo info = null;
int result;
long position = 0;
SeekableDataInput dataInput = null;
IVirtualFile vFile = null;
int requestedSize = size;
IAction asyncAction = null;
if (id == STDIN_ID) { // stdin
log.warn("UNIMPLEMENTED:hleIoRead id = stdin");
result = 0;
} else {
try {
info = fileIds.get(id);
if (info == null) {
log.warn("hleIoRead - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.asyncPending || info.asyncResultPending) {
log.warn("hleIoRead - id " + Integer.toHexString(id) + " PSP_ERROR_ASYNC_BUSY");
result = ERROR_KERNEL_ASYNC_BUSY;
} else if ((data_addr < MemoryMap.START_RAM) && (data_addr + size > MemoryMap.END_RAM)) {
log.warn("hleIoRead - id " + Integer.toHexString(id) + " data is outside of ram 0x" + Integer.toHexString(data_addr) + " - 0x" + Integer.toHexString(data_addr + size));
result = ERROR_KERNEL_FILE_READ_ERROR;
} else if ((info.flags & PSP_O_RDWR) == PSP_O_WRONLY) {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.vFile != null) {
timings = info.vFile.getTimings();
if (info.sectorBlockMode) {
// In sectorBlockMode, the size is a number of sectors
size *= UmdIsoFile.sectorLength;
}
// Using readFully for ms/umd compatibility, but now we must
// manually make sure it doesn't read off the end of the file.
if (info.position + size > info.vFile.length()) {
int oldSize = size;
size = (int) (info.vFile.length() - info.position);
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoRead - clamping size old=%d, new=%d, position=%d, len=%d", oldSize, size, info.position, info.vFile.length()));
}
}
if (async) {
// Execute the read operation in the IO async thread
asyncAction = new IOAsyncReadAction(info, data_addr, requestedSize, size);
result = 0;
} else {
position = info.position;
vFile = info.vFile;
result = info.vFile.ioRead(new TPointer(Memory.getInstance(), data_addr), size);
if (result >= 0) {
size = result;
info.position += result;
} else {
size = 0;
}
if (log.isTraceEnabled()) {
log.trace(String.format("hleIoRead: %s", Utilities.getMemoryDump(data_addr, Math.min(16, size))));
}
// Invalidate any compiled code in the read range
RuntimeContext.invalidateRange(data_addr, size);
if (info.sectorBlockMode && result > 0) {
result /= UmdIsoFile.sectorLength;
}
}
} else if ((info.readOnlyFile == null) || (info.position >= info.readOnlyFile.length())) {
// Ignore empty handles and allow seeking off the end of the file, just return 0 bytes read/written.
result = 0;
} else {
if (info.sectorBlockMode) {
// In sectorBlockMode, the size is a number of sectors
size *= UmdIsoFile.sectorLength;
}
// Using readFully for ms/umd compatibility, but now we must
// manually make sure it doesn't read off the end of the file.
if (info.readOnlyFile.getFilePointer() + size > info.readOnlyFile.length()) {
int oldSize = size;
size = (int) (info.readOnlyFile.length() - info.readOnlyFile.getFilePointer());
if (log.isDebugEnabled()) {
log.debug("hleIoRead - clamping size old=" + oldSize + " new=" + size + " fp=" + info.readOnlyFile.getFilePointer() + " len=" + info.readOnlyFile.length());
}
}
if (async) {
// Execute the read operation in the IO async thread
asyncAction = new IOAsyncReadAction(info, data_addr, requestedSize, size);
result = 0;
} else {
position = info.position;
dataInput = info.readOnlyFile;
Utilities.readFully(info.readOnlyFile, data_addr, size);
info.position += size;
result = size;
if (log.isTraceEnabled()) {
log.trace(String.format("hleIoRead: %s", Utilities.getMemoryDump(data_addr, Math.min(16, size))));
}
// Invalidate any compiled code in the read range
RuntimeContext.invalidateRange(data_addr, size);
if (info.sectorBlockMode) {
result /= UmdIsoFile.sectorLength;
}
}
}
} catch (IOException e) {
log.error("hleIoRead", e);
result = ERROR_KERNEL_FILE_READ_ERROR;
} catch (Exception e) {
log.error("hleIoRead", e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
}
result = (int) updateResult(info, result, async, false, timings.get(IoOperation.read), asyncAction, size);
// Call the IO listeners (performed in the async action if one is provided, otherwise call them here)
if (asyncAction == null) {
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoRead(result, id, data_addr, requestedSize, size, position, dataInput, vFile);
}
}
if (!async) {
if (size > 0x100) {
delayIoOperation(timings.get(IoOperation.read));
}
}
return result;
}
private long hleIoLseek(int id, long offset, int whence, boolean resultIs64bit, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoInfo info = null;
long result;
if (id == STDOUT_ID || id == STDERR_ID || id == STDIN_ID) { // stdio
log.error("seek - can't seek on stdio id " + Integer.toHexString(id));
result = -1;
} else {
try {
info = fileIds.get(id);
if (info == null) {
log.warn("seek - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.asyncPending || info.asyncResultPending) {
log.warn("seek - id " + Integer.toHexString(id) + " PSP_ERROR_ASYNC_BUSY");
result = ERROR_KERNEL_ASYNC_BUSY;
} else if (info.vFile != null) {
timings = info.vFile.getTimings();
if (info.sectorBlockMode) {
// In sectorBlockMode, the offset is a sector number
offset *= UmdIsoFile.sectorLength;
}
long newPosition;
switch (whence) {
case PSP_SEEK_SET:
newPosition = offset;
break;
case PSP_SEEK_CUR:
newPosition = info.position + offset;
break;
case PSP_SEEK_END:
newPosition = info.vFile.length() + offset;
break;
default:
log.error(String.format("seek - unhandled whence %d", whence));
// Force an invalid argument error
newPosition = -1;
break;
}
if (newPosition >= 0) {
info.position = newPosition;
if (info.position <= info.vFile.length()) {
info.vFile.ioLseek(info.position);
}
result = info.position;
if (info.sectorBlockMode) {
result /= UmdIsoFile.sectorLength;
}
} else {
// PSP returns -1 for this case
result = -1;
}
} else if (info.readOnlyFile == null) {
// Ignore empty handles.
result = 0;
} else {
if (info.sectorBlockMode) {
// In sectorBlockMode, the offset is a sector number
offset *= UmdIsoFile.sectorLength;
}
switch (whence) {
case PSP_SEEK_SET:
if (offset < 0) {
log.warn("SEEK_SET id " + Integer.toHexString(id) + " filename:'" + info.filename + "' offset=0x" + Long.toHexString(offset) + " (less than 0!)");
// PSP returns -1 for this case
result = -1;
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSeek64(ERROR_INVALID_ARGUMENT, id, offset, whence);
}
return result;
}
info.position = offset;
if (offset <= info.readOnlyFile.length()) {
info.readOnlyFile.seek(offset);
}
break;
case PSP_SEEK_CUR:
if (info.position + offset < 0) {
log.warn("SEEK_CUR id " + Integer.toHexString(id) + " filename:'" + info.filename + "' newposition=0x" + Long.toHexString(info.position + offset) + " (less than 0!)");
// PSP returns -1 for this case
result = -1;
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSeek64(ERROR_INVALID_ARGUMENT, id, offset, whence);
}
return result;
}
info.position += offset;
if (info.position <= info.readOnlyFile.length()) {
info.readOnlyFile.seek(info.position);
}
break;
case PSP_SEEK_END:
if (info.readOnlyFile.length() + offset < 0) {
log.warn("SEEK_END id " + Integer.toHexString(id) + " filename:'" + info.filename + "' newposition=0x" + Long.toHexString(info.position + offset) + " (less than 0!)");
// PSP returns -1 for this case
result = -1;
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSeek64(ERROR_INVALID_ARGUMENT, id, offset, whence);
}
return result;
}
info.position = info.readOnlyFile.length() + offset;
if (info.position <= info.readOnlyFile.length()) {
info.readOnlyFile.seek(info.position);
}
break;
default:
log.error("seek - unhandled whence " + whence);
break;
}
result = info.position;
if (info.sectorBlockMode) {
result /= UmdIsoFile.sectorLength;
}
}
} catch (IOException e) {
e.printStackTrace();
result = -1;
}
}
result = updateResult(info, result, async, resultIs64bit, timings.get(IoOperation.seek));
if (resultIs64bit) {
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSeek64(result, id, offset, whence);
}
} else {
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSeek32((int) result, id, (int) offset, whence);
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoLseek returning 0x%X", result));
}
if (!async) {
delayIoOperation(timings.get(IoOperation.seek));
}
return result;
}
public int hleIoIoctl(int id, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen, boolean async) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoInfo info = null;
int result;
Memory mem = Memory.getInstance();
boolean needDelayIoOperation = true;
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoIoctl(id=%x, cmd=0x%08X, indata=0x%08X, inlen=%d, outdata=0x%08X, outlen=%d, async=%b", id, cmd, indata_addr, inlen, outdata_addr, outlen, async));
if (Memory.isAddressGood(indata_addr)) {
for (int i = 0; i < inlen; i += 4) {
log.debug(String.format("hleIoIoctl indata[%d]=0x%08X", i / 4, mem.read32(indata_addr + i)));
}
}
if (Memory.isAddressGood(outdata_addr)) {
for (int i = 0; i < Math.min(outlen, 256); i += 4) {
log.debug(String.format("hleIoIoctl outdata[%d]=0x%08X", i / 4, mem.read32(outdata_addr + i)));
}
}
}
info = fileIds.get(id);
if (info == null) {
IoDirInfo dirInfo = dirIds.get(id);
if (dirInfo != null) {
switch (cmd) {
// Set sceIoDread file name filter
case 0x02415050:
if (inlen == 4) {
int fileNameFilterAddr = mem.read32(indata_addr);
dirInfo.fileNameFilter = Utilities.readStringZ(mem, fileNameFilterAddr);
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoIoctl settings sceIoDread file name filter '%s'", dirInfo.fileNameFilter));
}
result = 0;
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X 0x%08X %d unsupported parameters", cmd, indata_addr, inlen));
result = ERROR_INVALID_ARGUMENT;
}
break;
default:
result = -1;
log.warn(String.format("hleIoIoctl 0x%08X unknown command on IoDirInfo, inlen=%d, outlen=%d", cmd, inlen, outlen));
if (Memory.isAddressGood(indata_addr)) {
for (int i = 0; i < inlen; i += 4) {
log.warn(String.format("hleIoIoctl indata[%d]=0x%08X", i / 4, mem.read32(indata_addr + i)));
}
}
if (Memory.isAddressGood(outdata_addr)) {
for (int i = 0; i < Math.min(outlen, 256); i += 4) {
log.warn(String.format("hleIoIoctl outdata[%d]=0x%08X", i / 4, mem.read32(outdata_addr + i)));
}
}
break;
}
} else {
log.warn(String.format("hleIoIoctl - unknown id 0x%X", id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
} else if (info.asyncPending || info.asyncResultPending) {
// Can't execute another operation until the previous one completed
log.warn(String.format("hleIoIoctl - id 0x%X PSP_ERROR_ASYNC_BUSY", id));
result = ERROR_KERNEL_ASYNC_BUSY;
} else if (info.vFile != null && cmd != 0x04100001) {
timings = info.vFile.getTimings();
result = info.vFile.ioIoctl(cmd, new TPointer(mem, indata_addr), inlen, new TPointer(mem, outdata_addr), outlen);
} else {
switch (cmd) {
// UMD file seek set.
case 0x01010005: {
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
if (info.isUmdFile()) {
try {
int offset = mem.read32(indata_addr);
log.debug("hleIoIoctl umd file seek set " + offset);
info.readOnlyFile.seek(offset);
info.position = offset;
result = 0;
} catch (IOException e) {
// Should never happen?
log.warn("hleIoIoctl cmd=0x01010005 exception: " + e.getMessage());
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn("hleIoIoctl cmd=0x01010005 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01010005 " + String.format("0x%08X %d", indata_addr, inlen) + " unsupported parameters");
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// UMD file ahead (from info listed in the log file of "The Legend of Heroes: Trails in the Sky SC")
case 0x0101000A: {
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
int length = mem.read32(indata_addr);
if (log.isInfoEnabled()) {
log.info(String.format("hleIoIoctl cmd=0x%08X length=0x%X", cmd, length));
}
result = 0;
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X 0x%08X %d unsupported parameters", cmd, indata_addr, inlen));
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// Get UMD Primary Volume Descriptor
case 0x01020001: {
if (Memory.isAddressGood(outdata_addr) && outlen == UmdIsoFile.sectorLength) {
if (info.isUmdFile() && iso != null) {
try {
byte[] primaryVolumeSector = iso.readSector(UmdIsoReader.startSector);
IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(outdata_addr, outlen, 1);
for (int i = 0; i < outlen; i++) {
memoryWriter.writeNext(primaryVolumeSector[i] & 0xFF);
}
memoryWriter.flush();
result = 0;
} catch (IOException e) {
log.error(e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn("hleIoIoctl cmd=0x01020001 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020001 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// Get UMD Path Table
case 0x01020002: {
if (Memory.isAddressGood(outdata_addr) && outlen <= UmdIsoFile.sectorLength) {
if (info.isUmdFile() && iso != null) {
try {
byte[] primaryVolumeSector = iso.readSector(UmdIsoReader.startSector);
ByteBuffer primaryVolume = ByteBuffer.wrap(primaryVolumeSector);
primaryVolume.position(140);
int pathTableLocation = Utilities.readWord(primaryVolume);
byte[] pathTableSector = iso.readSector(pathTableLocation);
IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(outdata_addr, outlen, 1);
for (int i = 0; i < outlen; i++) {
memoryWriter.writeNext(pathTableSector[i] & 0xFF);
}
memoryWriter.flush();
result = 0;
} catch (IOException e) {
log.error(e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn("hleIoIoctl cmd=0x01020002 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020002 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// Get Sector size
case 0x01020003: {
if (Memory.isAddressGood(outdata_addr) && outlen == 4) {
if (info.isUmdFile() && iso != null) {
mem.write32(outdata_addr, UmdIsoFile.sectorLength);
result = 0;
} else {
log.warn("hleIoIoctl cmd=0x01020003 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020003 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
needDelayIoOperation = false;
break;
}
// Get UMD file pointer.
case 0x01020004: {
if (Memory.isAddressGood(outdata_addr) && outlen >= 4) {
if (info.isUmdFile()) {
try {
int fPointer = (int) info.readOnlyFile.getFilePointer();
// TODO for block files, does it return a number of blocks or a number of bytes?
mem.write32(outdata_addr, fPointer);
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoIoctl umd file get file pointer 0x%X", fPointer));
}
result = 0;
} catch (IOException e) {
log.warn("hleIoIoctl cmd=0x01020004 exception: " + e.getMessage());
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn("hleIoIoctl cmd=0x01020004 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020004 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// Get UMD file start sector.
case 0x01020006: {
if (Memory.isAddressGood(outdata_addr) && outlen >= 4) {
int startSector = 0;
if (info.isUmdFile() && info.readOnlyFile instanceof UmdIsoFile) {
UmdIsoFile file = (UmdIsoFile) info.readOnlyFile;
startSector = file.getStartSector();
log.debug("hleIoIoctl umd file get start sector " + startSector);
mem.write32(outdata_addr, startSector);
result = 0;
} else {
log.warn("hleIoIoctl cmd=0x01020006 only allowed on UMD files and only implemented for UmdIsoFile");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020006 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// Get UMD file length in bytes.
case 0x01020007: {
if (Memory.isAddressGood(outdata_addr) && outlen >= 8) {
if (info.isUmdFile()) {
try {
long length = info.readOnlyFile.length();
mem.write64(outdata_addr, length);
log.debug("hleIoIoctl get file size " + length);
result = 0;
} catch (IOException e) {
// Should never happen?
log.warn("hleIoIoctl cmd=0x01020007 exception: " + e.getMessage());
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn("hleIoIoctl cmd=0x01020007 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01020007 " + String.format("0x%08X %d", outdata_addr, outlen) + " unsupported parameters");
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// Read UMD file.
case 0x01030008: {
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
int length = mem.read32(indata_addr);
if (length > 0) {
if (Memory.isAddressGood(outdata_addr) && outlen >= length) {
try {
Utilities.readFully(info.readOnlyFile, outdata_addr, length);
info.position += length;
result = length;
} catch (IOException e) {
log.error(e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X inlen=%d unsupported output parameters 0x%08X %d", cmd, inlen, outdata_addr, outlen));
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X unsupported input parameters 0x%08X %d, length=%d", cmd, indata_addr, inlen, length));
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X unsupported input parameters 0x%08X %d", cmd, indata_addr, inlen));
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// UMD disc read sectors operation.
case 0x01F30003: {
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
int numberOfSectors = mem.read32(indata_addr);
if (numberOfSectors > 0) {
if (Memory.isAddressGood(outdata_addr) && outlen >= numberOfSectors) {
try {
int length = numberOfSectors * UmdIsoFile.sectorLength;
Utilities.readFully(info.readOnlyFile, outdata_addr, length);
info.position += length;
result = length / UmdIsoFile.sectorLength;
} catch (IOException e) {
log.error(e);
result = ERROR_KERNEL_FILE_READ_ERROR;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X inlen=%d unsupported output parameters 0x%08X %d", cmd, inlen, outdata_addr, outlen));
result = ERROR_ERRNO_INVALID_ARGUMENT;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X unsupported input parameters 0x%08X %d numberOfSectors=%d", cmd, indata_addr, inlen, numberOfSectors));
result = ERROR_ERRNO_INVALID_ARGUMENT;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x%08X unsupported input parameters 0x%08X %d", cmd, indata_addr, inlen));
result = ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// UMD file seek whence.
case 0x01F100A6: {
if (Memory.isAddressGood(indata_addr) && inlen >= 16) {
if (info.isUmdFile()) {
try {
long offset = mem.read64(indata_addr);
int whence = mem.read32(indata_addr + 12);
if (info.sectorBlockMode) {
offset *= UmdIsoFile.sectorLength;
}
if (log.isDebugEnabled()) {
log.debug("hleIoIoctl UMD file seek offset " + offset + ", whence " + whence);
}
switch (whence) {
case PSP_SEEK_SET: {
info.position = offset;
info.readOnlyFile.seek(info.position);
result = 0;
break;
}
case PSP_SEEK_CUR: {
info.position = info.position + offset;
info.readOnlyFile.seek(info.position);
result = 0;
break;
}
case PSP_SEEK_END: {
info.position = info.readOnlyFile.length() + offset;
info.readOnlyFile.seek(info.position);
result = 0;
break;
}
default: {
log.error("hleIoIoctl - unhandled whence " + whence);
result = -1;
break;
}
}
} catch (IOException e) {
// Should never happen?
log.warn("hleIoIoctl cmd=0x01F100A6 exception: " + e.getMessage());
result = -1;
}
} else {
log.warn("hleIoIoctl cmd=0x01F100A6 only allowed on UMD files");
result = ERROR_INVALID_ARGUMENT;
}
} else {
log.warn("hleIoIoctl cmd=0x01F100A6 " + String.format("0x%08X %d", indata_addr, inlen) + " unsupported parameters");
result = ERROR_INVALID_ARGUMENT;
}
break;
}
// Define decryption key (DRM by amctrl.prx).
case 0x04100001: {
if (Memory.isAddressGood(indata_addr) && inlen == 16) {
// Store the key.
byte[] keyBuf = new byte[0x10];
StringBuilder keyHex = new StringBuilder();
for (int i = 0; i < 0x10; i++) {
keyBuf[i] = (byte) mem.read8(indata_addr + i);
keyHex.append(String.format("%02X", keyBuf[i] & 0xFF));
}
if (log.isDebugEnabled()) {
log.debug(String.format("hleIoIoctl get AES key %s", keyHex.toString()));
}
IVirtualFile ioctlFile = null;
if (info.readOnlyFile instanceof UmdIsoFile) {
ioctlFile = new UmdIsoVirtualFile((UmdIsoFile) info.readOnlyFile);
}
PGDVirtualFile pgdFile = new PGDVirtualFile(keyBuf, new SeekableDataInputVirtualFile(info.readOnlyFile, ioctlFile));
if (!pgdFile.isHeaderPresent()) {
// No "PGD" found in the header, leave the file unchanged
result = 0;
} else if (pgdFile.isValid()) {
info.vFile = pgdFile;
result = 0;
} else {
result = SceKernelErrors.ERROR_PGD_INVALID_HEADER;
}
} else {
log.warn(String.format("hleIoIoctl cmd=0x04100001 indata=0x%08X inlen=%d unsupported parameters", indata_addr, inlen));
result = ERROR_INVALID_ARGUMENT;
}
break;
}
default: {
result = -1;
log.warn(String.format("hleIoIoctl 0x%08X unknown command, inlen=%d, outlen=%d", cmd, inlen, outlen));
if (Memory.isAddressGood(indata_addr)) {
for (int i = 0; i < inlen; i += 4) {
log.warn(String.format("hleIoIoctl indata[%d]=0x%08X", i / 4, mem.read32(indata_addr + i)));
}
}
if (Memory.isAddressGood(outdata_addr)) {
for (int i = 0; i < Math.min(outlen, 256); i += 4) {
log.warn(String.format("hleIoIoctl outdata[%d]=0x%08X", i / 4, mem.read32(outdata_addr + i)));
}
}
break;
}
}
}
result = (int) updateResult(info, result, async, false, timings.get(IoOperation.ioctl));
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoIoctl(result, id, cmd, indata_addr, inlen, outdata_addr, outlen);
}
if (needDelayIoOperation && !async) {
delayIoOperation(timings.get(IoOperation.ioctl));
}
return result;
}
public void hleRegisterStdPipe(int id, SceKernelMppInfo msgPipeInfo) {
if (id < 0 || id >= stdRedirects.length) {
return;
}
stdRedirects[id] = msgPipeInfo;
}
public void hleEjectMemoryStick() {
if (MemoryStick.isInserted()) {
previousFatMsState = MemoryStick.getStateFatMs();
MemoryStick.setStateFatMs(MemoryStick.PSP_FAT_MEMORYSTICK_STATE_REMOVED);
Modules.ThreadManForUserModule.hleKernelNotifyCallback(THREAD_CALLBACK_MEMORYSTICK_FAT, -1, MemoryStick.getStateFatMs());
Emulator.getMainGUI().onMemoryStickChange();
}
}
public void hleInsertMemoryStick() {
if (!MemoryStick.isInserted()) {
MemoryStick.setStateFatMs(previousFatMsState);
Modules.ThreadManForUserModule.hleKernelNotifyCallback(THREAD_CALLBACK_MEMORYSTICK_FAT, -1, MemoryStick.getStateFatMs());
Emulator.getMainGUI().onMemoryStickChange();
}
}
private int hleIoRename(int oldFileNameAddr, String oldFileName, int newFileNameAddr, String newFileName) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
// The new file name can omit the file directory, in which case the directory
// of the old file name is used.
// I.e., when renaming "ms0:/PSP/SAVEDATA/xxxx" into "yyyy",
// actually rename into "ms0:/PSP/SAVEDATA/yyyy".
if (!newFileName.contains("/")) {
int prefixOffset = oldFileName.lastIndexOf("/");
if (prefixOffset >= 0) {
newFileName = oldFileName.substring(0, prefixOffset + 1) + newFileName;
}
}
String oldpcfilename = getDeviceFilePath(oldFileName);
String newpcfilename = getDeviceFilePath(newFileName);
int result;
String absoluteOldFileName = getAbsoluteFileName(oldFileName);
StringBuilder localOldFileName = new StringBuilder();
IVirtualFileSystem oldVfs = vfsManager.getVirtualFileSystem(absoluteOldFileName, localOldFileName);
if (oldVfs != null) {
String absoluteNewFileName = getAbsoluteFileName(newFileName);
StringBuilder localNewFileName = new StringBuilder();
IVirtualFileSystem newVfs = vfsManager.getVirtualFileSystem(absoluteNewFileName, localNewFileName);
if (oldVfs != newVfs) {
log.error(String.format("sceIoRename - renaming across devices not allowed '%s' - '%s'", oldFileName, newFileName));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else {
timings = oldVfs.getTimings();
result = oldVfs.ioRename(localOldFileName.toString(), localNewFileName.toString());
}
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoRename - device not found '%s'", oldFileName));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (oldpcfilename != null) {
if (isUmdPath(oldpcfilename)) {
result = -1;
} else {
File file = new File(oldpcfilename);
File newfile = new File(newpcfilename);
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoRename: renaming file '%s' to '%s'", oldpcfilename, newpcfilename));
}
if (file.renameTo(newfile)) {
result = 0;
} else {
log.warn(String.format("sceIoRename failed: %s(%s) to %s(%s)", oldFileName, oldpcfilename, newFileName, newpcfilename));
if (file.exists()) {
result = -1;
} else {
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
}
}
} else {
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoRename(result, oldFileNameAddr, oldFileName, newFileNameAddr, newFileName);
}
delayIoOperation(timings.get(IoOperation.rename));
return result;
}
public int hleIoGetstat(int filenameAddr, String filename, TPointer statAddr) {
int result;
SceIoStat stat = null;
String absoluteFileName = getAbsoluteFileName(filename);
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
stat = new SceIoStat();
result = vfs.ioGetstat(localFileName.toString(), stat);
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoGetstat - device not found '%s'", filename));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else {
String pcfilename = getDeviceFilePath(filename);
stat = stat(pcfilename);
result = (stat != null) ? 0 : ERROR_ERRNO_FILE_NOT_FOUND;
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoGetstat returning 0x%08X, %s", result, stat));
}
if (stat != null && result == 0) {
stat.write(statAddr);
}
if (filenameAddr != 0) {
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoGetStat(result, filenameAddr, filename, statAddr.getAddress());
}
}
return result;
}
/**
* sceIoPollAsync
*
* @param id
* @param resAddr
*
* @return
*/
@HLEFunction(nid = 0x3251EA56, version = 150, checkInsideInterrupt = true)
public int sceIoPollAsync(int id, @CanBeNull TPointer64 resAddr) {
return hleIoWaitAsync(id, resAddr, false, false);
}
/**
* sceIoWaitAsync
*
* @param id
* @param resAddr
*
* @return
*/
@HLEFunction(nid = 0xE23EEC33, version = 150, checkInsideInterrupt = true)
public int sceIoWaitAsync(int id, @CanBeNull TPointer64 resAddr) {
return hleIoWaitAsync(id, resAddr, true, false);
}
/**
* sceIoWaitAsyncCB
*
* @param id
* @param resAddr
*
* @return
*/
@HLEFunction(nid = 0x35DBD746, version = 150, checkInsideInterrupt = true)
public int sceIoWaitAsyncCB(int id, @CanBeNull TPointer64 resAddr) {
return hleIoWaitAsync(id, resAddr, true, true);
}
/**
* sceIoGetAsyncStat
*
* @param id
* @param poll
* @param res_addr
*
* @return
*/
@HLEFunction(nid = 0xCB05F8D6, version = 150, checkInsideInterrupt = true)
public int sceIoGetAsyncStat(int id, int poll, @CanBeNull TPointer64 res_addr) {
return hleIoWaitAsync(id, res_addr, (poll == 0), false);
}
/**
* sceIoChangeAsyncPriority
*
* @param id
* @param priority
*
* @return
*/
@HLEFunction(nid = 0xB293727F, version = 150, checkInsideInterrupt = true)
public int sceIoChangeAsyncPriority(int id, int priority) {
if (priority == -1) {
// Take the priority of the thread executing the first async operation,
// do not take the priority of the thread executing sceIoChangeAsyncPriority().
} else if (priority < 0) {
return SceKernelErrors.ERROR_KERNEL_ILLEGAL_PRIORITY;
}
if (id == -1) {
defaultAsyncPriority = priority;
return 0;
}
IoInfo info = fileIds.get(id);
if (info == null) {
log.warn("sceIoChangeAsyncPriority invalid fd=" + id);
return SceKernelErrors.ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
info.asyncThreadPriority = priority;
if (info.asyncThread != null) {
if (priority < 0) {
// If the async thread has already been started,
// change its priority to the priority of the current thread,
// i.e. to the priority of the thread having called sceIoChangeAsyncPriority().
priority = Modules.ThreadManForUserModule.getCurrentThread().currentPriority;
}
Modules.ThreadManForUserModule.hleKernelChangeThreadPriority(info.asyncThread, priority);
}
return 0;
}
/**
* sceIoSetAsyncCallback
*
* @param id
* @param cbid
* @param notifyArg
*
* @return
*/
@HLEFunction(nid = 0xA12A0514, version = 150, checkInsideInterrupt = true)
public int sceIoSetAsyncCallback(int id, int cbid, int notifyArg) {
IoInfo info = fileIds.get(id);
if (info == null) {
log.warn("sceIoSetAsyncCallback - unknown id " + Integer.toHexString(id));
return ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
if (!Modules.ThreadManForUserModule.hleKernelRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_IO, cbid)) {
log.warn("sceIoSetAsyncCallback - not a callback id " + Integer.toHexString(id));
return -1;
}
info.cbid = cbid;
info.notifyArg = notifyArg;
triggerAsyncThread(info);
return 0;
}
/**
* sceIoClose
*
* @param id
*
* @return
*/
@HLEFunction(nid = 0x810C4BC3, version = 150, checkInsideInterrupt = true)
public int sceIoClose(int id) {
return hleIoClose(id, false);
}
/**
* sceIoCloseAsync
*
* @param id
*
* @return
*/
@HLEFunction(nid = 0xFF5940B6, version = 150, checkInsideInterrupt = true)
public int sceIoCloseAsync(int id) {
return hleIoClose(id, true);
}
/**
* sceIoOpen
*
* @param filename
* @param flags
* @param permissions
*
* @return
*/
@HLEFunction(nid = 0x109F50BC, version = 150, checkInsideInterrupt = true)
public int sceIoOpen(PspString filename, int flags, int permissions) {
return hleIoOpen(filename, flags, permissions, /* async = */ false);
}
/**
* sceIoOpenAsync
*
* @param filename
* @param flags
* @param permissions
*
* @return
*/
@HLEFunction(nid = 0x89AA9906, version = 150, checkInsideInterrupt = true)
public int sceIoOpenAsync(PspString filename, int flags, int permissions) {
return hleIoOpen(filename, flags, permissions, /* async = */ true);
}
/**
* sceIoRead
*
* @param id
* @param data_addr
* @param size
*
* @return
*/
@HLEFunction(nid = 0x6A638D83, version = 150, checkInsideInterrupt = true)
public int sceIoRead(int id, TPointer data_addr, int size) {
return hleIoRead(id, data_addr.getAddress(), size, false);
}
/**
* sceIoReadAsync
*
* @param id
* @param data_addr
* @param size
*
* @return
*/
@HLEFunction(nid = 0xA0B5A7C2, version = 150, checkInsideInterrupt = true)
public int sceIoReadAsync(int id, TPointer data_addr, int size) {
return hleIoRead(id, data_addr.getAddress(), size, true);
}
/**
* sceIoWrite
*
* @param id
* @param data_addr
* @param size
*
* @return
*/
@HLEFunction(nid = 0x42EC03AC, version = 150, checkInsideInterrupt = true)
public int sceIoWrite(int id, TPointer dataAddr, int size) {
return hleIoWrite(id, dataAddr, size, false);
}
/**
* sceIoWriteAsync
*
* @param id
* @param data_addr
* @param size
*
* @return
*/
@HLEFunction(nid = 0x0FACAB19, version = 150, checkInsideInterrupt = true)
public int sceIoWriteAsync(int id, TPointer dataAddr, int size) {
return hleIoWrite(id, dataAddr, size, true);
}
/**
* sceIoLseek
*
* @param id
* @param offset
* @param whence
*
* @return
*/
@HLEFunction(nid = 0x27EB27B8, version = 150, checkInsideInterrupt = true)
public long sceIoLseek(int id, long offset, int whence) {
return hleIoLseek(id, offset, whence, true, false);
}
/**
* sceIoLseekAsync
*
* @param id
* @param offset
* @param whence
*
* @return
*/
@HLEFunction(nid = 0x71B19E77, version = 150, checkInsideInterrupt = true)
public int sceIoLseekAsync(int id, long offset, int whence) {
return (int) hleIoLseek(id, offset, whence, true, true);
}
/**
* sceIoLseek32
*
* @param id
* @param offset
* @param whence
*
* @return
*/
@HLEFunction(nid = 0x68963324, version = 150, checkInsideInterrupt = true)
public int sceIoLseek32(int id, int offset, int whence) {
return (int) hleIoLseek(id, (long) offset, whence, false, false);
}
/**
* sceIoLseek32Async
*
* @param id
* @param offset
* @param whence
*/
@HLEFunction(nid = 0x1B385D8F, version = 150, checkInsideInterrupt = true)
public int sceIoLseek32Async(int id, int offset, int whence) {
return (int) hleIoLseek(id, (long) offset, whence, false, true);
}
/**
* sceIoIoctl
*
* @param id
* @param cmd
* @param indata_addr
* @param inlen
* @param outdata_addr
* @param outlen
*
* @return
*/
@HLEFunction(nid = 0x63632449, version = 150, checkInsideInterrupt = true)
public int sceIoIoctl(int id, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen) {
return hleIoIoctl(id, cmd, indata_addr, inlen, outdata_addr, outlen, false);
}
/**
* sceIoIoctlAsync
*
* @param id
* @param cmd
* @param indata_addr
* @param inlen
* @param outdata_addr
* @param outlen
*
* @return
*/
@HLEFunction(nid = 0xE95A012B, version = 150, checkInsideInterrupt = true)
public int sceIoIoctlAsync(int id, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen) {
return hleIoIoctl(id, cmd, indata_addr, inlen, outdata_addr, outlen, true);
}
/**
* Opens a directory for listing.
*
* @param dirname
*
* @return
*/
@HLEFunction(nid = 0xB29DDF9C, version = 150, checkInsideInterrupt = true)
public int sceIoDopen(PspString dirname) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
int result;
String pcfilename = getDeviceFilePath(dirname.getString());
String absoluteFileName = getAbsoluteFileName(dirname.getString());
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
timings = vfs.getTimings();
String[] fileNames = vfs.ioDopen(localFileName.toString());
if (fileNames == null) {
result = ERROR_ERRNO_FILE_NOT_FOUND;
} else {
IoDirInfo info = new IoDirInfo(localFileName.toString(), fileNames, vfs);
result = info.id;
}
} else if (pcfilename != null) {
if (isUmdPath(pcfilename)) {
// Files in our iso virtual file system
String isofilename = trimUmdPrefix(pcfilename);
if (log.isDebugEnabled()) {
log.debug("sceIoDopen - isofilename = " + isofilename);
}
if (iso == null) { // check umd is mounted
log.error("sceIoDopen - no umd mounted");
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (!Modules.sceUmdUserModule.isUmdActivated()) { // check umd is activated
log.warn("sceIoDopen - umd mounted but not activated");
result = ERROR_KERNEL_NO_SUCH_DEVICE;
} else {
try {
if (iso.isDirectory(isofilename)) {
String[] filenames = iso.listDirectory(isofilename);
IoDirInfo info = new IoDirInfo(pcfilename, filenames);
result = info.id;
} else {
log.warn("sceIoDopen '" + isofilename + "' not a umd directory!");
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
} catch (FileNotFoundException e) {
log.warn("sceIoDopen - '" + isofilename + "' umd file not found");
result = ERROR_ERRNO_FILE_NOT_FOUND;
} catch (IOException e) {
log.warn("sceIoDopen - umd io error: " + e.getMessage());
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
}
} else if (dirname.getString().startsWith("/") && dirname.getString().indexOf(":") != -1) {
log.warn("sceIoDopen apps running outside of ms0 dir are not fully supported, relative child paths should still work");
result = -1;
} else {
// Regular apps run from inside mstick dir or absolute path given
if (log.isDebugEnabled()) {
log.debug("sceIoDopen - pcfilename = " + pcfilename);
}
File f = new File(pcfilename);
if (f.isDirectory()) {
String files[] = f.list();
if (files != null) {
for (int i = 0; i < files.length; i++) {
files[i] = getMsFileName(files[i]);
}
}
IoDirInfo info = new IoDirInfo(pcfilename, files);
result = info.id;
} else {
log.warn("sceIoDopen '" + pcfilename + "' not a directory! (could be missing)");
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
}
} else {
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDopen(result, dirname.getAddress(), dirname.getString());
}
delayIoOperation(timings.get(IoOperation.open));
return result;
}
/**
* sceIoDread
*
* @param id
* @param direntAddr
*
* @return
*/
@HLEFunction(nid = 0xE3EB004C, version = 150, checkInsideInterrupt = true)
public int sceIoDread(int id, TPointer direntAddr) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoDirInfo info = dirIds.get(id);
int result;
if (info == null) {
log.warn("sceIoDread unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.hasNext()) {
String filename = info.next();
if (info.fileNameFilter != null) {
// PSP file name pattern:
// '?' matches one character
// '*' matches any character sequence
// To convert to regular expressions:
// replace '?' with '.'
// replace '*' with '.*'
String pattern = info.fileNameFilter.replace('?', '.');
pattern = pattern.replace("*", ".*");
while (!filename.matches(pattern)) {
if (!info.hasNext()) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDread id=0x%X no more files matching pattern '%s'", id, info.fileNameFilter));
}
filename = null;
break;
}
filename = info.next();
}
}
SceIoDirent dirent = null;
if (filename == null) {
result = 0;
} else if (info.vfs != null) {
timings = info.vfs.getTimings();
SceIoStat stat = new SceIoStat();
dirent = new SceIoDirent(stat, filename);
result = info.vfs.ioDread(info.path, dirent);
} else {
SceIoStat stat = stat(info.path + "/" + filename);
if (stat != null) {
dirent = new SceIoDirent(stat, filename);
result = 1;
} else {
log.warn("sceIoDread id=" + Integer.toHexString(id) + " stat failed (" + info.path + "/" + filename + ")");
result = -1;
}
}
if (dirent != null && result > 0) {
if (log.isDebugEnabled()) {
String type = (dirent.stat.attr & 0x10) != 0 ? "dir" : "file";
log.debug(String.format("sceIoDread id=0x%X #%d %s='%s', dir='%s'", id, info.printableposition, type, info.path, filename));
}
if (info.vfs == null) {
// Write only the extended info for the MemoryStick
dirent.setUseExtendedInfo(!info.path.startsWith("disc"));
}
dirent.write(direntAddr);
}
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDread id=0x%X no more files", id));
}
result = 0;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDread(result, id, direntAddr.getAddress());
}
delayIoOperation(timings.get(IoOperation.dread));
return result;
}
/**
* sceIoDclose
*
* @param id
*
* @return
*/
@HLEFunction(nid = 0xEB092469, version = 150, checkInsideInterrupt = true)
public int sceIoDclose(int id) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
IoDirInfo info = dirIds.get(id);
int result;
if (info == null) {
log.warn("sceIoDclose - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else if (info.vfs != null) {
result = info.vfs.ioDclose(info.path);
info.close();
} else {
info.close();
result = 0;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDclose(result, id);
}
delayIoOperation(timings.get(IoOperation.close));
return result;
}
/**
* sceIoRemove
*
* @param filename
*
* @return
*/
@HLEFunction(nid = 0xF27A9C51, version = 150, checkInsideInterrupt = true)
public int sceIoRemove(PspString filename) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
String pcfilename = getDeviceFilePath(filename.getString());
int result;
String absoluteFileName = getAbsoluteFileName(filename.getString());
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
result = vfs.ioRemove(localFileName.toString());
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoRemove - device not found '%s'", filename));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (pcfilename != null) {
if (isUmdPath(pcfilename)) {
result = -1;
} else {
File file = new File(pcfilename);
if (file.delete()) {
result = 0;
} else {
if (file.exists()) {
result = -1;
} else {
result = ERROR_ERRNO_FILE_NOT_FOUND;
}
}
}
} else {
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoRemove(result, filename.getAddress(), filename.getString());
}
delayIoOperation(timings.get(IoOperation.remove));
return result;
}
/**
* Creates a directory.
*
* @param dirname - Name of the directory
* @param permissions -
*
* @return
*/
@HLEFunction(nid = 0x06A70004, version = 150, checkInsideInterrupt = true)
public int sceIoMkdir(PspString dirname, int permissions) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
String pcfilename = getDeviceFilePath(dirname.getString());
int result;
String absoluteFileName = getAbsoluteFileName(dirname.getString());
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
result = vfs.ioMkdir(localFileName.toString(), permissions);
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoMkdir - device not found '%s'", dirname));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (pcfilename != null) {
File f = new File(pcfilename);
f.mkdir();
result = 0;
} else {
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoMkdir(result, dirname.getAddress(), dirname.getString(), permissions);
}
delayIoOperation(timings.get(IoOperation.mkdir));
return result;
}
/**
* Removes a directory.
*
* @param dirname
*
* @return
*/
@HLEFunction(nid = 0x1117C65F, version = 150, checkInsideInterrupt = true)
public int sceIoRmdir(PspString dirname) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
String pcfilename = getDeviceFilePath(dirname.getString());
int result;
String absoluteFileName = getAbsoluteFileName(dirname.getString());
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
result = vfs.ioRmdir(localFileName.toString());
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoRmdir - device not found '%s'", dirname));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (pcfilename != null) {
File f = new File(pcfilename);
if (f.delete()) {
result = 0;
} else {
result = -1;
}
} else {
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoRmdir(result, dirname.getAddress(), dirname.getString());
}
delayIoOperation(timings.get(IoOperation.remove));
return result;
}
/**
* Changes the current directory.
*
* @param path
*
* @return
*/
@HLEFunction(nid = 0x55F4717D, version = 150, checkInsideInterrupt = true)
public int sceIoChdir(PspString path) {
int result;
if (path.getString().equals("..")) {
int index = filepath.lastIndexOf("/");
if (index != -1) {
filepath = filepath.substring(0, index);
}
log.info("pspiofilemgr - filepath " + filepath + " (going up one level)");
result = 0;
} else {
String pcfilename = getDeviceFilePath(path.getString());
if (pcfilename != null) {
filepath = pcfilename;
log.info("pspiofilemgr - filepath " + filepath);
result = 0;
} else {
result = -1;
}
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoChdir(result, path.getAddress(), path.getString());
}
return result;
}
/**
* sceIoSync
*
* @param devicename
* @param flag
*
* @return
*/
@HLEFunction(nid = 0xAB96437F, version = 150, checkInsideInterrupt = true)
public int sceIoSync(PspString devicename, int flag) {
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoSync(0, devicename.getAddress(), devicename.getString(), flag);
}
return 0;
}
/**
* sceIoGetstat
*
* @param filename
* @param stat_addr
*
* @return
*/
@HLEFunction(nid = 0xACE946E8, version = 150, checkInsideInterrupt = true)
public int sceIoGetstat(PspString filename, TPointer statAddr) {
return hleIoGetstat(filename.getAddress(), filename.getString(), statAddr);
}
/**
* sceIoChstat
*
* @param filename
* @param statAddr
* @param bits
*
* @return
*/
@HLEFunction(nid = 0xB8A740F4, version = 150, checkInsideInterrupt = true)
public int sceIoChstat(PspString filename, TPointer statAddr, int bits) {
String pcfilename = getDeviceFilePath(filename.getString());
int result;
SceIoStat stat = new SceIoStat();
stat.read(statAddr);
String absoluteFileName = getAbsoluteFileName(filename.getString());
StringBuilder localFileName = new StringBuilder();
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(absoluteFileName, localFileName);
if (vfs != null) {
result = vfs.ioChstat(localFileName.toString(), stat, bits);
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoChstat - device not found '%s'", filename));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
} else if (pcfilename != null) {
if (isUmdPath(pcfilename)) {
result = -1;
} else {
File file = new File(pcfilename);
int mode = stat.mode;
boolean successful = true;
if ((bits & 0x0001) != 0) { // Others execute permission
if (!file.setExecutable((mode & 0x0001) != 0)) {
// This always fails under Windows
// successful = false;
}
}
if ((bits & 0x0002) != 0) { // Others write permission
if (!file.setWritable((mode & 0x0002) != 0)) {
successful = false;
}
}
if ((bits & 0x0004) != 0) { // Others read permission
if (!file.setReadable((mode & 0x0004) != 0)) {
// This always fails under Windows
// successful = false;
}
}
if ((bits & 0x0040) != 0) { // User execute permission
if (!file.setExecutable((mode & 0x0040) != 0, true)) {
// This always fails under Windows
// successful = false;
}
}
if ((bits & 0x0080) != 0) { // User write permission
if (!file.setWritable((mode & 0x0080) != 0, true)) {
successful = false;
}
}
if ((bits & 0x0100) != 0) { // User read permission
if (!file.setReadable((mode & 0x0100) != 0, true)) {
// This always fails under Windows
// successful = false;
}
}
if (successful) {
result = 0;
} else {
result = -1;
}
}
} else {
result = -1;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoChstat(result, filename.getAddress(), filename.getString(), statAddr.getAddress(), bits);
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoChstat returning 0x%08X", result));
}
return result;
}
/**
* sceIoRename
*
* @param oldfilename
* @param newfilename
*
* @return
*/
@HLEFunction(nid = 0x779103A0, version = 150, checkInsideInterrupt = true)
public int sceIoRename(PspString pspOldFileName, PspString pspNewFileName) {
return hleIoRename(pspOldFileName.getAddress(), pspOldFileName.getString(), pspNewFileName.getAddress(), pspNewFileName.getString());
}
/**
* sceIoDevctl
*
* @param processor
* @param devicename
* @param cmd
* @param indata_addr
* @param inlen
* @param outdata_addr
*
* @param outlen
*/
@HLEFunction(nid = 0x54F5FB11, version = 150, checkInsideInterrupt = true)
public int sceIoDevctl(PspString devicename, int cmd, int indata_addr, int inlen, int outdata_addr, int outlen) {
Map<IoOperation, IoOperationTiming> timings = defaultTimings;
Memory mem = Processor.memory;
int result = -1;
if (log.isDebugEnabled()) {
if (Memory.isAddressGood(indata_addr)) {
for (int i = 0; i < inlen; i += 4) {
log.debug(String.format("sceIoDevctl indata[%d]=0x%08X", i / 4, mem.read32(indata_addr + i)));
}
}
if (Memory.isAddressGood(outdata_addr)) {
for (int i = 0; i < outlen; i += 4) {
log.debug(String.format("sceIoDevctl outdata[%d]=0x%08X", i / 4, mem.read32(outdata_addr + i)));
}
}
}
IVirtualFileSystem vfs = vfsManager.getVirtualFileSystem(devicename.getString(), null);
if (vfs != null) {
result = vfs.ioDevctl(devicename.getString(), cmd, new TPointer(mem, indata_addr), inlen, new TPointer(mem, outdata_addr), outlen);
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDevctl(result, devicename.getAddress(), devicename.getString(), cmd, indata_addr, inlen, outdata_addr, outlen);
}
delayIoOperation(timings.get(IoOperation.iodevctl));
return result;
} else if (useVirtualFileSystem) {
log.error(String.format("sceIoDevctl - device not found '%s'", devicename));
result = ERROR_ERRNO_DEVICE_NOT_FOUND;
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDevctl(result, devicename.getAddress(), devicename.getString(), cmd, indata_addr, inlen, outdata_addr, outlen);
}
delayIoOperation(timings.get(IoOperation.iodevctl));
return result;
}
boolean needDelayIoOperation = true;
switch (cmd) {
// Check disk region
case 0x01E18030:
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDevctl 0x%08X check disk region", cmd));
}
if (inlen >= 16) {
int unknown1 = mem.read32(indata_addr + 0);
int unknown2 = mem.read32(indata_addr + 4);
int unknown3 = mem.read32(indata_addr + 8);
int unknown4 = mem.read32(indata_addr + 12);
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDevctl 0x%08X check disk region unknown1=0x%X, unknown2=0x%X, unknown3=0x%X, unknown4=0x%X", cmd, unknown1, unknown2, unknown3, unknown4));
}
// Return 0 if the disk region is not matching,
// return 1 if the disk region is matching.
result = 1;
} else {
result = -1;
}
break;
// Get UMD disc type.
case 0x01F20001: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " get disc type");
}
if (Memory.isAddressGood(outdata_addr) && outlen >= 8) {
// 0 = No disc.
// 0x10 = Game disc.
// 0x20 = Video disc.
// 0x40 = Audio disc.
// 0x80 = Cleaning disc.
int out;
if (iso == null) {
out = 0; // No disc
} else {
out = 0x10; // Return game disc by default
// Retrieve the disc type from the UMD_DATA.BIN file
IVirtualFileSystem vfsIso = new UmdIsoVirtualFileSystem(iso);
IVirtualFile vfsUmdData = vfsIso.ioOpen("UMD_DATA.BIN", 0, PSP_O_RDONLY);
if (vfsUmdData != null) {
byte buffer[] = new byte[(int) vfsUmdData.length()];
int length = vfsUmdData.ioRead(buffer, 0, buffer.length);
if (length > 0) {
String umdData = new String(buffer);
String[] umdDataParts = umdData.split("\\|");
if (umdDataParts != null && umdDataParts.length >= 4) {
String umdType = umdDataParts[3];
if (umdType != null && umdType.length() > 0) {
switch (umdType.charAt(0)) {
case 'G': out = 0x10; break; // Game disc
case 'V': out = 0x20; break; // Video disc
default:
log.warn(String.format("Unknown disc type '%s' in UMD_DATA.BIN", umdType));
break;
}
}
}
}
vfsUmdData.ioClose();
}
}
mem.write32(outdata_addr + 4, out);
result = 0;
} else {
result = -1;
}
break;
}
// Get UMD current LBA.
case 0x01F20002: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " get current LBA");
}
if (Memory.isAddressGood(outdata_addr) && outlen >= 4) {
mem.write32(outdata_addr, 0); // Assume first sector.
result = 0;
} else {
result = -1;
}
break;
}
// Seek UMD disc (raw).
case 0x01F100A3: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " seek UMD disc");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4)) {
int sector = mem.read32(indata_addr);
if (log.isDebugEnabled()) {
log.debug("sector=" + sector);
}
result = 0;
} else {
result = -1;
}
break;
}
// Prepare UMD data into cache.
case 0x01F100A4: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " prepare UMD data to cache");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4)) {
// UMD cache read struct (16-bytes).
int unk1 = mem.read32(indata_addr); // NULL.
int sector = mem.read32(indata_addr + 4); // First sector of data to read.
int unk2 = mem.read32(indata_addr + 8); // NULL.
int sectorNum = mem.read32(indata_addr + 12); // Length of data to read.
if (log.isDebugEnabled()) {
log.debug(String.format("sector=%d, sectorNum=%d, unk1=%d, unk2=%d", sector, sectorNum, unk1, unk2));
}
result = 0;
} else {
result = -1;
}
break;
}
// Prepare UMD data into cache and get status.
case 0x01F300A5: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " prepare UMD data to cache and get status");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4) && (Memory.isAddressGood(outdata_addr) && outlen >= 4)) {
// UMD cache read struct (16-bytes).
int unk1 = mem.read32(indata_addr); // NULL.
int sector = mem.read32(indata_addr + 4); // First sector of data to read.
int unk2 = mem.read32(indata_addr + 8); // NULL.
int sectorNum = mem.read32(indata_addr + 12); // Length of data to read.
if (log.isDebugEnabled()) {
log.debug(String.format("sector=%d, sectorNum=%d, unk1=%d, unk2=%d", sector, sectorNum, unk1, unk2));
}
mem.write32(outdata_addr, 1); // Status (unitary index of the requested read, greater or equal to 1).
result = 0;
} else {
result = -1;
}
break;
}
// Wait for the UMD data cache thread.
case 0x01F300A7: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " wait for the UMD data cache thread");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4)) {
int index = mem.read32(indata_addr); // Index set by command 0x01F300A5.
if (log.isDebugEnabled()) {
log.debug(String.format("index=%d", index));
}
// Place the calling thread in wait state.
// Disabled the following lines as long as the UMD data cache thread has not been implemented.
// Otherwise nobody would wake-up the thread.
//ThreadManForUser threadMan = Modules.ThreadManForUserModule;
//SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
//threadMan.hleKernelThreadEnterWaitState(JPCSP_WAIT_IO, currentThread.wait.Io_id, ioWaitStateChecker, true);
result = 0;
} else {
result = -1;
}
break;
}
// Poll the UMD data cache thread.
case 0x01F300A8: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " poll the UMD data cache thread");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4)) {
int index = mem.read32(indata_addr); // Index set by command 0x01F300A5.
if (log.isDebugEnabled()) {
log.debug(String.format("index=%d", index));
}
// 0 - UMD data cache thread has finished.
// 0x10 - UMD data cache thread is waiting.
// 0x20 - UMD data cache thread is running.
result = 0; // Return finished.
} else {
result = -1;
}
break;
}
// Cancel the UMD data cache thread.
case 0x01F300A9: {
if (log.isDebugEnabled()) {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " cancel the UMD data cache thread");
}
if ((Memory.isAddressGood(indata_addr) && inlen >= 4)) {
int index = mem.read32(indata_addr); // Index set by command 0x01F300A5.
if (log.isDebugEnabled()) {
log.debug(String.format("index=%d", index));
}
// Wake up the thread waiting for the UMD data cache handling.
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext();) {
SceKernelThreadInfo thread = it.next();
if (thread.isWaitingForType(JPCSP_WAIT_IO)) {
thread.cpuContext._v0 = SceKernelErrors.ERROR_KERNEL_WAIT_CANCELLED;
threadMan.hleKernelWakeupThread(thread);
}
}
result = 0;
} else {
result = -1;
}
break;
}
// Check the MemoryStick's driver status (mscmhc0).
case 0x02025801: {
log.debug("sceIoDevctl " + String.format("0x%08X", cmd) + " check ms driver status");
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(outdata_addr)) {
// 0 = Driver busy.
// 1 = Driver ready.
mem.write32(outdata_addr, 4);
result = 0;
} else {
result = -1;
}
break;
}
// Register MemoryStick's insert/eject callback (mscmhc0).
case 0x02015804: {
log.debug("sceIoDevctl register memorystick insert/eject callback (mscmhc0)");
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(indata_addr) && inlen == 4) {
int cbid = mem.read32(indata_addr);
final int callbackType = SceKernelThreadInfo.THREAD_CALLBACK_MEMORYSTICK;
if (threadMan.hleKernelRegisterCallback(callbackType, cbid)) {
// Trigger the registered callback immediately.
threadMan.hleKernelNotifyCallback(callbackType, cbid, MemoryStick.getStateMs());
result = 0; // Success.
} else {
result = SceKernelErrors.ERROR_MEMSTICK_DEVCTL_TOO_MANY_CALLBACKS;
}
} else {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
}
// Unregister MemoryStick's insert/eject callback (mscmhc0).
case 0x02015805: {
log.debug("sceIoDevctl unregister memorystick insert/eject callback (mscmhc0)");
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(indata_addr) && inlen == 4) {
int cbid = mem.read32(indata_addr);
if (threadMan.hleKernelUnRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_MEMORYSTICK, cbid)) {
result = 0; // Success.
} else {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS; // No such callback.
}
} else {
result = -1; // Invalid parameters.
}
break;
}
// ???
case 0x02015807: {
log.debug("sceIoDevctl ??? (mscmhc0)");
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(outdata_addr) && outlen == 4) {
mem.write32(outdata_addr, 0); // Unknown value: seems to be 0 or 1?
result = 0; // Success.
} else {
result = -1; // Invalid parameters.
}
break;
}
// ???
case 0x0201580B: {
log.debug("sceIoDevctl ??? (mscmhc0)");
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(indata_addr) && inlen == 20) {
result = 0; // Success.
} else {
result = -1; // Invalid parameters.
}
break;
}
// Check if the device is inserted (mscmhc0).
case 0x02025806: {
log.debug("sceIoDevctl check ms inserted (mscmhc0)");
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(outdata_addr)) {
// 0 = Not inserted.
// 1 = Inserted.
mem.write32(outdata_addr, 1);
result = 0;
} else {
result = -1;
}
break;
}
// ???
case 0x0202580A: {
log.debug("sceIoDevctl ??? (mscmhc0)");
if (!devicename.getString().equals("mscmhc0:")) {
result = ERROR_KERNEL_UNSUPPORTED_OPERATION;
} else if (Memory.isAddressGood(outdata_addr) && outlen == 16) {
// When value1 or value2 are < 10000, sceUtilitySavedata is
// returning an error 0x8011032C (bad status).
// When value1 or value2 are > 10000, sceUtilitySavedata is
// returning an error 0x8011032A (the system has been shifted to sleep mode).
final int value1 = 10000;
final int value2 = 10000;
// When value3 or value4 are < 10000, sceUtilitySavedata is
// returning an error 0x8011032C (bad status)
// When value3 or value4 are > 10000, sceUtilitySavedata is
// returning an error 0x80110322 (the memory stick has been removed).
final int value3 = 10000;
final int value4 = 10000;
// No error is returned by sceUtilitySavedata only when
// all 4 values are set to 10000.
mem.write32(outdata_addr + 0, value1);
mem.write32(outdata_addr + 4, value2);
mem.write32(outdata_addr + 8, value3);
mem.write32(outdata_addr + 12, value4);
result = 0;
} else {
result = -1;
}
break;
}
// Register memorystick insert/eject callback (fatms0).
case 0x02415821: {
log.debug("sceIoDevctl register memorystick insert/eject callback (fatms0)");
needDelayIoOperation = false;
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!devicename.getString().equals("fatms0:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(indata_addr) && inlen == 4) {
int cbid = mem.read32(indata_addr);
final int callbackType = SceKernelThreadInfo.THREAD_CALLBACK_MEMORYSTICK_FAT;
if (threadMan.hleKernelRegisterCallback(callbackType, cbid)) {
// Trigger the registered callback immediately.
// Only trigger this one callback, not all the MS callbacks.
threadMan.hleKernelNotifyCallback(callbackType, cbid, MemoryStick.getStateFatMs());
result = 0; // Success.
} else {
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
} else {
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// Unregister memorystick insert/eject callback (fatms0).
case 0x02415822: {
log.debug("sceIoDevctl unregister memorystick insert/eject callback (fatms0)");
needDelayIoOperation = false;
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!devicename.getString().equals("fatms0:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(indata_addr) && inlen == 4) {
int cbid = mem.read32(indata_addr);
threadMan.hleKernelUnRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_MEMORYSTICK_FAT, cbid);
result = 0; // Success.
} else {
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// Set if the device is assigned/inserted or not (fatms0).
case 0x02415823: {
log.debug("sceIoDevctl set assigned device (fatms0)");
if (!devicename.getString().equals("fatms0:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
// 0 - Device is not assigned (callback not registered).
// 1 - Device is assigned (callback registered).
MemoryStick.setStateFatMs(mem.read32(indata_addr));
result = 0;
} else {
result = -1;
}
break;
}
case 0x02415857: {
if (!devicename.getString().equals("ms0:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(outdata_addr) && outlen == 4) {
mem.write32(outdata_addr, 0); // Unknown value
result = 0;
} else {
result = SceKernelErrors.ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
}
// Check if the device is write protected (fatms0).
case 0x02425824: {
log.debug("sceIoDevctl check write protection (fatms0)");
if (!devicename.getString().equals("fatms0:") && !devicename.getString().equals("ms0:")) { // For this command the alias "ms0:" is also supported.
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(outdata_addr)) {
// 0 - Device is not protected.
// 1 - Device is protected.
mem.write32(outdata_addr, 0);
result = 0;
} else {
result = -1;
}
break;
}
// Get MS capacity (fatms0).
case 0x02425818: {
log.debug("sceIoDevctl get MS capacity (fatms0)");
int sectorSize = 0x200;
int sectorCount = MemoryStick.getSectorSize() / sectorSize;
int maxClusters = (int) ((MemoryStick.getFreeSize() * 95L / 100) / (sectorSize * sectorCount));
int freeClusters = maxClusters;
int maxSectors = maxClusters;
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
int addr = mem.read32(indata_addr);
if (Memory.isAddressGood(addr)) {
log.debug("sceIoDevctl refer ms free space");
mem.write32(addr, maxClusters);
mem.write32(addr + 4, freeClusters);
mem.write32(addr + 8, maxSectors);
mem.write32(addr + 12, sectorSize);
mem.write32(addr + 16, sectorCount);
result = 0;
} else {
log.warn("sceIoDevctl 0x02425818 bad save address " + String.format("0x%08X", addr));
result = -1;
}
} else {
log.warn("sceIoDevctl 0x02425818 bad param address " + String.format("0x%08X", indata_addr) + " or size " + inlen);
result = -1;
}
break;
}
// Check if the device is assigned/inserted (fatms0).
case 0x02425823: {
log.debug("sceIoDevctl check assigned device (fatms0)");
needDelayIoOperation = false;
if (!devicename.getString().equals("fatms0:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else if (Memory.isAddressGood(outdata_addr) && outlen >= 4) {
// 0 - Device is not assigned (callback not registered).
// 1 - Device is assigned (callback registered).
mem.write32(outdata_addr, MemoryStick.getStateFatMs());
result = 0;
} else {
result = -1;
}
break;
}
// Register USB thread.
case 0x03415001: {
log.debug("sceIoDevctl register usb thread");
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
// Unknown params.
result = 0;
} else {
result = -1;
}
break;
}
// Unregister USB thread.
case 0x03415002: {
log.debug("sceIoDevctl unregister usb thread");
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
// Unknown params.
result = 0;
} else {
result = -1;
}
break;
}
case 0x02425856: {
if (Memory.isAddressGood(indata_addr) && inlen >= 4) {
// This is the value contained in the registry entry
// "/CONFIG/SYSTEM/CHARACTER_SET/oem"
int characterSet = mem.read32(indata_addr);
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDevctl '%s' set character set to 0x%X", devicename.getString(), characterSet));
}
result = 0;
}
break;
}
case 0x02415830: {
if (Memory.isAddressGood(indata_addr) && inlen >= 8) {
int oldFileNameAddr = mem.read32(indata_addr);
int newFileNameAddr = mem.read32(indata_addr + 4);
String oldFileName = Utilities.readStringZ(mem, oldFileNameAddr);
String newFileName = Utilities.readStringZ(mem, newFileNameAddr);
result = hleIoRename(oldFileNameAddr, devicename.getString() + oldFileName, newFileNameAddr, devicename.getString() + newFileName);
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoDevctl file rename oldFileName='%s', newFileName='%s', result=0x%X", oldFileName, newFileName, result));
}
}
break;
}
case 0x00005802: {
if (!devicename.getString().equals("flash1:")) {
result = ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
} else {
result = 0;
}
break;
}
default:
log.warn(String.format("sceIoDevctl 0x%08X unknown command", cmd));
if (Memory.isAddressGood(indata_addr)) {
log.warn(String.format("sceIoDevctl indata: %s", Utilities.getMemoryDump(indata_addr, inlen)));
}
result = SceKernelErrors.ERROR_ERRNO_INVALID_IODEVCTL_CMD;
break;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoDevctl(result, devicename.getAddress(), devicename.getString(), cmd, indata_addr, inlen, outdata_addr, outlen);
}
if (needDelayIoOperation) {
delayIoOperation(timings.get(IoOperation.iodevctl));
}
return result;
}
/**
* sceIoGetDevType
*
* @param id
*
* @return
*/
@HLEFunction(nid = 0x08BD7374, version = 150, checkInsideInterrupt = true)
public int sceIoGetDevType(int id) {
int result;
if (id == STDIN_ID || id == STDOUT_ID || id == STDERR_ID) {
result = PSP_DEV_TYPE_FILESYSTEM;
} else {
IoInfo info = fileIds.get(id);
if (info == null) {
log.warn("sceIoGetDevType - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else {
// For now, return alias type, since it's the most used.
result = PSP_DEV_TYPE_ALIAS;
}
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceIoGetDevType id=0x%X returning 0x%X", id, result));
}
return result;
}
/**
* sceIoAssign: mounts physicalDev on filesystemDev and sets an alias to represent it.
*
* @param alias
* @param physicalDev
* @param filesystemDev
* @param mode 0 IOASSIGN_RDWR
* 1 IOASSIGN_RDONLY
* @param arg_addr
* @param argSize
*
* @return
*/
@HLELogging(level="warn")
@HLEFunction(nid = 0xB2A628C1, version = 150, checkInsideInterrupt = true)
public int sceIoAssign(PspString alias, PspString physicalDev, PspString filesystemDev, int mode, int arg_addr, int argSize) {
int result = 0;
// Do not assign "disc0:".
// Example from "Ridge Racer UCES00002":
// sceIoAssign alias=0x0899F1E0('disc0:'), physicalDev=0x0899F1D0('umd0:'), filesystemDev=0x0899F1D8('isofs0:'), mode=0x1, arg_addr=0x0, argSize=0x0
if (!alias.getString().equals("disc0:")) {
assignedDevices.put(alias.getString().replace(":", ""), filesystemDev.getString().replace(":", ""));
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoAssign(result, alias.getAddress(), alias.getString(), physicalDev.getAddress(), physicalDev.getString(), filesystemDev.getAddress(), filesystemDev.getString(), mode, arg_addr, argSize);
}
return result;
}
/**
* sceIoUnassign
*
* @param alias
*
* @return
*/
@HLELogging(level="warn")
@HLEFunction(nid = 0x6D08A871, version = 150, checkInsideInterrupt = true)
public int sceIoUnassign(PspString alias) {
assignedDevices.remove(alias.getString().replace(":", ""));
return 0;
}
/**
* sceIoCancel
*
* @param id
*
* @return
*/
@HLEFunction(nid = 0xE8BC6571, version = 150, checkInsideInterrupt = true)
public int sceIoCancel(int id) {
IoInfo info = fileIds.get(id);
int result;
if (info == null) {
log.warn("sceIoCancel - unknown id " + Integer.toHexString(id));
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else {
info.closePending = true;
result = 0;
}
for (IIoListener ioListener : ioListeners) {
ioListener.sceIoCancel(result, id);
}
return result;
}
/**
* sceIoGetFdList
*
* @param outAddr
* @param outSize
* @param fdNumAddr
*
* @return
*/
@HLEFunction(nid = 0x5C2BE2CC, version = 150, checkInsideInterrupt = true)
public int sceIoGetFdList(@CanBeNull TPointer32 outAddr, int outSize, @CanBeNull TPointer32 fdNumAddr) {
int count = 0;
if (outAddr.isNotNull() && outSize > 0) {
int offset = 0;
for (Integer fd : fileIds.keySet()) {
if (offset >= outSize) {
break;
}
outAddr.setValue(offset, fd.intValue());
offset += 4;
}
count = offset / 4;
}
// Return the total number of files open
fdNumAddr.setValue(fileIds.size());
return count;
}
/**
* Reopens an existing file descriptor.
*
* @param filename the new file to open.
* @param flags the open flags.
* @param permissions the open mode.
* @param id the old file descriptor to reopen.
* @return < 0 on error, otherwise the reopened file descriptor.
*/
@HLEUnimplemented
@HLEFunction(nid = 0x3C54E908, version = 150)
public int sceIoReopen(PspString filename, int flags, int permissions, int id) {
return -1;
}
@HLEUnimplemented
@HLEFunction(nid = 0xC7F35804, version = 150)
public int sceIoDelDrv(PspString driverName) {
return 0;
}
@HLEUnimplemented
@HLEFunction(nid = 0x8E982A74, version = 150)
public int sceIoAddDrv(TPointer pspIoDrvAddr) {
pspIoDrv pspIoDrv = new pspIoDrv();
pspIoDrv.read(pspIoDrvAddr);
log.warn(String.format("sceIoAddDrv pspIoDrv=%s", pspIoDrv));
return 0;
}
}