Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/PSPLoaders.cpp
5654 views
1
// Copyright (C) 2012 PPSSPP Project
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "Core/Core.h"
19
#include "Common/System/Request.h"
20
21
#include "Common/File/AndroidContentURI.h"
22
#include "Common/File/FileUtil.h"
23
#include "Common/StringUtils.h"
24
#ifdef _WIN32
25
#include "Common/CommonWindows.h"
26
#endif
27
28
#include "Core/ELF/ParamSFO.h"
29
#include "Core/ELF/PBPReader.h"
30
31
#include "Core/FileSystems/BlockDevices.h"
32
#include "Core/FileSystems/BlobFileSystem.h"
33
#include "Core/FileSystems/DirectoryFileSystem.h"
34
#include "Core/FileSystems/ISOFileSystem.h"
35
#include "Core/FileSystems/MetaFileSystem.h"
36
#include "Core/FileSystems/VirtualDiscFileSystem.h"
37
38
#include "Core/Loaders.h"
39
#include "Core/MemMap.h"
40
#include "Core/HDRemaster.h"
41
42
43
#include "Core/Config.h"
44
#include "Core/ConfigValues.h"
45
#include "Core/System.h"
46
#include "Core/PSPLoaders.h"
47
#include "Core/HLE/sceKernelModule.h"
48
49
static void UseLargeMem(int memsize) {
50
if (memsize != 1) {
51
// Nothing requested.
52
return;
53
}
54
55
if (Memory::g_PSPModel != PSP_MODEL_FAT) {
56
INFO_LOG(Log::Loader, "Game requested full PSP-2000 memory access");
57
Memory::g_MemorySize = Memory::RAM_DOUBLE_SIZE;
58
} else {
59
WARN_LOG(Log::Loader, "Game requested full PSP-2000 memory access, ignoring in PSP-1000 mode");
60
}
61
}
62
63
bool MountGameISO(FileLoader *fileLoader, std::string *errorString) {
64
std::shared_ptr<IFileSystem> fileSystem;
65
std::shared_ptr<IFileSystem> blockSystem;
66
67
if (fileLoader->IsDirectory()) {
68
fileSystem = std::make_shared<VirtualDiscFileSystem>(&pspFileSystem, fileLoader->GetPath());
69
blockSystem = fileSystem;
70
} else {
71
auto bd = ConstructBlockDevice(fileLoader, errorString);
72
if (!bd) {
73
// Can only fail if the ISO is bad.
74
return false;
75
}
76
77
auto iso = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);
78
fileSystem = iso;
79
blockSystem = std::make_shared<ISOBlockSystem>(iso);
80
}
81
82
pspFileSystem.Mount("umd0:", blockSystem);
83
pspFileSystem.Mount("umd1:", blockSystem);
84
pspFileSystem.Mount("umd:", blockSystem);
85
pspFileSystem.Mount("disc0:", fileSystem);
86
return true;
87
}
88
89
bool LoadParamSFOFromDisc() {
90
std::string sfoPath("disc0:/PSP_GAME/PARAM.SFO");
91
PSPFileInfo fileInfo = pspFileSystem.GetFileInfo(sfoPath.c_str());
92
if (fileInfo.exists) {
93
std::vector<u8> paramsfo;
94
pspFileSystem.ReadEntireFile(sfoPath, paramsfo);
95
if (g_paramSFO.ReadSFO(paramsfo)) {
96
g_paramSFORaw = g_paramSFO;
97
return true;
98
}
99
}
100
return false;
101
}
102
103
// We gather the game info before actually loading/booting the ISO
104
// to determine if the emulator should enable extra memory and
105
// double-sized texture coordinates.
106
void InitMemorySizeForGame() {
107
std::string gameID;
108
std::string umdData;
109
110
if (g_paramSFO.IsValid()) {
111
UseLargeMem(g_paramSFO.GetValueInt("MEMSIZE"));
112
gameID = g_paramSFO.GetValueString("DISC_ID");
113
}
114
115
for (size_t i = 0; i < g_HDRemastersCount; i++) {
116
const auto &entry = g_HDRemasters[i];
117
if (entry.gameID != gameID) {
118
continue;
119
}
120
121
if (umdData.empty()) {
122
std::vector<u8> umdDataBin;
123
if (pspFileSystem.ReadEntireFile("disc0:/UMD_DATA.BIN", umdDataBin) >= 0) {
124
umdData = std::string((const char *)&umdDataBin[0], umdDataBin.size());
125
}
126
}
127
128
if (entry.umdDataValue && umdData.find(entry.umdDataValue) == umdData.npos) {
129
continue;
130
}
131
132
g_RemasterMode = true;
133
Memory::g_MemorySize = entry.memorySize;
134
g_DoubleTextureCoordinates = entry.doubleTextureCoordinates;
135
break;
136
}
137
if (g_RemasterMode) {
138
INFO_LOG(Log::Loader, "HDRemaster found, using increased memory");
139
}
140
}
141
142
bool LoadParamSFOFromPBP(FileLoader *fileLoader) {
143
PBPReader pbp(fileLoader);
144
if (pbp.IsValid() && !pbp.IsELF()) {
145
std::vector<u8> sfoData;
146
if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
147
// Carefully parse param SFO for PBP files.
148
ParamSFOData paramSFO;
149
if (paramSFO.ReadSFO(sfoData)) {
150
g_paramSFORaw = paramSFO;
151
std::string title = paramSFO.GetValueString("TITLE");
152
if (g_paramSFO.GetValueString("TITLE").empty() && !title.empty()) {
153
g_paramSFO.SetValue("TITLE", title, (int)title.size());
154
}
155
156
// Copy over the MEMSIZE flag for later inspection.
157
if (paramSFO.HasKey("MEMSIZE")) {
158
g_paramSFO.SetValue("MEMSIZE", paramSFO.GetValueInt("MEMSIZE"), 4);
159
}
160
161
std::string discID = paramSFO.GetValueString("DISC_ID");
162
std::string systemVer = paramSFO.GetValueString("PSP_SYSTEM_VER");
163
// Homebrew typically always leave this zero.
164
bool discTotalCheck = paramSFO.GetValueInt("DISC_TOTAL") != 0;
165
// A lot of homebrew reuse real game disc IDs - avoid.
166
bool formatCheck = discID.substr(0, 2) != "NP" && discID.substr(0, 2) != "UL" && discID.substr(0, 2) != "UC";
167
char region = discID.size() > 3 ? discID[2] : '\0';
168
bool regionCheck = region != 'A' && region != 'E' && region != 'H' && region != 'I' && region != 'J' && region != 'K' && region != 'U' && region != 'X';
169
bool systemVerCheck = !systemVer.empty() && systemVer[0] >= '5';
170
if ((formatCheck || regionCheck || discTotalCheck || systemVerCheck) && !discID.empty()) {
171
// This is NOT a homebrew, so we set the ID. Otherwise it'll later be generated
172
// on the first access. This is kinda backwards and weird.
173
g_paramSFO.SetValue("DISC_ID", discID, (int)discID.size());
174
std::string ver = paramSFO.GetValueString("DISC_VERSION");
175
if (ver.empty())
176
ver = "1.00";
177
g_paramSFO.SetValue("DISC_VERSION", ver, (int)ver.size());
178
}
179
return true;
180
}
181
}
182
}
183
return false;
184
}
185
186
187
// Chinese translators like to rename EBOOT.BIN and replace it with some kind of stub
188
// that probably loads a plugin and then launches the actual game. These stubs don't work in PPSSPP.
189
// No idea why they are doing this, but it works to just bypass it. They could stop
190
// inventing new filenames though...
191
static const char * const altBootNames[] = {
192
"disc0:/PSP_GAME/SYSDIR/EBOOT.OLD",
193
"disc0:/PSP_GAME/SYSDIR/EBOOT.DAT",
194
"disc0:/PSP_GAME/SYSDIR/EBOOT.BI",
195
"disc0:/PSP_GAME/SYSDIR/EBOOT.LLD",
196
//"disc0:/PSP_GAME/SYSDIR/OLD_EBOOT.BIN", //Utawareru Mono Chinese version
197
"disc0:/PSP_GAME/SYSDIR/EBOOT.123",
198
//"disc0:/PSP_GAME/SYSDIR/EBOOT_LRC_CH.BIN", // Hatsune Miku Project Diva Extend chinese version
199
"disc0:/PSP_GAME/SYSDIR/BOOT0.OLD",
200
"disc0:/PSP_GAME/SYSDIR/BOOT1.OLD",
201
"disc0:/PSP_GAME/SYSDIR/BINOT.BIN",
202
"disc0:/PSP_GAME/SYSDIR/EBOOT.FRY",
203
"disc0:/PSP_GAME/SYSDIR/EBOOT.Z.Y",
204
"disc0:/PSP_GAME/SYSDIR/EBOOT.LEI",
205
"disc0:/PSP_GAME/SYSDIR/EBOOT.DNR",
206
"disc0:/PSP_GAME/SYSDIR/DBZ2.BIN",
207
//"disc0:/PSP_GAME/SYSDIR/ss.RAW",//Code Geass: Lost Colors chinese version
208
};
209
210
bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {
211
std::string bootpath("disc0:/PSP_GAME/SYSDIR/EBOOT.BIN");
212
213
// Bypass Chinese translation patches, see comment above.
214
for (size_t i = 0; i < ARRAY_SIZE(altBootNames); i++) {
215
if (pspFileSystem.GetFileInfo(altBootNames[i]).exists) {
216
WARN_LOG(Log::Boot, "Bypassing suspected translation patch. Booting '%s' instead of '%s'.", altBootNames[i], bootpath.c_str());
217
bootpath = altBootNames[i];
218
// break; // should have a break here, but it would effectively reverse the evaluation order.
219
}
220
}
221
222
// Bypass another more dangerous one where the file is in USRDIR - this could collide with files in some game.
223
std::string id = g_paramSFO.GetValueString("DISC_ID");
224
if (id == "NPJH50624" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN").exists) {
225
bootpath = "disc0:/PSP_GAME/USRDIR/PAKFILE2.BIN";
226
}
227
if (id == "NPJH00100" && pspFileSystem.GetFileInfo("disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL").exists) {
228
bootpath = "disc0:/PSP_GAME/USRDIR/DATA/GIM/GBL";
229
}
230
231
bool hasEncrypted = false;
232
int fd;
233
if ((fd = pspFileSystem.OpenFile(bootpath, FILEACCESS_READ)) >= 0) {
234
u8 head[4];
235
pspFileSystem.ReadFile(fd, head, 4);
236
if (memcmp(head, "~PSP", 4) == 0 || memcmp(head, "\x7F""ELF", 4) == 0) {
237
hasEncrypted = true;
238
}
239
pspFileSystem.CloseFile(fd);
240
}
241
242
if (!hasEncrypted) {
243
// try unencrypted Boot.BIN
244
bootpath = "disc0:/PSP_GAME/SYSDIR/BOOT.BIN";
245
}
246
247
// Fail early with a clearer message for some types of ISOs.
248
if (!pspFileSystem.GetFileInfo(bootpath).exists) {
249
// Can't tell for sure if it's PS1 or PS2, but doesn't much matter.
250
if (pspFileSystem.GetFileInfo("disc0:/SYSTEM.CNF;1").exists || pspFileSystem.GetFileInfo("disc0:/PSX.EXE;1").exists) {
251
*error_string = "PPSSPP plays PSP games, not PlayStation 1 or 2 games.";
252
} else if (pspFileSystem.GetFileInfo("disc0:/UMD_VIDEO/PLAYLIST.UMD").exists) {
253
*error_string = "PPSSPP doesn't support UMD Video.";
254
} else if (pspFileSystem.GetFileInfo("disc0:/UMD_AUDIO/PLAYLIST.UMD").exists) {
255
*error_string = "PPSSPP doesn't support UMD Music.";
256
} else if (pspFileSystem.GetDirListing("disc0:/").empty()) {
257
*error_string = "Not a valid disc image.";
258
} else {
259
*error_string = "A PSP game couldn't be found on the disc.";
260
}
261
return false;
262
}
263
264
// If there's a game-specific config, load it.
265
g_Config.LoadGameConfig(id);
266
267
System_PostUIMessage(UIMessage::CONFIG_LOADED);
268
INFO_LOG(Log::Loader, "Loading %s...", bootpath.c_str());
269
// TODO: We can't use the initial error_string pointer.
270
return __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString);
271
}
272
273
// TODO: Move this to common. Merge with ResolvePath?
274
static Path NormalizePath(const Path &path) {
275
if (path.Type() != PathType::NATIVE) {
276
// Nothing to do - these can't be non-normalized.
277
return path;
278
}
279
280
#ifdef _WIN32
281
std::wstring wpath = path.ToWString();
282
std::wstring buf;
283
buf.resize(512);
284
size_t sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);
285
if (sz != 0 && sz < buf.size()) {
286
buf.resize(sz);
287
} else if (sz > buf.size()) {
288
buf.resize(sz);
289
sz = GetFullPathName(wpath.c_str(), (DWORD)buf.size(), &buf[0], nullptr);
290
// This should truncate off the null terminator.
291
buf.resize(sz);
292
}
293
return Path(buf);
294
#else
295
char buf[PATH_MAX + 1];
296
if (!realpath(path.c_str(), buf))
297
return Path();
298
return Path(buf);
299
#endif
300
}
301
302
bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {
303
// This is really just for headless, might need tweaking later.
304
if (PSP_CoreParameter().mountIsoLoader != nullptr) {
305
auto bd = ConstructBlockDevice(PSP_CoreParameter().mountIsoLoader, error_string);
306
if (bd) {
307
auto umd2 = std::make_shared<ISOFileSystem>(&pspFileSystem, bd);
308
auto blockSystem = std::make_shared<ISOBlockSystem>(umd2);
309
310
pspFileSystem.Mount("umd1:", blockSystem);
311
pspFileSystem.Mount("disc0:", umd2);
312
pspFileSystem.Mount("umd:", blockSystem);
313
} else {
314
ERROR_LOG(Log::Loader, "mountIso failed: %s", error_string->c_str());
315
}
316
}
317
318
Path full_path = fileLoader->GetPath();
319
std::string path = full_path.GetDirectory();
320
std::string file = full_path.GetFilename();
321
322
if (full_path.Type() == PathType::CONTENT_URI) {
323
path = AndroidContentURI(full_path.GetDirectory()).FilePath();
324
}
325
326
size_t pos = path.find("PSP/GAME/");
327
std::string ms_path;
328
if (pos != std::string::npos) {
329
ms_path = "ms0:/" + path.substr(pos) + "/";
330
} else {
331
// This is wrong, but it's better than not having a working directory at all.
332
// Note that umd0:/ is actually the writable containing directory, in this case.
333
ms_path = "umd0:/";
334
}
335
336
Path dir;
337
if (!PSP_CoreParameter().mountRoot.empty()) {
338
// We don't want to worry about .. and cwd and such.
339
const Path rootNorm = NormalizePath(PSP_CoreParameter().mountRoot);
340
Path pathNorm = NormalizePath(Path(path));
341
342
if (full_path.Type() == PathType::CONTENT_URI) {
343
pathNorm = full_path.NavigateUp();
344
}
345
346
// If root is not a subpath of path, we can't boot the game.
347
if (!pathNorm.StartsWith(rootNorm)) {
348
*error_string = "Cannot boot ELF located outside mountRoot.";
349
return false;
350
}
351
352
std::string filepath;
353
if (full_path.Type() == PathType::CONTENT_URI) {
354
std::string rootFilePath = AndroidContentURI(rootNorm.c_str()).FilePath();
355
std::string pathFilePath = AndroidContentURI(pathNorm.c_str()).FilePath();
356
filepath = pathFilePath.substr(rootFilePath.size());
357
} else {
358
filepath = ReplaceAll(pathNorm.ToString().substr(rootNorm.ToString().size()), "\\", "/");
359
}
360
361
file = filepath + "/" + file;
362
path = rootNorm.ToString();
363
pspFileSystem.SetStartingDirectory(filepath);
364
dir = Path(path);
365
} else {
366
pspFileSystem.SetStartingDirectory(ms_path);
367
dir = full_path.NavigateUp();
368
}
369
370
auto fs = std::make_shared<DirectoryFileSystem>(&pspFileSystem, dir, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD);
371
pspFileSystem.Mount("umd0:", fs);
372
373
std::string finalName = ms_path + file;
374
375
std::string homebrewName = PSP_CoreParameter().fileToStart.ToVisualString();
376
std::size_t lslash = homebrewName.find_last_of('/');
377
std::size_t rslash = homebrewName.find_last_of('\\');
378
if (lslash != homebrewName.npos)
379
homebrewName = homebrewName.substr(lslash + 1);
380
if (rslash != homebrewName.npos)
381
homebrewName = homebrewName.substr(rslash + 1);
382
std::string discID = g_paramSFO.GetDiscID();
383
std::string discVersion = g_paramSFO.GetValueString("DISC_VERSION");
384
std::string madeUpID = g_paramSFO.GenerateFakeID(Path());
385
386
// Migrate old save states from old versions of fake game IDs.
387
// Ugh, this might actually be slow on Android.
388
const Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);
389
for (int i = 0; i < 5; ++i) {
390
Path newPrefix = savestateDir / StringFromFormat("%s_%s_%d", discID.c_str(), discVersion.c_str(), i);
391
Path oldNamePrefix = savestateDir / StringFromFormat("%s_%d", homebrewName.c_str(), i);
392
Path oldIDPrefix = savestateDir / StringFromFormat("%s_1.00_%d", madeUpID.c_str(), i);
393
394
if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".ppst")))
395
File::Rename(oldIDPrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));
396
else if (File::Exists(oldNamePrefix.WithExtraExtension(".ppst")))
397
File::Rename(oldNamePrefix.WithExtraExtension(".ppst"), newPrefix.WithExtraExtension(".ppst"));
398
if (oldIDPrefix != newPrefix && File::Exists(oldIDPrefix.WithExtraExtension(".jpg")))
399
File::Rename(oldIDPrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));
400
else if (File::Exists(oldNamePrefix.WithExtraExtension(".jpg")))
401
File::Rename(oldNamePrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));
402
}
403
404
return __KernelLoadExec(finalName.c_str(), 0, error_string);
405
}
406
407
bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
408
auto umd = std::make_shared<BlobFileSystem>(&pspFileSystem, fileLoader, "data.ppdmp");
409
pspFileSystem.Mount("disc0:", umd);
410
411
return __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString);
412
}
413
414