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