CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/FileSystems/DirectoryFileSystem.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"1819#ifdef __MINGW32__20#include <unistd.h>21#ifndef _POSIX_THREAD_SAFE_FUNCTIONS22#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L23#endif24#endif2526#include <algorithm>27#include <ctime>28#include <limits>2930#include "Common/Data/Text/I18n.h"31#include "Common/Data/Encoding/Utf8.h"32#include "Common/Serialize/Serializer.h"33#include "Common/Serialize/SerializeFuncs.h"34#include "Common/StringUtils.h"35#include "Common/System/OSD.h"36#include "Common/File/FileUtil.h"37#include "Common/File/DiskFree.h"38#include "Common/File/VFS/VFS.h"39#include "Common/SysError.h"40#include "Core/FileSystems/DirectoryFileSystem.h"41#include "Core/FileSystems/ISOFileSystem.h"42#include "Core/HLE/sceKernel.h"43#include "Core/HW/MemoryStick.h"44#include "Core/CoreTiming.h"45#include "Core/System.h"46#include "Core/Replay.h"47#include "Core/Reporting.h"48#include "Core/ELF/ParamSFO.h"4950#ifdef _WIN3251#include "Common/CommonWindows.h"52#include <sys/stat.h>53#if PPSSPP_PLATFORM(UWP)54#include <fileapifromapp.h>55#endif56#undef FILE_OPEN57#else58#include <dirent.h>59#include <unistd.h>60#include <sys/stat.h>61#if defined(__ANDROID__)62#include <sys/types.h>63#include <sys/vfs.h>64#define statvfs statfs65#else66#include <sys/statvfs.h>67#endif68#include <ctype.h>69#include <fcntl.h>70#endif7172DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, const Path & _basePath, FileSystemFlags _flags) : basePath(_basePath), flags(_flags) {73File::CreateFullPath(basePath);74hAlloc = _hAlloc;75}7677DirectoryFileSystem::~DirectoryFileSystem() {78CloseAll();79}8081// TODO(scoped): Merge the two below functions somehow.8283Path DirectoryFileHandle::GetLocalPath(const Path &basePath, std::string localpath) const {84if (localpath.empty())85return basePath;8687if (localpath[0] == '/')88localpath.erase(0, 1);8990if (fileSystemFlags_ & FileSystemFlags::STRIP_PSP) {91if (startsWithNoCase(localpath, "PSP/")) {92localpath = localpath.substr(4);93}94}9596return basePath / localpath;97}9899Path DirectoryFileSystem::GetLocalPath(std::string internalPath) const {100if (internalPath.empty())101return basePath;102103if (internalPath[0] == '/')104internalPath.erase(0, 1);105106if (flags & FileSystemFlags::STRIP_PSP) {107if (startsWithNoCase(internalPath, "PSP/")) {108internalPath = internalPath.substr(4);109}110}111112return basePath / internalPath;113}114115bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, FileAccess access, u32 &error) {116error = 0;117118if (access == FILEACCESS_NONE) {119error = SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;120return false;121}122123#if HOST_IS_CASE_SENSITIVE124if (access & (FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE)) {125DEBUG_LOG(Log::FileSystem, "Checking case for path %s", fileName.c_str());126if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {127error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;128return false; // or go on and attempt (for a better error code than just 0?)129}130}131// else we try fopen first (in case we're lucky) before simulating case insensitivity132#endif133134Path fullName = GetLocalPath(basePath, fileName);135136// On the PSP, truncating doesn't lose data. If you seek later, you'll recover it.137// This is abnormal, so we deviate from the PSP's behavior and truncate on write/close.138// This means it's incorrectly not truncated before the write.139if (access & FILEACCESS_TRUNCATE) {140needsTrunc_ = 0;141}142143//TODO: tests, should append seek to end of file? seeking in a file opened for append?144#if PPSSPP_PLATFORM(WINDOWS)145// Convert parameters to Windows permissions and access146DWORD desired = 0;147DWORD sharemode = 0;148DWORD openmode = 0;149if (access & FILEACCESS_READ) {150desired |= GENERIC_READ;151sharemode |= FILE_SHARE_READ;152}153if (access & FILEACCESS_WRITE) {154desired |= GENERIC_WRITE;155sharemode |= FILE_SHARE_WRITE | FILE_SHARE_READ;156}157if (access & FILEACCESS_CREATE) {158if (access & FILEACCESS_EXCL) {159openmode = CREATE_NEW;160} else {161openmode = OPEN_ALWAYS;162}163} else {164openmode = OPEN_EXISTING;165}166167// Let's do it!168#if PPSSPP_PLATFORM(UWP)169hFile = CreateFile2FromAppW(fullName.ToWString().c_str(), desired, sharemode, openmode, nullptr);170#else171hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0);172#endif173bool success = hFile != INVALID_HANDLE_VALUE;174if (!success) {175DWORD w32err = GetLastError();176177if (w32err == ERROR_SHARING_VIOLATION) {178// Sometimes, the file is locked for write, let's try again.179sharemode |= FILE_SHARE_WRITE;180#if PPSSPP_PLATFORM(UWP)181hFile = CreateFile2FromAppW(fullName.ToWString().c_str(), desired, sharemode, openmode, nullptr);182#else183hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0);184#endif185success = hFile != INVALID_HANDLE_VALUE;186if (!success) {187w32err = GetLastError();188}189}190191if (w32err == ERROR_DISK_FULL || w32err == ERROR_NOT_ENOUGH_QUOTA) {192// This is returned when the disk is full.193auto err = GetI18NCategory(I18NCat::ERRORS);194g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");195error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;196} else if (!success) {197error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;198}199}200#else201if (fullName.Type() == PathType::CONTENT_URI) {202// Convert flags. Don't want to share this type, bad dependency.203u32 flags = File::OPEN_NONE;204if (access & FILEACCESS_READ)205flags |= File::OPEN_READ;206if (access & FILEACCESS_WRITE)207flags |= File::OPEN_WRITE;208if (access & FILEACCESS_APPEND)209flags |= File::OPEN_APPEND;210if (access & FILEACCESS_CREATE)211flags |= File::OPEN_CREATE;212// Important: don't pass TRUNCATE here, the PSP truncates weirdly. See #579.213// See above about truncate behavior. Just add READ to preserve data here.214if (access & FILEACCESS_TRUNCATE)215flags |= File::OPEN_READ;216217int fd = File::OpenFD(fullName, (File::OpenFlag)flags);218// Try to detect reads/writes to PSP/GAME to avoid them in replays.219if (fullName.FilePathContainsNoCase("PSP/GAME/")) {220inGameDir_ = true;221}222hFile = fd;223if (fd != -1) {224// Success225return true;226} else {227ERROR_LOG(Log::FileSystem, "File::OpenFD returned an error");228// TODO: Need better error codes from OpenFD so we can distinguish229// disk full. Just set not found for now.230error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;231return false;232}233}234235int flags = 0;236if (access & FILEACCESS_APPEND) {237flags |= O_APPEND;238}239if ((access & FILEACCESS_READ) && (access & FILEACCESS_WRITE)) {240flags |= O_RDWR;241} else if (access & FILEACCESS_READ) {242flags |= O_RDONLY;243} else if (access & FILEACCESS_WRITE) {244flags |= O_WRONLY;245}246if (access & FILEACCESS_CREATE) {247flags |= O_CREAT;248}249if (access & FILEACCESS_EXCL) {250flags |= O_EXCL;251}252253hFile = open(fullName.c_str(), flags, 0666);254bool success = hFile != -1;255#endif256257#if HOST_IS_CASE_SENSITIVE258if (!success && !(access & FILEACCESS_CREATE)) {259if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {260error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;261return false;262}263fullName = GetLocalPath(basePath, fileName);264DEBUG_LOG(Log::FileSystem, "Case may have been incorrect, second try opening %s (%s)", fullName.c_str(), fileName.c_str());265266// And try again with the correct case this time267#ifdef _WIN32268hFile = CreateFile(fullName.c_str(), desired, sharemode, 0, openmode, 0, 0);269success = hFile != INVALID_HANDLE_VALUE;270#else271hFile = open(fullName.c_str(), flags, 0666);272success = hFile != -1;273#endif274}275#endif276277#ifndef _WIN32278if (success) {279// Reject directories, even if we succeed in opening them.280// TODO: Might want to do this stat first...281struct stat st;282if (fstat(hFile, &st) == 0 && S_ISDIR(st.st_mode)) {283close(hFile);284errno = EISDIR;285success = false;286}287} else if (errno == ENOSPC) {288// This is returned when the disk is full.289auto err = GetI18NCategory(I18NCat::ERRORS);290g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");291error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;292} else {293error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;294}295#endif296297// Try to detect reads/writes to PSP/GAME to avoid them in replays.298if (fullName.FilePathContainsNoCase("PSP/GAME/")) {299inGameDir_ = true;300}301if (access & (FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE)) {302MemoryStick_NotifyWrite();303}304305return success;306}307308size_t DirectoryFileHandle::Read(u8* pointer, s64 size)309{310size_t bytesRead = 0;311if (needsTrunc_ != -1) {312// If the file was marked to be truncated, pretend there's nothing.313// On a PSP. it actually is truncated, but the data wasn't erased.314off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);315if (needsTrunc_ <= off) {316return replay_ ? ReplayApplyDiskRead(pointer, 0, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : 0;317}318if (needsTrunc_ < off + size) {319size = needsTrunc_ - off;320}321}322if (size > 0) {323#ifdef _WIN32324::ReadFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesRead, 0);325#else326bytesRead = read(hFile, pointer, size);327#endif328}329return replay_ ? ReplayApplyDiskRead(pointer, (uint32_t)bytesRead, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : bytesRead;330}331332size_t DirectoryFileHandle::Write(const u8* pointer, s64 size)333{334size_t bytesWritten = 0;335bool diskFull = false;336337#ifdef _WIN32338BOOL success = ::WriteFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesWritten, 0);339if (success == FALSE) {340DWORD err = GetLastError();341diskFull = err == ERROR_DISK_FULL || err == ERROR_NOT_ENOUGH_QUOTA;342}343#else344bytesWritten = write(hFile, pointer, size);345if (bytesWritten == (size_t)-1) {346diskFull = errno == ENOSPC;347}348#endif349if (needsTrunc_ != -1) {350off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);351if (needsTrunc_ < off) {352needsTrunc_ = off;353}354}355356if (replay_) {357bytesWritten = ReplayApplyDiskWrite(pointer, (uint64_t)bytesWritten, (uint64_t)size, &diskFull, inGameDir_, CoreTiming::GetGlobalTimeUs());358}359360MemoryStick_NotifyWrite();361362if (diskFull) {363ERROR_LOG(Log::FileSystem, "Disk full");364auto err = GetI18NCategory(I18NCat::ERRORS);365g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");366// We only return an error when the disk is actually full.367// When writing this would cause the disk to be full, so it wasn't written, we return 0.368if (MemoryStick_FreeSpace() == 0) {369// Sign extend on 64-bit.370return (size_t)(s64)(s32)SCE_KERNEL_ERROR_ERRNO_DEVICE_NO_FREE_SPACE;371}372}373374return bytesWritten;375}376377size_t DirectoryFileHandle::Seek(s32 position, FileMove type)378{379if (needsTrunc_ != -1) {380// If the file is "currently truncated" move to the end based on that position.381// The actual, underlying file hasn't been truncated (yet.)382if (type == FILEMOVE_END) {383type = FILEMOVE_BEGIN;384position = (s32)(needsTrunc_ + position);385}386}387388size_t result;389#ifdef _WIN32390DWORD moveMethod = 0;391switch (type) {392case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;393case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break;394case FILEMOVE_END: moveMethod = FILE_END; break;395}396397LARGE_INTEGER distance;398distance.QuadPart = position;399LARGE_INTEGER cursor;400SetFilePointerEx(hFile, distance, &cursor, moveMethod);401result = (size_t)cursor.QuadPart;402#else403int moveMethod = 0;404switch (type) {405case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;406case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;407case FILEMOVE_END: moveMethod = SEEK_END; break;408}409result = lseek(hFile, position, moveMethod);410#endif411412return replay_ ? (size_t)ReplayApplyDisk64(ReplayAction::FILE_SEEK, result, CoreTiming::GetGlobalTimeUs()) : result;413}414415void DirectoryFileHandle::Close()416{417if (needsTrunc_ != -1) {418#ifdef _WIN32419Seek((s32)needsTrunc_, FILEMOVE_BEGIN);420if (SetEndOfFile(hFile) == 0) {421ERROR_LOG_REPORT(Log::FileSystem, "Failed to truncate file.");422}423#elif !PPSSPP_PLATFORM(SWITCH)424// Note: it's not great that Switch cannot truncate appropriately...425if (ftruncate(hFile, (off_t)needsTrunc_) != 0) {426ERROR_LOG_REPORT(Log::FileSystem, "Failed to truncate file.");427}428#endif429}430#ifdef _WIN32431if (hFile != (HANDLE)-1)432CloseHandle(hFile);433#else434if (hFile != -1)435close(hFile);436#endif437}438439void DirectoryFileSystem::CloseAll() {440for (auto iter = entries.begin(); iter != entries.end(); ++iter) {441INFO_LOG(Log::FileSystem, "DirectoryFileSystem::CloseAll(): Force closing %d (%s)", (int)iter->first, iter->second.guestFilename.c_str());442iter->second.hFile.Close();443}444entries.clear();445}446447bool DirectoryFileSystem::MkDir(const std::string &dirname) {448bool result;449#if HOST_IS_CASE_SENSITIVE450// Must fix case BEFORE attempting, because MkDir would create451// duplicate (different case) directories452453std::string fixedCase = dirname;454if (!FixPathCase(basePath, fixedCase, FPC_PARTIAL_ALLOWED))455result = false;456else457result = File::CreateFullPath(GetLocalPath(fixedCase));458#else459result = File::CreateFullPath(GetLocalPath(dirname));460#endif461MemoryStick_NotifyWrite();462return ReplayApplyDisk(ReplayAction::MKDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;463}464465bool DirectoryFileSystem::RmDir(const std::string &dirname) {466Path fullName = GetLocalPath(dirname);467468#if HOST_IS_CASE_SENSITIVE469// Maybe we're lucky?470if (File::DeleteDirRecursively(fullName)) {471MemoryStick_NotifyWrite();472return (bool)ReplayApplyDisk(ReplayAction::RMDIR, true, CoreTiming::GetGlobalTimeUs());473}474475// Nope, fix case and try again. Should we try again?476std::string fullPath = dirname;477if (!FixPathCase(basePath, fullPath, FPC_FILE_MUST_EXIST))478return (bool)ReplayApplyDisk(ReplayAction::RMDIR, false, CoreTiming::GetGlobalTimeUs());479480fullName = GetLocalPath(fullPath);481#endif482483bool result = File::DeleteDirRecursively(fullName);484MemoryStick_NotifyWrite();485return ReplayApplyDisk(ReplayAction::RMDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;486}487488int DirectoryFileSystem::RenameFile(const std::string &from, const std::string &to) {489std::string fullTo = to;490491// Rename ignores the path (even if specified) on to.492size_t chop_at = to.find_last_of('/');493if (chop_at != to.npos)494fullTo = to.substr(chop_at + 1);495496// Now put it in the same directory as from.497size_t dirname_end = from.find_last_of('/');498if (dirname_end != from.npos)499fullTo = from.substr(0, dirname_end + 1) + fullTo;500501// At this point, we should check if the paths match and give an already exists error.502if (from == fullTo)503return ReplayApplyDisk(ReplayAction::FILE_RENAME, SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS, CoreTiming::GetGlobalTimeUs());504505Path fullFrom = GetLocalPath(from);506507#if HOST_IS_CASE_SENSITIVE508// In case TO should overwrite a file with different case. Check error code?509if (!FixPathCase(basePath, fullTo, FPC_PATH_MUST_EXIST))510return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());511#endif512513Path fullToPath = GetLocalPath(fullTo);514515bool retValue = File::Rename(fullFrom, fullToPath);516517#if HOST_IS_CASE_SENSITIVE518if (!retValue)519{520// May have failed due to case sensitivity on FROM, so try again. Check error code?521std::string fullFromPath = from;522if (!FixPathCase(basePath, fullFromPath, FPC_FILE_MUST_EXIST))523return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());524fullFrom = GetLocalPath(fullFromPath);525526retValue = File::Rename(fullFrom, fullToPath);527}528#endif529530// TODO: Better error codes.531int result = retValue ? 0 : (int)SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS;532MemoryStick_NotifyWrite();533return ReplayApplyDisk(ReplayAction::FILE_RENAME, result, CoreTiming::GetGlobalTimeUs());534}535536bool DirectoryFileSystem::RemoveFile(const std::string &filename) {537Path localPath = GetLocalPath(filename);538539bool retValue = File::Delete(localPath);540541#if HOST_IS_CASE_SENSITIVE542if (!retValue)543{544// May have failed due to case sensitivity, so try again. Try even if it fails?545std::string fullNamePath = filename;546if (!FixPathCase(basePath, fullNamePath, FPC_FILE_MUST_EXIST))547return (bool)ReplayApplyDisk(ReplayAction::FILE_REMOVE, false, CoreTiming::GetGlobalTimeUs());548localPath = GetLocalPath(fullNamePath);549550retValue = File::Delete(localPath);551}552#endif553554MemoryStick_NotifyWrite();555return ReplayApplyDisk(ReplayAction::FILE_REMOVE, retValue, CoreTiming::GetGlobalTimeUs()) != 0;556}557558int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {559OpenFileEntry entry;560entry.hFile.fileSystemFlags_ = flags;561u32 err = 0;562bool success = entry.hFile.Open(basePath, filename, (FileAccess)(access & FILEACCESS_PSP_FLAGS), err);563if (err == 0 && !success) {564err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;565}566567err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs());568if (err != 0) {569std::string errorString;570int logError;571#ifdef _WIN32572auto win32err = GetLastError();573logError = (int)win32err;574errorString = GetStringErrorMsg(win32err);575#else576logError = (int)errno;577#endif578if (!(access & FILEACCESS_PPSSPP_QUIET)) {579ERROR_LOG(Log::FileSystem, "DirectoryFileSystem::OpenFile('%s'): FAILED, %d - access = %d '%s'", filename.c_str(), logError, (int)(access & FILEACCESS_PSP_FLAGS), errorString.c_str());580}581return err;582} else {583#ifdef _WIN32584if (access & FILEACCESS_APPEND) {585entry.hFile.Seek(0, FILEMOVE_END);586}587#endif588589u32 newHandle = hAlloc->GetNewHandle();590591entry.guestFilename = filename;592entry.access = (FileAccess)(access & FILEACCESS_PSP_FLAGS);593594entries[newHandle] = entry;595596return newHandle;597}598}599600void DirectoryFileSystem::CloseFile(u32 handle) {601EntryMap::iterator iter = entries.find(handle);602if (iter != entries.end()) {603hAlloc->FreeHandle(handle);604iter->second.hFile.Close();605entries.erase(iter);606} else {607//This shouldn't happen...608ERROR_LOG(Log::FileSystem,"Cannot close file that hasn't been opened: %08x", handle);609}610}611612bool DirectoryFileSystem::OwnsHandle(u32 handle) {613EntryMap::iterator iter = entries.find(handle);614return (iter != entries.end());615}616617int DirectoryFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {618return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;619}620621PSPDevType DirectoryFileSystem::DevType(u32 handle) {622return PSPDevType::FILE;623}624625size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {626int ignored;627return ReadFile(handle, pointer, size, ignored);628}629630size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {631EntryMap::iterator iter = entries.find(handle);632if (iter != entries.end()) {633if (size < 0) {634ERROR_LOG_REPORT(Log::FileSystem, "Invalid read for %lld bytes from disk %s", size, iter->second.guestFilename.c_str());635return 0;636}637638size_t bytesRead = iter->second.hFile.Read(pointer,size);639return bytesRead;640} else {641// This shouldn't happen...642ERROR_LOG(Log::FileSystem,"Cannot read file that hasn't been opened: %08x", handle);643return 0;644}645}646647size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {648int ignored;649return WriteFile(handle, pointer, size, ignored);650}651652size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {653EntryMap::iterator iter = entries.find(handle);654if (iter != entries.end()) {655size_t bytesWritten = iter->second.hFile.Write(pointer,size);656return bytesWritten;657} else {658//This shouldn't happen...659ERROR_LOG(Log::FileSystem,"Cannot write to file that hasn't been opened: %08x", handle);660return 0;661}662}663664size_t DirectoryFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {665EntryMap::iterator iter = entries.find(handle);666if (iter != entries.end()) {667return iter->second.hFile.Seek(position,type);668} else {669//This shouldn't happen...670ERROR_LOG(Log::FileSystem,"Cannot seek in file that hasn't been opened: %08x", handle);671return 0;672}673}674675PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {676PSPFileInfo x;677x.name = filename;678679File::FileInfo info;680Path fullName = GetLocalPath(filename);681if (!File::GetFileInfo(fullName, &info)) {682#if HOST_IS_CASE_SENSITIVE683if (! FixPathCase(basePath, filename, FPC_FILE_MUST_EXIST))684return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());685fullName = GetLocalPath(filename);686687if (!File::GetFileInfo(fullName, &info))688return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());689#else690return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());691#endif692}693694x.type = info.isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;695x.exists = true;696697if (x.type != FILETYPE_DIRECTORY) {698x.size = info.size;699}700x.access = info.access;701time_t atime = info.atime;702time_t ctime = info.ctime;703time_t mtime = info.mtime;704705localtime_r((time_t*)&atime, &x.atime);706localtime_r((time_t*)&ctime, &x.ctime);707localtime_r((time_t*)&mtime, &x.mtime);708709return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());710}711712#ifdef _WIN32713#define FILETIME_FROM_UNIX_EPOCH_US 11644473600000000ULL714715static void tmFromFiletime(tm &dest, const FILETIME &src) {716u64 from_1601_us = (((u64) src.dwHighDateTime << 32ULL) + (u64) src.dwLowDateTime) / 10ULL;717u64 from_1970_us = from_1601_us - FILETIME_FROM_UNIX_EPOCH_US;718719time_t t = (time_t) (from_1970_us / 1000000UL);720localtime_r(&t, &dest);721}722#endif723724// This simulates a bug in the PSP VFAT driver.725//726// Windows NT VFAT optimizes valid DOS filenames that are in lowercase.727// The PSP VFAT driver doesn't support this optimization, and behaves like Windows 98.728// Some homebrew depends on this bug in the PSP firmware.729//730// This essentially tries to simulate the "Windows 98 world view" on modern operating systems.731// Essentially all lowercase files are seen as UPPERCASE.732//733// Note: PSP-created files would stay lowercase, but this uppercases them too.734// Hopefully no PSP games read directories after they create files in them...735static std::string SimulateVFATBug(std::string filename) {736// These are the characters allowed in DOS filenames.737static const char * const FAT_UPPER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&'(){}-_`~";738static const char * const FAT_LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&'(){}-_`~";739static const char * const LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz";740741// To avoid logging/comparing, skip all this if it has no lowercase chars to begin with.742size_t lowerchar = filename.find_first_of(LOWER_CHARS);743if (lowerchar == filename.npos) {744return filename;745}746747bool apply_hack = false;748size_t dot_pos = filename.find('.');749if (dot_pos == filename.npos && filename.length() <= 8) {750size_t badchar = filename.find_first_not_of(FAT_LOWER_CHARS);751if (badchar == filename.npos) {752// It's all lowercase. Convert to upper.753apply_hack = true;754}755} else {756// There's a separate flag for each, so we compare separately.757// But they both have to either be all upper or lowercase.758std::string base = filename.substr(0, dot_pos);759std::string ext = filename.substr(dot_pos + 1);760761// The filename must be short enough to fit.762if (base.length() <= 8 && ext.length() <= 3) {763size_t base_non_lower = base.find_first_not_of(FAT_LOWER_CHARS);764size_t base_non_upper = base.find_first_not_of(FAT_UPPER_CHARS);765size_t ext_non_lower = ext.find_first_not_of(FAT_LOWER_CHARS);766size_t ext_non_upper = ext.find_first_not_of(FAT_UPPER_CHARS);767768// As long as neither is mixed, we apply the hack.769bool base_apply_hack = base_non_lower == base.npos || base_non_upper == base.npos;770bool ext_apply_hack = ext_non_lower == ext.npos || ext_non_upper == ext.npos;771apply_hack = base_apply_hack && ext_apply_hack;772}773}774775if (apply_hack) {776VERBOSE_LOG(Log::FileSystem, "Applying VFAT hack to filename: %s", filename.c_str());777// In this situation, NT would write UPPERCASE, and just set a flag to say "actually lowercase".778// That VFAT flag isn't read by the PSP firmware, so let's pretend to "not read it."779std::transform(filename.begin(), filename.end(), filename.begin(), toupper);780}781782return filename;783}784785bool DirectoryFileSystem::ComputeRecursiveDirSizeIfFast(const std::string &path, int64_t *size) {786Path localPath = GetLocalPath(path);787788int64_t sizeTemp = File::ComputeRecursiveDirectorySize(localPath);789if (sizeTemp >= 0) {790*size = sizeTemp;791return true;792} else {793return false;794}795}796797std::vector<PSPFileInfo> DirectoryFileSystem::GetDirListing(const std::string &path, bool *exists) {798std::vector<PSPFileInfo> myVector;799800std::vector<File::FileInfo> files;801Path localPath = GetLocalPath(path);802const int flags = File::GETFILES_GETHIDDEN | File::GETFILES_GET_NAVIGATION_ENTRIES;803bool success = File::GetFilesInDir(localPath, &files, nullptr, flags);804#if HOST_IS_CASE_SENSITIVE805if (!success) {806// TODO: Case sensitivity should be checked on a file system basis, right?807std::string fixedPath = path;808if (FixPathCase(basePath, fixedPath, FPC_FILE_MUST_EXIST)) {809// May have failed due to case sensitivity, try again810localPath = GetLocalPath(fixedPath);811success = File::GetFilesInDir(localPath, &files, nullptr, flags);812}813}814#endif815if (!success) {816if (exists)817*exists = false;818return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());819}820821bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles;822823// Then apply transforms to match PSP idiosynchrasies, as we convert the entries.824for (auto &file : files) {825PSPFileInfo entry;826if (Flags() & FileSystemFlags::SIMULATE_FAT32) {827entry.name = SimulateVFATBug(file.name);828} else {829entry.name = file.name;830}831if (hideISOFiles) {832if (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso") || endsWithNoCase(entry.name, ".chd")) { // chd not really necessary, but let's hide them too.833// Workaround for DJ Max Portable, see compat.ini.834continue;835} else if (file.isDirectory) {836if (endsWithNoCase(path, "SAVEDATA")) {837// Don't let it see savedata from other games, it can misinterpret stuff.838std::string gameID = g_paramSFO.GetDiscID();839if (entry.name.size() > 2 && !startsWithNoCase(entry.name, gameID)) {840continue;841}842} else if (file.name == "GAME" || file.name == "TEXTURES" || file.name == "PPSSPP_STATE" || file.name == "PLUGINS" || file.name == "SYSTEM" || equalsNoCase(file.name, "Cheats")) {843// The game scans these folders on startup which can take time. Skip them.844continue;845}846}847}848if (file.name == "..") {849entry.size = 4096;850} else {851entry.size = file.size;852}853if (file.isDirectory) {854entry.type = FILETYPE_DIRECTORY;855} else {856entry.type = FILETYPE_NORMAL;857}858entry.access = file.access;859entry.exists = file.exists;860861localtime_r((time_t*)&file.atime, &entry.atime);862localtime_r((time_t*)&file.ctime, &entry.ctime);863localtime_r((time_t*)&file.mtime, &entry.mtime);864865myVector.push_back(entry);866}867868if (exists)869*exists = true;870return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());871}872873u64 DirectoryFileSystem::FreeSpace(const std::string &path) {874int64_t result = 0;875if (free_disk_space(GetLocalPath(path), result)) {876return ReplayApplyDisk64(ReplayAction::FREESPACE, (uint64_t)result, CoreTiming::GetGlobalTimeUs());877}878879#if HOST_IS_CASE_SENSITIVE880std::string fixedCase = path;881if (FixPathCase(basePath, fixedCase, FPC_FILE_MUST_EXIST)) {882// May have failed due to case sensitivity, try again.883if (free_disk_space(GetLocalPath(fixedCase), result)) {884return ReplayApplyDisk64(ReplayAction::FREESPACE, result, CoreTiming::GetGlobalTimeUs());885}886}887#endif888889// Just assume they're swimming in free disk space if we don't know otherwise.890return ReplayApplyDisk64(ReplayAction::FREESPACE, std::numeric_limits<u64>::max(), CoreTiming::GetGlobalTimeUs());891}892893void DirectoryFileSystem::DoState(PointerWrap &p) {894auto s = p.Section("DirectoryFileSystem", 0, 2);895if (!s)896return;897898// Savestate layout:899// u32: number of entries900// per-entry:901// u32: handle number902// std::string filename (in guest's terms, untranslated)903// enum FileAccess file access mode904// u32 seek position905// s64 current truncate position (v2+ only)906907u32 num = (u32) entries.size();908Do(p, num);909910if (p.mode == p.MODE_READ) {911CloseAll();912u32 key;913OpenFileEntry entry;914entry.hFile.fileSystemFlags_ = flags;915for (u32 i = 0; i < num; i++) {916Do(p, key);917Do(p, entry.guestFilename);918Do(p, entry.access);919u32 err;920bool brokenFile = false;921if (!entry.hFile.Open(basePath,entry.guestFilename,entry.access, err)) {922ERROR_LOG(Log::FileSystem, "Failed to reopen file while loading state: %s", entry.guestFilename.c_str());923brokenFile = true;924}925u32 position;926Do(p, position);927if (position != entry.hFile.Seek(position, FILEMOVE_BEGIN)) {928ERROR_LOG(Log::FileSystem, "Failed to restore seek position while loading state: %s", entry.guestFilename.c_str());929brokenFile = true;930}931if (s >= 2) {932Do(p, entry.hFile.needsTrunc_);933}934// Let's hope that things don't go that badly with the file mysteriously auto-closed.935// Better than not loading the save state at all, hopefully.936if (!brokenFile) {937entries[key] = entry;938}939}940} else {941for (auto iter = entries.begin(); iter != entries.end(); ++iter) {942u32 key = iter->first;943Do(p, key);944Do(p, iter->second.guestFilename);945Do(p, iter->second.access);946u32 position = (u32)iter->second.hFile.Seek(0, FILEMOVE_CURRENT);947Do(p, position);948Do(p, iter->second.hFile.needsTrunc_);949}950}951}952953954955VFSFileSystem::VFSFileSystem(IHandleAllocator *_hAlloc, std::string _basePath) : basePath(_basePath) {956hAlloc = _hAlloc;957}958959VFSFileSystem::~VFSFileSystem() {960for (auto iter = entries.begin(); iter != entries.end(); ++iter) {961delete [] iter->second.fileData;962}963entries.clear();964}965966std::string VFSFileSystem::GetLocalPath(const std::string &localPath) {967return basePath + localPath;968}969970bool VFSFileSystem::MkDir(const std::string &dirname) {971// NOT SUPPORTED - READ ONLY972return false;973}974975bool VFSFileSystem::RmDir(const std::string &dirname) {976// NOT SUPPORTED - READ ONLY977return false;978}979980int VFSFileSystem::RenameFile(const std::string &from, const std::string &to) {981// NOT SUPPORTED - READ ONLY982return -1;983}984985bool VFSFileSystem::RemoveFile(const std::string &filename) {986// NOT SUPPORTED - READ ONLY987return false;988}989990int VFSFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {991if (access != FILEACCESS_READ) {992ERROR_LOG(Log::FileSystem, "VFSFileSystem only supports plain reading");993return SCE_KERNEL_ERROR_ERRNO_INVALID_FLAG;994}995996std::string fullName = GetLocalPath(filename);997const char *fullNameC = fullName.c_str();998VERBOSE_LOG(Log::FileSystem, "VFSFileSystem actually opening %s (%s)", fullNameC, filename.c_str());9991000size_t size;1001u8 *data = g_VFS.ReadFile(fullNameC, &size);1002if (!data) {1003ERROR_LOG(Log::FileSystem, "VFSFileSystem failed to open %s", filename.c_str());1004return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;1005}10061007OpenFileEntry entry;1008entry.fileData = data;1009entry.size = size;1010entry.seekPos = 0;1011u32 newHandle = hAlloc->GetNewHandle();1012entries[newHandle] = entry;1013return newHandle;1014}10151016PSPFileInfo VFSFileSystem::GetFileInfo(std::string filename) {1017PSPFileInfo x;1018x.name = filename;10191020std::string fullName = GetLocalPath(filename);1021File::FileInfo fo;1022if (g_VFS.GetFileInfo(fullName.c_str(), &fo)) {1023x.exists = fo.exists;1024if (x.exists) {1025x.size = fo.size;1026x.type = fo.isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;1027x.access = fo.isWritable ? 0666 : 0444;1028}1029} else {1030x.exists = false;1031}1032return x;1033}10341035void VFSFileSystem::CloseFile(u32 handle) {1036EntryMap::iterator iter = entries.find(handle);1037if (iter != entries.end()) {1038delete [] iter->second.fileData;1039entries.erase(iter);1040} else {1041//This shouldn't happen...1042ERROR_LOG(Log::FileSystem,"Cannot close file that hasn't been opened: %08x", handle);1043}1044}10451046bool VFSFileSystem::OwnsHandle(u32 handle) {1047EntryMap::iterator iter = entries.find(handle);1048return (iter != entries.end());1049}10501051int VFSFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {1052return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;1053}10541055PSPDevType VFSFileSystem::DevType(u32 handle) {1056return PSPDevType::FILE;1057}10581059size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {1060int ignored;1061return ReadFile(handle, pointer, size, ignored);1062}10631064size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {1065DEBUG_LOG(Log::FileSystem,"VFSFileSystem::ReadFile %08x %p %i", handle, pointer, (u32)size);1066EntryMap::iterator iter = entries.find(handle);1067if (iter != entries.end())1068{1069if(iter->second.seekPos + size > iter->second.size)1070size = iter->second.size - iter->second.seekPos;1071if(size < 0) size = 0;1072size_t bytesRead = size;1073memcpy(pointer, iter->second.fileData + iter->second.seekPos, size);1074iter->second.seekPos += size;1075return bytesRead;1076} else {1077ERROR_LOG(Log::FileSystem,"Cannot read file that hasn't been opened: %08x", handle);1078return 0;1079}1080}10811082size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {1083int ignored;1084return WriteFile(handle, pointer, size, ignored);1085}10861087size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {1088// NOT SUPPORTED - READ ONLY1089return 0;1090}10911092size_t VFSFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {1093EntryMap::iterator iter = entries.find(handle);1094if (iter != entries.end()) {1095switch (type) {1096case FILEMOVE_BEGIN: iter->second.seekPos = position; break;1097case FILEMOVE_CURRENT: iter->second.seekPos += position; break;1098case FILEMOVE_END: iter->second.seekPos = iter->second.size + position; break;1099}1100return iter->second.seekPos;1101} else {1102//This shouldn't happen...1103ERROR_LOG(Log::FileSystem,"Cannot seek in file that hasn't been opened: %08x", handle);1104return 0;1105}1106}11071108std::vector<PSPFileInfo> VFSFileSystem::GetDirListing(const std::string &path, bool *exists) {1109std::vector<PSPFileInfo> myVector;1110// TODO1111if (exists)1112*exists = false;1113return myVector;1114}11151116void VFSFileSystem::DoState(PointerWrap &p) {1117// Note: used interchangeably with DirectoryFileSystem for flash0:, so we use the same name.1118auto s = p.Section("DirectoryFileSystem", 0, 2);1119if (!s)1120return;11211122u32 num = (u32) entries.size();1123Do(p, num);11241125if (num != 0) {1126p.SetError(p.ERROR_WARNING);1127ERROR_LOG(Log::FileSystem, "FIXME: Open files during savestate, could go badly.");1128}1129}113011311132