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