CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/FileSystems/DirectoryFileSystem.cpp
Views: 1401
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#ifdef __MINGW32__
21
#include <unistd.h>
22
#ifndef _POSIX_THREAD_SAFE_FUNCTIONS
23
#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
24
#endif
25
#endif
26
27
#include <algorithm>
28
#include <ctime>
29
#include <limits>
30
31
#include "Common/Data/Text/I18n.h"
32
#include "Common/Data/Encoding/Utf8.h"
33
#include "Common/Serialize/Serializer.h"
34
#include "Common/Serialize/SerializeFuncs.h"
35
#include "Common/StringUtils.h"
36
#include "Common/System/OSD.h"
37
#include "Common/File/FileUtil.h"
38
#include "Common/File/DiskFree.h"
39
#include "Common/File/VFS/VFS.h"
40
#include "Common/SysError.h"
41
#include "Core/FileSystems/DirectoryFileSystem.h"
42
#include "Core/FileSystems/ISOFileSystem.h"
43
#include "Core/HLE/sceKernel.h"
44
#include "Core/HW/MemoryStick.h"
45
#include "Core/CoreTiming.h"
46
#include "Core/System.h"
47
#include "Core/Replay.h"
48
#include "Core/Reporting.h"
49
#include "Core/ELF/ParamSFO.h"
50
51
#ifdef _WIN32
52
#include "Common/CommonWindows.h"
53
#include <sys/stat.h>
54
#if PPSSPP_PLATFORM(UWP)
55
#include <fileapifromapp.h>
56
#endif
57
#undef FILE_OPEN
58
#else
59
#include <dirent.h>
60
#include <unistd.h>
61
#include <sys/stat.h>
62
#if defined(__ANDROID__)
63
#include <sys/types.h>
64
#include <sys/vfs.h>
65
#define statvfs statfs
66
#else
67
#include <sys/statvfs.h>
68
#endif
69
#include <ctype.h>
70
#include <fcntl.h>
71
#endif
72
73
DirectoryFileSystem::DirectoryFileSystem(IHandleAllocator *_hAlloc, const Path & _basePath, FileSystemFlags _flags) : basePath(_basePath), flags(_flags) {
74
File::CreateFullPath(basePath);
75
hAlloc = _hAlloc;
76
}
77
78
DirectoryFileSystem::~DirectoryFileSystem() {
79
CloseAll();
80
}
81
82
// TODO(scoped): Merge the two below functions somehow.
83
84
Path DirectoryFileHandle::GetLocalPath(const Path &basePath, std::string localpath) const {
85
if (localpath.empty())
86
return basePath;
87
88
if (localpath[0] == '/')
89
localpath.erase(0, 1);
90
91
if (fileSystemFlags_ & FileSystemFlags::STRIP_PSP) {
92
if (startsWithNoCase(localpath, "PSP/")) {
93
localpath = localpath.substr(4);
94
}
95
}
96
97
return basePath / localpath;
98
}
99
100
Path DirectoryFileSystem::GetLocalPath(std::string internalPath) const {
101
if (internalPath.empty())
102
return basePath;
103
104
if (internalPath[0] == '/')
105
internalPath.erase(0, 1);
106
107
if (flags & FileSystemFlags::STRIP_PSP) {
108
if (startsWithNoCase(internalPath, "PSP/")) {
109
internalPath = internalPath.substr(4);
110
}
111
}
112
113
return basePath / internalPath;
114
}
115
116
bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, FileAccess access, u32 &error) {
117
error = 0;
118
119
if (access == FILEACCESS_NONE) {
120
error = SCE_KERNEL_ERROR_ERRNO_INVALID_ARGUMENT;
121
return false;
122
}
123
124
#if HOST_IS_CASE_SENSITIVE
125
if (access & (FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE)) {
126
DEBUG_LOG(Log::FileSystem, "Checking case for path %s", fileName.c_str());
127
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {
128
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
129
return false; // or go on and attempt (for a better error code than just 0?)
130
}
131
}
132
// else we try fopen first (in case we're lucky) before simulating case insensitivity
133
#endif
134
135
Path fullName = GetLocalPath(basePath, fileName);
136
137
// On the PSP, truncating doesn't lose data. If you seek later, you'll recover it.
138
// This is abnormal, so we deviate from the PSP's behavior and truncate on write/close.
139
// This means it's incorrectly not truncated before the write.
140
if (access & FILEACCESS_TRUNCATE) {
141
needsTrunc_ = 0;
142
}
143
144
//TODO: tests, should append seek to end of file? seeking in a file opened for append?
145
#if PPSSPP_PLATFORM(WINDOWS)
146
// Convert parameters to Windows permissions and access
147
DWORD desired = 0;
148
DWORD sharemode = 0;
149
DWORD openmode = 0;
150
if (access & FILEACCESS_READ) {
151
desired |= GENERIC_READ;
152
sharemode |= FILE_SHARE_READ;
153
}
154
if (access & FILEACCESS_WRITE) {
155
desired |= GENERIC_WRITE;
156
sharemode |= FILE_SHARE_WRITE | FILE_SHARE_READ;
157
}
158
if (access & FILEACCESS_CREATE) {
159
if (access & FILEACCESS_EXCL) {
160
openmode = CREATE_NEW;
161
} else {
162
openmode = OPEN_ALWAYS;
163
}
164
} else {
165
openmode = OPEN_EXISTING;
166
}
167
168
// Let's do it!
169
#if PPSSPP_PLATFORM(UWP)
170
hFile = CreateFile2FromAppW(fullName.ToWString().c_str(), desired, sharemode, openmode, nullptr);
171
#else
172
hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0);
173
#endif
174
bool success = hFile != INVALID_HANDLE_VALUE;
175
if (!success) {
176
DWORD w32err = GetLastError();
177
178
if (w32err == ERROR_SHARING_VIOLATION) {
179
// Sometimes, the file is locked for write, let's try again.
180
sharemode |= FILE_SHARE_WRITE;
181
#if PPSSPP_PLATFORM(UWP)
182
hFile = CreateFile2FromAppW(fullName.ToWString().c_str(), desired, sharemode, openmode, nullptr);
183
#else
184
hFile = CreateFile(fullName.ToWString().c_str(), desired, sharemode, 0, openmode, 0, 0);
185
#endif
186
success = hFile != INVALID_HANDLE_VALUE;
187
if (!success) {
188
w32err = GetLastError();
189
}
190
}
191
192
if (w32err == ERROR_DISK_FULL || w32err == ERROR_NOT_ENOUGH_QUOTA) {
193
// This is returned when the disk is full.
194
auto err = GetI18NCategory(I18NCat::ERRORS);
195
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");
196
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
197
} else if (!success) {
198
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
199
}
200
}
201
#else
202
if (fullName.Type() == PathType::CONTENT_URI) {
203
// Convert flags. Don't want to share this type, bad dependency.
204
u32 flags = File::OPEN_NONE;
205
if (access & FILEACCESS_READ)
206
flags |= File::OPEN_READ;
207
if (access & FILEACCESS_WRITE)
208
flags |= File::OPEN_WRITE;
209
if (access & FILEACCESS_APPEND)
210
flags |= File::OPEN_APPEND;
211
if (access & FILEACCESS_CREATE)
212
flags |= File::OPEN_CREATE;
213
// Important: don't pass TRUNCATE here, the PSP truncates weirdly. See #579.
214
// See above about truncate behavior. Just add READ to preserve data here.
215
if (access & FILEACCESS_TRUNCATE)
216
flags |= File::OPEN_READ;
217
218
int fd = File::OpenFD(fullName, (File::OpenFlag)flags);
219
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
220
if (fullName.FilePathContainsNoCase("PSP/GAME/")) {
221
inGameDir_ = true;
222
}
223
hFile = fd;
224
if (fd != -1) {
225
// Success
226
return true;
227
} else {
228
ERROR_LOG(Log::FileSystem, "File::OpenFD returned an error");
229
// TODO: Need better error codes from OpenFD so we can distinguish
230
// disk full. Just set not found for now.
231
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
232
return false;
233
}
234
}
235
236
int flags = 0;
237
if (access & FILEACCESS_APPEND) {
238
flags |= O_APPEND;
239
}
240
if ((access & FILEACCESS_READ) && (access & FILEACCESS_WRITE)) {
241
flags |= O_RDWR;
242
} else if (access & FILEACCESS_READ) {
243
flags |= O_RDONLY;
244
} else if (access & FILEACCESS_WRITE) {
245
flags |= O_WRONLY;
246
}
247
if (access & FILEACCESS_CREATE) {
248
flags |= O_CREAT;
249
}
250
if (access & FILEACCESS_EXCL) {
251
flags |= O_EXCL;
252
}
253
254
hFile = open(fullName.c_str(), flags, 0666);
255
bool success = hFile != -1;
256
#endif
257
258
#if HOST_IS_CASE_SENSITIVE
259
if (!success && !(access & FILEACCESS_CREATE)) {
260
if (!FixPathCase(basePath, fileName, FPC_PATH_MUST_EXIST)) {
261
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
262
return false;
263
}
264
fullName = GetLocalPath(basePath, fileName);
265
DEBUG_LOG(Log::FileSystem, "Case may have been incorrect, second try opening %s (%s)", fullName.c_str(), fileName.c_str());
266
267
// And try again with the correct case this time
268
#ifdef _WIN32
269
hFile = CreateFile(fullName.c_str(), desired, sharemode, 0, openmode, 0, 0);
270
success = hFile != INVALID_HANDLE_VALUE;
271
#else
272
hFile = open(fullName.c_str(), flags, 0666);
273
success = hFile != -1;
274
#endif
275
}
276
#endif
277
278
#ifndef _WIN32
279
if (success) {
280
// Reject directories, even if we succeed in opening them.
281
// TODO: Might want to do this stat first...
282
struct stat st;
283
if (fstat(hFile, &st) == 0 && S_ISDIR(st.st_mode)) {
284
close(hFile);
285
errno = EISDIR;
286
success = false;
287
}
288
} else if (errno == ENOSPC) {
289
// This is returned when the disk is full.
290
auto err = GetI18NCategory(I18NCat::ERRORS);
291
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");
292
error = SCE_KERNEL_ERROR_ERRNO_NO_PERM;
293
} else {
294
error = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
295
}
296
#endif
297
298
// Try to detect reads/writes to PSP/GAME to avoid them in replays.
299
if (fullName.FilePathContainsNoCase("PSP/GAME/")) {
300
inGameDir_ = true;
301
}
302
if (access & (FILEACCESS_APPEND | FILEACCESS_CREATE | FILEACCESS_WRITE)) {
303
MemoryStick_NotifyWrite();
304
}
305
306
return success;
307
}
308
309
size_t DirectoryFileHandle::Read(u8* pointer, s64 size)
310
{
311
size_t bytesRead = 0;
312
if (needsTrunc_ != -1) {
313
// If the file was marked to be truncated, pretend there's nothing.
314
// On a PSP. it actually is truncated, but the data wasn't erased.
315
off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);
316
if (needsTrunc_ <= off) {
317
return replay_ ? ReplayApplyDiskRead(pointer, 0, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : 0;
318
}
319
if (needsTrunc_ < off + size) {
320
size = needsTrunc_ - off;
321
}
322
}
323
if (size > 0) {
324
#ifdef _WIN32
325
::ReadFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesRead, 0);
326
#else
327
bytesRead = read(hFile, pointer, size);
328
#endif
329
}
330
return replay_ ? ReplayApplyDiskRead(pointer, (uint32_t)bytesRead, (uint32_t)size, inGameDir_, CoreTiming::GetGlobalTimeUs()) : bytesRead;
331
}
332
333
size_t DirectoryFileHandle::Write(const u8* pointer, s64 size)
334
{
335
size_t bytesWritten = 0;
336
bool diskFull = false;
337
338
#ifdef _WIN32
339
BOOL success = ::WriteFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesWritten, 0);
340
if (success == FALSE) {
341
DWORD err = GetLastError();
342
diskFull = err == ERROR_DISK_FULL || err == ERROR_NOT_ENOUGH_QUOTA;
343
}
344
#else
345
bytesWritten = write(hFile, pointer, size);
346
if (bytesWritten == (size_t)-1) {
347
diskFull = errno == ENOSPC;
348
}
349
#endif
350
if (needsTrunc_ != -1) {
351
off_t off = (off_t)Seek(0, FILEMOVE_CURRENT);
352
if (needsTrunc_ < off) {
353
needsTrunc_ = off;
354
}
355
}
356
357
if (replay_) {
358
bytesWritten = ReplayApplyDiskWrite(pointer, (uint64_t)bytesWritten, (uint64_t)size, &diskFull, inGameDir_, CoreTiming::GetGlobalTimeUs());
359
}
360
361
MemoryStick_NotifyWrite();
362
363
if (diskFull) {
364
ERROR_LOG(Log::FileSystem, "Disk full");
365
auto err = GetI18NCategory(I18NCat::ERRORS);
366
g_OSD.Show(OSDType::MESSAGE_ERROR, err->T("Disk full while writing data"), 0.0f, "diskfull");
367
// We only return an error when the disk is actually full.
368
// When writing this would cause the disk to be full, so it wasn't written, we return 0.
369
if (MemoryStick_FreeSpace() == 0) {
370
// Sign extend on 64-bit.
371
return (size_t)(s64)(s32)SCE_KERNEL_ERROR_ERRNO_DEVICE_NO_FREE_SPACE;
372
}
373
}
374
375
return bytesWritten;
376
}
377
378
size_t DirectoryFileHandle::Seek(s32 position, FileMove type)
379
{
380
if (needsTrunc_ != -1) {
381
// If the file is "currently truncated" move to the end based on that position.
382
// The actual, underlying file hasn't been truncated (yet.)
383
if (type == FILEMOVE_END) {
384
type = FILEMOVE_BEGIN;
385
position = (s32)(needsTrunc_ + position);
386
}
387
}
388
389
size_t result;
390
#ifdef _WIN32
391
DWORD moveMethod = 0;
392
switch (type) {
393
case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break;
394
case FILEMOVE_CURRENT: moveMethod = FILE_CURRENT; break;
395
case FILEMOVE_END: moveMethod = FILE_END; break;
396
}
397
398
LARGE_INTEGER distance;
399
distance.QuadPart = position;
400
LARGE_INTEGER cursor;
401
SetFilePointerEx(hFile, distance, &cursor, moveMethod);
402
result = (size_t)cursor.QuadPart;
403
#else
404
int moveMethod = 0;
405
switch (type) {
406
case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break;
407
case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break;
408
case FILEMOVE_END: moveMethod = SEEK_END; break;
409
}
410
result = lseek(hFile, position, moveMethod);
411
#endif
412
413
return replay_ ? (size_t)ReplayApplyDisk64(ReplayAction::FILE_SEEK, result, CoreTiming::GetGlobalTimeUs()) : result;
414
}
415
416
void DirectoryFileHandle::Close()
417
{
418
if (needsTrunc_ != -1) {
419
#ifdef _WIN32
420
Seek((s32)needsTrunc_, FILEMOVE_BEGIN);
421
if (SetEndOfFile(hFile) == 0) {
422
ERROR_LOG_REPORT(Log::FileSystem, "Failed to truncate file.");
423
}
424
#elif !PPSSPP_PLATFORM(SWITCH)
425
// Note: it's not great that Switch cannot truncate appropriately...
426
if (ftruncate(hFile, (off_t)needsTrunc_) != 0) {
427
ERROR_LOG_REPORT(Log::FileSystem, "Failed to truncate file.");
428
}
429
#endif
430
}
431
#ifdef _WIN32
432
if (hFile != (HANDLE)-1)
433
CloseHandle(hFile);
434
#else
435
if (hFile != -1)
436
close(hFile);
437
#endif
438
}
439
440
void DirectoryFileSystem::CloseAll() {
441
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
442
INFO_LOG(Log::FileSystem, "DirectoryFileSystem::CloseAll(): Force closing %d (%s)", (int)iter->first, iter->second.guestFilename.c_str());
443
iter->second.hFile.Close();
444
}
445
entries.clear();
446
}
447
448
bool DirectoryFileSystem::MkDir(const std::string &dirname) {
449
bool result;
450
#if HOST_IS_CASE_SENSITIVE
451
// Must fix case BEFORE attempting, because MkDir would create
452
// duplicate (different case) directories
453
454
std::string fixedCase = dirname;
455
if (!FixPathCase(basePath, fixedCase, FPC_PARTIAL_ALLOWED))
456
result = false;
457
else
458
result = File::CreateFullPath(GetLocalPath(fixedCase));
459
#else
460
result = File::CreateFullPath(GetLocalPath(dirname));
461
#endif
462
MemoryStick_NotifyWrite();
463
return ReplayApplyDisk(ReplayAction::MKDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;
464
}
465
466
bool DirectoryFileSystem::RmDir(const std::string &dirname) {
467
Path fullName = GetLocalPath(dirname);
468
469
#if HOST_IS_CASE_SENSITIVE
470
// Maybe we're lucky?
471
if (File::DeleteDirRecursively(fullName)) {
472
MemoryStick_NotifyWrite();
473
return (bool)ReplayApplyDisk(ReplayAction::RMDIR, true, CoreTiming::GetGlobalTimeUs());
474
}
475
476
// Nope, fix case and try again. Should we try again?
477
std::string fullPath = dirname;
478
if (!FixPathCase(basePath, fullPath, FPC_FILE_MUST_EXIST))
479
return (bool)ReplayApplyDisk(ReplayAction::RMDIR, false, CoreTiming::GetGlobalTimeUs());
480
481
fullName = GetLocalPath(fullPath);
482
#endif
483
484
bool result = File::DeleteDirRecursively(fullName);
485
MemoryStick_NotifyWrite();
486
return ReplayApplyDisk(ReplayAction::RMDIR, result, CoreTiming::GetGlobalTimeUs()) != 0;
487
}
488
489
int DirectoryFileSystem::RenameFile(const std::string &from, const std::string &to) {
490
std::string fullTo = to;
491
492
// Rename ignores the path (even if specified) on to.
493
size_t chop_at = to.find_last_of('/');
494
if (chop_at != to.npos)
495
fullTo = to.substr(chop_at + 1);
496
497
// Now put it in the same directory as from.
498
size_t dirname_end = from.find_last_of('/');
499
if (dirname_end != from.npos)
500
fullTo = from.substr(0, dirname_end + 1) + fullTo;
501
502
// At this point, we should check if the paths match and give an already exists error.
503
if (from == fullTo)
504
return ReplayApplyDisk(ReplayAction::FILE_RENAME, SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS, CoreTiming::GetGlobalTimeUs());
505
506
Path fullFrom = GetLocalPath(from);
507
508
#if HOST_IS_CASE_SENSITIVE
509
// In case TO should overwrite a file with different case. Check error code?
510
if (!FixPathCase(basePath, fullTo, FPC_PATH_MUST_EXIST))
511
return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());
512
#endif
513
514
Path fullToPath = GetLocalPath(fullTo);
515
516
bool retValue = File::Rename(fullFrom, fullToPath);
517
518
#if HOST_IS_CASE_SENSITIVE
519
if (!retValue)
520
{
521
// May have failed due to case sensitivity on FROM, so try again. Check error code?
522
std::string fullFromPath = from;
523
if (!FixPathCase(basePath, fullFromPath, FPC_FILE_MUST_EXIST))
524
return ReplayApplyDisk(ReplayAction::FILE_RENAME, -1, CoreTiming::GetGlobalTimeUs());
525
fullFrom = GetLocalPath(fullFromPath);
526
527
retValue = File::Rename(fullFrom, fullToPath);
528
}
529
#endif
530
531
// TODO: Better error codes.
532
int result = retValue ? 0 : (int)SCE_KERNEL_ERROR_ERRNO_FILE_ALREADY_EXISTS;
533
MemoryStick_NotifyWrite();
534
return ReplayApplyDisk(ReplayAction::FILE_RENAME, result, CoreTiming::GetGlobalTimeUs());
535
}
536
537
bool DirectoryFileSystem::RemoveFile(const std::string &filename) {
538
Path localPath = GetLocalPath(filename);
539
540
bool retValue = File::Delete(localPath);
541
542
#if HOST_IS_CASE_SENSITIVE
543
if (!retValue)
544
{
545
// May have failed due to case sensitivity, so try again. Try even if it fails?
546
std::string fullNamePath = filename;
547
if (!FixPathCase(basePath, fullNamePath, FPC_FILE_MUST_EXIST))
548
return (bool)ReplayApplyDisk(ReplayAction::FILE_REMOVE, false, CoreTiming::GetGlobalTimeUs());
549
localPath = GetLocalPath(fullNamePath);
550
551
retValue = File::Delete(localPath);
552
}
553
#endif
554
555
MemoryStick_NotifyWrite();
556
return ReplayApplyDisk(ReplayAction::FILE_REMOVE, retValue, CoreTiming::GetGlobalTimeUs()) != 0;
557
}
558
559
int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
560
OpenFileEntry entry;
561
entry.hFile.fileSystemFlags_ = flags;
562
u32 err = 0;
563
bool success = entry.hFile.Open(basePath, filename, (FileAccess)(access & FILEACCESS_PSP_FLAGS), err);
564
if (err == 0 && !success) {
565
err = SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
566
}
567
568
err = ReplayApplyDisk(ReplayAction::FILE_OPEN, err, CoreTiming::GetGlobalTimeUs());
569
if (err != 0) {
570
std::string errorString;
571
int logError;
572
#ifdef _WIN32
573
auto win32err = GetLastError();
574
logError = (int)win32err;
575
errorString = GetStringErrorMsg(win32err);
576
#else
577
logError = (int)errno;
578
#endif
579
if (!(access & FILEACCESS_PPSSPP_QUIET)) {
580
ERROR_LOG(Log::FileSystem, "DirectoryFileSystem::OpenFile('%s'): FAILED, %d - access = %d '%s'", filename.c_str(), logError, (int)(access & FILEACCESS_PSP_FLAGS), errorString.c_str());
581
}
582
return err;
583
} else {
584
#ifdef _WIN32
585
if (access & FILEACCESS_APPEND) {
586
entry.hFile.Seek(0, FILEMOVE_END);
587
}
588
#endif
589
590
u32 newHandle = hAlloc->GetNewHandle();
591
592
entry.guestFilename = filename;
593
entry.access = (FileAccess)(access & FILEACCESS_PSP_FLAGS);
594
595
entries[newHandle] = entry;
596
597
return newHandle;
598
}
599
}
600
601
void DirectoryFileSystem::CloseFile(u32 handle) {
602
EntryMap::iterator iter = entries.find(handle);
603
if (iter != entries.end()) {
604
hAlloc->FreeHandle(handle);
605
iter->second.hFile.Close();
606
entries.erase(iter);
607
} else {
608
//This shouldn't happen...
609
ERROR_LOG(Log::FileSystem,"Cannot close file that hasn't been opened: %08x", handle);
610
}
611
}
612
613
bool DirectoryFileSystem::OwnsHandle(u32 handle) {
614
EntryMap::iterator iter = entries.find(handle);
615
return (iter != entries.end());
616
}
617
618
int DirectoryFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
619
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
620
}
621
622
PSPDevType DirectoryFileSystem::DevType(u32 handle) {
623
return PSPDevType::FILE;
624
}
625
626
size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {
627
int ignored;
628
return ReadFile(handle, pointer, size, ignored);
629
}
630
631
size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
632
EntryMap::iterator iter = entries.find(handle);
633
if (iter != entries.end()) {
634
if (size < 0) {
635
ERROR_LOG_REPORT(Log::FileSystem, "Invalid read for %lld bytes from disk %s", size, iter->second.guestFilename.c_str());
636
return 0;
637
}
638
639
size_t bytesRead = iter->second.hFile.Read(pointer,size);
640
return bytesRead;
641
} else {
642
// This shouldn't happen...
643
ERROR_LOG(Log::FileSystem,"Cannot read file that hasn't been opened: %08x", handle);
644
return 0;
645
}
646
}
647
648
size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {
649
int ignored;
650
return WriteFile(handle, pointer, size, ignored);
651
}
652
653
size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {
654
EntryMap::iterator iter = entries.find(handle);
655
if (iter != entries.end()) {
656
size_t bytesWritten = iter->second.hFile.Write(pointer,size);
657
return bytesWritten;
658
} else {
659
//This shouldn't happen...
660
ERROR_LOG(Log::FileSystem,"Cannot write to file that hasn't been opened: %08x", handle);
661
return 0;
662
}
663
}
664
665
size_t DirectoryFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
666
EntryMap::iterator iter = entries.find(handle);
667
if (iter != entries.end()) {
668
return iter->second.hFile.Seek(position,type);
669
} else {
670
//This shouldn't happen...
671
ERROR_LOG(Log::FileSystem,"Cannot seek in file that hasn't been opened: %08x", handle);
672
return 0;
673
}
674
}
675
676
PSPFileInfo DirectoryFileSystem::GetFileInfo(std::string filename) {
677
PSPFileInfo x;
678
x.name = filename;
679
680
File::FileInfo info;
681
Path fullName = GetLocalPath(filename);
682
if (!File::GetFileInfo(fullName, &info)) {
683
#if HOST_IS_CASE_SENSITIVE
684
if (! FixPathCase(basePath, filename, FPC_FILE_MUST_EXIST))
685
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
686
fullName = GetLocalPath(filename);
687
688
if (!File::GetFileInfo(fullName, &info))
689
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
690
#else
691
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
692
#endif
693
}
694
695
x.type = info.isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
696
x.exists = true;
697
698
if (x.type != FILETYPE_DIRECTORY) {
699
x.size = info.size;
700
}
701
x.access = info.access;
702
time_t atime = info.atime;
703
time_t ctime = info.ctime;
704
time_t mtime = info.mtime;
705
706
localtime_r((time_t*)&atime, &x.atime);
707
localtime_r((time_t*)&ctime, &x.ctime);
708
localtime_r((time_t*)&mtime, &x.mtime);
709
710
return ReplayApplyDiskFileInfo(x, CoreTiming::GetGlobalTimeUs());
711
}
712
713
#ifdef _WIN32
714
#define FILETIME_FROM_UNIX_EPOCH_US 11644473600000000ULL
715
716
static void tmFromFiletime(tm &dest, const FILETIME &src) {
717
u64 from_1601_us = (((u64) src.dwHighDateTime << 32ULL) + (u64) src.dwLowDateTime) / 10ULL;
718
u64 from_1970_us = from_1601_us - FILETIME_FROM_UNIX_EPOCH_US;
719
720
time_t t = (time_t) (from_1970_us / 1000000UL);
721
localtime_r(&t, &dest);
722
}
723
#endif
724
725
// This simulates a bug in the PSP VFAT driver.
726
//
727
// Windows NT VFAT optimizes valid DOS filenames that are in lowercase.
728
// The PSP VFAT driver doesn't support this optimization, and behaves like Windows 98.
729
// Some homebrew depends on this bug in the PSP firmware.
730
//
731
// This essentially tries to simulate the "Windows 98 world view" on modern operating systems.
732
// Essentially all lowercase files are seen as UPPERCASE.
733
//
734
// Note: PSP-created files would stay lowercase, but this uppercases them too.
735
// Hopefully no PSP games read directories after they create files in them...
736
static std::string SimulateVFATBug(std::string filename) {
737
// These are the characters allowed in DOS filenames.
738
static const char * const FAT_UPPER_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&'(){}-_`~";
739
static const char * const FAT_LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&'(){}-_`~";
740
static const char * const LOWER_CHARS = "abcdefghijklmnopqrstuvwxyz";
741
742
// To avoid logging/comparing, skip all this if it has no lowercase chars to begin with.
743
size_t lowerchar = filename.find_first_of(LOWER_CHARS);
744
if (lowerchar == filename.npos) {
745
return filename;
746
}
747
748
bool apply_hack = false;
749
size_t dot_pos = filename.find('.');
750
if (dot_pos == filename.npos && filename.length() <= 8) {
751
size_t badchar = filename.find_first_not_of(FAT_LOWER_CHARS);
752
if (badchar == filename.npos) {
753
// It's all lowercase. Convert to upper.
754
apply_hack = true;
755
}
756
} else {
757
// There's a separate flag for each, so we compare separately.
758
// But they both have to either be all upper or lowercase.
759
std::string base = filename.substr(0, dot_pos);
760
std::string ext = filename.substr(dot_pos + 1);
761
762
// The filename must be short enough to fit.
763
if (base.length() <= 8 && ext.length() <= 3) {
764
size_t base_non_lower = base.find_first_not_of(FAT_LOWER_CHARS);
765
size_t base_non_upper = base.find_first_not_of(FAT_UPPER_CHARS);
766
size_t ext_non_lower = ext.find_first_not_of(FAT_LOWER_CHARS);
767
size_t ext_non_upper = ext.find_first_not_of(FAT_UPPER_CHARS);
768
769
// As long as neither is mixed, we apply the hack.
770
bool base_apply_hack = base_non_lower == base.npos || base_non_upper == base.npos;
771
bool ext_apply_hack = ext_non_lower == ext.npos || ext_non_upper == ext.npos;
772
apply_hack = base_apply_hack && ext_apply_hack;
773
}
774
}
775
776
if (apply_hack) {
777
VERBOSE_LOG(Log::FileSystem, "Applying VFAT hack to filename: %s", filename.c_str());
778
// In this situation, NT would write UPPERCASE, and just set a flag to say "actually lowercase".
779
// That VFAT flag isn't read by the PSP firmware, so let's pretend to "not read it."
780
std::transform(filename.begin(), filename.end(), filename.begin(), toupper);
781
}
782
783
return filename;
784
}
785
786
bool DirectoryFileSystem::ComputeRecursiveDirSizeIfFast(const std::string &path, int64_t *size) {
787
Path localPath = GetLocalPath(path);
788
789
int64_t sizeTemp = File::ComputeRecursiveDirectorySize(localPath);
790
if (sizeTemp >= 0) {
791
*size = sizeTemp;
792
return true;
793
} else {
794
return false;
795
}
796
}
797
798
std::vector<PSPFileInfo> DirectoryFileSystem::GetDirListing(const std::string &path, bool *exists) {
799
std::vector<PSPFileInfo> myVector;
800
801
std::vector<File::FileInfo> files;
802
Path localPath = GetLocalPath(path);
803
const int flags = File::GETFILES_GETHIDDEN | File::GETFILES_GET_NAVIGATION_ENTRIES;
804
bool success = File::GetFilesInDir(localPath, &files, nullptr, flags);
805
#if HOST_IS_CASE_SENSITIVE
806
if (!success) {
807
// TODO: Case sensitivity should be checked on a file system basis, right?
808
std::string fixedPath = path;
809
if (FixPathCase(basePath, fixedPath, FPC_FILE_MUST_EXIST)) {
810
// May have failed due to case sensitivity, try again
811
localPath = GetLocalPath(fixedPath);
812
success = File::GetFilesInDir(localPath, &files, nullptr, flags);
813
}
814
}
815
#endif
816
if (!success) {
817
if (exists)
818
*exists = false;
819
return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());
820
}
821
822
bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles;
823
824
// Then apply transforms to match PSP idiosynchrasies, as we convert the entries.
825
for (auto &file : files) {
826
PSPFileInfo entry;
827
if (Flags() & FileSystemFlags::SIMULATE_FAT32) {
828
entry.name = SimulateVFATBug(file.name);
829
} else {
830
entry.name = file.name;
831
}
832
if (hideISOFiles) {
833
if (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso") || endsWithNoCase(entry.name, ".chd")) { // chd not really necessary, but let's hide them too.
834
// Workaround for DJ Max Portable, see compat.ini.
835
continue;
836
} else if (file.isDirectory) {
837
if (endsWithNoCase(path, "SAVEDATA")) {
838
// Don't let it see savedata from other games, it can misinterpret stuff.
839
std::string gameID = g_paramSFO.GetDiscID();
840
if (entry.name.size() > 2 && !startsWithNoCase(entry.name, gameID)) {
841
continue;
842
}
843
} else if (file.name == "GAME" || file.name == "TEXTURES" || file.name == "PPSSPP_STATE" || file.name == "PLUGINS" || file.name == "SYSTEM" || equalsNoCase(file.name, "Cheats")) {
844
// The game scans these folders on startup which can take time. Skip them.
845
continue;
846
}
847
}
848
}
849
if (file.name == "..") {
850
entry.size = 4096;
851
} else {
852
entry.size = file.size;
853
}
854
if (file.isDirectory) {
855
entry.type = FILETYPE_DIRECTORY;
856
} else {
857
entry.type = FILETYPE_NORMAL;
858
}
859
entry.access = file.access;
860
entry.exists = file.exists;
861
862
localtime_r((time_t*)&file.atime, &entry.atime);
863
localtime_r((time_t*)&file.ctime, &entry.ctime);
864
localtime_r((time_t*)&file.mtime, &entry.mtime);
865
866
myVector.push_back(entry);
867
}
868
869
if (exists)
870
*exists = true;
871
return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs());
872
}
873
874
u64 DirectoryFileSystem::FreeSpace(const std::string &path) {
875
int64_t result = 0;
876
if (free_disk_space(GetLocalPath(path), result)) {
877
return ReplayApplyDisk64(ReplayAction::FREESPACE, (uint64_t)result, CoreTiming::GetGlobalTimeUs());
878
}
879
880
#if HOST_IS_CASE_SENSITIVE
881
std::string fixedCase = path;
882
if (FixPathCase(basePath, fixedCase, FPC_FILE_MUST_EXIST)) {
883
// May have failed due to case sensitivity, try again.
884
if (free_disk_space(GetLocalPath(fixedCase), result)) {
885
return ReplayApplyDisk64(ReplayAction::FREESPACE, result, CoreTiming::GetGlobalTimeUs());
886
}
887
}
888
#endif
889
890
// Just assume they're swimming in free disk space if we don't know otherwise.
891
return ReplayApplyDisk64(ReplayAction::FREESPACE, std::numeric_limits<u64>::max(), CoreTiming::GetGlobalTimeUs());
892
}
893
894
void DirectoryFileSystem::DoState(PointerWrap &p) {
895
auto s = p.Section("DirectoryFileSystem", 0, 2);
896
if (!s)
897
return;
898
899
// Savestate layout:
900
// u32: number of entries
901
// per-entry:
902
// u32: handle number
903
// std::string filename (in guest's terms, untranslated)
904
// enum FileAccess file access mode
905
// u32 seek position
906
// s64 current truncate position (v2+ only)
907
908
u32 num = (u32) entries.size();
909
Do(p, num);
910
911
if (p.mode == p.MODE_READ) {
912
CloseAll();
913
u32 key;
914
OpenFileEntry entry;
915
entry.hFile.fileSystemFlags_ = flags;
916
for (u32 i = 0; i < num; i++) {
917
Do(p, key);
918
Do(p, entry.guestFilename);
919
Do(p, entry.access);
920
u32 err;
921
bool brokenFile = false;
922
if (!entry.hFile.Open(basePath,entry.guestFilename,entry.access, err)) {
923
ERROR_LOG(Log::FileSystem, "Failed to reopen file while loading state: %s", entry.guestFilename.c_str());
924
brokenFile = true;
925
}
926
u32 position;
927
Do(p, position);
928
if (position != entry.hFile.Seek(position, FILEMOVE_BEGIN)) {
929
ERROR_LOG(Log::FileSystem, "Failed to restore seek position while loading state: %s", entry.guestFilename.c_str());
930
brokenFile = true;
931
}
932
if (s >= 2) {
933
Do(p, entry.hFile.needsTrunc_);
934
}
935
// Let's hope that things don't go that badly with the file mysteriously auto-closed.
936
// Better than not loading the save state at all, hopefully.
937
if (!brokenFile) {
938
entries[key] = entry;
939
}
940
}
941
} else {
942
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
943
u32 key = iter->first;
944
Do(p, key);
945
Do(p, iter->second.guestFilename);
946
Do(p, iter->second.access);
947
u32 position = (u32)iter->second.hFile.Seek(0, FILEMOVE_CURRENT);
948
Do(p, position);
949
Do(p, iter->second.hFile.needsTrunc_);
950
}
951
}
952
}
953
954
955
956
VFSFileSystem::VFSFileSystem(IHandleAllocator *_hAlloc, std::string _basePath) : basePath(_basePath) {
957
hAlloc = _hAlloc;
958
}
959
960
VFSFileSystem::~VFSFileSystem() {
961
for (auto iter = entries.begin(); iter != entries.end(); ++iter) {
962
delete [] iter->second.fileData;
963
}
964
entries.clear();
965
}
966
967
std::string VFSFileSystem::GetLocalPath(const std::string &localPath) {
968
return basePath + localPath;
969
}
970
971
bool VFSFileSystem::MkDir(const std::string &dirname) {
972
// NOT SUPPORTED - READ ONLY
973
return false;
974
}
975
976
bool VFSFileSystem::RmDir(const std::string &dirname) {
977
// NOT SUPPORTED - READ ONLY
978
return false;
979
}
980
981
int VFSFileSystem::RenameFile(const std::string &from, const std::string &to) {
982
// NOT SUPPORTED - READ ONLY
983
return -1;
984
}
985
986
bool VFSFileSystem::RemoveFile(const std::string &filename) {
987
// NOT SUPPORTED - READ ONLY
988
return false;
989
}
990
991
int VFSFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) {
992
if (access != FILEACCESS_READ) {
993
ERROR_LOG(Log::FileSystem, "VFSFileSystem only supports plain reading");
994
return SCE_KERNEL_ERROR_ERRNO_INVALID_FLAG;
995
}
996
997
std::string fullName = GetLocalPath(filename);
998
const char *fullNameC = fullName.c_str();
999
VERBOSE_LOG(Log::FileSystem, "VFSFileSystem actually opening %s (%s)", fullNameC, filename.c_str());
1000
1001
size_t size;
1002
u8 *data = g_VFS.ReadFile(fullNameC, &size);
1003
if (!data) {
1004
ERROR_LOG(Log::FileSystem, "VFSFileSystem failed to open %s", filename.c_str());
1005
return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
1006
}
1007
1008
OpenFileEntry entry;
1009
entry.fileData = data;
1010
entry.size = size;
1011
entry.seekPos = 0;
1012
u32 newHandle = hAlloc->GetNewHandle();
1013
entries[newHandle] = entry;
1014
return newHandle;
1015
}
1016
1017
PSPFileInfo VFSFileSystem::GetFileInfo(std::string filename) {
1018
PSPFileInfo x;
1019
x.name = filename;
1020
1021
std::string fullName = GetLocalPath(filename);
1022
File::FileInfo fo;
1023
if (g_VFS.GetFileInfo(fullName.c_str(), &fo)) {
1024
x.exists = fo.exists;
1025
if (x.exists) {
1026
x.size = fo.size;
1027
x.type = fo.isDirectory ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
1028
x.access = fo.isWritable ? 0666 : 0444;
1029
}
1030
} else {
1031
x.exists = false;
1032
}
1033
return x;
1034
}
1035
1036
void VFSFileSystem::CloseFile(u32 handle) {
1037
EntryMap::iterator iter = entries.find(handle);
1038
if (iter != entries.end()) {
1039
delete [] iter->second.fileData;
1040
entries.erase(iter);
1041
} else {
1042
//This shouldn't happen...
1043
ERROR_LOG(Log::FileSystem,"Cannot close file that hasn't been opened: %08x", handle);
1044
}
1045
}
1046
1047
bool VFSFileSystem::OwnsHandle(u32 handle) {
1048
EntryMap::iterator iter = entries.find(handle);
1049
return (iter != entries.end());
1050
}
1051
1052
int VFSFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
1053
return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
1054
}
1055
1056
PSPDevType VFSFileSystem::DevType(u32 handle) {
1057
return PSPDevType::FILE;
1058
}
1059
1060
size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {
1061
int ignored;
1062
return ReadFile(handle, pointer, size, ignored);
1063
}
1064
1065
size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
1066
DEBUG_LOG(Log::FileSystem,"VFSFileSystem::ReadFile %08x %p %i", handle, pointer, (u32)size);
1067
EntryMap::iterator iter = entries.find(handle);
1068
if (iter != entries.end())
1069
{
1070
if(iter->second.seekPos + size > iter->second.size)
1071
size = iter->second.size - iter->second.seekPos;
1072
if(size < 0) size = 0;
1073
size_t bytesRead = size;
1074
memcpy(pointer, iter->second.fileData + iter->second.seekPos, size);
1075
iter->second.seekPos += size;
1076
return bytesRead;
1077
} else {
1078
ERROR_LOG(Log::FileSystem,"Cannot read file that hasn't been opened: %08x", handle);
1079
return 0;
1080
}
1081
}
1082
1083
size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) {
1084
int ignored;
1085
return WriteFile(handle, pointer, size, ignored);
1086
}
1087
1088
size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {
1089
// NOT SUPPORTED - READ ONLY
1090
return 0;
1091
}
1092
1093
size_t VFSFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
1094
EntryMap::iterator iter = entries.find(handle);
1095
if (iter != entries.end()) {
1096
switch (type) {
1097
case FILEMOVE_BEGIN: iter->second.seekPos = position; break;
1098
case FILEMOVE_CURRENT: iter->second.seekPos += position; break;
1099
case FILEMOVE_END: iter->second.seekPos = iter->second.size + position; break;
1100
}
1101
return iter->second.seekPos;
1102
} else {
1103
//This shouldn't happen...
1104
ERROR_LOG(Log::FileSystem,"Cannot seek in file that hasn't been opened: %08x", handle);
1105
return 0;
1106
}
1107
}
1108
1109
std::vector<PSPFileInfo> VFSFileSystem::GetDirListing(const std::string &path, bool *exists) {
1110
std::vector<PSPFileInfo> myVector;
1111
// TODO
1112
if (exists)
1113
*exists = false;
1114
return myVector;
1115
}
1116
1117
void VFSFileSystem::DoState(PointerWrap &p) {
1118
// Note: used interchangeably with DirectoryFileSystem for flash0:, so we use the same name.
1119
auto s = p.Section("DirectoryFileSystem", 0, 2);
1120
if (!s)
1121
return;
1122
1123
u32 num = (u32) entries.size();
1124
Do(p, num);
1125
1126
if (num != 0) {
1127
p.SetError(p.ERROR_WARNING);
1128
ERROR_LOG(Log::FileSystem, "FIXME: Open files during savestate, could go badly.");
1129
}
1130
}
1131
1132