CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

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

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/PSPLoaders.cpp
Views: 1401
1
// Copyright (C) 2012 PPSSPP Project
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <thread>
19
20
#include "Common/Data/Encoding/Utf8.h"
21
#include "Common/Thread/ThreadUtil.h"
22
#include "Common/System/Request.h"
23
24
#include "Common/File/AndroidContentURI.h"
25
#include "Common/File/FileUtil.h"
26
#include "Common/StringUtils.h"
27
#ifdef _WIN32
28
#include "Common/CommonWindows.h"
29
#endif
30
31
#include "Core/ELF/ElfReader.h"
32
#include "Core/ELF/ParamSFO.h"
33
34
#include "Core/FileSystems/BlockDevices.h"
35
#include "Core/FileSystems/BlobFileSystem.h"
36
#include "Core/FileSystems/DirectoryFileSystem.h"
37
#include "Core/FileSystems/ISOFileSystem.h"
38
#include "Core/FileSystems/MetaFileSystem.h"
39
#include "Core/FileSystems/VirtualDiscFileSystem.h"
40
41
#include "Core/Loaders.h"
42
#include "Core/MemMap.h"
43
#include "Core/HDRemaster.h"
44
45
#include "Core/MIPS/MIPS.h"
46
#include "Core/MIPS/MIPSAnalyst.h"
47
#include "Core/MIPS/MIPSCodeUtils.h"
48
49
#include "Core/Config.h"
50
#include "Core/ConfigValues.h"
51
#include "Core/System.h"
52
#include "Core/PSPLoaders.h"
53
#include "Core/HLE/HLE.h"
54
#include "Core/HLE/sceKernel.h"
55
#include "Core/HLE/sceKernelThread.h"
56
#include "Core/HLE/sceKernelModule.h"
57
#include "Core/HLE/sceKernelMemory.h"
58
59
static std::thread g_loadingThread;
60
61
static void UseLargeMem(int memsize) {
62
if (memsize != 1) {
63
// Nothing requested.
64
return;
65
}
66
67
if (Memory::g_PSPModel != PSP_MODEL_FAT) {
68
INFO_LOG(Log::Loader, "Game requested full PSP-2000 memory access");
69
Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;
70
} else {
71
WARN_LOG(Log::Loader, "Game requested full PSP-2000 memory access, ignoring in PSP-1000 mode");
72
}
73
}
74
75
// We gather the game info before actually loading/booting the ISO
76
// to determine if the emulator should enable extra memory and
77
// double-sized texture coordinates.
78
void InitMemoryForGameISO(FileLoader *fileLoader) {
79
if (!fileLoader->Exists()) {
80
return;
81
}
82
83
std::shared_ptr<IFileSystem> fileSystem;
84
std::shared_ptr<IFileSystem> blockSystem;
85
86
if (fileLoader->IsDirectory()) {
87
fileSystem = std::make_shared<VirtualDiscFileSystem>(&pspFileSystem, fileLoader->GetPath());
88
blockSystem = fileSystem;
89
} else {
90
auto bd = constructBlockDevice(fileLoader);
91
// Can't init anything without a block device...
92
if (!bd)
93
return;
94
95
auto iso = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);
96
fileSystem = iso;
97
blockSystem = std::make_shared<ISOBlockSystem>(iso);
98
}
99
100
pspFileSystem.Mount("umd0:", blockSystem);
101
pspFileSystem.Mount("umd1:", blockSystem);
102
pspFileSystem.Mount("disc0:", fileSystem);
103
pspFileSystem.Mount("umd:", blockSystem);
104
// TODO: Should we do this?
105
//pspFileSystem.Mount("host0:", fileSystem);
106
107
std::string gameID;
108
std::string umdData;
109
110
std::string sfoPath("disc0:/PSP_GAME/PARAM.SFO");
111
PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());
112
113
if (fileInfo.exists) {
114
std::vector<u8> paramsfo;
115
pspFileSystem.ReadEntireFile(sfoPath, paramsfo);
116
if (g_paramSFO.ReadSFO(paramsfo)) {
117
UseLargeMem(g_paramSFO.GetValueInt("MEMSIZE"));
118
gameID = g_paramSFO.GetValueString("DISC_ID");
119
}
120
121
std::vector<u8> umdDataBin;
122
if (pspFileSystem.ReadEntireFile("disc0:/UMD_DATA.BIN", umdDataBin) >= 0) {
123
umdData = std::string((const char *)&umdDataBin[0], umdDataBin.size());
124
}
125
}
126
127
for (size_t i = 0; i < g_HDRemastersCount; i++) {
128
const auto &entry = g_HDRemasters[i];
129
if (entry.gameID != gameID) {
130
continue;
131
}
132
if (entry.umdDataValue && umdData.find(entry.umdDataValue) == umdData.npos) {
133
continue;
134
}
135
136
g_RemasterMode = true;
137
Memory::g_MemorySize = entry.memorySize;
138
g_DoubleTextureCoordinates = entry.doubleTextureCoordinates;
139
break;
140
}
141
if (g_RemasterMode) {
142
INFO_LOG(Log::Loader, "HDRemaster found, using increased memory");
143
}
144
}
145
146
bool ReInitMemoryForGameISO(FileLoader *fileLoader) {
147
if (!fileLoader->Exists()) {
148
return false;
149
}
150
151
std::shared_ptr<IFileSystem> fileSystem;
152
std::shared_ptr<IFileSystem> blockSystem;
153
154
if (fileLoader->IsDirectory()) {
155
fileSystem = std::make_shared<VirtualDiscFileSystem>(&pspFileSystem, fileLoader->GetPath());
156
blockSystem = fileSystem;
157
} else {
158
auto bd = constructBlockDevice(fileLoader);
159
if (!bd)
160
return false;
161
162
auto iso = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);
163
fileSystem = iso;
164
blockSystem = std::make_shared<ISOBlockSystem>(iso);
165
}
166
167
pspFileSystem.Remount("umd0:", blockSystem);
168
pspFileSystem.Remount("umd1:", blockSystem);
169
pspFileSystem.Remount("umd:", blockSystem);
170
pspFileSystem.Remount("disc0:", fileSystem);
171
172
return true;
173
}
174
175
void InitMemoryForGamePBP(FileLoader *fileLoader) {
176
if (!fileLoader->Exists()) {
177
return;
178
}
179
180
PBPReader pbp(fileLoader);
181
if (pbp.IsValid() && !pbp.IsELF()) {
182
std::vector<u8> sfoData;
183
if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
184
ParamSFOData paramSFO;
185
if (paramSFO.ReadSFO(sfoData)) {
186
// This is the parameter CFW uses to determine homebrew wants the full 64MB.
187
UseLargeMem(paramSFO.GetValueInt("MEMSIZE"));
188
189
// Take this moment to bring over the title, if set.
190
std::string title = paramSFO.GetValueString("TITLE");
191
if (g_paramSFO.GetValueString("TITLE").empty() && !title.empty()) {
192
g_paramSFO.SetValue("TITLE", title, (int)title.size());
193
}
194
195
std::string discID = paramSFO.GetValueString("DISC_ID");
196
std::string systemVer = paramSFO.GetValueString("PSP_SYSTEM_VER");
197
// Homebrew typically always leave this zero.
198
bool discTotalCheck = paramSFO.GetValueInt("DISC_TOTAL") != 0;
199
// A lot of homebrew reuse real game disc IDs - avoid.
200
bool formatCheck = discID.substr(0, 2) != "NP" && discID.substr(0, 2) != "UL" && discID.substr(0, 2) != "UC";
201
char region = discID.size() > 3 ? discID[2] : '\0';
202
bool regionCheck = region != 'A' && region != 'E' && region != 'H' && region != 'I' && region != 'J' && region != 'K' && region != 'U' && region != 'X';
203
bool systemVerCheck = !systemVer.empty() && systemVer[0] >= '5';
204
if ((formatCheck || regionCheck || discTotalCheck || systemVerCheck) && !discID.empty()) {
205
g_paramSFO.SetValue("DISC_ID", discID, (int)discID.size());
206
std::string ver = paramSFO.GetValueString("DISC_VERSION");
207
if (ver.empty())
208
ver = "1.00";
209
g_paramSFO.SetValue("DISC_VERSION", ver, (int)ver.size());
210
}
211
}
212
}
213
}
214
}
215
216
217
// Chinese translators like to rename EBOOT.BIN and replace it with some kind of stub
218
// that probably loads a plugin and then launches the actual game. These stubs don't work in PPSSPP.
219
// No idea why they are doing this, but it works to just bypass it. They could stop
220
// inventing new filenames though...
221
static const char * const altBootNames[] = {
222
"disc0:/PSP_GAME/SYSDIR/EBOOT.OLD",
223
"disc0:/PSP_GAME/SYSDIR/EBOOT.DAT",
224
"disc0:/PSP_GAME/SYSDIR/EBOOT.BI",
225
"disc0:/PSP_GAME/SYSDIR/EBOOT.LLD",
226
//"disc0:/PSP_GAME/SYSDIR/OLD_EBOOT.BIN", //Utawareru Mono Chinese version
227
"disc0:/PSP_GAME/SYSDIR/EBOOT.123",
228
//"disc0:/PSP_GAME/SYSDIR/EBOOT_LRC_CH.BIN", // Hatsune Miku Project Diva Extend chinese version
229
"disc0:/PSP_GAME/SYSDIR/BOOT0.OLD",
230
"disc0:/PSP_GAME/SYSDIR/BOOT1.OLD",
231
"disc0:/PSP_GAME/SYSDIR/BINOT.BIN",
232
"disc0:/PSP_GAME/SYSDIR/EBOOT.FRY",
233
"disc0:/PSP_GAME/SYSDIR/EBOOT.Z.Y",
234
"disc0:/PSP_GAME/SYSDIR/EBOOT.LEI",
235
"disc0:/PSP_GAME/SYSDIR/EBOOT.DNR",
236
"disc0:/PSP_GAME/SYSDIR/DBZ2.BIN",
237
//"disc0:/PSP_GAME/SYSDIR/ss.RAW",//Code Geass: Lost Colors chinese version
238
};
239
240
bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {
241
// Mounting stuff relocated to InitMemoryForGameISO due to HD Remaster restructuring of code.
242
243
std::string sfoPath("disc0:/PSP_GAME/PARAM.SFO");
244
PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());
245
if (fileInfo.exists) {
246
std::vector<u8> paramsfo;
247
pspFileSystem.ReadEntireFile(sfoPath, paramsfo);
248
if (g_paramSFO.ReadSFO(paramsfo)) {
249
std::string title = StringFromFormat("%s : %s", g_paramSFO.GetValueString("DISC_ID").c_str(), g_paramSFO.GetValueString("TITLE").c_str());
250
INFO_LOG(Log::Loader, "%s", title.c_str());
251
System_SetWindowTitle(title);
252
}
253
}
254
255
std::string bootpath("disc0:/PSP_GAME/SYSDIR/EBOOT.BIN");
256
257
// Bypass Chinese translation patches, see comment above.
258
for (size_t i = 0; i < ARRAY_SIZE(altBootNames); i++) {
259
if (pspFileSystem.GetFileInfo(altBootNames[i]).exists) {
260
bootpath = altBootNames[i];
261
}
262
}
263
264
// Bypass another more dangerous one where the file is in USRDIR - this could collide with files in some game.
265
std::string id = g_paramSFO.GetValueString("DISC_ID");
266
if (id == "NPJH50624" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN").exists) {
267
bootpath = "disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN";
268
}
269
if (id == "NPJH00100" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL").exists) {
270
bootpath = "disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL";
271
}
272
273
bool hasEncrypted = false;
274
int fd;
275
if ((fd = pspFileSystem.OpenFile(bootpath, FILEACCESS_READ)) >= 0)
276
{
277
u8 head[4];
278
pspFileSystem.ReadFile(fd, head, 4);
279
if (memcmp(head, "~PSP", 4) == 0 || memcmp(head, "\x7F""ELF", 4) == 0) {
280
hasEncrypted = true;
281
}
282
pspFileSystem.CloseFile(fd);
283
}
284
if (!hasEncrypted) {
285
// try unencrypted Boot.BIN
286
bootpath = "disc0:/PSP_GAME/SYSDIR/BOOT.BIN";
287
}
288
289
// Fail early with a clearer message for some types of ISOs.
290
if (!pspFileSystem.GetFileInfo(bootpath).exists) {
291
// Can't tell for sure if it's PS1 or PS2, but doesn't much matter.
292
if (pspFileSystem.GetFileInfo("disc0:/SYSTEM.CNF;1").exists || pspFileSystem.GetFileInfo("disc0:/PSX.EXE;1").exists) {
293
*error_string = "PPSSPP plays PSP games, not PlayStation 1 or 2 games.";
294
} else if (pspFileSystem.GetFileInfo("disc0:/UMD_VIDEO/PLAYLIST.UMD").exists) {
295
*error_string = "PPSSPP doesn't support UMD Video.";
296
} else if (pspFileSystem.GetFileInfo("disc0:/UMD_AUDIO/PLAYLIST.UMD").exists) {
297
*error_string = "PPSSPP doesn't support UMD Music.";
298
} else if (pspFileSystem.GetDirListing("disc0:/").empty()) {
299
*error_string = "Not a valid disc image.";
300
} else {
301
*error_string = "A PSP game couldn't be found on the disc.";
302
}
303
coreState = CORE_BOOT_ERROR;
304
return false;
305
}
306
307
//in case we didn't go through EmuScreen::boot
308
g_Config.loadGameConfig(id, g_paramSFO.GetValueString("TITLE"));
309
System_PostUIMessage(UIMessage::CONFIG_LOADED);
310
INFO_LOG(Log::Loader, "Loading %s...", bootpath.c_str());
311
312
PSPLoaders_Shutdown();
313
// Note: this thread reads the game binary, loads caches, and links HLE while UI spins.
314
// To do something deterministically when the game starts, disabling this thread won't be enough.
315
// Instead: Use Core_ListenLifecycle() or watch coreState.
316
g_loadingThread = std::thread([bootpath] {
317
SetCurrentThreadName("ExecLoader");
318
PSP_LoadingLock guard;
319
if (coreState != CORE_POWERUP)
320
return;
321
322
AndroidJNIThreadContext jniContext;
323
324
PSP_SetLoading("Loading executable...");
325
// TODO: We can't use the initial error_string pointer.
326
bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString);
327
if (success && coreState == CORE_POWERUP) {
328
coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;
329
} else {
330
coreState = CORE_BOOT_ERROR;
331
// TODO: This is a crummy way to communicate the error...
332
PSP_CoreParameter().fileToStart.clear();
333
}
334
});
335
return true;
336
}
337
338
static Path NormalizePath(const Path &path) {
339
if (path.Type() != PathType::NATIVE) {
340
// Nothing to do - these can't be non-normalized.
341
return path;
342
}
343
344
#ifdef _WIN32
345
std::wstring wpath = path.ToWString();
346
std::wstring buf;
347
buf.resize(512);
348
size_t sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);
349
if (sz != 0 && sz < buf.size()) {
350
buf.resize(sz);
351
} else if (sz > buf.size()) {
352
buf.resize(sz);
353
sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);
354
// This should truncate off the null terminator.
355
buf.resize(sz);
356
}
357
return Path(buf);
358
#else
359
char buf[PATH_MAX + 1];
360
if (!realpath(path.c_str(), buf))
361
return Path();
362
return Path(buf);
363
#endif
364
}
365
366
bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {
367
// This is really just for headless, might need tweaking later.
368
if (PSP_CoreParameter().mountIsoLoader != nullptr) {
369
auto bd = constructBlockDevice(PSP_CoreParameter().mountIsoLoader);
370
if (bd != NULL) {
371
auto umd2 = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);
372
auto blockSystem = std::make_shared<ISOBlockSystem>(umd2);
373
374
pspFileSystem.Mount("umd1:", blockSystem);
375
pspFileSystem.Mount("disc0:", umd2);
376
pspFileSystem.Mount("umd:", blockSystem);
377
}
378
}
379
380
Path full_path = fileLoader->GetPath();
381
std::string path = full_path.GetDirectory();
382
std::string file = full_path.GetFilename();
383
384
if (full_path.Type() == PathType::CONTENT_URI) {
385
path = AndroidContentURI(full_path.GetDirectory()).FilePath();
386
}
387
388
size_t pos = path.find("PSP/GAME/");
389
std::string ms_path;
390
if (pos != std::string::npos) {
391
ms_path = "ms0:/" + path.substr(pos) + "/";
392
} else {
393
// This is wrong, but it's better than not having a working directory at all.
394
// Note that umd0:/ is actually the writable containing directory, in this case.
395
ms_path = "umd0:/";
396
}
397
398
Path dir;
399
if (!PSP_CoreParameter().mountRoot.empty()) {
400
// We don't want to worry about .. and cwd and such.
401
const Path rootNorm = NormalizePath(PSP_CoreParameter().mountRoot);
402
Path pathNorm = NormalizePath(Path(path));
403
404
if (full_path.Type() == PathType::CONTENT_URI) {
405
pathNorm = full_path.NavigateUp();
406
}
407
408
// If root is not a subpath of path, we can't boot the game.
409
if (!pathNorm.StartsWith(rootNorm)) {
410
*error_string = "Cannot boot ELF located outside mountRoot.";
411
coreState = CORE_BOOT_ERROR;
412
return false;
413
}
414
415
std::string filepath;
416
if (full_path.Type() == PathType::CONTENT_URI) {
417
std::string rootFilePath = AndroidContentURI(rootNorm.c_str()).FilePath();
418
std::string pathFilePath = AndroidContentURI(pathNorm.c_str()).FilePath();
419
filepath = pathFilePath.substr(rootFilePath.size());
420
} else {
421
filepath = ReplaceAll(pathNorm.ToString().substr(rootNorm.ToString().size()), "\\", "/");
422
}
423
424
file = filepath + "/" + file;
425
path = rootNorm.ToString();
426
pspFileSystem.SetStartingDirectory(filepath);
427
dir = Path(path);
428
} else {
429
pspFileSystem.SetStartingDirectory(ms_path);
430
dir = full_path.NavigateUp();
431
}
432
433
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, dir, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
434
pspFileSystem.Mount("umd0:", fs);
435
436
std::string finalName = ms_path + file;
437
438
std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString();
439
std::size_t lslash = homebrewName.find_last_of('/');
440
#if PPSSPP_PLATFORM(UWP)
441
if (lslash == homebrewName.npos) {
442
lslash = homebrewName.find_last_of("\\");
443
}
444
#endif
445
if (lslash != homebrewName.npos)
446
homebrewName = homebrewName.substr(lslash + 1);
447
std::string homebrewTitle = g_paramSFO.GetValueString("TITLE");
448
if (homebrewTitle.empty())
449
homebrewTitle = homebrewName;
450
std::string discID = g_paramSFO.GetDiscID();
451
std::string discVersion = g_paramSFO.GetValueString("DISC_VERSION");
452
std::string madeUpID = g_paramSFO.GenerateFakeID(Path());
453
454
std::string title = StringFromFormat("%s : %s", discID.c_str(), homebrewTitle.c_str());
455
INFO_LOG(Log::Loader, "%s", title.c_str());
456
System_SetWindowTitle(title);
457
458
// Migrate old save states from old versions of fake game IDs.
459
const Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);
460
for (int i = 0; i < 5; ++i) {
461
Path newPrefix = savestateDir / StringFromFormat("%s_%s_%d", discID.c_str(), discVersion.c_str(), i);
462
Path oldNamePrefix = savestateDir / StringFromFormat("%s_%d", homebrewName.c_str(), i);
463
Path oldIDPrefix = savestateDir / StringFromFormat("%s_1.00_%d", madeUpID.c_str(), i);
464
465
if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".ppst")))
466
File::Rename(oldIDPrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));
467
else if (File::Exists(oldNamePrefix.WithExtraExtension(".ppst")))
468
File::Rename(oldNamePrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));
469
if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".jpg")))
470
File::Rename(oldIDPrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));
471
else if (File::Exists(oldNamePrefix.WithExtraExtension(".jpg")))
472
File::Rename(oldNamePrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));
473
}
474
475
PSPLoaders_Shutdown();
476
// Note: See Load_PSP_ISO for notes about this thread.
477
g_loadingThread = std::thread([finalName] {
478
SetCurrentThreadName("ExecLoader");
479
PSP_LoadingLock guard;
480
if (coreState != CORE_POWERUP)
481
return;
482
483
AndroidJNIThreadContext jniContext;
484
485
bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString);
486
if (success && coreState == CORE_POWERUP) {
487
coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;
488
} else {
489
coreState = CORE_BOOT_ERROR;
490
// TODO: This is a crummy way to communicate the error...
491
PSP_CoreParameter().fileToStart.clear();
492
}
493
});
494
return true;
495
}
496
497
bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
498
auto umd = std::make_shared<BlobFileSystem>(&pspFileSystem, fileLoader, "data.ppdmp");
499
pspFileSystem.Mount("disc0:", umd);
500
501
PSPLoaders_Shutdown();
502
// Note: See Load_PSP_ISO for notes about this thread.
503
g_loadingThread = std::thread([] {
504
SetCurrentThreadName("ExecLoader");
505
PSP_LoadingLock guard;
506
if (coreState != CORE_POWERUP)
507
return;
508
509
AndroidJNIThreadContext jniContext;
510
511
bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString);
512
if (success && coreState == CORE_POWERUP) {
513
coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING;
514
} else {
515
coreState = CORE_BOOT_ERROR;
516
// TODO: This is a crummy way to communicate the error...
517
PSP_CoreParameter().fileToStart.clear();
518
}
519
});
520
return true;
521
}
522
523
void PSPLoaders_Shutdown() {
524
if (g_loadingThread.joinable())
525
g_loadingThread.join();
526
}
527
528