CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/GameInfoCache.cpp
Views: 1401
// Copyright (c) 2013- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/Common.h"1819#include <string>20#include <map>21#include <memory>22#include <algorithm>2324#include "Common/GPU/thin3d.h"25#include "Common/Thread/ThreadManager.h"26#include "Common/File/VFS/VFS.h"27#include "Common/File/FileUtil.h"28#include "Common/File/Path.h"29#include "Common/Render/ManagedTexture.h"30#include "Common/StringUtils.h"31#include "Common/TimeUtil.h"32#include "Core/FileSystems/ISOFileSystem.h"33#include "Core/FileSystems/DirectoryFileSystem.h"34#include "Core/FileSystems/VirtualDiscFileSystem.h"35#include "Core/HLE/sceUtility.h"36#include "Core/ELF/PBPReader.h"37#include "Core/SaveState.h"38#include "Core/System.h"39#include "Core/Loaders.h"40#include "Core/Util/GameManager.h"41#include "Core/Config.h"42#include "UI/GameInfoCache.h"4344GameInfoCache *g_gameInfoCache;4546void GameInfoTex::Clear() {47if (!data.empty()) {48data.clear();49dataLoaded = false;50}51if (texture) {52texture->Release();53texture = nullptr;54}55timeLoaded = 0.0;56}5758GameInfo::GameInfo(const Path &gamePath) : filePath_(gamePath) {59// here due to a forward decl.60fileType = IdentifiedFileType::UNKNOWN;61}6263GameInfo::~GameInfo() {64std::lock_guard<std::mutex> guard(lock);65sndDataLoaded = false;66icon.Clear();67pic0.Clear();68pic1.Clear();69fileLoader.reset();70}7172bool GameInfo::Delete() {73switch (fileType) {74case IdentifiedFileType::PSP_ISO:75case IdentifiedFileType::PSP_ISO_NP:76{77// Just delete the one file (TODO: handle two-disk games as well somehow).78Path fileToRemove = filePath_;79INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());80File::Delete(fileToRemove);81g_Config.RemoveRecent(filePath_.ToString());82return true;83}84case IdentifiedFileType::PSP_PBP_DIRECTORY:85case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:86{87// TODO: This could be handled by Core/Util/GameManager too somehow.88Path directoryToRemove = ResolvePBPDirectory(filePath_);89INFO_LOG(Log::System, "Deleting directory %s", directoryToRemove.c_str());90if (!File::DeleteDirRecursively(directoryToRemove)) {91ERROR_LOG(Log::System, "Failed to delete file");92return false;93}94g_Config.CleanRecent();95return true;96}97case IdentifiedFileType::PSP_ELF:98case IdentifiedFileType::UNKNOWN_BIN:99case IdentifiedFileType::UNKNOWN_ELF:100case IdentifiedFileType::UNKNOWN_ISO:101case IdentifiedFileType::ARCHIVE_RAR:102case IdentifiedFileType::ARCHIVE_ZIP:103case IdentifiedFileType::ARCHIVE_7Z:104case IdentifiedFileType::PPSSPP_GE_DUMP:105{106const Path &fileToRemove = filePath_;107INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());108File::Delete(fileToRemove);109g_Config.RemoveRecent(filePath_.ToString());110return true;111}112113case IdentifiedFileType::PPSSPP_SAVESTATE:114{115const Path &ppstPath = filePath_;116INFO_LOG(Log::System, "Deleting file %s", ppstPath.c_str());117File::Delete(ppstPath);118const Path screenshotPath = filePath_.WithReplacedExtension(".ppst", ".jpg");119if (File::Exists(screenshotPath)) {120File::Delete(screenshotPath);121}122return true;123}124125default:126INFO_LOG(Log::System, "Don't know how to delete this type of file: %s", filePath_.c_str());127return false;128}129}130131u64 GameInfo::GetSizeOnDiskInBytes() {132switch (fileType) {133case IdentifiedFileType::PSP_PBP_DIRECTORY:134case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:135return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));136case IdentifiedFileType::PSP_DISC_DIRECTORY:137return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());138default:139return GetFileLoader()->FileSize();140}141}142143u64 GameInfo::GetSizeUncompressedInBytes() {144switch (fileType) {145case IdentifiedFileType::PSP_PBP_DIRECTORY:146case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:147return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));148case IdentifiedFileType::PSP_DISC_DIRECTORY:149return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());150default:151{152BlockDevice *blockDevice = constructBlockDevice(GetFileLoader().get());153if (blockDevice) {154u64 size = blockDevice->GetUncompressedSize();155delete blockDevice;156return size;157} else {158return GetFileLoader()->FileSize();159}160}161}162}163164std::string GetFileDateAsString(const Path &filename) {165tm time;166if (File::GetModifTime(filename, time)) {167char buf[256];168switch (g_Config.iDateFormat) {169case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD:170strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);171break;172case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY:173strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time);174break;175case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY:176strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time);177break;178default: // Should never happen179return "";180}181return std::string(buf);182}183return "";184}185186std::string GameInfo::GetMTime() const {187switch (fileType) {188case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:189return GetFileDateAsString(GetFilePath() / "PARAM.SFO");190case IdentifiedFileType::PSP_PBP_DIRECTORY:191return GetFileDateAsString(GetFilePath() / "EBOOT.PBP");192default:193return GetFileDateAsString(GetFilePath());194}195}196197// Not too meaningful if the object itself is a savedata directory...198// Call this under lock.199std::vector<Path> GameInfo::GetSaveDataDirectories() {200_dbg_assert_(hasFlags & GameInfoFlags::PARAM_SFO); // so we know we have the ID.201Path memc = GetSysDirectory(DIRECTORY_SAVEDATA);202203std::vector<File::FileInfo> dirs;204File::GetFilesInDir(memc, &dirs);205206std::vector<Path> directories;207if (id.size() < 5) {208return directories;209}210for (size_t i = 0; i < dirs.size(); i++) {211if (startsWith(dirs[i].name, id)) {212directories.push_back(dirs[i].fullName);213}214}215216return directories;217}218219u64 GameInfo::GetGameSavedataSizeInBytes() {220if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {221return 0;222}223std::vector<Path> saveDataDir = GetSaveDataDirectories();224225u64 totalSize = 0;226u64 filesSizeInDir = 0;227for (size_t j = 0; j < saveDataDir.size(); j++) {228std::vector<File::FileInfo> fileInfo;229File::GetFilesInDir(saveDataDir[j], &fileInfo);230for (auto const &file : fileInfo) {231if (!file.isDirectory)232filesSizeInDir += file.size;233}234if (filesSizeInDir < 0xA00000) {235// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.236totalSize += filesSizeInDir;237}238filesSizeInDir = 0;239}240return totalSize;241}242243u64 GameInfo::GetInstallDataSizeInBytes() {244if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {245return 0;246}247std::vector<Path> saveDataDir = GetSaveDataDirectories();248249u64 totalSize = 0;250u64 filesSizeInDir = 0;251for (size_t j = 0; j < saveDataDir.size(); j++) {252std::vector<File::FileInfo> fileInfo;253File::GetFilesInDir(saveDataDir[j], &fileInfo);254for (auto const &file : fileInfo) {255// TODO: Might want to recurse here? Don't know games that use directories256// for install-data though.257if (!file.isDirectory)258filesSizeInDir += file.size;259}260if (filesSizeInDir >= 0xA00000) {261// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.262// This is probably GameInstall data.263totalSize += filesSizeInDir;264}265filesSizeInDir = 0;266}267return totalSize;268}269270bool GameInfo::CreateLoader() {271if (!fileLoader) {272std::lock_guard<std::mutex> guard(loaderLock);273fileLoader.reset(ConstructFileLoader(filePath_));274if (!fileLoader)275return false;276}277return true;278}279280std::shared_ptr<FileLoader> GameInfo::GetFileLoader() {281if (filePath_.empty()) {282// Happens when workqueue tries to figure out priorities,283// because Priority() calls GetFileLoader()... gnarly.284return fileLoader;285}286287std::lock_guard<std::mutex> guard(loaderLock);288if (!fileLoader) {289FileLoader *loader = ConstructFileLoader(filePath_);290fileLoader.reset(loader);291return fileLoader;292}293return fileLoader;294}295296void GameInfo::DisposeFileLoader() {297std::lock_guard<std::mutex> guard(loaderLock);298fileLoader.reset();299}300301bool GameInfo::DeleteAllSaveData() {302std::vector<Path> saveDataDir = GetSaveDataDirectories();303for (size_t j = 0; j < saveDataDir.size(); j++) {304std::vector<File::FileInfo> fileInfo;305File::GetFilesInDir(saveDataDir[j], &fileInfo);306307for (size_t i = 0; i < fileInfo.size(); i++) {308File::Delete(fileInfo[i].fullName);309}310311File::DeleteDir(saveDataDir[j]);312}313return true;314}315316void GameInfo::ParseParamSFO() {317title = paramSFO.GetValueString("TITLE");318id = paramSFO.GetValueString("DISC_ID");319id_version = id + "_" + paramSFO.GetValueString("DISC_VERSION");320disc_total = paramSFO.GetValueInt("DISC_TOTAL");321disc_number = paramSFO.GetValueInt("DISC_NUMBER");322// region = paramSFO.GetValueInt("REGION"); // Always seems to be 32768?323324region = GAMEREGION_OTHER;325if (id_version.size() >= 4) {326std::string regStr = id_version.substr(0, 4);327328// Guesswork329switch (regStr[2]) {330case 'E': region = GAMEREGION_EUROPE; break;331case 'U': region = GAMEREGION_USA; break;332case 'J': region = GAMEREGION_JAPAN; break;333case 'H': region = GAMEREGION_HONGKONG; break;334case 'A': region = GAMEREGION_ASIA; break;335case 'K': region = GAMEREGION_KOREA; break;336}337/*338if (regStr == "NPEZ" || regStr == "NPEG" || regStr == "ULES" || regStr == "UCES" ||339regStr == "NPEX") {340region = GAMEREGION_EUROPE;341} else if (regStr == "NPUG" || regStr == "NPUZ" || regStr == "ULUS" || regStr == "UCUS") {342region = GAMEREGION_USA;343} else if (regStr == "NPJH" || regStr == "NPJG" || regStr == "ULJM"|| regStr == "ULJS") {344region = GAMEREGION_JAPAN;345} else if (regStr == "NPHG") {346region = GAMEREGION_HONGKONG;347} else if (regStr == "UCAS") {348region = GAMEREGION_CHINA;349}*/350}351}352353std::string GameInfo::GetTitle() {354std::lock_guard<std::mutex> guard(lock);355if ((hasFlags & GameInfoFlags::PARAM_SFO) && !title.empty()) {356return title;357} else {358return filePath_.GetFilename();359}360}361362void GameInfo::SetTitle(const std::string &newTitle) {363std::lock_guard<std::mutex> guard(lock);364title = newTitle;365}366367void GameInfo::FinishPendingTextureLoads(Draw::DrawContext *draw) {368if (draw && icon.dataLoaded && !icon.texture) {369SetupTexture(draw, icon);370}371if (draw && pic0.dataLoaded && !pic0.texture) {372SetupTexture(draw, pic0);373}374if (draw && pic1.dataLoaded && !pic1.texture) {375SetupTexture(draw, pic1);376}377}378379void GameInfo::SetupTexture(Draw::DrawContext *thin3d, GameInfoTex &tex) {380if (tex.timeLoaded) {381// Failed before, skip.382return;383}384if (tex.data.empty()) {385tex.timeLoaded = time_now_d();386return;387}388using namespace Draw;389// TODO: Use TempImage to semi-load the image in the worker task, then here we390// could just call CreateTextureFromTempImage.391tex.texture = CreateTextureFromFileData(thin3d, (const uint8_t *)tex.data.data(), tex.data.size(), ImageFileType::DETECT, false, GetTitle().c_str());392tex.timeLoaded = time_now_d();393if (!tex.texture) {394ERROR_LOG(Log::G3D, "Failed creating texture (%s) from %d-byte file", GetTitle().c_str(), (int)tex.data.size());395}396}397398static bool ReadFileToString(IFileSystem *fs, const char *filename, std::string *contents, std::mutex *mtx) {399PSPFileInfo info = fs->GetFileInfo(filename);400if (!info.exists) {401return false;402}403404int handle = fs->OpenFile(filename, FILEACCESS_READ);405if (handle < 0) {406return false;407}408if (mtx) {409std::string data;410data.resize(info.size);411size_t readSize = fs->ReadFile(handle, (u8 *)data.data(), info.size);412fs->CloseFile(handle);413if (readSize != info.size) {414return false;415}416std::lock_guard<std::mutex> lock(*mtx);417*contents = std::move(data);418} else {419contents->resize(info.size);420size_t readSize = fs->ReadFile(handle, (u8 *)contents->data(), info.size);421fs->CloseFile(handle);422if (readSize != info.size) {423return false;424}425}426return true;427}428429static bool ReadLocalFileToString(const Path &path, std::string *contents, std::mutex *mtx) {430std::string data;431if (!File::ReadBinaryFileToString(path, &data)) {432return false;433}434if (mtx) {435std::lock_guard<std::mutex> lock(*mtx);436*contents = std::move(data);437} else {438*contents = std::move(data);439}440return true;441}442443static bool ReadVFSToString(const char *filename, std::string *contents, std::mutex *mtx) {444size_t sz;445uint8_t *data = g_VFS.ReadFile(filename, &sz);446if (data) {447if (mtx) {448std::lock_guard<std::mutex> lock(*mtx);449*contents = std::string((const char *)data, sz);450} else {451*contents = std::string((const char *)data, sz);452}453} else {454return false;455}456delete [] data;457return true;458}459460class GameInfoWorkItem : public Task {461public:462GameInfoWorkItem(const Path &gamePath, std::shared_ptr<GameInfo> &info, GameInfoFlags flags)463: gamePath_(gamePath), info_(info), flags_(flags) {}464465~GameInfoWorkItem() {466info_->DisposeFileLoader();467}468469TaskType Type() const override {470return TaskType::IO_BLOCKING;471}472473TaskPriority Priority() const override {474switch (gamePath_.Type()) {475case PathType::NATIVE:476case PathType::CONTENT_URI:477return TaskPriority::NORMAL;478479default:480// Remote/network access.481return TaskPriority::LOW;482}483}484485void Run() override {486// An early-return will result in the destructor running, where we can set487// flags like working and pending.488if (!info_->CreateLoader() || !info_->GetFileLoader() || !info_->GetFileLoader()->Exists()) {489// Mark everything requested as done, so490std::unique_lock<std::mutex> lock(info_->lock);491info_->MarkReadyNoLock(flags_);492ERROR_LOG(Log::Loader, "Failed getting game info for %s", info_->GetFilePath().ToVisualString().c_str());493return;494}495496std::string errorString;497498if (flags_ & GameInfoFlags::FILE_TYPE) {499info_->fileType = Identify_File(info_->GetFileLoader().get(), &errorString);500}501502if (!info_->Ready(GameInfoFlags::FILE_TYPE) && !(flags_ & GameInfoFlags::FILE_TYPE)) {503_dbg_assert_(false);504}505506switch (info_->fileType) {507case IdentifiedFileType::PSP_PBP:508case IdentifiedFileType::PSP_PBP_DIRECTORY:509{510auto pbpLoader = info_->GetFileLoader();511if (info_->fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {512Path ebootPath = ResolvePBPFile(gamePath_);513if (ebootPath != gamePath_) {514pbpLoader.reset(ConstructFileLoader(ebootPath));515}516}517518PBPReader pbp(pbpLoader.get());519if (!pbp.IsValid()) {520if (pbp.IsELF()) {521goto handleELF;522}523ERROR_LOG(Log::Loader, "invalid pbp '%s'\n", pbpLoader->GetPath().c_str());524// We can't win here - just mark everything pending as fetched, and let the caller525// handle the missing data.526std::unique_lock<std::mutex> lock(info_->lock);527info_->MarkReadyNoLock(flags_);528return;529}530531// First, PARAM.SFO.532if (flags_ & GameInfoFlags::PARAM_SFO) {533std::vector<u8> sfoData;534if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {535std::lock_guard<std::mutex> lock(info_->lock);536info_->paramSFO.ReadSFO(sfoData);537info_->ParseParamSFO();538539// Assuming PSP_PBP_DIRECTORY without ID or with disc_total < 1 in GAME dir must be homebrew540if ((info_->id.empty() || !info_->disc_total)541&& gamePath_.FilePathContainsNoCase("PSP/GAME/")542&& info_->fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {543info_->id = g_paramSFO.GenerateFakeID(gamePath_);544info_->id_version = info_->id + "_1.00";545info_->region = GAMEREGION_MAX + 1; // Homebrew546}547info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);548}549}550551// Then, ICON0.PNG.552if (flags_ & GameInfoFlags::ICON) {553if (pbp.GetSubFileSize(PBP_ICON0_PNG) > 0) {554std::lock_guard<std::mutex> lock(info_->lock);555pbp.GetSubFileAsString(PBP_ICON0_PNG, &info_->icon.data);556} else {557Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");558Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");559// Try using png/jpg screenshots first560if (File::Exists(screenshot_png))561ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);562else if (File::Exists(screenshot_jpg))563ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);564else565// Read standard icon566ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);567}568info_->icon.dataLoaded = true;569}570571if (flags_ & GameInfoFlags::BG) {572if (pbp.GetSubFileSize(PBP_PIC0_PNG) > 0) {573std::string data;574pbp.GetSubFileAsString(PBP_PIC0_PNG, &data);575std::lock_guard<std::mutex> lock(info_->lock);576info_->pic0.data = std::move(data);577info_->pic0.dataLoaded = true;578}579if (pbp.GetSubFileSize(PBP_PIC1_PNG) > 0) {580std::string data;581pbp.GetSubFileAsString(PBP_PIC1_PNG, &data);582std::lock_guard<std::mutex> lock(info_->lock);583info_->pic1.data = std::move(data);584info_->pic1.dataLoaded = true;585}586}587if (flags_ & GameInfoFlags::SND) {588if (pbp.GetSubFileSize(PBP_SND0_AT3) > 0) {589std::string data;590pbp.GetSubFileAsString(PBP_SND0_AT3, &data);591std::lock_guard<std::mutex> lock(info_->lock);592info_->sndFileData = std::move(data);593info_->sndDataLoaded = true;594}595}596}597break;598599case IdentifiedFileType::PSP_ELF:600handleELF:601// An elf on its own has no usable information, no icons, no nothing.602if (flags_ & GameInfoFlags::PARAM_SFO) {603info_->id = g_paramSFO.GenerateFakeID(gamePath_);604info_->id_version = info_->id + "_1.00";605info_->region = GAMEREGION_MAX + 1; // Homebrew606}607608if (flags_ & GameInfoFlags::ICON) {609std::string id = g_paramSFO.GenerateFakeID(gamePath_);610// Due to the dependency of the BASIC info, we fetch it already here.611Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.jpg");612Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.png");613// Try using png/jpg screenshots first614if (File::Exists(screenshot_png)) {615ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);616} else if (File::Exists(screenshot_jpg)) {617ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);618} else {619// Read standard icon620VERBOSE_LOG(Log::Loader, "Loading unknown.png because there was an ELF");621ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);622}623info_->icon.dataLoaded = true;624}625break;626627case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:628{629SequentialHandleAllocator handles;630VirtualDiscFileSystem umd(&handles, gamePath_);631632if (flags_ & GameInfoFlags::PARAM_SFO) {633// Alright, let's fetch the PARAM.SFO.634std::string paramSFOcontents;635if (ReadFileToString(&umd, "/PARAM.SFO", ¶mSFOcontents, 0)) {636std::lock_guard<std::mutex> lock(info_->lock);637info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());638info_->ParseParamSFO();639info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);640}641}642if (flags_ & GameInfoFlags::ICON) {643ReadFileToString(&umd, "/ICON0.PNG", &info_->icon.data, &info_->lock);644info_->icon.dataLoaded = true;645}646if (flags_ & GameInfoFlags::BG) {647ReadFileToString(&umd, "/PIC1.PNG", &info_->pic1.data, &info_->lock);648info_->pic1.dataLoaded = true;649}650break;651}652653case IdentifiedFileType::PPSSPP_SAVESTATE:654{655if (flags_ & GameInfoFlags::PARAM_SFO) {656info_->SetTitle(SaveState::GetTitle(gamePath_));657std::lock_guard<std::mutex> lock(info_->lock);658info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);659}660661// Let's use the screenshot as an icon, too.662if (flags_ & GameInfoFlags::ICON) {663Path screenshotPath = gamePath_.WithReplacedExtension(".ppst", ".jpg");664if (ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {665info_->icon.dataLoaded = true;666} else {667ERROR_LOG(Log::G3D, "Error loading screenshot data: '%s'", screenshotPath.c_str());668}669}670break;671}672673case IdentifiedFileType::PPSSPP_GE_DUMP:674{675if (flags_ & GameInfoFlags::ICON) {676Path screenshotPath = gamePath_.WithReplacedExtension(".ppdmp", ".png");677// Let's use the comparison screenshot as an icon, if it exists.678if (ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {679info_->icon.dataLoaded = true;680}681}682break;683}684685case IdentifiedFileType::PSP_DISC_DIRECTORY:686{687SequentialHandleAllocator handles;688VirtualDiscFileSystem umd(&handles, gamePath_);689690// Alright, let's fetch the PARAM.SFO.691if (flags_ & GameInfoFlags::PARAM_SFO) {692std::string paramSFOcontents;693if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", ¶mSFOcontents, nullptr)) {694std::lock_guard<std::mutex> lock(info_->lock);695info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());696info_->ParseParamSFO();697}698}699700if (flags_ & GameInfoFlags::ICON) {701ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock);702info_->icon.dataLoaded = true;703}704if (flags_ & GameInfoFlags::BG) {705ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);706info_->pic0.dataLoaded = true;707ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock);708info_->pic1.dataLoaded = true;709}710if (flags_ & GameInfoFlags::SND) {711ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock);712info_->pic1.dataLoaded = true;713}714break;715}716717case IdentifiedFileType::PSP_ISO:718case IdentifiedFileType::PSP_ISO_NP:719{720SequentialHandleAllocator handles;721// Let's assume it's an ISO.722// TODO: This will currently read in the whole directory tree. Not really necessary for just a723// few files.724auto fl = info_->GetFileLoader();725if (!fl) {726// BAD! Can't win here.727ERROR_LOG(Log::Loader, "Failed getting game info for ISO %s", info_->GetFilePath().ToVisualString().c_str());728std::unique_lock<std::mutex> lock(info_->lock);729info_->MarkReadyNoLock(flags_);730return;731}732BlockDevice *bd = constructBlockDevice(info_->GetFileLoader().get());733if (!bd) {734ERROR_LOG(Log::Loader, "Failed constructing block device for ISO %s", info_->GetFilePath().ToVisualString().c_str());735std::unique_lock<std::mutex> lock(info_->lock);736info_->MarkReadyNoLock(flags_);737return;738}739ISOFileSystem umd(&handles, bd);740741// Alright, let's fetch the PARAM.SFO.742if (flags_ & GameInfoFlags::PARAM_SFO) {743std::string paramSFOcontents;744if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", ¶mSFOcontents, nullptr)) {745{746std::lock_guard<std::mutex> lock(info_->lock);747info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());748info_->ParseParamSFO();749750// quick-update the info while we have the lock, so we don't need to wait for the image load to display the title.751info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);752}753}754}755756if (flags_ & GameInfoFlags::BG) {757info_->pic0.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);758info_->pic1.dataLoaded = ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock);759}760761if (flags_ & GameInfoFlags::SND) {762info_->sndDataLoaded = ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock);763}764765// Fall back to unknown icon if ISO is broken/is a homebrew ISO, override is allowed though766if (flags_ & GameInfoFlags::ICON) {767if (!ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock)) {768Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");769Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");770// Try using png/jpg screenshots first771if (File::Exists(screenshot_png))772info_->icon.dataLoaded = ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);773else if (File::Exists(screenshot_jpg))774info_->icon.dataLoaded = ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);775else {776DEBUG_LOG(Log::Loader, "Loading unknown.png because no icon was found");777info_->icon.dataLoaded = ReadVFSToString("unknown.png", &info_->icon.data, &info_->lock);778}779} else {780info_->icon.dataLoaded = true;781}782}783break;784}785786case IdentifiedFileType::ARCHIVE_ZIP:787if (flags_ & GameInfoFlags::ICON) {788ReadVFSToString("zip.png", &info_->icon.data, &info_->lock);789info_->icon.dataLoaded = true;790}791break;792793case IdentifiedFileType::ARCHIVE_RAR:794if (flags_ & GameInfoFlags::ICON) {795ReadVFSToString("rargray.png", &info_->icon.data, &info_->lock);796info_->icon.dataLoaded = true;797}798break;799800case IdentifiedFileType::ARCHIVE_7Z:801if (flags_ & GameInfoFlags::ICON) {802ReadVFSToString("7z.png", &info_->icon.data, &info_->lock);803info_->icon.dataLoaded = true;804}805break;806807case IdentifiedFileType::NORMAL_DIRECTORY:808default:809break;810}811812if (flags_ & GameInfoFlags::PARAM_SFO) {813// We fetch the hasConfig together with the params, since that's what fills out the id.814info_->hasConfig = g_Config.hasGameConfig(info_->id);815}816817if (flags_ & GameInfoFlags::SIZE) {818std::lock_guard<std::mutex> lock(info_->lock);819info_->gameSizeOnDisk = info_->GetSizeOnDiskInBytes();820switch (info_->fileType) {821case IdentifiedFileType::PSP_ISO:822case IdentifiedFileType::PSP_ISO_NP:823case IdentifiedFileType::PSP_DISC_DIRECTORY:824case IdentifiedFileType::PSP_PBP:825case IdentifiedFileType::PSP_PBP_DIRECTORY:826info_->saveDataSize = info_->GetGameSavedataSizeInBytes();827info_->installDataSize = info_->GetInstallDataSizeInBytes();828break;829default:830info_->saveDataSize = 0;831info_->installDataSize = 0;832break;833}834}835if (flags_ & GameInfoFlags::UNCOMPRESSED_SIZE) {836info_->gameSizeUncompressed = info_->GetSizeUncompressedInBytes();837}838839// Time to update the flags.840std::unique_lock<std::mutex> lock(info_->lock);841info_->MarkReadyNoLock(flags_);842// INFO_LOG(Log::System, "Completed writing info for %s", info_->GetTitle().c_str());843}844845private:846Path gamePath_;847std::shared_ptr<GameInfo> info_;848GameInfoFlags flags_{};849850DISALLOW_COPY_AND_ASSIGN(GameInfoWorkItem);851};852853GameInfoCache::GameInfoCache() {854Init();855}856857GameInfoCache::~GameInfoCache() {858Clear();859Shutdown();860}861862void GameInfoCache::Init() {}863864void GameInfoCache::Shutdown() {865CancelAll();866}867868void GameInfoCache::Clear() {869CancelAll();870871std::lock_guard<std::mutex> lock(mapLock_);872info_.clear();873}874875void GameInfoCache::CancelAll() {876std::lock_guard<std::mutex> lock(mapLock_);877for (auto info : info_) {878// GetFileLoader will create one if there isn't one already.879// Avoid that by checking.880if (info.second->HasFileLoader()) {881auto fl = info.second->GetFileLoader();882if (fl) {883fl->Cancel();884}885}886}887}888889void GameInfoCache::FlushBGs() {890std::lock_guard<std::mutex> lock(mapLock_);891for (auto iter = info_.begin(); iter != info_.end(); iter++) {892std::lock_guard<std::mutex> lock(iter->second->lock);893iter->second->pic0.Clear();894iter->second->pic1.Clear();895if (!iter->second->sndFileData.empty()) {896iter->second->sndFileData.clear();897iter->second->sndDataLoaded = false;898}899iter->second->hasFlags &= ~(GameInfoFlags::BG | GameInfoFlags::SND);900}901}902903void GameInfoCache::PurgeType(IdentifiedFileType fileType) {904bool retry = false;905int retryCount = 10;906// Trickery to avoid sleeping with the lock held.907do {908if (retry) {909retryCount--;910if (retryCount == 0) {911break;912}913}914retry = false;915{916std::lock_guard<std::mutex> lock(mapLock_);917for (auto iter = info_.begin(); iter != info_.end();) {918auto &info = iter->second;919if (!(info->hasFlags & GameInfoFlags::FILE_TYPE)) {920iter++;921continue;922}923if (info->fileType != fileType) {924iter++;925continue;926}927// TODO: Find a better way to wait here.928if (info->pendingFlags != (GameInfoFlags)0) {929INFO_LOG(Log::Loader, "%s: pending flags %08x, retrying", info->GetTitle().c_str(), (int)info->pendingFlags);930retry = true;931break;932}933iter = info_.erase(iter);934}935}936937sleep_ms(10);938} while (retry);939}940941// Call on the main thread ONLY - that is from stuff called from NativeFrame.942// Can also be called from the audio thread for menu background music, but that cannot request images!943std::shared_ptr<GameInfo> GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags) {944const std::string &pathStr = gamePath.ToString();945946// _dbg_assert_(gamePath != GetSysDirectory(DIRECTORY_SAVEDATA));947948// This is always needed to determine the method to get the other info, so make sure it's computed first.949wantFlags |= GameInfoFlags::FILE_TYPE;950951mapLock_.lock();952953auto iter = info_.find(pathStr);954if (iter != info_.end()) {955// There's already a structure about this game. Let's check.956std::shared_ptr<GameInfo> info = iter->second;957mapLock_.unlock();958959info->FinishPendingTextureLoads(draw);960info->lastAccessedTime = time_now_d();961GameInfoFlags wanted = (GameInfoFlags)0;962{963// Careful now!964std::unique_lock<std::mutex> lock(info->lock);965GameInfoFlags hasFlags = info->hasFlags | info->pendingFlags; // We don't want to re-fetch data that we have, so or in pendingFlags.966wanted = (GameInfoFlags)((int)wantFlags & ~(int)hasFlags); // & is reserved for testing. ugh.967info->pendingFlags |= wanted;968}969if (wanted != (GameInfoFlags)0) {970// We're missing info that we want. Go get it!971GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wanted);972g_threadManager.EnqueueTask(item);973}974return info;975}976977std::shared_ptr<GameInfo> info = std::make_shared<GameInfo>(gamePath);978info->pendingFlags = wantFlags;979info->lastAccessedTime = time_now_d();980info_.insert(std::make_pair(pathStr, info));981mapLock_.unlock();982983// Just get all the stuff we wanted.984GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wantFlags);985g_threadManager.EnqueueTask(item);986return info;987}988989990