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