From 3a047369fd90433eaf4d2ad848faf12a53e2a622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 5 Sep 2024 17:12:10 +0200 Subject: [PATCH] Add ability to install savedata from GameFAQs-style ZIP files --- Core/ELF/ParamSFO.cpp | 14 ++-- Core/ELF/ParamSFO.h | 1 + Core/Util/GameManager.cpp | 156 +++++++++++++++++++++++++++++--------- Core/Util/GameManager.h | 15 +++- UI/InstallZipScreen.cpp | 94 ++++++++++++++--------- UI/InstallZipScreen.h | 1 - UI/SavedataScreen.cpp | 42 +--------- UI/SavedataScreen.h | 40 ++++++++++ 8 files changed, 243 insertions(+), 120 deletions(-) diff --git a/Core/ELF/ParamSFO.cpp b/Core/ELF/ParamSFO.cpp index fb41536273..e9e96be1a6 100644 --- a/Core/ELF/ParamSFO.cpp +++ b/Core/ELF/ParamSFO.cpp @@ -62,24 +62,28 @@ void ParamSFOData::SetValue(const std::string &key, const u8 *value, unsigned in int ParamSFOData::GetValueInt(const std::string &key) const { std::map::const_iterator it = values.find(key); - if(it == values.end() || it->second.type != VT_INT) + if (it == values.end() || it->second.type != VT_INT) return 0; return it->second.i_value; } std::string ParamSFOData::GetValueString(const std::string &key) const { std::map::const_iterator it = values.find(key); - if(it == values.end() || (it->second.type != VT_UTF8)) + if (it == values.end() || (it->second.type != VT_UTF8)) return ""; return it->second.s_value; } +bool ParamSFOData::HasKey(const std::string &key) const { + return values.find(key) != values.end(); +} + const u8 *ParamSFOData::GetValueData(const std::string &key, unsigned int *size) const { std::map::const_iterator it = values.find(key); - if(it == values.end() || (it->second.type != VT_UTF8_SPE)) + if (it == values.end() || (it->second.type != VT_UTF8_SPE)) { return 0; - if(size) - { + } + if (size) { *size = it->second.u_size; } return it->second.u_value; diff --git a/Core/ELF/ParamSFO.h b/Core/ELF/ParamSFO.h index 8531ded275..cb76cde4a6 100644 --- a/Core/ELF/ParamSFO.h +++ b/Core/ELF/ParamSFO.h @@ -35,6 +35,7 @@ public: int GetValueInt(const std::string &key) const; std::string GetValueString(const std::string &key) const; + bool HasKey(const std::string &key) const; const u8 *GetValueData(const std::string &key, unsigned int *size) const; std::vector GetKeys() const; diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 745172c28c..e77ba91442 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -51,7 +51,7 @@ GameManager g_GameManager; -static struct zip *ZipOpenPath(Path fileName) { +struct zip *ZipOpenPath(Path fileName) { int error = 0; // Need to special case for content URI here, similar to OpenCFile. struct zip *z; @@ -71,6 +71,10 @@ static struct zip *ZipOpenPath(Path fileName) { return z; } +void ZipClose(struct zip *z) { + zip_close(z); +} + GameManager::GameManager() { } @@ -190,17 +194,23 @@ void GameManager::Update() { } } -static void countSlashes(const std::string &fileName, int *slashLocation, int *slashCount) { - *slashCount = 0; +static int countSlashes(const std::string &fileName, int *slashLocation) { + int slashCount = 0; int lastSlashLocation = -1; - *slashLocation = -1; + if (slashLocation) { + *slashLocation = -1; + } for (size_t i = 0; i < fileName.size(); i++) { if (fileName[i] == '/') { - (*slashCount)++; - *slashLocation = lastSlashLocation; - lastSlashLocation = (int)i; + slashCount++; + if (slashLocation) { + *slashLocation = lastSlashLocation; + lastSlashLocation = (int)i; + } } } + + return slashCount; } bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info) { @@ -220,6 +230,23 @@ inline char asciitolower(char in) { return in; } +bool CanExtractWithoutOverwrite(struct zip *z, const Path &destination, int maxOkFiles) { + int numFiles = zip_get_num_files(z); + if (numFiles > maxOkFiles && maxOkFiles >= 0) { + // Ignore the check, just assume we can't. + return false; + } + for (int i = 0; i < numFiles; i++) { + const char *fn = zip_get_name(z, i, 0); + Path p = destination / fn; + if (File::Exists(p)) { + INFO_LOG(Log::HLE, "Extract zip check: %s exists, can't extract without overwrite", p.ToVisualString().c_str()); + return false; + } + } + return true; +} + void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { int numFiles = zip_get_num_files(z); @@ -233,7 +260,13 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { int isoFileIndex = -1; int stripCharsTexturePack = -1; int textureIniIndex = -1; + int filesInRoot = 0; + int directoriesInRoot = 0; + bool hasParamSFO = false; + bool hasIcon0PNG = false; + // 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); std::string zippedName = fn; @@ -243,20 +276,21 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { if (startsWith(zippedName, "__macosx/")) { continue; } + if (endsWith(zippedName, "/")) { + // 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) { - int slashCount = 0; - int slashLocation = -1; - countSlashes(zippedName, &slashLocation, &slashCount); - if (slashCount >= 1 && (!isPSPMemstickGame || slashLocation < stripChars + 1)) { - stripChars = slashLocation + 1; + if (slashCount >= 1 && (!isPSPMemstickGame || prevSlashLocation < stripChars + 1)) { + stripChars = prevSlashLocation + 1; isPSPMemstickGame = true; } else { INFO_LOG(Log::HLE, "Wrong number of slashes (%i) in '%s'", slashCount, fn); } + // TODO: Extract icon and param.sfo from the pbp to be able to display it on the install screen. } else if (endsWith(zippedName, ".iso") || endsWith(zippedName, ".cso") || endsWith(zippedName, ".chd")) { - int slashCount = 0; - int slashLocation = -1; - countSlashes(zippedName, &slashLocation, &slashCount); if (slashCount <= 1) { // We only do this if the ISO file is in the root or one level down. isZippedISO = true; @@ -265,6 +299,7 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { INFO_LOG(Log::HLE, "More than one ISO file found in zip. Ignoring additional ones."); } else { isoFileIndex = i; + info->contentName = zippedName; } } } else if (zippedName.find("textures.ini") != std::string::npos) { @@ -274,6 +309,26 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { isTexturePack = true; textureIniIndex = i; } + } else if (endsWith(zippedName, "/param.sfo")) { + // Get the game name so we can display it. + std::string paramSFOContents; + if (ZipExtractFileToMemory(z, i, ¶mSFOContents)) { + 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->savedataDir = sfo.GetValueString("SAVEDATA_DIRECTORY"); // should also be parsable from the path. + hasParamSFO = true; + } + } + } + } else if (endsWith(zippedName, "/icon0.png")) { + hasIcon0PNG = true; + } + if (slashCount == 0) { + filesInRoot++; } } @@ -283,7 +338,7 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { info->textureIniIndex = textureIniIndex; info->ignoreMetaFiles = false; - // If a ZIP is detected as both, let's let the memstick game interpretation prevail. + // Priority ordering for detecting the various kinds of zip file content.s if (isPSPMemstickGame) { info->contents = ZipFileContents::PSP_GAME_DIR; } else if (isZippedISO) { @@ -292,6 +347,9 @@ void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { info->stripChars = stripCharsTexturePack; info->ignoreMetaFiles = true; info->contents = ZipFileContents::TEXTURE_PACK; + } else if (stripChars == 0 && filesInRoot == 0 && hasParamSFO && hasIcon0PNG) { + // As downloaded from GameFAQs, for example. + info->contents = ZipFileContents::SAVE_DATA; } else { info->contents = ZipFileContents::UNKNOWN; } @@ -331,8 +389,6 @@ void GameManager::InstallZipContents(ZipFileTask task) { return; } - Path pspGame = GetSysDirectory(DIRECTORY_GAME); - Path dest = pspGame; int error = 0; struct zip *z = ZipOpenPath(task.fileName); @@ -349,48 +405,61 @@ void GameManager::InstallZipContents(ZipFileTask task) { // The normal case zipInfo = task.zipFileInfo.value(); } else { - DetectZipFileContents(task.fileName, &zipInfo); + DetectZipFileContents(z, &zipInfo); } switch (zipInfo.contents) { case ZipFileContents::PSP_GAME_DIR: + { + Path pspGame = GetSysDirectory(DIRECTORY_GAME); INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), pspGame.c_str()); - // InstallMemstickGame contains code to close (and delete) z. - success = InstallMemstickGame(z, task.fileName, pspGame, zipInfo, false, task.deleteAfter); + // InstallZipContents contains code to close (and delete) z. + success = ExtractZipContents(z, pspGame, zipInfo, false); break; + } case ZipFileContents::ISO_FILE: INFO_LOG(Log::HLE, "Installing '%s' into its containing directory", task.fileName.c_str()); // InstallZippedISO contains code to close z. success = InstallZippedISO(z, zipInfo.isoFileIndex, task.fileName, task.deleteAfter); break; case ZipFileContents::TEXTURE_PACK: + { // InstallMemstickGame contains code to close z, and works for textures too. + Path dest; if (DetectTexturePackDest(z, zipInfo.textureIniIndex, dest)) { INFO_LOG(Log::HLE, "Installing texture pack '%s' into '%s'", task.fileName.c_str(), dest.c_str()); File::CreateFullPath(dest); // Install as a zip file if textures.ini is in the root. Performs better on Android. if (zipInfo.stripChars == 0) { - success = InstallMemstickZip(z, task.fileName, dest / "textures.zip", zipInfo, task.deleteAfter); + success = InstallMemstickZip(z, task.fileName, dest / "textures.zip", zipInfo); } else { // TODO: Can probably remove this, as we now put .nomedia in /TEXTURES directly. File::CreateEmptyFile(dest / ".nomedia"); - success = InstallMemstickGame(z, task.fileName, dest, zipInfo, true, task.deleteAfter); + success = ExtractZipContents(z, dest, zipInfo, true); } } else { zip_close(z); z = nullptr; } break; + } + case ZipFileContents::SAVE_DATA: + { + Path pspSaveData = GetSysDirectory(DIRECTORY_SAVEDATA); + success = ExtractZipContents(z, pspSaveData, zipInfo, false); + break; + } default: ERROR_LOG(Log::HLE, "File not a PSP game, no EBOOT.PBP found."); SetInstallError(sy->T("Not a PSP game")); zip_close(z); z = nullptr; - if (task.deleteAfter) { - File::Delete(task.fileName); - } break; } + + if (task.deleteAfter && success) { + File::Delete(task.fileName); + } g_OSD.RemoveProgressBar("install", success, 0.5f); } @@ -515,6 +584,29 @@ std::string GameManager::GetISOGameID(FileLoader *loader) const { return sfo.GetValueString("DISC_ID"); } +bool ZipExtractFileToMemory(struct zip *z, int fileIndex, std::string *data) { + struct zip_stat zstat; + zip_stat_index(z, fileIndex, 0, &zstat); + if (zstat.size == 0) { + data->clear(); + return true; + } + + size_t readSize = zstat.size; + data->resize(readSize); + + zip_file *zf = zip_fopen_index(z, fileIndex, 0); + zip_int64_t retval = zip_fread(zf, data->data(), readSize); + zip_fclose(zf); + + if (retval < 0 || retval < readSize) { + ERROR_LOG(Log::HLE, "Failed to read %d bytes from zip (%d) - archive corrupt?", (int)readSize, (int)retval); + return false; + } else { + return true; + } +} + bool GameManager::ExtractFile(struct zip *z, int file_index, const Path &outFilename, size_t *bytesCopied, size_t allBytes) { struct zip_stat zstat; zip_stat_index(z, file_index, 0, &zstat); @@ -572,7 +664,8 @@ bool GameManager::ExtractFile(struct zip *z, int file_index, const Path &outFile } } -bool GameManager::InstallMemstickGame(struct zip *z, const Path &zipfile, const Path &dest, const ZipFileInfo &info, bool allowRoot, bool deleteAfter) { +// Doesn't care what it is, just extracts the whole ZIP to the requested location. +bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot) { size_t allBytes = 0; size_t bytesCopied = 0; @@ -657,10 +750,6 @@ bool GameManager::InstallMemstickGame(struct zip *z, const Path &zipfile, const zip_close(z); z = nullptr; installProgress_ = 1.0f; - if (deleteAfter) { - INFO_LOG(Log::HLE, "Deleting '%s' after extraction", zipfile.c_str()); - File::Delete(zipfile); - } InstallDone(); ResetInstallError(); g_OSD.RemoveProgressBar("install", true, 0.5f); @@ -681,7 +770,7 @@ bail: return false; } -bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const Path &dest, const ZipFileInfo &info, bool deleteAfter) { +bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const Path &dest, const ZipFileInfo &info) { size_t allBytes = 0; size_t bytesCopied = 0; @@ -731,9 +820,6 @@ bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const P } installProgress_ = 1.0f; - if (deleteAfter) { - File::Delete(zipfile); - } InstallDone(); ResetInstallError(); g_OSD.RemoveProgressBar("install", true, 0.5f); diff --git a/Core/Util/GameManager.h b/Core/Util/GameManager.h index ba82073fd7..fccca0af13 100644 --- a/Core/Util/GameManager.h +++ b/Core/Util/GameManager.h @@ -40,15 +40,18 @@ enum class ZipFileContents { PSP_GAME_DIR, ISO_FILE, TEXTURE_PACK, + SAVE_DATA, }; struct ZipFileInfo { ZipFileContents contents; int numFiles; - int stripChars; // for PSP game + int stripChars; // for PSP game - how much to strip from the path. int isoFileIndex; // for ISO int textureIniIndex; // for textures bool ignoreMetaFiles; + std::string contentName; + std::string savedataDir; }; struct ZipFileTask { @@ -106,8 +109,8 @@ public: private: void InstallZipContents(ZipFileTask task); - bool InstallMemstickGame(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info, bool allowRoot, bool deleteAfter); - bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info, bool deleteAfter); + bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot); + bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info); bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter); bool InstallRawISO(const Path &zipFile, const std::string &originalName, bool deleteAfter); void UninstallGame(const std::string &name); @@ -135,5 +138,11 @@ private: extern GameManager g_GameManager; +struct zip *ZipOpenPath(Path fileName); +void ZipClose(zip *z); + void DetectZipFileContents(struct zip *z, ZipFileInfo *info); bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info); + +bool ZipExtractFileToMemory(struct zip *z, int fileIndex, std::string *data); +bool CanExtractWithoutOverwrite(struct zip *z, const Path &destination, int maxOkFiles); diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index 0bc20dccb0..8ac9db4526 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -21,9 +21,12 @@ #include "Common/StringUtils.h" #include "Common/Data/Text/I18n.h" +#include "Core/System.h" #include "Core/Util/GameManager.h" #include "UI/InstallZipScreen.h" #include "UI/MainScreen.h" +#include "UI/OnScreenDisplay.h" +#include "UI/SavedataScreen.h" InstallZipScreen::InstallZipScreen(const Path &zipPath) : zipPath_(zipPath) { g_GameManager.ResetInstallError(); @@ -37,12 +40,13 @@ void InstallZipScreen::CreateViews() { auto di = GetI18NCategory(I18NCat::DIALOG); auto iz = GetI18NCategory(I18NCat::INSTALLZIP); + auto er = GetI18NCategory(I18NCat::ERRORS); Margins actionMenuMargins(0, 100, 15, 0); root_ = new LinearLayout(ORIENT_HORIZONTAL); - ViewGroup *leftColumn = new AnchorLayout(new LinearLayoutParams(1.0f)); + ViewGroup *leftColumn = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(1.0f, Margins(12))); ViewGroup *rightColumnItems = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); root_->Add(leftColumn); root_->Add(rightColumnItems); @@ -50,46 +54,71 @@ void InstallZipScreen::CreateViews() { std::string shortFilename = zipPath_.GetFilename(); // TODO: Do in the background? - DetectZipFileContents(zipPath_, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents. + struct zip *z = ZipOpenPath(zipPath_); - if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) { - std::string_view question = iz->T("Install game from ZIP file?"); - leftColumn->Add(new TextView(question, ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); - leftColumn->Add(new TextView(shortFilename, ALIGN_LEFT, false, new AnchorLayoutParams(10, 60, NONE, NONE))); + bool showDeleteCheckbox = false; + returnToHomebrew_ = false; + installChoice_ = nullptr; + doneView_ = nullptr; + installChoice_ = nullptr; - doneView_ = leftColumn->Add(new TextView("", new AnchorLayoutParams(10, 120, NONE, NONE))); - progressBar_ = leftColumn->Add(new ProgressBar(new AnchorLayoutParams(10, 200, 200, NONE))); + 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) { + std::string_view question = iz->T("Install game from ZIP file?"); - installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); - installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); - backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); - rightColumnItems->Add(new CheckBox(&deleteZipFile_, iz->T("Delete ZIP file"))); + leftColumn->Add(new TextView(question)); + leftColumn->Add(new TextView(shortFilename)); - returnToHomebrew_ = true; - } else if (zipFileInfo_.contents == ZipFileContents::TEXTURE_PACK) { - std::string_view question = iz->T("Install textures from ZIP file?"); - leftColumn->Add(new TextView(question, ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); - leftColumn->Add(new TextView(shortFilename, ALIGN_LEFT, false, new AnchorLayoutParams(10, 60, NONE, NONE))); + doneView_ = leftColumn->Add(new TextView("")); - doneView_ = leftColumn->Add(new TextView("", new AnchorLayoutParams(10, 120, NONE, NONE))); - progressBar_ = leftColumn->Add(new ProgressBar(new AnchorLayoutParams(10, 200, 200, NONE))); + installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); + installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); + returnToHomebrew_ = true; + showDeleteCheckbox = true; + } else if (zipFileInfo_.contents == ZipFileContents::TEXTURE_PACK) { + std::string_view question = iz->T("Install textures from ZIP file?"); + leftColumn->Add(new TextView(question)); + leftColumn->Add(new TextView(shortFilename)); - installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); - installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); - backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); - rightColumnItems->Add(new CheckBox(&deleteZipFile_, iz->T("Delete ZIP file"))); + doneView_ = leftColumn->Add(new TextView("")); - returnToHomebrew_ = false; + installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); + installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); + backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); + + 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)); + + // 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)); + } + + installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install"))); + installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall); + + doneView_ = leftColumn->Add(new TextView("")); + showDeleteCheckbox = true; + } else { + leftColumn->Add(new TextView(iz->T("Zip file does not contain PSP software"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); + } } else { - leftColumn->Add(new TextView(iz->T("Zip file does not contain PSP software"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); - doneView_ = nullptr; - progressBar_ = nullptr; - installChoice_ = nullptr; - backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); + leftColumn->Add(new TextView(er->T("Error reading file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE))); } // OK so that EmuScreen will handle it right. + backChoice_ = rightColumnItems->Add(new Choice(di->T("Back"))); backChoice_->OnClick.Handle(this, &UIScreen::OnOK); + + if (showDeleteCheckbox) { + rightColumnItems->Add(new CheckBox(&deleteZipFile_, iz->T("Delete ZIP file"))); + } } bool InstallZipScreen::key(const KeyInput &key) { @@ -120,17 +149,10 @@ void InstallZipScreen::update() { using namespace UI; if (g_GameManager.GetState() != GameManagerState::IDLE) { - if (progressBar_) { - progressBar_->SetVisibility(V_VISIBLE); - progressBar_->SetProgress(g_GameManager.GetCurrentInstallProgressPercentage()); - } if (backChoice_) { backChoice_->SetEnabled(false); } } else { - if (progressBar_) { - progressBar_->SetVisibility(V_GONE); - } if (backChoice_) { backChoice_->SetEnabled(true); } diff --git a/UI/InstallZipScreen.h b/UI/InstallZipScreen.h index a2e5d383de..17bea53dc9 100644 --- a/UI/InstallZipScreen.h +++ b/UI/InstallZipScreen.h @@ -40,7 +40,6 @@ private: UI::Choice *installChoice_ = nullptr; UI::Choice *backChoice_ = nullptr; - UI::ProgressBar *progressBar_ = nullptr; UI::TextView *doneView_ = nullptr; Path zipPath_; ZipFileInfo zipFileInfo_{}; diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index e580ba89dc..79b034110b 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -192,45 +192,6 @@ void SortedLinearLayout::Update() { UI::LinearLayout::Update(); } -class SavedataButton : public UI::Clickable { -public: - SavedataButton(const Path &gamePath, UI::LayoutParams *layoutParams = 0) - : UI::Clickable(layoutParams), savePath_(gamePath) { - SetTag(gamePath.ToString()); - } - - void Draw(UIContext &dc) override; - bool UpdateText(); - std::string DescribeText() const override; - void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { - w = 500; - h = 74; - } - - const Path &GamePath() const { return savePath_; } - - uint64_t GetTotalSize() const { - return totalSize_; - } - int64_t GetDateSeconds() const { - return dateSeconds_; - } - - void UpdateTotalSize(); - void UpdateDateSeconds(); - -private: - void UpdateText(const std::shared_ptr &ginfo); - - Path savePath_; - std::string title_; - std::string subtitle_; - uint64_t totalSize_ = 0; - int64_t dateSeconds_ = 0; - bool hasTotalSize_ = false; - bool hasDateSeconds_ = false; -}; - void SavedataButton::UpdateTotalSize() { if (hasTotalSize_) return; @@ -288,8 +249,9 @@ void SavedataButton::UpdateText(const std::shared_ptr &ginfo) { 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)", ginfo->gameSizeOnDisk / 1024); + subtitle_ = CleanSaveString(savedata_title) + StringFromFormat(" (%lld kB, %s)", ginfo->gameSizeOnDisk / 1024, date.c_str()); } } diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index b1dc6565e4..69544d9bf4 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -105,3 +105,43 @@ private: int textureWidth_ = 0; int textureHeight_ = 0; }; + +class SavedataButton : public UI::Clickable { +public: + SavedataButton(const Path &gamePath, UI::LayoutParams *layoutParams = 0) + : UI::Clickable(layoutParams), savePath_(gamePath) { + SetTag(gamePath.ToString()); + } + + void Draw(UIContext &dc) override; + bool UpdateText(); + std::string DescribeText() const override; + void GetContentDimensions(const UIContext &dc, float &w, float &h) const override { + w = 500; + h = 74; + } + + const Path &GamePath() const { return savePath_; } + + uint64_t GetTotalSize() const { + return totalSize_; + } + int64_t GetDateSeconds() const { + return dateSeconds_; + } + + void UpdateTotalSize(); + void UpdateDateSeconds(); + +private: + void UpdateText(const std::shared_ptr &ginfo); + + Path savePath_; + std::string title_; + std::string subtitle_; + uint64_t totalSize_ = 0; + int64_t dateSeconds_ = 0; + bool hasTotalSize_ = false; + bool hasDateSeconds_ = false; +}; +