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 <algorithm>
19 #include <cstdio>
20
21 #include "Common/File/FileUtil.h"
22 #include "Common/File/Path.h"
23 #include "Common/StringUtils.h"
24 #include "Core/FileLoaders/CachingFileLoader.h"
25 #include "Core/FileLoaders/DiskCachingFileLoader.h"
26 #include "Core/FileLoaders/HTTPFileLoader.h"
27 #include "Core/FileLoaders/LocalFileLoader.h"
28 #include "Core/FileLoaders/RetryingFileLoader.h"
29 #include "Core/FileSystems/MetaFileSystem.h"
30 #include "Core/PSPLoaders.h"
31 #include "Core/MemMap.h"
32 #include "Core/Loaders.h"
33 #include "Core/System.h"
34 #include "Core/ELF/PBPReader.h"
35 #include "Core/ELF/ParamSFO.h"
36
37 static std::map<std::string, std::unique_ptr<FileLoaderFactory>> factories;
38
RegisterFileLoaderFactory(std::string prefix,std::unique_ptr<FileLoaderFactory> factory)39 void RegisterFileLoaderFactory(std::string prefix, std::unique_ptr<FileLoaderFactory> factory) {
40 factories[prefix] = std::move(factory);
41 }
42
ConstructFileLoader(const Path & filename)43 FileLoader *ConstructFileLoader(const Path &filename) {
44 if (filename.Type() == PathType::HTTP) {
45 FileLoader *baseLoader = new RetryingFileLoader(new HTTPFileLoader(filename));
46 // For headless, avoid disk caching since it's usually used for tests that might mutate.
47 if (!PSP_CoreParameter().headLess) {
48 baseLoader = new DiskCachingFileLoader(baseLoader);
49 }
50 return new CachingFileLoader(baseLoader);
51 }
52
53 for (auto &iter : factories) {
54 if (startsWith(filename.ToString(), iter.first)) {
55 return iter.second->ConstructFileLoader(filename);
56 }
57 }
58 return new LocalFileLoader(filename);
59 }
60
61 // TODO : improve, look in the file more
Identify_File(FileLoader * fileLoader,std::string * errorString)62 IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorString) {
63 *errorString = "";
64 if (fileLoader == nullptr) {
65 *errorString = "Invalid fileLoader";
66 return IdentifiedFileType::ERROR_IDENTIFYING;
67 }
68 if (fileLoader->GetPath().size() == 0) {
69 *errorString = "Invalid filename " + fileLoader->GetPath().ToString();
70 return IdentifiedFileType::ERROR_IDENTIFYING;
71 }
72
73 if (!fileLoader->Exists()) {
74 *errorString = "IdentifyFile: File doesn't exist: " + fileLoader->GetPath().ToString();
75 return IdentifiedFileType::ERROR_IDENTIFYING;
76 }
77
78 std::string extension = fileLoader->GetPath().GetFileExtension();
79 if (extension == ".iso") {
80 // may be a psx iso, they have 2352 byte sectors. You never know what some people try to open
81 if ((fileLoader->FileSize() % 2352) == 0) {
82 unsigned char sync[12];
83 fileLoader->ReadAt(0, 12, sync);
84
85 // each sector in a mode2 image starts with these 12 bytes
86 if (memcmp(sync,"\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12) == 0) {
87 *errorString = "ISO in Mode 2: Not a PSP game";
88 return IdentifiedFileType::ISO_MODE2;
89 }
90
91 // maybe it also just happened to have that size, let's assume it's a PSP ISO and error out later if it's not.
92 }
93 return IdentifiedFileType::PSP_ISO;
94 } else if (extension == ".cso") {
95 return IdentifiedFileType::PSP_ISO;
96 } else if (extension == ".ppst") {
97 return IdentifiedFileType::PPSSPP_SAVESTATE;
98 } else if (extension == ".ppdmp") {
99 char data[8]{};
100 fileLoader->ReadAt(0, 8, data);
101 if (memcmp(data, "PPSSPPGE", 8) == 0) {
102 return IdentifiedFileType::PPSSPP_GE_DUMP;
103 }
104 }
105
106 // First, check if it's a directory with an EBOOT.PBP in it.
107 if (fileLoader->IsDirectory()) {
108 Path filename = fileLoader->GetPath();
109 if (filename.size() > 4) {
110 // Check for existence of EBOOT.PBP, as required for "Directory games".
111 if (File::Exists(filename / "EBOOT.PBP")) {
112 return IdentifiedFileType::PSP_PBP_DIRECTORY;
113 }
114
115 // check if it's a disc directory
116 if (File::Exists(filename / "PSP_GAME")) {
117 return IdentifiedFileType::PSP_DISC_DIRECTORY;
118 }
119
120 // Not that, okay, let's guess it's a savedata directory if it has a param.sfo...
121 if (File::Exists(filename / "PARAM.SFO")) {
122 return IdentifiedFileType::PSP_SAVEDATA_DIRECTORY;
123 }
124 }
125
126 return IdentifiedFileType::NORMAL_DIRECTORY;
127 }
128
129 u32_le id;
130
131 size_t readSize = fileLoader->ReadAt(0, 4, 1, &id);
132 if (readSize != 1) {
133 *errorString = "Failed to read identification bytes";
134 return IdentifiedFileType::ERROR_IDENTIFYING;
135 }
136
137 u32_le psar_offset = 0, psar_id = 0;
138 u32 _id = id;
139 if (!memcmp(&_id, "PK\x03\x04", 4) || !memcmp(&_id, "PK\x05\x06", 4) || !memcmp(&_id, "PK\x07\x08", 4)) {
140 return IdentifiedFileType::ARCHIVE_ZIP;
141 } else if (!memcmp(&_id, "\x00PBP", 4)) {
142 fileLoader->ReadAt(0x24, 4, 1, &psar_offset);
143 fileLoader->ReadAt(psar_offset, 4, 1, &psar_id);
144 } else if (!memcmp(&_id, "Rar!", 4)) {
145 return IdentifiedFileType::ARCHIVE_RAR;
146 }
147
148 if (id == 'FLE\x7F') {
149 Path filename = fileLoader->GetPath();
150 // There are a few elfs misnamed as pbp (like Trig Wars), accept that.
151 if (extension == ".plf" || strstr(filename.GetFilename().c_str(), "BOOT.BIN") ||
152 extension == ".elf" || extension == ".prx" || extension == ".pbp") {
153 return IdentifiedFileType::PSP_ELF;
154 }
155 return IdentifiedFileType::UNKNOWN_ELF;
156 } else if (id == 'PBP\x00') {
157 // Do this PS1 eboot check FIRST before checking other eboot types.
158 // It seems like some are malformed and slip through the PSAR check below.
159 PBPReader pbp(fileLoader);
160 if (pbp.IsValid() && !pbp.IsELF()) {
161 std::vector<u8> sfoData;
162 if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
163 ParamSFOData paramSFO;
164 paramSFO.ReadSFO(sfoData);
165 // PS1 Eboots are supposed to use "ME" as their PARAM SFO category.
166 // If they don't, and they're still malformed (e.g. PSISOIMG0000 isn't found), there's nothing we can do.
167 if (paramSFO.GetValueString("CATEGORY") == "ME")
168 return IdentifiedFileType::PSP_PS1_PBP;
169 }
170 }
171
172 if (psar_id == 'MUPN') {
173 return IdentifiedFileType::PSP_ISO_NP;
174 }
175 // PS1 PSAR begins with "PSISOIMG0000"
176 if (psar_id == 'SISP') {
177 return IdentifiedFileType::PSP_PS1_PBP;
178 }
179
180 // Let's check if we got pointed to a PBP within such a directory.
181 // If so we just move up and return the directory itself as the game.
182 // If loading from memstick...
183 if (fileLoader->GetPath().FilePathContains("PSP/GAME/")) {
184 return IdentifiedFileType::PSP_PBP_DIRECTORY;
185 }
186 return IdentifiedFileType::PSP_PBP;
187 } else if (extension == ".pbp") {
188 ERROR_LOG(LOADER, "A PBP with the wrong magic number?");
189 return IdentifiedFileType::PSP_PBP;
190 } else if (extension == ".bin") {
191 return IdentifiedFileType::UNKNOWN_BIN;
192 } else if (extension == ".zip") {
193 return IdentifiedFileType::ARCHIVE_ZIP;
194 } else if (extension == ".rar") {
195 return IdentifiedFileType::ARCHIVE_RAR;
196 } else if (extension == ".r00") {
197 return IdentifiedFileType::ARCHIVE_RAR;
198 } else if (extension == ".r01") {
199 return IdentifiedFileType::ARCHIVE_RAR;
200 } else if (extension == ".7z") {
201 return IdentifiedFileType::ARCHIVE_7Z;
202 }
203 return IdentifiedFileType::UNKNOWN;
204 }
205
ResolveFileLoaderTarget(FileLoader * fileLoader)206 FileLoader *ResolveFileLoaderTarget(FileLoader *fileLoader) {
207 std::string errorString;
208 IdentifiedFileType type = Identify_File(fileLoader, &errorString);
209 if (type == IdentifiedFileType::PSP_PBP_DIRECTORY) {
210 const Path ebootFilename = ResolvePBPFile(fileLoader->GetPath());
211 if (ebootFilename != fileLoader->GetPath()) {
212 // Switch fileLoader to the actual EBOOT.
213 delete fileLoader;
214 fileLoader = ConstructFileLoader(ebootFilename);
215 }
216 }
217 return fileLoader;
218 }
219
ResolvePBPDirectory(const Path & filename)220 Path ResolvePBPDirectory(const Path &filename) {
221 if (filename.GetFilename() == "EBOOT.PBP") {
222 return filename.NavigateUp();
223 } else {
224 return filename;
225 }
226 }
227
ResolvePBPFile(const Path & filename)228 Path ResolvePBPFile(const Path &filename) {
229 if (filename.GetFilename() != "EBOOT.PBP") {
230 return filename / "EBOOT.PBP";
231 } else {
232 return filename;
233 }
234 }
235
LoadFile(FileLoader ** fileLoaderPtr,std::string * error_string)236 bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) {
237 FileLoader *&fileLoader = *fileLoaderPtr;
238 // Note that this can modify filename!
239 IdentifiedFileType type = Identify_File(fileLoader, error_string);
240 switch (type) {
241 case IdentifiedFileType::PSP_PBP_DIRECTORY:
242 {
243 // TODO: Perhaps we should/can never get here now?
244 fileLoader = ResolveFileLoaderTarget(fileLoader);
245 if (fileLoader->Exists()) {
246 INFO_LOG(LOADER, "File is a PBP in a directory!");
247 IdentifiedFileType ebootType = Identify_File(fileLoader, error_string);
248 if (ebootType == IdentifiedFileType::PSP_ISO_NP) {
249 InitMemoryForGameISO(fileLoader);
250 pspFileSystem.SetStartingDirectory("disc0:/PSP_GAME/USRDIR");
251 return Load_PSP_ISO(fileLoader, error_string);
252 }
253 else if (ebootType == IdentifiedFileType::PSP_PS1_PBP) {
254 *error_string = "PS1 EBOOTs are not supported by PPSSPP.";
255 coreState = CORE_BOOT_ERROR;
256 return false;
257 } else if (ebootType == IdentifiedFileType::ERROR_IDENTIFYING) {
258 // IdentifyFile will have written to errorString.
259 coreState = CORE_BOOT_ERROR;
260 return false;
261 }
262
263 std::string dir = fileLoader->GetPath().GetDirectory();
264 size_t pos = dir.find("PSP/GAME/");
265 if (pos != std::string::npos) {
266 dir = ResolvePBPDirectory(Path(dir)).ToString();
267 pspFileSystem.SetStartingDirectory("ms0:/" + dir.substr(pos));
268 }
269 return Load_PSP_ELF_PBP(fileLoader, error_string);
270 } else {
271 *error_string = "No EBOOT.PBP, misidentified game";
272 coreState = CORE_BOOT_ERROR;
273 return false;
274 }
275 }
276
277 case IdentifiedFileType::PSP_PBP:
278 case IdentifiedFileType::PSP_ELF:
279 {
280 INFO_LOG(LOADER, "File is an ELF or loose PBP!");
281 return Load_PSP_ELF_PBP(fileLoader, error_string);
282 }
283
284 case IdentifiedFileType::PSP_ISO:
285 case IdentifiedFileType::PSP_ISO_NP:
286 case IdentifiedFileType::PSP_DISC_DIRECTORY: // behaves the same as the mounting is already done by now
287 pspFileSystem.SetStartingDirectory("disc0:/PSP_GAME/USRDIR");
288 return Load_PSP_ISO(fileLoader, error_string);
289
290 case IdentifiedFileType::PSP_PS1_PBP:
291 *error_string = "PS1 EBOOTs are not supported by PPSSPP.";
292 break;
293
294 case IdentifiedFileType::ARCHIVE_RAR:
295 #ifdef WIN32
296 *error_string = "RAR file detected (Require WINRAR)";
297 #else
298 *error_string = "RAR file detected (Require UnRAR)";
299 #endif
300 break;
301
302 case IdentifiedFileType::ARCHIVE_ZIP:
303 #ifdef WIN32
304 *error_string = "ZIP file detected (Require WINRAR)";
305 #else
306 *error_string = "ZIP file detected (Require UnRAR)";
307 #endif
308 break;
309
310 case IdentifiedFileType::ARCHIVE_7Z:
311 #ifdef WIN32
312 *error_string = "7z file detected (Require 7-Zip)";
313 #else
314 *error_string = "7z file detected (Require 7-Zip)";
315 #endif
316 break;
317
318 case IdentifiedFileType::ISO_MODE2:
319 *error_string = "PSX game image detected.";
320 break;
321
322 case IdentifiedFileType::NORMAL_DIRECTORY:
323 ERROR_LOG(LOADER, "Just a directory.");
324 *error_string = "Just a directory.";
325 break;
326
327 case IdentifiedFileType::PPSSPP_SAVESTATE:
328 *error_string = "This is a saved state, not a game."; // Actually, we could make it load it...
329 break;
330
331 case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
332 *error_string = "This is save data, not a game."; // Actually, we could make it load it...
333 break;
334
335 case IdentifiedFileType::PPSSPP_GE_DUMP:
336 return Load_PSP_GE_Dump(fileLoader, error_string);
337
338 case IdentifiedFileType::UNKNOWN_BIN:
339 case IdentifiedFileType::UNKNOWN_ELF:
340 case IdentifiedFileType::UNKNOWN:
341 ERROR_LOG(LOADER, "Unknown file type: %s (%s)", fileLoader->GetPath().c_str(), error_string->c_str());
342 *error_string = "Unknown file type: " + fileLoader->GetPath().ToString();
343 break;
344
345 case IdentifiedFileType::ERROR_IDENTIFYING:
346 *error_string = *error_string + ": " + (fileLoader ? fileLoader->LatestError() : "");
347 ERROR_LOG(LOADER, "Error while identifying file: %s", error_string->c_str());
348 break;
349
350 default:
351 *error_string = StringFromFormat("Unhandled identified file type %d", (int)type);
352 ERROR_LOG(LOADER, "%s", error_string->c_str());
353 break;
354 }
355
356 coreState = CORE_BOOT_ERROR;
357 return false;
358 }
359
UmdReplace(const Path & filepath,std::string & error)360 bool UmdReplace(const Path &filepath, std::string &error) {
361 IFileSystem *currentUMD = pspFileSystem.GetSystem("disc0:");
362
363 if (!currentUMD) {
364 error = "has no disc";
365 return false;
366 }
367
368 FileLoader *loadedFile = ConstructFileLoader(filepath);
369
370 if (!loadedFile->Exists()) {
371 delete loadedFile;
372 error = loadedFile->GetPath().ToVisualString() + " doesn't exist";
373 return false;
374 }
375 UpdateLoadedFile(loadedFile);
376
377 loadedFile = ResolveFileLoaderTarget(loadedFile);
378
379
380 std::string errorString;
381 IdentifiedFileType type = Identify_File(loadedFile, &errorString);
382
383 switch (type) {
384 case IdentifiedFileType::PSP_ISO:
385 case IdentifiedFileType::PSP_ISO_NP:
386 case IdentifiedFileType::PSP_DISC_DIRECTORY:
387 if (!ReInitMemoryForGameISO(loadedFile)) {
388 error = "reinit memory failed";
389 return false;
390 }
391
392 break;
393 default:
394 error = "Unsupported file type: " + std::to_string((int)type) + " " + errorString;
395 return false;
396 break;
397 }
398 return true;
399 }
400