2012-11-01 16:19:01 +01:00
// 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
2012-11-04 23:01:49 +01:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 16:19:01 +01:00
// 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/.
2017-02-24 18:59:41 +01:00
# include "ppsspp_config.h"
2021-02-14 09:49:14 -08:00
# ifdef __MINGW32__
# include <unistd.h>
# ifndef _POSIX_THREAD_SAFE_FUNCTIONS
# define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
# endif
# endif
2017-06-12 22:55:50 -07:00
# include <algorithm>
2021-02-14 09:25:20 -08:00
# include <ctime>
2014-10-31 23:48:08 -07:00
# include <limits>
2020-09-29 12:19:22 +02:00
2020-10-01 13:05:04 +02:00
# include "Common/Data/Text/I18n.h"
# include "Common/Data/Encoding/Utf8.h"
2020-08-10 00:12:51 -07:00
# include "Common/Serialize/Serializer.h"
# include "Common/Serialize/SerializeFuncs.h"
2020-09-29 12:19:22 +02:00
# include "Common/StringUtils.h"
2023-06-30 17:15:49 +02:00
# include "Common/System/OSD.h"
2020-10-04 20:48:47 +02:00
# include "Common/File/FileUtil.h"
2020-10-04 00:25:21 +02:00
# include "Common/File/DiskFree.h"
# include "Common/File/VFS/VFS.h"
2021-05-01 09:37:36 -07:00
# include "Common/SysError.h"
2014-11-02 13:29:25 -08:00
# include "Core/FileSystems/DirectoryFileSystem.h"
# include "Core/FileSystems/ISOFileSystem.h"
2013-06-23 20:09:44 -07:00
# include "Core/HLE/sceKernel.h"
2014-11-02 12:40:31 -08:00
# include "Core/HW/MemoryStick.h"
2018-03-17 23:30:04 -07:00
# include "Core/CoreTiming.h"
2019-08-01 18:38:40 +02:00
# include "Core/System.h"
2018-03-17 23:30:04 -07:00
# include "Core/Replay.h"
2015-01-09 15:50:06 -08:00
# include "Core/Reporting.h"
2023-05-16 17:51:27 +02:00
# include "Core/ELF/ParamSFO.h"
2013-02-04 20:19:00 +01:00
2012-11-01 16:19:01 +01:00
# ifdef _WIN32
2013-07-28 20:43:25 -07:00
# include "Common/CommonWindows.h"
2013-01-21 01:15:57 -08:00
# include <sys/stat.h>
2022-07-06 22:59:47 +01:00
# if PPSSPP_PLATFORM(UWP)
# include <fileapifromapp.h>
# endif
2021-02-14 10:02:28 -08:00
# undef FILE_OPEN
2012-11-01 16:19:01 +01:00
# else
2012-12-27 04:27:07 -08:00
# include <dirent.h>
2012-11-01 16:19:01 +01:00
# include <unistd.h>
# include <sys/stat.h>
2016-10-12 11:13:16 +02:00
# if defined(__ANDROID__)
2014-11-01 00:19:31 -07:00
# include <sys/types.h>
# include <sys/vfs.h>
# define statvfs statfs
2014-11-02 12:27:20 +10:00
# else
2014-11-01 00:19:31 -07:00
# include <sys/statvfs.h>
# endif
2012-12-27 23:41:22 +01:00
# include <ctype.h>
2014-02-15 00:14:56 -08:00
# include <fcntl.h>
2012-11-01 16:19:01 +01:00
# endif
2021-05-06 01:31:38 +02:00
DirectoryFileSystem : : DirectoryFileSystem ( IHandleAllocator * _hAlloc , const Path & _basePath , FileSystemFlags _flags ) : basePath ( _basePath ) , flags ( _flags ) {
2014-04-13 23:22:17 +02:00
File : : CreateFullPath ( basePath ) ;
hAlloc = _hAlloc ;
}
2019-02-25 20:51:19 +01:00
DirectoryFileSystem : : ~ DirectoryFileSystem ( ) {
CloseAll ( ) ;
}
2021-07-24 18:16:12 +02:00
// TODO(scoped): Merge the two below functions somehow.
Path DirectoryFileHandle : : GetLocalPath ( const Path & basePath , std : : string localpath ) const {
2013-07-25 13:45:30 +02:00
if ( localpath . empty ( ) )
return basePath ;
if ( localpath [ 0 ] = = ' / ' )
2019-07-04 21:00:14 -07:00
localpath . erase ( 0 , 1 ) ;
2021-07-24 18:16:12 +02:00
if ( fileSystemFlags_ & FileSystemFlags : : STRIP_PSP ) {
2021-12-16 11:37:05 -08:00
if ( startsWithNoCase ( localpath , " PSP/ " ) ) {
2021-07-24 18:16:12 +02:00
localpath = localpath . substr ( 4 ) ;
}
}
2021-05-11 09:50:28 +02:00
return basePath / localpath ;
2013-07-25 13:45:30 +02:00
}
2021-07-24 18:16:12 +02:00
Path DirectoryFileSystem : : GetLocalPath ( std : : string internalPath ) const {
if ( internalPath . empty ( ) )
return basePath ;
if ( internalPath [ 0 ] = = ' / ' )
internalPath . erase ( 0 , 1 ) ;
if ( flags & FileSystemFlags : : STRIP_PSP ) {
2021-12-16 11:37:05 -08:00
if ( startsWithNoCase ( internalPath , " PSP/ " ) ) {
2021-07-24 18:16:12 +02:00
internalPath = internalPath . substr ( 4 ) ;
}
}
return basePath / internalPath ;
}
2021-05-06 01:31:38 +02:00
bool DirectoryFileHandle : : Open ( const Path & basePath , std : : string & fileName , FileAccess access , u32 & error ) {
2014-11-02 13:29:25 -08:00
error = 0 ;
2014-11-02 12:40:31 -08:00
2022-10-15 20:55:26 -07:00
if ( access = = FILEACCESS_NONE ) {
error = SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT ;
return false ;
}
2013-07-24 22:49:45 +02:00
# if HOST_IS_CASE_SENSITIVE
2021-05-16 12:37:08 +02:00
if ( access & ( FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE ) ) {
2013-09-07 22:02:55 +02:00
DEBUG_LOG ( FILESYS , " Checking case for path %s " , fileName . c_str ( ) ) ;
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fileName , FPC_PATH_MUST_EXIST ) ) {
2018-05-06 10:15:05 -07:00
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
2013-07-24 22:49:45 +02:00
return false ; // or go on and attempt (for a better error code than just 0?)
2018-05-06 10:15:05 -07:00
}
2013-07-24 22:49:45 +02:00
}
// else we try fopen first (in case we're lucky) before simulating case insensitivity
2013-07-23 17:24:33 +02:00
# endif
2021-05-06 01:31:38 +02:00
Path fullName = GetLocalPath ( basePath , fileName ) ;
2013-07-24 22:49:45 +02:00
2014-02-15 00:59:54 -08:00
// 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 ;
}
2013-07-24 22:49:45 +02:00
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
2021-05-16 12:37:08 +02:00
# if PPSSPP_PLATFORM(WINDOWS)
2013-07-24 22:49:45 +02:00
// 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 ;
2019-08-10 08:34:01 -07:00
sharemode | = FILE_SHARE_WRITE | FILE_SHARE_READ ;
2013-07-24 22:49:45 +02:00
}
if ( access & FILEACCESS_CREATE ) {
2017-02-19 10:04:54 +01:00
if ( access & FILEACCESS_EXCL ) {
openmode = CREATE_NEW ;
} else {
openmode = OPEN_ALWAYS ;
}
2013-07-24 22:49:45 +02:00
} else {
openmode = OPEN_EXISTING ;
}
2017-02-24 18:59:41 +01:00
// Let's do it!
# if PPSSPP_PLATFORM(UWP)
2022-07-06 22:59:47 +01:00
hFile = CreateFile2FromAppW ( fullName . ToWString ( ) . c_str ( ) , desired , sharemode , openmode , nullptr ) ;
2017-02-24 18:59:41 +01:00
# else
2021-05-06 01:31:38 +02:00
hFile = CreateFile ( fullName . ToWString ( ) . c_str ( ) , desired , sharemode , 0 , openmode , 0 , 0 ) ;
2017-02-24 18:59:41 +01:00
# endif
2013-07-24 22:49:45 +02:00
bool success = hFile ! = INVALID_HANDLE_VALUE ;
2014-11-02 12:40:31 -08:00
if ( ! success ) {
DWORD w32err = GetLastError ( ) ;
2016-06-04 18:03:58 -07:00
if ( w32err = = ERROR_SHARING_VIOLATION ) {
// Sometimes, the file is locked for write, let's try again.
sharemode | = FILE_SHARE_WRITE ;
2017-02-24 18:59:41 +01:00
# if PPSSPP_PLATFORM(UWP)
2022-07-06 22:59:47 +01:00
hFile = CreateFile2FromAppW ( fullName . ToWString ( ) . c_str ( ) , desired , sharemode , openmode , nullptr ) ;
2017-02-24 18:59:41 +01:00
# else
2021-05-06 01:31:38 +02:00
hFile = CreateFile ( fullName . ToWString ( ) . c_str ( ) , desired , sharemode , 0 , openmode , 0 , 0 ) ;
2017-02-24 18:59:41 +01:00
# endif
2016-06-04 18:03:58 -07:00
success = hFile ! = INVALID_HANDLE_VALUE ;
if ( ! success ) {
w32err = GetLastError ( ) ;
}
}
2014-11-02 12:40:31 -08:00
if ( w32err = = ERROR_DISK_FULL | | w32err = = ERROR_NOT_ENOUGH_QUOTA ) {
// This is returned when the disk is full.
2023-04-06 00:34:50 +02:00
auto err = GetI18NCategory ( I18NCat : : ERRORS ) ;
2023-06-20 14:40:46 +02:00
g_OSD . Show ( OSDType : : MESSAGE_ERROR , err - > T ( " Disk full while writing data " ) ) ;
2014-11-02 13:29:25 -08:00
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM ;
2018-05-08 18:25:15 -07:00
} else if ( ! success ) {
2018-05-06 10:15:05 -07:00
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
2014-11-02 12:40:31 -08:00
}
}
2013-07-24 22:49:45 +02:00
# else
2021-05-16 12:37:08 +02:00
if ( fullName . Type ( ) = = PathType : : CONTENT_URI ) {
// Convert flags. Don't want to share this type, bad dependency.
u32 flags = File : : OPEN_NONE ;
if ( access & FILEACCESS_READ )
flags | = File : : OPEN_READ ;
if ( access & FILEACCESS_WRITE )
flags | = File : : OPEN_WRITE ;
if ( access & FILEACCESS_APPEND )
flags | = File : : OPEN_APPEND ;
if ( access & FILEACCESS_CREATE )
flags | = File : : OPEN_CREATE ;
2022-02-06 22:05:42 -08:00
// Important: don't pass TRUNCATE here, the PSP truncates weirdly. See #579.
// See above about truncate behavior. Just add READ to preserve data here.
2021-05-16 12:37:08 +02:00
if ( access & FILEACCESS_TRUNCATE )
2022-02-06 22:05:42 -08:00
flags | = File : : OPEN_READ ;
2021-05-16 12:37:08 +02:00
int fd = File : : OpenFD ( fullName , ( File : : OpenFlag ) flags ) ;
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
2021-12-16 11:37:05 -08:00
if ( fullName . FilePathContainsNoCase ( " PSP/GAME/ " ) ) {
2021-05-16 12:37:08 +02:00
inGameDir_ = true ;
}
2021-06-06 10:50:58 +02:00
hFile = fd ;
if ( fd ! = - 1 ) {
// Success
return true ;
} else {
2021-07-19 12:44:56 +02:00
ERROR_LOG ( FILESYS , " File::OpenFD returned an error " ) ;
2021-06-06 10:50:58 +02:00
// TODO: Need better error codes from OpenFD so we can distinguish
// disk full. Just set not found for now.
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
return false ;
}
2021-05-16 12:37:08 +02:00
}
2014-02-15 00:14:56 -08:00
int flags = 0 ;
2013-07-24 22:49:45 +02:00
if ( access & FILEACCESS_APPEND ) {
2014-02-15 00:14:56 -08:00
flags | = O_APPEND ;
}
if ( ( access & FILEACCESS_READ ) & & ( access & FILEACCESS_WRITE ) ) {
flags | = O_RDWR ;
} else if ( access & FILEACCESS_READ ) {
flags | = O_RDONLY ;
2013-07-24 22:49:45 +02:00
} else if ( access & FILEACCESS_WRITE ) {
2014-02-15 00:14:56 -08:00
flags | = O_WRONLY ;
}
if ( access & FILEACCESS_CREATE ) {
flags | = O_CREAT ;
2013-07-24 22:49:45 +02:00
}
2017-02-19 10:04:54 +01:00
if ( access & FILEACCESS_EXCL ) {
flags | = O_EXCL ;
}
2013-07-24 22:49:45 +02:00
2014-02-15 00:14:56 -08:00
hFile = open ( fullName . c_str ( ) , flags , 0666 ) ;
bool success = hFile ! = - 1 ;
2013-07-24 22:49:45 +02:00
# endif
# if HOST_IS_CASE_SENSITIVE
2014-02-15 00:14:56 -08:00
if ( ! success & & ! ( access & FILEACCESS_CREATE ) ) {
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fileName , FPC_PATH_MUST_EXIST ) ) {
2018-11-06 19:28:22 -08:00
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
return false ;
}
2021-05-06 01:31:38 +02:00
fullName = GetLocalPath ( basePath , fileName ) ;
2024-01-18 00:08:15 +01:00
DEBUG_LOG ( FILESYS , " Case may have been incorrect, second try opening %s (%s) " , fullName . c_str ( ) , fileName . c_str ( ) ) ;
2013-07-24 22:49:45 +02:00
// And try again with the correct case this time
# ifdef _WIN32
2024-01-18 00:08:15 +01:00
hFile = CreateFile ( fullName . c_str ( ) , desired , sharemode , 0 , openmode , 0 , 0 ) ;
2013-07-24 22:49:45 +02:00
success = hFile ! = INVALID_HANDLE_VALUE ;
# else
2024-01-18 00:08:15 +01:00
hFile = open ( fullName . c_str ( ) , flags , 0666 ) ;
2014-02-15 00:14:56 -08:00
success = hFile ! = - 1 ;
2013-07-24 22:49:45 +02:00
# endif
}
# endif
2014-09-04 18:03:28 +09:00
# ifndef _WIN32
if ( success ) {
2017-02-19 10:04:54 +01:00
// Reject directories, even if we succeed in opening them.
// TODO: Might want to do this stat first...
2014-09-04 18:03:28 +09:00
struct stat st ;
if ( fstat ( hFile , & st ) = = 0 & & S_ISDIR ( st . st_mode ) ) {
close ( hFile ) ;
errno = EISDIR ;
success = false ;
}
2014-11-02 12:40:31 -08:00
} else if ( errno = = ENOSPC ) {
// This is returned when the disk is full.
2023-04-06 00:34:50 +02:00
auto err = GetI18NCategory ( I18NCat : : ERRORS ) ;
2023-06-20 14:40:46 +02:00
g_OSD . Show ( OSDType : : MESSAGE_ERROR , err - > T ( " Disk full while writing data " ) ) ;
2014-11-02 13:29:25 -08:00
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM ;
2018-05-06 10:15:05 -07:00
} else {
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
2014-09-04 18:03:28 +09:00
}
# endif
2019-07-04 21:00:14 -07:00
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
2021-12-16 11:37:05 -08:00
if ( fullName . FilePathContainsNoCase ( " PSP/GAME/ " ) ) {
2019-07-04 21:00:14 -07:00
inGameDir_ = true ;
}
2021-09-11 07:29:15 -07:00
if ( access & ( FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE ) ) {
MemoryStick_NotifyWrite ( ) ;
}
2019-07-04 21:00:14 -07:00
2013-07-24 22:49:45 +02:00
return success ;
}
size_t DirectoryFileHandle : : Read ( u8 * pointer , s64 size )
{
2013-08-12 23:33:11 -07:00
size_t bytesRead = 0 ;
2014-02-15 00:59:54 -08:00
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 ) {
2019-07-04 21:00:14 -07:00
return replay_ ? ReplayApplyDiskRead ( pointer , 0 , ( uint32_t ) size , inGameDir_ , CoreTiming : : GetGlobalTimeUs ( ) ) : 0 ;
2014-02-15 00:59:54 -08:00
}
if ( needsTrunc_ < off + size ) {
size = needsTrunc_ - off ;
}
}
2021-10-07 23:59:21 +02:00
if ( size > 0 ) {
2013-07-24 22:49:45 +02:00
# ifdef _WIN32
2021-10-07 23:59:21 +02:00
: : ReadFile ( hFile , ( LPVOID ) pointer , ( DWORD ) size , ( LPDWORD ) & bytesRead , 0 ) ;
2013-07-24 22:49:45 +02:00
# else
2021-10-07 23:59:21 +02:00
bytesRead = read ( hFile , pointer , size ) ;
2013-07-24 22:49:45 +02:00
# endif
2021-10-07 23:59:21 +02:00
}
2019-07-04 21:00:14 -07:00
return replay_ ? ReplayApplyDiskRead ( pointer , ( uint32_t ) bytesRead , ( uint32_t ) size , inGameDir_ , CoreTiming : : GetGlobalTimeUs ( ) ) : bytesRead ;
2013-07-24 22:49:45 +02:00
}
size_t DirectoryFileHandle : : Write ( const u8 * pointer , s64 size )
{
2013-08-12 23:33:11 -07:00
size_t bytesWritten = 0 ;
2014-11-02 10:53:17 -08:00
bool diskFull = false ;
2013-07-24 22:49:45 +02:00
# ifdef _WIN32
2014-11-02 10:53:17 -08:00
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 ;
}
2013-07-24 22:49:45 +02:00
# else
2014-02-15 00:14:56 -08:00
bytesWritten = write ( hFile , pointer , size ) ;
2014-11-02 10:53:17 -08:00
if ( bytesWritten = = ( size_t ) - 1 ) {
diskFull = errno = = ENOSPC ;
}
2013-07-24 22:49:45 +02:00
# endif
2014-02-15 00:59:54 -08:00
if ( needsTrunc_ ! = - 1 ) {
off_t off = ( off_t ) Seek ( 0 , FILEMOVE_CURRENT ) ;
if ( needsTrunc_ < off ) {
needsTrunc_ = off ;
}
}
2014-11-02 10:53:17 -08:00
2019-07-04 21:00:14 -07:00
if ( replay_ ) {
bytesWritten = ReplayApplyDiskWrite ( pointer , ( uint64_t ) bytesWritten , ( uint64_t ) size , & diskFull , inGameDir_ , CoreTiming : : GetGlobalTimeUs ( ) ) ;
}
2021-09-11 07:29:15 -07:00
MemoryStick_NotifyWrite ( ) ;
2014-11-02 10:53:17 -08:00
if ( diskFull ) {
ERROR_LOG ( FILESYS , " Disk full " ) ;
2023-04-06 00:34:50 +02:00
auto err = GetI18NCategory ( I18NCat : : ERRORS ) ;
2023-06-20 14:40:46 +02:00
g_OSD . Show ( OSDType : : MESSAGE_ERROR , err - > T ( " Disk full while writing data " ) ) ;
2014-11-02 12:40:31 -08:00
// 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 ) {
2016-05-27 22:00:14 -07:00
// Sign extend on 64-bit.
2014-11-02 12:40:31 -08:00
return ( size_t ) ( s64 ) ( s32 ) SCE_KERNEL_ERROR_ERRNO_DEVICE_NO_FREE_SPACE ;
}
2014-11-02 10:53:17 -08:00
}
2013-07-24 22:49:45 +02:00
return bytesWritten ;
}
size_t DirectoryFileHandle : : Seek ( s32 position , FileMove type )
{
2014-02-15 00:59:54 -08:00
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 ;
}
}
2018-03-17 23:30:04 -07:00
size_t result ;
2013-07-24 22:49:45 +02:00
# 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 ;
}
2017-02-24 18:59:41 +01:00
LARGE_INTEGER distance ;
distance . QuadPart = position ;
LARGE_INTEGER cursor ;
2021-02-15 09:28:07 -08:00
SetFilePointerEx ( hFile , distance , & cursor , moveMethod ) ;
2018-03-17 23:30:04 -07:00
result = ( size_t ) cursor . QuadPart ;
2013-07-24 22:49:45 +02:00
# 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 ;
}
2018-03-17 23:30:04 -07:00
result = lseek ( hFile , position , moveMethod ) ;
2013-07-24 22:49:45 +02:00
# endif
2018-03-17 23:30:04 -07:00
2018-04-17 07:51:49 -07:00
return replay_ ? ( size_t ) ReplayApplyDisk64 ( ReplayAction : : FILE_SEEK , result , CoreTiming : : GetGlobalTimeUs ( ) ) : result ;
2013-07-24 22:49:45 +02:00
}
void DirectoryFileHandle : : Close ( )
{
2014-02-15 00:59:54 -08:00
if ( needsTrunc_ ! = - 1 ) {
# ifdef _WIN32
Seek ( ( s32 ) needsTrunc_ , FILEMOVE_BEGIN ) ;
2015-04-08 12:08:46 -07:00
if ( SetEndOfFile ( hFile ) = = 0 ) {
ERROR_LOG_REPORT ( FILESYS , " Failed to truncate file. " ) ;
}
2020-03-03 22:53:03 -08:00
# elif !PPSSPP_PLATFORM(SWITCH)
// Note: it's not great that Switch cannot truncate appropriately...
2015-04-08 12:08:46 -07:00
if ( ftruncate ( hFile , ( off_t ) needsTrunc_ ) ! = 0 ) {
ERROR_LOG_REPORT ( FILESYS , " Failed to truncate file. " ) ;
}
2014-02-15 00:59:54 -08:00
# endif
}
2013-07-24 22:49:45 +02:00
# ifdef _WIN32
if ( hFile ! = ( HANDLE ) - 1 )
CloseHandle ( hFile ) ;
# else
2014-02-15 00:14:56 -08:00
if ( hFile ! = - 1 )
close ( hFile ) ;
2013-07-24 22:49:45 +02:00
# endif
}
2012-11-01 16:19:01 +01:00
2013-12-28 23:43:02 -08:00
void DirectoryFileSystem : : CloseAll ( ) {
2012-12-26 08:24:19 +01:00
for ( auto iter = entries . begin ( ) ; iter ! = entries . end ( ) ; + + iter ) {
2019-02-25 20:51:19 +01:00
INFO_LOG ( FILESYS , " DirectoryFileSystem::CloseAll(): Force closing %d (%s) " , ( int ) iter - > first , iter - > second . guestFilename . c_str ( ) ) ;
2013-07-24 22:49:45 +02:00
iter - > second . hFile . Close ( ) ;
2012-12-26 08:24:19 +01:00
}
2013-12-28 23:43:02 -08:00
entries . clear ( ) ;
}
2012-12-26 08:24:19 +01:00
bool DirectoryFileSystem : : MkDir ( const std : : string & dirname ) {
2018-03-17 23:30:04 -07:00
bool result ;
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
// Must fix case BEFORE attempting, because MkDir would create
// duplicate (different case) directories
std : : string fixedCase = dirname ;
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fixedCase , FPC_PARTIAL_ALLOWED ) )
2018-03-17 23:30:04 -07:00
result = false ;
else
result = File : : CreateFullPath ( GetLocalPath ( fixedCase ) ) ;
2012-12-27 04:27:07 -08:00
# else
2018-03-17 23:30:04 -07:00
result = File : : CreateFullPath ( GetLocalPath ( dirname ) ) ;
2012-12-27 04:27:07 -08:00
# endif
2021-09-11 07:29:15 -07:00
MemoryStick_NotifyWrite ( ) ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : MKDIR , result , CoreTiming : : GetGlobalTimeUs ( ) ) ! = 0 ;
2012-11-01 16:19:01 +01:00
}
2012-12-26 08:24:19 +01:00
bool DirectoryFileSystem : : RmDir ( const std : : string & dirname ) {
2021-05-06 01:31:38 +02:00
Path fullName = GetLocalPath ( dirname ) ;
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
// Maybe we're lucky?
2021-09-11 07:29:15 -07:00
if ( File : : DeleteDirRecursively ( fullName ) ) {
MemoryStick_NotifyWrite ( ) ;
2018-03-17 23:30:04 -07:00
return ( bool ) ReplayApplyDisk ( ReplayAction : : RMDIR , true , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2021-09-11 07:29:15 -07:00
}
2012-12-27 04:27:07 -08:00
2018-03-17 23:30:04 -07:00
// Nope, fix case and try again. Should we try again?
2021-05-06 01:31:38 +02:00
std : : string fullPath = dirname ;
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fullPath , FPC_FILE_MUST_EXIST ) )
2018-03-17 23:30:04 -07:00
return ( bool ) ReplayApplyDisk ( ReplayAction : : RMDIR , false , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-12-27 04:27:07 -08:00
2021-05-06 01:31:38 +02:00
fullName = GetLocalPath ( fullPath ) ;
2012-12-27 04:27:07 -08:00
# endif
2021-05-09 15:02:23 +02:00
bool result = File : : DeleteDirRecursively ( fullName ) ;
2021-09-11 07:29:15 -07:00
MemoryStick_NotifyWrite ( ) ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : RMDIR , result , CoreTiming : : GetGlobalTimeUs ( ) ) ! = 0 ;
2012-11-28 04:57:30 +10:00
}
2013-06-23 20:09:44 -07:00
int DirectoryFileSystem : : RenameFile ( const std : : string & from , const std : : string & to ) {
2012-11-28 04:57:30 +10:00
std : : string fullTo = to ;
2013-01-21 00:20:48 +01:00
2013-06-23 20:09:44 -07:00
// 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 )
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : FILE_RENAME , SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2013-01-21 00:20:48 +01:00
2021-05-06 01:31:38 +02:00
Path fullFrom = GetLocalPath ( from ) ;
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
2018-03-17 23:30:04 -07:00
// In case TO should overwrite a file with different case. Check error code?
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fullTo , FPC_PATH_MUST_EXIST ) )
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : FILE_RENAME , - 1 , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-11-01 16:19:01 +01:00
# endif
2012-12-27 04:27:07 -08:00
2021-05-06 01:31:38 +02:00
Path fullToPath = GetLocalPath ( fullTo ) ;
2012-12-27 04:27:07 -08:00
2021-07-19 09:38:04 +02:00
bool retValue = File : : Rename ( fullFrom , fullToPath ) ;
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
2021-07-19 09:38:04 +02:00
if ( ! retValue )
2012-12-27 04:27:07 -08:00
{
2018-03-17 23:30:04 -07:00
// May have failed due to case sensitivity on FROM, so try again. Check error code?
2021-05-06 01:31:38 +02:00
std : : string fullFromPath = from ;
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fullFromPath , FPC_FILE_MUST_EXIST ) )
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : FILE_RENAME , - 1 , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2021-05-06 01:31:38 +02:00
fullFrom = GetLocalPath ( fullFromPath ) ;
2012-12-27 04:27:07 -08:00
2021-07-19 09:38:04 +02:00
retValue = File : : Rename ( fullFrom , fullToPath ) ;
2012-12-27 04:27:07 -08:00
}
# endif
2013-06-23 20:09:44 -07:00
// TODO: Better error codes.
2018-03-17 23:30:04 -07:00
int result = retValue ? 0 : ( int ) SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS ;
2021-09-11 07:29:15 -07:00
MemoryStick_NotifyWrite ( ) ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : FILE_RENAME , result , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-11-01 16:19:01 +01:00
}
2013-02-08 10:20:52 -08:00
bool DirectoryFileSystem : : RemoveFile ( const std : : string & filename ) {
2021-07-19 09:38:04 +02:00
Path localPath = GetLocalPath ( filename ) ;
bool retValue = File : : Delete ( localPath ) ;
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
2021-07-19 09:38:04 +02:00
if ( ! retValue )
2012-12-27 04:27:07 -08:00
{
2018-03-17 23:30:04 -07:00
// May have failed due to case sensitivity, so try again. Try even if it fails?
2021-05-06 01:31:38 +02:00
std : : string fullNamePath = filename ;
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , fullNamePath , FPC_FILE_MUST_EXIST ) )
2018-03-17 23:30:04 -07:00
return ( bool ) ReplayApplyDisk ( ReplayAction : : FILE_REMOVE , false , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2021-07-19 09:38:04 +02:00
localPath = GetLocalPath ( fullNamePath ) ;
2012-12-27 04:27:07 -08:00
2021-07-19 09:38:04 +02:00
retValue = File : : Delete ( localPath ) ;
2012-12-27 04:27:07 -08:00
}
# endif
2021-09-11 07:29:15 -07:00
MemoryStick_NotifyWrite ( ) ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk ( ReplayAction : : FILE_REMOVE , retValue , CoreTiming : : GetGlobalTimeUs ( ) ) ! = 0 ;
2012-11-01 16:19:01 +01:00
}
2019-10-20 11:03:37 -07:00
int DirectoryFileSystem : : OpenFile ( std : : string filename , FileAccess access , const char * devicename ) {
2012-11-01 16:19:01 +01:00
OpenFileEntry entry ;
2021-07-25 18:14:46 +02:00
entry . hFile . fileSystemFlags_ = flags ;
2014-11-02 12:40:31 -08:00
u32 err = 0 ;
2023-03-16 23:08:49 +01:00
bool success = entry . hFile . Open ( basePath , filename , ( FileAccess ) ( access & FILEACCESS_PSP_FLAGS ) , err ) ;
2018-03-17 23:30:04 -07:00
if ( err = = 0 & & ! success ) {
2018-05-06 10:15:05 -07:00
err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
2018-03-17 23:30:04 -07:00
}
2012-12-27 04:27:07 -08:00
2018-03-17 23:30:04 -07:00
err = ReplayApplyDisk ( ReplayAction : : FILE_OPEN , err , CoreTiming : : GetGlobalTimeUs ( ) ) ;
if ( err ! = 0 ) {
2021-05-16 12:37:08 +02:00
std : : string errorString ;
2021-06-09 21:51:43 -07:00
int logError ;
2012-11-01 16:19:01 +01:00
# ifdef _WIN32
2021-05-01 09:37:36 -07:00
auto win32err = GetLastError ( ) ;
2021-06-09 21:51:43 -07:00
logError = ( int ) win32err ;
2021-05-16 12:37:08 +02:00
errorString = GetStringErrorMsg ( win32err ) ;
2012-12-27 04:27:07 -08:00
# else
2021-06-09 21:51:43 -07:00
logError = ( int ) errno ;
2012-11-01 16:19:01 +01:00
# endif
2023-03-16 23:08:49 +01:00
if ( ! ( access & FILEACCESS_PPSSPP_QUIET ) ) {
ERROR_LOG ( FILESYS , " DirectoryFileSystem::OpenFile('%s'): FAILED, %d - access = %d '%s' " , filename . c_str ( ) , logError , ( int ) ( access & FILEACCESS_PSP_FLAGS ) , errorString . c_str ( ) ) ;
}
2014-11-02 12:40:31 -08:00
return err ;
2012-12-26 08:24:19 +01:00
} else {
2012-12-27 04:27:07 -08:00
# ifdef _WIN32
2021-05-16 12:37:08 +02:00
if ( access & FILEACCESS_APPEND ) {
entry . hFile . Seek ( 0 , FILEMOVE_END ) ;
}
2012-12-27 04:27:07 -08:00
# endif
2012-11-01 16:19:01 +01:00
u32 newHandle = hAlloc - > GetNewHandle ( ) ;
2013-12-28 23:43:02 -08:00
entry . guestFilename = filename ;
2023-03-16 23:08:49 +01:00
entry . access = ( FileAccess ) ( access & FILEACCESS_PSP_FLAGS ) ;
2013-12-28 23:43:02 -08:00
2012-11-01 16:19:01 +01:00
entries [ newHandle ] = entry ;
return newHandle ;
}
}
2012-12-26 08:24:19 +01:00
void DirectoryFileSystem : : CloseFile ( u32 handle ) {
2012-11-01 16:19:01 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
2012-12-26 08:24:19 +01:00
if ( iter ! = entries . end ( ) ) {
2012-11-01 16:19:01 +01:00
hAlloc - > FreeHandle ( handle ) ;
2013-07-24 22:49:45 +02:00
iter - > second . hFile . Close ( ) ;
2012-11-01 16:19:01 +01:00
entries . erase ( iter ) ;
2012-12-26 08:24:19 +01:00
} else {
2012-11-01 16:19:01 +01:00
//This shouldn't happen...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot close file that hasn't been opened: %08x " , handle ) ;
2012-11-01 16:19:01 +01:00
}
}
2012-12-26 08:24:19 +01:00
bool DirectoryFileSystem : : OwnsHandle ( u32 handle ) {
2012-11-01 16:19:01 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
return ( iter ! = entries . end ( ) ) ;
}
2013-12-26 14:47:17 -08:00
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 ;
}
2020-05-21 17:57:41 -07:00
PSPDevType DirectoryFileSystem : : DevType ( u32 handle ) {
return PSPDevType : : FILE ;
2013-12-26 22:07:41 -08:00
}
2012-12-26 08:24:19 +01:00
size_t DirectoryFileSystem : : ReadFile ( u32 handle , u8 * pointer , s64 size ) {
2013-12-27 16:36:51 -08:00
int ignored ;
return ReadFile ( handle , pointer , size , ignored ) ;
}
size_t DirectoryFileSystem : : ReadFile ( u32 handle , u8 * pointer , s64 size , int & usec ) {
2012-11-01 16:19:01 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
2015-01-09 15:50:06 -08:00
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 ;
}
2013-07-24 22:49:45 +02:00
size_t bytesRead = iter - > second . hFile . Read ( pointer , size ) ;
2012-11-01 16:19:01 +01:00
return bytesRead ;
2012-12-26 08:24:19 +01:00
} else {
2021-07-19 09:38:04 +02:00
// This shouldn't happen...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot read file that hasn't been opened: %08x " , handle ) ;
2012-11-01 16:19:01 +01:00
return 0 ;
}
}
2012-12-26 08:24:19 +01:00
size_t DirectoryFileSystem : : WriteFile ( u32 handle , const u8 * pointer , s64 size ) {
2013-12-27 16:36:51 -08:00
int ignored ;
return WriteFile ( handle , pointer , size , ignored ) ;
}
size_t DirectoryFileSystem : : WriteFile ( u32 handle , const u8 * pointer , s64 size , int & usec ) {
2012-11-01 16:19:01 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
2021-07-19 09:38:04 +02:00
if ( iter ! = entries . end ( ) ) {
2013-07-24 22:49:45 +02:00
size_t bytesWritten = iter - > second . hFile . Write ( pointer , size ) ;
2012-11-01 16:19:01 +01:00
return bytesWritten ;
2012-12-26 08:24:19 +01:00
} else {
2012-11-01 16:19:01 +01:00
//This shouldn't happen...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot write to file that hasn't been opened: %08x " , handle ) ;
2012-11-01 16:19:01 +01:00
return 0 ;
}
}
2012-12-26 08:24:19 +01:00
size_t DirectoryFileSystem : : SeekFile ( u32 handle , s32 position , FileMove type ) {
2012-11-01 16:19:01 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
2012-12-26 08:24:19 +01:00
if ( iter ! = entries . end ( ) ) {
2013-07-24 22:49:45 +02:00
return iter - > second . hFile . Seek ( position , type ) ;
2012-12-26 08:24:19 +01:00
} else {
2012-11-01 16:19:01 +01:00
//This shouldn't happen...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot seek in file that hasn't been opened: %08x " , handle ) ;
2012-11-01 16:19:01 +01:00
return 0 ;
}
}
2012-12-26 08:24:19 +01:00
PSPFileInfo DirectoryFileSystem : : GetFileInfo ( std : : string filename ) {
PSPFileInfo x ;
2012-11-05 12:02:09 +01:00
x . name = filename ;
2012-11-01 16:19:01 +01:00
2022-10-09 08:02:11 -07:00
File : : FileInfo info ;
2021-05-06 01:31:38 +02:00
Path fullName = GetLocalPath ( filename ) ;
2022-10-09 08:02:11 -07:00
if ( ! File : : GetFileInfo ( fullName , & info ) ) {
2012-12-27 04:27:07 -08:00
# if HOST_IS_CASE_SENSITIVE
2021-07-19 12:44:56 +02:00
if ( ! FixPathCase ( basePath , filename , FPC_FILE_MUST_EXIST ) )
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskFileInfo ( x , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-12-27 04:27:07 -08:00
fullName = GetLocalPath ( filename ) ;
2022-10-09 08:02:11 -07:00
if ( ! File : : GetFileInfo ( fullName , & info ) )
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskFileInfo ( x , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-12-27 04:27:07 -08:00
# else
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskFileInfo ( x , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-12-27 04:27:07 -08:00
# endif
2012-11-05 13:42:23 +01:00
}
2021-07-19 09:38:04 +02:00
2022-10-09 08:02:11 -07:00
x . type = info . isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL ;
2012-11-05 13:42:23 +01:00
x . exists = true ;
2012-11-01 16:19:01 +01:00
2015-09-23 09:48:55 +02:00
if ( x . type ! = FILETYPE_DIRECTORY ) {
2022-10-09 08:02:11 -07:00
x . size = info . size ;
x . access = info . access ;
time_t atime = info . atime ;
time_t ctime = info . ctime ;
time_t mtime = info . mtime ;
2013-07-23 17:24:33 +02:00
2022-10-09 08:02:11 -07:00
localtime_r ( ( time_t * ) & atime , & x . atime ) ;
localtime_r ( ( time_t * ) & ctime , & x . ctime ) ;
localtime_r ( ( time_t * ) & mtime , & x . mtime ) ;
2013-01-02 01:08:18 +01:00
}
2012-11-01 16:19:01 +01:00
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskFileInfo ( x , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-11-01 16:19:01 +01:00
}
2013-03-06 23:58:12 -08:00
# ifdef _WIN32
# define FILETIME_FROM_UNIX_EPOCH_US 11644473600000000ULL
2022-12-10 21:02:44 -08:00
static void tmFromFiletime ( tm & dest , const FILETIME & src ) {
2013-03-06 23:58:12 -08:00
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
2017-06-12 22:55:50 -07:00
// 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...
2021-06-06 10:50:58 +02:00
static std : : string SimulateVFATBug ( std : : string filename ) {
2017-06-12 22:55:50 -07:00
// These are the characters allowed in DOS filenames.
2023-12-28 14:00:01 +01:00
static const char * const FAT_UPPER_CHARS = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&'(){}-_`~ " ;
static const char * const FAT_LOWER_CHARS = " abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&'(){}-_`~ " ;
static const char * const LOWER_CHARS = " abcdefghijklmnopqrstuvwxyz " ;
2020-03-21 22:49:00 -07:00
// 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 ;
}
2017-06-12 22:55:50 -07:00
bool apply_hack = false ;
size_t dot_pos = filename . find ( ' . ' ) ;
2020-03-21 22:49:00 -07:00
if ( dot_pos = = filename . npos & & filename . length ( ) < = 8 ) {
2017-06-12 22:55:50 -07:00
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 ) {
2020-03-21 22:49:00 -07:00
VERBOSE_LOG ( FILESYS , " Applying VFAT hack to filename: %s " , filename . c_str ( ) ) ;
2017-06-12 22:55:50 -07:00
// 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 ;
}
2021-09-11 17:50:07 +02:00
bool DirectoryFileSystem : : ComputeRecursiveDirSizeIfFast ( const std : : string & path , int64_t * size ) {
Path localPath = GetLocalPath ( path ) ;
int64_t sizeTemp = File : : ComputeRecursiveDirectorySize ( localPath ) ;
if ( sizeTemp > = 0 ) {
* size = sizeTemp ;
return true ;
} else {
return false ;
}
}
2022-10-09 09:08:18 -07:00
std : : vector < PSPFileInfo > DirectoryFileSystem : : GetDirListing ( const std : : string & path , bool * exists ) {
2012-11-01 16:19:01 +01:00
std : : vector < PSPFileInfo > myVector ;
2013-01-22 06:31:42 -08:00
2021-07-19 10:14:11 +02:00
std : : vector < File : : FileInfo > files ;
2021-07-19 12:44:56 +02:00
Path localPath = GetLocalPath ( path ) ;
2021-07-19 17:38:01 +02:00
const int flags = File : : GETFILES_GETHIDDEN | File : : GETFILES_GET_NAVIGATION_ENTRIES ;
2022-10-09 09:33:39 -07:00
bool success = File : : GetFilesInDir ( localPath , & files , nullptr , flags ) ;
2013-01-22 06:31:42 -08:00
# if HOST_IS_CASE_SENSITIVE
2022-10-09 09:33:39 -07:00
if ( ! success ) {
// TODO: Case sensitivity should be checked on a file system basis, right?
2022-10-09 09:08:18 -07:00
std : : string fixedPath = path ;
if ( FixPathCase ( basePath , fixedPath , FPC_FILE_MUST_EXIST ) ) {
2021-07-19 10:14:11 +02:00
// May have failed due to case sensitivity, try again
2022-10-09 09:08:18 -07:00
localPath = GetLocalPath ( fixedPath ) ;
2022-10-09 09:33:39 -07:00
success = File : : GetFilesInDir ( localPath , & files , nullptr , flags ) ;
2021-07-19 10:14:11 +02:00
}
2022-10-09 09:33:39 -07:00
}
# endif
if ( ! success ) {
2022-10-09 09:08:18 -07:00
if ( exists )
* exists = false ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskListing ( myVector , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-12-29 23:55:34 +01:00
}
2019-08-01 18:38:40 +02:00
bool hideISOFiles = PSP_CoreParameter ( ) . compat . flags ( ) . HideISOFiles ;
2021-07-19 10:14:11 +02:00
// Then apply transforms to match PSP idiosynchrasies, as we convert the entries.
for ( auto & file : files ) {
PSPFileInfo entry ;
if ( Flags ( ) & FileSystemFlags : : SIMULATE_FAT32 ) {
entry . name = SimulateVFATBug ( file . name ) ;
} else {
entry . name = file . name ;
}
2023-05-16 17:51:27 +02:00
if ( hideISOFiles ) {
2024-03-12 22:34:17 +01:00
if ( endsWithNoCase ( entry . name , " .cso " ) | | endsWithNoCase ( entry . name , " .iso " ) | | endsWithNoCase ( entry . name , " .chd " ) ) { // chd not really necessary, but let's hide them too.
2023-05-16 17:51:27 +02:00
// Workaround for DJ Max Portable, see compat.ini.
continue ;
} else if ( file . isDirectory ) {
if ( endsWithNoCase ( path , " SAVEDATA " ) ) {
// Don't let it see savedata from other games, it can misinterpret stuff.
std : : string gameID = g_paramSFO . GetDiscID ( ) ;
if ( entry . name . size ( ) > 2 & & ! startsWithNoCase ( entry . name , gameID ) ) {
continue ;
}
2023-11-22 21:33:18 +08:00
} else if ( file . name = = " GAME " | | file . name = = " TEXTURES " | | file . name = = " PPSSPP_STATE " | | file . name = = " PLUGINS " | | file . name = = " SYSTEM " | | equalsNoCase ( file . name , " Cheats " ) ) {
2023-05-16 17:51:27 +02:00
// The game scans these folders on startup which can take time. Skip them.
continue ;
}
}
2019-08-01 18:38:40 +02:00
}
2021-07-19 10:14:11 +02:00
if ( file . name = = " .. " ) {
entry . size = 4096 ;
} else {
entry . size = file . size ;
}
if ( file . isDirectory ) {
entry . type = FILETYPE_DIRECTORY ;
} else {
entry . type = FILETYPE_NORMAL ;
}
entry . access = file . access ;
2022-10-09 09:08:18 -07:00
entry . exists = file . exists ;
2019-08-01 18:38:40 +02:00
2021-07-19 10:14:11 +02:00
localtime_r ( ( time_t * ) & file . atime , & entry . atime ) ;
localtime_r ( ( time_t * ) & file . ctime , & entry . ctime ) ;
localtime_r ( ( time_t * ) & file . mtime , & entry . mtime ) ;
myVector . push_back ( entry ) ;
2012-12-29 23:55:34 +01:00
}
2018-03-17 23:30:04 -07:00
2022-10-09 09:08:18 -07:00
if ( exists )
* exists = true ;
2018-03-17 23:30:04 -07:00
return ReplayApplyDiskListing ( myVector , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2012-11-01 16:19:01 +01:00
}
2014-10-31 23:48:08 -07:00
u64 DirectoryFileSystem : : FreeSpace ( const std : : string & path ) {
2021-07-25 00:16:30 +02:00
int64_t result = 0 ;
2021-07-19 09:38:04 +02:00
if ( free_disk_space ( GetLocalPath ( path ) , result ) ) {
2021-07-25 00:16:30 +02:00
return ReplayApplyDisk64 ( ReplayAction : : FREESPACE , ( uint64_t ) result , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2015-11-15 21:57:35 -08:00
}
2014-11-01 00:19:31 -07:00
# if HOST_IS_CASE_SENSITIVE
std : : string fixedCase = path ;
2021-07-19 12:44:56 +02:00
if ( FixPathCase ( basePath , fixedCase , FPC_FILE_MUST_EXIST ) ) {
2014-11-01 00:19:31 -07:00
// May have failed due to case sensitivity, try again.
2021-07-19 12:44:56 +02:00
if ( free_disk_space ( GetLocalPath ( fixedCase ) , result ) ) {
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk64 ( ReplayAction : : FREESPACE , result , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2014-11-01 00:19:31 -07:00
}
}
2014-10-31 23:48:08 -07:00
# endif
// Just assume they're swimming in free disk space if we don't know otherwise.
2018-03-17 23:30:04 -07:00
return ReplayApplyDisk64 ( ReplayAction : : FREESPACE , std : : numeric_limits < u64 > : : max ( ) , CoreTiming : : GetGlobalTimeUs ( ) ) ;
2014-10-31 23:48:08 -07:00
}
2012-12-27 22:14:31 -08:00
void DirectoryFileSystem : : DoState ( PointerWrap & p ) {
2014-02-15 00:59:54 -08:00
auto s = p . Section ( " DirectoryFileSystem " , 0 , 2 ) ;
2013-12-28 23:43:02 -08:00
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
2014-02-15 00:59:54 -08:00
// s64 current truncate position (v2+ only)
2013-12-28 23:43:02 -08:00
u32 num = ( u32 ) entries . size ( ) ;
2020-08-09 21:20:42 -07:00
Do ( p , num ) ;
2013-12-28 23:43:02 -08:00
if ( p . mode = = p . MODE_READ ) {
CloseAll ( ) ;
u32 key ;
OpenFileEntry entry ;
2021-07-25 18:14:46 +02:00
entry . hFile . fileSystemFlags_ = flags ;
2013-12-28 23:43:02 -08:00
for ( u32 i = 0 ; i < num ; i + + ) {
2020-08-09 21:20:42 -07:00
Do ( p , key ) ;
Do ( p , entry . guestFilename ) ;
Do ( p , entry . access ) ;
2014-11-02 12:40:31 -08:00
u32 err ;
2023-04-02 12:17:59 -07:00
bool brokenFile = false ;
2014-11-02 12:40:31 -08:00
if ( ! entry . hFile . Open ( basePath , entry . guestFilename , entry . access , err ) ) {
2013-12-28 23:43:02 -08:00
ERROR_LOG ( FILESYS , " Failed to reopen file while loading state: %s " , entry . guestFilename . c_str ( ) ) ;
2023-04-02 12:17:59 -07:00
brokenFile = true ;
2013-12-28 23:43:02 -08:00
}
u32 position ;
2020-08-09 21:20:42 -07:00
Do ( p , position ) ;
2013-12-28 23:43:02 -08:00
if ( position ! = entry . hFile . Seek ( position , FILEMOVE_BEGIN ) ) {
ERROR_LOG ( FILESYS , " Failed to restore seek position while loading state: %s " , entry . guestFilename . c_str ( ) ) ;
2023-04-02 12:17:59 -07:00
brokenFile = true ;
2013-12-28 23:43:02 -08:00
}
2014-02-15 00:59:54 -08:00
if ( s > = 2 ) {
2020-08-09 21:20:42 -07:00
Do ( p , entry . hFile . needsTrunc_ ) ;
2014-02-15 00:59:54 -08:00
}
2023-04-02 12:17:59 -07:00
// Let's hope that things don't go that badly with the file mysteriously auto-closed.
// Better than not loading the save state at all, hopefully.
if ( ! brokenFile ) {
entries [ key ] = entry ;
}
2013-12-28 23:43:02 -08:00
}
} else {
for ( auto iter = entries . begin ( ) ; iter ! = entries . end ( ) ; + + iter ) {
u32 key = iter - > first ;
2020-08-09 21:20:42 -07:00
Do ( p , key ) ;
Do ( p , iter - > second . guestFilename ) ;
Do ( p , iter - > second . access ) ;
2013-12-30 21:37:19 -08:00
u32 position = ( u32 ) iter - > second . hFile . Seek ( 0 , FILEMOVE_CURRENT ) ;
2020-08-09 21:20:42 -07:00
Do ( p , position ) ;
Do ( p , iter - > second . hFile . needsTrunc_ ) ;
2013-12-28 23:43:02 -08:00
}
2012-12-27 22:14:31 -08:00
}
}
2013-02-27 00:59:30 +01:00
2013-07-24 22:49:45 +02:00
2013-02-27 00:59:30 +01:00
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 ( ) ;
}
2023-12-14 14:22:24 +03:00
std : : string VFSFileSystem : : GetLocalPath ( const std : : string & localPath ) {
2013-02-27 00:59:30 +01:00
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 ;
}
2013-06-23 20:09:44 -07:00
int VFSFileSystem : : RenameFile ( const std : : string & from , const std : : string & to ) {
2013-02-27 00:59:30 +01:00
// NOT SUPPORTED - READ ONLY
2013-06-23 20:09:44 -07:00
return - 1 ;
2013-02-27 00:59:30 +01:00
}
bool VFSFileSystem : : RemoveFile ( const std : : string & filename ) {
// NOT SUPPORTED - READ ONLY
return false ;
}
2019-10-20 11:03:37 -07:00
int VFSFileSystem : : OpenFile ( std : : string filename , FileAccess access , const char * devicename ) {
2013-02-27 00:59:30 +01:00
if ( access ! = FILEACCESS_READ ) {
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " VFSFileSystem only supports plain reading " ) ;
2019-10-20 11:03:37 -07:00
return SCE_KERNEL_ERROR_ERRNO_INVALID_FLAG ;
2013-02-27 00:59:30 +01:00
}
std : : string fullName = GetLocalPath ( filename ) ;
const char * fullNameC = fullName . c_str ( ) ;
2021-07-19 12:44:56 +02:00
VERBOSE_LOG ( FILESYS , " VFSFileSystem actually opening %s (%s) " , fullNameC , filename . c_str ( ) ) ;
2013-02-27 00:59:30 +01:00
size_t size ;
2023-03-06 14:23:56 +01:00
u8 * data = g_VFS . ReadFile ( fullNameC , & size ) ;
2013-02-27 00:59:30 +01:00
if ( ! data ) {
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " VFSFileSystem failed to open %s " , filename . c_str ( ) ) ;
2019-10-20 11:03:37 -07:00
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND ;
2013-02-27 00:59:30 +01:00
}
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 ) ;
2021-04-25 20:38:22 +02:00
File : : FileInfo fo ;
2023-03-06 14:23:56 +01:00
if ( g_VFS . GetFileInfo ( fullName . c_str ( ) , & fo ) ) {
2014-03-23 13:01:52 -07:00
x . exists = fo . exists ;
if ( x . exists ) {
x . size = fo . size ;
x . type = fo . isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL ;
2020-05-21 18:20:38 -07:00
x . access = fo . isWritable ? 0666 : 0444 ;
2014-03-23 13:01:52 -07:00
}
} else {
x . exists = false ;
2013-02-27 00:59:30 +01:00
}
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...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot close file that hasn't been opened: %08x " , handle ) ;
2013-02-27 00:59:30 +01:00
}
}
bool VFSFileSystem : : OwnsHandle ( u32 handle ) {
EntryMap : : iterator iter = entries . find ( handle ) ;
return ( iter ! = entries . end ( ) ) ;
}
2013-12-26 14:47:17 -08:00
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 ;
}
2020-05-21 17:57:41 -07:00
PSPDevType VFSFileSystem : : DevType ( u32 handle ) {
return PSPDevType : : FILE ;
2013-12-26 22:07:41 -08:00
}
2013-02-27 00:59:30 +01:00
size_t VFSFileSystem : : ReadFile ( u32 handle , u8 * pointer , s64 size ) {
2013-12-27 16:36:51 -08:00
int ignored ;
return ReadFile ( handle , pointer , size , ignored ) ;
}
size_t VFSFileSystem : : ReadFile ( u32 handle , u8 * pointer , s64 size , int & usec ) {
2014-06-09 19:40:05 +02:00
DEBUG_LOG ( FILESYS , " VFSFileSystem::ReadFile %08x %p %i " , handle , pointer , ( u32 ) size ) ;
2013-02-27 00:59:30 +01:00
EntryMap : : iterator iter = entries . find ( handle ) ;
if ( iter ! = entries . end ( ) )
{
2024-02-26 17:17:33 +00:00
if ( iter - > second . seekPos + size > iter - > second . size )
size = iter - > second . size - iter - > second . seekPos ;
if ( size < 0 ) size = 0 ;
2013-02-27 00:59:30 +01:00
size_t bytesRead = size ;
memcpy ( pointer , iter - > second . fileData + iter - > second . seekPos , size ) ;
iter - > second . seekPos + = size ;
return bytesRead ;
} else {
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot read file that hasn't been opened: %08x " , handle ) ;
2013-02-27 00:59:30 +01:00
return 0 ;
}
}
size_t VFSFileSystem : : WriteFile ( u32 handle , const u8 * pointer , s64 size ) {
2013-12-27 16:36:51 -08:00
int ignored ;
return WriteFile ( handle , pointer , size , ignored ) ;
}
size_t VFSFileSystem : : WriteFile ( u32 handle , const u8 * pointer , s64 size , int & usec ) {
2013-02-27 00:59:30 +01:00
// 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...
2013-09-07 22:02:55 +02:00
ERROR_LOG ( FILESYS , " Cannot seek in file that hasn't been opened: %08x " , handle ) ;
2013-02-27 00:59:30 +01:00
return 0 ;
}
}
2022-10-09 09:08:18 -07:00
std : : vector < PSPFileInfo > VFSFileSystem : : GetDirListing ( const std : : string & path , bool * exists ) {
2013-02-27 00:59:30 +01:00
std : : vector < PSPFileInfo > myVector ;
// TODO
2022-10-09 09:08:18 -07:00
if ( exists )
* exists = false ;
2013-02-27 00:59:30 +01:00
return myVector ;
}
void VFSFileSystem : : DoState ( PointerWrap & p ) {
2014-07-04 13:33:37 -07:00
// 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 ( ) ;
2020-08-09 21:20:42 -07:00
Do ( p , num ) ;
2014-07-04 13:33:37 -07:00
if ( num ! = 0 ) {
2013-04-13 01:39:17 -07:00
p . SetError ( p . ERROR_WARNING ) ;
2013-02-27 00:59:30 +01:00
ERROR_LOG ( FILESYS , " FIXME: Open files during savestate, could go badly. " ) ;
}
}