Return fds in the range 0 - 64 like the PSP.

Some games appear to check ids are valid specifically by checking this
range.  Reproduced on a PSP.
This commit is contained in:
Unknown W. Brackets 2013-06-16 10:05:30 -07:00
parent c1ed09949d
commit 2bc3da5b94

View file

@ -97,7 +97,12 @@ typedef s32 SceMode;
typedef s64 SceOff; typedef s64 SceOff;
typedef u64 SceIores; typedef u64 SceIores;
int asyncNotifyEvent = -1; const int PSP_COUNT_FDS = 64;
// TODO: Should be 3, and stdin/stdout/stderr are special values aliased to 0?
const int PSP_MIN_FD = 4;
static int asyncNotifyEvent = -1;
static SceUID fds[PSP_COUNT_FDS];
u32 ioErrorCode = 0; u32 ioErrorCode = 0;
#define SCE_STM_FDIR 0x1000 #define SCE_STM_FDIR 0x1000
@ -213,12 +218,43 @@ public:
/******************************************************************************/ /******************************************************************************/
void __IoCompleteAsyncIO(SceUID id); void __IoCompleteAsyncIO(int fd);
static void TellFsThreadEnded (SceUID threadID) { static void TellFsThreadEnded (SceUID threadID) {
pspFileSystem.ThreadEnded(threadID); pspFileSystem.ThreadEnded(threadID);
} }
FileNode *__IoGetFd(int fd, u32 &error) {
if (fd < 0 || fd >= PSP_COUNT_FDS) {
error = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
return NULL;
}
return kernelObjects.Get<FileNode>(fds[fd], error);
}
int __IoAllocFd(FileNode *f) {
// The PSP takes the lowest available id after stderr/etc.
for (int possible = PSP_MIN_FD; possible < PSP_COUNT_FDS; ++possible) {
if (fds[possible] == 0) {
fds[possible] = f->GetUID();
return possible;
}
}
// Bugger, out of fds...
return SCE_KERNEL_ERROR_MFILE;
}
void __IoFreeFd(int fd, u32 &error) {
if (fd < PSP_MIN_FD || fd >= PSP_COUNT_FDS) {
error = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else {
error = kernelObjects.Destroy<FileNode>(fds[fd]);
fds[fd] = 0;
}
}
// Async IO seems to work roughly like this: // Async IO seems to work roughly like this:
// 1. Game calls SceIo*Async() to start the process. // 1. Game calls SceIo*Async() to start the process.
// 2. This runs a thread with a customizable priority. // 2. This runs a thread with a customizable priority.
@ -236,7 +272,7 @@ static void TellFsThreadEnded (SceUID threadID) {
// For now, let's at least delay the callback mnotification. // For now, let's at least delay the callback mnotification.
void __IoAsyncNotify(u64 userdata, int cyclesLate) { void __IoAsyncNotify(u64 userdata, int cyclesLate) {
SceUID threadID = userdata >> 32; SceUID threadID = userdata >> 32;
SceUID fd = (SceUID) (userdata & 0xFFFFFFFF); int fd = (int) (userdata & 0xFFFFFFFF);
__IoCompleteAsyncIO(fd); __IoCompleteAsyncIO(fd);
u32 error; u32 error;
@ -245,14 +281,14 @@ void __IoAsyncNotify(u64 userdata, int cyclesLate) {
if (waitID == fd && error == 0) { if (waitID == fd && error == 0) {
__KernelResumeThreadFromWait(threadID, 0); __KernelResumeThreadFromWait(threadID, 0);
FileNode *f = kernelObjects.Get<FileNode>(fd, error); FileNode *f = __IoGetFd(fd, error);
if (Memory::IsValidAddress(address) && f) { if (Memory::IsValidAddress(address) && f) {
Memory::Write_U64((u64) f->asyncResult, address); Memory::Write_U64((u64) f->asyncResult, address);
} }
// If this was a sceIoCloseAsync, we should close it at this point. // If this was a sceIoCloseAsync, we should close it at this point.
if (f->closePending) { if (f->closePending) {
kernelObjects.Destroy<FileNode>(fd); __IoFreeFd(fd, error);
} }
} }
} }
@ -280,9 +316,12 @@ void __IoInit() {
pspFileSystem.Mount("flash0:", flash0); pspFileSystem.Mount("flash0:", flash0);
__KernelListenThreadEnd(&TellFsThreadEnded); __KernelListenThreadEnd(&TellFsThreadEnded);
memset(fds, 0, sizeof(fds));
} }
void __IoDoState(PointerWrap &p) { void __IoDoState(PointerWrap &p) {
p.DoArray(fds, ARRAY_SIZE(fds));
p.Do(asyncNotifyEvent); p.Do(asyncNotifyEvent);
CoreTiming::RestoreRegisterEvent(asyncNotifyEvent, "IoAsyncNotify", __IoAsyncNotify); CoreTiming::RestoreRegisterEvent(asyncNotifyEvent, "IoAsyncNotify", __IoAsyncNotify);
} }
@ -292,9 +331,9 @@ void __IoShutdown() {
u32 __IoGetFileHandleFromId(u32 id, u32 &outError) u32 __IoGetFileHandleFromId(u32 id, u32 &outError)
{ {
FileNode *f = kernelObjects.Get < FileNode > (id, outError); FileNode *f = __IoGetFd(id, outError);
if (!f) { if (!f) {
return -1; return (u32)-1;
} }
return f->handle; return f->handle;
} }
@ -342,9 +381,9 @@ u32 sceKernelStderr() {
return 2; return 2;
} }
void __IoCompleteAsyncIO(SceUID id) { void __IoCompleteAsyncIO(int fd) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(fd, error);
if (f) { if (f) {
if (f->callbackID) { if (f->callbackID) {
__KernelNotifyCallback(THREAD_CALLBACK_IO, f->callbackID, f->callbackArg); __KernelNotifyCallback(THREAD_CALLBACK_IO, f->callbackID, f->callbackArg);
@ -384,7 +423,7 @@ void __IoGetStat(SceIoStat *stat, PSPFileInfo &info) {
stat->st_private[0] = info.startSector; stat->st_private[0] = info.startSector;
} }
void __IoSchedAsync(FileNode *f, SceUID fd, int usec) { void __IoSchedAsync(FileNode *f, int fd, int usec) {
u64 param = ((u64)__KernelGetCurThread()) << 32 | fd; u64 param = ((u64)__KernelGetCurThread()) << 32 | fd;
CoreTiming::ScheduleEvent(usToCycles(usec), asyncNotifyEvent, param); CoreTiming::ScheduleEvent(usToCycles(usec), asyncNotifyEvent, param);
@ -478,7 +517,7 @@ int __IoRead(int id, u32 data_addr, int size) {
} }
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
if(!(f->openMode & FILEACCESS_READ)) if(!(f->openMode & FILEACCESS_READ))
{ {
@ -520,7 +559,7 @@ u32 sceIoRead(int id, u32 data_addr, int size) {
u32 sceIoReadAsync(int id, u32 data_addr, int size) { u32 sceIoReadAsync(int id, u32 data_addr, int size) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
f->asyncResult = __IoRead(id, data_addr, size); f->asyncResult = __IoRead(id, data_addr, size);
// TODO: Not sure what the correct delay is (and technically we shouldn't read into the buffer yet...) // TODO: Not sure what the correct delay is (and technically we shouldn't read into the buffer yet...)
@ -542,7 +581,7 @@ int __IoWrite(int id, void *data_ptr, int size) {
return size; return size;
} }
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
if(!(f->openMode & FILEACCESS_WRITE)) { if(!(f->openMode & FILEACCESS_WRITE)) {
return ERROR_KERNEL_BAD_FILE_DESCRIPTOR; return ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
@ -574,7 +613,7 @@ u32 sceIoWrite(int id, u32 data_addr, int size) {
u32 sceIoWriteAsync(int id, u32 data_addr, int size) { u32 sceIoWriteAsync(int id, u32 data_addr, int size) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
f->asyncResult = __IoWrite(id, Memory::GetPointer(data_addr), size); f->asyncResult = __IoWrite(id, Memory::GetPointer(data_addr), size);
// TODO: Not sure what the correct delay is (and technically we shouldn't read into the buffer yet...) // TODO: Not sure what the correct delay is (and technically we shouldn't read into the buffer yet...)
@ -591,7 +630,7 @@ u32 sceIoGetDevType(int id)
{ {
ERROR_LOG_REPORT(HLE, "UNIMPL sceIoGetDevType(%d)", id); ERROR_LOG_REPORT(HLE, "UNIMPL sceIoGetDevType(%d)", id);
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
int result; int result;
if (f) if (f)
result = PSP_DEV_TYPE_ALIAS; result = PSP_DEV_TYPE_ALIAS;
@ -607,7 +646,7 @@ u32 sceIoCancel(int id)
{ {
ERROR_LOG_REPORT(HLE, "UNIMPL sceIoCancel(%d)", id); ERROR_LOG_REPORT(HLE, "UNIMPL sceIoCancel(%d)", id);
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
// TODO: Cancel the async operation if possible? // TODO: Cancel the async operation if possible?
} else { } else {
@ -643,7 +682,7 @@ u32 npdrmLseek(FileNode *f, s32 where, FileMove whence)
s64 __IoLseek(SceUID id, s64 offset, int whence) { s64 __IoLseek(SceUID id, s64 offset, int whence) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
FileMove seek = FILEMOVE_BEGIN; FileMove seek = FILEMOVE_BEGIN;
@ -700,7 +739,7 @@ u32 sceIoLseek32(int id, int offset, int whence) {
u32 sceIoLseekAsync(int id, s64 offset, int whence) { u32 sceIoLseekAsync(int id, s64 offset, int whence) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get<FileNode>(id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
f->asyncResult = __IoLseek(id, offset, whence); f->asyncResult = __IoLseek(id, offset, whence);
// Educated guess at timing. // Educated guess at timing.
@ -716,7 +755,7 @@ u32 sceIoLseekAsync(int id, s64 offset, int whence) {
u32 sceIoLseek32Async(int id, int offset, int whence) { u32 sceIoLseek32Async(int id, int offset, int whence) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get<FileNode>(id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
f->asyncResult = __IoLseek(id, offset, whence); f->asyncResult = __IoLseek(id, offset, whence);
// Educated guess at timing. // Educated guess at timing.
@ -785,18 +824,24 @@ u32 sceIoOpen(const char* filename, int flags, int mode) {
} }
} }
SceUID id = f->GetUID(); int id = __IoAllocFd(f);
DEBUG_LOG(HLE, "%i=sceIoOpen(%s, %08x, %08x)", id, filename, flags, mode); if (id < 0) {
// Timing is not accurate, aiming low for now. ERROR_LOG(HLE, "%08x=sceIoOpen(%s, %08x, %08x): out of fds", id, filename, flags, mode);
return hleDelayResult(id, "file opened", 100); kernelObjects.Destroy<FileNode>(f->GetUID());
return id;
} else {
DEBUG_LOG(HLE, "%i=sceIoOpen(%s, %08x, %08x)", id, filename, flags, mode);
// Timing is not accurate, aiming low for now.
return hleDelayResult(id, "file opened", 100);
}
} }
u32 sceIoClose(int id) { u32 sceIoClose(int id) {
u32 error; u32 error;
DEBUG_LOG(HLE, "sceIoClose(%d)", id); DEBUG_LOG(HLE, "sceIoClose(%d)", id);
FileNode *f = kernelObjects.Get<FileNode>(id, error); __IoFreeFd(id, error);
// Timing is not accurate, aiming low for now. // Timing is not accurate, aiming low for now.
return hleDelayResult(kernelObjects.Destroy<FileNode>(id), "file closed", 100); return hleDelayResult(error, "file closed", 100);
} }
u32 sceIoRemove(const char *filename) { u32 sceIoRemove(const char *filename) {
@ -1197,7 +1242,7 @@ int sceIoCloseAsync(int id)
{ {
DEBUG_LOG(HLE, "sceIoCloseAsync(%d)", id); DEBUG_LOG(HLE, "sceIoCloseAsync(%d)", id);
u32 error; u32 error;
FileNode *f = kernelObjects.Get<FileNode>(id, error); FileNode *f = __IoGetFd(id, error);
if (f) if (f)
{ {
f->closePending = true; f->closePending = true;
@ -1215,7 +1260,7 @@ u32 sceIoSetAsyncCallback(int id, u32 clbckId, u32 clbckArg)
DEBUG_LOG(HLE, "sceIoSetAsyncCallback(%d, %i, %08x)", id, clbckId, clbckArg); DEBUG_LOG(HLE, "sceIoSetAsyncCallback(%d, %i, %08x)", id, clbckId, clbckArg);
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) if (f)
{ {
f->callbackID = clbckId; f->callbackID = clbckId;
@ -1236,7 +1281,7 @@ u32 sceIoOpenAsync(const char *filename, int flags, int mode)
sceKernelResumeDispatchThread(1); sceKernelResumeDispatchThread(1);
FileNode *f = __IoOpen(filename, flags, mode); FileNode *f = __IoOpen(filename, flags, mode);
SceUID fd; int fd;
// We have to return an fd here, which may have been destroyed when we reach Wait if it failed. // We have to return an fd here, which may have been destroyed when we reach Wait if it failed.
if (f == NULL) if (f == NULL)
@ -1244,16 +1289,26 @@ u32 sceIoOpenAsync(const char *filename, int flags, int mode)
ERROR_LOG(HLE, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpenAsync(%s, %08x, %08x) - file not found", filename, flags, mode); ERROR_LOG(HLE, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpenAsync(%s, %08x, %08x) - file not found", filename, flags, mode);
f = new FileNode(); f = new FileNode();
fd = kernelObjects.Create(f); f->handle = kernelObjects.Create(f);
f->handle = fd;
f->fullpath = filename; f->fullpath = filename;
f->asyncResult = ERROR_ERRNO_FILE_NOT_FOUND; f->asyncResult = ERROR_ERRNO_FILE_NOT_FOUND;
f->closePending = true; f->closePending = true;
fd = __IoAllocFd(f);
} }
else else
{ {
fd = f->GetUID(); fd = __IoAllocFd(f);
DEBUG_LOG(HLE, "%x=sceIoOpenAsync(%s, %08x, %08x)", fd, filename, flags, mode); if (fd >= 0) {
DEBUG_LOG(HLE, "%x=sceIoOpenAsync(%s, %08x, %08x)", fd, filename, flags, mode);
f->asyncResult = fd;
}
}
if (fd < 0) {
ERROR_LOG(HLE, "%08x=sceIoOpenAsync(%s, %08x, %08x): out of fds", fd, filename, flags, mode);
kernelObjects.Destroy<FileNode>(f->GetUID());
return fd;
} }
// TODO: Timing is very inconsistent. From ms0, 10ms - 20ms depending on filesize/dir depth? From umd, can take > 1s. // TODO: Timing is very inconsistent. From ms0, 10ms - 20ms depending on filesize/dir depth? From umd, can take > 1s.
@ -1266,7 +1321,7 @@ u32 sceIoOpenAsync(const char *filename, int flags, int mode)
u32 sceIoGetAsyncStat(int id, u32 poll, u32 address) u32 sceIoGetAsyncStat(int id, u32 poll, u32 address)
{ {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) if (f)
{ {
if (f->pendingAsyncResult) { if (f->pendingAsyncResult) {
@ -1286,7 +1341,7 @@ u32 sceIoGetAsyncStat(int id, u32 poll, u32 address)
f->hasAsyncResult = false; f->hasAsyncResult = false;
if (f->closePending) { if (f->closePending) {
kernelObjects.Destroy<FileNode>(id); __IoFreeFd(id, error);
} }
} else { } else {
WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoGetAsyncStat(%i, %i, %08x)", id, poll, address); WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoGetAsyncStat(%i, %i, %08x)", id, poll, address);
@ -1303,7 +1358,7 @@ u32 sceIoGetAsyncStat(int id, u32 poll, u32 address)
int sceIoWaitAsync(int id, u32 address) { int sceIoWaitAsync(int id, u32 address) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
if (f->pendingAsyncResult) { if (f->pendingAsyncResult) {
DEBUG_LOG(HLE, "%lli = sceIoWaitAsync(%i, %08x): waiting", f->asyncResult, id, address); DEBUG_LOG(HLE, "%lli = sceIoWaitAsync(%i, %08x): waiting", f->asyncResult, id, address);
@ -1314,7 +1369,7 @@ int sceIoWaitAsync(int id, u32 address) {
f->hasAsyncResult = false; f->hasAsyncResult = false;
if (f->closePending) { if (f->closePending) {
kernelObjects.Destroy<FileNode>(id); __IoFreeFd(id, error);
} }
} else { } else {
WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsync(%i, %08x)", id, address); WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsync(%i, %08x)", id, address);
@ -1330,7 +1385,7 @@ int sceIoWaitAsync(int id, u32 address) {
int sceIoWaitAsyncCB(int id, u32 address) { int sceIoWaitAsyncCB(int id, u32 address) {
// Should process callbacks here // Should process callbacks here
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
hleCheckCurrentCallbacks(); hleCheckCurrentCallbacks();
if (f->pendingAsyncResult) { if (f->pendingAsyncResult) {
@ -1342,7 +1397,7 @@ int sceIoWaitAsyncCB(int id, u32 address) {
f->hasAsyncResult = false; f->hasAsyncResult = false;
if (f->closePending) { if (f->closePending) {
kernelObjects.Destroy<FileNode>(id); __IoFreeFd(id, error);
} }
} else { } else {
WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsyncCB(%i, %08x)", id, address); WARN_LOG(HLE, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsyncCB(%i, %08x)", id, address);
@ -1357,7 +1412,7 @@ int sceIoWaitAsyncCB(int id, u32 address) {
u32 sceIoPollAsync(int id, u32 address) { u32 sceIoPollAsync(int id, u32 address) {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
if (f->pendingAsyncResult) { if (f->pendingAsyncResult) {
DEBUG_LOG(HLE, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address); DEBUG_LOG(HLE, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address);
@ -1368,7 +1423,7 @@ u32 sceIoPollAsync(int id, u32 address) {
f->hasAsyncResult = false; f->hasAsyncResult = false;
if (f->closePending) { if (f->closePending) {
kernelObjects.Destroy<FileNode>(id); __IoFreeFd(id, error);
} }
return 0; //completed return 0; //completed
} else { } else {
@ -1465,7 +1520,7 @@ u32 sceIoDclose(int id) {
int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen) int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen)
{ {
u32 error; u32 error;
FileNode *f = kernelObjects.Get<FileNode>(id, error); FileNode *f = __IoGetFd(id, error);
if (error) { if (error) {
ERROR_LOG(HLE, "UNIMPL %08x=sceIoIoctl id: %08x, cmd %08x, bad file", error, id, cmd); ERROR_LOG(HLE, "UNIMPL %08x=sceIoIoctl id: %08x, cmd %08x, bad file", error, id, cmd);
return error; return error;
@ -1596,7 +1651,7 @@ u32 sceIoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 ou
u32 sceIoIoctlAsync(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen) u32 sceIoIoctlAsync(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen)
{ {
u32 error; u32 error;
FileNode *f = kernelObjects.Get < FileNode > (id, error); FileNode *f = __IoGetFd(id, error);
if (f) { if (f) {
DEBUG_LOG(HLE, "sceIoIoctlAsync(%08x, %08x, %08x, %08x, %08x, %08x)", id, cmd, indataPtr, inlen, outdataPtr, outlen); DEBUG_LOG(HLE, "sceIoIoctlAsync(%08x, %08x, %08x, %08x, %08x, %08x)", id, cmd, indataPtr, inlen, outdataPtr, outlen);
f->asyncResult = __IoIoctl(id, cmd, indataPtr, inlen, outdataPtr, outlen); f->asyncResult = __IoIoctl(id, cmd, indataPtr, inlen, outdataPtr, outlen);
@ -1608,33 +1663,39 @@ u32 sceIoIoctlAsync(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u
} }
} }
struct GetFdListArg {
u32 outAddr;
int outLeft;
int total;
};
bool __GetFdListIterator(FileNode *f, GetFdListArg *state) {
++state->total;
if (Memory::IsValidAddress(state->outAddr) && state->outLeft > 0) {
Memory::Write_U32(f->GetUID(), state->outAddr);
state->outAddr += 4;
--state->outLeft;
}
return true;
}
u32 sceIoGetFdList(u32 outAddr, int outSize, u32 fdNumAddr) { u32 sceIoGetFdList(u32 outAddr, int outSize, u32 fdNumAddr) {
WARN_LOG(HLE, "sceIoGetFdList(%08x, %i, %08x)", outAddr, outSize, fdNumAddr); WARN_LOG(HLE, "sceIoGetFdList(%08x, %i, %08x)", outAddr, outSize, fdNumAddr);
GetFdListArg arg;
arg.outAddr = outAddr;
arg.outLeft = outSize;
arg.total = 0;
kernelObjects.Iterate(&__GetFdListIterator, &arg, PPSSPP_KERNEL_TMID_File); PSPPointer<SceUID> out;
out = outAddr;
int count = 0;
// Always have the first three.
for (int i = 0; i < PSP_MIN_FD; ++i) {
// TODO: Technically it seems like these are fixed ids > PSP_COUNT_FDS.
if (count < outSize && out.Valid()) {
out[count] = i;
}
++count;
}
for (int i = PSP_MIN_FD; i < PSP_COUNT_FDS; ++i) {
if (fds[i] == 0) {
continue;
}
if (count < outSize && out.Valid()) {
out[count] = i;
}
++count;
}
if (Memory::IsValidAddress(fdNumAddr)) if (Memory::IsValidAddress(fdNumAddr))
Memory::Write_U32(arg.total, fdNumAddr); Memory::Write_U32(count, fdNumAddr);
return outSize - arg.outLeft; if (count >= outSize) {
return outSize;
} else {
return count;
}
} }
// Presumably lets you hook up stderr to a MsgPipe. // Presumably lets you hook up stderr to a MsgPipe.