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