ppsspp/Core/HLE/sceIo.cpp
Unknown W. Brackets a8d918b50a Automatically mount exdata/ for remasters.
Using memstick/exdata/GAMEID/.
2015-12-28 14:13:05 -08:00

2482 lines
78 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cstdlib>
#include <set>
#include "thread/thread.h"
#include "thread/threadutil.h"
#include "profiler/profiler.h"
#include "Core/Core.h"
#include "Core/Config.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/MemMapHelpers.h"
#include "Core/System.h"
#include "Core/HDRemaster.h"
#include "Core/Host.h"
#include "Core/SaveState.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/sceKernel.h"
#include "Core/MIPS/MIPS.h"
#include "Core/HW/MemoryStick.h"
#include "Core/HW/AsyncIOManager.h"
#include "Core/CoreTiming.h"
#include "Core/Reporting.h"
#include "Core/FileSystems/FileSystem.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/FileSystems/ISOFileSystem.h"
#include "Core/FileSystems/DirectoryFileSystem.h"
extern "C" {
#include "ext/libkirk/amctrl.h"
};
#include "Core/HLE/sceIo.h"
#include "Core/HLE/sceRtc.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelMemory.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/HLE/sceKernelInterrupt.h"
#include "Core/HLE/KernelWaitHelpers.h"
// For headless screenshots.
#include "Core/HLE/sceDisplay.h"
const int ERROR_ERRNO_FILE_NOT_FOUND = 0x80010002;
const int ERROR_ERRNO_IO_ERROR = 0x80010005;
const int ERROR_ERRNO_FILE_ALREADY_EXISTS = 0x80010011;
const int ERROR_MEMSTICK_DEVCTL_BAD_PARAMS = 0x80220081;
const int ERROR_MEMSTICK_DEVCTL_TOO_MANY_CALLBACKS = 0x80220082;
const int ERROR_KERNEL_BAD_FILE_DESCRIPTOR = 0x80020323;
const int ERROR_PGD_INVALID_HEADER = 0x80510204;
/*
TODO: async io is missing features!
flash0: - fat access - system file volume
flash1: - fat access - configuration file volume
flashfat#: this too
lflash: - block access - entire flash
fatms: memstick
isofs: fat access - umd
disc0: fat access - umd
ms0: - fat access - memcard
umd: - block access - umd
irda?: - (?=0..9) block access - infra-red port (doesnt support seeking, maybe send/recieve data from port tho)
mscm0: - block access - memstick cm??
umd00: block access - umd
umd01: block access - umd
*/
#define O_RDONLY 0x0001
#define O_WRONLY 0x0002
#define O_RDWR 0x0003
#define O_NBLOCK 0x0010
#define O_APPEND 0x0100
#define O_CREAT 0x0200
#define O_TRUNC 0x0400
#define O_NOWAIT 0x8000
#define O_NPDRM 0x40000000
// chstat
#define SCE_CST_MODE 0x0001
#define SCE_CST_ATTR 0x0002
#define SCE_CST_SIZE 0x0004
#define SCE_CST_CT 0x0008
#define SCE_CST_AT 0x0010
#define SCE_CST_MT 0x0020
#define SCE_CST_PRVT 0x0040
typedef s32 SceMode;
typedef s64 SceOff;
typedef u64 SceIores;
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;
const int PSP_STDOUT = 1;
const int PSP_STDERR = 2;
const int PSP_STDIN = 3;
static int asyncNotifyEvent = -1;
static int syncNotifyEvent = -1;
static SceUID fds[PSP_COUNT_FDS];
static std::set<SceUID> memStickCallbacks;
static std::set<SceUID> memStickFatCallbacks;
static AsyncIOManager ioManager;
static bool ioManagerThreadEnabled = false;
static std::thread *ioManagerThread;
// TODO: Is it better to just put all on the thread?
// Let's try. (was 256)
const int IO_THREAD_MIN_DATA_SIZE = 0;
#define SCE_STM_FDIR 0x1000
#define SCE_STM_FREG 0x2000
#define SCE_STM_FLNK 0x4000
enum {
TYPE_DIR = 0x10,
TYPE_FILE = 0x20
};
struct SceIoStat {
SceMode_le st_mode;
u32_le st_attr;
SceOff_le st_size;
ScePspDateTime st_c_time;
ScePspDateTime st_a_time;
ScePspDateTime st_m_time;
u32_le st_private[6];
};
struct SceIoDirEnt {
SceIoStat d_stat;
char d_name[256];
u32_le d_private;
};
class FileNode : public KernelObject {
public:
FileNode() : callbackID(0), callbackArg(0), asyncResult(0), hasAsyncResult(false), pendingAsyncResult(false), sectorBlockMode(false), closePending(false), npdrm(0), pgdInfo(NULL) {}
~FileNode() {
pspFileSystem.CloseFile(handle);
pgd_close(pgdInfo);
}
const char *GetName() override { return fullpath.c_str(); }
const char *GetTypeName() override { return "OpenFile"; }
void GetQuickInfo(char *ptr, int size) override {
sprintf(ptr, "Seekpos: %08x", (u32)pspFileSystem.GetSeekPos(handle));
}
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_BADF; }
static int GetStaticIDType() { return PPSSPP_KERNEL_TMID_File; }
int GetIDType() const override { return PPSSPP_KERNEL_TMID_File; }
bool asyncBusy() {
return pendingAsyncResult || hasAsyncResult;
}
void DoState(PointerWrap &p) override {
auto s = p.Section("FileNode", 1, 2);
if (!s)
return;
p.Do(fullpath);
p.Do(handle);
p.Do(callbackID);
p.Do(callbackArg);
p.Do(asyncResult);
p.Do(hasAsyncResult);
p.Do(pendingAsyncResult);
p.Do(sectorBlockMode);
p.Do(closePending);
p.Do(info);
p.Do(openMode);
p.Do(npdrm);
p.Do(pgd_offset);
bool hasPGD = pgdInfo != NULL;
p.Do(hasPGD);
if (hasPGD) {
if (p.mode == p.MODE_READ) {
pgdInfo = (PGD_DESC*) malloc(sizeof(PGD_DESC));
}
p.DoVoid(pgdInfo, sizeof(PGD_DESC));
if (p.mode == p.MODE_READ) {
pgdInfo->block_buf = (u8 *)malloc(pgdInfo->block_size * 2);
}
}
p.Do(waitingThreads);
if (s >= 2) {
p.Do(waitingSyncThreads);
}
p.Do(pausedWaits);
}
std::string fullpath;
u32 handle;
u32 callbackID;
u32 callbackArg;
s64 asyncResult;
bool hasAsyncResult;
bool pendingAsyncResult;
bool sectorBlockMode;
// TODO: Use an enum instead?
bool closePending;
PSPFileInfo info;
u32 openMode;
u32 npdrm;
u32 pgd_offset;
PGD_DESC *pgdInfo;
std::vector<SceUID> waitingThreads;
std::vector<SceUID> waitingSyncThreads;
// Key is the callback id it was for, or if no callback, the thread id.
// Value is actually meaningless but kept for consistency with other wait types.
std::map<SceUID, u64> pausedWaits;
};
/******************************************************************************/
/******************************************************************************/
u64 __IoCompleteAsyncIO(FileNode *f);
static void TellFsThreadEnded (SceUID threadID) {
pspFileSystem.ThreadEnded(threadID);
}
static 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);
}
static 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;
}
static void __IoFreeFd(int fd, u32 &error) {
if (fd < PSP_MIN_FD || fd >= PSP_COUNT_FDS) {
error = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
} else {
FileNode *f = __IoGetFd(fd, error);
if (f) {
// Wake anyone waiting on the file before closing it.
for (size_t i = 0; i < f->waitingThreads.size(); ++i) {
HLEKernel::ResumeFromWait(f->waitingThreads[i], WAITTYPE_ASYNCIO, f->GetUID(), (int)SCE_KERNEL_ERROR_WAIT_DELETE);
}
CoreTiming::UnscheduleEvent(asyncNotifyEvent, fd);
for (size_t i = 0; i < f->waitingSyncThreads.size(); ++i) {
CoreTiming::UnscheduleEvent(syncNotifyEvent, ((u64)f->waitingSyncThreads[i] << 32) | fd);
}
PROFILE_THIS_SCOPE("io_rw");
// Discard any pending results.
AsyncIOResult managerResult;
ioManager.WaitResult(f->handle, managerResult);
}
error = kernelObjects.Destroy<FileNode>(fds[fd]);
fds[fd] = 0;
}
}
// Async IO seems to work roughly like this:
// 1. Game calls SceIo*Async() to start the process.
// 2. This runs a thread with a customizable priority.
// 3. The operation runs, which takes an inconsistent amount of time from UMD.
// 4. Once done (regardless of other syscalls), the fd-registered callback is notified.
// 5. The game can find out via *CB() or sceKernelCheckCallback().
// 6. At this point, the fd is STILL not usable.
// 7. One must call sceIoWaitAsync / sceIoWaitAsyncCB / sceIoPollAsync / possibly sceIoGetAsyncStat.
// 8. Finally, the fd is usable (or closed via sceIoCloseAsync.) Presumably the io thread has joined now.
// TODO: Closed files are a bit special: until the fd is reused (?), the async result is still available.
// Clearly a buffer is used, it doesn't seem like they are actually kernel objects.
// TODO: We don't do any of that yet.
// For now, let's at least delay the callback notification.
static void __IoAsyncNotify(u64 userdata, int cyclesLate) {
int fd = (int) userdata;
u32 error;
FileNode *f = __IoGetFd(fd, error);
if (!f) {
ERROR_LOG_REPORT(SCEIO, "__IoAsyncNotify: file no longer exists?");
return;
}
if (g_Config.iIOTimingMethod == IOTIMING_HOST) {
// Not all async operations actually queue up. Maybe should separate them?
if (!ioManager.HasResult(f->handle) && ioManager.HasOperation(f->handle)) {
// Try again in another 0.5ms until the IO completes on the host.
CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, asyncNotifyEvent, userdata);
return;
}
__IoCompleteAsyncIO(f);
} else if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) {
u64 finishTicks = __IoCompleteAsyncIO(f);
if (finishTicks > CoreTiming::GetTicks()) {
// Reschedule for later, since we now know how long it ought to take.
CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), asyncNotifyEvent, userdata);
return;
}
} else {
__IoCompleteAsyncIO(f);
}
if (f->waitingThreads.empty()) {
return;
}
SceUID threadID = f->waitingThreads.front();
f->waitingThreads.erase(f->waitingThreads.begin());
u32 address = __KernelGetWaitValue(threadID, error);
if (HLEKernel::VerifyWait(threadID, WAITTYPE_ASYNCIO, f->GetUID())) {
HLEKernel::ResumeFromWait(threadID, WAITTYPE_ASYNCIO, f->GetUID(), 0);
// Someone woke up, so it's no longer got one.
f->hasAsyncResult = false;
if (Memory::IsValidAddress(address)) {
Memory::Write_U64((u64) f->asyncResult, address);
}
// If this was a sceIoCloseAsync, we should close it at this point.
if (f->closePending) {
__IoFreeFd(fd, error);
}
}
}
static void __IoSyncNotify(u64 userdata, int cyclesLate) {
PROFILE_THIS_SCOPE("io_rw");
SceUID threadID = userdata >> 32;
int fd = (int) (userdata & 0xFFFFFFFF);
s64 result = -1;
u32 error;
FileNode *f = __IoGetFd(fd, error);
if (!f) {
ERROR_LOG_REPORT(SCEIO, "__IoSyncNotify: file no longer exists?");
return;
}
if (g_Config.iIOTimingMethod == IOTIMING_HOST) {
if (!ioManager.HasResult(f->handle)) {
// Try again in another 0.5ms until the IO completes on the host.
CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, syncNotifyEvent, userdata);
return;
}
} else if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) {
u64 finishTicks = ioManager.ResultFinishTicks(f->handle);
if (finishTicks > CoreTiming::GetTicks()) {
// Reschedule for later when the result should finish.
CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), syncNotifyEvent, userdata);
return;
}
}
f->pendingAsyncResult = false;
f->hasAsyncResult = false;
AsyncIOResult managerResult;
if (ioManager.WaitResult(f->handle, managerResult)) {
result = managerResult.result;
} else {
ERROR_LOG(SCEIO, "Unable to complete IO operation on %s", f->GetName());
}
f->pendingAsyncResult = false;
f->hasAsyncResult = false;
HLEKernel::ResumeFromWait(threadID, WAITTYPE_IO, fd, result);
f->waitingSyncThreads.erase(std::remove(f->waitingSyncThreads.begin(), f->waitingSyncThreads.end(), threadID), f->waitingSyncThreads.end());
}
static void __IoAsyncBeginCallback(SceUID threadID, SceUID prevCallbackId) {
auto result = HLEKernel::WaitBeginCallback<FileNode, WAITTYPE_ASYNCIO, SceUID>(threadID, prevCallbackId, -1);
if (result == HLEKernel::WAIT_CB_SUCCESS) {
DEBUG_LOG(SCEIO, "sceIoWaitAsync: Suspending wait for callback");
} else if (result == HLEKernel::WAIT_CB_BAD_WAIT_ID) {
WARN_LOG_REPORT(SCEIO, "sceIoWaitAsync: beginning callback with bad wait id?");
}
}
static bool __IoCheckAsyncWait(FileNode *f, SceUID threadID, u32 &error, int result, bool &wokeThreads)
{
int fd = -1;
for (int i = 0; i < (int)ARRAY_SIZE(fds); ++i) {
if (fds[i] == f->GetUID()) {
fd = i;
break;
}
}
if (fd == -1) {
ERROR_LOG_REPORT(SCEIO, "__IoCheckAsyncWait: could not find io handle");
return true;
}
if (!HLEKernel::VerifyWait(threadID, WAITTYPE_ASYNCIO, f->GetUID())) {
return true;
}
// If result is an error code, we're just letting it go.
if (result == 0) {
if (f->pendingAsyncResult || !f->hasAsyncResult) {
return false;
}
u32 address = __KernelGetWaitValue(threadID, error);
Memory::Write_U64((u64) f->asyncResult, address);
f->hasAsyncResult = false;
if (f->closePending) {
__IoFreeFd(fd, error);
}
}
__KernelResumeThreadFromWait(threadID, result);
wokeThreads = true;
return true;
}
static void __IoAsyncEndCallback(SceUID threadID, SceUID prevCallbackId) {
auto result = HLEKernel::WaitEndCallback<FileNode, WAITTYPE_ASYNCIO, SceUID>(threadID, prevCallbackId, -1, __IoCheckAsyncWait);
if (result == HLEKernel::WAIT_CB_RESUMED_WAIT) {
DEBUG_LOG(SCEIO, "sceKernelWaitEventFlagCB: Resuming lock wait for callback");
}
}
static DirectoryFileSystem *memstickSystem = nullptr;
static DirectoryFileSystem *exdataSystem = nullptr;
#if defined(USING_WIN_UI) || defined(APPLE)
static DirectoryFileSystem *flash0System = nullptr;
#else
static VFSFileSystem *flash0System = nullptr;
#endif
static void __IoManagerThread() {
setCurrentThreadName("IO");
while (ioManagerThreadEnabled && coreState != CORE_ERROR && coreState != CORE_POWERDOWN) {
ioManager.RunEventsUntil(CoreTiming::GetTicks() + msToCycles(1000));
}
}
static void __IoWakeManager() {
// Ping the thread so that it knows to check coreState.
ioManagerThreadEnabled = false;
ioManager.FinishEventLoop();
}
void __IoInit() {
MemoryStick_Init();
asyncNotifyEvent = CoreTiming::RegisterEvent("IoAsyncNotify", __IoAsyncNotify);
syncNotifyEvent = CoreTiming::RegisterEvent("IoSyncNotify", __IoSyncNotify);
memstickSystem = new DirectoryFileSystem(&pspFileSystem, g_Config.memStickDirectory, FILESYSTEM_SIMULATE_FAT32);
#if defined(USING_WIN_UI) || defined(APPLE)
flash0System = new DirectoryFileSystem(&pspFileSystem, g_Config.flash0Directory);
#else
flash0System = new VFSFileSystem(&pspFileSystem, "flash0");
#endif
pspFileSystem.Mount("ms0:", memstickSystem);
pspFileSystem.Mount("fatms0:", memstickSystem);
pspFileSystem.Mount("fatms:", memstickSystem);
pspFileSystem.Mount("pfat0:", memstickSystem);
pspFileSystem.Mount("flash0:", flash0System);
if (g_RemasterMode) {
const std::string gameId = g_paramSFO.GetValueString("DISC_ID");
const std::string exdataPath = g_Config.memStickDirectory + "exdata/" + gameId + "/";
if (File::Exists(exdataPath)) {
exdataSystem = new DirectoryFileSystem(&pspFileSystem, exdataPath, FILESYSTEM_SIMULATE_FAT32);
pspFileSystem.Mount("exdata0:", exdataSystem);
INFO_LOG(SCEIO, "Mounted exdata/%s/ under memstick for exdata0:/", gameId.c_str());
} else {
INFO_LOG(SCEIO, "Did not find exdata/%s/ under memstick for exdata0:/", gameId.c_str());
}
}
__KernelListenThreadEnd(&TellFsThreadEnded);
memset(fds, 0, sizeof(fds));
ioManagerThreadEnabled = g_Config.bSeparateIOThread;
ioManager.SetThreadEnabled(ioManagerThreadEnabled);
if (ioManagerThreadEnabled) {
Core_ListenShutdown(&__IoWakeManager);
ioManagerThread = new std::thread(&__IoManagerThread);
#ifdef _XBOX
SuspendThread(ioManagerThread->native_handle());
XSetThreadProcessor(ioManagerThread->native_handle(), 4);
ResumeThread(ioManagerThread->native_handle());
#endif
ioManagerThread->detach();
}
__KernelRegisterWaitTypeFuncs(WAITTYPE_ASYNCIO, __IoAsyncBeginCallback, __IoAsyncEndCallback);
}
void __IoDoState(PointerWrap &p) {
auto s = p.Section("sceIo", 1);
if (!s)
return;
ioManager.DoState(p);
p.DoArray(fds, ARRAY_SIZE(fds));
p.Do(asyncNotifyEvent);
CoreTiming::RestoreRegisterEvent(asyncNotifyEvent, "IoAsyncNotify", __IoAsyncNotify);
p.Do(syncNotifyEvent);
CoreTiming::RestoreRegisterEvent(syncNotifyEvent, "IoSyncNotify", __IoSyncNotify);
p.Do(memStickCallbacks);
p.Do(memStickFatCallbacks);
}
void __IoShutdown() {
ioManagerThreadEnabled = false;
ioManager.SyncThread();
ioManager.FinishEventLoop();
if (ioManagerThread != NULL) {
delete ioManagerThread;
ioManagerThread = NULL;
ioManager.Shutdown();
}
pspFileSystem.Unmount("ms0:", memstickSystem);
pspFileSystem.Unmount("fatms0:", memstickSystem);
pspFileSystem.Unmount("fatms:", memstickSystem);
pspFileSystem.Unmount("pfat0:", memstickSystem);
pspFileSystem.Unmount("flash0:", flash0System);
if (g_RemasterMode && exdataSystem) {
pspFileSystem.Unmount("exdata0:", exdataSystem);
delete exdataSystem;
exdataSystem = nullptr;
}
delete memstickSystem;
memstickSystem = nullptr;
delete flash0System;
flash0System = nullptr;
memStickCallbacks.clear();
memStickFatCallbacks.clear();
}
u32 __IoGetFileHandleFromId(u32 id, u32 &outError)
{
FileNode *f = __IoGetFd(id, outError);
if (!f) {
return (u32)-1;
}
return f->handle;
}
static u32 sceIoAssign(u32 alias_addr, u32 physical_addr, u32 filesystem_addr, int mode, u32 arg_addr, int argSize)
{
std::string alias = Memory::GetCharPointer(alias_addr);
std::string physical_dev = Memory::GetCharPointer(physical_addr);
std::string filesystem_dev = Memory::GetCharPointer(filesystem_addr);
std::string perm;
switch (mode) {
case 0:
perm = "IOASSIGN_RDWR";
break;
case 1:
perm = "IOASSIGN_RDONLY";
break;
default:
perm = "unhandled";
break;
}
WARN_LOG_REPORT(SCEIO, "sceIoAssign(%s, %s, %s, %s, %08x, %i)", alias.c_str(), physical_dev.c_str(), filesystem_dev.c_str(), perm.c_str(), arg_addr, argSize);
return 0;
}
static u32 sceIoUnassign(const char *alias)
{
WARN_LOG_REPORT(SCEIO, "sceIoUnassign(%s)", alias);
return 0;
}
static u32 sceKernelStdin() {
DEBUG_LOG(SCEIO, "%d=sceKernelStdin()", PSP_STDIN);
return PSP_STDIN;
}
static u32 sceKernelStdout() {
DEBUG_LOG(SCEIO, "%d=sceKernelStdout()", PSP_STDOUT);
return PSP_STDOUT;
}
static u32 sceKernelStderr() {
DEBUG_LOG(SCEIO, "%d=sceKernelStderr()", PSP_STDERR);
return PSP_STDERR;
}
u64 __IoCompleteAsyncIO(FileNode *f) {
PROFILE_THIS_SCOPE("io_rw");
if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) {
u64 finishTicks = ioManager.ResultFinishTicks(f->handle);
if (finishTicks > CoreTiming::GetTicks()) {
return finishTicks;
}
}
AsyncIOResult managerResult;
if (ioManager.WaitResult(f->handle, managerResult)) {
f->asyncResult = managerResult.result;
} else {
// It's okay, not all operations are deferred.
}
if (f->callbackID) {
__KernelNotifyCallback(f->callbackID, f->callbackArg);
}
f->pendingAsyncResult = false;
f->hasAsyncResult = true;
return 0;
}
void __IoCopyDate(ScePspDateTime& date_out, const tm& date_in)
{
date_out.year = date_in.tm_year+1900;
date_out.month = date_in.tm_mon+1;
date_out.day = date_in.tm_mday;
date_out.hour = date_in.tm_hour;
date_out.minute = date_in.tm_min;
date_out.second = date_in.tm_sec;
date_out.microsecond = 0;
}
static void __IoGetStat(SceIoStat *stat, PSPFileInfo &info) {
memset(stat, 0xfe, sizeof(SceIoStat));
stat->st_size = (s64) info.size;
int type, attr;
if (info.type & FILETYPE_DIRECTORY)
type = SCE_STM_FDIR, attr = TYPE_DIR;
else
type = SCE_STM_FREG, attr = TYPE_FILE;
stat->st_mode = type | info.access;
stat->st_attr = attr;
stat->st_size = info.size;
__IoCopyDate(stat->st_a_time, info.atime);
__IoCopyDate(stat->st_c_time, info.ctime);
__IoCopyDate(stat->st_m_time, info.mtime);
stat->st_private[0] = info.startSector;
}
static void __IoSchedAsync(FileNode *f, int fd, int usec) {
CoreTiming::ScheduleEvent(usToCycles(usec), asyncNotifyEvent, fd);
f->pendingAsyncResult = true;
f->hasAsyncResult = false;
}
static void __IoSchedSync(FileNode *f, int fd, int usec) {
u64 param = ((u64)__KernelGetCurThread()) << 32 | fd;
CoreTiming::ScheduleEvent(usToCycles(usec), syncNotifyEvent, param);
f->pendingAsyncResult = false;
f->hasAsyncResult = false;
}
static u32 sceIoGetstat(const char *filename, u32 addr) {
// TODO: Improve timing (although this seems normally slow..)
int usec = 1000;
SceIoStat stat;
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
if (info.exists) {
__IoGetStat(&stat, info);
if (Memory::IsValidAddress(addr)) {
Memory::WriteStruct(addr, &stat);
DEBUG_LOG(SCEIO, "sceIoGetstat(%s, %08x) : sector = %08x", filename, addr, info.startSector);
return hleDelayResult(0, "io getstat", usec);
} else {
ERROR_LOG(SCEIO, "sceIoGetstat(%s, %08x) : bad address", filename, addr);
return hleDelayResult(-1, "io getstat", usec);
}
} else {
DEBUG_LOG(SCEIO, "sceIoGetstat(%s, %08x) : FILE NOT FOUND", filename, addr);
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "io getstat", usec);
}
}
static u32 sceIoChstat(const char *filename, u32 iostatptr, u32 changebits) {
ERROR_LOG(SCEIO, "UNIMPL sceIoChstat(%s, %08x, %08x)", filename, iostatptr, changebits);
if (changebits & SCE_CST_MODE)
ERROR_LOG(SCEIO, "sceIoChstat: change mode requested");
if (changebits & SCE_CST_ATTR)
ERROR_LOG(SCEIO, "sceIoChstat: change attr requested");
if (changebits & SCE_CST_SIZE)
ERROR_LOG(SCEIO, "sceIoChstat: change size requested");
if (changebits & SCE_CST_CT)
ERROR_LOG(SCEIO, "sceIoChstat: change creation time requested");
if (changebits & SCE_CST_AT)
ERROR_LOG(SCEIO, "sceIoChstat: change access time requested");
if (changebits & SCE_CST_MT)
ERROR_LOG(SCEIO, "sceIoChstat: change modification time requested");
if (changebits & SCE_CST_PRVT)
ERROR_LOG(SCEIO, "sceIoChstat: change private data requested");
return 0;
}
static u32 npdrmRead(FileNode *f, u8 *data, int size) {
PGD_DESC *pgd = f->pgdInfo;
u32 block, offset, blockPos;
u32 remain_size, copy_size;
block = pgd->file_offset/pgd->block_size;
offset = pgd->file_offset%pgd->block_size;
remain_size = size;
while(remain_size){
if(pgd->current_block!=block){
blockPos = block*pgd->block_size;
pspFileSystem.SeekFile(f->handle, (s32)pgd->data_offset+blockPos, FILEMOVE_BEGIN);
pspFileSystem.ReadFile(f->handle, pgd->block_buf, pgd->block_size);
pgd_decrypt_block(pgd, block);
pgd->current_block = block;
}
if(offset+remain_size>pgd->block_size){
copy_size = pgd->block_size-offset;
memcpy(data, pgd->block_buf+offset, copy_size);
block += 1;
offset = 0;
} else {
copy_size = remain_size;
memcpy(data, pgd->block_buf+offset, copy_size);
}
data += copy_size;
remain_size -= copy_size;
pgd->file_offset += copy_size;
}
return size;
}
static bool __IoRead(int &result, int id, u32 data_addr, int size, int &us) {
PROFILE_THIS_SCOPE("io_rw");
// Low estimate, may be improved later from the ReadFile result.
us = size / 100;
if (us < 100) {
us = 100;
}
if (id == PSP_STDIN) {
DEBUG_LOG(SCEIO, "sceIoRead STDIN");
result = 0; //stdin
return true;
}
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
result = SCE_KERNEL_ERROR_ASYNC_BUSY;
return true;
}
if (!(f->openMode & FILEACCESS_READ)) {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
return true;
} else if (size < 0) {
result = SCE_KERNEL_ERROR_ILLEGAL_ADDR;
return true;
} else if (Memory::IsValidAddress(data_addr)) {
CBreakPoints::ExecMemCheck(data_addr, true, size, currentMIPS->pc);
u8 *data = (u8*) Memory::GetPointer(data_addr);
if (f->npdrm) {
result = npdrmRead(f, data, size);
currentMIPS->InvalidateICache(data_addr, size);
return true;
}
bool useThread = __KernelIsDispatchEnabled() && ioManagerThreadEnabled && size > IO_THREAD_MIN_DATA_SIZE;
if (useThread) {
// If there's a pending operation on this file, wait for it to finish and don't overwrite it.
useThread = !ioManager.HasOperation(f->handle);
if (!useThread) {
ioManager.SyncThread();
}
}
if (useThread) {
AsyncIOEvent ev = IO_EVENT_READ;
ev.handle = f->handle;
ev.buf = data;
ev.bytes = size;
ev.invalidateAddr = data_addr;
ioManager.ScheduleOperation(ev);
return false;
} else {
if (g_Config.iIOTimingMethod != IOTIMING_REALISTIC) {
result = (int) pspFileSystem.ReadFile(f->handle, data, size);
} else {
result = (int) pspFileSystem.ReadFile(f->handle, data, size, us);
}
currentMIPS->InvalidateICache(data_addr, size);
return true;
}
} else {
if (size != 0) {
// TODO: For some combinations of bad pointer + size, SCE_KERNEL_ERROR_ILLEGAL_ADDR.
// Seems like only for kernel RAM. For most cases, it really is -1.
result = -1;
} else {
result = 0;
}
return true;
}
} else {
result = error;
return true;
}
}
static u32 sceIoRead(int id, u32 data_addr, int size) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (id > 2 && f != NULL) {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "sceIoRead(%d, %08x, %x): dispatch disabled", id, data_addr, size);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
if (__IsInInterrupt()) {
DEBUG_LOG(SCEIO, "sceIoRead(%d, %08x, %x): inside interrupt", id, data_addr, size);
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
}
int result;
int us;
bool complete = __IoRead(result, id, data_addr, size, us);
if (!complete) {
DEBUG_LOG(SCEIO, "sceIoRead(%d, %08x, %x): deferring result", id, data_addr, size);
__IoSchedSync(f, id, us);
__KernelWaitCurThread(WAITTYPE_IO, id, 0, 0, false, "io read");
f->waitingSyncThreads.push_back(__KernelGetCurThread());
return 0;
} else if (result >= 0) {
DEBUG_LOG(SCEIO, "%x=sceIoRead(%d, %08x, %x)", result, id, data_addr, size);
return hleDelayResult(result, "io read", us);
} else {
WARN_LOG(SCEIO, "sceIoRead(%d, %08x, %x): error %08x", id, data_addr, size, result);
return result;
}
}
static u32 sceIoReadAsync(int id, u32 data_addr, int size) {
// TODO: Technically we shouldn't read into the buffer yet...
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoReadAsync(%d, %08x, %x): async busy", id, data_addr, size);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
int result;
int us;
bool complete = __IoRead(result, id, data_addr, size, us);
if (complete) {
f->asyncResult = result;
DEBUG_LOG(SCEIO, "%llx=sceIoReadAsync(%d, %08x, %x)", f->asyncResult, id, data_addr, size);
} else {
DEBUG_LOG(SCEIO, "sceIoReadAsync(%d, %08x, %x): deferring result", id, data_addr, size);
}
__IoSchedAsync(f, id, us);
return 0;
} else {
ERROR_LOG(SCEIO, "sceIoReadAsync: bad file %d", id);
return error;
}
}
static bool __IoWrite(int &result, int id, u32 data_addr, int size, int &us) {
PROFILE_THIS_SCOPE("io_rw");
// Low estimate, may be improved later from the WriteFile result.
us = size / 100;
if (us < 100) {
us = 100;
}
const void *data_ptr = Memory::GetPointer(data_addr);
// Let's handle stdout/stderr specially.
if (id == PSP_STDOUT || id == PSP_STDERR) {
const char *str = (const char *) data_ptr;
const int str_size = size == 0 ? 0 : (str[size - 1] == '\n' ? size - 1 : size);
INFO_LOG(SCEIO, "%s: %.*s", id == 1 ? "stdout" : "stderr", str_size, str);
result = size;
return true;
}
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
result = SCE_KERNEL_ERROR_ASYNC_BUSY;
return true;
}
if (!(f->openMode & FILEACCESS_WRITE)) {
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
return true;
}
if (size < 0) {
result = SCE_KERNEL_ERROR_ILLEGAL_ADDR;
return true;
}
CBreakPoints::ExecMemCheck(data_addr, false, size, currentMIPS->pc);
bool useThread = __KernelIsDispatchEnabled() && ioManagerThreadEnabled && size > IO_THREAD_MIN_DATA_SIZE;
if (useThread) {
// If there's a pending operation on this file, wait for it to finish and don't overwrite it.
useThread = !ioManager.HasOperation(f->handle);
if (!useThread) {
ioManager.SyncThread();
}
}
if (useThread) {
AsyncIOEvent ev = IO_EVENT_WRITE;
ev.handle = f->handle;
ev.buf = (u8 *) data_ptr;
ev.bytes = size;
ev.invalidateAddr = 0;
ioManager.ScheduleOperation(ev);
return false;
} else {
if (g_Config.iIOTimingMethod != IOTIMING_REALISTIC) {
result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size);
} else {
result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size, us);
}
}
return true;
} else {
ERROR_LOG(SCEIO, "sceIoWrite ERROR: no file open");
result = (s32) error;
return true;
}
}
static u32 sceIoWrite(int id, u32 data_addr, int size) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (id > 2 && f != NULL) {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "sceIoWrite(%d, %08x, %x): dispatch disabled", id, data_addr, size);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
if (__IsInInterrupt()) {
DEBUG_LOG(SCEIO, "sceIoWrite(%d, %08x, %x): inside interrupt", id, data_addr, size);
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
}
int result;
int us;
bool complete = __IoWrite(result, id, data_addr, size, us);
if (!complete) {
DEBUG_LOG(SCEIO, "sceIoWrite(%d, %08x, %x): deferring result", id, data_addr, size);
__IoSchedSync(f, id, us);
__KernelWaitCurThread(WAITTYPE_IO, id, 0, 0, false, "io write");
f->waitingSyncThreads.push_back(__KernelGetCurThread());
return 0;
} else if (result >= 0) {
DEBUG_LOG(SCEIO, "%x=sceIoWrite(%d, %08x, %x)", result, id, data_addr, size);
if (__KernelIsDispatchEnabled()) {
// If we wrote to stdout, return an error (even though we did log it) rather than delaying.
// On actual hardware, it would just return this... we just want the log output.
if (__IsInInterrupt()) {
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
return hleDelayResult(result, "io write", us);
} else {
return result;
}
} else {
WARN_LOG(SCEIO, "sceIoWrite(%d, %08x, %x): error %08x", id, data_addr, size, result);
return result;
}
}
static u32 sceIoWriteAsync(int id, u32 data_addr, int size) {
// TODO: Technically we shouldn't read from the buffer yet...
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoWriteAsync(%d, %08x, %x): async busy", id, data_addr, size);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
int result;
int us;
bool complete = __IoWrite(result, id, data_addr, size, us);
if (complete) {
f->asyncResult = result;
DEBUG_LOG(SCEIO, "%llx=sceIoWriteAsync(%d, %08x, %x)", f->asyncResult, id, data_addr, size);
} else {
DEBUG_LOG(SCEIO, "sceIoWriteAsync(%d, %08x, %x): deferring result", id, data_addr, size);
}
__IoSchedAsync(f, id, us);
return 0;
} else {
ERROR_LOG(SCEIO, "sceIoWriteAsync: bad file %d", id);
return error;
}
}
static u32 sceIoGetDevType(int id) {
if (id == PSP_STDOUT || id == PSP_STDERR || id == PSP_STDIN) {
DEBUG_LOG(SCEIO, "sceIoGetDevType(%d)", id);
return PSP_DEV_TYPE_FILE;
}
u32 error;
FileNode *f = __IoGetFd(id, error);
int result;
if (f) {
// TODO: When would this return PSP_DEV_TYPE_ALIAS?
WARN_LOG(SCEIO, "sceIoGetDevType(%d - %s)", id, f->fullpath.c_str());
result = pspFileSystem.DevType(f->handle);
} else {
ERROR_LOG(SCEIO, "sceIoGetDevType: unknown id %d", id);
result = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
return result;
}
static u32 sceIoCancel(int id)
{
ERROR_LOG_REPORT(SCEIO, "UNIMPL sceIoCancel(%d)", id);
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
// TODO: Cancel the async operation if possible?
} else {
ERROR_LOG(SCEIO, "sceIoCancel: unknown id %d", id);
error = ERROR_KERNEL_BAD_FILE_DESCRIPTOR;
}
return error;
}
static u32 npdrmLseek(FileNode *f, s32 where, FileMove whence)
{
u32 newPos, blockPos;
if(whence==FILEMOVE_BEGIN){
newPos = where;
}else if(whence==FILEMOVE_CURRENT){
newPos = f->pgdInfo->file_offset+where;
}else{
newPos = f->pgdInfo->data_size+where;
}
if (newPos > f->pgdInfo->data_size)
return -EINVAL;
f->pgdInfo->file_offset = newPos;
blockPos = newPos&~(f->pgdInfo->block_size-1);
pspFileSystem.SeekFile(f->handle, (s32)f->pgdInfo->data_offset+blockPos, whence);
return newPos;
}
static s64 __IoLseekDest(FileNode *f, s64 offset, int whence, FileMove &seek) {
PROFILE_THIS_SCOPE("io_rw");
seek = FILEMOVE_BEGIN;
// Let's make sure this isn't incorrect mid-operation.
if (ioManager.HasOperation(f->handle)) {
ioManager.SyncThread();
}
s64 newPos = 0;
switch (whence) {
case 0:
newPos = offset;
break;
case 1:
newPos = pspFileSystem.GetSeekPos(f->handle) + offset;
seek = FILEMOVE_CURRENT;
break;
case 2:
newPos = f->info.size + offset;
seek = FILEMOVE_END;
break;
default:
return (s32)SCE_KERNEL_ERROR_INVAL;
}
// Yes, -1 is the correct return code for this case.
if (newPos < 0)
return -1;
return newPos;
}
static s64 __IoLseek(SceUID id, s64 offset, int whence) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoLseek*(%d, %llx, %i): async busy", id, offset, whence);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
FileMove seek;
s64 newPos = __IoLseekDest(f, offset, whence, seek);
if(f->npdrm)
return npdrmLseek(f, (s32)offset, seek);
if (newPos < 0)
return newPos;
return pspFileSystem.SeekFile(f->handle, (s32) offset, seek);
} else {
return (s32) error;
}
}
static s64 sceIoLseek(int id, s64 offset, int whence) {
s64 result = __IoLseek(id, offset, whence);
if (result >= 0 || result == -1) {
DEBUG_LOG(SCEIO, "%lli = sceIoLseek(%d, %llx, %i)", result, id, offset, whence);
// Educated guess at timing.
return hleDelayResult(result, "io seek", 100);
} else {
ERROR_LOG(SCEIO, "sceIoLseek(%d, %llx, %i) - ERROR: invalid file", id, offset, whence);
return result;
}
}
static u32 sceIoLseek32(int id, int offset, int whence) {
s32 result = (s32) __IoLseek(id, offset, whence);
if (result >= 0 || result == -1) {
DEBUG_LOG(SCEIO, "%i = sceIoLseek32(%d, %x, %i)", result, id, offset, whence);
// Educated guess at timing.
return hleDelayResult(result, "io seek", 100);
} else {
ERROR_LOG(SCEIO, "sceIoLseek32(%d, %x, %i) - ERROR: invalid file", id, offset, whence);
return result;
}
}
static u32 sceIoLseekAsync(int id, s64 offset, int whence) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (whence < 0 || whence > 2) {
WARN_LOG(SCEIO, "sceIoLseekAsync(%d, %llx, %i): invalid whence", id, offset, whence);
return SCE_KERNEL_ERROR_INVAL;
}
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoLseekAsync(%d, %llx, %i): async busy", id, offset, whence);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
f->asyncResult = __IoLseek(id, offset, whence);
// Educated guess at timing.
__IoSchedAsync(f, id, 100);
DEBUG_LOG(SCEIO, "%lli = sceIoLseekAsync(%d, %llx, %i)", f->asyncResult, id, offset, whence);
return 0;
} else {
ERROR_LOG(SCEIO, "sceIoLseekAsync(%d, %llx, %i) - ERROR: invalid file", id, offset, whence);
return error;
}
return 0;
}
static u32 sceIoLseek32Async(int id, int offset, int whence) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (whence < 0 || whence > 2) {
WARN_LOG(SCEIO, "sceIoLseek32Async(%d, %x, %i): invalid whence", id, offset, whence);
return SCE_KERNEL_ERROR_INVAL;
}
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoLseek*(%d, %x, %i): async busy", id, offset, whence);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
f->asyncResult = __IoLseek(id, offset, whence);
// Educated guess at timing.
__IoSchedAsync(f, id, 100);
DEBUG_LOG(SCEIO, "%lli = sceIoLseek32Async(%d, %x, %i)", f->asyncResult, id, offset, whence);
return 0;
} else {
ERROR_LOG(SCEIO, "sceIoLseek32Async(%d, %x, %i) - ERROR: invalid file", id, offset, whence);
return error;
}
return 0;
}
static FileNode *__IoOpen(int &error, const char* filename, int flags, int mode) {
//memory stick filename
int access = FILEACCESS_NONE;
if (flags & O_RDONLY)
access |= FILEACCESS_READ;
if (flags & O_WRONLY)
access |= FILEACCESS_WRITE;
if (flags & O_APPEND)
access |= FILEACCESS_APPEND;
if (flags & O_CREAT)
access |= FILEACCESS_CREATE;
if (flags & O_TRUNC)
access |= FILEACCESS_TRUNCATE;
PSPFileInfo info = pspFileSystem.GetFileInfo(filename);
u32 h = pspFileSystem.OpenWithError(error, filename, (FileAccess) access);
if (h == 0) {
return NULL;
}
FileNode *f = new FileNode();
SceUID id = kernelObjects.Create(f);
f->handle = h;
f->fullpath = filename;
f->asyncResult = id;
f->info = info;
f->openMode = access;
f->npdrm = (flags & O_NPDRM)? true: false;
f->pgd_offset = 0;
return f;
}
static u32 sceIoOpen(const char *filename, int flags, int mode) {
if (!__KernelIsDispatchEnabled())
return -1;
int error;
FileNode *f = __IoOpen(error, filename, flags, mode);
if (f == NULL)
{
// Timing is not accurate, aiming low for now.
if (error == (int)SCE_KERNEL_ERROR_NOCWD)
{
ERROR_LOG(SCEIO, "SCE_KERNEL_ERROR_NOCWD=sceIoOpen(%s, %08x, %08x) - no current working directory", filename, flags, mode);
return hleDelayResult(SCE_KERNEL_ERROR_NOCWD, "no cwd", 10000);
}
else if (error != 0)
{
ERROR_LOG(SCEIO, "%08x=sceIoOpen(%s, %08x, %08x)", error, filename, flags, mode);
return hleDelayResult(error, "file opened", 10000);
}
else
{
ERROR_LOG(SCEIO, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpen(%s, %08x, %08x) - file not found", filename, flags, mode);
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file opened", 10000);
}
}
int id = __IoAllocFd(f);
if (id < 0) {
ERROR_LOG(SCEIO, "%08x=sceIoOpen(%s, %08x, %08x): out of fds", id, filename, flags, mode);
kernelObjects.Destroy<FileNode>(f->GetUID());
return id;
} else {
DEBUG_LOG(SCEIO, "%i=sceIoOpen(%s, %08x, %08x)", id, filename, flags, mode);
// Timing is not accurate, aiming low for now.
return hleDelayResult(id, "file opened", 100);
}
}
static u32 sceIoClose(int id) {
u32 error;
DEBUG_LOG(SCEIO, "sceIoClose(%d)", id);
__IoFreeFd(id, error);
// Timing is not accurate, aiming low for now.
return hleDelayResult(error, "file closed", 100);
}
static u32 sceIoRemove(const char *filename) {
DEBUG_LOG(SCEIO, "sceIoRemove(%s)", filename);
// TODO: This timing isn't necessarily accurate, low end for now.
if(!pspFileSystem.GetFileInfo(filename).exists)
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file removed", 100);
pspFileSystem.RemoveFile(filename);
return hleDelayResult(0, "file removed", 100);
}
static u32 sceIoMkdir(const char *dirname, int mode) {
DEBUG_LOG(SCEIO, "sceIoMkdir(%s, %i)", dirname, mode);
// TODO: Improve timing.
if (pspFileSystem.MkDir(dirname))
return hleDelayResult(0, "mkdir", 1000);
else
return hleDelayResult(ERROR_ERRNO_FILE_ALREADY_EXISTS, "mkdir", 1000);
}
static u32 sceIoRmdir(const char *dirname) {
DEBUG_LOG(SCEIO, "sceIoRmdir(%s)", dirname);
// TODO: Improve timing.
if (pspFileSystem.RmDir(dirname))
return hleDelayResult(0, "rmdir", 1000);
else
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "rmdir", 1000);
}
static u32 sceIoSync(const char *devicename, int flag) {
DEBUG_LOG(SCEIO, "UNIMPL sceIoSync(%s, %i)", devicename, flag);
return 0;
}
struct DeviceSize {
u32_le maxClusters;
u32_le freeClusters;
u32_le maxSectors;
u32_le sectorSize;
u32_le sectorCount;
};
static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 outPtr, int outLen) {
if (strcmp(name, "emulator:")) {
DEBUG_LOG(SCEIO,"sceIoDevctl(\"%s\", %08x, %08x, %i, %08x, %i)", name, cmd, argAddr, argLen, outPtr, outLen);
}
// UMD checks
switch (cmd) {
case 0x01F20001:
// Get UMD disc type
if (Memory::IsValidAddress(outPtr) && outLen >= 8) {
Memory::Write_U32(0x10, outPtr + 4); // Always return game disc (if present)
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F20002:
// Get UMD current LBA
if (Memory::IsValidAddress(outPtr) && outLen >= 4) {
Memory::Write_U32(0x10, outPtr); // Assume first sector
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F20003:
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
PSPFileInfo info = pspFileSystem.GetFileInfo("umd1:");
Memory::Write_U32((u32) (info.size) - 1, outPtr);
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F100A3:
// Seek UMD disc (raw)
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
return hleDelayResult(0, "dev seek", 100);
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F100A4:
// Prepare UMD data into cache.
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F300A5:
// Prepare UMD data into cache and get status
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
Memory::Write_U32(1, outPtr); // Status (unitary index of the requested read, greater or equal to 1)
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F300A7:
// Wait for the UMD data cache thread
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
// TODO :
// Place the calling thread in wait state
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F300A8:
// Poll the UMD data cache thread
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
// 0 - UMD data cache thread has finished
// 0x10 - UMD data cache thread is waiting
// 0x20 - UMD data cache thread is running
return 0; // Return finished
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x01F300A9:
// Cancel the UMD data cache thread
if (Memory::IsValidAddress(argAddr) && argLen >= 4) {
// TODO :
// Wake up the thread waiting for the UMD data cache handling.
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
// TODO: What do these do? Seem to require a u32 in, no output.
case 0x01F100A6:
case 0x01F100A8:
case 0x01F100A9:
ERROR_LOG_REPORT(SCEIO, "UNIMPL sceIoDevctl(\"%s\", %08x, %08x, %i, %08x, %i)", name, cmd, argAddr, argLen, outPtr, outLen);
return 0;
}
// This should really send it on to a FileSystem implementation instead.
if (!strcmp(name, "mscmhc0:") || !strcmp(name, "ms0:") || !strcmp(name, "memstick:"))
{
// MemorySticks Checks
switch (cmd) {
case 0x02025801:
// Check the MemoryStick's driver status (mscmhc0).
if (Memory::IsValidAddress(outPtr)) {
Memory::Write_U32(4, outPtr); // JPSCP: The right return value is 4 for some reason
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x02015804:
// Register MemoryStick's insert/eject callback (mscmhc0)
if (Memory::IsValidAddress(argAddr) && argLen == 4) {
// TODO: Verify how duplicates work / how many are allowed.
u32 cbId = Memory::Read_U32(argAddr);
if (memStickCallbacks.find(cbId) == memStickCallbacks.end()) {
memStickCallbacks.insert(cbId);
DEBUG_LOG(SCEIO, "sceIoDevCtl: Memstick callback %i registered, notifying immediately.", cbId);
__KernelNotifyCallback(cbId, MemoryStick_State());
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_TOO_MANY_CALLBACKS;
}
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x02025805:
// Unregister MemoryStick's insert/eject callback (mscmhc0)
if (Memory::IsValidAddress(argAddr) && argLen == 4) {
// TODO: Verify how duplicates work / how many are allowed.
u32 cbId = Memory::Read_U32(argAddr);
if (memStickCallbacks.find(cbId) != memStickCallbacks.end()) {
memStickCallbacks.erase(cbId);
DEBUG_LOG(SCEIO, "sceIoDevCtl: Unregistered memstick callback %i", cbId);
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x02025806:
// Check if the device is inserted (mscmhc0)
if (Memory::IsValidAddress(outPtr)) {
// 0 = Not inserted.
// 1 = Inserted.
Memory::Write_U32(1, outPtr);
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x02425818:
// // Get MS capacity (fatms0).
// Pretend we have a 2GB memory stick.
if (Memory::IsValidAddress(argAddr) && argLen >= 4) { // NOTE: not outPtr
u32 pointer = Memory::Read_U32(argAddr);
u32 sectorSize = 0x200;
u32 memStickSectorSize = 32 * 1024;
u32 sectorCount = memStickSectorSize / sectorSize;
u64 freeSize = 1 * 1024 * 1024 * 1024;
DeviceSize deviceSize;
deviceSize.maxClusters = (u32)((freeSize * 95 / 100) / (sectorSize * sectorCount));
deviceSize.freeClusters = deviceSize.maxClusters;
deviceSize.maxSectors = deviceSize.maxClusters;
deviceSize.sectorSize = sectorSize;
deviceSize.sectorCount = sectorCount;
Memory::WriteStruct(pointer, &deviceSize);
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
case 0x02425824: // Check if write protected
if (Memory::IsValidAddress(outPtr) && outLen == 4) {
Memory::Write_U32(0, outPtr);
return 0;
} else {
ERROR_LOG(SCEIO, "Failed 0x02425824 fat");
return -1;
}
break;
}
}
if (!strcmp(name, "fatms0:"))
{
switch (cmd) {
case 0x02415821: // MScmRegisterMSInsertEjectCallback
{
// TODO: Verify how duplicates work / how many are allowed.
u32 cbId = Memory::Read_U32(argAddr);
if (memStickFatCallbacks.find(cbId) == memStickFatCallbacks.end()) {
memStickFatCallbacks.insert(cbId);
DEBUG_LOG(SCEIO, "sceIoDevCtl: Memstick FAT callback %i registered, notifying immediately.", cbId);
__KernelNotifyCallback(cbId, MemoryStick_FatState());
return 0;
} else {
return -1;
}
}
break;
case 0x02415822:
{
// MScmUnregisterMSInsertEjectCallback
// TODO: Verify how duplicates work / how many are allowed.
u32 cbId = Memory::Read_U32(argAddr);
if (memStickFatCallbacks.find(cbId) != memStickFatCallbacks.end()) {
memStickFatCallbacks.erase(cbId);
DEBUG_LOG(SCEIO, "sceIoDevCtl: Unregistered memstick FAT callback %i", cbId);
return 0;
} else {
return -1;
}
}
break;
case 0x02415823:
// Set FAT as enabled
if (Memory::IsValidAddress(argAddr) && argLen == 4) {
MemoryStick_SetFatState((MemStickFatState)Memory::Read_U32(argAddr));
return 0;
} else {
ERROR_LOG(SCEIO, "Failed 0x02415823 fat");
return -1;
}
break;
case 0x02425823:
// Check if FAT enabled
// If the values added together are >= 0x80000000, or less than outPtr, invalid address.
if (((int)outPtr + outLen) < (int)outPtr) {
ERROR_LOG(SCEIO, "sceIoDevctl: fatms0: 0x02425823 command, bad address");
return SCE_KERNEL_ERROR_ILLEGAL_ADDR;
} else if (!Memory::IsValidAddress(outPtr)) {
// Technically, only checks for NULL, crashes for many bad addresses.
ERROR_LOG(SCEIO, "sceIoDevctl: fatms0: 0x02425823 command, no output address");
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
} else {
// Does not care about outLen, even if it's 0.
Memory::Write_U32(MemoryStick_FatState(), outPtr);
return hleDelayResult(0, "check fat state", cyclesToUs(23500));
}
break;
case 0x02425824:
// Check if write protected
if (Memory::IsValidAddress(outPtr) && outLen == 4) {
Memory::Write_U32(0, outPtr);
return 0;
} else {
ERROR_LOG(SCEIO, "Failed 0x02425824 fat");
return -1;
}
break;
case 0x02425818:
// // Get MS capacity (fatms0).
// Pretend we have a 2GB memory stick.
if (Memory::IsValidAddress(argAddr) && argLen >= 4) { // NOTE: not outPtr
u32 pointer = Memory::Read_U32(argAddr);
u32 sectorSize = 0x200;
u32 memStickSectorSize = 32 * 1024;
u32 sectorCount = memStickSectorSize / sectorSize;
u64 freeSize = 1 * 1024 * 1024 * 1024;
DeviceSize deviceSize;
deviceSize.maxClusters = (u32)((freeSize * 95 / 100) / (sectorSize * sectorCount));
deviceSize.freeClusters = deviceSize.maxClusters;
deviceSize.maxSectors = deviceSize.maxClusters;
deviceSize.sectorSize = sectorSize;
deviceSize.sectorCount = sectorCount;
Memory::WriteStruct(pointer, &deviceSize);
return 0;
} else {
return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS;
}
break;
}
}
if (!strcmp(name, "kemulator:") || !strcmp(name, "emulator:"))
{
// Emulator special tricks!
switch (cmd) {
case 1: // EMULATOR_DEVCTL__GET_HAS_DISPLAY
if (Memory::IsValidAddress(outPtr))
Memory::Write_U32(0, outPtr); // TODO: Make a headless mode for running tests!
return 0;
case 2: // EMULATOR_DEVCTL__SEND_OUTPUT
{
std::string data(Memory::GetCharPointer(argAddr), argLen);
if (PSP_CoreParameter().printfEmuLog) {
host->SendDebugOutput(data);
} else {
if (PSP_CoreParameter().collectEmuLog) {
*PSP_CoreParameter().collectEmuLog += data;
} else {
DEBUG_LOG(SCEIO, "%s", data.c_str());
}
}
return 0;
}
case 3: // EMULATOR_DEVCTL__IS_EMULATOR
if (Memory::IsValidAddress(outPtr))
Memory::Write_U32(1, outPtr);
return 0;
case 4: // EMULATOR_DEVCTL__VERIFY_STATE
// Note that this is async, and makes sure the save state matches up.
SaveState::Verify();
// TODO: Maybe save/load to a file just to be sure?
return 0;
case 0x20: // EMULATOR_DEVCTL__EMIT_SCREENSHOT
u8 *topaddr;
u32 linesize, pixelFormat;
__DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, 0);
// TODO: Convert based on pixel format / mode / something?
host->SendDebugScreenshot(topaddr, linesize, 272);
return 0;
}
ERROR_LOG(SCEIO, "sceIoDevCtl: UNKNOWN PARAMETERS");
return 0;
}
//089c6d1c weird branch
/*
089c6bdc ]: HLE: sceKernelCreateCallback(name= MemoryStick Detection ,entry= 089c7484 ) (z_un_089c6bc4)
089c6c40 ]: HLE: sceKernelCreateCallback(name= MemoryStick Assignment ,entry= 089c7534 ) (z_un_089c6bc4)
*/
ERROR_LOG_REPORT(SCEIO, "UNIMPL sceIoDevctl(\"%s\", %08x, %08x, %i, %08x, %i)", name, cmd, argAddr, argLen, outPtr, outLen);
return SCE_KERNEL_ERROR_UNSUP;
}
static u32 sceIoRename(const char *from, const char *to) {
DEBUG_LOG(SCEIO, "sceIoRename(%s, %s)", from, to);
// TODO: Timing isn't terribly accurate.
if (!pspFileSystem.GetFileInfo(from).exists)
return hleDelayResult(ERROR_ERRNO_FILE_NOT_FOUND, "file renamed", 1000);
int result = pspFileSystem.RenameFile(from, to);
if (result < 0)
WARN_LOG(SCEIO, "Could not move %s to %s", from, to);
return hleDelayResult(result, "file renamed", 1000);
}
static u32 sceIoChdir(const char *dirname) {
DEBUG_LOG(SCEIO, "sceIoChdir(%s)", dirname);
return pspFileSystem.ChDir(dirname);
}
static int sceIoChangeAsyncPriority(int id, int priority)
{
// priority = -1 is valid
if (priority < 0 && priority != -1) {
ERROR_LOG(SCEIO, "sceIoChangeAsyncPriority : Illegal Priority %i", priority);
return SCE_KERNEL_ERROR_ILLEGAL_PRIORITY;
}
ERROR_LOG(SCEIO, "UNIMPL sceIoChangeAsyncPriority(%d, %d)", id, priority);
return 0;
}
static int sceIoCloseAsync(int id)
{
DEBUG_LOG(SCEIO, "sceIoCloseAsync(%d)", id);
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f)
{
f->closePending = true;
f->asyncResult = 0;
// TODO: Rough estimate.
__IoSchedAsync(f, id, 100);
return 0;
}
else
return error;
}
static u32 sceIoSetAsyncCallback(int id, u32 clbckId, u32 clbckArg)
{
DEBUG_LOG(SCEIO, "sceIoSetAsyncCallback(%d, %i, %08x)", id, clbckId, clbckArg);
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f)
{
f->callbackID = clbckId;
f->callbackArg = clbckArg;
return 0;
}
else
{
return error;
}
}
static u32 sceIoOpenAsync(const char *filename, int flags, int mode)
{
// TOOD: Use an internal method so as not to pollute the log?
// Intentionally does not work when interrupts disabled.
if (!__KernelIsDispatchEnabled())
sceKernelResumeDispatchThread(1);
int error;
FileNode *f = __IoOpen(error, filename, flags, mode);
int fd;
// We have to return an fd here, which may have been destroyed when we reach Wait if it failed.
if (f == NULL)
{
ERROR_LOG(SCEIO, "ERROR_ERRNO_FILE_NOT_FOUND=sceIoOpenAsync(%s, %08x, %08x) - file not found", filename, flags, mode);
f = new FileNode();
f->handle = kernelObjects.Create(f);
f->fullpath = filename;
f->asyncResult = error == 0 ? ERROR_ERRNO_FILE_NOT_FOUND : error;
f->closePending = true;
fd = __IoAllocFd(f);
}
else
{
fd = __IoAllocFd(f);
if (fd >= 0) {
DEBUG_LOG(SCEIO, "%x=sceIoOpenAsync(%s, %08x, %08x)", fd, filename, flags, mode);
f->asyncResult = fd;
}
}
if (fd < 0) {
ERROR_LOG(SCEIO, "%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.
// For now let's aim low.
__IoSchedAsync(f, fd, 100);
return fd;
}
static u32 sceIoGetAsyncStat(int id, u32 poll, u32 address) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (__IsInInterrupt()) {
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x): illegal context", f->asyncResult, id, poll, address);
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
if (f->pendingAsyncResult) {
if (poll) {
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x): not ready", f->asyncResult, id, poll, address);
return 1;
} else {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x): dispatch disabled", f->asyncResult, id, poll, address);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x): waiting", f->asyncResult, id, poll, address);
f->waitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_ASYNCIO, f->GetUID(), address, 0, false, "io waited");
}
} else if (f->hasAsyncResult) {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x): dispatch disabled", f->asyncResult, id, poll, address);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
DEBUG_LOG(SCEIO, "%lli = sceIoGetAsyncStat(%i, %i, %08x)", f->asyncResult, id, poll, address);
Memory::Write_U64((u64) f->asyncResult, address);
f->hasAsyncResult = false;
if (f->closePending) {
__IoFreeFd(id, error);
}
} else {
WARN_LOG(SCEIO, "SCE_KERNEL_ERROR_NOASYNC = sceIoGetAsyncStat(%i, %i, %08x)", id, poll, address);
return SCE_KERNEL_ERROR_NOASYNC;
}
return 0; //completed
}
else
{
ERROR_LOG(SCEIO, "ERROR - sceIoGetAsyncStat with invalid id %i", id);
return SCE_KERNEL_ERROR_BADF;
}
}
static int sceIoWaitAsync(int id, u32 address) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (__IsInInterrupt()) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsync(%i, %08x): illegal context", f->asyncResult, id, address);
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
if (f->pendingAsyncResult) {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsync(%i, %08x): dispatch disabled", f->asyncResult, id, address);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsync(%i, %08x): waiting", f->asyncResult, id, address);
f->waitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_ASYNCIO, f->GetUID(), address, 0, false, "io waited");
} else if (f->hasAsyncResult) {
if (!__KernelIsDispatchEnabled()) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsync(%i, %08x): dispatch disabled", f->asyncResult, id, address);
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
}
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsync(%i, %08x)", f->asyncResult, id, address);
Memory::Write_U64((u64) f->asyncResult, address);
f->hasAsyncResult = false;
if (f->closePending) {
__IoFreeFd(id, error);
}
} else {
WARN_LOG(SCEIO, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsync(%i, %08x)", id, address);
return SCE_KERNEL_ERROR_NOASYNC;
}
return 0; //completed
} else {
ERROR_LOG(SCEIO, "ERROR - sceIoWaitAsync waiting for invalid id %i", id);
return SCE_KERNEL_ERROR_BADF;
}
}
static int sceIoWaitAsyncCB(int id, u32 address) {
// Should process callbacks here
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (__IsInInterrupt()) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsyncCB(%i, %08x): illegal context", f->asyncResult, id, address);
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
}
hleCheckCurrentCallbacks();
if (f->pendingAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsyncCB(%i, %08x): waiting", f->asyncResult, id, address);
// TODO: This seems to re-enable dispatch or something?
f->waitingThreads.push_back(__KernelGetCurThread());
__KernelWaitCurThread(WAITTYPE_ASYNCIO, f->GetUID(), address, 0, false, "io waited");
} else if (f->hasAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoWaitAsyncCB(%i, %08x)", f->asyncResult, id, address);
Memory::Write_U64((u64) f->asyncResult, address);
f->hasAsyncResult = false;
if (f->closePending) {
__IoFreeFd(id, error);
}
} else {
WARN_LOG(SCEIO, "SCE_KERNEL_ERROR_NOASYNC = sceIoWaitAsyncCB(%i, %08x)", id, address);
return SCE_KERNEL_ERROR_NOASYNC;
}
return 0; //completed
} else {
ERROR_LOG(SCEIO, "ERROR - sceIoWaitAsyncCB waiting for invalid id %i", id);
return SCE_KERNEL_ERROR_BADF;
}
}
static u32 sceIoPollAsync(int id, u32 address) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->pendingAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x): not ready", f->asyncResult, id, address);
return 1;
} else if (f->hasAsyncResult) {
DEBUG_LOG(SCEIO, "%lli = sceIoPollAsync(%i, %08x)", f->asyncResult, id, address);
Memory::Write_U64((u64) f->asyncResult, address);
f->hasAsyncResult = false;
if (f->closePending) {
__IoFreeFd(id, error);
}
return 0; //completed
} else {
// This seems to be normal-ish usage so demoted to DEBUG from WARN.
DEBUG_LOG(SCEIO, "SCE_KERNEL_ERROR_NOASYNC = sceIoPollAsync(%i, %08x)", id, address);
return SCE_KERNEL_ERROR_NOASYNC;
}
} else {
ERROR_LOG(SCEIO, "ERROR - sceIoPollAsync waiting for invalid id %i", id);
return SCE_KERNEL_ERROR_BADF;
}
}
class DirListing : public KernelObject {
public:
const char *GetName() override { return name.c_str(); }
const char *GetTypeName() override { return "DirListing"; }
static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_BADF; }
static int GetStaticIDType() { return PPSSPP_KERNEL_TMID_DirList; }
int GetIDType() const override { return PPSSPP_KERNEL_TMID_DirList; }
void DoState(PointerWrap &p) override {
auto s = p.Section("DirListing", 1);
if (!s)
return;
p.Do(name);
p.Do(index);
// TODO: Is this the right way for it to wake up?
int count = (int) listing.size();
p.Do(count);
listing.resize(count);
for (int i = 0; i < count; ++i) {
listing[i].DoState(p);
}
}
std::string name;
std::vector<PSPFileInfo> listing;
int index;
};
static u32 sceIoDopen(const char *path) {
DEBUG_LOG(SCEIO, "sceIoDopen(\"%s\")", path);
if(!pspFileSystem.GetFileInfo(path).exists)
{
return ERROR_ERRNO_FILE_NOT_FOUND;
}
DirListing *dir = new DirListing();
SceUID id = kernelObjects.Create(dir);
dir->listing = pspFileSystem.GetDirListing(path);
dir->index = 0;
dir->name = std::string(path);
// TODO: The result is delayed only from the memstick, it seems.
return id;
}
// For some reason strncpy will fill up the entire output buffer. No reason to do that,
// so we use this trivial replacement.
static void strcpy_limit(char *dest, const char *src, int limit) {
int i;
for (i = 0; i < limit - 1; i++) {
if (!src[i])
break;
dest[i] = src[i];
}
// Always null terminate.
dest[i] = 0;
}
static u32 sceIoDread(int id, u32 dirent_addr) {
u32 error;
DirListing *dir = kernelObjects.Get<DirListing>(id, error);
if (dir) {
SceIoDirEnt *entry = (SceIoDirEnt*) Memory::GetPointer(dirent_addr);
if (dir->index == (int) dir->listing.size()) {
DEBUG_LOG(SCEIO, "sceIoDread( %d %08x ) - end of the line", id, dirent_addr);
entry->d_name[0] = '\0';
return 0;
}
PSPFileInfo &info = dir->listing[dir->index];
__IoGetStat(&entry->d_stat, info);
strncpy(entry->d_name, info.name.c_str(), 256);
entry->d_name[255] = '\0';
bool isFAT = false;
IFileSystem *sys = pspFileSystem.GetSystemFromFilename(dir->name);
if (sys && (sys->Flags() & FILESYSTEM_SIMULATE_FAT32))
isFAT = true;
else
isFAT = false;
// Only write d_private for memory stick
if (isFAT) {
// write d_private for supporting Custom BGM
// ref JPCSP https://code.google.com/p/jpcsp/source/detail?r=3468
if (Memory::IsValidAddress(entry->d_private)){
if (sceKernelGetCompiledSdkVersion() <= 0x0307FFFF){
// d_private is pointing to an area of unknown size
// - [0..12] "8.3" file name (null-terminated), could be empty.
// - [13..???] long file name (null-terminated)
// Hm, so currently we don't write the short name at all to d_private? TODO
strcpy_limit((char*)Memory::GetPointer(entry->d_private + 13), (const char*)entry->d_name, ARRAY_SIZE(entry->d_name));
}
else {
// d_private is pointing to an area of total size 1044
// - [0..3] size of area
// - [4..19] "8.3" file name (null-terminated), could be empty.
// - [20..???] long file name (null-terminated)
auto size = Memory::Read_U32(entry->d_private);
// Hm, so currently we don't write the short name at all to d_private? TODO
if (size >= 1044) {
strcpy_limit((char*)Memory::GetPointer(entry->d_private + 20), (const char*)entry->d_name, ARRAY_SIZE(entry->d_name));
}
}
}
}
DEBUG_LOG(SCEIO, "sceIoDread( %d %08x ) = %s", id, dirent_addr, entry->d_name);
// TODO: Improve timing. Only happens on the *first* entry read, ms and umd.
if (dir->index++ == 0) {
return hleDelayResult(1, "readdir", 1000);
}
return 1;
} else {
DEBUG_LOG(SCEIO, "sceIoDread - invalid listing %i, error %08x", id, error);
return SCE_KERNEL_ERROR_BADF;
}
}
static u32 sceIoDclose(int id) {
DEBUG_LOG(SCEIO, "sceIoDclose(%d)", id);
return kernelObjects.Destroy<DirListing>(id);
}
static int __IoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
u32 error;
FileNode *f = __IoGetFd(id, error);
if (error) {
ERROR_LOG(SCEIO, "%08x=sceIoIoctl id: %08x, cmd %08x, bad file", error, id, cmd);
return error;
}
if (f->asyncBusy()) {
ERROR_LOG(SCEIO, "%08x=sceIoIoctl id: %08x, cmd %08x, async busy", error, id, cmd);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
// TODO: Move this into each command, probably?
usec = 100;
//KD Hearts:
//56:46:434 HLE\sceIo.cpp:886 E[HLE]: UNIMPL 0=sceIoIoctrl id: 0000011f, cmd 04100001, indataPtr 08b313d8, inlen 00000010, outdataPtr 00000000, outLen 0
// 0000000
// TODO: This kind of stuff should be moved to the devices (wherever that would be)
// and does not belong in this file. Same thing with Devctl.
switch (cmd) {
// Define decryption key (amctrl.prx DRM)
case 0x04100001: {
u8 keybuf[16];
u8 *key_ptr;
u8 pgd_header[0x90];
u8 pgd_magic[4] = {0x00, 0x50, 0x47, 0x44};
if (Memory::IsValidAddress(indataPtr) && inlen == 16) {
memcpy(keybuf, Memory::GetPointer(indataPtr), 16);
key_ptr = keybuf;
}else{
key_ptr = NULL;
}
DEBUG_LOG(SCEIO, "Decrypting PGD DRM files");
pspFileSystem.SeekFile(f->handle, (s32)f->pgd_offset, FILEMOVE_BEGIN);
pspFileSystem.ReadFile(f->handle, pgd_header, 0x90);
f->pgdInfo = pgd_open(pgd_header, 2, key_ptr);
if(f->pgdInfo==NULL){
ERROR_LOG(SCEIO, "Not a valid PGD file. Open as normal file.");
f->npdrm = false;
pspFileSystem.SeekFile(f->handle, (s32)0, FILEMOVE_BEGIN);
if(memcmp(pgd_header, pgd_magic, 4)==0){
// File is PGD file, but key mismatch
return ERROR_PGD_INVALID_HEADER;
}else{
// File is decrypted.
return 0;
}
}else{
// Everthing OK.
f->npdrm = true;
f->pgdInfo->data_offset += f->pgd_offset;
return 0;
}
break;
}
// Set PGD offset. Called from sceNpDrmEdataSetupKey
case 0x04100002:
f->pgd_offset = indataPtr;
break;
// Get PGD data size. Called from sceNpDrmEdataGetDataSize
case 0x04100010:
if(f->pgdInfo)
return f->pgdInfo->data_size;
else
return (int)f->info.size;
break;
// Get UMD sector size
case 0x01020003:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Asked for sector size of file %i", id);
if (Memory::IsValidAddress(outdataPtr) && outlen >= 4) {
// ISOs always use 2048 sized sectors.
Memory::Write_U32(2048, outdataPtr);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Get UMD file offset
case 0x01020004:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Asked for file offset of file %i", id);
if (Memory::IsValidAddress(outdataPtr) && outlen >= 4) {
u32 offset = (u32)pspFileSystem.GetSeekPos(f->handle);
Memory::Write_U32(offset, outdataPtr);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
case 0x01010005:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Seek for file %i", id);
// Even if the size is 4, it still actually reads a 16 byte struct, it seems.
if (Memory::IsValidAddress(indataPtr) && inlen >= 4) {
struct SeekInfo {
u64 offset;
u32 unk;
u32 whence;
};
const auto seekInfo = PSPPointer<SeekInfo>::Create(indataPtr);
FileMove seek;
s64 newPos = __IoLseekDest(f, seekInfo->offset, seekInfo->whence, seek);
if (newPos < 0 || newPos > f->info.size) {
// Not allowed to seek past the end of the file with this API.
return ERROR_ERRNO_IO_ERROR;
}
pspFileSystem.SeekFile(f->handle, (s32)seekInfo->offset, seek);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Get UMD file start sector.
case 0x01020006:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Asked for start sector of file %i", id);
if (Memory::IsValidAddress(outdataPtr) && outlen >= 4) {
Memory::Write_U32(f->info.startSector, outdataPtr);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Get UMD file size in bytes.
case 0x01020007:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Asked for size of file %i", id);
if (Memory::IsValidAddress(outdataPtr) && outlen >= 8) {
Memory::Write_U64(f->info.size, outdataPtr);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Read from UMD file.
case 0x01030008:
// TODO: Should not work for umd0:/, ms0:/, etc.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Read from file %i", id);
if (Memory::IsValidAddress(indataPtr) && inlen >= 4) {
u32 size = Memory::Read_U32(indataPtr);
if (Memory::IsValidAddress(outdataPtr) && size <= outlen) {
// sceIoRead does its own delaying (and deferring.)
usec = 0;
return sceIoRead(id, outdataPtr, size);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Get current sector seek pos from UMD device file.
case 0x01d20001:
// TODO: Should work only for umd0:/, etc. not for ms0:/ or disc0:/.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Sector tell from file %i", id);
if (Memory::IsValidAddress(outdataPtr) && outlen >= 4) {
Memory::Write_U32((u32)pspFileSystem.GetSeekPos(f->handle), outdataPtr);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Read raw sectors from UMD device file.
case 0x01f30003:
// TODO: Should work only for umd0:/, etc. not for ms0:/ or disc0:/.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Sector read from file %i", id);
if (Memory::IsValidAddress(indataPtr) && inlen >= 4) {
u32 size = Memory::Read_U32(indataPtr);
// Note that size is specified in sectors, not bytes.
if (size > 0 && Memory::IsValidAddress(outdataPtr) && size <= outlen) {
// sceIoRead does its own delaying (and deferring.)
usec = 0;
return sceIoRead(id, outdataPtr, size);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
// Seek by sector in UMD device file.
case 0x01f100a6:
// TODO: Should work only for umd0:/, etc. not for ms0:/ or disc0:/.
// TODO: Should probably move this to something common between ISOFileSystem and VirtualDiscSystem.
INFO_LOG(SCEIO, "sceIoIoctl: Sector seek for file %i", id);
// Even if the size is 4, it still actually reads a 16 byte struct, it seems.
if (Memory::IsValidAddress(indataPtr) && inlen >= 4) {
struct SeekInfo {
u64 offset;
u32 unk;
u32 whence;
};
const auto seekInfo = PSPPointer<SeekInfo>::Create(indataPtr);
FileMove seek;
s64 newPos = __IoLseekDest(f, seekInfo->offset, seekInfo->whence, seek);
// Position is in sectors, don't forget.
if (newPos < 0 || newPos > f->info.size) {
// Not allowed to seek past the end of the file with this API.
return SCE_KERNEL_ERROR_ERRNO_INVALID_FILE_SIZE;
}
pspFileSystem.SeekFile(f->handle, (s32)seekInfo->offset, seek);
} else {
return SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
}
break;
default:
{
int result = pspFileSystem.Ioctl(f->handle, cmd, indataPtr, inlen, outdataPtr, outlen, usec);
if (result == (int)SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED) {
char temp[256];
// We want the reported message to include the cmd, so it's unique.
sprintf(temp, "sceIoIoctl(%%s, %08x, %%08x, %%x, %%08x, %%x)", cmd);
Reporting::ReportMessage(temp, f->fullpath.c_str(), indataPtr, inlen, outdataPtr, outlen);
ERROR_LOG(SCEIO, "UNIMPL 0=sceIoIoctl id: %08x, cmd %08x, indataPtr %08x, inlen %08x, outdataPtr %08x, outLen %08x", id,cmd,indataPtr,inlen,outdataPtr,outlen);
}
return result;
}
break;
}
return 0;
}
u32 sceIoIoctl(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen)
{
int usec = 0;
int result = __IoIoctl(id, cmd, indataPtr, inlen, outdataPtr, outlen, usec);
if (usec != 0) {
return hleDelayResult(result, "io ctrl command", usec);
}
return result;
}
static u32 sceIoIoctlAsync(u32 id, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen)
{
u32 error;
FileNode *f = __IoGetFd(id, error);
if (f) {
if (f->asyncBusy()) {
WARN_LOG(SCEIO, "sceIoIoctlAsync(%08x, %08x, %08x, %08x, %08x, %08x): async busy", id, cmd, indataPtr, inlen, outdataPtr, outlen);
return SCE_KERNEL_ERROR_ASYNC_BUSY;
}
DEBUG_LOG(SCEIO, "sceIoIoctlAsync(%08x, %08x, %08x, %08x, %08x, %08x)", id, cmd, indataPtr, inlen, outdataPtr, outlen);
int usec = 100;
f->asyncResult = __IoIoctl(id, cmd, indataPtr, inlen, outdataPtr, outlen, usec);
__IoSchedAsync(f, id, usec);
return 0;
} else {
ERROR_LOG(SCEIO, "UNIMPL %08x=sceIoIoctlAsync id: %08x, cmd %08x, bad file", error, id, cmd);
return error;
}
}
static u32 sceIoGetFdList(u32 outAddr, int outSize, u32 fdNumAddr) {
WARN_LOG(SCEIO, "sceIoGetFdList(%08x, %i, %08x)", outAddr, outSize, fdNumAddr);
auto out = PSPPointer<SceUID_le>::Create(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.IsValid()) {
out[count] = i;
}
++count;
}
for (int i = PSP_MIN_FD; i < PSP_COUNT_FDS; ++i) {
if (fds[i] == 0) {
continue;
}
if (count < outSize && out.IsValid()) {
out[count] = i;
}
++count;
}
if (Memory::IsValidAddress(fdNumAddr))
Memory::Write_U32(count, fdNumAddr);
if (count >= outSize) {
return outSize;
} else {
return count;
}
}
// Presumably lets you hook up stderr to a MsgPipe.
static u32 sceKernelRegisterStderrPipe(u32 msgPipeUID) {
ERROR_LOG_REPORT(SCEIO, "UNIMPL sceKernelRegisterStderrPipe(%08x)", msgPipeUID);
return 0;
}
static u32 sceKernelRegisterStdoutPipe(u32 msgPipeUID) {
ERROR_LOG_REPORT(SCEIO, "UNIMPL sceKernelRegisterStdoutPipe(%08x)", msgPipeUID);
return 0;
}
KernelObject *__KernelFileNodeObject() {
return new FileNode;
}
KernelObject *__KernelDirListingObject() {
return new DirListing;
}
const HLEFunction IoFileMgrForUser[] = {
{0XB29DDF9C, &WrapU_C<sceIoDopen>, "sceIoDopen", 'x', "s" },
{0XE3EB004C, &WrapU_IU<sceIoDread>, "sceIoDread", 'x', "ix" },
{0XEB092469, &WrapU_I<sceIoDclose>, "sceIoDclose", 'x', "i" },
{0XE95A012B, &WrapU_UUUUUU<sceIoIoctlAsync>, "sceIoIoctlAsync", 'x', "xxxxxx"},
{0X63632449, &WrapU_UUUUUU<sceIoIoctl>, "sceIoIoctl", 'x', "xxxxxx"},
{0XACE946E8, &WrapU_CU<sceIoGetstat>, "sceIoGetstat", 'x', "sx" },
{0XB8A740F4, &WrapU_CUU<sceIoChstat>, "sceIoChstat", 'x', "sxx" },
{0X55F4717D, &WrapU_C<sceIoChdir>, "sceIoChdir", 'x', "s" },
{0X08BD7374, &WrapU_I<sceIoGetDevType>, "sceIoGetDevType", 'x', "i" },
{0XB2A628C1, &WrapU_UUUIUI<sceIoAssign>, "sceIoAssign", 'x', "xxxixi"},
{0XE8BC6571, &WrapU_I<sceIoCancel>, "sceIoCancel", 'x', "i" },
{0XB293727F, &WrapI_II<sceIoChangeAsyncPriority>, "sceIoChangeAsyncPriority", 'i', "ii" },
{0X810C4BC3, &WrapU_I<sceIoClose>, "sceIoClose", 'x', "i" },
{0XFF5940B6, &WrapI_I<sceIoCloseAsync>, "sceIoCloseAsync", 'i', "i" },
{0X54F5FB11, &WrapU_CIUIUI<sceIoDevctl>, "sceIoDevctl", 'x', "sixixi"},
{0XCB05F8D6, &WrapU_IUU<sceIoGetAsyncStat>, "sceIoGetAsyncStat", 'x', "ixx" },
{0X27EB27B8, &WrapI64_II64I<sceIoLseek>, "sceIoLseek", 'I', "iIi" },
{0X68963324, &WrapU_III<sceIoLseek32>, "sceIoLseek32", 'x', "iii" },
{0X1B385D8F, &WrapU_III<sceIoLseek32Async>, "sceIoLseek32Async", 'x', "iii" },
{0X71B19E77, &WrapU_II64I<sceIoLseekAsync>, "sceIoLseekAsync", 'x', "iIi" },
{0X109F50BC, &WrapU_CII<sceIoOpen>, "sceIoOpen", 'x', "sii" },
{0X89AA9906, &WrapU_CII<sceIoOpenAsync>, "sceIoOpenAsync", 'x', "sii" },
{0X06A70004, &WrapU_CI<sceIoMkdir>, "sceIoMkdir", 'x', "si" },
{0X3251EA56, &WrapU_IU<sceIoPollAsync>, "sceIoPollAsync", 'x', "ix" },
{0X6A638D83, &WrapU_IUI<sceIoRead>, "sceIoRead", 'x', "ixi" },
{0XA0B5A7C2, &WrapU_IUI<sceIoReadAsync>, "sceIoReadAsync", 'x', "ixi" },
{0XF27A9C51, &WrapU_C<sceIoRemove>, "sceIoRemove", 'x', "s" },
{0X779103A0, &WrapU_CC<sceIoRename>, "sceIoRename", 'x', "ss" },
{0X1117C65F, &WrapU_C<sceIoRmdir>, "sceIoRmdir", 'x', "s" },
{0XA12A0514, &WrapU_IUU<sceIoSetAsyncCallback>, "sceIoSetAsyncCallback", 'x', "ixx" },
{0XAB96437F, &WrapU_CI<sceIoSync>, "sceIoSync", 'x', "si" },
{0X6D08A871, &WrapU_C<sceIoUnassign>, "sceIoUnassign", 'x', "s" },
{0X42EC03AC, &WrapU_IUI<sceIoWrite>, "sceIoWrite", 'x', "ixi" },
{0X0FACAB19, &WrapU_IUI<sceIoWriteAsync>, "sceIoWriteAsync", 'x', "ixi" },
{0X35DBD746, &WrapI_IU<sceIoWaitAsyncCB>, "sceIoWaitAsyncCB", 'i', "ix" },
{0XE23EEC33, &WrapI_IU<sceIoWaitAsync>, "sceIoWaitAsync", 'i', "ix" },
{0X5C2BE2CC, &WrapU_UIU<sceIoGetFdList>, "sceIoGetFdList", 'x', "xix" },
};
void Register_IoFileMgrForUser() {
RegisterModule("IoFileMgrForUser", ARRAY_SIZE(IoFileMgrForUser), IoFileMgrForUser);
}
const HLEFunction StdioForUser[] = {
{0X172D316E, &WrapU_V<sceKernelStdin>, "sceKernelStdin", 'x', "" },
{0XA6BAB2E9, &WrapU_V<sceKernelStdout>, "sceKernelStdout", 'x', "" },
{0XF78BA90A, &WrapU_V<sceKernelStderr>, "sceKernelStderr", 'x', "" },
{0X432D8F5C, &WrapU_U<sceKernelRegisterStdoutPipe>, "sceKernelRegisterStdoutPipe", 'x', "x" },
{0X6F797E03, &WrapU_U<sceKernelRegisterStderrPipe>, "sceKernelRegisterStderrPipe", 'x', "x" },
{0XA46785C9, nullptr, "sceKernelStdioSendChar", '?', "" },
{0X0CBB0571, nullptr, "sceKernelStdioLseek", '?', "" },
{0X3054D478, nullptr, "sceKernelStdioRead", '?', "" },
{0XA3B931DB, nullptr, "sceKernelStdioWrite", '?', "" },
{0X924ABA61, nullptr, "sceKernelStdioOpen", '?', "" },
{0X9D061C19, nullptr, "sceKernelStdioClose", '?', "" },
};
void Register_StdioForUser() {
RegisterModule("StdioForUser", ARRAY_SIZE(StdioForUser), StdioForUser);
}