mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-03 21:03:44 +01:00
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:
9 files changed
+225
-76
No files matched your search
+1
-1
@@ -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
@@ -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);
|
||||
}));
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user