Savedata manager: Export a game's saves from the popup of one of them

The page-wide button depended on the game the screen was opened for, which
the screen doesn't show. The popup knows the game from the save itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-11 12:14:46 +01:00
1 parent 9be62bb7d5
commit 95e7f2d686
2 files changed
+29 -27

No files matched your search

+28 -23
View File
@@ -67,6 +67,19 @@ static void AddFolderToZip(const Path &folder, const std::string &nameInZip, std
}
}
// The save folders of a game next to a save of it, without installed data.
static std::vector<Path> FindGameSaves(const Path &saveDir, std::string_view gameID) {
std::vector<Path> saves;
std::vector<File::FileInfo> dirs;
File::GetFilesInDir(saveDir, &dirs);
for (const File::FileInfo &dir : dirs) {
if (dir.isDirectory && startsWith(dir.name, gameID) && File::Exists(dir.fullName / "PARAM.SFO") && !IsInstalledGameData(dir.fullName)) {
saves.push_back(dir.fullName);
}
}
return saves;
}
static void ExportAsZip(std::string_view zipName, const std::vector<ShareZipEntry> &entries) {
if (!ShareFilesAsZip(zipName, entries)) {
auto sa = GetI18NCategory(I18NCat::SAVEDATA);
@@ -207,6 +220,21 @@ void SavedataPopupScreen::CreatePopupContents(UI::ViewGroup *parent) {
}
}
if (canExport && !isState && ginfo->Ready(GameInfoFlags::PARAM_SFO) && ginfo->id.size() >= 5) {
// The game ID starts the folder names (the same rule as GetSaveDataDirectories()).
std::vector<Path> gameSaves = FindGameSaves(savePath_.NavigateUp(), ginfo->id);
if (gameSaves.size() > 1) {
std::string zipName = ginfo->GetTitle() + " (" + ginfo->id + ")";
content->Add(new Choice(sa->T("Export all saves for this game"), ImageID("I_SHARE"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)))->OnClick.Add([gameSaves, zipName](UI::EventParams &e) {
std::vector<ShareZipEntry> entries;
for (const Path &dir : gameSaves) {
AddFolderToZip(dir, dir.GetFilename(), &entries);
}
ExportAsZip(zipName, entries);
});
}
}
LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT));
buttonRow->SetSpacing(12);
@@ -762,8 +790,6 @@ void SavedataScreen::CreateSavestateTab(UI::ViewGroup *savestate) {
bool SavedataScreen::LookUpGame() {
gameID_.clear();
gameTitle_.clear();
gameSaveDirs_.clear();
if (gamePath_.empty()) {
return true;
}
@@ -778,13 +804,6 @@ bool SavedataScreen::LookUpGame() {
return true;
}
gameID_ = ginfo->id;
gameTitle_ = ginfo->GetTitle();
for (const Path &dir : ginfo->GetSaveDataDirectories()) {
// The folders the browser lists, without installed data.
if (File::Exists(dir / "PARAM.SFO") && !IsInstalledGameData(dir)) {
gameSaveDirs_.push_back(dir);
}
}
return true;
}
@@ -814,12 +833,6 @@ void SavedataScreen::CreateTabs() {
void SavedataScreen::CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) {
using namespace UI;
if (!gameSaveDirs_.empty() && CanShareFiles()) {
auto sa = GetI18NCategory(I18NCat::SAVEDATA);
Choice *exportButton = verticalLayout->Add(new Choice(sa->T("Export all saves for this game"), ImageID("I_SHARE"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0, 0, margins, margins))));
exportButton->OnClick.Handle(this, &SavedataScreen::OnExportGameSaves);
exportButton->SetRoundCorners();
}
if (System_GetPropertyBool(SYSPROP_HAS_TEXT_INPUT_DIALOG)) {
auto di = GetI18NCategory(I18NCat::DIALOG);
Choice *searchButton = verticalLayout->Add(new Choice(di->T("Search"), ImageID("I_SEARCH"), new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 0.0f, Margins(0, 0, margins, margins))));
@@ -837,14 +850,6 @@ void SavedataScreen::OnSearch(UI::EventParams &e) {
}
}
void SavedataScreen::OnExportGameSaves(UI::EventParams &e) {
std::vector<ShareZipEntry> entries;
for (const Path &dir : gameSaveDirs_) {
AddFolderToZip(dir, dir.GetFilename(), &entries);
}
ExportAsZip(gameTitle_ + " (" + gameID_ + ")", entries);
}
void SavedataScreen::OnSavedataButtonClick(UI::EventParams &e) {
// the game path: e.s;
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), Path(e.s), GameInfoFlags::PARAM_SFO);
+1 -4
View File
@@ -118,16 +118,13 @@ protected:
private:
void OnSavedataButtonClick(UI::EventParams &e);
void OnSearch(UI::EventParams &e);
void OnExportGameSaves(UI::EventParams &e);
void CreateSavedataTab(UI::ViewGroup *savedata);
void CreateSavestateTab(UI::ViewGroup *savestate);
// The ID and title of the game the screen was opened for, once its PARAM.SFO is loaded.
// The ID of the game the screen was opened for (its saves are highlighted), once its PARAM.SFO is loaded.
bool LookUpGame();
std::string gameID_;
std::string gameTitle_;
std::vector<Path> gameSaveDirs_; // The game's saves, without installed data.
bool gameLookupPending_ = false;
SavedataSortOption sortOption_ = SavedataSortOption::FILENAME;
SavedataBrowser *dataBrowser_ = nullptr;