mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Also move colorutil.cpp/h linking build fix experiment Delete a bunch of unused CMakeLists.txt files CMakeLists.txt linking fix Don't include NativeApp.h from any headers. Android.mk buildfix Half of the UWP fix Buildfix Minor project file cleanup Buildfixes Guess what? More buildfixes!
1198 lines
35 KiB
C++
1198 lines
35 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 "ppsspp_config.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#include "file/free.h"
|
|
#include "file/zip_read.h"
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Serialize/Serializer.h"
|
|
#include "Common/Serialize/SerializeFuncs.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Core/FileSystems/DirectoryFileSystem.h"
|
|
#include "Core/FileSystems/ISOFileSystem.h"
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/HW/MemoryStick.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Host.h"
|
|
#include "Core/Replay.h"
|
|
#include "Core/Reporting.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "Common/CommonWindows.h"
|
|
#include <sys/stat.h>
|
|
#else
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#if defined(__ANDROID__)
|
|
#include <sys/types.h>
|
|
#include <sys/vfs.h>
|
|
#define statvfs statfs
|
|
#else
|
|
#include <sys/statvfs.h>
|
|
#endif
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
static bool FixFilenameCase(const std::string &path, std::string &filename)
|
|
{
|
|
// Are we lucky?
|
|
if (File::Exists(path + filename))
|
|
return true;
|
|
|
|
size_t filenameSize = filename.size(); // size in bytes, not characters
|
|
for (size_t i = 0; i < filenameSize; i++)
|
|
{
|
|
filename[i] = tolower(filename[i]);
|
|
}
|
|
|
|
//TODO: lookup filename in cache for "path"
|
|
struct dirent *result = NULL;
|
|
|
|
DIR *dirp = opendir(path.c_str());
|
|
if (!dirp)
|
|
return false;
|
|
|
|
bool retValue = false;
|
|
|
|
while ((result = readdir(dirp)))
|
|
{
|
|
if (strlen(result->d_name) != filenameSize)
|
|
continue;
|
|
|
|
size_t i;
|
|
for (i = 0; i < filenameSize; i++)
|
|
{
|
|
if (filename[i] != tolower(result->d_name[i]))
|
|
break;
|
|
}
|
|
|
|
if (i < filenameSize)
|
|
continue;
|
|
|
|
filename = result->d_name;
|
|
retValue = true;
|
|
}
|
|
|
|
closedir(dirp);
|
|
|
|
return retValue;
|
|
}
|
|
|
|
bool FixPathCase(const std::string &basePath, std::string &path, FixPathCaseBehavior behavior)
|
|
{
|
|
size_t len = path.size();
|
|
|
|
if (len == 0)
|
|
return true;
|
|
|
|
if (path[len - 1] == '/')
|
|
{
|
|
len--;
|
|
|
|
if (len == 0)
|
|
return true;
|
|
}
|
|
|
|
std::string fullPath;
|
|
fullPath.reserve(basePath.size() + len + 1);
|
|
fullPath.append(basePath);
|
|
|
|
size_t start = 0;
|
|
while (start < len)
|
|
{
|
|
size_t i = path.find('/', start);
|
|
if (i == std::string::npos)
|
|
i = len;
|
|
|
|
if (i > start)
|
|
{
|
|
std::string component = path.substr(start, i - start);
|
|
|
|
// Fix case and stop on nonexistant path component
|
|
if (FixFilenameCase(fullPath, component) == false) {
|
|
// Still counts as success if partial matches allowed or if this
|
|
// is the last component and only the ones before it are required
|
|
return (behavior == FPC_PARTIAL_ALLOWED || (behavior == FPC_PATH_MUST_EXIST && i >= len));
|
|
}
|
|
|
|
path.replace(start, i - start, component);
|
|
|
|
fullPath.append(component);
|
|
fullPath.append(1, '/');
|
|
}
|
|
|
|
start = i + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
|
|
DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, std::string _basePath, FileSystemFlags _flags) : basePath(_basePath), flags(_flags) {
|
|
File::CreateFullPath(basePath);
|
|
hAlloc = _hAlloc;
|
|
}
|
|
|
|
DirectoryFileSystem::~DirectoryFileSystem() {
|
|
CloseAll();
|
|
}
|
|
|
|
std::string DirectoryFileHandle::GetLocalPath(const std::string &basePath, std::string localpath)
|
|
{
|
|
if (localpath.empty())
|
|
return basePath;
|
|
|
|
if (localpath[0] == '/')
|
|
localpath.erase(0, 1);
|
|
|
|
std::string result = basePath + localpath;
|
|
#ifdef _WIN32
|
|
for (char &c : result) {
|
|
if (c == '/')
|
|
c = '\\';
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
bool DirectoryFileHandle::Open(const std::string &basePath, std::string &fileName, FileAccess access, u32 &error) {
|
|
error = 0;
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (access & (FILEACCESS_APPEND|FILEACCESS_CREATE|FILEACCESS_WRITE)) {
|
|
DEBUG_LOG(FILESYS, "Checking case for path %s", fileName.c_str());
|
|
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {
|
|
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
return false; // or go on and attempt (for a better error code than just 0?)
|
|
}
|
|
}
|
|
// else we try fopen first (in case we're lucky) before simulating case insensitivity
|
|
#endif
|
|
|
|
std::string fullName = GetLocalPath(basePath, fileName);
|
|
VERBOSE_LOG(FILESYS,"Actually opening %s", fullName.c_str());
|
|
|
|
// On the PSP, truncating doesn't lose data. If you seek later, you'll recover it.
|
|
// This is abnormal, so we deviate from the PSP's behavior and truncate on write/close.
|
|
// This means it's incorrectly not truncated before the write.
|
|
if (access & FILEACCESS_TRUNCATE) {
|
|
needsTrunc_ = 0;
|
|
}
|
|
|
|
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
|
|
#ifdef _WIN32
|
|
// Convert parameters to Windows permissions and access
|
|
DWORD desired = 0;
|
|
DWORD sharemode = 0;
|
|
DWORD openmode = 0;
|
|
if (access & FILEACCESS_READ) {
|
|
desired |= GENERIC_READ;
|
|
sharemode |= FILE_SHARE_READ;
|
|
}
|
|
if (access & FILEACCESS_WRITE) {
|
|
desired |= GENERIC_WRITE;
|
|
sharemode |= FILE_SHARE_WRITE | FILE_SHARE_READ;
|
|
}
|
|
if (access & FILEACCESS_CREATE) {
|
|
if (access & FILEACCESS_EXCL) {
|
|
openmode = CREATE_NEW;
|
|
} else {
|
|
openmode = OPEN_ALWAYS;
|
|
}
|
|
} else {
|
|
openmode = OPEN_EXISTING;
|
|
}
|
|
|
|
// Let's do it!
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
hFile = CreateFile2(ConvertUTF8ToWString(fullName).c_str(), desired, sharemode, openmode, nullptr);
|
|
#else
|
|
hFile = CreateFile(ConvertUTF8ToWString(fullName).c_str(), desired, sharemode, 0, openmode, 0, 0);
|
|
#endif
|
|
bool success = hFile != INVALID_HANDLE_VALUE;
|
|
if (!success) {
|
|
DWORD w32err = GetLastError();
|
|
|
|
if (w32err == ERROR_SHARING_VIOLATION) {
|
|
// Sometimes, the file is locked for write, let's try again.
|
|
sharemode |= FILE_SHARE_WRITE;
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
hFile = CreateFile2(ConvertUTF8ToWString(fullName).c_str(), desired, sharemode, openmode, nullptr);
|
|
#else
|
|
hFile = CreateFile(ConvertUTF8ToWString(fullName).c_str(), desired, sharemode, 0, openmode, 0, 0);
|
|
#endif
|
|
success = hFile != INVALID_HANDLE_VALUE;
|
|
if (!success) {
|
|
w32err = GetLastError();
|
|
}
|
|
}
|
|
|
|
if (w32err == ERROR_DISK_FULL || w32err == ERROR_NOT_ENOUGH_QUOTA) {
|
|
// This is returned when the disk is full.
|
|
auto err = GetI18NCategory("Error");
|
|
host->NotifyUserMessage(err->T("Disk full while writing data"));
|
|
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
|
|
} else if (!success) {
|
|
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
}
|
|
}
|
|
#else
|
|
int flags = 0;
|
|
if (access & FILEACCESS_APPEND) {
|
|
flags |= O_APPEND;
|
|
}
|
|
if ((access & FILEACCESS_READ) && (access & FILEACCESS_WRITE)) {
|
|
flags |= O_RDWR;
|
|
} else if (access & FILEACCESS_READ) {
|
|
flags |= O_RDONLY;
|
|
} else if (access & FILEACCESS_WRITE) {
|
|
flags |= O_WRONLY;
|
|
}
|
|
if (access & FILEACCESS_CREATE) {
|
|
flags |= O_CREAT;
|
|
}
|
|
if (access & FILEACCESS_EXCL) {
|
|
flags |= O_EXCL;
|
|
}
|
|
|
|
hFile = open(fullName.c_str(), flags, 0666);
|
|
bool success = hFile != -1;
|
|
#endif
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (!success && !(access & FILEACCESS_CREATE)) {
|
|
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {
|
|
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
return false;
|
|
}
|
|
fullName = GetLocalPath(basePath,fileName);
|
|
const char *fullNameC = fullName.c_str();
|
|
|
|
DEBUG_LOG(FILESYS, "Case may have been incorrect, second try opening %s (%s)", fullNameC, fileName.c_str());
|
|
|
|
// And try again with the correct case this time
|
|
#ifdef _WIN32
|
|
hFile = CreateFile(fullNameC, desired, sharemode, 0, openmode, 0, 0);
|
|
success = hFile != INVALID_HANDLE_VALUE;
|
|
#else
|
|
hFile = open(fullNameC, flags, 0666);
|
|
success = hFile != -1;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#ifndef _WIN32
|
|
if (success) {
|
|
// Reject directories, even if we succeed in opening them.
|
|
// TODO: Might want to do this stat first...
|
|
struct stat st;
|
|
if (fstat(hFile, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
close(hFile);
|
|
errno = EISDIR;
|
|
success = false;
|
|
}
|
|
} else if (errno == ENOSPC) {
|
|
// This is returned when the disk is full.
|
|
auto err = GetI18NCategory("Error");
|
|
host->NotifyUserMessage(err->T("Disk full while writing data"));
|
|
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
|
|
} else {
|
|
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
}
|
|
#endif
|
|
|
|
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
|
|
if (fullName.find("/PSP/GAME/") != fullName.npos || fullName.find("\\PSP\\GAME\\") != fullName.npos) {
|
|
inGameDir_ = true;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
size_t DirectoryFileHandle::Read(u8* pointer, s64 size)
|
|
{
|
|
size_t bytesRead = 0;
|
|
if (needsTrunc_ != -1) {
|
|
// If the file was marked to be truncated, pretend there's nothing.
|
|
// On a PSP. it actually is truncated, but the data wasn't erased.
|
|
off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);
|
|
if (needsTrunc_ <= off) {
|
|
return replay_ ? ReplayApplyDiskRead(pointer, 0, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : 0;
|
|
}
|
|
if (needsTrunc_ < off + size) {
|
|
size = needsTrunc_ - off;
|
|
}
|
|
}
|
|
#ifdef _WIN32
|
|
::ReadFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesRead, 0);
|
|
#else
|
|
bytesRead = read(hFile, pointer, size);
|
|
#endif
|
|
return replay_ ? ReplayApplyDiskRead(pointer, (uint32_t)bytesRead, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : bytesRead;
|
|
}
|
|
|
|
size_t DirectoryFileHandle::Write(const u8* pointer, s64 size)
|
|
{
|
|
size_t bytesWritten = 0;
|
|
bool diskFull = false;
|
|
|
|
#ifdef _WIN32
|
|
BOOL success = ::WriteFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesWritten, 0);
|
|
if (success == FALSE) {
|
|
DWORD err = GetLastError();
|
|
diskFull = err == ERROR_DISK_FULL || err == ERROR_NOT_ENOUGH_QUOTA;
|
|
}
|
|
#else
|
|
bytesWritten = write(hFile, pointer, size);
|
|
if (bytesWritten == (size_t)-1) {
|
|
diskFull = errno == ENOSPC;
|
|
}
|
|
#endif
|
|
if (needsTrunc_ != -1) {
|
|
off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);
|
|
if (needsTrunc_ < off) {
|
|
needsTrunc_ = off;
|
|
}
|
|
}
|
|
|
|
if (replay_) {
|
|
bytesWritten = ReplayApplyDiskWrite(pointer, (uint64_t)bytesWritten, (uint64_t)size, &diskFull, inGameDir_, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
if (diskFull) {
|
|
ERROR_LOG(FILESYS, "Disk full");
|
|
auto err = GetI18NCategory("Error");
|
|
host->NotifyUserMessage(err->T("Disk full while writing data"));
|
|
// We only return an error when the disk is actually full.
|
|
// When writing this would cause the disk to be full, so it wasn't written, we return 0.
|
|
if (MemoryStick_FreeSpace() == 0) {
|
|
// Sign extend on 64-bit.
|
|
return (size_t)(s64)(s32)SCE_KERNEL_ERROR_ERRNO_DEVICE_NO_FREE_SPACE;
|
|
}
|
|
}
|
|
|
|
return bytesWritten;
|
|
}
|
|
|
|
size_t DirectoryFileHandle::Seek(s32 position, FileMove type)
|
|
{
|
|
if (needsTrunc_ != -1) {
|
|
// If the file is "currently truncated" move to the end based on that position.
|
|
// The actual, underlying file hasn't been truncated (yet.)
|
|
if (type == FILEMOVE_END) {
|
|
type = FILEMOVE_BEGIN;
|
|
position = needsTrunc_ + position;
|
|
}
|
|
}
|
|
|
|
size_t result;
|
|
#ifdef _WIN32
|
|
DWORD moveMethod = 0;
|
|
switch (type) {
|
|
case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;
|
|
case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break;
|
|
case FILEMOVE_END: moveMethod = FILE_END; break;
|
|
}
|
|
|
|
LARGE_INTEGER distance;
|
|
distance.QuadPart = position;
|
|
LARGE_INTEGER cursor;
|
|
DWORD newPos = SetFilePointerEx(hFile, distance, &cursor, moveMethod);
|
|
result = (size_t)cursor.QuadPart;
|
|
#else
|
|
int moveMethod = 0;
|
|
switch (type) {
|
|
case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;
|
|
case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;
|
|
case FILEMOVE_END: moveMethod = SEEK_END; break;
|
|
}
|
|
result = lseek(hFile, position, moveMethod);
|
|
#endif
|
|
|
|
return replay_ ? (size_t)ReplayApplyDisk64(ReplayAction::FILE_SEEK, result, CoreTiming::GetGlobalTimeUs()) : result;
|
|
}
|
|
|
|
void DirectoryFileHandle::Close()
|
|
{
|
|
if (needsTrunc_ != -1) {
|
|
#ifdef _WIN32
|
|
Seek((s32)needsTrunc_, FILEMOVE_BEGIN);
|
|
if (SetEndOfFile(hFile) == 0) {
|
|
ERROR_LOG_REPORT(FILESYS, "Failed to truncate file.");
|
|
}
|
|
#elif !PPSSPP_PLATFORM(SWITCH)
|
|
// Note: it's not great that Switch cannot truncate appropriately...
|
|
if (ftruncate(hFile, (off_t)needsTrunc_) != 0) {
|
|
ERROR_LOG_REPORT(FILESYS, "Failed to truncate file.");
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef _WIN32
|
|
if (hFile != (HANDLE)-1)
|
|
CloseHandle(hFile);
|
|
#else
|
|
if (hFile != -1)
|
|
close(hFile);
|
|
#endif
|
|
}
|
|
|
|
void DirectoryFileSystem::CloseAll() {
|
|
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
|
|
INFO_LOG(FILESYS, "DirectoryFileSystem::CloseAll(): Force closing %d (%s)", (int)iter->first, iter->second.guestFilename.c_str());
|
|
iter->second.hFile.Close();
|
|
}
|
|
entries.clear();
|
|
}
|
|
|
|
std::string DirectoryFileSystem::GetLocalPath(std::string localpath) {
|
|
if (localpath.empty())
|
|
return basePath;
|
|
|
|
if (localpath[0] == '/')
|
|
localpath.erase(0,1);
|
|
//Convert slashes
|
|
#ifdef _WIN32
|
|
for (size_t i = 0; i < localpath.size(); i++) {
|
|
if (localpath[i] == '/')
|
|
localpath[i] = '\\';
|
|
}
|
|
#endif
|
|
return basePath + localpath;
|
|
}
|
|
|
|
bool DirectoryFileSystem::MkDir(const std::string &dirname) {
|
|
bool result;
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
// Must fix case BEFORE attempting, because MkDir would create
|
|
// duplicate (different case) directories
|
|
|
|
std::string fixedCase = dirname;
|
|
if (!FixPathCase(basePath,fixedCase, FPC_PARTIAL_ALLOWED))
|
|
result = false;
|
|
else
|
|
result = File::CreateFullPath(GetLocalPath(fixedCase));
|
|
#else
|
|
result = File::CreateFullPath(GetLocalPath(dirname));
|
|
#endif
|
|
return ReplayApplyDisk(ReplayAction::MKDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;
|
|
}
|
|
|
|
bool DirectoryFileSystem::RmDir(const std::string &dirname) {
|
|
std::string fullName = GetLocalPath(dirname);
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
// Maybe we're lucky?
|
|
if (File::DeleteDirRecursively(fullName))
|
|
return (bool)ReplayApplyDisk(ReplayAction::RMDIR, true, CoreTiming::GetGlobalTimeUs());
|
|
|
|
// Nope, fix case and try again. Should we try again?
|
|
fullName = dirname;
|
|
if (!FixPathCase(basePath,fullName, FPC_FILE_MUST_EXIST))
|
|
return (bool)ReplayApplyDisk(ReplayAction::RMDIR, false, CoreTiming::GetGlobalTimeUs());
|
|
|
|
fullName = GetLocalPath(fullName);
|
|
#endif
|
|
|
|
/*#ifdef _WIN32
|
|
return RemoveDirectory(fullName.c_str()) == TRUE;
|
|
#else
|
|
return 0 == rmdir(fullName.c_str());
|
|
#endif*/
|
|
bool result = File::DeleteDirRecursively(fullName);
|
|
return ReplayApplyDisk(ReplayAction::RMDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;
|
|
}
|
|
|
|
int DirectoryFileSystem::RenameFile(const std::string &from, const std::string &to) {
|
|
std::string fullTo = to;
|
|
|
|
// Rename ignores the path (even if specified) on to.
|
|
size_t chop_at = to.find_last_of('/');
|
|
if (chop_at != to.npos)
|
|
fullTo = to.substr(chop_at + 1);
|
|
|
|
// Now put it in the same directory as from.
|
|
size_t dirname_end = from.find_last_of('/');
|
|
if (dirname_end != from.npos)
|
|
fullTo = from.substr(0, dirname_end + 1) + fullTo;
|
|
|
|
// At this point, we should check if the paths match and give an already exists error.
|
|
if (from == fullTo)
|
|
return ReplayApplyDisk(ReplayAction::FILE_RENAME, SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS, CoreTiming::GetGlobalTimeUs());
|
|
|
|
std::string fullFrom = GetLocalPath(from);
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
// In case TO should overwrite a file with different case. Check error code?
|
|
if (!FixPathCase(basePath,fullTo, FPC_PATH_MUST_EXIST))
|
|
return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());
|
|
#endif
|
|
|
|
fullTo = GetLocalPath(fullTo);
|
|
const char * fullToC = fullTo.c_str();
|
|
|
|
#ifdef _WIN32
|
|
bool retValue = (MoveFileEx(ConvertUTF8ToWString(fullFrom).c_str(), ConvertUTF8ToWString(fullToC).c_str(), 0) == TRUE);
|
|
#else
|
|
bool retValue = (0 == rename(fullFrom.c_str(), fullToC));
|
|
#endif
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (! retValue)
|
|
{
|
|
// May have failed due to case sensitivity on FROM, so try again. Check error code?
|
|
fullFrom = from;
|
|
if (!FixPathCase(basePath,fullFrom, FPC_FILE_MUST_EXIST))
|
|
return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());
|
|
fullFrom = GetLocalPath(fullFrom);
|
|
|
|
#ifdef _WIN32
|
|
retValue = (MoveFile(fullFrom.c_str(), fullToC) == TRUE);
|
|
#else
|
|
retValue = (0 == rename(fullFrom.c_str(), fullToC));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
// TODO: Better error codes.
|
|
int result = retValue ? 0 : (int)SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS;
|
|
return ReplayApplyDisk(ReplayAction::FILE_RENAME, result, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
bool DirectoryFileSystem::RemoveFile(const std::string &filename) {
|
|
std::string fullName = GetLocalPath(filename);
|
|
#ifdef _WIN32
|
|
bool retValue = (::DeleteFileA(fullName.c_str()) == TRUE);
|
|
#else
|
|
bool retValue = (0 == unlink(fullName.c_str()));
|
|
#endif
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (! retValue)
|
|
{
|
|
// May have failed due to case sensitivity, so try again. Try even if it fails?
|
|
fullName = filename;
|
|
if (!FixPathCase(basePath,fullName, FPC_FILE_MUST_EXIST))
|
|
return (bool)ReplayApplyDisk(ReplayAction::FILE_REMOVE, false, CoreTiming::GetGlobalTimeUs());
|
|
fullName = GetLocalPath(fullName);
|
|
|
|
#ifdef _WIN32
|
|
retValue = (::DeleteFileA(fullName.c_str()) == TRUE);
|
|
#else
|
|
retValue = (0 == unlink(fullName.c_str()));
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
return ReplayApplyDisk(ReplayAction::FILE_REMOVE, retValue, CoreTiming::GetGlobalTimeUs()) != 0;
|
|
}
|
|
|
|
int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
|
|
OpenFileEntry entry;
|
|
u32 err = 0;
|
|
bool success = entry.hFile.Open(basePath, filename, access, err);
|
|
if (err == 0 && !success) {
|
|
err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
}
|
|
|
|
err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs());
|
|
if (err != 0) {
|
|
#ifdef _WIN32
|
|
ERROR_LOG(FILESYS, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i", GetLastError(), (int)access);
|
|
#else
|
|
ERROR_LOG(FILESYS, "DirectoryFileSystem::OpenFile: FAILED, %i - access = %i", errno, (int)access);
|
|
#endif
|
|
//wwwwaaaaahh!!
|
|
return err;
|
|
} else {
|
|
#ifdef _WIN32
|
|
if (access & FILEACCESS_APPEND)
|
|
entry.hFile.Seek(0,FILEMOVE_END);
|
|
#endif
|
|
|
|
u32 newHandle = hAlloc->GetNewHandle();
|
|
|
|
entry.guestFilename = filename;
|
|
entry.access = access;
|
|
|
|
entries[newHandle] = entry;
|
|
|
|
return newHandle;
|
|
}
|
|
}
|
|
|
|
void DirectoryFileSystem::CloseFile(u32 handle) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end()) {
|
|
hAlloc->FreeHandle(handle);
|
|
iter->second.hFile.Close();
|
|
entries.erase(iter);
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot close file that hasn't been opened: %08x", handle);
|
|
}
|
|
}
|
|
|
|
bool DirectoryFileSystem::OwnsHandle(u32 handle) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
return (iter != entries.end());
|
|
}
|
|
|
|
int DirectoryFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
|
|
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
|
|
}
|
|
|
|
PSPDevType DirectoryFileSystem::DevType(u32 handle) {
|
|
return PSPDevType::FILE;
|
|
}
|
|
|
|
size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {
|
|
int ignored;
|
|
return ReadFile(handle, pointer, size, ignored);
|
|
}
|
|
|
|
size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end()) {
|
|
if (size < 0) {
|
|
ERROR_LOG_REPORT(FILESYS, "Invalid read for %lld bytes from disk %s", size, iter->second.guestFilename.c_str());
|
|
return 0;
|
|
}
|
|
|
|
size_t bytesRead = iter->second.hFile.Read(pointer,size);
|
|
return bytesRead;
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot read file that hasn't been opened: %08x", handle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {
|
|
int ignored;
|
|
return WriteFile(handle, pointer, size, ignored);
|
|
}
|
|
|
|
size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end())
|
|
{
|
|
size_t bytesWritten = iter->second.hFile.Write(pointer,size);
|
|
return bytesWritten;
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot write to file that hasn't been opened: %08x", handle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t DirectoryFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end()) {
|
|
return iter->second.hFile.Seek(position,type);
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot seek in file that hasn't been opened: %08x", handle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {
|
|
PSPFileInfo x;
|
|
x.name = filename;
|
|
|
|
std::string fullName = GetLocalPath(filename);
|
|
if (!File::Exists(fullName)) {
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (! FixPathCase(basePath,filename, FPC_FILE_MUST_EXIST))
|
|
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
|
|
fullName = GetLocalPath(filename);
|
|
|
|
if (! File::Exists(fullName))
|
|
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
|
|
#else
|
|
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
|
|
#endif
|
|
}
|
|
x.type = File::IsDirectory(fullName) ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
|
|
x.exists = true;
|
|
|
|
if (x.type != FILETYPE_DIRECTORY) {
|
|
File::FileDetails details;
|
|
if (!File::GetFileDetails(fullName, &details)) {
|
|
ERROR_LOG(FILESYS, "DirectoryFileSystem::GetFileInfo: GetFileDetails failed: %s", fullName.c_str());
|
|
} else {
|
|
x.size = details.size;
|
|
x.access = details.access;
|
|
time_t atime = details.atime;
|
|
time_t ctime = details.ctime;
|
|
time_t mtime = details.mtime;
|
|
|
|
localtime_r((time_t*)&atime, &x.atime);
|
|
localtime_r((time_t*)&ctime, &x.ctime);
|
|
localtime_r((time_t*)&mtime, &x.mtime);
|
|
}
|
|
}
|
|
|
|
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
bool DirectoryFileSystem::GetHostPath(const std::string &inpath, std::string &outpath) {
|
|
outpath = GetLocalPath(inpath);
|
|
return true;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
#define FILETIME_FROM_UNIX_EPOCH_US 11644473600000000ULL
|
|
|
|
static void tmFromFiletime(tm &dest, FILETIME &src) {
|
|
u64 from_1601_us = (((u64) src.dwHighDateTime << 32ULL) + (u64) src.dwLowDateTime) / 10ULL;
|
|
u64 from_1970_us = from_1601_us - FILETIME_FROM_UNIX_EPOCH_US;
|
|
|
|
time_t t = (time_t) (from_1970_us / 1000000UL);
|
|
localtime_r(&t, &dest);
|
|
}
|
|
#endif
|
|
|
|
// This simulates a bug in the PSP VFAT driver.
|
|
//
|
|
// Windows NT VFAT optimizes valid DOS filenames that are in lowercase.
|
|
// The PSP VFAT driver doesn't support this optimization, and behaves like Windows 98.
|
|
// Some homebrew depends on this bug in the PSP firmware.
|
|
//
|
|
// This essentially tries to simulate the "Windows 98 world view" on modern operating systems.
|
|
// Essentially all lowercase files are seen as UPPERCASE.
|
|
//
|
|
// Note: PSP-created files would stay lowercase, but this uppercases them too.
|
|
// Hopefully no PSP games read directories after they create files in them...
|
|
static std::string SimulateVFATBug(std::string filename) {
|
|
// These are the characters allowed in DOS filenames.
|
|
static const char *FAT_UPPER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&'(){}-_`~";
|
|
static const char *FAT_LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&'(){}-_`~";
|
|
static const char *LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz";
|
|
|
|
// To avoid logging/comparing, skip all this if it has no lowercase chars to begin with.
|
|
size_t lowerchar = filename.find_first_of(LOWER_CHARS);
|
|
if (lowerchar == filename.npos) {
|
|
return filename;
|
|
}
|
|
|
|
bool apply_hack = false;
|
|
size_t dot_pos = filename.find('.');
|
|
if (dot_pos == filename.npos && filename.length() <= 8) {
|
|
size_t badchar = filename.find_first_not_of(FAT_LOWER_CHARS);
|
|
if (badchar == filename.npos) {
|
|
// It's all lowercase. Convert to upper.
|
|
apply_hack = true;
|
|
}
|
|
} else {
|
|
// There's a separate flag for each, so we compare separately.
|
|
// But they both have to either be all upper or lowercase.
|
|
std::string base = filename.substr(0, dot_pos);
|
|
std::string ext = filename.substr(dot_pos + 1);
|
|
|
|
// The filename must be short enough to fit.
|
|
if (base.length() <= 8 && ext.length() <= 3) {
|
|
size_t base_non_lower = base.find_first_not_of(FAT_LOWER_CHARS);
|
|
size_t base_non_upper = base.find_first_not_of(FAT_UPPER_CHARS);
|
|
size_t ext_non_lower = ext.find_first_not_of(FAT_LOWER_CHARS);
|
|
size_t ext_non_upper = ext.find_first_not_of(FAT_UPPER_CHARS);
|
|
|
|
// As long as neither is mixed, we apply the hack.
|
|
bool base_apply_hack = base_non_lower == base.npos || base_non_upper == base.npos;
|
|
bool ext_apply_hack = ext_non_lower == ext.npos || ext_non_upper == ext.npos;
|
|
apply_hack = base_apply_hack && ext_apply_hack;
|
|
}
|
|
}
|
|
|
|
if (apply_hack) {
|
|
VERBOSE_LOG(FILESYS, "Applying VFAT hack to filename: %s", filename.c_str());
|
|
// In this situation, NT would write UPPERCASE, and just set a flag to say "actually lowercase".
|
|
// That VFAT flag isn't read by the PSP firmware, so let's pretend to "not read it."
|
|
std::transform(filename.begin(), filename.end(), filename.begin(), toupper);
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
std::vector<PSPFileInfo> DirectoryFileSystem::GetDirListing(std::string path) {
|
|
std::vector<PSPFileInfo> myVector;
|
|
bool listingRoot = path == "/" || path == "\\";
|
|
|
|
#ifdef _WIN32
|
|
WIN32_FIND_DATA findData;
|
|
HANDLE hFind;
|
|
|
|
std::string w32path = GetLocalPath(path) + "\\*.*";
|
|
|
|
hFind = FindFirstFileEx(ConvertUTF8ToWString(w32path).c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0);
|
|
|
|
if (hFind == INVALID_HANDLE_VALUE) {
|
|
// Just return the empty array.
|
|
return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles;
|
|
while (true) {
|
|
PSPFileInfo entry;
|
|
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
entry.type = FILETYPE_DIRECTORY;
|
|
else
|
|
entry.type = FILETYPE_NORMAL;
|
|
|
|
// TODO: Make this more correct?
|
|
entry.access = entry.type == FILETYPE_NORMAL ? 0666 : 0777;
|
|
// TODO: is this just for .. or all subdirectories? Need to add a directory to the test
|
|
// to find out. Also why so different than the old test results?
|
|
if (!wcscmp(findData.cFileName, L".."))
|
|
entry.size = 4096;
|
|
else
|
|
entry.size = findData.nFileSizeLow | ((u64)findData.nFileSizeHigh<<32);
|
|
entry.name = ConvertWStringToUTF8(findData.cFileName);
|
|
if (Flags() & FileSystemFlags::SIMULATE_FAT32)
|
|
entry.name = SimulateVFATBug(entry.name);
|
|
|
|
bool hideFile = false;
|
|
if (hideISOFiles && (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso"))) {
|
|
// Workaround for DJ Max Portable, see compat.ini.
|
|
hideFile = true;
|
|
}
|
|
tmFromFiletime(entry.atime, findData.ftLastAccessTime);
|
|
tmFromFiletime(entry.ctime, findData.ftCreationTime);
|
|
tmFromFiletime(entry.mtime, findData.ftLastWriteTime);
|
|
if (!hideFile && (!listingRoot || (wcscmp(findData.cFileName, L"..") && wcscmp(findData.cFileName, L"."))))
|
|
myVector.push_back(entry);
|
|
|
|
int retval = FindNextFile(hFind, &findData);
|
|
if (!retval)
|
|
break;
|
|
}
|
|
FindClose(hFind);
|
|
#else
|
|
dirent *dirp;
|
|
std::string localPath = GetLocalPath(path);
|
|
DIR *dp = opendir(localPath.c_str());
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
if (dp == NULL && FixPathCase(basePath,path, FPC_FILE_MUST_EXIST)) {
|
|
// May have failed due to case sensitivity, try again
|
|
localPath = GetLocalPath(path);
|
|
dp = opendir(localPath.c_str());
|
|
}
|
|
#endif
|
|
|
|
if (dp == NULL) {
|
|
ERROR_LOG(FILESYS,"Error opening directory %s\n",path.c_str());
|
|
return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles;
|
|
while ((dirp = readdir(dp)) != NULL) {
|
|
PSPFileInfo entry;
|
|
struct stat s;
|
|
std::string fullName = GetLocalPath(path) + "/"+dirp->d_name;
|
|
stat(fullName.c_str(), &s);
|
|
if (S_ISDIR(s.st_mode))
|
|
entry.type = FILETYPE_DIRECTORY;
|
|
else
|
|
entry.type = FILETYPE_NORMAL;
|
|
entry.access = s.st_mode & 0x1FF;
|
|
entry.name = dirp->d_name;
|
|
if (Flags() & FileSystemFlags::SIMULATE_FAT32)
|
|
entry.name = SimulateVFATBug(entry.name);
|
|
entry.size = s.st_size;
|
|
|
|
bool hideFile = false;
|
|
if (hideISOFiles && (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso"))) {
|
|
// Workaround for DJ Max Portable, see compat.ini.
|
|
hideFile = true;
|
|
}
|
|
|
|
localtime_r((time_t*)&s.st_atime,&entry.atime);
|
|
localtime_r((time_t*)&s.st_ctime,&entry.ctime);
|
|
localtime_r((time_t*)&s.st_mtime,&entry.mtime);
|
|
if (!hideFile && (!listingRoot || (strcmp(dirp->d_name, "..") && strcmp(dirp->d_name, "."))))
|
|
myVector.push_back(entry);
|
|
}
|
|
closedir(dp);
|
|
#endif
|
|
|
|
return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
u64 DirectoryFileSystem::FreeSpace(const std::string &path) {
|
|
uint64_t result = 0;
|
|
if (free_disk_space(GetLocalPath(path), result)) {
|
|
return ReplayApplyDisk64(ReplayAction::FREESPACE, result, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
#if HOST_IS_CASE_SENSITIVE
|
|
std::string fixedCase = path;
|
|
if (FixPathCase(basePath, fixedCase, FPC_FILE_MUST_EXIST)) {
|
|
// May have failed due to case sensitivity, try again.
|
|
if (free_disk_space(GetLocalPath(fixedCase), result)) {
|
|
return ReplayApplyDisk64(ReplayAction::FREESPACE, result, CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Just assume they're swimming in free disk space if we don't know otherwise.
|
|
return ReplayApplyDisk64(ReplayAction::FREESPACE, std::numeric_limits<u64>::max(), CoreTiming::GetGlobalTimeUs());
|
|
}
|
|
|
|
void DirectoryFileSystem::DoState(PointerWrap &p) {
|
|
auto s = p.Section("DirectoryFileSystem", 0, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
// Savestate layout:
|
|
// u32: number of entries
|
|
// per-entry:
|
|
// u32: handle number
|
|
// std::string filename (in guest's terms, untranslated)
|
|
// enum FileAccess file access mode
|
|
// u32 seek position
|
|
// s64 current truncate position (v2+ only)
|
|
|
|
u32 num = (u32) entries.size();
|
|
Do(p, num);
|
|
|
|
if (p.mode == p.MODE_READ) {
|
|
CloseAll();
|
|
u32 key;
|
|
OpenFileEntry entry;
|
|
for (u32 i = 0; i < num; i++) {
|
|
Do(p, key);
|
|
Do(p, entry.guestFilename);
|
|
Do(p, entry.access);
|
|
u32 err;
|
|
if (!entry.hFile.Open(basePath,entry.guestFilename,entry.access, err)) {
|
|
ERROR_LOG(FILESYS, "Failed to reopen file while loading state: %s", entry.guestFilename.c_str());
|
|
continue;
|
|
}
|
|
u32 position;
|
|
Do(p, position);
|
|
if (position != entry.hFile.Seek(position, FILEMOVE_BEGIN)) {
|
|
ERROR_LOG(FILESYS, "Failed to restore seek position while loading state: %s", entry.guestFilename.c_str());
|
|
continue;
|
|
}
|
|
if (s >= 2) {
|
|
Do(p, entry.hFile.needsTrunc_);
|
|
}
|
|
entries[key] = entry;
|
|
}
|
|
} else {
|
|
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
|
|
u32 key = iter->first;
|
|
Do(p, key);
|
|
Do(p, iter->second.guestFilename);
|
|
Do(p, iter->second.access);
|
|
u32 position = (u32)iter->second.hFile.Seek(0, FILEMOVE_CURRENT);
|
|
Do(p, position);
|
|
Do(p, iter->second.hFile.needsTrunc_);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
VFSFileSystem::VFSFileSystem(IHandleAllocator *_hAlloc, std::string _basePath) : basePath(_basePath) {
|
|
hAlloc = _hAlloc;
|
|
}
|
|
|
|
VFSFileSystem::~VFSFileSystem() {
|
|
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
|
|
delete [] iter->second.fileData;
|
|
}
|
|
entries.clear();
|
|
}
|
|
|
|
std::string VFSFileSystem::GetLocalPath(std::string localPath) {
|
|
return basePath + localPath;
|
|
}
|
|
|
|
bool VFSFileSystem::MkDir(const std::string &dirname) {
|
|
// NOT SUPPORTED - READ ONLY
|
|
return false;
|
|
}
|
|
|
|
bool VFSFileSystem::RmDir(const std::string &dirname) {
|
|
// NOT SUPPORTED - READ ONLY
|
|
return false;
|
|
}
|
|
|
|
int VFSFileSystem::RenameFile(const std::string &from, const std::string &to) {
|
|
// NOT SUPPORTED - READ ONLY
|
|
return -1;
|
|
}
|
|
|
|
bool VFSFileSystem::RemoveFile(const std::string &filename) {
|
|
// NOT SUPPORTED - READ ONLY
|
|
return false;
|
|
}
|
|
|
|
int VFSFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
|
|
if (access != FILEACCESS_READ) {
|
|
ERROR_LOG(FILESYS, "VFSFileSystem only supports plain reading");
|
|
return SCE_KERNEL_ERROR_ERRNO_INVALID_FLAG;
|
|
}
|
|
|
|
std::string fullName = GetLocalPath(filename);
|
|
const char *fullNameC = fullName.c_str();
|
|
VERBOSE_LOG(FILESYS,"VFSFileSystem actually opening %s (%s)", fullNameC, filename.c_str());
|
|
|
|
size_t size;
|
|
u8 *data = VFSReadFile(fullNameC, &size);
|
|
if (!data) {
|
|
ERROR_LOG(FILESYS, "VFSFileSystem failed to open %s", filename.c_str());
|
|
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
|
|
}
|
|
|
|
OpenFileEntry entry;
|
|
entry.fileData = data;
|
|
entry.size = size;
|
|
entry.seekPos = 0;
|
|
u32 newHandle = hAlloc->GetNewHandle();
|
|
entries[newHandle] = entry;
|
|
return newHandle;
|
|
}
|
|
|
|
PSPFileInfo VFSFileSystem::GetFileInfo(std::string filename) {
|
|
PSPFileInfo x;
|
|
x.name = filename;
|
|
|
|
std::string fullName = GetLocalPath(filename);
|
|
FileInfo fo;
|
|
if (VFSGetFileInfo(fullName.c_str(), &fo)) {
|
|
x.exists = fo.exists;
|
|
if (x.exists) {
|
|
x.size = fo.size;
|
|
x.type = fo.isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
|
|
x.access = fo.isWritable ? 0666 : 0444;
|
|
}
|
|
} else {
|
|
x.exists = false;
|
|
}
|
|
return x;
|
|
}
|
|
|
|
void VFSFileSystem::CloseFile(u32 handle) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end()) {
|
|
delete [] iter->second.fileData;
|
|
entries.erase(iter);
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot close file that hasn't been opened: %08x", handle);
|
|
}
|
|
}
|
|
|
|
bool VFSFileSystem::OwnsHandle(u32 handle) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
return (iter != entries.end());
|
|
}
|
|
|
|
int VFSFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
|
|
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
|
|
}
|
|
|
|
PSPDevType VFSFileSystem::DevType(u32 handle) {
|
|
return PSPDevType::FILE;
|
|
}
|
|
|
|
size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {
|
|
int ignored;
|
|
return ReadFile(handle, pointer, size, ignored);
|
|
}
|
|
|
|
size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
|
|
DEBUG_LOG(FILESYS,"VFSFileSystem::ReadFile %08x %p %i", handle, pointer, (u32)size);
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end())
|
|
{
|
|
size_t bytesRead = size;
|
|
memcpy(pointer, iter->second.fileData + iter->second.seekPos, size);
|
|
iter->second.seekPos += size;
|
|
return bytesRead;
|
|
} else {
|
|
ERROR_LOG(FILESYS,"Cannot read file that hasn't been opened: %08x", handle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {
|
|
int ignored;
|
|
return WriteFile(handle, pointer, size, ignored);
|
|
}
|
|
|
|
size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {
|
|
// NOT SUPPORTED - READ ONLY
|
|
return 0;
|
|
}
|
|
|
|
size_t VFSFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
|
|
EntryMap::iterator iter = entries.find(handle);
|
|
if (iter != entries.end()) {
|
|
switch (type) {
|
|
case FILEMOVE_BEGIN: iter->second.seekPos = position; break;
|
|
case FILEMOVE_CURRENT: iter->second.seekPos += position; break;
|
|
case FILEMOVE_END: iter->second.seekPos = iter->second.size + position; break;
|
|
}
|
|
return iter->second.seekPos;
|
|
} else {
|
|
//This shouldn't happen...
|
|
ERROR_LOG(FILESYS,"Cannot seek in file that hasn't been opened: %08x", handle);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
bool VFSFileSystem::GetHostPath(const std::string &inpath, std::string &outpath) {
|
|
// NOT SUPPORTED
|
|
return false;
|
|
}
|
|
|
|
std::vector<PSPFileInfo> VFSFileSystem::GetDirListing(std::string path) {
|
|
std::vector<PSPFileInfo> myVector;
|
|
// TODO
|
|
return myVector;
|
|
}
|
|
|
|
void VFSFileSystem::DoState(PointerWrap &p) {
|
|
// Note: used interchangeably with DirectoryFileSystem for flash0:, so we use the same name.
|
|
auto s = p.Section("DirectoryFileSystem", 0, 2);
|
|
if (!s)
|
|
return;
|
|
|
|
u32 num = (u32) entries.size();
|
|
Do(p, num);
|
|
|
|
if (num != 0) {
|
|
p.SetError(p.ERROR_WARNING);
|
|
ERROR_LOG(FILESYS, "FIXME: Open files during savestate, could go badly.");
|
|
}
|
|
}
|