CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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