mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-24 14:00:03 +00:00
a8d918b50a
Using memstick/exdata/GAMEID/.
2482 lines
78 KiB
C++
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);
|
|
}
|