Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/GameInfoCache.cpp
3185 views
1
2
// Copyright (c) 2013- PPSSPP Project.
3
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, version 2.0 or later versions.
7
8
// This program is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
// GNU General Public License 2.0 for more details.
12
13
// A copy of the GPL 2.0 should have been included with the program.
14
// If not, see http://www.gnu.org/licenses/
15
16
// Official git repository and contact information can be found at
17
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
18
19
#include "Common/Common.h"
20
21
#include <string>
22
#include <map>
23
#include <memory>
24
#include <algorithm>
25
26
#include "Common/GPU/thin3d.h"
27
#include "Common/Thread/ThreadManager.h"
28
#include "Common/File/VFS/VFS.h"
29
#include "Common/File/VFS/ZipFileReader.h"
30
#include "Common/File/FileUtil.h"
31
#include "Common/File/Path.h"
32
#include "Common/Render/ManagedTexture.h"
33
#include "Common/System/Request.h"
34
#include "Common/StringUtils.h"
35
#include "Common/TimeUtil.h"
36
#include "Core/FileSystems/ISOFileSystem.h"
37
#include "Core/FileSystems/DirectoryFileSystem.h"
38
#include "Core/FileSystems/VirtualDiscFileSystem.h"
39
#include "Core/HLE/sceUtility.h"
40
#include "Core/ELF/PBPReader.h"
41
#include "Core/SaveState.h"
42
#include "Core/System.h"
43
#include "Core/Loaders.h"
44
#include "Core/Util/GameManager.h"
45
#include "Core/Util/RecentFiles.h"
46
#include "Core/Config.h"
47
#include "UI/GameInfoCache.h"
48
49
GameInfoCache *g_gameInfoCache;
50
51
void GameInfoTex::Clear() {
52
if (!data.empty()) {
53
data.clear();
54
dataLoaded = false;
55
}
56
if (texture) {
57
texture->Release();
58
texture = nullptr;
59
}
60
timeLoaded = 0.0;
61
}
62
63
GameInfo::GameInfo(const Path &gamePath) : filePath_(gamePath) {
64
// here due to a forward decl.
65
fileType = IdentifiedFileType::UNKNOWN;
66
}
67
68
GameInfo::~GameInfo() {
69
std::lock_guard<std::mutex> guard(lock);
70
sndDataLoaded = false;
71
icon.Clear();
72
pic0.Clear();
73
pic1.Clear();
74
fileLoader.reset();
75
}
76
77
bool IsReasonableEbootDirectory(Path path) {
78
// First some sanity checks.
79
if (path == Path("/")) {
80
return false;
81
}
82
for (int i = 0; i < (int)PSPDirectories::COUNT; i++) {
83
if (path == GetSysDirectory((PSPDirectories)i)) {
84
return false;
85
}
86
}
87
return true;
88
}
89
90
static bool MoveFileToTrashOrDelete(const Path &path) {
91
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
92
// TODO: Way to see if it succeeded
93
System_MoveToTrash(path);
94
return true;
95
} else {
96
return File::Delete(path);
97
}
98
}
99
100
static bool MoveDirectoryTreeToTrashOrDelete(const Path &path) {
101
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
102
// TODO: Way to see if it succeeded
103
System_MoveToTrash(path);
104
return true;
105
} else {
106
return File::DeleteDirRecursively(path);
107
}
108
}
109
110
bool GameInfo::Delete() {
111
switch (fileType) {
112
case IdentifiedFileType::PSP_ISO:
113
case IdentifiedFileType::PSP_ISO_NP:
114
{
115
// Just delete the one file (TODO: handle two-disk games as well somehow).
116
Path fileToRemove = filePath_;
117
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
118
MoveFileToTrashOrDelete(fileToRemove);
119
g_recentFiles.Remove(filePath_.ToString());
120
return true;
121
}
122
case IdentifiedFileType::PSP_PBP_DIRECTORY:
123
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
124
{
125
// TODO: This could be handled by Core/Util/GameManager too somehow.
126
Path directoryToRemove = ResolvePBPDirectory(filePath_);
127
128
// Check that the directory isn't the base of the GAME folder, or something similarly stupid.
129
// This can happen if the PBP is misplaced, see issue #20187
130
if (!IsReasonableEbootDirectory(directoryToRemove)) {
131
// Just delete the eboot.
132
MoveFileToTrashOrDelete(filePath_);
133
g_recentFiles.Remove(filePath_.ToString());
134
return true;
135
}
136
137
// Delete the whole tree. We better be sure, see IsReasonableEbootDirectory.
138
INFO_LOG(Log::System, "Deleting directory %s", directoryToRemove.c_str());
139
if (!MoveDirectoryTreeToTrashOrDelete(directoryToRemove)) {
140
ERROR_LOG(Log::System, "Failed to delete file");
141
return false;
142
}
143
g_recentFiles.Clean();
144
return true;
145
}
146
case IdentifiedFileType::PSP_ELF:
147
case IdentifiedFileType::UNKNOWN_BIN:
148
case IdentifiedFileType::UNKNOWN_ELF:
149
case IdentifiedFileType::UNKNOWN_ISO:
150
case IdentifiedFileType::ARCHIVE_RAR:
151
case IdentifiedFileType::ARCHIVE_ZIP:
152
case IdentifiedFileType::ARCHIVE_7Z:
153
case IdentifiedFileType::PPSSPP_GE_DUMP:
154
{
155
const Path &fileToRemove = filePath_;
156
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
157
MoveFileToTrashOrDelete(fileToRemove);
158
g_recentFiles.Remove(filePath_.ToString());
159
return true;
160
}
161
162
case IdentifiedFileType::PPSSPP_SAVESTATE:
163
{
164
const Path &ppstPath = filePath_;
165
INFO_LOG(Log::System, "Deleting file %s", ppstPath.c_str());
166
MoveFileToTrashOrDelete(ppstPath);
167
const Path screenshotPath = filePath_.WithReplacedExtension(".ppst", ".jpg");
168
if (File::Exists(screenshotPath)) {
169
MoveFileToTrashOrDelete(screenshotPath);
170
}
171
return true;
172
}
173
174
default:
175
INFO_LOG(Log::System, "Don't know how to delete this type of file: %s", filePath_.c_str());
176
return false;
177
}
178
}
179
180
u64 GameInfo::GetSizeOnDiskInBytes() {
181
switch (fileType) {
182
case IdentifiedFileType::PSP_PBP_DIRECTORY:
183
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
184
return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));
185
case IdentifiedFileType::PSP_DISC_DIRECTORY:
186
return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());
187
default:
188
return GetFileLoader()->FileSize();
189
}
190
}
191
192
u64 GameInfo::GetSizeUncompressedInBytes() {
193
switch (fileType) {
194
case IdentifiedFileType::PSP_PBP_DIRECTORY:
195
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
196
return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));
197
case IdentifiedFileType::PSP_DISC_DIRECTORY:
198
return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());
199
default:
200
{
201
std::string errorString;
202
BlockDevice *blockDevice = ConstructBlockDevice(GetFileLoader().get(), &errorString);
203
if (blockDevice) {
204
u64 size = blockDevice->GetUncompressedSize();
205
delete blockDevice;
206
return size;
207
} else {
208
return GetFileLoader()->FileSize();
209
}
210
}
211
}
212
}
213
214
std::string GetFileDateAsString(const Path &filename) {
215
tm time;
216
if (File::GetModifTime(filename, time)) {
217
char buf[256];
218
switch (g_Config.iDateFormat) {
219
case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD:
220
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);
221
break;
222
case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY:
223
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time);
224
break;
225
case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY:
226
strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time);
227
break;
228
default: // Should never happen
229
return "";
230
}
231
return std::string(buf);
232
}
233
return "";
234
}
235
236
std::string GameInfo::GetMTime() const {
237
switch (fileType) {
238
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
239
return GetFileDateAsString(GetFilePath() / "PARAM.SFO");
240
case IdentifiedFileType::PSP_PBP_DIRECTORY:
241
return GetFileDateAsString(GetFilePath() / "EBOOT.PBP");
242
default:
243
return GetFileDateAsString(GetFilePath());
244
}
245
}
246
247
// Not too meaningful if the object itself is a savedata directory...
248
// Call this under lock.
249
std::vector<Path> GameInfo::GetSaveDataDirectories() {
250
_dbg_assert_(hasFlags & GameInfoFlags::PARAM_SFO); // so we know we have the ID.
251
Path memc = GetSysDirectory(DIRECTORY_SAVEDATA);
252
253
std::vector<Path> directories;
254
if (id.size() < 5) {
255
// Invalid game ID.
256
return directories;
257
}
258
259
std::vector<File::FileInfo> dirs;
260
const std::string &prefix = id;
261
File::GetFilesInDir(memc, &dirs, nullptr, 0, prefix);
262
263
for (size_t i = 0; i < dirs.size(); i++) {
264
directories.push_back(dirs[i].fullName);
265
}
266
267
return directories;
268
}
269
270
u64 GameInfo::GetGameSavedataSizeInBytes() {
271
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {
272
return 0;
273
}
274
std::vector<Path> saveDataDir = GetSaveDataDirectories();
275
276
u64 totalSize = 0;
277
u64 filesSizeInDir = 0;
278
for (size_t j = 0; j < saveDataDir.size(); j++) {
279
std::vector<File::FileInfo> fileInfo;
280
File::GetFilesInDir(saveDataDir[j], &fileInfo);
281
for (auto const &file : fileInfo) {
282
if (!file.isDirectory)
283
filesSizeInDir += file.size;
284
}
285
if (filesSizeInDir < 0xA00000) {
286
// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.
287
totalSize += filesSizeInDir;
288
}
289
filesSizeInDir = 0;
290
}
291
return totalSize;
292
}
293
294
u64 GameInfo::GetInstallDataSizeInBytes() {
295
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {
296
return 0;
297
}
298
std::vector<Path> saveDataDir = GetSaveDataDirectories();
299
300
u64 totalSize = 0;
301
u64 filesSizeInDir = 0;
302
for (size_t j = 0; j < saveDataDir.size(); j++) {
303
std::vector<File::FileInfo> fileInfo;
304
File::GetFilesInDir(saveDataDir[j], &fileInfo);
305
for (auto const &file : fileInfo) {
306
// TODO: Might want to recurse here? Don't know games that use directories
307
// for install-data though.
308
if (!file.isDirectory)
309
filesSizeInDir += file.size;
310
}
311
if (filesSizeInDir >= 0xA00000) {
312
// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.
313
// This is probably GameInstall data.
314
totalSize += filesSizeInDir;
315
}
316
filesSizeInDir = 0;
317
}
318
return totalSize;
319
}
320
321
bool GameInfo::CreateLoader() {
322
if (!fileLoader) {
323
std::lock_guard<std::mutex> guard(loaderLock);
324
fileLoader.reset(ConstructFileLoader(filePath_));
325
if (!fileLoader)
326
return false;
327
}
328
return true;
329
}
330
331
std::shared_ptr<FileLoader> GameInfo::GetFileLoader() {
332
if (filePath_.empty()) {
333
// Happens when workqueue tries to figure out priorities,
334
// because Priority() calls GetFileLoader()... gnarly.
335
return fileLoader;
336
}
337
338
std::lock_guard<std::mutex> guard(loaderLock);
339
if (!fileLoader) {
340
FileLoader *loader = ConstructFileLoader(filePath_);
341
fileLoader.reset(loader);
342
return fileLoader;
343
}
344
return fileLoader;
345
}
346
347
void GameInfo::DisposeFileLoader() {
348
std::lock_guard<std::mutex> guard(loaderLock);
349
fileLoader.reset();
350
}
351
352
bool GameInfo::DeleteAllSaveData() {
353
std::vector<Path> saveDataDir = GetSaveDataDirectories();
354
for (size_t j = 0; j < saveDataDir.size(); j++) {
355
INFO_LOG(Log::System, "Deleting savedata from %s", saveDataDir[j].c_str());
356
if (!MoveDirectoryTreeToTrashOrDelete(saveDataDir[j])) {
357
ERROR_LOG(Log::System, "Failed to delete savedata %s", saveDataDir[j].c_str());
358
}
359
}
360
return true;
361
}
362
363
void GameInfo::ParseParamSFO() {
364
title = paramSFO.GetValueString("TITLE");
365
id = paramSFO.GetValueString("DISC_ID");
366
id_version = id + "_" + paramSFO.GetValueString("DISC_VERSION");
367
disc_total = paramSFO.GetValueInt("DISC_TOTAL");
368
disc_number = paramSFO.GetValueInt("DISC_NUMBER");
369
// region = paramSFO.GetValueInt("REGION"); // Always seems to be 32768?
370
region = DetectGameRegionFromID(id);
371
}
372
373
std::string GameInfo::GetTitle() {
374
std::lock_guard<std::mutex> guard(lock);
375
if ((hasFlags & GameInfoFlags::PARAM_SFO) && !title.empty()) {
376
return title;
377
} else {
378
return filePath_.GetFilename();
379
}
380
}
381
382
void GameInfo::SetTitle(const std::string &newTitle) {
383
std::lock_guard<std::mutex> guard(lock);
384
title = newTitle;
385
}
386
387
void GameInfo::FinishPendingTextureLoads(Draw::DrawContext *draw) {
388
if (draw && icon.dataLoaded && !icon.texture) {
389
SetupTexture(draw, icon);
390
}
391
if (draw && pic0.dataLoaded && !pic0.texture) {
392
SetupTexture(draw, pic0);
393
}
394
if (draw && pic1.dataLoaded && !pic1.texture) {
395
SetupTexture(draw, pic1);
396
}
397
}
398
399
void GameInfo::SetupTexture(Draw::DrawContext *thin3d, GameInfoTex &tex) {
400
if (tex.timeLoaded) {
401
// Failed before, skip.
402
return;
403
}
404
if (tex.data.empty()) {
405
tex.timeLoaded = time_now_d();
406
return;
407
}
408
using namespace Draw;
409
// TODO: Use TempImage to semi-load the image in the worker task, then here we
410
// could just call CreateTextureFromTempImage.
411
tex.texture = CreateTextureFromFileData(thin3d, (const uint8_t *)tex.data.data(), tex.data.size(), ImageFileType::DETECT, false, GetTitle().c_str());
412
tex.timeLoaded = time_now_d();
413
if (!tex.texture) {
414
ERROR_LOG(Log::G3D, "Failed creating texture (%s) from %d-byte file", GetTitle().c_str(), (int)tex.data.size());
415
}
416
}
417
418
static bool ReadFileToString(IFileSystem *fs, const char *filename, std::string *contents, std::mutex *mtx) {
419
PSPFileInfo info = fs->GetFileInfo(filename);
420
if (!info.exists) {
421
return false;
422
}
423
424
int handle = fs->OpenFile(filename, FILEACCESS_READ);
425
if (handle < 0) {
426
return false;
427
}
428
if (mtx) {
429
std::string data;
430
data.resize(info.size);
431
size_t readSize = fs->ReadFile(handle, (u8 *)data.data(), info.size);
432
fs->CloseFile(handle);
433
if (readSize != info.size) {
434
return false;
435
}
436
std::lock_guard<std::mutex> lock(*mtx);
437
*contents = std::move(data);
438
} else {
439
contents->resize(info.size);
440
size_t readSize = fs->ReadFile(handle, (u8 *)contents->data(), info.size);
441
fs->CloseFile(handle);
442
if (readSize != info.size) {
443
return false;
444
}
445
}
446
return true;
447
}
448
449
static bool ReadLocalFileToString(const Path &path, std::string *contents, std::mutex *mtx) {
450
std::string data;
451
if (!File::ReadBinaryFileToString(path, &data)) {
452
return false;
453
}
454
if (mtx) {
455
std::lock_guard<std::mutex> lock(*mtx);
456
*contents = std::move(data);
457
} else {
458
*contents = std::move(data);
459
}
460
return true;
461
}
462
463
static bool ReadVFSToString(const char *filename, std::string *contents, std::mutex *mtx) {
464
size_t sz;
465
uint8_t *data = g_VFS.ReadFile(filename, &sz);
466
if (!data) {
467
return false;
468
}
469
if (mtx) {
470
std::lock_guard<std::mutex> lock(*mtx);
471
*contents = std::string((const char *)data, sz);
472
} else {
473
*contents = std::string((const char *)data, sz);
474
}
475
delete [] data;
476
return true;
477
}
478
479
static bool LoadReplacementImage(GameInfo *info, GameInfoTex *tex, const char *filename) {
480
if (!g_Config.bReplaceTextures) {
481
return false;
482
}
483
484
const Path customIconFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / filename;
485
const Path zipFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / "textures.zip";
486
if (ReadLocalFileToString(customIconFilename, &tex->data, &info->lock)) {
487
tex->dataLoaded = true;
488
return true;
489
} else if (ReadSingleFileFromZip(zipFilename, filename, &tex->data, &info->lock)) {
490
tex->dataLoaded = true;
491
return true;
492
} else {
493
return false;
494
}
495
}
496
497
class GameInfoWorkItem : public Task {
498
public:
499
GameInfoWorkItem(const Path &gamePath, std::shared_ptr<GameInfo> &info, GameInfoFlags flags)
500
: gamePath_(gamePath), info_(info), flags_(flags) {}
501
502
~GameInfoWorkItem() {
503
info_->DisposeFileLoader();
504
}
505
506
TaskType Type() const override {
507
return TaskType::IO_BLOCKING;
508
}
509
510
TaskPriority Priority() const override {
511
switch (gamePath_.Type()) {
512
case PathType::NATIVE:
513
case PathType::CONTENT_URI:
514
return TaskPriority::NORMAL;
515
516
default:
517
// Remote/network access.
518
return TaskPriority::LOW;
519
}
520
}
521
522
void Run() override {
523
// An early-return will result in the destructor running, where we can set
524
// flags like working and pending.
525
if (!info_->CreateLoader() || !info_->GetFileLoader() || !info_->GetFileLoader()->Exists()) {
526
// Mark everything requested as done, so
527
std::unique_lock<std::mutex> lock(info_->lock);
528
info_->MarkReadyNoLock(flags_);
529
ERROR_LOG(Log::Loader, "Failed getting game info for %s", info_->GetFilePath().ToVisualString().c_str());
530
return;
531
}
532
533
std::string errorString;
534
535
if (flags_ & GameInfoFlags::FILE_TYPE) {
536
info_->fileType = Identify_File(info_->GetFileLoader().get(), &errorString);
537
}
538
539
switch (info_->fileType) {
540
case IdentifiedFileType::PSP_PBP:
541
case IdentifiedFileType::PSP_PBP_DIRECTORY:
542
{
543
auto pbpLoader = info_->GetFileLoader();
544
if (info_->fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
545
Path ebootPath = ResolvePBPFile(gamePath_);
546
if (ebootPath != gamePath_) {
547
pbpLoader.reset(ConstructFileLoader(ebootPath));
548
}
549
}
550
551
PBPReader pbp(pbpLoader.get());
552
if (!pbp.IsValid()) {
553
if (pbp.IsELF()) {
554
goto handleELF;
555
}
556
ERROR_LOG(Log::Loader, "invalid pbp '%s'\n", pbpLoader->GetPath().c_str());
557
// We can't win here - just mark everything pending as fetched, and let the caller
558
// handle the missing data.
559
std::unique_lock<std::mutex> lock(info_->lock);
560
info_->MarkReadyNoLock(flags_);
561
return;
562
}
563
564
// First, PARAM.SFO.
565
if (flags_ & GameInfoFlags::PARAM_SFO) {
566
std::vector<u8> sfoData;
567
if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
568
std::lock_guard<std::mutex> lock(info_->lock);
569
info_->paramSFO.ReadSFO(sfoData);
570
info_->ParseParamSFO();
571
572
// Assuming PSP_PBP_DIRECTORY without ID or with disc_total < 1 in GAME dir must be homebrew
573
if ((info_->id.empty() || !info_->disc_total)
574
&& gamePath_.FilePathContainsNoCase("PSP/GAME/")
575
&& info_->fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
576
info_->id = g_paramSFO.GenerateFakeID(gamePath_);
577
info_->id_version = info_->id + "_1.00";
578
info_->region = GameRegion::HOMEBREW; // Homebrew
579
}
580
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
581
}
582
}
583
584
// Then, ICON0.PNG.
585
if (flags_ & GameInfoFlags::ICON) {
586
if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) {
587
// Nothing more to do
588
} else if (pbp.GetSubFileSize(PBP_ICON0_PNG) > 0) {
589
std::lock_guard<std::mutex> lock(info_->lock);
590
pbp.GetSubFileAsString(PBP_ICON0_PNG, &info_->icon.data);
591
} else {
592
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");
593
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");
594
// Try using png/jpg screenshots first
595
if (File::Exists(screenshot_png)) {
596
ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
597
} else if (File::Exists(screenshot_jpg)) {
598
ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
599
} else {
600
// Read standard icon
601
ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);
602
}
603
}
604
info_->icon.dataLoaded = true;
605
}
606
607
if (flags_ & GameInfoFlags::PIC0) {
608
if (pbp.GetSubFileSize(PBP_PIC0_PNG) > 0) {
609
std::string data;
610
pbp.GetSubFileAsString(PBP_PIC0_PNG, &data);
611
std::lock_guard<std::mutex> lock(info_->lock);
612
info_->pic0.data = std::move(data);
613
info_->pic0.dataLoaded = true;
614
}
615
}
616
if (flags_ & GameInfoFlags::PIC1) {
617
if (pbp.GetSubFileSize(PBP_PIC1_PNG) > 0) {
618
std::string data;
619
pbp.GetSubFileAsString(PBP_PIC1_PNG, &data);
620
std::lock_guard<std::mutex> lock(info_->lock);
621
info_->pic1.data = std::move(data);
622
info_->pic1.dataLoaded = true;
623
}
624
}
625
if (flags_ & GameInfoFlags::SND) {
626
if (pbp.GetSubFileSize(PBP_SND0_AT3) > 0) {
627
std::string data;
628
pbp.GetSubFileAsString(PBP_SND0_AT3, &data);
629
std::lock_guard<std::mutex> lock(info_->lock);
630
info_->sndFileData = std::move(data);
631
info_->sndDataLoaded = true;
632
}
633
}
634
}
635
break;
636
637
case IdentifiedFileType::PSP_ELF:
638
handleELF:
639
// An elf on its own has no usable information, no icons, no nothing.
640
if (flags_ & GameInfoFlags::PARAM_SFO) {
641
info_->id = g_paramSFO.GenerateFakeID(gamePath_);
642
info_->id_version = info_->id + "_1.00";
643
info_->region = GameRegion::HOMEBREW; // Homebrew
644
}
645
646
if (flags_ & GameInfoFlags::ICON) {
647
std::string id = g_paramSFO.GenerateFakeID(gamePath_);
648
// Due to the dependency of the BASIC info, we fetch it already here.
649
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.jpg");
650
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.png");
651
// Try using png/jpg screenshots first
652
if (File::Exists(screenshot_png)) {
653
ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
654
} else if (File::Exists(screenshot_jpg)) {
655
ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
656
} else {
657
// Read standard icon
658
VERBOSE_LOG(Log::Loader, "Loading unknown.png because there was an ELF");
659
ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);
660
}
661
info_->icon.dataLoaded = true;
662
}
663
break;
664
665
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
666
{
667
SequentialHandleAllocator handles;
668
VirtualDiscFileSystem umd(&handles, gamePath_);
669
670
if (flags_ & GameInfoFlags::PARAM_SFO) {
671
// Alright, let's fetch the PARAM.SFO.
672
std::string paramSFOcontents;
673
if (ReadFileToString(&umd, "/PARAM.SFO", &paramSFOcontents, 0)) {
674
std::lock_guard<std::mutex> lock(info_->lock);
675
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
676
info_->ParseParamSFO();
677
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
678
}
679
}
680
if (flags_ & GameInfoFlags::ICON) {
681
ReadFileToString(&umd, "/ICON0.PNG", &info_->icon.data, &info_->lock);
682
info_->icon.dataLoaded = true;
683
}
684
if (flags_ & GameInfoFlags::PIC1) {
685
ReadFileToString(&umd, "/PIC1.PNG", &info_->pic1.data, &info_->lock);
686
info_->pic1.dataLoaded = true;
687
}
688
break;
689
}
690
691
case IdentifiedFileType::PPSSPP_SAVESTATE:
692
{
693
if (flags_ & GameInfoFlags::PARAM_SFO) {
694
info_->SetTitle(SaveState::GetTitle(gamePath_));
695
std::lock_guard<std::mutex> lock(info_->lock);
696
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
697
}
698
699
// Let's use the screenshot as an icon, too.
700
if (flags_ & GameInfoFlags::ICON) {
701
Path screenshotPath = gamePath_.WithReplacedExtension(".ppst", ".jpg");
702
if (ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {
703
info_->icon.dataLoaded = true;
704
}
705
}
706
break;
707
}
708
709
case IdentifiedFileType::PPSSPP_GE_DUMP:
710
{
711
if (flags_ & GameInfoFlags::ICON) {
712
Path screenshotPath = gamePath_.WithReplacedExtension(".ppdmp", ".png");
713
// Let's use the comparison screenshot as an icon, if it exists.
714
if (ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {
715
info_->icon.dataLoaded = true;
716
}
717
}
718
break;
719
}
720
721
case IdentifiedFileType::PSP_DISC_DIRECTORY:
722
{
723
SequentialHandleAllocator handles;
724
VirtualDiscFileSystem umd(&handles, gamePath_);
725
726
// Alright, let's fetch the PARAM.SFO.
727
if (flags_ & GameInfoFlags::PARAM_SFO) {
728
std::string paramSFOcontents;
729
if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", &paramSFOcontents, nullptr)) {
730
std::lock_guard<std::mutex> lock(info_->lock);
731
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
732
info_->ParseParamSFO();
733
}
734
}
735
736
if (flags_ & GameInfoFlags::ICON) {
737
ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock);
738
info_->icon.dataLoaded = true;
739
}
740
if (flags_ & GameInfoFlags::PIC0) {
741
ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);
742
info_->pic0.dataLoaded = true;
743
}
744
if (flags_ & GameInfoFlags::PIC1) {
745
ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock);
746
info_->pic1.dataLoaded = true;
747
}
748
if (flags_ & GameInfoFlags::SND) {
749
ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock);
750
info_->pic1.dataLoaded = true;
751
}
752
break;
753
}
754
755
case IdentifiedFileType::PSP_ISO:
756
case IdentifiedFileType::PSP_ISO_NP:
757
{
758
SequentialHandleAllocator handles;
759
// Let's assume it's an ISO.
760
// TODO: This will currently read in the whole directory tree. Not really necessary for just a
761
// few files.
762
auto fl = info_->GetFileLoader();
763
if (!fl) {
764
// BAD! Can't win here.
765
ERROR_LOG(Log::Loader, "Failed getting game info for ISO %s", info_->GetFilePath().ToVisualString().c_str());
766
std::unique_lock<std::mutex> lock(info_->lock);
767
info_->MarkReadyNoLock(flags_);
768
return;
769
}
770
BlockDevice *bd = ConstructBlockDevice(info_->GetFileLoader().get(), &errorString);
771
if (!bd) {
772
ERROR_LOG(Log::Loader, "Failed constructing block device for ISO %s: %s", info_->GetFilePath().ToVisualString().c_str(), errorString.c_str());
773
std::unique_lock<std::mutex> lock(info_->lock);
774
info_->MarkReadyNoLock(flags_);
775
return;
776
}
777
ISOFileSystem umd(&handles, bd);
778
779
// Alright, let's fetch the PARAM.SFO.
780
if (flags_ & GameInfoFlags::PARAM_SFO) {
781
std::string paramSFOcontents;
782
if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", &paramSFOcontents, nullptr)) {
783
{
784
std::lock_guard<std::mutex> lock(info_->lock);
785
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
786
info_->ParseParamSFO();
787
788
// quick-update the info while we have the lock, so we don't need to wait for the image load to display the title.
789
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
790
}
791
}
792
}
793
794
if (flags_ & GameInfoFlags::PIC0) {
795
info_->pic0.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);
796
}
797
798
if (flags_ & GameInfoFlags::PIC1) {
799
info_->pic1.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock);
800
}
801
802
if (flags_ & GameInfoFlags::SND) {
803
info_->sndDataLoaded = ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock);
804
}
805
806
// Fall back to unknown icon if ISO is broken/is a homebrew ISO, override is allowed though
807
// First, do try to get an icon from the replacement texture pack, if available.
808
if (flags_ & GameInfoFlags::ICON) {
809
if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) {
810
// Nothing more to do
811
} else if (ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock)) {
812
info_->icon.dataLoaded = true;
813
} else {
814
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");
815
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");
816
// Try using png/jpg screenshots first
817
if (File::Exists(screenshot_png))
818
info_->icon.dataLoaded = ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
819
else if (File::Exists(screenshot_jpg))
820
info_->icon.dataLoaded = ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
821
else {
822
DEBUG_LOG(Log::Loader, "Loading unknown.png because no icon was found");
823
info_->icon.dataLoaded = ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);
824
}
825
}
826
}
827
break;
828
}
829
830
case IdentifiedFileType::ARCHIVE_ZIP:
831
if (flags_ & GameInfoFlags::ICON) {
832
ReadVFSToString("zip.png", &info_->icon.data, &info_->lock);
833
info_->icon.dataLoaded = true;
834
}
835
break;
836
837
case IdentifiedFileType::ARCHIVE_RAR:
838
if (flags_ & GameInfoFlags::ICON) {
839
ReadVFSToString("rargray.png", &info_->icon.data, &info_->lock);
840
info_->icon.dataLoaded = true;
841
}
842
break;
843
844
case IdentifiedFileType::ARCHIVE_7Z:
845
if (flags_ & GameInfoFlags::ICON) {
846
ReadVFSToString("7z.png", &info_->icon.data, &info_->lock);
847
info_->icon.dataLoaded = true;
848
}
849
break;
850
851
case IdentifiedFileType::NORMAL_DIRECTORY:
852
default:
853
break;
854
}
855
856
if (flags_ & GameInfoFlags::PARAM_SFO) {
857
// We fetch the hasConfig together with the params, since that's what fills out the id.
858
info_->hasConfig = g_Config.hasGameConfig(info_->id);
859
}
860
861
if (flags_ & GameInfoFlags::SIZE) {
862
std::lock_guard<std::mutex> lock(info_->lock);
863
info_->gameSizeOnDisk = info_->GetSizeOnDiskInBytes();
864
switch (info_->fileType) {
865
case IdentifiedFileType::PSP_ISO:
866
case IdentifiedFileType::PSP_ISO_NP:
867
case IdentifiedFileType::PSP_DISC_DIRECTORY:
868
case IdentifiedFileType::PSP_PBP:
869
case IdentifiedFileType::PSP_PBP_DIRECTORY:
870
info_->saveDataSize = info_->GetGameSavedataSizeInBytes();
871
info_->installDataSize = info_->GetInstallDataSizeInBytes();
872
break;
873
default:
874
info_->saveDataSize = 0;
875
info_->installDataSize = 0;
876
break;
877
}
878
}
879
if (flags_ & GameInfoFlags::UNCOMPRESSED_SIZE) {
880
info_->gameSizeUncompressed = info_->GetSizeUncompressedInBytes();
881
}
882
883
// Time to update the flags.
884
std::unique_lock<std::mutex> lock(info_->lock);
885
info_->MarkReadyNoLock(flags_);
886
// INFO_LOG(Log::System, "Completed writing info for %s", info_->GetTitle().c_str());
887
}
888
889
private:
890
Path gamePath_;
891
std::shared_ptr<GameInfo> info_;
892
GameInfoFlags flags_{};
893
894
DISALLOW_COPY_AND_ASSIGN(GameInfoWorkItem);
895
};
896
897
GameInfoCache::GameInfoCache() {
898
Init();
899
}
900
901
GameInfoCache::~GameInfoCache() {
902
Clear();
903
Shutdown();
904
}
905
906
void GameInfoCache::Init() {}
907
908
void GameInfoCache::Shutdown() {
909
CancelAll();
910
}
911
912
void GameInfoCache::Clear() {
913
CancelAll();
914
915
std::lock_guard<std::mutex> lock(mapLock_);
916
info_.clear();
917
}
918
919
void GameInfoCache::CancelAll() {
920
std::lock_guard<std::mutex> lock(mapLock_);
921
for (auto info : info_) {
922
// GetFileLoader will create one if there isn't one already.
923
// Avoid that by checking.
924
if (info.second->HasFileLoader()) {
925
auto fl = info.second->GetFileLoader();
926
if (fl) {
927
fl->Cancel();
928
}
929
}
930
}
931
}
932
933
void GameInfoCache::FlushBGs() {
934
std::lock_guard<std::mutex> lock(mapLock_);
935
for (auto iter = info_.begin(); iter != info_.end(); iter++) {
936
std::lock_guard<std::mutex> lock(iter->second->lock);
937
iter->second->pic0.Clear();
938
iter->second->pic1.Clear();
939
if (!iter->second->sndFileData.empty()) {
940
iter->second->sndFileData.clear();
941
iter->second->sndDataLoaded = false;
942
}
943
iter->second->hasFlags &= ~(GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::SND);
944
}
945
}
946
947
void GameInfoCache::PurgeType(IdentifiedFileType fileType) {
948
bool retry = false;
949
int retryCount = 10;
950
// Trickery to avoid sleeping with the lock held.
951
do {
952
if (retry) {
953
retryCount--;
954
if (retryCount == 0) {
955
break;
956
}
957
}
958
retry = false;
959
{
960
std::lock_guard<std::mutex> lock(mapLock_);
961
for (auto iter = info_.begin(); iter != info_.end();) {
962
auto &info = iter->second;
963
if (!(info->hasFlags & GameInfoFlags::FILE_TYPE)) {
964
iter++;
965
continue;
966
}
967
if (info->fileType != fileType) {
968
iter++;
969
continue;
970
}
971
// TODO: Find a better way to wait here.
972
if (info->pendingFlags != (GameInfoFlags)0) {
973
INFO_LOG(Log::Loader, "%s: pending flags %08x, retrying", info->GetTitle().c_str(), (int)info->pendingFlags);
974
retry = true;
975
break;
976
}
977
iter = info_.erase(iter);
978
}
979
}
980
981
sleep_ms(10, "game-info-cache-purge-poll");
982
} while (retry);
983
}
984
985
// Call on the main thread ONLY - that is from stuff called from NativeFrame.
986
// Can also be called from the audio thread for menu background music, but that cannot request images!
987
std::shared_ptr<GameInfo> GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags) {
988
const std::string &pathStr = gamePath.ToString();
989
990
// _dbg_assert_(gamePath != GetSysDirectory(DIRECTORY_SAVEDATA));
991
992
// This is always needed to determine the method to get the other info, so make sure it's computed first.
993
wantFlags |= GameInfoFlags::FILE_TYPE;
994
995
mapLock_.lock();
996
997
auto iter = info_.find(pathStr);
998
if (iter != info_.end()) {
999
// There's already a structure about this game. Let's check.
1000
std::shared_ptr<GameInfo> info = iter->second;
1001
mapLock_.unlock();
1002
1003
info->FinishPendingTextureLoads(draw);
1004
info->lastAccessedTime = time_now_d();
1005
GameInfoFlags wanted = (GameInfoFlags)0;
1006
{
1007
// Careful now!
1008
std::unique_lock<std::mutex> lock(info->lock);
1009
GameInfoFlags hasFlags = info->hasFlags | info->pendingFlags; // We don't want to re-fetch data that we have, so or in pendingFlags.
1010
wanted = (GameInfoFlags)((int)wantFlags & ~(int)hasFlags); // & is reserved for testing. ugh.
1011
info->pendingFlags |= wanted;
1012
}
1013
if (wanted != (GameInfoFlags)0) {
1014
// We're missing info that we want. Go get it!
1015
GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wanted);
1016
g_threadManager.EnqueueTask(item);
1017
}
1018
return info;
1019
}
1020
1021
std::shared_ptr<GameInfo> info = std::make_shared<GameInfo>(gamePath);
1022
info->pendingFlags = wantFlags;
1023
info->lastAccessedTime = time_now_d();
1024
info_.insert(std::make_pair(pathStr, info));
1025
mapLock_.unlock();
1026
1027
// Just get all the stuff we wanted.
1028
GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wantFlags);
1029
g_threadManager.EnqueueTask(item);
1030
return info;
1031
}
1032
1033