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 #ifdef __MINGW32__
20 #include <unistd.h>
21 #ifndef _POSIX_THREAD_SAFE_FUNCTIONS
22 #define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
23 #endif
24 #endif
25 #include <ctime>
26 
27 #include "Common/File/FileUtil.h"
28 #include "Common/File/DirListing.h"
29 #include "Common/StringUtils.h"
30 #include "Common/Serialize/Serializer.h"
31 #include "Common/Serialize/SerializeFuncs.h"
32 #include "Core/FileSystems/VirtualDiscFileSystem.h"
33 #include "Core/FileSystems/ISOFileSystem.h"
34 #include "Core/HLE/sceKernel.h"
35 #include "Core/Reporting.h"
36 #include "Common/Data/Encoding/Utf8.h"
37 
38 #ifdef _WIN32
39 #include "Common/CommonWindows.h"
40 #include <sys/stat.h>
41 #else
42 #include <dirent.h>
43 #include <unistd.h>
44 #include <sys/stat.h>
45 #include <ctype.h>
46 #if !PPSSPP_PLATFORM(SWITCH)
47 #include <dlfcn.h>
48 #endif
49 #endif
50 
51 const std::string INDEX_FILENAME = ".ppsspp-index.lst";
52 
VirtualDiscFileSystem(IHandleAllocator * _hAlloc,const Path & _basePath)53 VirtualDiscFileSystem::VirtualDiscFileSystem(IHandleAllocator *_hAlloc, const Path &_basePath)
54 	: basePath(_basePath), currentBlockIndex(0) {
55 	hAlloc = _hAlloc;
56 	LoadFileListIndex();
57 }
58 
~VirtualDiscFileSystem()59 VirtualDiscFileSystem::~VirtualDiscFileSystem() {
60 	for (auto iter = entries.begin(), end = entries.end(); iter != end; ++iter) {
61 		if (iter->second.type != VFILETYPE_ISO) {
62 			iter->second.Close();
63 		}
64 	}
65 	for (auto iter = handlers.begin(), end = handlers.end(); iter != end; ++iter) {
66 		delete iter->second;
67 	}
68 }
69 
LoadFileListIndex()70 void VirtualDiscFileSystem::LoadFileListIndex() {
71 	const Path filename = basePath / INDEX_FILENAME;
72 	if (!File::Exists(filename)) {
73 		return;
74 	}
75 
76 	FILE *f = File::OpenCFile(filename, "r");
77 	if (!f) {
78 		return;
79 	}
80 
81 	std::string buf;
82 	static const int MAX_LINE_SIZE = 2048;
83 	char linebuf[MAX_LINE_SIZE]{};
84 	while (fgets(linebuf, MAX_LINE_SIZE, f)) {
85 		std::string line = linebuf;
86 		// Strip newline from fgets.
87 		if (!line.empty() && line.back() == '\n')
88 			line.resize(line.size() - 1);
89 
90 		// Ignore any UTF-8 BOM.
91 		if (line.substr(0, 3) == "\xEF\xBB\xBF") {
92 			line = line.substr(3);
93 		}
94 
95 		if (line.empty() || line[0] == ';') {
96 			continue;
97 		}
98 
99 		FileListEntry entry = {""};
100 
101 		// Syntax: HEXPOS filename or HEXPOS filename:handler
102 		size_t filename_pos = line.find(' ');
103 		if (filename_pos == line.npos) {
104 			ERROR_LOG(FILESYS, "Unexpected line in %s: %s", INDEX_FILENAME.c_str(), line.c_str());
105 			continue;
106 		}
107 
108 		filename_pos++;
109 		// Strip any slash prefix.
110 		while (filename_pos < line.length() && line[filename_pos] == '/') {
111 			filename_pos++;
112 		}
113 
114 		// Check if there's a handler specified.
115 		size_t handler_pos = line.find(':', filename_pos);
116 		if (handler_pos != line.npos) {
117 			entry.fileName = line.substr(filename_pos, handler_pos - filename_pos);
118 
119 			std::string handler = line.substr(handler_pos + 1);
120 			size_t trunc = handler.find_last_not_of("\r\n");
121 			if (trunc != handler.npos && trunc != handler.size())
122 				handler.resize(trunc + 1);
123 
124 			if (handlers.find(handler) == handlers.end())
125 				handlers[handler] = new Handler(handler.c_str(), this);
126 			if (handlers[handler]->IsValid())
127 				entry.handler = handlers[handler];
128 		} else {
129 			entry.fileName = line.substr(filename_pos);
130 		}
131 		size_t trunc = entry.fileName.find_last_not_of("\r\n");
132 		if (trunc != entry.fileName.npos && trunc != entry.fileName.size())
133 			entry.fileName.resize(trunc + 1);
134 
135 		entry.firstBlock = strtol(line.c_str(), NULL, 16);
136 		if (entry.handler != NULL && entry.handler->IsValid()) {
137 			HandlerFileHandle temp = entry.handler;
138 			if (temp.Open(basePath.ToString(), entry.fileName, FILEACCESS_READ)) {
139 				entry.totalSize = (u32)temp.Seek(0, FILEMOVE_END);
140 				temp.Close();
141 			} else {
142 				ERROR_LOG(FILESYS, "Unable to open virtual file: %s", entry.fileName.c_str());
143 			}
144 		} else {
145 			entry.totalSize = File::GetFileSize(GetLocalPath(entry.fileName));
146 		}
147 
148 		// Try to keep currentBlockIndex sane, in case there are other files.
149 		u32 nextBlock = entry.firstBlock + (entry.totalSize + 2047) / 2048;
150 		if (nextBlock > currentBlockIndex) {
151 			currentBlockIndex = nextBlock;
152 		}
153 
154 		fileList.push_back(entry);
155 	}
156 
157 	fclose(f);
158 }
159 
DoState(PointerWrap & p)160 void VirtualDiscFileSystem::DoState(PointerWrap &p)
161 {
162 	auto s = p.Section("VirtualDiscFileSystem", 1, 2);
163 	if (!s)
164 		return;
165 
166 	int fileListSize = (int)fileList.size();
167 	int entryCount = (int)entries.size();
168 
169 	Do(p, fileListSize);
170 	Do(p, entryCount);
171 	Do(p, currentBlockIndex);
172 
173 	FileListEntry dummy = {""};
174 	fileList.resize(fileListSize, dummy);
175 
176 	for (int i = 0; i < fileListSize; i++)
177 	{
178 		Do(p, fileList[i].fileName);
179 		Do(p, fileList[i].firstBlock);
180 		Do(p, fileList[i].totalSize);
181 	}
182 
183 	if (p.mode == p.MODE_READ)
184 	{
185 		entries.clear();
186 
187 		for (int i = 0; i < entryCount; i++)
188 		{
189 			u32 fd = 0;
190 			OpenFileEntry of(Flags());
191 
192 			Do(p, fd);
193 			Do(p, of.fileIndex);
194 			Do(p, of.type);
195 			Do(p, of.curOffset);
196 			Do(p, of.startOffset);
197 			Do(p, of.size);
198 
199 			// open file
200 			if (of.type != VFILETYPE_ISO) {
201 				if (fileList[of.fileIndex].handler != NULL) {
202 					of.handler = fileList[of.fileIndex].handler;
203 				}
204 
205 				bool success = of.Open(basePath, fileList[of.fileIndex].fileName, FILEACCESS_READ);
206 				if (!success) {
207 					ERROR_LOG(FILESYS, "Failed to create file handle for %s.", fileList[of.fileIndex].fileName.c_str());
208 				} else {
209 					if (of.type == VFILETYPE_LBN) {
210 						of.Seek(of.curOffset + of.startOffset, FILEMOVE_BEGIN);
211 					} else {
212 						of.Seek(of.curOffset, FILEMOVE_BEGIN);
213 					}
214 				}
215 			}
216 
217 			// TODO: I think we only need to write to the map on load?
218 			entries[fd] = of;
219 		}
220 	} else {
221 		for (EntryMap::iterator it = entries.begin(), end = entries.end(); it != end; ++it)
222 		{
223 			OpenFileEntry &of = it->second;
224 
225 			Do(p, it->first);
226 			Do(p, of.fileIndex);
227 			Do(p, of.type);
228 			Do(p, of.curOffset);
229 			Do(p, of.startOffset);
230 			Do(p, of.size);
231 		}
232 	}
233 
234 	if (s >= 2) {
235 		Do(p, lastReadBlock_);
236 	} else {
237 		lastReadBlock_ = 0;
238 	}
239 
240 	// We don't savestate handlers (loaded on fs load), but if they change, it may not load properly.
241 }
242 
GetLocalPath(std::string localpath)243 Path VirtualDiscFileSystem::GetLocalPath(std::string localpath) {
244 	if (localpath.empty())
245 		return basePath;
246 
247 	if (localpath[0] == '/')
248 		localpath.erase(0,1);
249 	//Convert slashes
250 #ifdef _WIN32
251 	for (size_t i = 0; i < localpath.size(); i++) {
252 		if (localpath[i] == '/')
253 			localpath[i] = '\\';
254 	}
255 #endif
256 	return basePath / localpath;
257 }
258 
getFileListIndex(std::string & fileName)259 int VirtualDiscFileSystem::getFileListIndex(std::string &fileName)
260 {
261 	std::string normalized;
262 	if (fileName.length() >= 1 && fileName[0] == '/') {
263 		normalized = fileName.substr(1);
264 	} else {
265 		normalized = fileName;
266 	}
267 
268 	for (size_t i = 0; i < fileList.size(); i++)
269 	{
270 		if (fileList[i].fileName == normalized)
271 			return (int)i;
272 	}
273 
274 	// unknown file - add it
275 	Path fullName = GetLocalPath(fileName);
276 	if (! File::Exists(fullName)) {
277 #if HOST_IS_CASE_SENSITIVE
278 		if (! FixPathCase(basePath, fileName, FPC_FILE_MUST_EXIST))
279 			return -1;
280 		fullName = GetLocalPath(fileName);
281 
282 		if (! File::Exists(fullName))
283 			return -1;
284 #else
285 		return -1;
286 #endif
287 	}
288 
289 	if (File::IsDirectory(fullName))
290 		return -1;
291 
292 	FileListEntry entry = {""};
293 	entry.fileName = normalized;
294 	entry.totalSize = File::GetFileSize(fullName);
295 	entry.firstBlock = currentBlockIndex;
296 	currentBlockIndex += (entry.totalSize+2047)/2048;
297 
298 	fileList.push_back(entry);
299 
300 	return (int)fileList.size()-1;
301 }
302 
getFileListIndex(u32 accessBlock,u32 accessSize,bool blockMode)303 int VirtualDiscFileSystem::getFileListIndex(u32 accessBlock, u32 accessSize, bool blockMode)
304 {
305 	for (size_t i = 0; i < fileList.size(); i++)
306 	{
307 		if (fileList[i].firstBlock <= accessBlock)
308 		{
309 			u32 sectorOffset = (accessBlock-fileList[i].firstBlock)*2048;
310 			u32 totalFileSize = blockMode ? (fileList[i].totalSize+2047) & ~2047 : fileList[i].totalSize;
311 
312 			u32 endOffset = sectorOffset+accessSize;
313 			if (endOffset <= totalFileSize)
314 			{
315 				return (int)i;
316 			}
317 		}
318 	}
319 
320 	return -1;
321 }
322 
OpenFile(std::string filename,FileAccess access,const char * devicename)323 int VirtualDiscFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename)
324 {
325 	OpenFileEntry entry(Flags());
326 	entry.curOffset = 0;
327 	entry.size = 0;
328 	entry.startOffset = 0;
329 
330 	if (filename == "")
331 	{
332 		entry.type = VFILETYPE_ISO;
333 		entry.fileIndex = -1;
334 
335 		u32 newHandle = hAlloc->GetNewHandle();
336 		entries[newHandle] = entry;
337 
338 		return newHandle;
339 	}
340 
341 	if (filename.compare(0,8,"/sce_lbn") == 0)
342 	{
343 		u32 sectorStart = 0xFFFFFFFF, readSize = 0xFFFFFFFF;
344 		parseLBN(filename, &sectorStart, &readSize);
345 
346 		entry.type = VFILETYPE_LBN;
347 		entry.size = readSize;
348 
349 		int fileIndex = getFileListIndex(sectorStart,readSize);
350 		if (fileIndex == -1)
351 		{
352 			ERROR_LOG(FILESYS, "VirtualDiscFileSystem: sce_lbn used without calling fileinfo.");
353 			return 0;
354 		}
355 		entry.fileIndex = (u32)fileIndex;
356 
357 		entry.startOffset = (sectorStart-fileList[entry.fileIndex].firstBlock)*2048;
358 
359 		// now we just need an actual file handle
360 		if (fileList[entry.fileIndex].handler != NULL) {
361 			entry.handler = fileList[entry.fileIndex].handler;
362 		}
363 		bool success = entry.Open(basePath, fileList[entry.fileIndex].fileName, FILEACCESS_READ);
364 
365 		if (!success) {
366 #ifdef _WIN32
367 			ERROR_LOG(FILESYS, "VirtualDiscFileSystem::OpenFile: FAILED, %i", (int)GetLastError());
368 #else
369 			ERROR_LOG(FILESYS, "VirtualDiscFileSystem::OpenFile: FAILED");
370 #endif
371 			return 0;
372 		}
373 
374 		// seek to start
375 		entry.Seek(entry.startOffset, FILEMOVE_BEGIN);
376 
377 		u32 newHandle = hAlloc->GetNewHandle();
378 		entries[newHandle] = entry;
379 
380 		return newHandle;
381 	}
382 
383 	entry.type = VFILETYPE_NORMAL;
384 	entry.fileIndex = getFileListIndex(filename);
385 
386 	if (entry.fileIndex != (u32)-1 && fileList[entry.fileIndex].handler != NULL) {
387 		entry.handler = fileList[entry.fileIndex].handler;
388 	}
389 	bool success = entry.Open(basePath, filename, access);
390 
391 	if (!success) {
392 #ifdef _WIN32
393 		ERROR_LOG(FILESYS, "VirtualDiscFileSystem::OpenFile: FAILED, %i - access = %i", (int)GetLastError(), (int)access);
394 #else
395 		ERROR_LOG(FILESYS, "VirtualDiscFileSystem::OpenFile: FAILED, access = %i", (int)access);
396 #endif
397 		//wwwwaaaaahh!!
398 		return SCE_KERNEL_ERROR_ERRNO_FILE_NOT_FOUND;
399 	} else {
400 		u32 newHandle = hAlloc->GetNewHandle();
401 		entries[newHandle] = entry;
402 
403 		return newHandle;
404 	}
405 }
406 
SeekFile(u32 handle,s32 position,FileMove type)407 size_t VirtualDiscFileSystem::SeekFile(u32 handle, s32 position, FileMove type) {
408 	EntryMap::iterator iter = entries.find(handle);
409 	if (iter != entries.end()) {
410 		auto &entry = iter->second;
411 		switch (entry.type)
412 		{
413 		case VFILETYPE_NORMAL:
414 			{
415 				return entry.Seek(position, type);
416 			}
417 		case VFILETYPE_LBN:
418 			{
419 				switch (type)
420 				{
421 				case FILEMOVE_BEGIN:    entry.curOffset = position;                     break;
422 				case FILEMOVE_CURRENT:  entry.curOffset += position;                    break;
423 				case FILEMOVE_END:      entry.curOffset = entry.size + position;        break;
424 				}
425 
426 				u32 off = entry.startOffset + entry.curOffset;
427 				entry.Seek(off, FILEMOVE_BEGIN);
428 				return entry.curOffset;
429 			}
430 		case VFILETYPE_ISO:
431 			{
432 				switch (type)
433 				{
434 				case FILEMOVE_BEGIN:    entry.curOffset = position;                     break;
435 				case FILEMOVE_CURRENT:  entry.curOffset += position;                    break;
436 				case FILEMOVE_END:      entry.curOffset = currentBlockIndex + position; break;
437 				}
438 
439 				return entry.curOffset;
440 			}
441 		}
442 		return 0;
443 	} else {
444 		//This shouldn't happen...
445 		ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot seek in file that hasn't been opened: %08x", handle);
446 		return 0;
447 	}
448 }
449 
ReadFile(u32 handle,u8 * pointer,s64 size)450 size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) {
451 	int ignored;
452 	return ReadFile(handle, pointer, size, ignored);
453 }
454 
ReadFile(u32 handle,u8 * pointer,s64 size,int & usec)455 size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {
456 	EntryMap::iterator iter = entries.find(handle);
457 	if (iter != entries.end()) {
458 		if (size < 0) {
459 			ERROR_LOG_REPORT(FILESYS, "Invalid read for %lld bytes from virtual umd", size);
460 			return 0;
461 		}
462 
463 		// it's the whole iso... it could reference any of the files on the disc.
464 		// For now let's just open and close the files on demand. Can certainly be done
465 		// better though
466 		if (iter->second.type == VFILETYPE_ISO)
467 		{
468 			int fileIndex = getFileListIndex(iter->second.curOffset,size*2048,true);
469 			if (fileIndex == -1)
470 			{
471 				ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Reading from unknown address in %08x at %08llx", handle, iter->second.curOffset);
472 				return 0;
473 			}
474 
475 			OpenFileEntry temp(Flags());
476 			if (fileList[fileIndex].handler != NULL) {
477 				temp.handler = fileList[fileIndex].handler;
478 			}
479 			bool success = temp.Open(basePath, fileList[fileIndex].fileName, FILEACCESS_READ);
480 
481 			if (!success)
482 			{
483 				ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Error opening file %s", fileList[fileIndex].fileName.c_str());
484 				return 0;
485 			}
486 
487 			u32 startOffset = (iter->second.curOffset-fileList[fileIndex].firstBlock)*2048;
488 			size_t bytesRead;
489 
490 			temp.Seek(startOffset, FILEMOVE_BEGIN);
491 
492 			u32 remainingSize = fileList[fileIndex].totalSize-startOffset;
493 			if (remainingSize < size * 2048)
494 			{
495 				// the file doesn't fill the whole last sector
496 				// read what's there and zero fill the rest like on a real disc
497 				bytesRead = temp.Read(pointer, remainingSize);
498 				memset(&pointer[bytesRead], 0, size * 2048 - bytesRead);
499 			} else {
500 				bytesRead = temp.Read(pointer, size * 2048);
501 			}
502 
503 			temp.Close();
504 
505 			iter->second.curOffset += size;
506 			// TODO: This probably isn't enough...
507 			if (abs((int)lastReadBlock_ - (int)iter->second.curOffset) > 100) {
508 				// This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time.
509 				usec = 100000;
510 			}
511 			lastReadBlock_ = iter->second.curOffset;
512 			return size;
513 		}
514 
515 		if (iter->second.type == VFILETYPE_LBN && iter->second.curOffset + size > iter->second.size) {
516 			// Clamp to the remaining size, but read what we can.
517 			const s64 newSize = iter->second.size - iter->second.curOffset;
518 			WARN_LOG(FILESYS, "VirtualDiscFileSystem: Reading beyond end of file, clamping size %lld to %lld", size, newSize);
519 			size = newSize;
520 		}
521 
522 		size_t bytesRead = iter->second.Read(pointer, size);
523 		iter->second.curOffset += bytesRead;
524 		return bytesRead;
525 	} else {
526 		//This shouldn't happen...
527 		ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot read file that hasn't been opened: %08x", handle);
528 		return 0;
529 	}
530 }
531 
CloseFile(u32 handle)532 void VirtualDiscFileSystem::CloseFile(u32 handle) {
533 	EntryMap::iterator iter = entries.find(handle);
534 	if (iter != entries.end()) {
535 		hAlloc->FreeHandle(handle);
536 		iter->second.Close();
537 		entries.erase(iter);
538 	} else {
539 		//This shouldn't happen...
540 		ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot close file that hasn't been opened: %08x", handle);
541 	}
542 }
543 
OwnsHandle(u32 handle)544 bool VirtualDiscFileSystem::OwnsHandle(u32 handle) {
545 	EntryMap::iterator iter = entries.find(handle);
546 	return (iter != entries.end());
547 }
548 
Ioctl(u32 handle,u32 cmd,u32 indataPtr,u32 inlen,u32 outdataPtr,u32 outlen,int & usec)549 int VirtualDiscFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) {
550 	// TODO: How to support these?
551 	return SCE_KERNEL_ERROR_ERRNO_FUNCTION_NOT_SUPPORTED;
552 }
553 
DevType(u32 handle)554 PSPDevType VirtualDiscFileSystem::DevType(u32 handle) {
555 	EntryMap::iterator iter = entries.find(handle);
556 	if (iter == entries.end())
557 		return PSPDevType::FILE;
558 	PSPDevType type = iter->second.type == VFILETYPE_ISO ? PSPDevType::BLOCK : PSPDevType::FILE;
559 	if (iter->second.type == VFILETYPE_LBN)
560 		type |= PSPDevType::EMU_LBN;
561 	return type;
562 }
563 
GetFileInfo(std::string filename)564 PSPFileInfo VirtualDiscFileSystem::GetFileInfo(std::string filename) {
565 	PSPFileInfo x;
566 	x.name = filename;
567 	x.access = FILEACCESS_READ;
568 
569 	if (filename.compare(0,8,"/sce_lbn") == 0) {
570 		u32 sectorStart = 0xFFFFFFFF, readSize = 0xFFFFFFFF;
571 		parseLBN(filename, &sectorStart, &readSize);
572 
573 		PSPFileInfo fileInfo;
574 		fileInfo.name = filename;
575 		fileInfo.exists = true;
576 		fileInfo.type = FILETYPE_NORMAL;
577 		fileInfo.size = readSize;
578 		fileInfo.access = 0444;
579 		fileInfo.startSector = sectorStart;
580 		fileInfo.isOnSectorSystem = true;
581 		fileInfo.numSectors = (readSize + 2047) / 2048;
582 		return fileInfo;
583 	}
584 
585 	int fileIndex = getFileListIndex(filename);
586 	if (fileIndex != -1 && fileList[fileIndex].handler != NULL) {
587 		x.type = FILETYPE_NORMAL;
588 		x.isOnSectorSystem = true;
589 		x.startSector = fileList[fileIndex].firstBlock;
590 		x.access = 0555;
591 
592 		HandlerFileHandle temp = fileList[fileIndex].handler;
593 		if (temp.Open(basePath.ToString(), filename, FILEACCESS_READ)) {
594 			x.exists = true;
595 			x.size = temp.Seek(0, FILEMOVE_END);
596 			temp.Close();
597 		}
598 
599 		// TODO: Probably should include dates or something...
600 		return x;
601 	}
602 
603 	Path fullName = GetLocalPath(filename);
604 	if (!File::Exists(fullName)) {
605 #if HOST_IS_CASE_SENSITIVE
606 		if (! FixPathCase(basePath, filename, FPC_FILE_MUST_EXIST))
607 			return x;
608 		fullName = GetLocalPath(filename);
609 
610 		if (! File::Exists(fullName))
611 			return x;
612 #else
613 		return x;
614 #endif
615 	}
616 
617 	x.type = File::IsDirectory(fullName) ? FILETYPE_DIRECTORY : FILETYPE_NORMAL;
618 	x.exists = true;
619 	x.access = 0555;
620 	if (fileIndex != -1) {
621 		x.isOnSectorSystem = true;
622 		x.startSector = fileList[fileIndex].firstBlock;
623 	}
624 
625 	if (x.type != FILETYPE_DIRECTORY) {
626 		File::FileInfo details;
627 		if (!File::GetFileInfo(fullName, &details)) {
628 			ERROR_LOG(FILESYS, "DirectoryFileSystem::GetFileInfo: GetFileInfo failed: %s", fullName.c_str());
629 			x.size = 0;
630 			x.access = 0;
631 		} else {
632 			x.size = details.size;
633 			time_t atime = details.atime;
634 			time_t ctime = details.ctime;
635 			time_t mtime = details.mtime;
636 
637 			localtime_r((time_t*)&atime, &x.atime);
638 			localtime_r((time_t*)&ctime, &x.ctime);
639 			localtime_r((time_t*)&mtime, &x.mtime);
640 		}
641 
642 		x.startSector = fileList[fileIndex].firstBlock;
643 		x.numSectors = (x.size+2047)/2048;
644 	}
645 
646 	return x;
647 }
648 
649 #ifdef _WIN32
650 #define FILETIME_FROM_UNIX_EPOCH_US 11644473600000000ULL
651 
tmFromFiletime(tm & dest,FILETIME & src)652 static void tmFromFiletime(tm &dest, FILETIME &src)
653 {
654 	u64 from_1601_us = (((u64) src.dwHighDateTime << 32ULL) + (u64) src.dwLowDateTime) / 10ULL;
655 	u64 from_1970_us = from_1601_us - FILETIME_FROM_UNIX_EPOCH_US;
656 
657 	time_t t = (time_t) (from_1970_us / 1000000UL);
658 	localtime_r(&t, &dest);
659 }
660 #endif
661 
GetDirListing(std::string path)662 std::vector<PSPFileInfo> VirtualDiscFileSystem::GetDirListing(std::string path)
663 {
664 	std::vector<PSPFileInfo> myVector;
665 
666 	// TODO(scoped): Switch this over to GetFilesInDir!
667 
668 #ifdef _WIN32
669 	WIN32_FIND_DATA findData;
670 	HANDLE hFind;
671 
672 	// TODO: Handler files that are virtual might not be listed.
673 
674 	std::wstring w32path = GetLocalPath(path).ToWString() + L"\\*.*";
675 
676 	hFind = FindFirstFileEx(w32path.c_str(), FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, 0);
677 
678 	if (hFind == INVALID_HANDLE_VALUE) {
679 		return myVector; //the empty list
680 	}
681 
682 	for (BOOL retval = 1; retval; retval = FindNextFile(hFind, &findData)) {
683 		if (!wcscmp(findData.cFileName, L"..") || !wcscmp(findData.cFileName, L".")) {
684 			continue;
685 		}
686 
687 		PSPFileInfo entry;
688 		if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
689 			entry.type = FILETYPE_DIRECTORY;
690 		} else {
691 			entry.type = FILETYPE_NORMAL;
692 		}
693 
694 		entry.access = 0555;
695 		entry.size = findData.nFileSizeLow | ((u64)findData.nFileSizeHigh<<32);
696 		entry.name = ConvertWStringToUTF8(findData.cFileName);
697 		tmFromFiletime(entry.atime, findData.ftLastAccessTime);
698 		tmFromFiletime(entry.ctime, findData.ftCreationTime);
699 		tmFromFiletime(entry.mtime, findData.ftLastWriteTime);
700 		entry.isOnSectorSystem = true;
701 
702 		std::string fullRelativePath = path + "/" + entry.name;
703 		int fileIndex = getFileListIndex(fullRelativePath);
704 		if (fileIndex != -1)
705 			entry.startSector = fileList[fileIndex].firstBlock;
706 		myVector.push_back(entry);
707 	}
708 	FindClose(hFind);
709 #else
710 	dirent *dirp;
711 	Path localPath = GetLocalPath(path);
712 	DIR *dp = opendir(localPath.c_str());
713 
714 #if HOST_IS_CASE_SENSITIVE
715 	if(dp == NULL && FixPathCase(basePath, path, FPC_FILE_MUST_EXIST)) {
716 		// May have failed due to case sensitivity, try again
717 		localPath = GetLocalPath(path);
718 		dp = opendir(localPath.c_str());
719 	}
720 #endif
721 
722 	if (dp == NULL) {
723 		ERROR_LOG(FILESYS,"Error opening directory %s\n", path.c_str());
724 		return myVector;
725 	}
726 
727 	while ((dirp = readdir(dp)) != NULL) {
728 		if (!strcmp(dirp->d_name, "..") || !strcmp(dirp->d_name, ".")) {
729 			continue;
730 		}
731 
732 		PSPFileInfo entry;
733 		struct stat s;
734 		std::string fullName = (GetLocalPath(path) / std::string(dirp->d_name)).ToString();
735 		stat(fullName.c_str(), &s);
736 		if (S_ISDIR(s.st_mode))
737 			entry.type = FILETYPE_DIRECTORY;
738 		else
739 			entry.type = FILETYPE_NORMAL;
740 		entry.access = 0555;
741 		entry.name = dirp->d_name;
742 		entry.size = s.st_size;
743 		localtime_r((time_t*)&s.st_atime,&entry.atime);
744 		localtime_r((time_t*)&s.st_ctime,&entry.ctime);
745 		localtime_r((time_t*)&s.st_mtime,&entry.mtime);
746 		entry.isOnSectorSystem = true;
747 
748 		std::string fullRelativePath = path + "/" + entry.name;
749 		int fileIndex = getFileListIndex(fullRelativePath);
750 		if (fileIndex != -1)
751 			entry.startSector = fileList[fileIndex].firstBlock;
752 		myVector.push_back(entry);
753 	}
754 	closedir(dp);
755 #endif
756 	return myVector;
757 }
758 
WriteFile(u32 handle,const u8 * pointer,s64 size)759 size_t VirtualDiscFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size)
760 {
761 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot write to file on virtual disc");
762 	return 0;
763 }
764 
WriteFile(u32 handle,const u8 * pointer,s64 size,int & usec)765 size_t VirtualDiscFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec)
766 {
767 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot write to file on virtual disc");
768 	return 0;
769 }
770 
MkDir(const std::string & dirname)771 bool VirtualDiscFileSystem::MkDir(const std::string &dirname)
772 {
773 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot create directory on virtual disc");
774 	return false;
775 }
776 
RmDir(const std::string & dirname)777 bool VirtualDiscFileSystem::RmDir(const std::string &dirname)
778 {
779 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot remove directory on virtual disc");
780 	return false;
781 }
782 
RenameFile(const std::string & from,const std::string & to)783 int VirtualDiscFileSystem::RenameFile(const std::string &from, const std::string &to)
784 {
785 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot rename file on virtual disc");
786 	return -1;
787 }
788 
RemoveFile(const std::string & filename)789 bool VirtualDiscFileSystem::RemoveFile(const std::string &filename)
790 {
791 	ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot remove file on virtual disc");
792 	return false;
793 }
794 
HandlerLogger(void * arg,HandlerHandle handle,LogTypes::LOG_LEVELS level,const char * msg)795 void VirtualDiscFileSystem::HandlerLogger(void *arg, HandlerHandle handle, LogTypes::LOG_LEVELS level, const char *msg) {
796 	VirtualDiscFileSystem *sys = static_cast<VirtualDiscFileSystem *>(arg);
797 
798 	// TODO: Probably could do this smarter / use a lookup.
799 	const char *filename = NULL;
800 	for (auto it = sys->entries.begin(), end = sys->entries.end(); it != end; ++it) {
801 		if (it->second.fileIndex != (u32)-1 && it->second.handler.handle == handle) {
802 			filename = sys->fileList[it->second.fileIndex].fileName.c_str();
803 			break;
804 		}
805 	}
806 
807 	if (filename != NULL) {
808 		GENERIC_LOG(LogTypes::FILESYS, level, "%s: %s", filename, msg);
809 	} else {
810 		GENERIC_LOG(LogTypes::FILESYS, level, "%s", msg);
811 	}
812 }
813 
Handler(const char * filename,VirtualDiscFileSystem * const sys)814 VirtualDiscFileSystem::Handler::Handler(const char *filename, VirtualDiscFileSystem *const sys) {
815 #if !PPSSPP_PLATFORM(SWITCH)
816 #ifdef _WIN32
817 #if PPSSPP_PLATFORM(UWP)
818 #define dlopen(name, ignore) (void *)LoadPackagedLibrary(ConvertUTF8ToWString(name).c_str(), 0)
819 #define dlsym(mod, name) GetProcAddress((HMODULE)mod, name)
820 #define dlclose(mod) FreeLibrary((HMODULE)mod)
821 #else
822 #define dlopen(name, ignore) (void *)LoadLibrary(ConvertUTF8ToWString(name).c_str())
823 #define dlsym(mod, name) GetProcAddress((HMODULE)mod, name)
824 #define dlclose(mod) FreeLibrary((HMODULE)mod)
825 #endif
826 #endif
827 
828 	library = dlopen(filename, RTLD_LOCAL | RTLD_NOW);
829 	if (library != NULL) {
830 		Init = (InitFunc)dlsym(library, "Init");
831 		Shutdown = (ShutdownFunc)dlsym(library, "Shutdown");
832 		Open = (OpenFunc)dlsym(library, "Open");
833 		Seek = (SeekFunc)dlsym(library, "Seek");
834 		Read = (ReadFunc)dlsym(library, "Read");
835 		Close = (CloseFunc)dlsym(library, "Close");
836 
837 		if (Init == NULL || Shutdown == NULL || Open == NULL || Seek == NULL || Read == NULL || Close == NULL) {
838 			ERROR_LOG(FILESYS, "Unable to find all handler functions: %s", filename);
839 			dlclose(library);
840 			library = NULL;
841 		} else if (!Init(&HandlerLogger, sys)) {
842 			ERROR_LOG(FILESYS, "Unable to initialize handler: %s", filename);
843 			dlclose(library);
844 			library = NULL;
845 		}
846 	} else {
847 		ERROR_LOG(FILESYS, "Unable to load handler: %s", filename);
848 	}
849 #ifdef _WIN32
850 #undef dlopen
851 #undef dlsym
852 #undef dlclose
853 #endif
854 #endif
855 }
856 
~Handler()857 VirtualDiscFileSystem::Handler::~Handler() {
858 	if (library != NULL) {
859 		Shutdown();
860 
861 #if !PPSSPP_PLATFORM(UWP) && !PPSSPP_PLATFORM(SWITCH)
862 #ifdef _WIN32
863 		FreeLibrary((HMODULE)library);
864 #else
865 		dlclose(library);
866 #endif
867 #endif
868 	}
869 }
870 
871