Merge pull request #22854 from hrydgard/ui-io-off-thread

UI: Move file I/O off the render thread in a few screens
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2026-10-30 13:08:45 +01:00
commit e3a25dccdf
9 files changed
+225 -76

No files matched your search

+1 -1
View File
@@ -216,7 +216,7 @@ const char *g_ExceptionActionValues[] = {
static const CommandLineParam g_autoParams[] = {
{POFF(fullscreen), CmdParamType::Bool, "fullscreen", '\0', "Force full screen mode", CmdLineMode::Application},
{POFF(fullscreen), CmdParamType::BoolInverse, "windowed", '\0', "Force windowed mode", CmdLineMode::Application},
{POFF(startScreen), CmdParamType::String, "start-screen", '\0', "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest')", CmdLineMode::Application},
{POFF(startScreen), CmdParamType::String, "start-screen", '\0', "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest', 'firmware')", CmdLineMode::Application},
{POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application},
{POFF(gameWindow), CmdParamType::Bool, "game-window", '\0', "Show the game in a second window, for testing multiple outputs (Vulkan, or Metal on Mac)", CmdLineMode::Application},
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Exit the application when the game ends, instead of returning to the menu", CmdLineMode::Application},
+60 -13
View File
@@ -21,6 +21,8 @@
#include "Common/StringUtils.h"
#include "Common/System/Request.h"
#include "Common/System/System.h"
#include "Common/Thread/Promise.h"
#include "Common/Thread/ThreadManager.h"
#include "Common/UI/Notice.h"
#include "Common/UI/PopupScreens.h"
#include "Common/UI/ScreenManager.h"
@@ -41,8 +43,51 @@ std::string_view FirmwareScreen::GetTitle() const {
}
void FirmwareScreen::BeforeCreateViews() {
nandRoot_ = GetSysDirectory(DIRECTORY_NAND);
ReadInstalledFirmwareInfo(nandRoot_, &info_);
if (!scanned_ && !scan_) {
nandRoot_ = GetSysDirectory(DIRECTORY_NAND);
StartScan(false);
}
}
void FirmwareScreen::StartScan(bool eraseFirst) {
if (scan_) {
rescanPending_ = true;
erasePending_ = erasePending_ || eraseFirst;
return;
}
scan_ = std::make_shared<ScanState>();
g_threadManager.EnqueueTask(new IndependentTask(TaskType::IO_BLOCKING, TaskPriority::NORMAL,
[state = scan_, nandRoot = nandRoot_, eraseFirst]() {
if (eraseFirst) {
EraseInstalledFirmware(nandRoot, &state->eraseError);
}
ReadInstalledFirmwareInfo(nandRoot, &state->info);
// Everything above is published by this store.
state->done = true;
}));
// Disables the buttons that would touch the NAND while this runs.
RecreateViews();
}
void FirmwareScreen::update() {
UITwoPaneBaseDialogScreen::update();
if (!scan_ || !scan_->done) {
return;
}
if (!scan_->eraseError.empty()) {
ERROR_LOG(Log::Loader, "Failed to erase the firmware: %s", scan_->eraseError.c_str());
}
info_ = scan_->info;
scan_.reset();
scanned_ = true;
if (rescanPending_) {
const bool erase = erasePending_;
rescanPending_ = false;
erasePending_ = false;
StartScan(erase);
} else {
RecreateViews();
}
}
void FirmwareScreen::CreateContentViews(UI::ViewGroup *parent) {
@@ -55,6 +100,12 @@ void FirmwareScreen::CreateContentViews(UI::ViewGroup *parent) {
ChoiceGroupList *list = parent->Add(new ChoiceGroupList());
if (!scanned_) {
auto mm = GetI18NCategory(I18NCat::MAINMENU);
list->Add(new TextView(mm->T("Loading..."), ALIGN_LEFT, false));
return;
}
if (!info_.anythingInstalled) {
list->Add(new NoticeView(NoticeLevel::INFO, sy->T("No firmware installed"),
sy->T("FirmwareSources", "PPSSPP has its own replacements for the PSP's system files, so a firmware is optional. Installing one gets you the real fonts, and lets you launch the XMB. Most game discs carry a firmware updater - open a game's info screen to install from it.")));
@@ -109,6 +160,7 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) {
// Booting anything replaces the running game, and erasing the NAND out from under a game
// that has flash0 mounted is worse - so while a game is running, this screen is read-only.
const bool gameRunning = PSP_IsInited();
const bool scanning = scan_ != nullptr;
ChoiceGroupList *groups = parent->Add(new ChoiceGroupList());
if (info_.hasVsh && !FirmwareVersionSupportsVSH(info_.version)) {
@@ -134,7 +186,7 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) {
install->OnClick.Add([this](UI::EventParams &) {
BrowseForUpdater();
});
install->SetEnabled(!gameRunning);
install->SetEnabled(!gameRunning && !scanning);
}
if (info_.anythingInstalled) {
@@ -142,7 +194,7 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) {
erase->OnClick.Add([this](UI::EventParams &) {
AskToErase();
});
erase->SetEnabled(!gameRunning);
erase->SetEnabled(!gameRunning && !scanning);
}
if (System_GetPropertyBool(SYSPROP_HAS_OPEN_DIRECTORY)) {
@@ -166,10 +218,9 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) {
}
void FirmwareScreen::dialogFinished(const Screen *dialog, DialogResult result) {
// Coming back from the installer (or the erase confirmation), the NAND may well look
// different than it did when we scanned it. Rescanning is cheap, so just always redo it -
// BeforeCreateViews does the scan.
RecreateViews();
// Coming back from the installer, the NAND may well look different than it did when we
// scanned it, so always rescan.
StartScan(false);
UITwoPaneBaseDialogScreen::dialogFinished(dialog, result);
}
@@ -195,10 +246,6 @@ void FirmwareScreen::AskToErase() {
if (!erase) {
return;
}
std::string error;
if (!EraseInstalledFirmware(nandRoot_, &error)) {
ERROR_LOG(Log::Loader, "Failed to erase the firmware: %s", error.c_str());
}
RecreateViews();
StartScan(true);
}));
}
+18
View File
@@ -17,6 +17,10 @@
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include "Common/File/Path.h"
#include "Common/UI/UIScreen.h"
#include "Common/UI/ViewGroup.h"
@@ -35,6 +39,8 @@ public:
const char *tag() const override { return "Firmware"; }
void update() override;
protected:
std::string_view GetTitle() const override;
void BeforeCreateViews() override;
@@ -46,7 +52,19 @@ private:
void LaunchVSH();
void BrowseForUpdater();
void AskToErase();
// Scans (and first erases, if asked) on a worker thread, since the NAND can be on slow storage.
void StartScan(bool eraseFirst);
struct ScanState {
std::atomic<bool> done{};
InstalledFirmwareInfo info;
std::string eraseError;
};
Path nandRoot_;
InstalledFirmwareInfo info_;
std::shared_ptr<ScanState> scan_; // While a scan is running.
bool scanned_ = false;
bool rescanPending_ = false;
bool erasePending_ = false;
};
+33 -26
View File
@@ -221,37 +221,40 @@ u64 GameInfo::GetSizeUncompressedInBytes() {
}
}
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);
static std::string FormatFileDate(const tm &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);
}
Path GameInfo::GetMTimePath() const {
switch (fileType) {
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
return GetFilePath() / "PARAM.SFO";
case IdentifiedFileType::PSP_PBP_DIRECTORY:
return GetFilePath() / "EBOOT.PBP";
default:
return GetFilePath();
}
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());
if (!hasMTime) {
return "";
}
return FormatFileDate(mtime);
}
// Not too meaningful if the object itself is a savedata directory...
@@ -974,11 +977,15 @@ handleELF:
if (flags_ & GameInfoFlags::SIZE) {
const u64 gameSizeOnDisk = info_->GetSizeOnDiskInBytes();
tm mtime{};
const bool hasMTime = File::GetModifTime(info_->GetMTimePath(), mtime);
// 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;
info_->mtime = mtime;
info_->hasMTime = hasMTime;
}
if (flags_ & GameInfoFlags::SAVEDATA_SIZE) {
+7 -2
View File
@@ -17,6 +17,7 @@
#pragma once
#include <ctime>
#include <string>
#include <map>
#include <memory>
@@ -97,9 +98,10 @@ public:
u64 GetGameSavedataSizeInBytes() const; // For games
u64 GetInstallDataSizeInBytes() const;
// For various kinds of savedata, mainly.
// NOTE: This one actually performs I/O directly, not cached.
// For various kinds of savedata, mainly. Read along with SIZE, empty before that.
std::string GetMTime() const;
// The file whose modification time GetMTime() shows.
Path GetMTimePath() const;
void ParseParamSFO(IdentifiedFileType type);
const ParamSFOData &GetParamSFO() const {
@@ -190,6 +192,9 @@ public:
u64 gameSizeOnDisk = 0; // compressed size, in case of CSO
u64 saveDataSize = 0;
u64 installDataSize = 0;
// With SIZE.
bool hasMTime = false;
tm mtime{};
// The firmware updater bundled on the disc, if any - see GameInfoFlags::BUNDLED_UPDATE_INFO.
// Always left empty for anything that isn't an ISO.
+63 -23
View File
@@ -20,6 +20,8 @@
#include "ppsspp_config.h"
#include "Common/Render/DrawBuffer.h"
#include "Common/Thread/Promise.h"
#include "Common/Thread/ThreadManager.h"
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
@@ -188,6 +190,19 @@ void GameScreen::update() {
}
}
if (!lookupsStarted_ && (knownFlags_ & GameInfoFlags::PARAM_SFO)) {
StartLookups();
}
if (lookupTask_ && lookupTask_->done) {
lookups_ = std::move(lookupTask_->result);
lookupTask_.reset();
if (lookupsPending_) {
lookupsPending_ = false;
StartLookups();
}
recreate = true;
}
// Has the user requested a CRC32?
if (CRC32string == "...") {
// Wait until the CRC32 is ready. It might take time on some devices.
@@ -229,7 +244,7 @@ static bool FileTypeHasIcon(IdentifiedFileType fileType) {
}
}
static bool FileTypeIsPlayable(IdentifiedFileType fileType) {
static bool FileTypeIsPlayable(IdentifiedFileType fileType, bool firmwareRunsPS1Discs) {
switch (fileType) {
case IdentifiedFileType::ERROR_IDENTIFYING:
case IdentifiedFileType::UNKNOWN:
@@ -245,19 +260,44 @@ static bool FileTypeIsPlayable(IdentifiedFileType fileType) {
return false;
case IdentifiedFileType::PSX_ISO:
// Through the firmware's POPS.
return InstalledFirmwareRunsPS1Discs();
return firmwareRunsPS1Discs;
default:
return true;
}
}
void GameScreen::RefreshInstalledUpdate() {
hasInstalledUpdate_ = false;
// Homebrew reuses real disc IDs often enough that we'd get false positives.
if (isHomebrew_ || !(knownFlags_ & GameInfoFlags::PARAM_SFO)) {
void GameScreen::StartLookups() {
lookupsStarted_ = true;
if (lookupTask_) {
lookupsPending_ = true;
return;
}
hasInstalledUpdate_ = FindInstalledGameUpdate(info_->id, &installedUpdate_);
lookupTask_ = std::make_shared<LookupTask>();
// Homebrew reuses real disc IDs often enough that we'd get false positives.
const std::string updateId = info_->region == GameRegion::HOMEBREW ? std::string() : info_->id;
g_threadManager.EnqueueTask(new IndependentTask(TaskType::IO_BLOCKING, TaskPriority::NORMAL,
[task = lookupTask_, id = info_->id, updateId, fileType = info_->fileType, lang = g_Config.sLanguageIni]() {
Lookups &result = task->result;
if (!updateId.empty()) {
result.hasInstalledUpdate = FindInstalledGameUpdate(updateId, &result.installedUpdate);
}
result.plugins = HLEPlugins::FindPlugins(id, lang);
if (fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO) {
result.umdVideoVSHPath = GetUmdVideoVSHPath();
} else if (fileType == IdentifiedFileType::PSX_ISO) {
result.firmwareRunsPS1Discs = InstalledFirmwareRunsPS1Discs();
}
// Everything above is published by this store.
task->done = true;
}));
}
void GameScreen::dialogFinished(const Screen *dialog, DialogResult result) {
// Could have been the firmware installer, or a deleted game update.
if (lookupsStarted_) {
StartLookups();
}
UITwoPaneBaseDialogScreen::dialogFinished(dialog, result);
}
// Both panes use isHomebrew_, and which is created first depends on orientation.
@@ -370,7 +410,7 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
LinearLayout *infoLayout = new LinearLayout(ORIENT_VERTICAL, new AnchorLayoutParams(10, 200, NONE, NONE));
leftColumn->Add(infoLayout);
if (info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO && GetUmdVideoVSHPath().empty()) {
if (info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO && !lookupTask_ && lookupsStarted_ && lookups_.umdVideoVSHPath.empty()) {
auto er = GetI18NCategory(I18NCat::ERRORS);
leftColumn->Add(new NoticeView(NoticeLevel::INFO, ApplySafeSubstitutions(er->T("PSP firmware version %1 or later must be installed to play UMD video"), UMD_VIDEO_MIN_FIRMWARE), ""));
leftColumn->Add(new Choice(di->T("More info"), ImageID("I_LINK_OUT_QUESTION"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Add([](UI::EventParams &e) {
@@ -510,24 +550,24 @@ void GameScreen::CreateContentViews(UI::ViewGroup *parent) {
// An installed game update replaces the disc's executable, so it's worth saying so here -
// otherwise there's nothing in the UI to explain why a patched game is running.
RefreshInstalledUpdate();
if (hasInstalledUpdate_) {
const InstalledGameUpdate &installedUpdate = lookups_.installedUpdate;
if (lookups_.hasInstalledUpdate) {
infoLayout->Add(new ItemHeader(ga->T("Game update")));
std::string updateLine = installedUpdate_.title;
if (!installedUpdate_.appVer.empty()) {
const std::string version = ApplySafeSubstitutions(ga->T("Version %1"), installedUpdate_.appVer);
std::string updateLine = installedUpdate.title;
if (!installedUpdate.appVer.empty()) {
const std::string version = ApplySafeSubstitutions(ga->T("Version %1"), installedUpdate.appVer);
updateLine = updateLine.empty() ? version : updateLine + " - " + version;
}
if (updateLine.empty()) {
updateLine = ga->T("Installed");
}
updateLine += " - " + NiceSizeFormat(installedUpdate_.sizeOnDisk);
updateLine += " - " + NiceSizeFormat(installedUpdate.sizeOnDisk);
infoLayout->Add(new TextView(updateLine, ALIGN_LEFT, true))->SetBullet(true);
infoLayout->Add(new TextView(GetFriendlyPath(installedUpdate_.folder), ALIGN_LEFT | FLAG_WRAP_TEXT, true))->SetBullet(true);
infoLayout->Add(new TextView(GetFriendlyPath(installedUpdate.folder), ALIGN_LEFT | FLAG_WRAP_TEXT, true))->SetBullet(true);
}
// Show plugin info_, if any. Later might add checkboxes.
auto plugins = HLEPlugins::FindPlugins(info_->id, g_Config.sLanguageIni);
const std::vector<HLEPlugins::PluginInfo> &plugins = lookups_.plugins;
if (!plugins.empty()) {
auto sy = GetI18NCategory(I18NCat::SYSTEM);
infoLayout->Add(new ItemHeader(sy->T("Plugins")));
@@ -558,8 +598,8 @@ void GameScreen::CreateSettingsViews(UI::ViewGroup *rightColumn) {
ChoiceGroup *choices = rightColumnItems->Add(new ChoiceGroup());
// UMD Video plays in the XMB, with the disc in the drive.
const Path vshPath = info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO ? GetUmdVideoVSHPath() : Path();
if (!inGame_ && (FileTypeIsPlayable(info_->fileType) || !vshPath.empty())) {
const Path vshPath = info_->fileType == IdentifiedFileType::PSP_UMD_VIDEO_ISO ? lookups_.umdVideoVSHPath : Path();
if (!inGame_ && (FileTypeIsPlayable(info_->fileType, lookups_.firmwareRunsPS1Discs) || !vshPath.empty())) {
Choice *play = choices->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")));
play->OnClick.Add([this, vshPath](UI::EventParams &e) {
if (vshPath.empty()) {
@@ -693,8 +733,7 @@ void GameScreen::CreateContextMenu(UI::ViewGroup *parent) {
});
}
RefreshInstalledUpdate();
if (!inGame_ && hasInstalledUpdate_) {
if (!inGame_ && lookups_.hasInstalledUpdate) {
Choice *btnDeleteUpdate = parent->Add(new Choice(ga->T("Delete game update"), ImageID("I_TRASHCAN")));
btnDeleteUpdate->OnClick.Handle(this, &GameScreen::OnDeleteGameUpdate);
}
@@ -735,9 +774,10 @@ void GameScreen::CreateContextMenu(UI::ViewGroup *parent) {
}
void GameScreen::OnDeleteGameUpdate(UI::EventParams &e) {
if (!hasInstalledUpdate_) {
if (!lookups_.hasInstalledUpdate) {
return;
}
const InstalledGameUpdate &installedUpdate = lookups_.installedUpdate;
auto di = GetI18NCategory(I18NCat::DIALOG);
auto ga = GetI18NCategory(I18NCat::GAME);
@@ -745,10 +785,10 @@ void GameScreen::OnDeleteGameUpdate(UI::EventParams &e) {
prompt += "\n\n";
// Say exactly what disappears - for a digital game the folder holds the game itself, so only
// the update's executable goes.
prompt += GetFriendlyPath(installedUpdate_.sharesFolderWithGame ? installedUpdate_.pbootPath : installedUpdate_.folder);
prompt += GetFriendlyPath(installedUpdate.sharesFolderWithGame ? installedUpdate.pbootPath : installedUpdate.folder);
const bool trashAvailable = System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN);
const InstalledGameUpdate update = installedUpdate_;
const InstalledGameUpdate update = installedUpdate;
screenManager()->push(
new UI::MessagePopupScreen(ga->T("Delete game update"), prompt, trashAvailable ? di->T("Move to trash") : di->T("Delete"), di->T("Cancel"),
[this, update](bool yes) {
+25 -6
View File
@@ -17,12 +17,16 @@
#pragma once
#include <atomic>
#include <functional>
#include <memory>
#include <vector>
#include "UI/BaseScreens.h"
#include "Common/UI/UIScreen.h"
#include "Common/File/Path.h"
#include "UI/GameInfoCache.h"
#include "Core/HLE/Plugins.h"
#include "Core/Util/PkgUnpack.h"
#include "UI/SimpleDialogScreen.h"
@@ -52,6 +56,7 @@ protected:
void CreateContentViews(UI::ViewGroup *parent) override;
void CreateSettingsViews(UI::ViewGroup *parent) override;
void CreateContextMenu(UI::ViewGroup *parent) override;
void dialogFinished(const Screen *dialog, DialogResult result) override;
std::string_view GetTitle() const override;
// The progress bar of a pack running in the background.
@@ -67,9 +72,21 @@ private:
void OnSetBackground(UI::EventParams &e);
void OnDeleteGameUpdate(UI::EventParams &e);
// Checks whether a game update is installed for this game. Both the info pane and the context
// menu need the answer, and either can be built first.
void RefreshInstalledUpdate();
// What's known about the game from outside its file: an installed update, plugins, and whether
// the installed firmware can play it. All disk access, so it's looked up on a worker thread,
// and the views are recreated when it's in.
struct Lookups {
bool hasInstalledUpdate = false;
InstalledGameUpdate installedUpdate;
std::vector<HLEPlugins::PluginInfo> plugins;
Path umdVideoVSHPath;
bool firmwareRunsPS1Discs = false;
};
struct LookupTask {
std::atomic<bool> done{};
Lookups result;
};
void StartLookups();
std::string CRC32string;
@@ -87,9 +104,11 @@ private:
int packGeneration_ = 0;
bool packBarShown_ = false;
// A game update installed in PSP/GAME/<DISC_ID>, which is what actually runs when there is one.
InstalledGameUpdate installedUpdate_;
bool hasInstalledUpdate_ = false;
// installedUpdate is a game update in PSP/GAME/<DISC_ID>, which is what actually runs when there is one.
Lookups lookups_;
std::shared_ptr<LookupTask> lookupTask_; // While one runs.
bool lookupsStarted_ = false;
bool lookupsPending_ = false;
std::shared_ptr<GameInfo> info_;
mutable std::string titleCache_;
+10 -3
View File
@@ -89,9 +89,14 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons
// Otherwise (launching from a file association, a shortcut, drag-and-drop...) we have to block until
// it's available - we can't decide what to do below without it. HIGH, so we don't wait behind
// whatever else is queued, like a search's loads for every game in a big list.
const GameInfoFlags neededFlags = GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO;
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, path, neededFlags, nullptr, GameInfoFlags::EMPTY, TaskPriority::HIGH);
info->WaitUntilReady(neededFlags);
// Only the file type at first: just a few of the cases below need the PARAM.SFO, and reading
// it out of a disc image costs more disk access.
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, path, GameInfoFlags::FILE_TYPE, nullptr, GameInfoFlags::EMPTY, TaskPriority::HIGH);
info->WaitUntilReady(GameInfoFlags::FILE_TYPE);
const auto waitForParamSFO = [&]() {
g_gameInfoCache->GetInfo(nullptr, path, GameInfoFlags::PARAM_SFO, nullptr, GameInfoFlags::EMPTY, TaskPriority::HIGH);
info->WaitUntilReady(GameInfoFlags::PARAM_SFO);
};
switch (info->fileType) {
case IdentifiedFileType::PSP_UMD_VIDEO_ISO:
@@ -138,6 +143,7 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
{
// Show the savedata popup, why not?
waitForParamSFO();
std::string title = SanitizeString(info->GetTitle(), StringRestriction::NoLineBreaksOrSpecials, 0, 200);
screenManager->push(new SavedataPopupScreen(Path(), path, title));
return;
@@ -147,6 +153,7 @@ static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, cons
{
// Check if it's an update file. If so, we'll offer to install it directly,
// instead of running it (which currently will not work).
waitForParamSFO();
if (info->id == "MSTKUPDATE") {
std::string title = info->GetTitle(); // includes the version.
// The unpacker wants the PBP itself, not the folder it happens to sit in.
+8 -2
View File
@@ -210,6 +210,7 @@ private:
UI::Button *launchButton_ = nullptr;
UI::Button *cancelButton_ = nullptr;
bool wasInstalled_ = false;
GameManagerState lastState_ = GameManagerState::IDLE;
};
void ProductView::CreateViews() {
@@ -293,8 +294,13 @@ void ProductView::CreateViews() {
}
void ProductView::Update() {
if (wasInstalled_ != IsGameInstalled()) {
CreateViews();
// Installing or uninstalling is the only thing that changes it, so don't hit the disk every frame.
const GameManagerState state = g_GameManager.GetState();
if (state != lastState_) {
lastState_ = state;
if (state == GameManagerState::IDLE && wasInstalled_ != IsGameInstalled()) {
CreateViews();
}
}
if (installButton_) {
installButton_->SetEnabled(g_GameManager.GetState() == GameManagerState::IDLE);