Much more UI work on savedata import

fix
This commit is contained in:
Henrik Rydgård 2024-09-07 15:01:13 +02:00
parent f38cc4a959
commit 8186f14a57
11 changed files with 247 additions and 88 deletions

View File

@ -266,11 +266,17 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
int directoriesInRoot = 0;
bool hasParamSFO = false;
bool hasIcon0PNG = false;
s64 totalFileSize = 0;
// TODO: It might be cleaner to write separate detection functions, but this big loop doing it all at once
// is quite convenient and makes it easy to add shared heuristics.
for (int i = 0; i < numFiles; i++) {
const char *fn = zip_get_name(z, i, 0);
zip_stat_t stat{};
zip_stat_index(z, i, 0, &stat);
totalFileSize += stat.size;
std::string zippedName = fn;
std::transform(zippedName.begin(), zippedName.end(), zippedName.begin(),
[](unsigned char c) { return asciitolower(c); }); // Not using std::tolower to avoid Turkish I->ı conversion.
@ -282,6 +288,7 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
// A directory. Not all zips bother including these.
continue;
}
int prevSlashLocation = -1;
int slashCount = countSlashes(zippedName, &prevSlashLocation);
if (zippedName.find("eboot.pbp") != std::string::npos) {
@ -318,9 +325,12 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
ParamSFOData sfo;
if (sfo.ReadSFO((const u8 *)paramSFOContents.data(), paramSFOContents.size())) {
if (sfo.HasKey("TITLE")) {
std::string title = sfo.GetValueString("TITLE") + ": " + sfo.GetValueString("SAVEDATA_TITLE");
std::string details = sfo.GetValueString("SAVEDATA_DETAIL");
info->contentName = title + "\n\n" + details;
info->gameTitle = sfo.GetValueString("TITLE");
info->savedataTitle = sfo.GetValueString("SAVEDATA_TITLE");
char buff[20];
strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&stat.mtime));
info->mTime = buff;
info->savedataDetails = sfo.GetValueString("SAVEDATA_DETAIL");
info->savedataDir = sfo.GetValueString("SAVEDATA_DIRECTORY"); // should also be parsable from the path.
hasParamSFO = true;
}
@ -339,6 +349,7 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) {
info->isoFileIndex = isoFileIndex;
info->textureIniIndex = textureIniIndex;
info->ignoreMetaFiles = false;
info->totalFileSize = totalFileSize;
// Priority ordering for detecting the various kinds of zip file content.s
if (isPSPMemstickGame) {
@ -613,7 +624,6 @@ bool GameManager::ExtractFile(struct zip *z, int file_index, const Path &outFile
struct zip_stat zstat;
zip_stat_index(z, file_index, 0, &zstat);
size_t size = zstat.size;
zip_file *zf = zip_fopen_index(z, file_index, 0);
if (!zf) {
ERROR_LOG(Log::HLE, "Failed to open file by index (%d) (%s)", file_index, outFilename.c_str());

View File

@ -50,8 +50,14 @@ struct ZipFileInfo {
int isoFileIndex; // for ISO
int textureIniIndex; // for textures
bool ignoreMetaFiles;
std::string contentName;
std::string gameTitle; // from PARAM.SFO if available
std::string savedataTitle;
std::string savedataDetails;
std::string savedataDir;
std::string mTime;
s64 totalFileSize;
std::string contentName;
};
struct ZipFileTask {

View File

@ -53,7 +53,7 @@ bool CwCheatScreen::TryLoadCheatInfo() {
if (!info->Ready(GameInfoFlags::PARAM_SFO)) {
return false;
}
gameID = info->paramSFO.GetValueString("DISC_ID");
gameID = info->GetParamSFO().GetValueString("DISC_ID");
if ((info->id.empty() || !info->disc_total)
&& gamePath_.FilePathContainsNoCase("PSP/GAME/")) {
gameID = g_paramSFO.GenerateFakeID(gamePath_);

View File

@ -33,6 +33,7 @@
#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"
@ -128,7 +129,7 @@ bool GameInfo::Delete() {
}
}
u64 GameInfo::GetGameSizeOnDiskInBytes() {
u64 GameInfo::GetSizeOnDiskInBytes() {
switch (fileType) {
case IdentifiedFileType::PSP_PBP_DIRECTORY:
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
@ -140,7 +141,7 @@ u64 GameInfo::GetGameSizeOnDiskInBytes() {
}
}
u64 GameInfo::GetGameSizeUncompressedInBytes() {
u64 GameInfo::GetSizeUncompressedInBytes() {
switch (fileType) {
case IdentifiedFileType::PSP_PBP_DIRECTORY:
case IdentifiedFileType::PSP_SAVEDATA_DIRECTORY:
@ -161,6 +162,39 @@ u64 GameInfo::GetGameSizeUncompressedInBytes() {
}
}
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() {
@ -183,7 +217,7 @@ std::vector<Path> GameInfo::GetSaveDataDirectories() {
return directories;
}
u64 GameInfo::GetSaveDataSizeInBytes() {
u64 GameInfo::GetGameSavedataSizeInBytes() {
if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) {
return 0;
}
@ -466,6 +500,10 @@ public:
info_->fileType = Identify_File(info_->GetFileLoader().get(), &errorString);
}
if (!info_->Ready(GameInfoFlags::FILE_TYPE) && !(flags_ & GameInfoFlags::FILE_TYPE)) {
_dbg_assert_(false);
}
switch (info_->fileType) {
case IdentifiedFileType::PSP_PBP:
case IdentifiedFileType::PSP_PBP_DIRECTORY:
@ -768,12 +806,24 @@ handleELF:
if (flags_ & GameInfoFlags::SIZE) {
std::lock_guard<std::mutex> lock(info_->lock);
info_->gameSizeOnDisk = info_->GetGameSizeOnDiskInBytes();
info_->saveDataSize = info_->GetSaveDataSizeInBytes();
info_->gameSizeOnDisk = info_->GetSizeOnDiskInBytes();
switch (info_->fileType) {
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
case IdentifiedFileType::PSP_DISC_DIRECTORY:
case IdentifiedFileType::PSP_PBP:
case IdentifiedFileType::PSP_PBP_DIRECTORY:
info_->saveDataSize = info_->GetGameSavedataSizeInBytes();
info_->installDataSize = info_->GetInstallDataSizeInBytes();
break;
default:
info_->saveDataSize = 0;
info_->installDataSize = 0;
break;
}
}
if (flags_ & GameInfoFlags::UNCOMPRESSED_SIZE) {
info_->gameSizeUncompressed = info_->GetGameSizeUncompressedInBytes();
info_->gameSizeUncompressed = info_->GetSizeUncompressedInBytes();
}
// Time to update the flags.
@ -883,6 +933,8 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) {
std::shared_ptr<GameInfo> GameInfoCache::GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags) {
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;

View File

@ -96,12 +96,20 @@ public:
std::shared_ptr<FileLoader> GetFileLoader();
void DisposeFileLoader();
u64 GetGameSizeUncompressedInBytes(); // NOTE: More expensive than GetGameSizeOnDiskInBytes().
u64 GetGameSizeOnDiskInBytes();
u64 GetSaveDataSizeInBytes();
u64 GetSizeUncompressedInBytes(); // NOTE: More expensive than GetGameSizeOnDiskInBytes().
u64 GetSizeOnDiskInBytes();
u64 GetGameSavedataSizeInBytes(); // For games
u64 GetInstallDataSizeInBytes();
// For various kinds of savedata, mainly.
// NOTE: This one actually performs I/O directly, not cached.
std::string GetMTime() const;
void ParseParamSFO();
const ParamSFOData &GetParamSFO() const {
_dbg_assert_(hasFlags & GameInfoFlags::PARAM_SFO);
return paramSFO;
}
void FinishPendingTextureLoads(Draw::DrawContext *draw);
std::vector<Path> GetSaveDataDirectories();
@ -152,7 +160,6 @@ public:
int disc_number = 0;
int region = -1;
IdentifiedFileType fileType;
ParamSFOData paramSFO;
bool hasConfig = false;
// Pre read the data, create a texture the next time (GL thread..)
@ -171,6 +178,7 @@ public:
u64 installDataSize = 0;
protected:
ParamSFOData paramSFO;
// Note: this can change while loading, use GetTitle().
std::string title;
@ -182,6 +190,7 @@ protected:
private:
DISALLOW_COPY_AND_ASSIGN(GameInfo);
friend class GameInfoWorkItem;
};
class GameInfoCache {
@ -197,6 +206,7 @@ public:
// but filled in later asynchronously in the background. So keep calling this,
// redrawing the UI often. Only set flags to GAMEINFO_WANTBG or WANTSND if you really want them
// because they're big. bgTextures and sound may be discarded over time as well.
// NOTE: This never returns null, so you don't need to check for that. Do check Ready() flags though.
std::shared_ptr<GameInfo> GetInfo(Draw::DrawContext *draw, const Path &gamePath, GameInfoFlags wantFlags);
void FlushBGs(); // Gets rid of all BG textures. Also gets rid of bg sounds.

View File

@ -503,7 +503,7 @@ UI::EventReturn GameScreen::OnPlay(UI::EventParams &e) {
UI::EventReturn GameScreen::OnGameSettings(UI::EventParams &e) {
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(NULL, gamePath_, GameInfoFlags::PARAM_SFO);
if (info && info->Ready(GameInfoFlags::PARAM_SFO)) {
std::string discID = info->paramSFO.GetValueString("DISC_ID");
std::string discID = info->GetParamSFO().GetValueString("DISC_ID");
if ((discID.empty() || !info->disc_total) && gamePath_.FilePathContainsNoCase("PSP/GAME/"))
discID = g_paramSFO.GenerateFakeID(gamePath_);
screenManager()->push(new GameSettingsScreen(gamePath_, discID, true));

View File

@ -21,8 +21,10 @@
#include "Common/StringUtils.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h"
#include "Core/System.h"
#include "Core/Util/GameManager.h"
#include "Core/Loaders.h"
#include "UI/InstallZipScreen.h"
#include "UI/MainScreen.h"
#include "UI/OnScreenDisplay.h"
@ -41,6 +43,7 @@ void InstallZipScreen::CreateViews() {
auto di = GetI18NCategory(I18NCat::DIALOG);
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
auto er = GetI18NCategory(I18NCat::ERRORS);
auto ga = GetI18NCategory(I18NCat::GAME);
Margins actionMenuMargins(0, 100, 15, 0);
@ -61,7 +64,7 @@ void InstallZipScreen::CreateViews() {
installChoice_ = nullptr;
doneView_ = nullptr;
installChoice_ = nullptr;
existingSaveView_ = nullptr;
if (z) {
DetectZipFileContents(z, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) {
@ -69,6 +72,9 @@ void InstallZipScreen::CreateViews() {
leftColumn->Add(new TextView(question));
leftColumn->Add(new TextView(shortFilename));
if (!zipFileInfo_.contentName.empty()) {
leftColumn->Add(new TextView(zipFileInfo_.contentName));
}
doneView_ = leftColumn->Add(new TextView(""));
@ -89,15 +95,36 @@ void InstallZipScreen::CreateViews() {
showDeleteCheckbox = true;
} else if (zipFileInfo_.contents == ZipFileContents::SAVE_DATA) {
std::string_view question = iz->T("Install savedata?");
leftColumn->Add(new TextView(question, ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
leftColumn->Add(new TextView(zipFileInfo_.contentName));
std::string_view question = iz->T("Import savedata from ZIP file");
leftColumn->Add(new TextView(question))->SetBig(true);
leftColumn->Add(new TextView(zipFileInfo_.gameTitle + ": " + zipFileInfo_.savedataDir));
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
bool overwrite = !CanExtractWithoutOverwrite(z, savedataDir, 50);
leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), ""));
int columnWidth = 300;
LinearLayout *compareColumns = leftColumn->Add(new LinearLayout(UI::ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
compareColumns->Add(new SavedataView(*screenManager()->getUIContext(), Path(), IdentifiedFileType::PSP_SAVEDATA_DIRECTORY,
zipFileInfo_.gameTitle, zipFileInfo_.savedataTitle, zipFileInfo_.savedataDetails, NiceSizeFormat(zipFileInfo_.totalFileSize), zipFileInfo_.mTime, false, new LinearLayoutParams(columnWidth, WRAP_CONTENT)));
// Check for potential overwrite at destination, and ask the user if it's OK to overwrite.
Path saveDir = GetSysDirectory(DIRECTORY_SAVEDATA);
if (!CanExtractWithoutOverwrite(z, saveDir, 50)) {
leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), "", new AnchorLayoutParams(10, 60, NONE, NONE)));
leftColumn->Add(new SavedataButton(GetSysDirectory(DIRECTORY_SAVEDATA) / zipFileInfo_.savedataDir));
if (overwrite) {
savedataToOverwrite_ = savedataDir / zipFileInfo_.savedataDir;
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savedataToOverwrite_, GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
LinearLayout *rightCompare = new LinearLayout(UI::ORIENT_VERTICAL);
rightCompare->Add(new TextView(iz->T("Existing data")));
compareColumns->Add(rightCompare);
existingSaveView_ = rightCompare->Add(new SavedataView(*screenManager()->getUIContext(), ginfo.get(), IdentifiedFileType::PSP_SAVEDATA_DIRECTORY, false, new LinearLayoutParams(columnWidth, WRAP_CONTENT)));
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
rightCompare->Add(new Button(ga->T("Show In Folder")))->OnClick.Add([=](UI::EventParams &) {
System_ShowFileInFolder(savedataToOverwrite_);
return UI::EVENT_DONE;
});
}
}
installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install")));
@ -166,5 +193,10 @@ void InstallZipScreen::update() {
MainScreen::showHomebrewTab = returnToHomebrew_;
}
}
if (existingSaveView_) {
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savedataToOverwrite_, GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
existingSaveView_->Update(ginfo.get());
}
UIScreen::update();
}

View File

@ -24,6 +24,8 @@
#include "UI/MiscScreens.h"
class SavedataView;
class InstallZipScreen : public UIDialogScreenWithBackground {
public:
InstallZipScreen(const Path &zipPath);
@ -41,6 +43,8 @@ private:
UI::Choice *installChoice_ = nullptr;
UI::Choice *backChoice_ = nullptr;
UI::TextView *doneView_ = nullptr;
SavedataView *existingSaveView_ = nullptr;
Path savedataToOverwrite_;
Path zipPath_;
ZipFileInfo zipFileInfo_{};
bool returnToHomebrew_ = true;

View File

@ -1544,6 +1544,7 @@ UI::EventReturn MainScreen::OnGameHighlight(UI::EventParams &e) {
}
UI::EventReturn MainScreen::OnGameSelectedInstant(UI::EventParams &e) {
// TODO: This is really not necessary here in all cases.
g_Config.Save("MainScreen::OnGameSelectedInstant");
ScreenManager *screen = screenManager();
LaunchFile(screen, Path(e.s));

View File

@ -23,6 +23,7 @@
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Math/curves.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/System/NativeApp.h"
#include "Common/System/Request.h"
#include "Common/Data/Encoding/Utf8.h"
@ -46,85 +47,112 @@
class SavedataButton;
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 "";
}
static std::string TrimString(const std::string &str) {
size_t pos = str.find_last_not_of(" \r\n\t");
if (pos != str.npos) {
return str.substr(0, pos + 1);
}
return str;
}
class SavedataPopupScreen : public PopupScreen {
public:
SavedataPopupScreen(std::string savePath, std::string title) : PopupScreen(TrimString(title)), savePath_(savePath) { }
const char *tag() const override { return "SavedataPopup"; }
void CreatePopupContents(UI::ViewGroup *parent) override {
SavedataView::SavedataView(UIContext &dc, const Path &savePath, IdentifiedFileType type, std::string_view title, std::string_view savedataTitle, std::string_view savedataDetail, std::string_view fileSize, std::string_view mtime, bool showIcon, UI::LayoutParams *layoutParams)
: LinearLayout(UI::ORIENT_VERTICAL, layoutParams)
{
using namespace UI;
UIContext &dc = *screenManager()->getUIContext();
const Style &textStyle = dc.theme->popupStyle;
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO))
return;
ScrollView *contentScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f, UI::Margins(0, 3)));
LinearLayout *content = new LinearLayout(ORIENT_VERTICAL);
parent->Add(contentScroll);
contentScroll->Add(content);
LinearLayout *toprow = new LinearLayout(ORIENT_HORIZONTAL, new LayoutParams(FILL_PARENT, WRAP_CONTENT));
content->Add(toprow);
Add(toprow);
toprow->SetSpacing(0.0);
if (ginfo->fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) {
std::string savedata_detail = ginfo->paramSFO.GetValueString("SAVEDATA_DETAIL");
std::string savedata_title = ginfo->paramSFO.GetValueString("SAVEDATA_TITLE");
if (ginfo->icon.texture) {
toprow->Add(new GameIconView(savePath_, 2.0f, new LinearLayoutParams(Margins(5, 5))));
savedataTitle_ = nullptr;
fileSize_ = nullptr;
mTime_ = nullptr;
detail_ = nullptr;
if (type == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY) {
if (showIcon) {
toprow->Add(new GameIconView(savePath, 2.0f, new LinearLayoutParams(Margins(5, 5))));
}
LinearLayout *topright = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1.0f));
topright->SetSpacing(1.0f);
topright->Add(new TextView(savedata_title, ALIGN_LEFT | FLAG_WRAP_TEXT, false))->SetTextColor(textStyle.fgColor);
topright->Add(new TextView(StringFromFormat("%lld kB", ginfo->gameSizeOnDisk / 1024), 0, true))->SetTextColor(textStyle.fgColor);
topright->Add(new TextView(GetFileDateAsString(savePath_ / "PARAM.SFO"), 0, true))->SetTextColor(textStyle.fgColor);
savedataTitle_ = topright->Add(new TextView(savedataTitle, ALIGN_LEFT | FLAG_WRAP_TEXT, false));
savedataTitle_->SetTextColor(textStyle.fgColor);
fileSize_ = topright->Add(new TextView(fileSize, 0, true));
fileSize_->SetTextColor(textStyle.fgColor);
mTime_ = topright->Add(new TextView(mtime, 0, true));
mTime_->SetTextColor(textStyle.fgColor);
toprow->Add(topright);
content->Add(new Spacer(3.0));
content->Add(new TextView(ReplaceAll(savedata_detail, "\r", ""), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(Margins(10, 0))))->SetTextColor(textStyle.fgColor);
content->Add(new Spacer(3.0));
Add(new Spacer(3.0));
detail_ = Add(new TextView(ReplaceAll(savedataDetail, "\r", ""), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(Margins(10, 0))));
detail_->SetTextColor(textStyle.fgColor);
Add(new Spacer(3.0));
} else {
Path image_path = savePath_.WithReplacedExtension(".ppst", ".jpg");
_dbg_assert_(type == IdentifiedFileType::PPSSPP_SAVESTATE);
Path image_path = savePath.WithReplacedExtension(".ppst", ".jpg");
if (File::Exists(image_path)) {
toprow->Add(new AsyncImageFileView(image_path, IS_KEEP_ASPECT, new LinearLayoutParams(480, 272, Margins(10, 0))));
} else {
auto sa = GetI18NCategory(I18NCat::SAVEDATA);
toprow->Add(new TextView(sa->T("No screenshot"), new LinearLayoutParams(Margins(10, 5))))->SetTextColor(textStyle.fgColor);
}
content->Add(new TextView(GetFileDateAsString(savePath_), 0, true, new LinearLayoutParams(Margins(10, 5))))->SetTextColor(textStyle.fgColor);
mTime_ = Add(new TextView(mtime, 0, true, new LinearLayoutParams(Margins(10, 5))));
mTime_->SetTextColor(textStyle.fgColor);
}
}
void SavedataView::Update(GameInfo *ginfo) {
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO | GameInfoFlags::SIZE)) {
return;
}
_dbg_assert_(savedataTitle_);
if (savedataTitle_) {
savedataTitle_->SetText(ginfo->GetParamSFO().GetValueString("SAVEDATA_TITLE"));
}
if (detail_) {
detail_->SetText(ginfo->GetParamSFO().GetValueString("SAVEDATA_DETAIL"));
}
if (fileSize_) {
fileSize_->SetText(NiceSizeFormat(ginfo->gameSizeOnDisk));
}
if (mTime_) {
mTime_->SetText(ginfo->GetMTime());
}
}
SavedataView::SavedataView(UIContext &dc, GameInfo *ginfo, IdentifiedFileType type, bool showIcon, UI::LayoutParams *layoutParams)
: SavedataView(dc,
ginfo->GetFilePath(),
type,
"",
"",
"",
"",
"",
showIcon,
layoutParams) {}
class SavedataPopupScreen : public PopupScreen {
public:
SavedataPopupScreen(Path savePath, std::string_view title) : PopupScreen(StripSpaces(title)), savePath_(savePath) { }
const char *tag() const override { return "SavedataPopup"; }
void update() override {
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) {
// Hm, this is no good. But hopefully the previous screen loaded it.
return;
}
if (savedataView_) {
savedataView_->Update(ginfo.get());
}
}
void CreatePopupContents(UI::ViewGroup *parent) override {
using namespace UI;
UIContext &dc = *screenManager()->getUIContext();
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savePath_, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) {
// This is OK, handled in Update. Though most likely, the previous screen loaded it.
}
ScrollView *contentScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f, UI::Margins(0, 3)));
parent->Add(contentScroll);
// TODO: If the game info wasn't already loaded, we'll get a bogus fileType here.
savedataView_ = contentScroll->Add(new SavedataView(dc, ginfo.get(), ginfo->fileType, true));
auto di = GetI18NCategory(I18NCat::DIALOG);
LinearLayout *buttons = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
@ -141,6 +169,7 @@ protected:
private:
UI::EventReturn OnDeleteButtonClick(UI::EventParams &e);
SavedataView *savedataView_ = nullptr;
Path savePath_;
};
@ -244,14 +273,15 @@ bool SavedataButton::UpdateText() {
}
void SavedataButton::UpdateText(const std::shared_ptr<GameInfo> &ginfo) {
_dbg_assert_(ginfo->Ready(GameInfoFlags::PARAM_SFO));
const std::string currentTitle = ginfo->GetTitle();
if (!currentTitle.empty()) {
title_ = CleanSaveString(currentTitle);
}
if (subtitle_.empty() && ginfo->gameSizeOnDisk > 0) {
std::string date = GetFileDateAsString(ginfo->GetFilePath() / "PARAM.SFO");
std::string savedata_title = ginfo->paramSFO.GetValueString("SAVEDATA_TITLE");
subtitle_ = CleanSaveString(savedata_title) + StringFromFormat(" (%lld kB, %s)", ginfo->gameSizeOnDisk / 1024, date.c_str());
std::string date = ginfo->GetMTime();
std::string savedata_title = ginfo->GetParamSFO().GetValueString("SAVEDATA_TITLE");
subtitle_ = CleanSaveString(savedata_title) + " (" + NiceSizeFormat(ginfo->gameSizeOnDisk) + ", " + date.c_str() + ")";
}
}
@ -648,7 +678,7 @@ UI::EventReturn SavedataScreen::OnSavedataButtonClick(UI::EventParams &e) {
if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) {
return UI::EVENT_DONE;
}
SavedataPopupScreen *popupScreen = new SavedataPopupScreen(e.s, ginfo->GetTitle());
SavedataPopupScreen *popupScreen = new SavedataPopupScreen(Path(e.s), ginfo->GetTitle());
if (e.v) {
popupScreen->SetPopupOrigin(e.v);
}

View File

@ -145,3 +145,17 @@ private:
bool hasDateSeconds_ = false;
};
// View used for the detailed popup, and also in the import savedata comparison.
// It doesn't do its own data loading for that reason.
class SavedataView : public UI::LinearLayout {
public:
SavedataView(UIContext &dc, GameInfo *ginfo, IdentifiedFileType type, bool showIcon, UI::LayoutParams *layoutParams = nullptr);
SavedataView(UIContext &dc, const Path &savePath, IdentifiedFileType type, std::string_view title, std::string_view savedataTitle, std::string_view savedataDetail, std::string_view fileSize, std::string_view mtime, bool showIcon, UI::LayoutParams *layoutParams = nullptr);
void Update(GameInfo *ginfo);
private:
UI::TextView *savedataTitle_ = nullptr;
UI::TextView *detail_ = nullptr;
UI::TextView *mTime_ = nullptr;
UI::TextView *fileSize_ = nullptr;
};