From ba94ea9da8b90c20912ae9a2130dbde739b8e8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 09:29:29 +0100 Subject: [PATCH 01/10] Savedata manager: Export saves as zip files - Fixes #23031 The savedata popup gets an Export button, which zips the save folder (or a savestate and its screenshot) and hands it to ShareFilesAsZip: the share sheet on Android and iOS, a Save As dialog on Windows. The zips use the layouts the zip import already recognizes, so they import back. Opened for a game, the manager outlines that game's saves and states, scrolls to them, and offers to export all of the game's saves in one zip. The game screen gets a button that opens it that way. Installed data is never exported. It looks like a save (same PARAM.SFO fields), but games write the installed files with sceIo rather than the savedata utility, so they're missing from SAVEDATA_FILE_LIST. IsInstalledGameData() counts a folder as installed data when the files outside that list add up to 10 MB or more. The game screen's split between save and install sizes now uses it too, instead of treating any folder over 10 MB as installed data. Co-Authored-By: Claude Opus 5.5 --- Common/UI/View.h | 1 + UI/GameInfoCache.cpp | 79 ++++++++++++-------- UI/GameInfoCache.h | 4 ++ UI/GameScreen.cpp | 10 +++ UI/SavedataScreen.cpp | 164 ++++++++++++++++++++++++++++++++++++++++-- UI/SavedataScreen.h | 17 ++++- 6 files changed, 238 insertions(+), 37 deletions(-) diff --git a/Common/UI/View.h b/Common/UI/View.h index 6f5a8cbfdd..a10333221f 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -576,6 +576,7 @@ public: // Outlines it, to point it out without the keyboard focus (which would turn on keyboard navigation). void SetHighlighted(bool highlighted) { highlighted_ = highlighted; } + bool IsHighlighted() const { return highlighted_; } Event OnClick; diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index c849745382..1780051aca 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -306,22 +306,11 @@ u64 GameInfo::GetGameSavedataSizeInBytes() const { if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) { return 0; } - std::vector saveDataDir = GetSaveDataDirectories(); - u64 totalSize = 0; - u64 filesSizeInDir = 0; - for (const auto& dir : saveDataDir) { - std::vector fileInfo; - File::GetFilesInDir(dir, &fileInfo); - for (auto const &file : fileInfo) { - if (!file.isDirectory) - filesSizeInDir += file.size; + for (const Path &dir : GetSaveDataDirectories()) { + if (!IsInstalledGameData(dir)) { + totalSize += File::ComputeRecursiveDirectorySize(dir); } - if (filesSizeInDir < 0xA00000) { - // HACK: Generally the savedata size in a dir shouldn't be more than 10MB. - totalSize += filesSizeInDir; - } - filesSizeInDir = 0; } return totalSize; } @@ -330,29 +319,57 @@ u64 GameInfo::GetInstallDataSizeInBytes() const { if (fileType == IdentifiedFileType::PSP_SAVEDATA_DIRECTORY || fileType == IdentifiedFileType::PPSSPP_SAVESTATE) { return 0; } - std::vector saveDataDir = GetSaveDataDirectories(); - u64 totalSize = 0; - u64 filesSizeInDir = 0; - for (const auto& dir : saveDataDir) { - std::vector fileInfo; - File::GetFilesInDir(dir, &fileInfo); - for (auto const &file : fileInfo) { - // TODO: Might want to recurse here? Don't know games that use directories - // for install-data though. - if (!file.isDirectory) - filesSizeInDir += file.size; + for (const Path &dir : GetSaveDataDirectories()) { + if (IsInstalledGameData(dir)) { + totalSize += File::ComputeRecursiveDirectorySize(dir); } - if (filesSizeInDir >= 0xA00000) { - // HACK: Generally the savedata size in a dir shouldn't be more than 10MB. - // This is probably GameInstall data. - totalSize += filesSizeInDir; - } - filesSizeInDir = 0; } return totalSize; } +bool IsInstalledGameData(const Path &saveDir) { + // Saves can have files outside the list too (PS1 memory cards, homebrew settings), but only a few MB. + // Installs are hundreds. + constexpr u64 MIN_INSTALLED_SIZE = 10 * 1024 * 1024; + // Written by the savedata utility, but never in the list. + static const char *const metaFiles[] = { "PARAM.SFO", "ICON0.PNG", "ICON1.PMF", "ICON1.PNG", "PIC1.PNG", "SND0.AT3" }; + + std::vector listed; + std::string sfoData; + ParamSFOData sfo; + if (File::ReadBinaryFileToString(saveDir / "PARAM.SFO", &sfoData) && sfo.ReadSFO((const u8 *)sfoData.data(), sfoData.size())) { + unsigned int size = 0; + const u8 *list = sfo.GetValueData("SAVEDATA_FILE_LIST", &size); + // Entries of 32 bytes (SaveSFOFileListEntry), starting with a 13-byte name. + constexpr unsigned int ENTRY_SIZE = 32; + constexpr unsigned int NAME_SIZE = 13; + for (unsigned int offset = 0; list && offset + ENTRY_SIZE <= size; offset += ENTRY_SIZE) { + const char *name = (const char *)list + offset; + const std::string_view entryName(name, strnlen(name, NAME_SIZE)); + if (!entryName.empty()) { + listed.emplace_back(entryName); + } + } + } + + std::vector files; + File::GetFilesInDir(saveDir, &files); + u64 unlistedSize = 0; + for (const File::FileInfo &file : files) { + if (file.isDirectory) { + unlistedSize += File::ComputeRecursiveDirectorySize(file.fullName); + continue; + } + const auto matches = [&file](std::string_view name) { return equalsNoCase(file.name, name); }; + if (std::any_of(std::begin(metaFiles), std::end(metaFiles), matches) || std::any_of(listed.begin(), listed.end(), matches)) { + continue; + } + unlistedSize += file.size; + } + return unlistedSize >= MIN_INSTALLED_SIZE; +} + bool GameInfo::CreateLoader() { if (!fileLoader) { std::lock_guard guard(loaderLock); diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 9b97f5bb8e..9550731236 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -253,3 +253,7 @@ private: // This one can be global, no good reason not to. extern GameInfoCache *g_gameInfoCache; + +// Whether a folder in PSP/SAVEDATA holds data a game installed (or downloaded) there, rather than a save. +// Games write those files with sceIo rather than the savedata utility, so PARAM.SFO's file list leaves them out. +bool IsInstalledGameData(const Path &saveDir); diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index c3015a6ca9..224a5d8c78 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -296,6 +296,10 @@ void GameScreen::dialogFinished(const Screen *dialog, DialogResult result) { if (lookupsStarted_) { StartLookups(); } + if (!strcmp(dialog->tag(), "Savedata")) { + // Saves may have been deleted or imported there. update() recreates the views when the size is in. + info_ = g_gameInfoCache->GetInfo(NULL, gamePath_, g_desiredFlags, &knownFlags_, GameInfoFlags::SAVEDATA_SIZE); + } UITwoPaneBaseDialogScreen::dialogFinished(dialog, result); } @@ -712,6 +716,12 @@ void GameScreen::CreateContextMenu(UI::ViewGroup *parent) { }); } + if (info_->saveDataSize || info_->installDataSize) { + auto sa = GetI18NCategory(I18NCat::SAVEDATA); + parent->Add(new Choice(sa->T("Savedata Manager"), ImageID("I_FILE_SAVE")))->OnClick.Add([this](UI::EventParams &e) { + screenManager()->push(new SavedataScreen(gamePath_)); + }); + } if (info_->saveDataSize) { Choice *btnDeleteSaveData = new Choice(ga->T("Delete Save Data"), ImageID("I_TRASHCAN")); parent->Add(btnDeleteSaveData)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData); diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 2ece491bc5..9e76cfda43 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -25,10 +25,13 @@ #include "Common/Math/curves.h" #include "Common/Data/Text/Parsers.h" #include "Common/System/NativeApp.h" +#include "Common/System/OSD.h" #include "Common/System/Request.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" #include "Common/UI/PopupScreens.h" +#include "Common/UI/Root.h" +#include "Common/UI/ScrollView.h" #include "Common/UI/ViewGroup.h" #include "Common/UI/AsyncImageFileView.h" #include "Common/UI/ScreenManager.h" @@ -37,6 +40,7 @@ #include "UI/GameInfoCache.h" #include "UI/PauseScreen.h" #include "UI/MiscScreens.h" +#include "UI/ShareFiles.h" #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" @@ -49,6 +53,27 @@ class SavedataButton; +// Adds a folder's files to a zip under nameInZip, the layout the savedata import expects (DIR/PARAM.SFO). +static void AddFolderToZip(const Path &folder, const std::string &nameInZip, std::vector *entries) { + std::vector files; + File::GetFilesInDir(folder, &files); + for (const File::FileInfo &file : files) { + const std::string name = nameInZip + "/" + file.name; + if (file.isDirectory) { + AddFolderToZip(file.fullName, name, entries); + } else { + entries->push_back({ file.fullName, name }); + } + } +} + +static void ExportAsZip(std::string_view zipName, const std::vector &entries) { + if (!ShareFilesAsZip(zipName, entries)) { + auto sa = GetI18NCategory(I18NCat::SAVEDATA); + g_OSD.Show(OSDType::MESSAGE_ERROR, sa->T("Export failed")); + } +} + 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(ORIENT_VERTICAL, layoutParams) { @@ -169,6 +194,18 @@ void SavedataPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { savedataView_ = content->Add(new SavedataView(dc, ginfo.get(), ginfo->fileType, true)); auto di = GetI18NCategory(I18NCat::DIALOG); + auto sa = GetI18NCategory(I18NCat::SAVEDATA); + + const bool isState = savePath_.GetFileExtension() == ".ppst"; + // Installed data is large and not a save, so it's left out of exports. + bool canExport = false; + if (CanShareFiles()) { + canExport = isState || !IsInstalledGameData(savePath_); + if (!canExport) { + TextView *installedNote = content->Add(new TextView(sa->T("InstalledDataNotExported", "This is data the game installed, not a save, so it can't be exported."), ALIGN_LEFT | FLAG_WRAP_TEXT, true, new LinearLayoutParams(Margins(10, 0, 10, 5)))); + installedNote->SetTextColor(dc.GetTheme().popupStyle.fgColor); + } + } LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); buttonRow->SetSpacing(12); @@ -193,6 +230,25 @@ void SavedataPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { System_ShowFileInFolder(savePath_); }); } + if (canExport) { + buttonRow->Add(new Choice(sa->T("Export"), ImageID("I_SHARE"), new LinearLayoutParams(1.0f)))->OnClick.Add([this, isState](UI::EventParams &e) { + std::vector entries; + if (isState) { + // The state and its screenshot, in the root of the zip, which the import takes as savestates. + entries.push_back({ savePath_, "" }); + Path screenshot; + if (savePath_.WithReplacedExtension(".ppst", ".jpg", &screenshot) && File::Exists(screenshot)) { + entries.push_back({ screenshot, "" }); + } + std::string name = savePath_.GetFilename(); + name.resize(name.size() - strlen(".ppst")); + ExportAsZip(name, entries); + } else { + AddFolderToZip(savePath_, savePath_.GetFilename(), &entries); + ExportAsZip(savePath_.GetFilename(), entries); + } + }); + } buttonRow->SetRoundCorners(); parent->Add(buttonRow); } @@ -406,6 +462,10 @@ void SavedataButton::Draw(UIContext &dc) { dc.PopScissor(); dc.RebindTexture(); + if (highlighted_) { + dc.Draw()->DrawRoundRect(bounds_, 0.0f, 0xFFFFFFFF, RoundRectStyle::OutlineThin); + dc.Draw()->Flush(); + } } std::string SavedataButton::DescribeText() const { @@ -413,13 +473,48 @@ std::string SavedataButton::DescribeText() const { return ApplySafeSubstitutions(u->T("%1 button"), title_) + "\n" + subtitle_; } -SavedataBrowser::SavedataBrowser(const Path &path, UI::LayoutParams *layoutParams) - : LinearLayout(ORIENT_VERTICAL, layoutParams), path_(path) { +SavedataBrowser::SavedataBrowser(const Path &path, std::string_view highlightPrefix, UI::LayoutParams *layoutParams) + : LinearLayout(ORIENT_VERTICAL, layoutParams), path_(path), highlightPrefix_(highlightPrefix) { Refresh(); } +void SavedataBrowser::ScrollToHighlighted() { + if (!gameList_) { + scrollPending_ = false; + return; + } + SavedataButton *first = nullptr; + for (int i = 0; i < gameList_->GetNumSubviews(); i++) { + SavedataButton *button = static_cast(gameList_->GetViewByIndex(i)); + if (!button->IsHighlighted() || button->GetVisibility() != UI::V_VISIBLE) { + continue; + } + // Scrolling needs the position, so wait until the tab has been shown and laid out (after sorting). + if (button->GetBounds().h <= 0.0f) { + return; + } + if (!first || button->GetBounds().y < first->GetBounds().y) { + first = button; + } + } + scrollPending_ = false; + if (!first) { + return; + } + UI::ScrollView *scrollView = UI::GetEnclosingScrollView(first); + if (scrollView) { + scrollView->ScrollToCenter(first); + } + if (UI::IsFocusMovementEnabled()) { + UI::SetFocusedView(first, UI::FocusFlags::CAUSE_RESTORE); + } +} + void SavedataBrowser::Update() { LinearLayout::Update(); + if (scrollPending_) { + ScrollToHighlighted(); + } if (searchPending_) { searchPending_ = false; @@ -556,7 +651,13 @@ void SavedataBrowser::Refresh() { isSaveData = true; if (isSaveData || isState) { - savedataButtons.push_back(new SavedataButton(fileInfo[i].fullName, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT))); + SavedataButton *button = new SavedataButton(fileInfo[i].fullName, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT)); + // Save folders and savestates both start with the game ID. + if (!highlightPrefix_.empty() && startsWith(fileInfo[i].name, highlightPrefix_)) { + button->SetHighlighted(true); + scrollPending_ = true; + } + savedataButtons.push_back(button); } } @@ -626,7 +727,7 @@ void SavedataScreen::CreateSavedataTab(UI::ViewGroup *savedata) { sortStrip->SetRoundCorners(); savedata->Add(sortStrip); - dataBrowser_ = savedata->Add(new SavedataBrowser(savedata_dir, new LayoutParams(FILL_PARENT, FILL_PARENT))); + dataBrowser_ = savedata->Add(new SavedataBrowser(savedata_dir, gameID_, new LayoutParams(FILL_PARENT, FILL_PARENT))); dataBrowser_->SetSortOption(sortOption_); if (!searchFilter_.empty()) dataBrowser_->SetSearchFilter(searchFilter_); @@ -652,13 +753,52 @@ void SavedataScreen::CreateSavestateTab(UI::ViewGroup *savestate) { sortStrip->SetRoundCorners(); savestate->Add(sortStrip); - stateBrowser_ = savestate->Add(new SavedataBrowser(savestate_dir)); + stateBrowser_ = savestate->Add(new SavedataBrowser(savestate_dir, gameID_)); stateBrowser_->SetSortOption(sortOption_); if (!searchFilter_.empty()) stateBrowser_->SetSearchFilter(searchFilter_); stateBrowser_->OnChoice.Handle(this, &SavedataScreen::OnSavedataButtonClick); } +bool SavedataScreen::LookUpGame() { + gameID_.clear(); + gameTitle_.clear(); + gameSaveDirs_.clear(); + if (gamePath_.empty()) { + return true; + } + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, gamePath_, GameInfoFlags::PARAM_SFO); + if (!ginfo->Ready(GameInfoFlags::PARAM_SFO)) { + gameLookupPending_ = true; + return false; + } + gameLookupPending_ = false; + // Same rule as GetSaveDataDirectories(), or every folder would match. + if (ginfo->id.size() < 5) { + 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; +} + +void SavedataScreen::PreCreateViews() { + LookUpGame(); +} + +void SavedataScreen::update() { + UITabbedBaseDialogScreen::update(); + if (gameLookupPending_ && LookUpGame()) { + RecreateViews(); + } +} + void SavedataScreen::CreateTabs() { using namespace UI; auto sa = GetI18NCategory(I18NCat::SAVEDATA); @@ -674,6 +814,12 @@ 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)))); @@ -691,6 +837,14 @@ void SavedataScreen::OnSearch(UI::EventParams &e) { } } +void SavedataScreen::OnExportGameSaves(UI::EventParams &e) { + std::vector 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 ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), Path(e.s), GameInfoFlags::PARAM_SFO); diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index c0040b0795..6f777077dd 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -19,6 +19,7 @@ #include #include +#include #include "Common/File/Path.h" @@ -38,7 +39,8 @@ enum class SavedataSortOption { class SavedataBrowser : public UI::LinearLayout { public: - SavedataBrowser(const Path &path, UI::LayoutParams *layoutParams = 0); + // Entries whose names start with highlightPrefix (a game ID) are outlined, and the first one scrolled to. + SavedataBrowser(const Path &path, std::string_view highlightPrefix, UI::LayoutParams *layoutParams = 0); void Update() override; @@ -58,13 +60,17 @@ private: void Refresh(); void SavedataButtonClick(UI::EventParams &e); + void ScrollToHighlighted(); + SavedataSortOption sortOption_ = SavedataSortOption::FILENAME; UI::ViewGroup *gameList_ = nullptr; UI::TextView *noMatchView_ = nullptr; UI::TextView *searchingView_ = nullptr; Path path_; + std::string highlightPrefix_; std::string searchFilter_; bool searchPending_ = false; + bool scrollPending_ = false; }; class SavedataView; @@ -95,6 +101,7 @@ public: void dialogFinished(const Screen *dialog, DialogResult result) override; void sendMessage(UIMessage message, const char *value) override; + void update() override; ViewLayoutMode LayoutMode() const override { return ViewLayoutMode::ApplyInsets; @@ -103,6 +110,7 @@ public: const char *tag() const override { return "Savedata"; } protected: + void PreCreateViews() override; void CreateTabs() override; void CreateExtraButtons(UI::ViewGroup *verticalLayout, int margins) override; @@ -110,10 +118,17 @@ 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. + bool LookUpGame(); + std::string gameID_; + std::string gameTitle_; + std::vector gameSaveDirs_; // The game's saves, without installed data. + bool gameLookupPending_ = false; SavedataSortOption sortOption_ = SavedataSortOption::FILENAME; SavedataBrowser *dataBrowser_ = nullptr; SavedataBrowser *stateBrowser_ = nullptr; From f2c049d178d69700d7b0661aaf12818c60950193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 10:15:28 +0100 Subject: [PATCH 02/10] Translate the savedata export strings Co-Authored-By: Claude Opus 5.5 --- assets/lang/ar_AE.ini | 4 ++++ assets/lang/az_AZ.ini | 4 ++++ assets/lang/be_BY.ini | 4 ++++ assets/lang/bg_BG.ini | 4 ++++ assets/lang/ca_ES.ini | 4 ++++ assets/lang/cz_CZ.ini | 4 ++++ assets/lang/da_DK.ini | 4 ++++ assets/lang/de_DE.ini | 4 ++++ assets/lang/dr_ID.ini | 4 ++++ assets/lang/en_US.ini | 4 ++++ assets/lang/es_ES.ini | 4 ++++ assets/lang/es_LA.ini | 4 ++++ assets/lang/fa_IR.ini | 4 ++++ assets/lang/fi_FI.ini | 4 ++++ assets/lang/fr_FR.ini | 4 ++++ assets/lang/gl_ES.ini | 4 ++++ assets/lang/gr_EL.ini | 4 ++++ assets/lang/he_IL.ini | 4 ++++ assets/lang/hr_HR.ini | 4 ++++ assets/lang/hu_HU.ini | 4 ++++ assets/lang/id_ID.ini | 4 ++++ assets/lang/it_IT.ini | 4 ++++ assets/lang/ja_JP.ini | 4 ++++ assets/lang/jv_ID.ini | 4 ++++ assets/lang/km_KH.ini | 4 ++++ assets/lang/ko_KR.ini | 4 ++++ assets/lang/ku_SO.ini | 4 ++++ assets/lang/lo_LA.ini | 4 ++++ assets/lang/lt-LT.ini | 4 ++++ assets/lang/ms_MY.ini | 4 ++++ assets/lang/nl_NL.ini | 4 ++++ assets/lang/nn_NO.ini | 4 ++++ assets/lang/no_NO.ini | 4 ++++ assets/lang/pl_PL.ini | 4 ++++ assets/lang/pt_BR.ini | 4 ++++ assets/lang/pt_PT.ini | 4 ++++ assets/lang/ro_RO.ini | 4 ++++ assets/lang/ru_RU.ini | 4 ++++ assets/lang/sv_SE.ini | 4 ++++ assets/lang/tg_PH.ini | 4 ++++ assets/lang/th_TH.ini | 4 ++++ assets/lang/tr_TR.ini | 4 ++++ assets/lang/uk_UA.ini | 4 ++++ assets/lang/vi_VN.ini | 4 ++++ assets/lang/zh_CN.ini | 4 ++++ assets/lang/zh_TW.ini | 4 ++++ 46 files changed, 184 insertions(+) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 7996038b9f..797929336c 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -1472,7 +1472,11 @@ View Feedback = أظهر كل المراجعات [Savedata] Date = التاريخ +Export = تصدير # AI translated +Export all saves for this game = تصدير كل بيانات الحفظ لهذه اللعبة # AI translated +Export failed = فشل التصدير # AI translated Filename = إسم الملف +InstalledDataNotExported = هذه بيانات ثبّتتها اللعبة، وليست بيانات حفظ، لذا لا يمكن تصديرها. # AI translated No screenshot = لا توجد لقطة شاشة # AI translated None yet. Things will appear here after you save. = ليس بعد. الأشياء سوف تظهر هنا بعد الحفظ. Nothing matching '%1' was found = لم يتم العثور على ما يطابق '%1' # AI translated diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index ac21aeb0c0..ca9ee089ae 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -1462,7 +1462,11 @@ View Feedback = Bütün geri bildirimləri gör [Savedata] Date = Tarix +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Fayl adı +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = Ekran çəkilişi yoxdur None yet. Things will appear here after you save. = Hələlik heç nə. Qoruduqdan sonra burada nəsnələr oluşacaq. Nothing matching '%1' was found = '%1' ilə uyğun heç nə tapılmadı # AI translated diff --git a/assets/lang/be_BY.ini b/assets/lang/be_BY.ini index 44b011463e..26bdbb1786 100644 --- a/assets/lang/be_BY.ini +++ b/assets/lang/be_BY.ini @@ -1462,7 +1462,11 @@ View Feedback = Усе водгукі # AI translated [Savedata] Date = Дата +Export = Экспартаваць # AI translated +Export all saves for this game = Экспартаваць усе захаванні гэтай гульні # AI translated +Export failed = Не ўдалося экспартаваць # AI translated Filename = Імя файла +InstalledDataNotExported = Гэта даныя, усталяваныя гульнёй, а не захаванне, таму іх нельга экспартаваць. # AI translated No screenshot = Няма здымка экрана # AI translated None yet. Things will appear here after you save. = Пакуль нічога няма. Тут з'явяцца захаванні, калі вы захаваецеся. # AI translated Nothing matching '%1' was found = Нічога не знойдзена па запыце '%1' # AI translated diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index b72abe744c..06cf68a459 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -1462,7 +1462,11 @@ View Feedback = Виж всички отзиви # AI translated [Savedata] Date = Дата # AI translated +Export = Експортирай # AI translated +Export all saves for this game = Експортиране на всички запазени данни за тази игра # AI translated +Export failed = Експортирането не бе успешно # AI translated Filename = Име на файла # AI translated +InstalledDataNotExported = Това са данни, инсталирани от играта, а не запис, затова не могат да бъдат експортирани. # AI translated No screenshot = Няма скрийншот # AI translated None yet. Things will appear here after you save. = Все още няма нищо. Тук ще се появят записи, след като запазите. # AI translated Nothing matching '%1' was found = Не е намерено нищо, съвпадащо с '%1' # AI translated diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 715482fb63..6fe196eedf 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -1462,7 +1462,11 @@ View Feedback = Veure tots els comentaris # AI translated [Savedata] Date = Data +Export = Exporta # AI translated +Export all saves for this game = Exporta totes les dades desades d'aquest joc # AI translated +Export failed = No s'ha pogut exportar # AI translated Filename = Nom del fitxer +InstalledDataNotExported = Són dades que ha instal·lat el joc, no una partida desada, així que no es poden exportar. # AI translated No screenshot = Sense captura de pantalla None yet. Things will appear here after you save. = No hi ha dades. Un cop desis partida apareixeran aquí. # AI translated Nothing matching '%1' was found = No s'ha trobat res que coincideixi amb '%1' # AI translated diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index 2f43f5674c..280e6dbd28 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -1462,7 +1462,11 @@ View Feedback = Zobrazit zpětnou vazbu # AI translated [Savedata] Date = Datum # AI translated +Export = Exportovat # AI translated +Export all saves for this game = Exportovat všechna uložená data této hry # AI translated +Export failed = Export se nezdařil # AI translated Filename = Název souboru # AI translated +InstalledDataNotExported = Toto jsou data nainstalovaná hrou, ne uložená pozice, takže je nelze exportovat. # AI translated No screenshot = Žádný snímek obrazovky # AI translated None yet. Things will appear here after you save. = Zatím žádné. Položky zde budou zobrazeny, až uložíte hru. Nothing matching '%1' was found = Nebylo nalezeno nic odpovídajícího '%1' # AI translated diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index d0a9e04c94..2fe70e9253 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -1462,7 +1462,11 @@ View Feedback = Se feedback [Savedata] Date = Dato # AI translated +Export = Eksportér # AI translated +Export all saves for this game = Eksportér alle gemte data for dette spil # AI translated +Export failed = Eksporten mislykkedes # AI translated Filename = Filnavn # AI translated +InstalledDataNotExported = Dette er data, som spillet har installeret, ikke gemte data, så de kan ikke eksporteres. # AI translated No screenshot = Intet skærmdump # AI translated None yet. Things will appear here after you save. = Intet endnu. Ting vises her, når du har gemt. # AI translated Nothing matching '%1' was found = Intet, der matcher '%1', blev fundet # AI translated diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 85080919a8..6540266245 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -1461,7 +1461,11 @@ View Feedback = Alle Rückmeldungen ansehen [Savedata] Date = Datum +Export = Exportieren # AI translated +Export all saves for this game = Alle Speicherdaten dieses Spiels exportieren # AI translated +Export failed = Export fehlgeschlagen # AI translated Filename = Dateiname +InstalledDataNotExported = Das sind vom Spiel installierte Daten, keine Speicherdaten, daher können sie nicht exportiert werden. # AI translated No screenshot = Kein Bildschirmfoto None yet. Things will appear here after you save. = Noch keine. Dinge werden hier erscheinen, nachdem du gespeichert hast. Nothing matching '%1' was found = Nichts gefunden, das '%1' entspricht # AI translated diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 93d4069f62..1c216408fc 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -1462,7 +1462,11 @@ View Feedback = View all feedbacks [Savedata] Date = Date +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Filename +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = No screenshot None yet. Things will appear here after you save. = None yet. Things will appear here after you save. Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 88fb41c31d..464de22878 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -1487,7 +1487,11 @@ View Feedback = View all feedbacks [Savedata] Date = Date +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Filename +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = No screenshot None yet. Things will appear here after you save. = None yet. Things will appear here after you save. Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index deaff51758..b430da7d20 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -1463,7 +1463,11 @@ View Feedback = Ver comentarios [Savedata] Date = Fecha +Export = Exportar # AI translated +Export all saves for this game = Exportar todos los datos guardados de este juego # AI translated +Export failed = Error al exportar # AI translated Filename = Nombre de archivo +InstalledDataNotExported = Son datos que instaló el juego, no una partida guardada, así que no se pueden exportar. # AI translated No screenshot = Sin captura de pantalla None yet. Things will appear here after you save. = Ninguna todavía. Las cosas aparecerán aquí después de guardar. Nothing matching '%1' was found = No se encontró nada que coincida con '%1' # AI translated diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 41987fcbe7..1715fda4c0 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -1464,7 +1464,11 @@ View Feedback = Ver comentarios [Savedata] Date = Fecha +Export = Exportar # AI translated +Export all saves for this game = Exportar todas las partidas guardadas de este juego # AI translated +Export failed = Error al exportar # AI translated Filename = Nombre de archivo +InstalledDataNotExported = Son datos que instaló el juego, no una partida guardada, así que no se pueden exportar. # AI translated No screenshot = No hay capturas de pantalla. None yet. Things will appear here after you save. = No hay datos. Una vez que guardes partida aparecerán aquí. Nothing matching '%1' was found = No se encontró nada que coincida con '%1' # AI translated diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 764fc4f476..5f1acec299 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -1462,7 +1462,11 @@ View Feedback = مشاهدهٔ همهٔ بازخوردها # AI translated [Savedata] Date = تاریخ +Export = خروجی گرفتن # AI translated +Export all saves for this game = خروجی گرفتن از همهٔ داده‌های ذخیرهٔ این بازی # AI translated +Export failed = خروجی گرفتن ناموفق بود # AI translated Filename = نام فایل +InstalledDataNotExported = این داده‌ای است که بازی نصب کرده، نه دادهٔ ذخیره، بنابراین نمی‌توان از آن خروجی گرفت. # AI translated No screenshot = بدون نماگرفت None yet. Things will appear here after you save. = هنوز چیزی نیست. پس از ذخیره، موارد اینجا نمایش داده می‌شوند. # AI translated Nothing matching '%1' was found = موردی مطابق با '%1' یافت نشد # AI translated diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 81395493a1..4529d5767a 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -1462,7 +1462,11 @@ View Feedback = Näytä kaikki palautteet [Savedata] Date = Päivämäärä +Export = Vie # AI translated +Export all saves for this game = Vie kaikki tämän pelin tallennukset # AI translated +Export failed = Vienti epäonnistui # AI translated Filename = Tiedostonimi +InstalledDataNotExported = Nämä ovat pelin asentamia tietoja eivätkä tallennus, joten niitä ei voi viedä. # AI translated No screenshot = Ei kuvakaappausta None yet. Things will appear here after you save. = Ei vielä. Tähän ilmestyy asioita tallentamisen jälkeen. Nothing matching '%1' was found = Hakuehtoa '%1' vastaavia kohteita ei löytynyt # AI translated diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 0be68e3cf7..781e4c69d4 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -1489,7 +1489,11 @@ View Feedback = Voir tous les rapports [Savedata] Date = Date # same as English +Export = Exporter # AI translated +Export all saves for this game = Exporter toutes les sauvegardes de ce jeu # AI translated +Export failed = Échec de l'exportation # AI translated Filename = Nom de fichier +InstalledDataNotExported = Ce sont des données installées par le jeu, pas une sauvegarde : elles ne peuvent donc pas être exportées. # AI translated No screenshot = Pas de capture d'écran None yet. Things will appear here after you save. = Rien à afficher pour le moment.\nDes choses apparaîtront ici lorsque vous sauvegarderez. Nothing matching '%1' was found = Aucun résultat pour '%1' # AI translated diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index c0f399f775..a6fdbce9b1 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -1462,7 +1462,11 @@ View Feedback = Ver todas as opinións # AI translated [Savedata] Date = Data # AI translated +Export = Exportar # AI translated +Export all saves for this game = Exportar todos os datos de gardado deste xogo # AI translated +Export failed = Non se puido exportar # AI translated Filename = Nome do ficheiro # AI translated +InstalledDataNotExported = Son datos que instalou o xogo, non unha partida gardada, así que non se poden exportar. # AI translated No screenshot = Sen captura # AI translated None yet. Things will appear here after you save. = Aínda nada. Os elementos aparecerán aquí despois de gardar. # AI translated Nothing matching '%1' was found = Non se atopou nada que coincida con '%1' # AI translated diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index d2670a0f2e..354e031605 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -1462,7 +1462,11 @@ View Feedback = Προβολή σχολίων [Savedata] Date = Ημερομηνία +Export = Εξαγωγή # AI translated +Export all saves for this game = Εξαγωγή όλων των δεδομένων αποθήκευσης αυτού του παιχνιδιού # AI translated +Export failed = Η εξαγωγή απέτυχε # AI translated Filename = Όνομα αρχείου # AI translated +InstalledDataNotExported = Αυτά είναι δεδομένα που εγκατέστησε το παιχνίδι, όχι αποθήκευση, οπότε δεν μπορούν να εξαχθούν. # AI translated No screenshot = Χωρίς στιγμιότυπο οθόνης None yet. Things will appear here after you save. = Τίποτα προς το παρόν. Θα εμφανιστούν στοιχεία αφότου πραγματοποιήσετε αποθήκευση. Nothing matching '%1' was found = Δεν βρέθηκε τίποτα που να ταιριάζει με '%1' # AI translated diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 4441f8f53c..1d8e7ad972 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -1462,7 +1462,11 @@ View Feedback = הצג את כל המשובים # AI translated [Savedata] Date = תאריך # AI translated +Export = ייצוא # AI translated +Export all saves for this game = ייצוא כל נתוני השמירה של משחק זה # AI translated +Export failed = הייצוא נכשל # AI translated Filename = שם קובץ # AI translated +InstalledDataNotExported = אלה נתונים שהמשחק התקין, לא שמירה, ולכן לא ניתן לייצא אותם. # AI translated No screenshot = אין צילום מסך # AI translated None yet. Things will appear here after you save. = אין עדיין. פריטים יופיעו כאן לאחר שתשמור. # AI translated Nothing matching '%1' was found = לא נמצא דבר התואם ל-'%1' # AI translated diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index ee514d36c2..b1cbaca938 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -1462,7 +1462,11 @@ View Feedback = Provjeri sve povratne informacije [Savedata] Date = Datum +Export = Izvezi # AI translated +Export all saves for this game = Izvezi sve savedate ove igre # AI translated +Export failed = Izvoz nije uspio # AI translated Filename = Ime datoteke +InstalledDataNotExported = Ovo su podaci koje je igra instalirala, a ne spremljena igra, pa se ne mogu izvesti. # AI translated No screenshot = Nema snimke zaslona None yet. Things will appear here after you save. = Još ništa. Stvari će se ovdje pojaviti nakon što nešto spremiš. Nothing matching '%1' was found = Ništa što odgovara '%1' nije pronađeno # AI translated diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 7b4589860a..5ea50822ee 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -1462,7 +1462,11 @@ View Feedback = Az összes visszajelzés megtekintése… [Savedata] Date = Dátum +Export = Exportálás # AI translated +Export all saves for this game = A játék összes mentésének exportálása # AI translated +Export failed = Az exportálás sikertelen # AI translated Filename = Fájlnév +InstalledDataNotExported = Ezek a játék által telepített adatok, nem mentés, ezért nem exportálhatók. # AI translated No screenshot = Nincs kép None yet. Things will appear here after you save. = Itt jelennek majd meg a mentések. Még nincs semmi. Nothing matching '%1' was found = Nincs a(z) '%1' keresésnek megfelelő találat # AI translated diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index f390dad47f..41c1f8be50 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -1462,7 +1462,11 @@ View Feedback = Lihat semua umpan balik [Savedata] Date = Tanggal +Export = Ekspor # AI translated +Export all saves for this game = Ekspor semua data simpanan game ini # AI translated +Export failed = Gagal mengekspor # AI translated Filename = Nama berkas +InstalledDataNotExported = Ini adalah data yang dipasang oleh game, bukan data simpanan, jadi tidak dapat diekspor. # AI translated No screenshot = Tidak ada tangkapan layar None yet. Things will appear here after you save. = Belum ada. Sesuatu akan muncul di sini setelah anda menyimpan. Nothing matching '%1' was found = Tidak ada yang cocok dengan '%1' # AI translated diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index f90d5922ac..6db3e76dc9 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -1465,7 +1465,11 @@ View Feedback = Visualizza tutti i Feedback [Savedata] Date = Data +Export = Esporta # AI translated +Export all saves for this game = Esporta tutti i salvataggi di questo gioco # AI translated +Export failed = Esportazione non riuscita # AI translated Filename = Nome del file +InstalledDataNotExported = Questi sono dati installati dal gioco, non un salvataggio, quindi non possono essere esportati. # AI translated No screenshot = Nessuno screenshot None yet. Things will appear here after you save. = Ancora niente. Si inizierà a vedere qualcosa dopo il salvataggio. Nothing matching '%1' was found = Nessun risultato per '%1' # AI translated diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index af60342ce8..2244bde652 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -1463,7 +1463,11 @@ View Feedback = 全てのフィードバックを見る [Savedata] Date = 日付 +Export = エクスポート # AI translated +Export all saves for this game = このゲームのセーブデータをすべてエクスポート # AI translated +Export failed = エクスポートに失敗しました # AI translated Filename = ファイル名 +InstalledDataNotExported = これはゲームがインストールしたデータで、セーブデータではないため、エクスポートできません。 # AI translated No screenshot = スクリーンショットなし None yet. Things will appear here after you save. = まだありません。セーブ後に項目が表示されます。 Nothing matching '%1' was found = '%1' に一致するものは見つかりませんでした # AI translated diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index a01a35736b..92d3172d67 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -1462,7 +1462,11 @@ View Feedback = View all feedbacks [Savedata] Date = Date +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Filename +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = Ora ana tangkapan layar None yet. Things will appear here after you save. = None yet. Things will appear here after you save. Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/km_KH.ini b/assets/lang/km_KH.ini index 4dcec79088..504bbe1749 100644 --- a/assets/lang/km_KH.ini +++ b/assets/lang/km_KH.ini @@ -1463,7 +1463,11 @@ View Feedback =មើលមតិកែលម្អទាំងអស់។ [Savedata] Date =កាលបរិច្ឆេទ +Export = នាំចេញ # AI translated +Export all saves for this game = Export all saves for this game +Export failed = ការនាំចេញបានបរាជ័យ # AI translated Filename =ឈ្មោះឯកសារ +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot =គ្មានរូបថតអេក្រង់ទេ។ None yet. Things will appear here after you save. =មិនទាន់មានទេ។ អ្វីៗនឹងបង្ហាញនៅទីនេះ បន្ទាប់ពីអ្នករក្សាទុក។ Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index d5f5cfa0b9..67dcf8d002 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -1462,7 +1462,11 @@ View Feedback = 모든 피드백 보기 [Savedata] Date = 날짜 +Export = 내보내기 # AI translated +Export all saves for this game = 이 게임의 모든 저장 데이터 내보내기 # AI translated +Export failed = 내보내기 실패 # AI translated Filename = 파일이름 +InstalledDataNotExported = 이 데이터는 게임이 설치한 데이터이며 저장 데이터가 아니므로 내보낼 수 없습니다. # AI translated No screenshot = 스크린샷 없음 None yet. Things will appear here after you save. = 아직 없습니다. 저장하면 여기에 항목이 표시됩니다. Nothing matching '%1' was found = '%1'와(과) 일치하는 항목이 없습니다 # AI translated diff --git a/assets/lang/ku_SO.ini b/assets/lang/ku_SO.ini index bc0f6727bc..088d32cb4f 100644 --- a/assets/lang/ku_SO.ini +++ b/assets/lang/ku_SO.ini @@ -1476,7 +1476,11 @@ View Feedback = View all feedbacks [Savedata] Date = Date +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Filename +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = No screenshot None yet. Things will appear here after you save. = None yet. Things will appear here after you save. Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index 3ba7ecb517..7efbe2f8f6 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -1462,7 +1462,11 @@ View Feedback = ເບິ່ງການລາຍງານ [Savedata] Date = Date +Export = Export +Export all saves for this game = Export all saves for this game +Export failed = Export failed Filename = Filename +InstalledDataNotExported = This is data the game installed, not a save, so it can't be exported. No screenshot = ບໍ່ມີການຈັບພາບໜ້າຈໍ None yet. Things will appear here after you save. = ຍັງບໍ່ມີຂໍ້ມູນເທື່ອ. ຈະມີກໍຕໍ່ເມື່ອເຈົ້າໄດ້ກົດບັນທຶກເອົາໄວ້. Nothing matching '%1' was found = Nothing matching '%1' was found diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 887f2c094b..7b469246c7 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -1462,7 +1462,11 @@ View Feedback = Peržiūrėti visus atsiliepimus # AI translated [Savedata] Date = Data # AI translated +Export = Eksportuoti # AI translated +Export all saves for this game = Eksportuoti visus šio žaidimo išsaugotus duomenis # AI translated +Export failed = Eksportuoti nepavyko # AI translated Filename = Failo pavadinimas # AI translated +InstalledDataNotExported = Tai žaidimo įdiegti duomenys, o ne išsaugojimas, todėl jų negalima eksportuoti. # AI translated No screenshot = Nėra ekrano nuotraukos # AI translated None yet. Things will appear here after you save. = Kol kas nieko nėra. Įrašai atsiras čia, kai išsaugosite. # AI translated Nothing matching '%1' was found = Nerasta nieko, kas atitiktų '%1' # AI translated diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 9a781010d7..46f98f170e 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -1462,7 +1462,11 @@ View Feedback = Lihat semua maklum balas [Savedata] Date = Tarikh +Export = Eksport # AI translated +Export all saves for this game = Eksport semua data simpanan permainan ini # AI translated +Export failed = Gagal mengeksport # AI translated Filename = Nama fail +InstalledDataNotExported = Ini ialah data yang dipasang oleh permainan, bukan data simpanan, jadi ia tidak boleh dieksport. # AI translated No screenshot = Tiada tangkapan skrin None yet. Things will appear here after you save. = Belum ada lagi. Item akan muncul di sini selepas anda menyimpan. Nothing matching '%1' was found = Tiada padanan untuk '%1' ditemui # AI translated diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 029fbde54d..308e2cf7dc 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -1462,7 +1462,11 @@ View Feedback = Alle feedback bekijken [Savedata] Date = Datum # AI translated +Export = Exporteren # AI translated +Export all saves for this game = Alle savedata van deze game exporteren # AI translated +Export failed = Exporteren mislukt # AI translated Filename = Bestandsnaam # AI translated +InstalledDataNotExported = Dit zijn gegevens die de game heeft geïnstalleerd, geen savedata, dus ze kunnen niet worden geëxporteerd. # AI translated No screenshot = Geen screenshot None yet. Things will appear here after you save. = Nog niets. Er zullen hier dingen verschijnen zodra u iets opslaat. Nothing matching '%1' was found = Niets gevonden dat overeenkomt met '%1' # AI translated diff --git a/assets/lang/nn_NO.ini b/assets/lang/nn_NO.ini index 9957f93131..66e15dde03 100644 --- a/assets/lang/nn_NO.ini +++ b/assets/lang/nn_NO.ini @@ -1487,7 +1487,11 @@ View Feedback = Vis alle tilbakemeldingar [Savedata] Date = Dato +Export = Eksporter # AI translated +Export all saves for this game = Eksporter alle lagringsdata for dette spelet # AI translated +Export failed = Eksporten mislukkast # AI translated Filename = Filnamn +InstalledDataNotExported = Dette er data som spelet har installert, ikkje lagringsdata, så dei kan ikkje eksporterast. # AI translated No screenshot = Ingen skjermbilete None yet. Things will appear here after you save. = Ingenting enno. Ting dukkar opp her etter at du har lagra. Nothing matching '%1' was found = Fann ingenting som samsvarar med '%1' # AI translated diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 8b6957d75f..1eed1a0420 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -1487,7 +1487,11 @@ View Feedback = Vis alle tilbakemeldinger [Savedata] Date = Dato +Export = Eksporter # AI translated +Export all saves for this game = Eksporter alle lagringsdata for dette spillet # AI translated +Export failed = Eksporten mislyktes # AI translated Filename = Filnavn +InstalledDataNotExported = Dette er data som spillet har installert, ikke lagringsdata, så de kan ikke eksporteres. # AI translated No screenshot = Ingen skjermklipp None yet. Things will appear here after you save. = Ingenting ennå. Ting vises her etter at du har lagret. Nothing matching '%1' was found = Fant ingenting som samsvarer med '%1' # AI translated diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 8db11b797e..2edffcce42 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -1466,7 +1466,11 @@ View Feedback = Zobacz opinie [Savedata] Date = Data # AI translated +Export = Eksportuj # AI translated +Export all saves for this game = Eksportuj wszystkie zapisy tej gry # AI translated +Export failed = Eksport nie powiódł się # AI translated Filename = Nazwa pliku +InstalledDataNotExported = To dane zainstalowane przez grę, a nie zapis, więc nie można ich wyeksportować. # AI translated No screenshot = Brak obrazka None yet. Things will appear here after you save. = Na razie nic tu nie ma. Wpisy pojawią się, gdy zapiszesz grę. Nothing matching '%1' was found = Nie znaleziono niczego pasującego do '%1' # AI translated diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index 3538f85486..dabf07fef7 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -1490,7 +1490,11 @@ View Feedback = Visualizar todos os feedbacks [Savedata] Date = Data +Export = Exportar # AI translated +Export all saves for this game = Exportar todos os dados salvos deste jogo # AI translated +Export failed = Falha ao exportar # AI translated Filename = Nome do arquivo +InstalledDataNotExported = Estes são dados instalados pelo jogo, não dados salvos, então não podem ser exportados. # AI translated No screenshot = Sem screenshot None yet. Things will appear here after you save. = Nada ainda. As coisas aparecerão aqui após você salvar. Nothing matching '%1' was found = Nada correspondente a '%1' foi encontrado # AI translated diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 59928e7e90..81bc2ebbc6 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -1486,7 +1486,11 @@ View Feedback = Ver todos os feedbacks [Savedata] Date = Data +Export = Exportar # AI translated +Export all saves for this game = Exportar todos os dados salvos deste jogo # AI translated +Export failed = Falha ao exportar # AI translated Filename = Nome do ficheiro +InstalledDataNotExported = Estes são dados instalados pelo jogo, não dados salvos, por isso não podem ser exportados. # AI translated No screenshot = Sem Captura de tela None yet. Things will appear here after you save. = Nada ainda. As coisas vão aparecer aqui após salvares. Nothing matching '%1' was found = Não foi encontrado nada correspondente a '%1' # AI translated diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index c64da7abed..97cd9ac2e3 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -1463,7 +1463,11 @@ View Feedback = Vezi tot feedbackul # AI translated [Savedata] Date = Dată # AI translated +Export = Exportă # AI translated +Export all saves for this game = Exportă toate salvările acestui joc # AI translated +Export failed = Exportul a eșuat # AI translated Filename = Nume fișier # AI translated +InstalledDataNotExported = Acestea sunt date instalate de joc, nu o salvare, deci nu pot fi exportate. # AI translated No screenshot = Fără captură de ecran # AI translated None yet. Things will appear here after you save. = Nimic încă. Elementele vor apărea aici după ce salvați. # AI translated Nothing matching '%1' was found = Nu s-a găsit nimic care să corespundă cu '%1' # AI translated diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index 430f89e85f..79d694589b 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -1462,7 +1462,11 @@ View Feedback = Показать все отзывы [Savedata] Date = Дата +Export = Экспорт # AI translated +Export all saves for this game = Экспортировать все сохранения этой игры # AI translated +Export failed = Не удалось экспортировать # AI translated Filename = Имя файла +InstalledDataNotExported = Это данные, установленные игрой, а не сохранение, поэтому их нельзя экспортировать. # AI translated No screenshot = Нет скриншота None yet. Things will appear here after you save. = Здесь ничего нет. Появится после сохранения. Nothing matching '%1' was found = Ничего не найдено по запросу '%1' # AI translated diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 74e0ce303e..260fab6e49 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -1463,7 +1463,11 @@ View Feedback = Visa all feedback [Savedata] Date = Datum +Export = Exportera # AI translated +Export all saves for this game = Exportera all sparad data för det här spelet # AI translated +Export failed = Exporten misslyckades # AI translated Filename = Filnamn +InstalledDataNotExported = Det här är data som spelet har installerat, inte sparad data, så den kan inte exporteras. # AI translated No screenshot = Ingen skärmdump None yet. Things will appear here after you save. = Inget ännu. Innehåll visas här när du har sparat. Nothing matching '%1' was found = Inget som matchar '%1' hittades # AI translated diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index fa08229342..fdb0d3e581 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -1463,7 +1463,11 @@ View Feedback = Tingnan ang Feedback [Savedata] Date = Petsa +Export = I-export # AI translated +Export all saves for this game = I-export ang lahat ng savedata ng larong ito # AI translated +Export failed = Nabigo ang pag-export # AI translated Filename = Pangalan ng file +InstalledDataNotExported = Ito ay data na in-install ng laro, hindi save, kaya hindi ito maaaring i-export. # AI translated No screenshot = Walang Screenshot None yet. Things will appear here after you save. = Wala pa. Makikita mo dito kapag may na i-savestate ka na Nothing matching '%1' was found = Walang nahanap na tugma sa '%1' # AI translated diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index ce61537619..6e0e985d40 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -1472,7 +1472,11 @@ View Feedback = ดูความคิดเห็น [Savedata] Date = ตามวันที่ +Export = ส่งออก # AI translated +Export all saves for this game = ส่งออกเซฟทั้งหมดของเกมนี้ # AI translated +Export failed = ส่งออกไม่สำเร็จ # AI translated Filename = ตามชื่อ +InstalledDataNotExported = นี่คือข้อมูลที่เกมติดตั้งไว้ ไม่ใช่เซฟ จึงส่งออกไม่ได้ # AI translated No screenshot = ไม่มีภาพประกอบ None yet. Things will appear here after you save. = ยังไม่มีข้อมูล จะมีก็ต่อเมื่อหลังจากที่คุณได้กดบันทึกเอาไว้ Nothing matching '%1' was found = ไม่พบรายการที่ตรงกับ '%1' # AI translated diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index 804b0fe6f2..d647767e9b 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -1463,7 +1463,11 @@ View Feedback = Tüm geri bildirimleri göster [Savedata] Date = Tarih +Export = Dışa aktar # AI translated +Export all saves for this game = Bu oyunun tüm kayıtlarını dışa aktar # AI translated +Export failed = Dışa aktarma başarısız # AI translated Filename = Dosya Adı +InstalledDataNotExported = Bu, oyunun yüklediği veridir, kayıt değildir, bu yüzden dışa aktarılamaz. # AI translated No screenshot = Ekran Görüntüsü Yok None yet. Things will appear here after you save. = Henüz bir şey yok. Kayıt aldığında kayıtların burada görünecek. Nothing matching '%1' was found = '%1' ile eşleşen bir şey bulunamadı # AI translated diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index 2a62b11ea3..e94a3d187d 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -1462,7 +1462,11 @@ View Feedback = Переглянути всі відгуки [Savedata] Date = Дата +Export = Експорт # AI translated +Export all saves for this game = Експортувати всі збереження цієї гри # AI translated +Export failed = Не вдалося експортувати # AI translated Filename = Ім'я файлу +InstalledDataNotExported = Це дані, встановлені грою, а не збереження, тому їх не можна експортувати. # AI translated No screenshot = Немає скріншоту None yet. Things will appear here after you save. = Тут поки що нічого немає. З'явиться після вашого збереження. Nothing matching '%1' was found = Нічого не знайдено за запитом '%1' # AI translated diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index 2b60c7e9e4..f0582d8a83 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -1462,7 +1462,11 @@ View Feedback = Xem tất cả phản hồi [Savedata] Date = Ngày # AI translated +Export = Xuất # AI translated +Export all saves for this game = Xuất tất cả dữ liệu lưu của trò chơi này # AI translated +Export failed = Xuất thất bại # AI translated Filename = Tên file +InstalledDataNotExported = Đây là dữ liệu do trò chơi cài đặt, không phải dữ liệu lưu, nên không thể xuất. # AI translated No screenshot = Không có screenshot None yet. Things will appear here after you save. = Không có gì ở đây. Mọi thứ sẽ xuất hiện ở đây sau khi bạn lưu. Nothing matching '%1' was found = Không tìm thấy kết quả nào khớp với '%1' # AI translated diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index a7b295b1b6..bea6ee8c32 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -1463,7 +1463,11 @@ View Feedback = 查看所有反馈 [Savedata] Date = 日期 +Export = 导出 # AI translated +Export all saves for this game = 导出此游戏的所有存档 # AI translated +Export failed = 导出失败 # AI translated Filename = 文件名 +InstalledDataNotExported = 这是游戏安装的数据,不是存档,因此无法导出。 # AI translated No screenshot = 没有截图 None yet. Things will appear here after you save. = 暂无内容,游戏保存数据之后再回来吧。 Nothing matching '%1' was found = 未找到与“%1”匹配的内容 # AI translated diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 1d741aeda2..1de29fa45a 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -1462,7 +1462,11 @@ View Feedback = 檢視所有意見反應 [Savedata] Date = 日期 +Export = 匯出 # AI translated +Export all saves for this game = 匯出此遊戲的所有儲存資料 # AI translated +Export failed = 匯出失敗 # AI translated Filename = 檔案名稱 +InstalledDataNotExported = 這是遊戲安裝的資料,不是儲存資料,因此無法匯出。 # AI translated No screenshot = 沒有螢幕截圖 None yet. Things will appear here after you save. = 尚無,將在存檔後出現新項目 Nothing matching '%1' was found = 找不到與「%1」相符的內容 # AI translated From 9be62bb7d5e4a00566fa8571e7365a047136f382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 10:27:09 +0100 Subject: [PATCH 03/10] Frame dumps: Share them as zips where folders can't be shown Where the file can't be shown in its folder (Android, iOS), "Create frame dump" in the dev menu now zips the dump and opens the share sheet. The frame dump hotkey does the same when its message is clicked, so the game isn't interrupted. Both go through OnFrameDumpCreated(), which moves the share to the main thread, since the recorder calls back on the emulation thread. Co-Authored-By: Claude Opus 5.5 --- UI/DevScreens.cpp | 40 ++++++++++++++++++++++++++++++++++------ UI/DevScreens.h | 4 ++++ UI/EmuScreen.cpp | 9 ++------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 90a0377514..e0e45a7425 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -82,6 +82,7 @@ #include "UI/ControlMappingScreen.h" #include "UI/GameSettingsScreen.h" #include "UI/JitCompareScreen.h" +#include "UI/ShareFiles.h" #include "android/jni/app-android.h" @@ -121,17 +122,44 @@ void AddOverlayList(UI::ViewGroup *items, ScreenManager *screenManager) { } } +// Zipped, so apps that only take known file types accept it. The zip import recognizes it too. +static void ShareFrameDump(const Path &dumpPath) { + std::string name = dumpPath.GetFilename(); + if (endsWithNoCase(name, ".ppdmp")) { + name.resize(name.size() - strlen(".ppdmp")); + } + if (!ShareFilesAsZip(name, { { dumpPath, "" } })) { + auto sy = GetI18NCategory(I18NCat::SYSTEM); + g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Failed")); + } +} + +void OnFrameDumpCreated(const Path &dumpPath, bool openNow) { + NOTICE_LOG(Log::System, "Frame dump created at '%s'", dumpPath.c_str()); + std::function open; + if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { + open = [dumpPath]() { System_ShowFileInFolder(dumpPath); }; + } else if (CanShareFiles()) { + open = [dumpPath]() { ShareFrameDump(dumpPath); }; + } + g_OSD.Show(OSDType::MESSAGE_SUCCESS, GetFriendlyPath(dumpPath), 7.0f, "framedump"); + if (!open) { + return; + } + if (openNow) { + // The recorder calls back on the emulation thread. + System_RunOnMainThread(open); + } else { + g_OSD.SetClickCallback("framedump", open); + } +} + void SaveFrameDump() { if (!gpu) { return; } gpu->GetRecorder()->RecordNextFrame([](const Path &dumpPath) { - NOTICE_LOG(Log::System, "Frame dump created at '%s'", dumpPath.c_str()); - if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { - System_ShowFileInFolder(dumpPath); - } else { - g_OSD.Show(OSDType::MESSAGE_SUCCESS, GetFriendlyPath(dumpPath), 7.0f); - } + OnFrameDumpCreated(dumpPath, true); }); } diff --git a/UI/DevScreens.h b/UI/DevScreens.h index fc4f2fae61..9240e12c04 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -213,3 +213,7 @@ public: void DrawProfile(UIContext &ui); void AddOverlayList(UI::ViewGroup *items, ScreenManager *screenManager); + +// Tells the user where the dump went. With openNow, shows it in its folder (or shares it, where +// folders can't be shown) right away, otherwise when the message is clicked. Callable from any thread. +void OnFrameDumpCreated(const Path &dumpPath, bool openNow); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 075d1bdfd1..35c0368260 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -771,14 +771,9 @@ void EmuScreen::ProcessVKey(VirtKey virtKey, bool down) { if (!gpu) { return; } + // Not opened right away, which would interrupt the game. gpu->GetRecorder()->RecordNextFrame([](const Path &dumpPath) { - NOTICE_LOG(Log::System, "Frame dump created at '%s'", dumpPath.c_str()); - g_OSD.Show(OSDType::MESSAGE_SUCCESS, GetFriendlyPath(dumpPath), 7.0f, "framedump"); - if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { - g_OSD.SetClickCallback("framedump", [dumpPath]() { - System_ShowFileInFolder(dumpPath); - }); - } + OnFrameDumpCreated(dumpPath, false); }); } break; From 95e7f2d6866b1928bab72e8bf0cbebe1757cd3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 11:25:46 +0100 Subject: [PATCH 04/10] 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 --- UI/SavedataScreen.cpp | 51 ++++++++++++++++++++++++------------------- UI/SavedataScreen.h | 5 +---- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 9e76cfda43..ee5fe99b32 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -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 FindGameSaves(const Path &saveDir, std::string_view gameID) { + std::vector saves; + std::vector 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 &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 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 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 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 ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), Path(e.s), GameInfoFlags::PARAM_SFO); diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index 6f777077dd..aeef0ee4d1 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -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 gameSaveDirs_; // The game's saves, without installed data. bool gameLookupPending_ = false; SavedataSortOption sortOption_ = SavedataSortOption::FILENAME; SavedataBrowser *dataBrowser_ = nullptr; From ec36fad065fd244a9871f2ea42dbba187ef238fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 11:30:43 +0100 Subject: [PATCH 05/10] Savedata manager: Make the highlight outline show up An outline with a zero radius isn't drawn at all, so use square corners instead. Medium width, so it doesn't look like the gaps between rows. Co-Authored-By: Claude Opus 5.5 --- UI/SavedataScreen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index ee5fe99b32..d043a10bb0 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -491,7 +491,8 @@ void SavedataButton::Draw(UIContext &dc) { dc.RebindTexture(); if (highlighted_) { - dc.Draw()->DrawRoundRect(bounds_, 0.0f, 0xFFFFFFFF, RoundRectStyle::OutlineThin); + // Square corners. (A zero radius draws no outline at all.) Medium, since a thin line looks like the gaps between the rows. + dc.Draw()->DrawRoundRect(bounds_, ROUND_RECT_RADII[0], 0xFFFFFFFF, RoundRectStyle::OutlineMedium, RoundCorners::None); dc.Draw()->Flush(); } } From 2fae0dadba5ae8f342880cd7d4319376c1c66f85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 11:53:49 +0100 Subject: [PATCH 06/10] Game screen: Export all of a game's saves from its menu Exporting a single save stays in the savedata manager's popup. Exporting a whole game's saves is rarer, so it moves to the game's menu. Co-Authored-By: Claude Opus 5.5 --- UI/GameScreen.cpp | 7 +++++++ UI/SavedataScreen.cpp | 39 +++++++++++---------------------------- UI/SavedataScreen.h | 3 +++ 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 224a5d8c78..2874e755ca 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -61,6 +61,7 @@ #include "UI/MainScreen.h" #include "UI/BackgroundAudio.h" #include "UI/SavedataScreen.h" +#include "UI/ShareFiles.h" #include "UI/MiscViews.h" // This is in an objective-C file. @@ -722,6 +723,12 @@ void GameScreen::CreateContextMenu(UI::ViewGroup *parent) { screenManager()->push(new SavedataScreen(gamePath_)); }); } + if (info_->saveDataSize && CanShareFiles()) { + auto sa = GetI18NCategory(I18NCat::SAVEDATA); + parent->Add(new Choice(sa->T("Export all saves for this game"), ImageID("I_SHARE")))->OnClick.Add([this](UI::EventParams &e) { + ExportGameSaves(info_.get()); + }); + } if (info_->saveDataSize) { Choice *btnDeleteSaveData = new Choice(ga->T("Delete Save Data"), ImageID("I_TRASHCAN")); parent->Add(btnDeleteSaveData)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData); diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index d043a10bb0..54e39d0165 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -67,19 +67,6 @@ 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 FindGameSaves(const Path &saveDir, std::string_view gameID) { - std::vector saves; - std::vector 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 &entries) { if (!ShareFilesAsZip(zipName, entries)) { auto sa = GetI18NCategory(I18NCat::SAVEDATA); @@ -87,6 +74,17 @@ static void ExportAsZip(std::string_view zipName, const std::vector entries; + for (const Path &dir : info->GetSaveDataDirectories()) { + // Installed data is large and not a save, so it's left out. + if (File::Exists(dir / "PARAM.SFO") && !IsInstalledGameData(dir)) { + AddFolderToZip(dir, dir.GetFilename(), &entries); + } + } + ExportAsZip(info->GetTitle() + " (" + info->id + ")", entries); +} + 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(ORIENT_VERTICAL, layoutParams) { @@ -220,21 +218,6 @@ 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 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 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); diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index aeef0ee4d1..f706c3114f 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -185,3 +185,6 @@ private: UI::TextView *mTime_ = nullptr; UI::TextView *fileSize_ = nullptr; }; + +// Zips a game's saves (without installed data) and shares the zip. +void ExportGameSaves(GameInfo *info); From 9cc2d2d378e6bffaa77437c21642693c05891bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 12:14:29 +0100 Subject: [PATCH 07/10] Reorder the buttons on the SavedataScreen Popup --- UI/GameScreen.cpp | 2 +- UI/SavedataScreen.cpp | 55 +++++++++++++++++++++++-------------------- UI/SavedataScreen.h | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 2874e755ca..1e18b7d784 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -726,7 +726,7 @@ void GameScreen::CreateContextMenu(UI::ViewGroup *parent) { if (info_->saveDataSize && CanShareFiles()) { auto sa = GetI18NCategory(I18NCat::SAVEDATA); parent->Add(new Choice(sa->T("Export all saves for this game"), ImageID("I_SHARE")))->OnClick.Add([this](UI::EventParams &e) { - ExportGameSaves(info_.get()); + ExportAllGameSavesForGame(info_.get()); }); } if (info_->saveDataSize) { diff --git a/UI/SavedataScreen.cpp b/UI/SavedataScreen.cpp index 54e39d0165..a880a7e544 100644 --- a/UI/SavedataScreen.cpp +++ b/UI/SavedataScreen.cpp @@ -74,7 +74,7 @@ static void ExportAsZip(std::string_view zipName, const std::vector entries; for (const Path &dir : info->GetSaveDataDirectories()) { // Installed data is large and not a save, so it's left out. @@ -218,31 +218,12 @@ void SavedataPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { } } - LinearLayout *buttonRow = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); - buttonRow->SetSpacing(12); + LinearLayout *buttonRow1 = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + LinearLayout *buttonRow2 = parent->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + buttonRow1->SetSpacing(12); - buttonRow->Add(new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams(1.0f)))->OnClick.Handle(this, &UIScreen::OnBack); - buttonRow->Add(new Choice(di->T("Delete"), ImageID("I_TRASHCAN"), new LinearLayoutParams(1.0f)))->OnClick.Add([this](UI::EventParams &e) { - auto di = GetI18NCategory(I18NCat::DIALOG); - std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, savePath_, GameInfoFlags::PARAM_SFO); - - const bool trashAvailable = System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN); - - std::string_view confirmMessage = di->T("Are you sure you want to delete the file?"); - screenManager()->push(new MessagePopupScreen(di->T("Delete"), confirmMessage, trashAvailable ? di->T("Move to trash") : di->T("Delete"), di->T("Cancel"), [=](bool result) { - if (result) { - ginfo->Delete(); - TriggerFinish(DR_NO); - } - })); - }); - if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { - buttonRow->Add(new Choice(di->T("Show in folder"), ImageID("I_FOLDER"), new LinearLayoutParams(1.0f)))->OnClick.Add([this](UI::EventParams &e) { - System_ShowFileInFolder(savePath_); - }); - } if (canExport) { - buttonRow->Add(new Choice(sa->T("Export"), ImageID("I_SHARE"), new LinearLayoutParams(1.0f)))->OnClick.Add([this, isState](UI::EventParams &e) { + buttonRow1->Add(new Choice(sa->T("Export"), ImageID("I_SHARE"), new LinearLayoutParams(1.0f)))->OnClick.Add([this, isState](UI::EventParams &e) { std::vector entries; if (isState) { // The state and its screenshot, in the root of the zip, which the import takes as savestates. @@ -260,8 +241,30 @@ void SavedataPopupScreen::CreatePopupContents(UI::ViewGroup *parent) { } }); } - buttonRow->SetRoundCorners(); - parent->Add(buttonRow); + + buttonRow1->Add(new Choice(di->T("Delete"), ImageID("I_TRASHCAN"), new LinearLayoutParams(1.0f)))->OnClick.Add([this](UI::EventParams &e) { + auto di = GetI18NCategory(I18NCat::DIALOG); + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(nullptr, savePath_, GameInfoFlags::PARAM_SFO); + + const bool trashAvailable = System_GetPropertyBool(SYSPROP_HAS_TRASH_BIN); + + std::string_view confirmMessage = di->T("Are you sure you want to delete the file?"); + screenManager()->push(new MessagePopupScreen(di->T("Delete"), confirmMessage, trashAvailable ? di->T("Move to trash") : di->T("Delete"), di->T("Cancel"), [=](bool result) { + if (result) { + ginfo->Delete(); + TriggerFinish(DR_NO); + } + })); + }); + + buttonRow2->Add(new Choice(di->T("Back"), ImageID("I_NAVIGATE_BACK"), new LinearLayoutParams(1.0f)))->OnClick.Handle(this, &UIScreen::OnBack); + if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { + buttonRow2->Add(new Choice(di->T("Show in folder"), ImageID("I_FOLDER"), new LinearLayoutParams(1.0f)))->OnClick.Add([this](UI::EventParams &e) { + System_ShowFileInFolder(savePath_); + }); + } + buttonRow1->SetRoundCorners(); + buttonRow2->SetRoundCorners(); } class SortedLinearLayout : public UI::LinearLayoutList { diff --git a/UI/SavedataScreen.h b/UI/SavedataScreen.h index f706c3114f..f632ac07cc 100644 --- a/UI/SavedataScreen.h +++ b/UI/SavedataScreen.h @@ -187,4 +187,4 @@ private: }; // Zips a game's saves (without installed data) and shares the zip. -void ExportGameSaves(GameInfo *info); +void ExportAllGameSavesForGame(GameInfo *info); From ad58640cdb01e18e242d3b2ae0b5349482ab5cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 12:43:03 +0100 Subject: [PATCH 08/10] Frame dumps: Offer Share, Save or Continue where folders can't be shown On Android, Save picks a place with the system's save picker, through a new BROWSE_FOR_FILE_SAVE implementation (ACTION_CREATE_DOCUMENT). Co-Authored-By: Claude Opus 5.5 --- UI/DevScreens.cpp | 93 ++++++++++++++++++- android/jni/app-android.cpp | 4 + .../ppsspp/DocumentResultProxyActivity.java | 4 +- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 24 +++++ 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index e0e45a7425..7a5c8b7cba 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -33,6 +33,7 @@ #include "Common/Audio/AudioBackend.h" #include "Common/File/AndroidStorage.h" +#include "Common/File/DirListing.h" #include "Common/Data/Text/I18n.h" #include "Common/Data/Text/Parsers.h" #include "Common/Data/Encoding/Utf8.h" @@ -134,13 +135,101 @@ static void ShareFrameDump(const Path &dumpPath) { } } +static bool CopyFileContents(const Path &src, const Path &dest) { + FILE *in = File::OpenCFile(src, "rb"); + if (!in) { + return false; + } + // Content URIs can be written but not created this way, so this works on a document the picker made. + FILE *out = File::OpenCFile(dest, "wb"); + if (!out) { + fclose(in); + return false; + } + bool success = true; + char buffer[65536]; + size_t count; + while ((count = fread(buffer, 1, sizeof(buffer), in)) > 0) { + if (fwrite(buffer, 1, count, out) != count) { + success = false; + break; + } + } + if (ferror(in)) { + success = false; + } + fclose(in); + if (fclose(out) != 0) { + success = false; + } + return success; +} + +static bool CanSaveFileAs() { +#if PPSSPP_PLATFORM(ANDROID) + return System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER); +#else + return false; +#endif +} + +// Where a dump can't be shown in its folder: share it, save it somewhere the user picks, or neither. +class FrameDumpPopupScreen : public UI::PopupScreen { +public: + FrameDumpPopupScreen(const Path &dumpPath) : PopupScreen(dumpPath.GetFilename()), dumpPath_(dumpPath) {} + const char *tag() const override { return "FrameDumpPopup"; } + +protected: + bool ShowButtons() const override { return false; } + void CreatePopupContents(UI::ViewGroup *parent) override { + using namespace UI; + auto di = GetI18NCategory(I18NCat::DIALOG); + auto pa = GetI18NCategory(I18NCat::PAUSE); + if (CanShareFiles()) { + parent->Add(new Choice(di->T("Share"), ImageID("I_SHARE")))->OnClick.Add([this](UI::EventParams &e) { + ShareFrameDump(dumpPath_); + TriggerFinish(DR_OK); + }); + } + if (CanSaveFileAs()) { + parent->Add(new Choice(di->T("Save"), ImageID("I_FILE_SAVE")))->OnClick.Add([this, di](UI::EventParams &e) { + // Stays open until the picker answers, since the answer is dropped once the screen is gone. + System_BrowseForFileSave(GetRequesterToken(), di->T("Save"), dumpPath_.GetFilename(), BrowseFileType::ANY, [this](std::string_view value, int) { + auto sy = GetI18NCategory(I18NCat::SYSTEM); + const Path dest(value); + if (CopyFileContents(dumpPath_, dest)) { + // A picked document's URI ends in an ID, so ask for its name. + File::FileInfo info; + std::string name = dumpPath_.GetFilename(); + if (File::GetFileInfo(dest, &info) && !info.name.empty()) { + name = info.name; + } + g_OSD.Show(OSDType::MESSAGE_SUCCESS, name); + } else { + g_OSD.Show(OSDType::MESSAGE_ERROR, sy->T("Failed")); + } + TriggerFinish(DR_OK); + }); + }); + } + parent->Add(new Choice(pa->T("Continue")))->OnClick.Add([this](UI::EventParams &e) { + TriggerFinish(DR_CANCEL); + }); + } + +private: + Path dumpPath_; +}; + +extern ScreenManager *g_screenManager; + void OnFrameDumpCreated(const Path &dumpPath, bool openNow) { NOTICE_LOG(Log::System, "Frame dump created at '%s'", dumpPath.c_str()); std::function open; if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) { open = [dumpPath]() { System_ShowFileInFolder(dumpPath); }; - } else if (CanShareFiles()) { - open = [dumpPath]() { ShareFrameDump(dumpPath); }; + } else if (CanShareFiles() || CanSaveFileAs()) { + open = [dumpPath]() { g_screenManager->push(new FrameDumpPopupScreen(dumpPath)); }; } g_OSD.Show(OSDType::MESSAGE_SUCCESS, GetFriendlyPath(dumpPath), 7.0f, "framedump"); if (!open) { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index cd646f9f1e..08ea4b63f9 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -1197,6 +1197,10 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string } return true; } + case SystemRequestType::BROWSE_FOR_FILE_SAVE: + // The type isn't passed on, the picker keeps the suggested name's extension. + PushCommand("browse_file_save", StringFromFormat("%d:@:%s", requestId, param2.c_str()), requestId); + return true; case SystemRequestType::BROWSE_FOR_FOLDER: PushCommand("browse_folder", StringFromFormat("%d", requestId), requestId); return true; diff --git a/android/src/org/ppsspp/ppsspp/DocumentResultProxyActivity.java b/android/src/org/ppsspp/ppsspp/DocumentResultProxyActivity.java index 7ac345ff13..25d8b41be4 100644 --- a/android/src/org/ppsspp/ppsspp/DocumentResultProxyActivity.java +++ b/android/src/org/ppsspp/ppsspp/DocumentResultProxyActivity.java @@ -53,8 +53,10 @@ public class DocumentResultProxyActivity extends AppCompatActivity { if (uri != null) { Log.i(TAG, "DocumentResultProxy: Selected URI: " + uri); final boolean isTree = pickerIntent != null && Intent.ACTION_OPEN_DOCUMENT_TREE.equals(pickerIntent.getAction()); + // A created document gets written to after the picker is gone. + final boolean isCreate = pickerIntent != null && Intent.ACTION_CREATE_DOCUMENT.equals(pickerIntent.getAction()); try { - if (isTree) { + if (isTree || isCreate) { getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } else { getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 682bddf66d..10a6cecb54 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -1808,6 +1808,30 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList } intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + Intent proxy = new Intent(this, DocumentResultProxyActivity.class); + proxy.putExtra("picker_intent", intent); + proxy.putExtra("request_id", requestId); + startActivity(proxy); + return true; + } catch (Exception e) { + NativeApp.reportException(e, params); + Log.e(TAG, e.toString()); + return false; + } + } else if (command.equals("browse_file_save")) { + try { + // requestId:@:suggested filename + String[] param = params.split(":@:", 2); + int requestId = Integer.parseInt(param[0]); + Log.i(TAG, "browse_file_save request ID: " + requestId); + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + // The generic type keeps the extension of the suggested name. + intent.setType("application/octet-stream"); + if (param.length > 1) { + intent.putExtra(Intent.EXTRA_TITLE, param[1]); + } + Intent proxy = new Intent(this, DocumentResultProxyActivity.class); proxy.putExtra("picker_intent", intent); proxy.putExtra("request_id", requestId); From 9bd1d5990266db75793690d3a47fda1ceb675342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 12:44:09 +0100 Subject: [PATCH 09/10] Reorder --- UI/DevScreens.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 7a5c8b7cba..5e11027ec5 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -272,12 +272,18 @@ void DevMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { screenManager()->push(new LogConfigScreen()); }); - items->Add(new Choice(dev->T("Debugger")))->OnClick.Add([](UI::EventParams &e) { + if (PSP_CoreParameter().fileType != IdentifiedFileType::PPSSPP_GE_DUMP) { + items->Add(new Choice(dev->T("Create frame dump")))->OnClick.Add([](UI::EventParams &e) { + SaveFrameDump(); + }); + } + + items->Add(new Choice(dev->T("Debugger"), ImageID("I_DEBUGGER")))->OnClick.Add([](UI::EventParams &e) { g_Config.bShowImDebugger = !g_Config.bShowImDebugger; }); if (WebServerRunning(WebServerFlags::DEBUGGER)) { - items->Add(new Choice(dev->T("Remote debugger")))->OnClick.Add([](UI::EventParams &e) { + items->Add(new Choice(dev->T("Remote debugger"), ImageID("I_NETWORK")))->OnClick.Add([](UI::EventParams &e) { int port = g_Config.iRemoteISOPort; // Also used for serving a local remote debugger. if (g_Config.bRemoteDebuggerLocal) { // TODO: Need to modify this URL to add /cpu when we upgrade to the latest version of the web debugger. @@ -330,12 +336,6 @@ void DevMenuScreen::CreatePopupContents(UI::ViewGroup *parent) { screenManager()->push(new GPIGPOScreen(dev->T("GPI/GPO switches/LEDs"))); }); - if (PSP_CoreParameter().fileType != IdentifiedFileType::PPSSPP_GE_DUMP) { - items->Add(new Choice(dev->T("Create frame dump")))->OnClick.Add([](UI::EventParams &e) { - SaveFrameDump(); - }); - } - // This one is not very useful these days, and only really on desktop. Hide it on other platforms. if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) { items->Add(new Choice(dev->T("Dump next frame to log")))->OnClick.Add([](UI::EventParams &e) { From 78c7ce36b164cf2d679845d590e1cfe799bbd894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 11 Nov 2026 12:45:04 +0100 Subject: [PATCH 10/10] Translate "Share" Co-Authored-By: Claude Opus 5.5 --- assets/lang/ar_AE.ini | 1 + assets/lang/az_AZ.ini | 1 + assets/lang/be_BY.ini | 1 + assets/lang/bg_BG.ini | 1 + assets/lang/ca_ES.ini | 1 + assets/lang/cz_CZ.ini | 1 + assets/lang/da_DK.ini | 1 + assets/lang/de_DE.ini | 1 + assets/lang/dr_ID.ini | 1 + assets/lang/en_US.ini | 1 + assets/lang/es_ES.ini | 1 + assets/lang/es_LA.ini | 1 + assets/lang/fa_IR.ini | 1 + assets/lang/fi_FI.ini | 1 + assets/lang/fr_FR.ini | 1 + assets/lang/gl_ES.ini | 1 + assets/lang/gr_EL.ini | 1 + assets/lang/he_IL.ini | 1 + assets/lang/hr_HR.ini | 1 + assets/lang/hu_HU.ini | 1 + assets/lang/id_ID.ini | 1 + assets/lang/it_IT.ini | 1 + assets/lang/ja_JP.ini | 1 + assets/lang/jv_ID.ini | 1 + assets/lang/km_KH.ini | 1 + assets/lang/ko_KR.ini | 1 + assets/lang/ku_SO.ini | 1 + assets/lang/lo_LA.ini | 1 + assets/lang/lt-LT.ini | 1 + assets/lang/ms_MY.ini | 1 + assets/lang/nl_NL.ini | 1 + assets/lang/nn_NO.ini | 1 + assets/lang/no_NO.ini | 1 + assets/lang/pl_PL.ini | 1 + assets/lang/pt_BR.ini | 1 + assets/lang/pt_PT.ini | 1 + assets/lang/ro_RO.ini | 1 + assets/lang/ru_RU.ini | 1 + assets/lang/sv_SE.ini | 1 + assets/lang/tg_PH.ini | 1 + assets/lang/th_TH.ini | 1 + assets/lang/tr_TR.ini | 1 + assets/lang/uk_UA.ini | 1 + assets/lang/vi_VN.ini | 1 + assets/lang/zh_CN.ini | 1 + assets/lang/zh_TW.ini | 1 + 46 files changed, 46 insertions(+) diff --git a/assets/lang/ar_AE.ini b/assets/lang/ar_AE.ini index 797929336c..ec97aafaef 100644 --- a/assets/lang/ar_AE.ini +++ b/assets/lang/ar_AE.ini @@ -596,6 +596,7 @@ Search = البحث seconds, 0:off = ثوانٍ، 0 = مُغلق # AI translated Select = حدد Settings = الإعدادات # AI translated +Share = مشاركة # AI translated Shift = شفت Show in folder = أظهر في المجلد Sign In = تسجيل الدخول # AI translated diff --git a/assets/lang/az_AZ.ini b/assets/lang/az_AZ.ini index ca9ee089ae..19bc8f99e7 100644 --- a/assets/lang/az_AZ.ini +++ b/assets/lang/az_AZ.ini @@ -588,6 +588,7 @@ Search = Axtar seconds, 0:off = saniyə, 0 = bağlı Select = Seç Settings = Quruluşlar +Share = Paylaş # AI translated Shift = Shift # same as English Show in folder = Qovluqda göstər Sign In = Daxil ol # AI translated diff --git a/assets/lang/be_BY.ini b/assets/lang/be_BY.ini index 26bdbb1786..e32f7d368f 100644 --- a/assets/lang/be_BY.ini +++ b/assets/lang/be_BY.ini @@ -588,6 +588,7 @@ Search = Пошук seconds, 0:off = секунд, 0 = выкл. # AI translated Select = Select # same as English Settings = Налады +Share = Падзяліцца # AI translated Shift = Shift # same as English Show in folder = Паказаць у тэчцы Sign In = Увайсці # AI translated diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index 06cf68a459..45f593cbf0 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -588,6 +588,7 @@ Search = Търсене seconds, 0:off = секунди, 0 = изкл. # AI translated Select = Изберете Settings = Настройки +Share = Сподели # AI translated Shift = Shift # same as English Show in folder = Покажи в папка Sign In = Вход # AI translated diff --git a/assets/lang/ca_ES.ini b/assets/lang/ca_ES.ini index 6fe196eedf..54361f0d69 100644 --- a/assets/lang/ca_ES.ini +++ b/assets/lang/ca_ES.ini @@ -588,6 +588,7 @@ Search = Cercar seconds, 0:off = segons, 0 = off Select = Seleccionar Settings = Configuració # AI translated +Share = Comparteix # AI translated Shift = Canviar Show in folder = Mostrar en carpeta Sign In = Inicia la sessió # AI translated diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index 280e6dbd28..114a6a38c8 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -588,6 +588,7 @@ Search = Hledat # AI translated seconds, 0:off = sekundy, 0 = vypnuto Select = Vybrat Settings = Nastavení # AI translated +Share = Sdílet # AI translated Shift = Shift # same as English Show in folder = Zobrazit ve složce Sign In = Přihlásit se # AI translated diff --git a/assets/lang/da_DK.ini b/assets/lang/da_DK.ini index 2fe70e9253..c11526c29c 100644 --- a/assets/lang/da_DK.ini +++ b/assets/lang/da_DK.ini @@ -588,6 +588,7 @@ Search = Søg # AI translated seconds, 0:off = sekunder, 0 = off Select = Vælg Settings = Indstillinger # AI translated +Share = Del # AI translated Shift = Shift # same as English Show in folder = Vis i katalog Sign In = Log ind # AI translated diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index 6540266245..ed4eaabf00 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -587,6 +587,7 @@ Search = Suche seconds, 0:off = Sekunden, 0 = Aus Select = Auswählen Settings = Einstellungen +Share = Teilen # AI translated Shift = Umschalt Show in folder = Im Ordner anzeigen Sign In = Anmelden # AI translated diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index 1c216408fc..aa6d112efb 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -588,6 +588,7 @@ Search = Search seconds, 0:off = seconds, 0 = off Select = Pillei Settings = Settings +Share = Share Shift = Angka'i Show in folder = Show in folder Sign In = Sign In diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 464de22878..f3a446bce6 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -612,6 +612,7 @@ Search = Search seconds, 0:off = seconds, 0 = off Select = Select Settings = Settings +Share = Share Shift = Shift Show in folder = Show in folder Sign In = Sign In diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index b430da7d20..4ea1d58134 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -589,6 +589,7 @@ Search = Buscar seconds, 0:off = segundos, 0 = no Select = Seleccionar Settings = Ajustes +Share = Compartir # AI translated Shift = Alternar Show in folder = Mostrar carpeta Sign In = Iniciar sesión # AI translated diff --git a/assets/lang/es_LA.ini b/assets/lang/es_LA.ini index 1715fda4c0..0d97ab8463 100644 --- a/assets/lang/es_LA.ini +++ b/assets/lang/es_LA.ini @@ -588,6 +588,7 @@ Search = Buscar seconds, 0:off = segundos, 0 = no # AI translated Select = Seleccionar Settings = Ajustes +Share = Compartir # AI translated Shift = Alternar Show in folder = Mostrar en carpeta Sign In = Iniciar sesión # AI translated diff --git a/assets/lang/fa_IR.ini b/assets/lang/fa_IR.ini index 5f1acec299..f7c7a171c3 100644 --- a/assets/lang/fa_IR.ini +++ b/assets/lang/fa_IR.ini @@ -588,6 +588,7 @@ Search = جستجو seconds, 0:off = ثانیه، 0 = خاموش # AI translated Select = انتخاب Settings = تنظیمات +Share = اشتراک‌گذاری # AI translated Shift = تغییر Show in folder = نمایش پوشه Sign In = ورود # AI translated diff --git a/assets/lang/fi_FI.ini b/assets/lang/fi_FI.ini index 4529d5767a..c7e8d44b83 100644 --- a/assets/lang/fi_FI.ini +++ b/assets/lang/fi_FI.ini @@ -588,6 +588,7 @@ Search = Etsi seconds, 0:off = sekunteissa, 0 = pois Select = Valitse Settings = Asetukset +Share = Jaa # AI translated Shift = Shift # same as English Show in folder = Näytä kansiossa Sign In = Kirjaudu sisään # AI translated diff --git a/assets/lang/fr_FR.ini b/assets/lang/fr_FR.ini index 781e4c69d4..218042a899 100644 --- a/assets/lang/fr_FR.ini +++ b/assets/lang/fr_FR.ini @@ -612,6 +612,7 @@ Search = Rechercher seconds, 0:off = secondes, 0 = off Select = Sélectionner Settings = Paramètres # AI translated +Share = Partager # AI translated Shift = Majuscule Show in folder = Montrer dans dossier Sign In = Connexion # AI translated diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index a6fdbce9b1..2139e5da5d 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -588,6 +588,7 @@ Search = Buscar # AI translated seconds, 0:off = segundos, 0 = desactivado # AI translated Select = Seleccionar Settings = Axustes # AI translated +Share = Compartir # AI translated Shift = Maiúsculas # AI translated Show in folder = Mostrar no cartafol # AI translated Sign In = Iniciar sesión # AI translated diff --git a/assets/lang/gr_EL.ini b/assets/lang/gr_EL.ini index 354e031605..60ac5b9a68 100644 --- a/assets/lang/gr_EL.ini +++ b/assets/lang/gr_EL.ini @@ -588,6 +588,7 @@ Search = Αναζήτηση # AI translated seconds, 0:off = δευτερόλεπτα, 0 = off Select = Επιλογή # AI translated Settings = Ρυθμίσεις # AI translated +Share = Κοινοποίηση # AI translated Shift = Shift # same as English Show in folder = Εμφάνιση σε Φάκελο Sign In = Σύνδεση # AI translated diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 1d8e7ad972..acfdf58bf1 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -588,6 +588,7 @@ Search = חיפוש # AI translated seconds, 0:off = שניות, 0 = כבוי # AI translated Select = בחר Settings = הגדרות # AI translated +Share = שתף # AI translated Shift = העבר Show in folder = הצג בתיקייה # AI translated Sign In = התחברות # AI translated diff --git a/assets/lang/hr_HR.ini b/assets/lang/hr_HR.ini index b1cbaca938..f537a393ad 100644 --- a/assets/lang/hr_HR.ini +++ b/assets/lang/hr_HR.ini @@ -588,6 +588,7 @@ Search = Pretraži # AI translated seconds, 0:off = sekundi, 0 = isključeno Select = Odaberi # AI translated Settings = Postavke # AI translated +Share = Dijeli # AI translated Shift = Shift # same as English Show in folder = Prikaži u mapi Sign In = Prijava # AI translated diff --git a/assets/lang/hu_HU.ini b/assets/lang/hu_HU.ini index 5ea50822ee..6c0927a3de 100644 --- a/assets/lang/hu_HU.ini +++ b/assets/lang/hu_HU.ini @@ -588,6 +588,7 @@ Search = Keresés seconds, 0:off = másodpercig, 0 = ki Select = Kiválaszt Settings = Beállítások +Share = Megosztás # AI translated Shift = Shift # same as English Show in folder = Mutatás mappában Sign In = Bejelentkezés # AI translated diff --git a/assets/lang/id_ID.ini b/assets/lang/id_ID.ini index 41c1f8be50..76e29ef357 100644 --- a/assets/lang/id_ID.ini +++ b/assets/lang/id_ID.ini @@ -588,6 +588,7 @@ Search = Cari seconds, 0:off = detik, 0 = mati Select = Pilih Settings = Pengaturan +Share = Bagikan # AI translated Shift = Geser Show in folder = Tampilkan di folder # AI translated Sign In = Masuk # AI translated diff --git a/assets/lang/it_IT.ini b/assets/lang/it_IT.ini index 6db3e76dc9..044685a1fa 100644 --- a/assets/lang/it_IT.ini +++ b/assets/lang/it_IT.ini @@ -588,6 +588,7 @@ Search = Cerca seconds, 0:off = secondi, 0 = spento Select = Seleziona Settings = Impostazioni +Share = Condividi # AI translated Shift = Maiuscolo Show in folder = Mostra nella Cartella Sign In = Accedi # AI translated diff --git a/assets/lang/ja_JP.ini b/assets/lang/ja_JP.ini index 2244bde652..1e4910a4c8 100644 --- a/assets/lang/ja_JP.ini +++ b/assets/lang/ja_JP.ini @@ -588,6 +588,7 @@ Search = 検索 seconds, 0:off = 秒, 0 = オフ Select = 選択 Settings = 設定 +Share = 共有 # AI translated Shift = シフト Show in folder = フォルダを表示する Sign In = サインイン # AI translated diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index 92d3172d67..b1d15daf16 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -588,6 +588,7 @@ Search = Search seconds, 0:off = detik, 0 = off Select = Peleh Settings = Settings +Share = Sebar # AI translated Shift = Shift Show in folder = Tampilno Nang Folder Sign In = Sign In diff --git a/assets/lang/km_KH.ini b/assets/lang/km_KH.ini index 504bbe1749..68e356f6cd 100644 --- a/assets/lang/km_KH.ini +++ b/assets/lang/km_KH.ini @@ -588,6 +588,7 @@ Search =ស្វែងរក seconds, 0:off =វិនាទី 0 = បិទ Select =ជ្រើសរើស Settings =ការកំណត់ +Share = ចែករំលែក # AI translated Shift =ប្ដូរ Show in folder =បង្ហាញក្នុងថតឯកសារ Sign In = Sign In diff --git a/assets/lang/ko_KR.ini b/assets/lang/ko_KR.ini index 67dcf8d002..4ebf6a899e 100644 --- a/assets/lang/ko_KR.ini +++ b/assets/lang/ko_KR.ini @@ -588,6 +588,7 @@ Search = 검색 seconds, 0:off = 초, 0 = 끔 Select = 선택 Settings = 설정 +Share = 공유 # AI translated Shift = 전환 Show in folder = 폴더에 표기 Sign In = 로그인 # AI translated diff --git a/assets/lang/ku_SO.ini b/assets/lang/ku_SO.ini index 088d32cb4f..9f8b146895 100644 --- a/assets/lang/ku_SO.ini +++ b/assets/lang/ku_SO.ini @@ -602,6 +602,7 @@ Search = گەڕان seconds, 0:off = چرکەکان، 0 = کوژاوە Select = دەستنیشان کردن Settings = سێتینگەکان +Share = بڵاو بکەرەوە # AI translated Shift = Shift Show in folder = نیشاندانەوە لە فۆڵدەر Sign In = Sign In diff --git a/assets/lang/lo_LA.ini b/assets/lang/lo_LA.ini index 7efbe2f8f6..b5356a204c 100644 --- a/assets/lang/lo_LA.ini +++ b/assets/lang/lo_LA.ini @@ -588,6 +588,7 @@ Search = Search seconds, 0:off = ວິນາທີ, 0 = ປິດ Select = ເລືອກ Settings = Settings +Share = ແບ່ງປັນ # AI translated Shift = ປ່ຽນ Show in folder = ສະແດງໃນໂຟນເດີ້ Sign In = Sign In diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index 7b469246c7..485202db13 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -588,6 +588,7 @@ Search = Ieškoti # AI translated seconds, 0:off = sekundžių, 0 = išjungta # AI translated Select = Pasirinkti Settings = Nustatymai # AI translated +Share = Bendrinti # AI translated Shift = Shift # same as English Show in folder = Rodyti aplanke Sign In = Prisijungti # AI translated diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 46f98f170e..c1bfd14a02 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -588,6 +588,7 @@ Search = Carian seconds, 0:off = saat, 0 = tutup Select = Pilih Settings = Tetapan +Share = Kongsi # AI translated Shift = Shift # AI translated Show in folder = Tunjukkan dalam folder Sign In = Log masuk # AI translated diff --git a/assets/lang/nl_NL.ini b/assets/lang/nl_NL.ini index 308e2cf7dc..50013850cc 100644 --- a/assets/lang/nl_NL.ini +++ b/assets/lang/nl_NL.ini @@ -588,6 +588,7 @@ Search = Zoeken # AI translated seconds, 0:off = seconden, 0 = uit # AI translated Select = Selecteren Settings = Instellingen # AI translated +Share = Delen # AI translated Shift = Shift # same as English Show in folder = Weergeven in map Sign In = Aanmelden # AI translated diff --git a/assets/lang/nn_NO.ini b/assets/lang/nn_NO.ini index 66e15dde03..3fa2849c91 100644 --- a/assets/lang/nn_NO.ini +++ b/assets/lang/nn_NO.ini @@ -612,6 +612,7 @@ Search = Søk seconds, 0:off = sekund, 0 = av Select = Vel Settings = Innstillingar +Share = Del # AI translated Shift = Skift Show in folder = Vis i mappe Sign In = Logg inn # AI translated diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 1eed1a0420..fc59ca2cbf 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -612,6 +612,7 @@ Search = Søk seconds, 0:off = sekunder, 0 = av Select = Velg Settings = Innstillinger +Share = Del # AI translated Shift = Skift Show in folder = Vis i mappe Sign In = Logg inn # AI translated diff --git a/assets/lang/pl_PL.ini b/assets/lang/pl_PL.ini index 2edffcce42..8774b0fe25 100644 --- a/assets/lang/pl_PL.ini +++ b/assets/lang/pl_PL.ini @@ -588,6 +588,7 @@ Search = Szukaj seconds, 0:off = sekundy, 0 = wył. Select = Wybierz Settings = Ustawienia +Share = Udostępnij # AI translated Shift = Shift # same as English Show in folder = Pokaż w folderze Sign In = Zaloguj się # AI translated diff --git a/assets/lang/pt_BR.ini b/assets/lang/pt_BR.ini index dabf07fef7..a60c680241 100644 --- a/assets/lang/pt_BR.ini +++ b/assets/lang/pt_BR.ini @@ -613,6 +613,7 @@ Search = Busca seconds, 0:off = segundos, 0 = desligado Select = Selecionar Settings = Configurações +Share = Compartilhar # AI translated Shift = Shift # same as English Show in folder = Mostrar na pasta Sign In = Entrar # AI translated diff --git a/assets/lang/pt_PT.ini b/assets/lang/pt_PT.ini index 81bc2ebbc6..6c698a3ff8 100644 --- a/assets/lang/pt_PT.ini +++ b/assets/lang/pt_PT.ini @@ -612,6 +612,7 @@ Search = Pesquisar seconds, 0:off = segundos, 0 = desativado Select = Selecionar Settings = Definições +Share = Partilhar # AI translated Shift = Shift # same as English Show in folder = Mostrar na pasta Sign In = Iniciar sessão # AI translated diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index 97cd9ac2e3..c157b7b03b 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -589,6 +589,7 @@ Search = Căutare # AI translated seconds, 0:off = secunde, 0 = off Select = Selectează Settings = Setări # AI translated +Share = Distribuie # AI translated Shift = Shift # same as English Show in folder = Arată în folder Sign In = Conectare # AI translated diff --git a/assets/lang/ru_RU.ini b/assets/lang/ru_RU.ini index 79d694589b..fc9d53eb66 100644 --- a/assets/lang/ru_RU.ini +++ b/assets/lang/ru_RU.ini @@ -588,6 +588,7 @@ Search = Поиск seconds, 0:off = секунд, 0 = выкл. Select = Выбрать Settings = Настройки +Share = Поделиться # AI translated Shift = Shift # AI translated Show in folder = Показать в папке Sign In = Вход # AI translated diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 260fab6e49..841469d50e 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -589,6 +589,7 @@ Search = Sök seconds, 0:off = sekunder, 0 = av Select = Välj Settings = Inställningar +Share = Dela # AI translated Shift = Skift Show in folder = Visa i mapp Sign In = Logga in # AI translated diff --git a/assets/lang/tg_PH.ini b/assets/lang/tg_PH.ini index fdb0d3e581..9c9fb5025d 100644 --- a/assets/lang/tg_PH.ini +++ b/assets/lang/tg_PH.ini @@ -589,6 +589,7 @@ Search = Hanapin seconds, 0:off = segundo, 0 = Nakapatay Select = Piliin Settings = Mga Setting +Share = Ibahagi # AI translated Shift = Shift # same as English Show in folder = Ipakita sa folder Sign In = Mag-sign in # AI translated diff --git a/assets/lang/th_TH.ini b/assets/lang/th_TH.ini index 6e0e985d40..882bd042fb 100644 --- a/assets/lang/th_TH.ini +++ b/assets/lang/th_TH.ini @@ -594,6 +594,7 @@ Search = ค้นหา seconds, 0:off = วินาที, 0 = ปิด Select = เลือก Settings = การตั้งค่า +Share = แชร์ # AI translated Shift = Shift # AI translated Show in folder = แสดงในโฟลเดอร์ Sign In = ลงชื่อเข้าใช้ # AI translated diff --git a/assets/lang/tr_TR.ini b/assets/lang/tr_TR.ini index d647767e9b..d62cd243ca 100644 --- a/assets/lang/tr_TR.ini +++ b/assets/lang/tr_TR.ini @@ -590,6 +590,7 @@ Search = Ara seconds, 0:off = saniye, 0 = kapalı Select = Seç Settings = Ayarlar +Share = Paylaş # AI translated Shift = Shift # same as English Show in folder = Klasörde göster Sign In = Oturum aç # AI translated diff --git a/assets/lang/uk_UA.ini b/assets/lang/uk_UA.ini index e94a3d187d..a2822d7958 100644 --- a/assets/lang/uk_UA.ini +++ b/assets/lang/uk_UA.ini @@ -588,6 +588,7 @@ Search = Пошук seconds, 0:off = секунд, 0 = вимк. Select = Вибрати Settings = Параметри +Share = Поділитися # AI translated Shift = Shift # same as English Show in folder = Показати в теці Sign In = Вхід # AI translated diff --git a/assets/lang/vi_VN.ini b/assets/lang/vi_VN.ini index f0582d8a83..45c25280c7 100644 --- a/assets/lang/vi_VN.ini +++ b/assets/lang/vi_VN.ini @@ -588,6 +588,7 @@ Search = Tìm kiếm # AI translated seconds, 0:off = giây, 0 = tắt Select = Lựa chọn Settings = Cài đặt # AI translated +Share = Chia sẻ # AI translated Shift = Shift # same as English Show in folder = Hiện trong thư mục Sign In = Đăng nhập # AI translated diff --git a/assets/lang/zh_CN.ini b/assets/lang/zh_CN.ini index bea6ee8c32..60e58e7757 100644 --- a/assets/lang/zh_CN.ini +++ b/assets/lang/zh_CN.ini @@ -588,6 +588,7 @@ Search = 搜索 seconds, 0:off = 秒,0=禁用 Select = 选择 Settings = 设置 +Share = 分享 # AI translated Shift = 翻页 Show in folder = 打开文件夹 Sign In = 登录 # AI translated diff --git a/assets/lang/zh_TW.ini b/assets/lang/zh_TW.ini index 1de29fa45a..03a32d3c06 100644 --- a/assets/lang/zh_TW.ini +++ b/assets/lang/zh_TW.ini @@ -588,6 +588,7 @@ Search = 搜尋 seconds, 0:off = 秒,0 = 關閉 Select = 選取 Settings = 設定 +Share = 分享 # AI translated Shift = 移位 Show in folder = 在資料夾中顯示 Sign In = 登入 # AI translated