mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 17:55:23 +02:00
GetInfo() masked out any flag that a *pending* work item was already going to fetch, FILE_TYPE included. But every work item starts by switching on info->fileType, so "another item will compute it" isn't good enough - if that item hadn't reached Identify_File yet, the second one fell through to default:, marked its flags ready and loaded nothing. The data then looked present forever, so e.g. a PIC1 requested while an ICON load was in flight could just never show up. Easy to hit since the screens request different flag combinations for the same path, and BackgroundAudio calls GetInfo from the audio thread. Always redo the identification unless FILE_TYPE is already in hasFlags (i.e. final), and have Run() switch on a local copy so a concurrent item can't shift it underneath us mid-switch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzzCUp8y1ahgVueb1Cq92Y
1136 lines
35 KiB
C++
1136 lines
35 KiB
C++
|
|
// Copyright (c) 2013- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include "Common/Common.h"
|
|
|
|
#include <string>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <algorithm>
|
|
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/Thread/ThreadManager.h"
|
|
#include "Common/File/VFS/VFS.h"
|
|
#include "Common/File/VFS/ZipFileReader.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/File/Path.h"
|
|
#include "Common/Render/ManagedTexture.h"
|
|
#include "Common/System/Request.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Core/FileSystems/ISOFileSystem.h"
|
|
#include "Core/FileSystems/DirectoryFileSystem.h"
|
|
#include "Core/FileSystems/VirtualDiscFileSystem.h"
|
|
#include "Core/HLE/sceUtility.h"
|
|
#include "Core/ELF/PBPReader.h"
|
|
#include "Core/SaveState.h"
|
|
#include "Core/System.h"
|
|
#include "Core/Util/GameDB.h"
|
|
#include "Core/Loaders.h"
|
|
#include "Core/Util/GameManager.h"
|
|
#include "Core/Util/RecentFiles.h"
|
|
#include "Core/Config.h"
|
|
#include "UI/GameInfoCache.h"
|
|
|
|
GameInfoCache *g_gameInfoCache;
|
|
|
|
void GameInfoTex::Clear() {
|
|
if (!data.empty()) {
|
|
data.clear();
|
|
dataLoaded = false;
|
|
}
|
|
if (texture) {
|
|
texture->Release();
|
|
texture = nullptr;
|
|
}
|
|
timeLoaded = 0.0;
|
|
}
|
|
|
|
GameInfo::GameInfo(const Path &gamePath) : filePath_(gamePath) {
|
|
// here due to a forward decl.
|
|
fileType = IdentifiedFileType::UNKNOWN;
|
|
}
|
|
|
|
GameInfo::~GameInfo() {
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
sndDataLoaded = false;
|
|
icon.Clear();
|
|
pic0.Clear();
|
|
pic1.Clear();
|
|
fileLoader.reset();
|
|
}
|
|
|
|
bool IsReasonableEbootDirectory(const Path& path) {
|
|
// First some sanity checks.
|
|
if (path == Path("/")) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < (int)PSPDirectories::COUNT; i++) {
|
|
if (path == GetSysDirectory((PSPDirectories)i)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool MoveFileToTrashOrDelete(const Path &path) {
|
|
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
|
|
// TODO: Way to see if it succeeded
|
|
System_MoveToTrash(path);
|
|
return true;
|
|
} else {
|
|
return File::Delete(path);
|
|
}
|
|
}
|
|
|
|
static bool MoveDirectoryTreeToTrashOrDelete(const Path &path) {
|
|
if (System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN)) {
|
|
// TODO: Way to see if it succeeded
|
|
System_MoveToTrash(path);
|
|
return true;
|
|
} else {
|
|
return File::DeleteDirRecursively(path);
|
|
}
|
|
}
|
|
|
|
bool GameInfo::Delete() {
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_ISO:
|
|
case IdentifiedFileType::PSP_ISO_NP:
|
|
{
|
|
// Just delete the one file (TODO: handle two-disk games as well somehow).
|
|
// Also remove from recent files.
|
|
Path fileToRemove = filePath_;
|
|
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
|
|
MoveFileToTrashOrDelete(fileToRemove);
|
|
g_recentFiles.Remove(filePath_.ToString());
|
|
return true;
|
|
}
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
|
|
{
|
|
// TODO: This could be handled by Core/Util/GameManager too somehow.
|
|
Path directoryToRemove = ResolvePBPDirectory(filePath_);
|
|
|
|
// Check that the directory isn't the base of the GAME folder, or something similarly stupid.
|
|
// This can happen if the PBP is misplaced, see issue #20187
|
|
if (!IsReasonableEbootDirectory(directoryToRemove)) {
|
|
// Just delete the eboot.
|
|
MoveFileToTrashOrDelete(filePath_);
|
|
g_recentFiles.Remove(filePath_.ToString());
|
|
return true;
|
|
}
|
|
|
|
// Delete the whole tree. We better be sure, see IsReasonableEbootDirectory.
|
|
INFO_LOG(Log::System, "Deleting directory %s", directoryToRemove.c_str());
|
|
if (!MoveDirectoryTreeToTrashOrDelete(directoryToRemove)) {
|
|
ERROR_LOG(Log::System, "Failed to delete file");
|
|
return false;
|
|
}
|
|
g_recentFiles.Clean();
|
|
return true;
|
|
}
|
|
case IdentifiedFileType::PSP_ELF:
|
|
case IdentifiedFileType::UNKNOWN_BIN:
|
|
case IdentifiedFileType::UNKNOWN_ELF:
|
|
case IdentifiedFileType::UNKNOWN_ISO:
|
|
case IdentifiedFileType::ARCHIVE_RAR:
|
|
case IdentifiedFileType::ARCHIVE_ZIP:
|
|
case IdentifiedFileType::ARCHIVE_7Z:
|
|
case IdentifiedFileType::UNKNOWN:
|
|
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
|
|
case IdentifiedFileType::PPSSPP_GE_DUMP:
|
|
{
|
|
const Path &fileToRemove = filePath_;
|
|
INFO_LOG(Log::System, "Deleting file %s", fileToRemove.c_str());
|
|
MoveFileToTrashOrDelete(fileToRemove);
|
|
g_recentFiles.Remove(filePath_.ToString());
|
|
return true;
|
|
}
|
|
|
|
case IdentifiedFileType::PPSSPP_SAVESTATE:
|
|
{
|
|
const Path &ppstPath = filePath_;
|
|
INFO_LOG(Log::System, "Deleting file %s", ppstPath.c_str());
|
|
MoveFileToTrashOrDelete(ppstPath);
|
|
const Path screenshotPath = filePath_.WithReplacedExtension(".ppst", ".jpg");
|
|
if (File::Exists(screenshotPath)) {
|
|
MoveFileToTrashOrDelete(screenshotPath);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
INFO_LOG(Log::System, "Don't know how to delete this type of file: %s", filePath_.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
u64 GameInfo::GetSizeOnDiskInBytes() {
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
|
|
return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));
|
|
case IdentifiedFileType::PSP_DISC_DIRECTORY:
|
|
return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());
|
|
default:
|
|
return GetFileLoader()->FileSize();
|
|
}
|
|
}
|
|
|
|
u64 GameInfo::GetSizeUncompressedInBytes() {
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
|
|
return File::ComputeRecursiveDirectorySize(ResolvePBPDirectory(filePath_));
|
|
case IdentifiedFileType::PSP_DISC_DIRECTORY:
|
|
return File::ComputeRecursiveDirectorySize(GetFileLoader()->GetPath());
|
|
default:
|
|
{
|
|
std::string errorString;
|
|
BlockDevice *blockDevice = ConstructBlockDevice(GetFileLoader().get(), &errorString);
|
|
if (blockDevice) {
|
|
u64 size = blockDevice->GetUncompressedSize();
|
|
delete blockDevice;
|
|
return size;
|
|
} else {
|
|
return GetFileLoader()->FileSize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string GetFileDateAsString(const Path &filename) {
|
|
tm time;
|
|
if (File::GetModifTime(filename, time)) {
|
|
char buf[256];
|
|
switch (g_Config.iDateFormat) {
|
|
case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD:
|
|
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time);
|
|
break;
|
|
case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY:
|
|
strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time);
|
|
break;
|
|
case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY:
|
|
strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time);
|
|
break;
|
|
default: // Should never happen
|
|
return "";
|
|
}
|
|
return std::string(buf);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string GameInfo::GetMTime() const {
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
|
|
return GetFileDateAsString(GetFilePath() / "PARAM.SFO");
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
return GetFileDateAsString(GetFilePath() / "EBOOT.PBP");
|
|
default:
|
|
return GetFileDateAsString(GetFilePath());
|
|
}
|
|
}
|
|
|
|
// Not too meaningful if the object itself is a savedata directory...
|
|
// Call this under lock.
|
|
std::vector<Path> GameInfo::GetSaveDataDirectories() const {
|
|
if (!(hasFlags & GameInfoFlags::PARAM_SFO)) {
|
|
ERROR_LOG(Log::UI, "Can't get savedata directories if we don't have PARAM_SFO.");
|
|
return std::vector<Path>();
|
|
}
|
|
|
|
Path memc = GetSysDirectory(DIRECTORY_SAVEDATA);
|
|
|
|
std::vector<Path> directories;
|
|
if (id.size() < 5) {
|
|
// Invalid game ID.
|
|
return directories;
|
|
}
|
|
|
|
std::vector<File::FileInfo> dirs;
|
|
const std::string &prefix = id;
|
|
File::GetFilesInDir(memc, &dirs, nullptr, 0, prefix);
|
|
|
|
for (const auto& dir : dirs) {
|
|
directories.push_back(dir.fullName);
|
|
}
|
|
|
|
return directories;
|
|
}
|
|
|
|
u64 GameInfo::GetGameSavedataSizeInBytes() const {
|
|
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {
|
|
return 0;
|
|
}
|
|
std::vector<Path> saveDataDir = GetSaveDataDirectories();
|
|
|
|
u64 totalSize = 0;
|
|
u64 filesSizeInDir = 0;
|
|
for (const auto& dir : saveDataDir) {
|
|
std::vector<File::FileInfo> fileInfo;
|
|
File::GetFilesInDir(dir, &fileInfo);
|
|
for (auto const &file : fileInfo) {
|
|
if (!file.isDirectory)
|
|
filesSizeInDir += file.size;
|
|
}
|
|
if (filesSizeInDir < 0xA00000) {
|
|
// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.
|
|
totalSize += filesSizeInDir;
|
|
}
|
|
filesSizeInDir = 0;
|
|
}
|
|
return totalSize;
|
|
}
|
|
|
|
u64 GameInfo::GetInstallDataSizeInBytes() const {
|
|
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {
|
|
return 0;
|
|
}
|
|
std::vector<Path> saveDataDir = GetSaveDataDirectories();
|
|
|
|
u64 totalSize = 0;
|
|
u64 filesSizeInDir = 0;
|
|
for (const auto& dir : saveDataDir) {
|
|
std::vector<File::FileInfo> fileInfo;
|
|
File::GetFilesInDir(dir, &fileInfo);
|
|
for (auto const &file : fileInfo) {
|
|
// TODO: Might want to recurse here? Don't know games that use directories
|
|
// for install-data though.
|
|
if (!file.isDirectory)
|
|
filesSizeInDir += file.size;
|
|
}
|
|
if (filesSizeInDir >= 0xA00000) {
|
|
// HACK: Generally the savedata size in a dir shouldn't be more than 10MB.
|
|
// This is probably GameInstall data.
|
|
totalSize += filesSizeInDir;
|
|
}
|
|
filesSizeInDir = 0;
|
|
}
|
|
return totalSize;
|
|
}
|
|
|
|
bool GameInfo::CreateLoader() {
|
|
if (!fileLoader) {
|
|
std::lock_guard<std::mutex> guard(loaderLock);
|
|
fileLoader.reset(ConstructFileLoader(filePath_));
|
|
if (!fileLoader)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<FileLoader> GameInfo::GetFileLoader() {
|
|
if (filePath_.empty()) {
|
|
// Happens when workqueue tries to figure out priorities,
|
|
// because Priority() calls GetFileLoader()... gnarly.
|
|
return fileLoader;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> guard(loaderLock);
|
|
if (!fileLoader) {
|
|
FileLoader *loader = ConstructFileLoader(filePath_);
|
|
fileLoader.reset(loader);
|
|
return fileLoader;
|
|
}
|
|
return fileLoader;
|
|
}
|
|
|
|
void GameInfo::DisposeFileLoader() {
|
|
std::lock_guard<std::mutex> guard(loaderLock);
|
|
fileLoader.reset();
|
|
}
|
|
|
|
bool GameInfo::DeleteAllSaveData() const {
|
|
std::vector<Path> saveDataDir = GetSaveDataDirectories();
|
|
for (const auto& dir : saveDataDir) {
|
|
INFO_LOG(Log::System, "Deleting savedata from %s", dir.c_str());
|
|
if (!MoveDirectoryTreeToTrashOrDelete(dir)) {
|
|
ERROR_LOG(Log::System, "Failed to delete savedata %s", dir.c_str());
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GameInfo::ParseParamSFO(IdentifiedFileType type) {
|
|
title = paramSFO.GetValueString("TITLE");
|
|
if (type != IdentifiedFileType::PSP_UMD_VIDEO_ISO) {
|
|
id = paramSFO.GetValueString("DISC_ID");
|
|
id_version = id + "_" + paramSFO.GetValueString("DISC_VERSION");
|
|
disc_total = paramSFO.GetValueInt("DISC_TOTAL");
|
|
disc_number = paramSFO.GetValueInt("DISC_NUMBER");
|
|
// region = paramSFO.GetValueInt("REGION"); // Always seems to be 32768?
|
|
region = DetectGameRegionFromID(id);
|
|
} else {
|
|
id.clear();
|
|
id_version.clear();
|
|
region = GameRegion::UNKNOWN;
|
|
}
|
|
}
|
|
|
|
std::string GameInfo::GetTitle() {
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
if ((hasFlags & GameInfoFlags::PARAM_SFO) && !title.empty()) {
|
|
return title;
|
|
} else {
|
|
return filePath_.GetFilename();
|
|
}
|
|
}
|
|
|
|
std::string GameInfo::GetDBTitle() {
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
// Without PARAM_SFO there's no id_version to look up, so don't bother.
|
|
if (hasFlags & GameInfoFlags::PARAM_SFO) {
|
|
std::vector<GameDBInfo> dbInfos;
|
|
if (g_gameDB.GetGameInfos(id_version, &dbInfos)) {
|
|
return std::string(dbInfos[0].title);
|
|
}
|
|
if (!title.empty()) {
|
|
return title;
|
|
}
|
|
}
|
|
// Same fallback as GetTitle(), which we can't just call from here since the lock isn't recursive.
|
|
return filePath_.GetFilename();
|
|
}
|
|
|
|
void GameInfo::SetTitle(const std::string &newTitle) {
|
|
std::lock_guard<std::mutex> guard(lock);
|
|
title = newTitle;
|
|
}
|
|
|
|
void GameInfo::FinishPendingTextureLoads(Draw::DrawContext *draw) {
|
|
if (!draw) {
|
|
return;
|
|
}
|
|
if (icon.dataLoaded && !icon.texture) {
|
|
SetupTexture(draw, icon, 2048, 2048);
|
|
}
|
|
if (pic0.dataLoaded && !pic0.texture) {
|
|
SetupTexture(draw, pic0);
|
|
}
|
|
if (pic1.dataLoaded && !pic1.texture) {
|
|
SetupTexture(draw, pic1);
|
|
}
|
|
}
|
|
|
|
void GameInfo::SetupTexture(Draw::DrawContext *thin3d, GameInfoTex &tex, int maxWidth, int maxHeight) {
|
|
if (tex.timeLoaded) {
|
|
// Failed before, skip.
|
|
return;
|
|
}
|
|
if (tex.data.empty()) {
|
|
tex.timeLoaded = time_now_d();
|
|
return;
|
|
}
|
|
using namespace Draw;
|
|
// TODO: Use TempImage to semi-load the image in the worker task, then here we
|
|
// could just call CreateTextureFromTempImage.
|
|
tex.texture = CreateTextureFromFileData(thin3d, (const uint8_t *)tex.data.data(), tex.data.size(), ImageFileType::DETECT, false, GetTitle().c_str(), maxWidth, maxHeight);
|
|
tex.timeLoaded = time_now_d();
|
|
if (!tex.texture) {
|
|
ERROR_LOG(Log::G3D, "Failed creating texture (%s) from %d-byte file", GetTitle().c_str(), (int)tex.data.size());
|
|
}
|
|
}
|
|
|
|
// Will clear contents on failure.
|
|
static bool ReadFileToString(IFileSystem *fs, std::string_view filename, std::string *contents, std::mutex *mtx) {
|
|
std::string fn(filename);
|
|
PSPFileInfo info = fs->GetFileInfo(fn);
|
|
if (!info.exists) {
|
|
return false;
|
|
}
|
|
|
|
int handle = fs->OpenFile(fn, FILEACCESS_READ);
|
|
if (handle < 0) {
|
|
return false;
|
|
}
|
|
if (mtx) {
|
|
std::string data;
|
|
data.resize(info.size);
|
|
size_t readSize = fs->ReadFile(handle, (u8 *)data.data(), info.size);
|
|
fs->CloseFile(handle);
|
|
std::lock_guard<std::mutex> lock(*mtx);
|
|
if (readSize != info.size) {
|
|
contents->clear();
|
|
return false;
|
|
}
|
|
*contents = std::move(data);
|
|
} else {
|
|
contents->resize(info.size);
|
|
size_t readSize = fs->ReadFile(handle, (u8 *)contents->data(), info.size);
|
|
fs->CloseFile(handle);
|
|
if (readSize != info.size) {
|
|
contents->clear();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ReadLocalFileToString(const Path &path, std::string *contents, std::mutex *mtx) {
|
|
std::string data;
|
|
if (!File::ReadBinaryFileToString(path, &data)) {
|
|
return false;
|
|
}
|
|
if (mtx) {
|
|
std::lock_guard<std::mutex> lock(*mtx);
|
|
*contents = std::move(data);
|
|
} else {
|
|
*contents = std::move(data);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool ReadVFSToString(const char *filename, std::string *contents, std::mutex *mtx) {
|
|
size_t sz;
|
|
uint8_t *data = g_VFS.ReadFile(filename, &sz);
|
|
if (!data) {
|
|
return false;
|
|
}
|
|
if (mtx) {
|
|
std::lock_guard<std::mutex> lock(*mtx);
|
|
*contents = std::string((const char *)data, sz);
|
|
} else {
|
|
*contents = std::string((const char *)data, sz);
|
|
}
|
|
delete [] data;
|
|
return true;
|
|
}
|
|
|
|
static bool LoadReplacementImage(GameInfo *info, GameInfoTex *tex, const char *filename) {
|
|
if (!g_Config.bReplaceTextures) {
|
|
return false;
|
|
}
|
|
|
|
const Path customIconFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / filename;
|
|
const Path zipFilename = GetSysDirectory(DIRECTORY_TEXTURES) / info->id / "textures.zip";
|
|
if (ReadLocalFileToString(customIconFilename, &tex->data, &info->lock)) {
|
|
tex->dataLoaded = true;
|
|
return true;
|
|
} else if (ReadSingleFileFromZip(zipFilename, filename, &tex->data, &info->lock)) {
|
|
tex->dataLoaded = true;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class GameInfoWorkItem : public Task {
|
|
public:
|
|
GameInfoWorkItem(const Path &gamePath, std::shared_ptr<GameInfo> &info, GameInfoFlags flags)
|
|
: gamePath_(gamePath), info_(info), flags_(flags) {}
|
|
|
|
~GameInfoWorkItem() {
|
|
info_->DisposeFileLoader();
|
|
}
|
|
|
|
TaskType Type() const override {
|
|
return TaskType::IO_BLOCKING;
|
|
}
|
|
|
|
TaskPriority Priority() const override {
|
|
switch (gamePath_.Type()) {
|
|
case PathType::NATIVE:
|
|
case PathType::CONTENT_URI:
|
|
return TaskPriority::NORMAL;
|
|
|
|
default:
|
|
// Remote/network access.
|
|
return TaskPriority::LOW;
|
|
}
|
|
}
|
|
|
|
void Run() override {
|
|
// An early-return will result in the destructor running, where we can set
|
|
// flags like working and pending.
|
|
if (!info_->CreateLoader() || !info_->GetFileLoader()) {
|
|
// Mark everything requested as done, so
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
ERROR_LOG(Log::Loader, "Failed getting game info for %s", info_->GetFilePath().ToVisualString().c_str());
|
|
return;
|
|
}
|
|
|
|
std::string errorString;
|
|
|
|
// Work on a local copy: another work item for the same GameInfo can be running alongside us,
|
|
// so info_->fileType isn't stable to switch on. GetInfo() guarantees we either compute it
|
|
// ourselves here, or that it's already in hasFlags and thus final.
|
|
IdentifiedFileType fileType;
|
|
if (flags_ & GameInfoFlags::FILE_TYPE) {
|
|
fileType = Identify_File(info_->GetFileLoader().get(), &errorString);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->fileType = fileType;
|
|
} else {
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
fileType = info_->fileType;
|
|
}
|
|
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_PBP:
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
{
|
|
auto pbpLoader = info_->GetFileLoader();
|
|
if (fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
|
|
Path ebootPath = ResolvePBPFile(gamePath_);
|
|
if (ebootPath != gamePath_) {
|
|
pbpLoader.reset(ConstructFileLoader(ebootPath));
|
|
}
|
|
}
|
|
|
|
if (!pbpLoader->Exists()) {
|
|
ERROR_LOG(Log::Loader, "File doesn't exist: %s\n", pbpLoader->GetPath().c_str());
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
return;
|
|
}
|
|
|
|
PBPReader pbp(pbpLoader.get());
|
|
if (!pbp.IsValid()) {
|
|
if (pbp.IsELF()) {
|
|
goto handleELF;
|
|
}
|
|
ERROR_LOG(Log::Loader, "invalid pbp '%s'\n", pbpLoader->GetPath().c_str());
|
|
// We can't win here - just mark everything pending as fetched, and let the caller
|
|
// handle the missing data.
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
return;
|
|
}
|
|
|
|
// First, PARAM.SFO.
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
std::vector<u8> sfoData;
|
|
if (pbp.GetSubFile(PBP_PARAM_SFO, &sfoData)) {
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->paramSFO.ReadSFO(sfoData);
|
|
info_->ParseParamSFO(fileType);
|
|
|
|
// Assuming PSP_PBP_DIRECTORY without ID or with disc_total < 1 in GAME dir must be homebrew
|
|
if ((info_->id.empty() || !info_->disc_total)
|
|
&& gamePath_.FilePathContainsNoCase("PSP/GAME/")
|
|
&& fileType == IdentifiedFileType::PSP_PBP_DIRECTORY) {
|
|
info_->id = g_paramSFO.GenerateFakeID(gamePath_);
|
|
info_->id_version = info_->id + "_1.00";
|
|
info_->region = GameRegion::HOMEBREW; // Homebrew
|
|
}
|
|
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
|
|
}
|
|
}
|
|
|
|
// Then, ICON0.PNG.
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) {
|
|
// Nothing more to do
|
|
} else if (pbp.GetSubFileSize(PBP_ICON0_PNG) > 0) {
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
pbp.GetSubFileAsString(PBP_ICON0_PNG, &info_->icon.data);
|
|
} else {
|
|
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");
|
|
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");
|
|
// Try using png/jpg screenshots first
|
|
if (File::Exists(screenshot_png)) {
|
|
ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
|
|
} else if (File::Exists(screenshot_jpg)) {
|
|
ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
|
|
} else {
|
|
// No icon.
|
|
}
|
|
}
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::ICON1_PMF) {
|
|
if (pbp.GetSubFileSize(PBP_ICON1_PMF) > 0) {
|
|
std::string data;
|
|
pbp.GetSubFileAsString(PBP_ICON1_PMF, &data);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->icon1pmf = std::move(data);
|
|
}
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::PIC0) {
|
|
if (pbp.GetSubFileSize(PBP_PIC0_PNG) > 0) {
|
|
std::string data;
|
|
pbp.GetSubFileAsString(PBP_PIC0_PNG, &data);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->pic0.data = std::move(data);
|
|
info_->pic0.dataLoaded = true;
|
|
}
|
|
}
|
|
if (flags_ & GameInfoFlags::PIC1) {
|
|
if (pbp.GetSubFileSize(PBP_PIC1_PNG) > 0) {
|
|
std::string data;
|
|
pbp.GetSubFileAsString(PBP_PIC1_PNG, &data);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->pic1.data = std::move(data);
|
|
info_->pic1.dataLoaded = true;
|
|
}
|
|
}
|
|
if (flags_ & GameInfoFlags::SND) {
|
|
if (pbp.GetSubFileSize(PBP_SND0_AT3) > 0) {
|
|
std::string data;
|
|
pbp.GetSubFileAsString(PBP_SND0_AT3, &data);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->sndFileData = std::move(data);
|
|
info_->sndDataLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IdentifiedFileType::PSP_ELF:
|
|
handleELF:
|
|
info_->title = info_->GetFilePath().GetFilename();
|
|
// An elf on its own has no usable information, no icons, no nothing.
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
info_->id = g_paramSFO.GenerateFakeID(gamePath_);
|
|
info_->id_version = info_->id + "_1.00";
|
|
info_->region = equals(info_->title, "vshmain.prx") ? GameRegion::VSH : GameRegion::HOMEBREW;
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
std::string id = g_paramSFO.GenerateFakeID(gamePath_);
|
|
// Due to the dependency of the BASIC info, we fetch it already here.
|
|
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.jpg");
|
|
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (id + "_00000.png");
|
|
// Try using png/jpg screenshots first
|
|
if (File::Exists(screenshot_png)) {
|
|
ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
|
|
} else if (File::Exists(screenshot_jpg)) {
|
|
ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
|
|
} else {
|
|
// No icon
|
|
}
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
break;
|
|
|
|
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
|
|
{
|
|
SequentialHandleAllocator handles;
|
|
VirtualDiscFileSystem umd(&handles, gamePath_);
|
|
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
// Alright, let's fetch the PARAM.SFO.
|
|
std::string paramSFOcontents;
|
|
if (ReadFileToString(&umd, "/PARAM.SFO", ¶mSFOcontents, 0)) {
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
|
|
info_->ParseParamSFO(fileType);
|
|
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
|
|
}
|
|
}
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
ReadFileToString(&umd, "/ICON0.PNG", &info_->icon.data, &info_->lock);
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
if (flags_ & GameInfoFlags::PIC1) {
|
|
ReadFileToString(&umd, "/PIC1.PNG", &info_->pic1.data, &info_->lock);
|
|
info_->pic1.dataLoaded = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IdentifiedFileType::PPSSPP_SAVESTATE:
|
|
{
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
info_->SetTitle(SaveState::GetTitle(gamePath_));
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
|
|
}
|
|
|
|
// Let's use the screenshot as an icon, too.
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
Path screenshotPath = gamePath_.WithReplacedExtension(".ppst", ".jpg");
|
|
if (ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IdentifiedFileType::PPSSPP_GE_DUMP:
|
|
{
|
|
info_->title = info_->GetFilePath().GetFilename();
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
Path screenshotPath = gamePath_.WithReplacedExtension(".ppdmp", ".png");
|
|
// Let's use the comparison screenshot as an icon, if it exists.
|
|
if (screenshotPath.IsLocalType() && ReadLocalFileToString(screenshotPath, &info_->icon.data, &info_->lock)) {
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IdentifiedFileType::PSP_DISC_DIRECTORY:
|
|
{
|
|
SequentialHandleAllocator handles;
|
|
VirtualDiscFileSystem umd(&handles, gamePath_);
|
|
|
|
// Alright, let's fetch the PARAM.SFO.
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
std::string paramSFOcontents;
|
|
if (ReadFileToString(&umd, "/PSP_GAME/PARAM.SFO", ¶mSFOcontents, nullptr)) {
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
|
|
info_->ParseParamSFO(fileType);
|
|
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
|
|
}
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
ReadFileToString(&umd, "/PSP_GAME/ICON0.PNG", &info_->icon.data, &info_->lock);
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
if (flags_ & GameInfoFlags::ICON1_PMF) {
|
|
ReadFileToString(&umd, "/PSP_GAME/ICON1.PMF", &info_->icon1pmf, &info_->lock);
|
|
}
|
|
if (flags_ & GameInfoFlags::PIC0) {
|
|
ReadFileToString(&umd, "/PSP_GAME/PIC0.PNG", &info_->pic0.data, &info_->lock);
|
|
info_->pic0.dataLoaded = true;
|
|
}
|
|
if (flags_ & GameInfoFlags::PIC1) {
|
|
ReadFileToString(&umd, "/PSP_GAME/PIC1.PNG", &info_->pic1.data, &info_->lock);
|
|
info_->pic1.dataLoaded = true;
|
|
}
|
|
if (flags_ & GameInfoFlags::SND) {
|
|
ReadFileToString(&umd, "/PSP_GAME/SND0.AT3", &info_->sndFileData, &info_->lock);
|
|
info_->sndDataLoaded = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IdentifiedFileType::PSP_ISO:
|
|
case IdentifiedFileType::PSP_ISO_NP:
|
|
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
|
|
{
|
|
std::string_view gameRoot = fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO ? "/UMD_VIDEO/" : "/PSP_GAME/";
|
|
SequentialHandleAllocator handles;
|
|
// Let's assume it's an ISO.
|
|
// TODO: This will currently read in the whole directory tree. Not really necessary for just a
|
|
// few files.
|
|
auto fl = info_->GetFileLoader();
|
|
if (!fl) {
|
|
// BAD! Can't win here.
|
|
ERROR_LOG(Log::Loader, "Failed getting game info for ISO %s", info_->GetFilePath().ToVisualString().c_str());
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
return;
|
|
}
|
|
std::shared_ptr<BlockDevice> bd(ConstructBlockDevice(info_->GetFileLoader().get(), &errorString));
|
|
if (!bd) {
|
|
ERROR_LOG(Log::Loader, "Failed constructing block device for ISO %s: %s", info_->GetFilePath().ToVisualString().c_str(), errorString.c_str());
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
return;
|
|
}
|
|
ISOFileSystem umd(&handles, bd);
|
|
|
|
// Alright, let's fetch the PARAM.SFO.
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
std::string paramSFOcontents;
|
|
|
|
if (ReadFileToString(&umd, join(gameRoot, "PARAM.SFO"), ¶mSFOcontents, nullptr)) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->paramSFO.ReadSFO((const u8 *)paramSFOcontents.data(), paramSFOcontents.size());
|
|
info_->ParseParamSFO(fileType);
|
|
|
|
// quick-update the info while we have the lock, so we don't need to wait for the image load to display the title.
|
|
info_->MarkReadyNoLock(GameInfoFlags::PARAM_SFO);
|
|
}
|
|
} else {
|
|
info_->title = info_->GetFilePath().GetFilename();
|
|
}
|
|
}
|
|
|
|
// Most UMDs carry a firmware updater, which is a source of things like the
|
|
// system fonts. Just note down what's there - unpacking it is a separate step.
|
|
if (flags_ & GameInfoFlags::BUNDLED_UPDATE_INFO) {
|
|
BundledUpdateInfo update;
|
|
ReadBundledUpdateInfo(&umd, "/", &update);
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->bundledUpdate = update;
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::PIC0) {
|
|
info_->pic0.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC0.PNG"), &info_->pic0.data, &info_->lock);
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::ICON1_PMF) {
|
|
ReadFileToString(&umd, join(gameRoot, "ICON1.PMF"), &info_->icon1pmf, &info_->lock);
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::PIC1) {
|
|
info_->pic1.dataLoaded = ReadFileToString(&umd, join(gameRoot, "PIC1.PNG"), &info_->pic1.data, &info_->lock);
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::SND) {
|
|
info_->sndDataLoaded = ReadFileToString(&umd, join(gameRoot, "SND0.AT3"), &info_->sndFileData, &info_->lock);
|
|
}
|
|
|
|
// Fall back to unknown icon if ISO is broken/is a homebrew ISO, override is allowed though
|
|
// First, do try to get an icon from the replacement texture pack, if available.
|
|
if (flags_ & GameInfoFlags::ICON) {
|
|
if (LoadReplacementImage(info_.get(), &info_->icon, "icon.png")) {
|
|
// Nothing more to do
|
|
} else if (ReadFileToString(&umd, join(gameRoot, "ICON0.PNG"), &info_->icon.data, &info_->lock)) {
|
|
info_->icon.dataLoaded = true;
|
|
} else {
|
|
Path screenshot_jpg = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.jpg");
|
|
Path screenshot_png = GetSysDirectory(DIRECTORY_SCREENSHOT) / (info_->id + "_00000.png");
|
|
// Try using png/jpg screenshots first
|
|
if (File::Exists(screenshot_png)) {
|
|
info_->icon.dataLoaded = ReadLocalFileToString(screenshot_png, &info_->icon.data, &info_->lock);
|
|
} else if (File::Exists(screenshot_jpg)) {
|
|
info_->icon.dataLoaded = ReadLocalFileToString(screenshot_jpg, &info_->icon.data, &info_->lock);
|
|
} else {
|
|
// This should be very rare.
|
|
info_->icon.dataLoaded = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IdentifiedFileType::ARCHIVE_ZIP:
|
|
info_->title = info_->GetFilePath().GetFilename();
|
|
info_->icon.dataLoaded = true;
|
|
break;
|
|
|
|
case IdentifiedFileType::NORMAL_DIRECTORY:
|
|
default:
|
|
info_->title = info_->GetFilePath().GetFilename();
|
|
if (info_->errorString.empty()) {
|
|
info_->errorString = errorString;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::PARAM_SFO) {
|
|
// We fetch the hasConfig together with the params, since that's what fills out the id.
|
|
info_->hasConfig = g_Config.HasGameConfig(info_->id);
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::SIZE) {
|
|
const u64 gameSizeOnDisk = info_->GetSizeOnDiskInBytes();
|
|
|
|
// NOTE: Don't touch saveDataSize/installDataSize here - those belong to SAVEDATA_SIZE,
|
|
// which can have been fetched by an earlier work item.
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->gameSizeOnDisk = gameSizeOnDisk;
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::SAVEDATA_SIZE) {
|
|
switch (fileType) {
|
|
case IdentifiedFileType::PSP_ISO:
|
|
case IdentifiedFileType::PSP_ISO_NP:
|
|
case IdentifiedFileType::PSP_DISC_DIRECTORY:
|
|
case IdentifiedFileType::PSP_PBP:
|
|
case IdentifiedFileType::PSP_PBP_DIRECTORY:
|
|
{
|
|
std::lock_guard<std::mutex> lock(info_->lock);
|
|
info_->saveDataSize = info_->GetGameSavedataSizeInBytes();
|
|
info_->installDataSize = info_->GetInstallDataSizeInBytes();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flags_ & GameInfoFlags::UNCOMPRESSED_SIZE) {
|
|
info_->gameSizeUncompressed = info_->GetSizeUncompressedInBytes();
|
|
}
|
|
|
|
// Time to update the flags.
|
|
std::unique_lock<std::mutex> lock(info_->lock);
|
|
info_->MarkReadyNoLock(flags_);
|
|
// INFO_LOG(Log::System, "Completed writing info for %s", info_->GetTitle().c_str());
|
|
}
|
|
|
|
private:
|
|
Path gamePath_;
|
|
std::shared_ptr<GameInfo> info_;
|
|
GameInfoFlags flags_{};
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(GameInfoWorkItem);
|
|
};
|
|
|
|
GameInfoCache::GameInfoCache() {
|
|
}
|
|
|
|
GameInfoCache::~GameInfoCache() {
|
|
Clear();
|
|
Shutdown();
|
|
}
|
|
|
|
void GameInfoCache::Shutdown() {
|
|
CancelAll();
|
|
}
|
|
|
|
void GameInfoCache::Clear() {
|
|
CancelAll();
|
|
|
|
std::lock_guard<std::mutex> lock(mapLock_);
|
|
// NOTE: Some shared_pointers might have other owners. We still need to wipe their textures here.
|
|
for (auto &[key, value] : info_) {
|
|
std::lock_guard<std::mutex> lock(value->lock);
|
|
value->pic0.Clear();
|
|
value->pic1.Clear();
|
|
value->icon.Clear();
|
|
value->hasFlags &= ~(GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::ICON);
|
|
}
|
|
info_.clear();
|
|
}
|
|
|
|
void GameInfoCache::CancelAll() {
|
|
std::lock_guard<std::mutex> lock(mapLock_);
|
|
for (const auto& info : info_) {
|
|
// GetFileLoader will create one if there isn't one already.
|
|
// Avoid that by checking.
|
|
if (info.second->HasFileLoader()) {
|
|
auto fl = info.second->GetFileLoader();
|
|
if (fl) {
|
|
fl->Cancel();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GameInfoCache::FlushBGs() {
|
|
std::lock_guard<std::mutex> lock(mapLock_);
|
|
for (const auto& iter : info_) {
|
|
std::lock_guard<std::mutex> lock(iter.second->lock);
|
|
iter.second->pic0.Clear();
|
|
iter.second->pic1.Clear();
|
|
if (!iter.second->sndFileData.empty()) {
|
|
iter.second->sndFileData.clear();
|
|
iter.second->sndDataLoaded = false;
|
|
}
|
|
iter.second->hasFlags &= ~(GameInfoFlags::PIC0 | GameInfoFlags::PIC1 | GameInfoFlags::SND);
|
|
}
|
|
}
|
|
|
|
void GameInfoCache::PurgeType(IdentifiedFileType fileType) {
|
|
bool retry = false;
|
|
int retryCount = 10;
|
|
// Trickery to avoid sleeping with the lock held.
|
|
do {
|
|
if (retry) {
|
|
retryCount--;
|
|
if (retryCount == 0) {
|
|
break;
|
|
}
|
|
}
|
|
retry = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mapLock_);
|
|
for (auto iter = info_.begin(); iter != info_.end();) {
|
|
auto &info = iter->second;
|
|
if (!(info->hasFlags & GameInfoFlags::FILE_TYPE)) {
|
|
iter++;
|
|
continue;
|
|
}
|
|
if (info->fileType != fileType) {
|
|
iter++;
|
|
continue;
|
|
}
|
|
// TODO: Find a better way to wait here.
|
|
if (info->pendingFlags != (GameInfoFlags)0) {
|
|
INFO_LOG(Log::Loader, "%s: pending flags %08x, retrying", info->GetTitle().c_str(), (int)info->pendingFlags);
|
|
retry = true;
|
|
break;
|
|
}
|
|
iter = info_.erase(iter);
|
|
}
|
|
}
|
|
|
|
sleep_ms(10, "game-info-cache-purge-poll");
|
|
} while (retry);
|
|
}
|
|
|
|
// Call on the main thread ONLY - that is from stuff called from NativeFrame.
|
|
// Can also be called from the audio thread for menu background music, but that cannot request images!
|
|
std::shared_ptr<GameInfo> GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags, GameInfoFlags *outHasFlags, GameInfoFlags refetchFlags) {
|
|
const std::string &pathStr = gamePath.ToString();
|
|
|
|
// _dbg_assert_(gamePath != GetSysDirectory(DIRECTORY_SAVEDATA));
|
|
|
|
// This is always needed to determine the method to get the other info, so make sure it's computed first.
|
|
|
|
wantFlags |= GameInfoFlags::FILE_TYPE;
|
|
|
|
mapLock_.lock();
|
|
|
|
auto iter = info_.find(pathStr);
|
|
if (iter != info_.end()) {
|
|
// There's already a structure about this game. Let's check.
|
|
std::shared_ptr<GameInfo> info = iter->second;
|
|
mapLock_.unlock();
|
|
|
|
info->FinishPendingTextureLoads(draw);
|
|
info->lastAccessedTime = time_now_d();
|
|
|
|
GameInfoFlags wanted = (GameInfoFlags)0;
|
|
{
|
|
// Careful now!
|
|
std::unique_lock<std::mutex> lock(info->lock);
|
|
if (refetchFlags != GameInfoFlags::EMPTY) {
|
|
// Forget some flags!
|
|
info->hasFlags &= ~refetchFlags;
|
|
}
|
|
GameInfoFlags willHaveFlags = info->hasFlags | info->pendingFlags; // We don't want to re-fetch data that we have, so or in pendingFlags.
|
|
wanted = (GameInfoFlags)((int)wantFlags & ~(int)willHaveFlags); // & is reserved for testing so we have to cast to int. ugh.
|
|
// FILE_TYPE is special: every work item switches on info->fileType, so it's not enough that
|
|
// some *pending* item is going to compute it - that item may not have got there yet, and we'd
|
|
// switch on UNKNOWN and mark our flags ready having loaded nothing. Cheap enough to redo.
|
|
if (wanted != GameInfoFlags::EMPTY && !(info->hasFlags & GameInfoFlags::FILE_TYPE)) {
|
|
wanted |= GameInfoFlags::FILE_TYPE;
|
|
}
|
|
info->pendingFlags |= wanted;
|
|
if (outHasFlags) {
|
|
*outHasFlags = info->hasFlags;
|
|
}
|
|
}
|
|
|
|
if (wanted != (GameInfoFlags)0) {
|
|
// We're missing info that we want. Go get it!
|
|
GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wanted);
|
|
g_threadManager.EnqueueTask(item);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
std::shared_ptr<GameInfo> info = std::make_shared<GameInfo>(gamePath);
|
|
info->pendingFlags = wantFlags;
|
|
info->lastAccessedTime = time_now_d();
|
|
info_.insert(std::make_pair(pathStr, info));
|
|
if (outHasFlags) {
|
|
*outHasFlags = info->hasFlags;
|
|
}
|
|
mapLock_.unlock();
|
|
|
|
// Just get all the stuff we wanted.
|
|
GameInfoWorkItem *item = new GameInfoWorkItem(gamePath, info, wantFlags);
|
|
g_threadManager.EnqueueTask(item);
|
|
return info;
|
|
}
|