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 _WIN32
21 #pragma warning(disable:4091)
22 #include "Common/CommonWindows.h"
23 #include <ShlObj.h>
24 #include <string>
25 #include <codecvt>
26 #if !PPSSPP_PLATFORM(UWP)
27 #include "Windows/W32Util/ShellUtil.h"
28 #endif
29 #endif
30 
31 #include <thread>
32 #include <mutex>
33 #include <condition_variable>
34 
35 #include "Common/System/System.h"
36 #include "Common/File/Path.h"
37 #include "Common/Math/math_util.h"
38 #include "Common/Thread/ThreadUtil.h"
39 #include "Common/Data/Encoding/Utf8.h"
40 
41 #include "Common/File/FileUtil.h"
42 #include "Common/TimeUtil.h"
43 #include "Common/GraphicsContext.h"
44 #include "Core/MemFault.h"
45 #include "Core/HDRemaster.h"
46 #include "Core/MIPS/MIPS.h"
47 #include "Core/MIPS/MIPSAnalyst.h"
48 #include "Core/MIPS/MIPSVFPUUtils.h"
49 #include "Core/Debugger/SymbolMap.h"
50 #include "Core/Host.h"
51 #include "Core/System.h"
52 #include "Core/HLE/HLE.h"
53 #include "Core/HLE/Plugins.h"
54 #include "Core/HLE/ReplaceTables.h"
55 #include "Core/HLE/sceKernel.h"
56 #include "Core/HLE/sceKernelMemory.h"
57 #include "Core/HLE/sceAudio.h"
58 #include "Core/Config.h"
59 #include "Core/Core.h"
60 #include "Core/CoreTiming.h"
61 #include "Core/CoreParameter.h"
62 #include "Core/FileLoaders/RamCachingFileLoader.h"
63 #include "Core/FileSystems/MetaFileSystem.h"
64 #include "Core/Loaders.h"
65 #include "Core/PSPLoaders.h"
66 #include "Core/ELF/ParamSFO.h"
67 #include "Core/SaveState.h"
68 #include "Common/LogManager.h"
69 #include "Common/ExceptionHandlerSetup.h"
70 #include "Core/HLE/sceAudiocodec.h"
71 #include "GPU/GPUState.h"
72 #include "GPU/GPUInterface.h"
73 #include "GPU/Debugger/RecordFormat.h"
74 
75 enum CPUThreadState {
76 	CPU_THREAD_NOT_RUNNING,
77 	CPU_THREAD_PENDING,
78 	CPU_THREAD_STARTING,
79 	CPU_THREAD_RUNNING,
80 	CPU_THREAD_SHUTDOWN,
81 	CPU_THREAD_QUIT,
82 
83 	CPU_THREAD_EXECUTE,
84 	CPU_THREAD_RESUME,
85 };
86 
87 MetaFileSystem pspFileSystem;
88 ParamSFOData g_paramSFO;
89 static GlobalUIState globalUIState;
90 static CoreParameter coreParameter;
91 static FileLoader *loadedFile;
92 // For background loading thread.
93 static std::mutex loadingLock;
94 // For loadingReason updates.
95 static std::mutex loadingReasonLock;
96 static std::string loadingReason;
97 
98 bool audioInitialized;
99 
100 bool coreCollectDebugStats = false;
101 bool coreCollectDebugStatsForced = false;
102 
103 // This can be read and written from ANYWHERE.
104 volatile CoreState coreState = CORE_STEPPING;
105 // If true, core state has been changed, but JIT has probably not noticed yet.
106 volatile bool coreStatePending = false;
107 
108 static volatile CPUThreadState cpuThreadState = CPU_THREAD_NOT_RUNNING;
109 
110 static GPUBackend gpuBackend;
111 static std::string gpuBackendDevice;
112 
113 // Ugly!
114 static volatile bool pspIsInited = false;
115 static volatile bool pspIsIniting = false;
116 static volatile bool pspIsQuitting = false;
117 
ResetUIState()118 void ResetUIState() {
119 	globalUIState = UISTATE_MENU;
120 }
121 
UpdateUIState(GlobalUIState newState)122 void UpdateUIState(GlobalUIState newState) {
123 	// Never leave the EXIT state.
124 	if (globalUIState != newState && globalUIState != UISTATE_EXIT) {
125 		globalUIState = newState;
126 		if (host)
127 			host->UpdateDisassembly();
128 		const char *state = nullptr;
129 		switch (globalUIState) {
130 		case UISTATE_EXIT: state = "exit";  break;
131 		case UISTATE_INGAME: state = "ingame"; break;
132 		case UISTATE_MENU: state = "menu"; break;
133 		case UISTATE_PAUSEMENU: state = "pausemenu"; break;
134 		case UISTATE_EXCEPTION: state = "exception"; break;
135 		}
136 		if (state) {
137 			System_SendMessage("uistate", state);
138 		}
139 	}
140 }
141 
GetUIState()142 GlobalUIState GetUIState() {
143 	return globalUIState;
144 }
145 
SetGPUBackend(GPUBackend type,const std::string & device)146 void SetGPUBackend(GPUBackend type, const std::string &device) {
147 	gpuBackend = type;
148 	gpuBackendDevice = device;
149 }
150 
GetGPUBackend()151 GPUBackend GetGPUBackend() {
152 	return gpuBackend;
153 }
154 
GetGPUBackendDevice()155 std::string GetGPUBackendDevice() {
156 	return gpuBackendDevice;
157 }
158 
IsAudioInitialised()159 bool IsAudioInitialised() {
160 	return audioInitialized;
161 }
162 
Audio_Init()163 void Audio_Init() {
164 	if (!audioInitialized) {
165 		audioInitialized = true;
166 		host->InitSound();
167 	}
168 }
169 
Audio_Shutdown()170 void Audio_Shutdown() {
171 	if (audioInitialized) {
172 		audioInitialized = false;
173 		host->ShutdownSound();
174 	}
175 }
176 
CPU_IsReady()177 bool CPU_IsReady() {
178 	if (coreState == CORE_POWERUP)
179 		return false;
180 	return cpuThreadState == CPU_THREAD_RUNNING || cpuThreadState == CPU_THREAD_NOT_RUNNING;
181 }
182 
CPU_IsShutdown()183 bool CPU_IsShutdown() {
184 	return cpuThreadState == CPU_THREAD_NOT_RUNNING;
185 }
186 
CPU_HasPendingAction()187 bool CPU_HasPendingAction() {
188 	return cpuThreadState != CPU_THREAD_RUNNING;
189 }
190 
191 void CPU_Shutdown();
192 
DiscIDFromGEDumpPath(const Path & path,FileLoader * fileLoader,std::string * id)193 bool DiscIDFromGEDumpPath(const Path &path, FileLoader *fileLoader, std::string *id) {
194 	using namespace GPURecord;
195 
196 	// For newer files, it's stored in the dump.
197 	Header header;
198 	if (fileLoader->ReadAt(0, sizeof(header), &header) == sizeof(header)) {
199 		const bool magicMatch = memcmp(header.magic, HEADER_MAGIC, sizeof(header.magic)) == 0;
200 		if (magicMatch && header.version <= VERSION && header.version >= 4) {
201 			size_t gameIDLength = strnlen(header.gameID, sizeof(header.gameID));
202 			if (gameIDLength != 0) {
203 				*id = std::string(header.gameID, gameIDLength);
204 				return true;
205 			}
206 		}
207 	}
208 
209 	// Fall back to using the filename.
210 	std::string filename = path.GetFilename();
211 	// Could be more discerning, but hey..
212 	if (filename.size() > 10 && filename[0] == 'U' && filename[9] == '_') {
213 		*id = filename.substr(0, 9);
214 		return true;
215 	} else {
216 		return false;
217 	}
218 }
219 
CPU_Init(std::string * errorString)220 bool CPU_Init(std::string *errorString) {
221 	coreState = CORE_POWERUP;
222 	currentMIPS = &mipsr4k;
223 
224 	g_symbolMap = new SymbolMap();
225 
226 	// Default memory settings
227 	// Seems to be the safest place currently..
228 	Memory::g_MemorySize = Memory::RAM_NORMAL_SIZE; // 32 MB of ram by default
229 
230 	g_RemasterMode = false;
231 	g_DoubleTextureCoordinates = false;
232 	Memory::g_PSPModel = g_Config.iPSPModel;
233 
234 	Path filename = coreParameter.fileToStart;
235 	loadedFile = ResolveFileLoaderTarget(ConstructFileLoader(filename));
236 #if PPSSPP_ARCH(AMD64)
237 	if (g_Config.bCacheFullIsoInRam) {
238 		loadedFile = new RamCachingFileLoader(loadedFile);
239 	}
240 #endif
241 
242 	IdentifiedFileType type = Identify_File(loadedFile, errorString);
243 
244 	// TODO: Put this somewhere better?
245 	if (!coreParameter.mountIso.empty()) {
246 		coreParameter.mountIsoLoader = ConstructFileLoader(coreParameter.mountIso);
247 	}
248 
249 	MIPSAnalyst::Reset();
250 	Replacement_Init();
251 
252 	bool allowPlugins = true;
253 	std::string geDumpDiscID;
254 
255 	switch (type) {
256 	case IdentifiedFileType::PSP_ISO:
257 	case IdentifiedFileType::PSP_ISO_NP:
258 	case IdentifiedFileType::PSP_DISC_DIRECTORY:
259 		InitMemoryForGameISO(loadedFile);
260 		break;
261 	case IdentifiedFileType::PSP_PBP:
262 	case IdentifiedFileType::PSP_PBP_DIRECTORY:
263 		// This is normal for homebrew.
264 		// ERROR_LOG(LOADER, "PBP directory resolution failed.");
265 		InitMemoryForGamePBP(loadedFile);
266 		break;
267 	case IdentifiedFileType::PSP_ELF:
268 		if (Memory::g_PSPModel != PSP_MODEL_FAT) {
269 			INFO_LOG(LOADER, "ELF, using full PSP-2000 memory access");
270 			Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;
271 		}
272 		break;
273 	case IdentifiedFileType::PPSSPP_GE_DUMP:
274 		// Try to grab the disc ID from the filenameor GE dump.
275 		if (DiscIDFromGEDumpPath(filename, loadedFile, &geDumpDiscID)) {
276 			// Store in SFO, otherwise it'll generate a fake disc ID.
277 			g_paramSFO.SetValue("DISC_ID", geDumpDiscID, 16);
278 		}
279 		allowPlugins = false;
280 		break;
281 	default:
282 		// Can we even get here?
283 		WARN_LOG(LOADER, "CPU_Init didn't recognize file. %s", errorString->c_str());
284 		break;
285 	}
286 
287 	// Here we have read the PARAM.SFO, let's see if we need any compatibility overrides.
288 	// Homebrew usually has an empty discID, and even if they do have a disc id, it's not
289 	// likely to collide with any commercial ones.
290 	coreParameter.compat.Load(g_paramSFO.GetDiscID());
291 
292 	InitVFPUSinCos();
293 
294 	if (allowPlugins)
295 		HLEPlugins::Init();
296 	if (!Memory::Init()) {
297 		// We're screwed.
298 		return false;
299 	}
300 	mipsr4k.Reset();
301 
302 	host->AttemptLoadSymbolMap();
303 
304 	if (coreParameter.enableSound) {
305 		Audio_Init();
306 	}
307 
308 	CoreTiming::Init();
309 
310 	// Init all the HLE modules
311 	HLEInit();
312 
313 	// TODO: Check Game INI here for settings, patches and cheats, and modify coreParameter accordingly
314 
315 	// If they shut down early, we'll catch it when load completes.
316 	// Note: this may return before init is complete, which is checked if CPU_IsReady().
317 	if (!LoadFile(&loadedFile, &coreParameter.errorString)) {
318 		CPU_Shutdown();
319 		coreParameter.fileToStart.clear();
320 		return false;
321 	}
322 
323 	if (coreParameter.updateRecent) {
324 		g_Config.AddRecent(filename.ToString());
325 	}
326 
327 	InstallExceptionHandler(&Memory::HandleFault);
328 	return true;
329 }
330 
PSP_LoadingLock()331 PSP_LoadingLock::PSP_LoadingLock() {
332 	loadingLock.lock();
333 }
334 
~PSP_LoadingLock()335 PSP_LoadingLock::~PSP_LoadingLock() {
336 	loadingLock.unlock();
337 }
338 
CPU_Shutdown()339 void CPU_Shutdown() {
340 	UninstallExceptionHandler();
341 
342 	// Since we load on a background thread, wait for startup to complete.
343 	PSP_LoadingLock lock;
344 	PSPLoaders_Shutdown();
345 
346 	if (g_Config.bAutoSaveSymbolMap) {
347 		host->SaveSymbolMap();
348 	}
349 
350 	Replacement_Shutdown();
351 
352 	CoreTiming::Shutdown();
353 	__KernelShutdown();
354 	HLEShutdown();
355 	if (coreParameter.enableSound) {
356 		Audio_Shutdown();
357 	}
358 
359 	pspFileSystem.Shutdown();
360 	mipsr4k.Shutdown();
361 	Memory::Shutdown();
362 	HLEPlugins::Shutdown();
363 
364 	delete loadedFile;
365 	loadedFile = nullptr;
366 
367 	delete coreParameter.mountIsoLoader;
368 	delete g_symbolMap;
369 	g_symbolMap = nullptr;
370 
371 	coreParameter.mountIsoLoader = nullptr;
372 }
373 
374 // TODO: Maybe loadedFile doesn't even belong here...
UpdateLoadedFile(FileLoader * fileLoader)375 void UpdateLoadedFile(FileLoader *fileLoader) {
376 	delete loadedFile;
377 	loadedFile = fileLoader;
378 }
379 
Core_UpdateState(CoreState newState)380 void Core_UpdateState(CoreState newState) {
381 	if ((coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME) && newState != CORE_RUNNING)
382 		coreStatePending = true;
383 	coreState = newState;
384 	Core_UpdateSingleStep();
385 }
386 
Core_UpdateDebugStats(bool collectStats)387 void Core_UpdateDebugStats(bool collectStats) {
388 	if (coreCollectDebugStats != collectStats) {
389 		coreCollectDebugStats = collectStats;
390 		mipsr4k.ClearJitCache();
391 	}
392 
393 	kernelStats.ResetFrame();
394 	gpuStats.ResetFrame();
395 }
396 
PSP_InitStart(const CoreParameter & coreParam,std::string * error_string)397 bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
398 	if (pspIsIniting || pspIsQuitting) {
399 		return false;
400 	}
401 
402 #if defined(_WIN32) && PPSSPP_ARCH(AMD64)
403 	INFO_LOG(BOOT, "PPSSPP %s Windows 64 bit", PPSSPP_GIT_VERSION);
404 #elif defined(_WIN32) && !PPSSPP_ARCH(AMD64)
405 	INFO_LOG(BOOT, "PPSSPP %s Windows 32 bit", PPSSPP_GIT_VERSION);
406 #else
407 	INFO_LOG(BOOT, "PPSSPP %s", PPSSPP_GIT_VERSION);
408 #endif
409 
410 	Core_NotifyLifecycle(CoreLifecycle::STARTING);
411 	GraphicsContext *temp = coreParameter.graphicsContext;
412 	coreParameter = coreParam;
413 	if (coreParameter.graphicsContext == nullptr) {
414 		coreParameter.graphicsContext = temp;
415 	}
416 	coreParameter.errorString = "";
417 	pspIsIniting = true;
418 	PSP_SetLoading("Loading game...");
419 
420 	if (!CPU_Init(&coreParameter.errorString)) {
421 		*error_string = coreParameter.errorString;
422 		if (error_string->empty()) {
423 			*error_string = "Failed initializing CPU/Memory";
424 		}
425 		pspIsIniting = false;
426 		return false;
427 	}
428 
429 	// Compat flags get loaded in CPU_Init (which is a bit of a misnomer) so we check for SW renderer here.
430 	if (g_Config.bSoftwareRendering || PSP_CoreParameter().compat.flags().ForceSoftwareRenderer) {
431 		coreParameter.gpuCore = GPUCORE_SOFTWARE;
432 	}
433 
434 	*error_string = coreParameter.errorString;
435 	bool success = !coreParameter.fileToStart.empty();
436 	if (!success) {
437 		Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE);
438 		pspIsIniting = false;
439 	}
440 	return success;
441 }
442 
PSP_InitUpdate(std::string * error_string)443 bool PSP_InitUpdate(std::string *error_string) {
444 	if (pspIsInited || !pspIsIniting) {
445 		return true;
446 	}
447 
448 	if (!CPU_IsReady()) {
449 		return false;
450 	}
451 
452 	bool success = !coreParameter.fileToStart.empty();
453 	*error_string = coreParameter.errorString;
454 	if (success && gpu == nullptr) {
455 		PSP_SetLoading("Starting graphics...");
456 		Draw::DrawContext *draw = coreParameter.graphicsContext ? coreParameter.graphicsContext->GetDrawContext() : nullptr;
457 		success = GPU_Init(coreParameter.graphicsContext, draw);
458 		if (!success) {
459 			*error_string = "Unable to initialize rendering engine.";
460 		}
461 	}
462 	if (!success) {
463 		PSP_Shutdown();
464 		return true;
465 	}
466 
467 	pspIsInited = GPU_IsReady();
468 	pspIsIniting = !pspIsInited;
469 	if (pspIsInited) {
470 		Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE);
471 	}
472 	return pspIsInited;
473 }
474 
PSP_Init(const CoreParameter & coreParam,std::string * error_string)475 bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) {
476 	if (!PSP_InitStart(coreParam, error_string))
477 		return false;
478 
479 	while (!PSP_InitUpdate(error_string))
480 		sleep_ms(10);
481 	return pspIsInited;
482 }
483 
PSP_IsIniting()484 bool PSP_IsIniting() {
485 	return pspIsIniting;
486 }
487 
PSP_IsInited()488 bool PSP_IsInited() {
489 	return pspIsInited && !pspIsQuitting;
490 }
491 
PSP_IsQuitting()492 bool PSP_IsQuitting() {
493 	return pspIsQuitting;
494 }
495 
PSP_Shutdown()496 void PSP_Shutdown() {
497 	// Do nothing if we never inited.
498 	if (!pspIsInited && !pspIsIniting && !pspIsQuitting) {
499 		return;
500 	}
501 
502 	// Make sure things know right away that PSP memory, etc. is going away.
503 	pspIsQuitting = true;
504 	if (coreState == CORE_RUNNING)
505 		Core_Stop();
506 
507 #ifndef MOBILE_DEVICE
508 	if (g_Config.bFuncHashMap) {
509 		MIPSAnalyst::StoreHashMap();
510 	}
511 #endif
512 
513 	if (pspIsIniting)
514 		Core_NotifyLifecycle(CoreLifecycle::START_COMPLETE);
515 	Core_NotifyLifecycle(CoreLifecycle::STOPPING);
516 	CPU_Shutdown();
517 	GPU_Shutdown();
518 	g_paramSFO.Clear();
519 	host->SetWindowTitle(0);
520 	currentMIPS = 0;
521 	pspIsInited = false;
522 	pspIsIniting = false;
523 	pspIsQuitting = false;
524 	g_Config.unloadGameConfig();
525 	Core_NotifyLifecycle(CoreLifecycle::STOPPED);
526 }
527 
PSP_BeginHostFrame()528 void PSP_BeginHostFrame() {
529 	// Reapply the graphics state of the PSP
530 	if (gpu) {
531 		gpu->BeginHostFrame();
532 	}
533 }
534 
PSP_EndHostFrame()535 void PSP_EndHostFrame() {
536 	if (gpu) {
537 		gpu->EndHostFrame();
538 	}
539 	SaveState::Cleanup();
540 }
541 
PSP_RunLoopWhileState()542 void PSP_RunLoopWhileState() {
543 	// We just run the CPU until we get to vblank. This will quickly sync up pretty nicely.
544 	// The actual number of cycles doesn't matter so much here as we will break due to CORE_NEXTFRAME, most of the time hopefully...
545 	int blockTicks = usToCycles(1000000 / 10);
546 
547 	// Run until CORE_NEXTFRAME
548 	while (coreState == CORE_RUNNING || coreState == CORE_STEPPING) {
549 		PSP_RunLoopFor(blockTicks);
550 		if (coreState == CORE_STEPPING) {
551 			// Keep the UI responsive.
552 			break;
553 		}
554 	}
555 }
556 
PSP_RunLoopUntil(u64 globalticks)557 void PSP_RunLoopUntil(u64 globalticks) {
558 	SaveState::Process();
559 	if (coreState == CORE_POWERDOWN || coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR) {
560 		return;
561 	} else if (coreState == CORE_STEPPING) {
562 		Core_ProcessStepping();
563 		return;
564 	}
565 
566 	mipsr4k.RunLoopUntil(globalticks);
567 	gpu->CleanupBeforeUI();
568 }
569 
PSP_RunLoopFor(int cycles)570 void PSP_RunLoopFor(int cycles) {
571 	PSP_RunLoopUntil(CoreTiming::GetTicks() + cycles);
572 }
573 
PSP_SetLoading(const std::string & reason)574 void PSP_SetLoading(const std::string &reason) {
575 	std::lock_guard<std::mutex> guard(loadingReasonLock);
576 	loadingReason = reason;
577 }
578 
PSP_GetLoading()579 std::string PSP_GetLoading() {
580 	std::lock_guard<std::mutex> guard(loadingReasonLock);
581 	return loadingReason;
582 }
583 
PSP_CoreParameter()584 CoreParameter &PSP_CoreParameter() {
585 	return coreParameter;
586 }
587 
GetSysDirectory(PSPDirectories directoryType)588 Path GetSysDirectory(PSPDirectories directoryType) {
589 	Path memStickDirectory = g_Config.memStickDirectory;
590 	Path pspDirectory;
591 	if (memStickDirectory.GetFilename() == "PSP") {
592 		// Let's strip this off, to easily allow choosing a root directory named "PSP" on Android.
593 		pspDirectory = memStickDirectory;
594 	} else {
595 		pspDirectory = memStickDirectory / "PSP";
596 	}
597 
598 	switch (directoryType) {
599 	case DIRECTORY_PSP:
600 		return pspDirectory;
601 	case DIRECTORY_CHEATS:
602 		return pspDirectory / "Cheats";
603 	case DIRECTORY_GAME:
604 		return pspDirectory / "GAME";
605 	case DIRECTORY_SAVEDATA:
606 		return pspDirectory / "SAVEDATA";
607 	case DIRECTORY_SCREENSHOT:
608 		return pspDirectory / "SCREENSHOT";
609 	case DIRECTORY_SYSTEM:
610 		return pspDirectory / "SYSTEM";
611 	case DIRECTORY_PAUTH:
612 		return memStickDirectory / "PAUTH";  // This one's at the root...
613 	case DIRECTORY_EXDATA:
614 		return memStickDirectory / "EXDATA";  // This one's traditionally at the root...
615 	case DIRECTORY_DUMP:
616 		return pspDirectory / "SYSTEM/DUMP";
617 	case DIRECTORY_SAVESTATE:
618 		return pspDirectory / "PPSSPP_STATE";
619 	case DIRECTORY_CACHE:
620 		return pspDirectory / "SYSTEM/CACHE";
621 	case DIRECTORY_TEXTURES:
622 		return pspDirectory / "TEXTURES";
623 	case DIRECTORY_PLUGINS:
624 		return pspDirectory / "PLUGINS";
625 	case DIRECTORY_APP_CACHE:
626 		if (!g_Config.appCacheDirectory.empty()) {
627 			return g_Config.appCacheDirectory;
628 		}
629 		return pspDirectory / "SYSTEM/CACHE";
630 	case DIRECTORY_VIDEO:
631 		return pspDirectory / "VIDEO";
632 	case DIRECTORY_AUDIO:
633 		return pspDirectory / "AUDIO";
634 	case DIRECTORY_CUSTOM_SHADERS:
635 		return pspDirectory / "shaders";
636 
637 	case DIRECTORY_MEMSTICK_ROOT:
638 		return g_Config.memStickDirectory;
639 	// Just return the memory stick root if we run into some sort of problem.
640 	default:
641 		ERROR_LOG(FILESYS, "Unknown directory type.");
642 		return g_Config.memStickDirectory;
643 	}
644 }
645 
646 #if PPSSPP_PLATFORM(WINDOWS)
647 // Run this at startup time. Please use GetSysDirectory if you need to query where folders are.
InitSysDirectories()648 void InitSysDirectories() {
649 	if (!g_Config.memStickDirectory.empty() && !g_Config.flash0Directory.empty())
650 		return;
651 
652 	const Path &path = File::GetExeDirectory();
653 
654 	// Mount a filesystem
655 	g_Config.flash0Directory = path / "assets/flash0";
656 
657 	// Detect the "My Documents"(XP) or "Documents"(on Vista/7/8) folder.
658 #if PPSSPP_PLATFORM(UWP)
659 	// We set g_Config.memStickDirectory outside.
660 
661 #else
662 	// Caller sets this to the Documents folder.
663 	const Path rootMyDocsPath = g_Config.internalDataDirectory;
664 	const Path myDocsPath = rootMyDocsPath / "PPSSPP";
665 	const Path installedFile = path / "installed.txt";
666 	const bool installed = File::Exists(installedFile);
667 
668 	// If installed.txt exists(and we can determine the Documents directory)
669 	if (installed && !rootMyDocsPath.empty()) {
670 		FILE *fp = File::OpenCFile(installedFile, "rt");
671 		if (fp) {
672 			char temp[2048];
673 			char *tempStr = fgets(temp, sizeof(temp), fp);
674 			// Skip UTF-8 encoding bytes if there are any. There are 3 of them.
675 			if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {
676 				tempStr += 3;
677 			}
678 			std::string tempString = tempStr ? tempStr : "";
679 			if (!tempString.empty() && tempString.back() == '\n')
680 				tempString.resize(tempString.size() - 1);
681 
682 			g_Config.memStickDirectory = Path(tempString);
683 			fclose(fp);
684 		}
685 
686 		// Check if the file is empty first, before appending the slash.
687 		if (g_Config.memStickDirectory.empty())
688 			g_Config.memStickDirectory = myDocsPath;
689 	} else {
690 		g_Config.memStickDirectory = path / "memstick";
691 	}
692 
693 	// Create the memstickpath before trying to write to it, and fall back on Documents yet again
694 	// if we can't make it.
695 	if (!File::Exists(g_Config.memStickDirectory)) {
696 		if (!File::CreateDir(g_Config.memStickDirectory))
697 			g_Config.memStickDirectory = myDocsPath;
698 		INFO_LOG(COMMON, "Memstick directory not present, creating at '%s'", g_Config.memStickDirectory.c_str());
699 	}
700 
701 	Path testFile = g_Config.memStickDirectory / "_writable_test.$$$";
702 
703 	// If any directory is read-only, fall back to the Documents directory.
704 	// We're screwed anyway if we can't write to Documents, or can't detect it.
705 	if (!File::CreateEmptyFile(testFile))
706 		g_Config.memStickDirectory = myDocsPath;
707 
708 	// Clean up our mess.
709 	if (File::Exists(testFile))
710 		File::Delete(testFile);
711 #endif
712 
713 	// Create the default directories that a real PSP creates. Good for homebrew so they can
714 	// expect a standard environment. Skipping THEME though, that's pointless.
715 	File::CreateDir(GetSysDirectory(DIRECTORY_PSP));
716 	File::CreateDir(GetSysDirectory(DIRECTORY_PSP) / "COMMON");
717 	File::CreateDir(GetSysDirectory(DIRECTORY_GAME));
718 	File::CreateDir(GetSysDirectory(DIRECTORY_SAVEDATA));
719 	File::CreateDir(GetSysDirectory(DIRECTORY_SAVESTATE));
720 
721 	if (g_Config.currentDirectory.empty()) {
722 		g_Config.currentDirectory = GetSysDirectory(DIRECTORY_GAME);
723 	}
724 }
725 #endif
726