Files
ppsspp/UI/InstallZipScreen.cpp
Henrik RydgårdandClaude Opus 5.5 1b60f76e58 Translate error messages once, from keys the source names
GameManager translated install errors when storing them, and the install
screens then looked the translated text up again as a key. It now stores the
category and key, and translates when asked. The raw ISO install error gets
the existing "Installation failed" string.

Some other errors went around as strings that were then used as keys:
- A missing savestate file now has its own error code, ERROR_FILE_MISSING,
  instead of a key in the failure reason.
- The PKG reader's reasons are technical English, so they're now shown
  untranslated under the translated "This PKG file isn't a PSP game update".
- The netconf dialog looked up the channel number. Only "Auto" is translated
  now, using the WLAN Channel setting's string.
- Adds the missing "Failed to read old directory" for memstick moves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 17:04:20 +01:00

401 lines
15 KiB
C++

// Copyright (c) 2013- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Common/UI/UI.h"
#include "Common/UI/View.h"
#include "Common/UI/ViewGroup.h"
#include "Common/UI/Notice.h"
#include "Common/UI/ScreenManager.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h"
#include "Common/Data/Format/IniFile.h"
#include "Common/File/VFS/VFS.h"
#include "Common/File/VFS/SevenZipFileReader.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/Util/GameManager.h"
#include "Core/Util/PathUtil.h"
#include "Core/Util/GameDB.h"
#include "Core/Loaders.h"
#include "UI/InstallZipScreen.h"
#include "UI/MainScreen.h"
#include "UI/OnScreenDisplay.h"
#include "UI/SavedataScreen.h"
#include "UI/EmuScreen.h"
InstallZipScreen::InstallZipScreen(const Path &zipPath) : UITwoPaneBaseDialogScreen(Path(), TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::ContentsCanScroll), zipPath_(zipPath) {
g_GameManager.ResetInstallError();
if (zipPath.GetFileExtension() == ".7z") {
SevenZipFileReader *sevenZipFile = SevenZipFileReader::Create(zipPath, "");
if (sevenZipFile) {
DetectArchiveContents(sevenZipFile, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
delete sevenZipFile;
}
} else {
ZipContainer zipFile = ZipOpenPath(zipPath_);
if (zipFile) {
DetectZipFileContents(zipFile, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
ZipClose(zipFile);
}
}
}
std::string_view InstallZipScreen::GetTitle() const {
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
if (zipFileInfo_.archiveType == ArchiveType::SevenZ) {
return iz->T("7z file");
}
return iz->T("ZIP file");
}
void InstallZipScreen::CreateSettingsViews(UI::ViewGroup *parent) {
using namespace UI;
auto di = GetI18NCategory(I18NCat::DIALOG);
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
auto er = GetI18NCategory(I18NCat::ERRORS);
auto ga = GetI18NCategory(I18NCat::GAME);
std::string shortFilename = zipPath_.GetFilename();
bool showDeleteCheckbox = false;
std::vector<Path> destOptions;
switch (zipFileInfo_.contents) {
case ZipFileContents::ISO_FILE:
case ZipFileContents::PSP_GAME_DIR:
case ZipFileContents::PSX_DISC:
installChoice_ = parent->Add(new Choice(iz->T("Install"), ImageID("I_FOLDER_UPLOAD")));
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
// NOTE: We detect PBP isos (like demos) as game dirs currently. Can't play them directly.
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE && zipFileInfo_.archiveType == ArchiveType::ZIP) {
playChoice_ = parent->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")));
playChoice_->OnClick.Handle(this, &InstallZipScreen::OnPlay);
}
returnToHomebrew_ = true;
showDeleteCheckbox = true;
break;
case ZipFileContents::TEXTURE_PACK:
case ZipFileContents::SAVE_DATA:
case ZipFileContents::SAVE_STATES:
case ZipFileContents::PRX_PLUGIN:
installChoice_ = parent->Add(new Choice(iz->T("Install"), ImageID("I_FOLDER_UPLOAD")));
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
showDeleteCheckbox = true;
break;
case ZipFileContents::FRAME_DUMP:
// It's a frame dump, add a play button!
playChoice_ = parent->Add(new Choice(ga->T("Play"), ImageID("I_PLAY")));
playChoice_->OnClick.Handle(this, &InstallZipScreen::OnPlay);
break;
default:
// Nothing to do!
break;
}
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
parent->Add(new Spacer(12.0f));
parent->Add(new Choice(di->T("Show in folder")))->OnClick.Add([this](UI::EventParams &) {
System_ShowFileInFolder(zipPath_);
});
}
if (showDeleteCheckbox) {
parent->Add(new Spacer(12.0f));
parent->Add(new CheckBox(&deleteZipFile_, iz->T("Delete ZIP file")));
}
}
void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
using namespace UI;
LinearLayout *leftColumn = parent->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(8))));
auto di = GetI18NCategory(I18NCat::DIALOG);
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
auto er = GetI18NCategory(I18NCat::ERRORS);
auto ga = GetI18NCategory(I18NCat::GAME);
std::string shortFilename = zipPath_.GetFilename();
destFolders_.clear();
std::vector<Path> destOptions;
bool overwrite = false;
switch (zipFileInfo_.contents) {
case ZipFileContents::ISO_FILE:
case ZipFileContents::PSP_GAME_DIR:
case ZipFileContents::PSX_DISC:
{
std::string_view question = iz->T("Install game from ZIP file?");
leftColumn->Add(new TextView(question));
leftColumn->Add(new TextView(shortFilename));
if (!zipFileInfo_.contentName.empty()) {
leftColumn->Add(new TextView(zipFileInfo_.contentName));
}
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSX_DISC) {
const bool isInDownloads = File::IsProbablyInDownloadsFolder(zipPath_);
Path parent;
if (!isInDownloads && zipPath_.CanNavigateUp()) {
parent = zipPath_.NavigateUp();
destFolders_.push_back(parent);
}
if (g_Config.currentDirectory.IsLocalType() && File::Exists(g_Config.currentDirectory) && g_Config.currentDirectory != parent) {
if (!Contains(destFolders_, g_Config.currentDirectory)) {
destFolders_.push_back(g_Config.currentDirectory);
}
}
if (!Contains(destFolders_, g_Config.memStickDirectory)) {
destFolders_.push_back(g_Config.memStickDirectory);
}
} else {
destFolders_.push_back(GetSysDirectory(DIRECTORY_GAME));
}
returnToHomebrew_ = true;
break;
}
case ZipFileContents::TEXTURE_PACK:
{
std::string_view question = iz->T("Install textures from ZIP file?");
leftColumn->Add(new TextView(question));
leftColumn->Add(new TextView(shortFilename));
break;
}
case ZipFileContents::PRX_PLUGIN:
{
leftColumn->Add(new TextView(iz->T("Install plugin from ZIP file?")));
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
Path pluginsDir = GetSysDirectory(DIRECTORY_PLUGINS);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, pluginsDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
std::stringstream sstream(zipFileInfo_.iniContents);
IniFile ini;
ini.Load(sstream);
if (Section *games = ini.GetSection("games")) {
leftColumn->Add(new TextView(iz->T("Supported games:")));
for (const auto &line : games->Lines()) {
std::string gameID(line.Key());
std::vector<GameDBInfo> infos;
if (g_gameDB.GetGameInfos(gameID, &infos)) {
for (const auto &info : infos) {
leftColumn->Add(new TextView(info.title + " - " + gameID));
break; // Let's just show the first match for each gameID, it gets messy otherwise.
}
} else {
leftColumn->Add(new TextView(gameID));
}
}
}
break;
}
case ZipFileContents::SAVE_STATES:
{
std::string_view question = iz->T("Import savestates from ZIP file");
leftColumn->Add(new TextView(question))->SetBig(true);
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
leftColumn->Add(new TextView(zipFileInfo_.gameTitle));
Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, savestateDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
destFolders_.push_back(savestateDir);
// TODO: Use the GameInfoCache to display data about the game if available.
break;
}
case ZipFileContents::SAVE_DATA:
{
std::string_view question = iz->T("Import savedata from ZIP file");
leftColumn->Add(new TextView(question))->SetBig(true);
leftColumn->Add(new TextView(zipFileInfo_.gameTitle + ": " + zipFileInfo_.savedataDir));
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, zipFileInfo_.saveDataInRoot ? savedataDir / zipFileInfo_.savedataDir : savedataDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
destFolders_.push_back(savedataDir);
int columnWidth = 300;
LinearLayout *compareColumns = leftColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
LinearLayout *leftCompare = new LinearLayout(ORIENT_VERTICAL);
leftCompare->Add(new TextView(iz->T("Data to import")));
compareColumns->Add(leftCompare);
leftCompare->Add(new SavedataView(*screenManager()->getUIContext(), Path(), IdentifiedFileType::PSP_SAVEDATA_DIRECTORY,
zipFileInfo_.gameTitle, zipFileInfo_.savedataTitle, zipFileInfo_.savedataDetails, NiceSizeFormat(zipFileInfo_.totalFileSize), zipFileInfo_.mTime, false, new LinearLayoutParams(columnWidth, WRAP_CONTENT)));
// Check for potential overwrite at destination, and ask the user if it's OK to overwrite.
if (overwrite) {
savedataToOverwrite_ = savedataDir / zipFileInfo_.savedataDir;
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savedataToOverwrite_, GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
LinearLayout *rightCompare = new LinearLayout(ORIENT_VERTICAL);
rightCompare->Add(new TextView(iz->T("Existing data")));
compareColumns->Add(rightCompare);
existingSaveView_ = rightCompare->Add(new SavedataView(*screenManager()->getUIContext(), ginfo.get(), IdentifiedFileType::PSP_SAVEDATA_DIRECTORY, false, new LinearLayoutParams(columnWidth, WRAP_CONTENT)));
if (System_GetPropertyBool(SYSPROP_CAN_SHOW_FILE)) {
rightCompare->Add(new Button(di->T("Show in folder")))->OnClick.Add([=](UI::EventParams &) {
System_ShowFileInFolder(savedataToOverwrite_);
});
}
}
break;
}
case ZipFileContents::FRAME_DUMP:
// Re-use an almost correct string, not worth it to add a new one
leftColumn->Add(new TextView(di->T("GE Frame Dumps")));
leftColumn->Add(new TextView(zipFileInfo_.contentName));
// It's a frame dump, add a play button!
break;
case ZipFileContents::EXTRACTED_GAME:
// We can do something smarter here later.
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
leftColumn->Add(new TextView(er->T("File format not supported")));
break;
case ZipFileContents::UNKNOWN:
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
leftColumn->Add(new TextView(iz->T("Zip file does not contain PSP software"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
break;
default:
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
leftColumn->Add(new TextView(er->T("The file is not a valid zip file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
break;
}
doneView_ = leftColumn->Add(new NoticeView(NoticeLevel::SUCCESS, "", ""));
doneView_->SetVisibility(Visibility::V_GONE);
if (destFolders_.size() > 1) {
leftColumn->Add(new TextView(iz->T("Install into folder")));
for (int i = 0; i < (int)destFolders_.size(); i++) {
leftColumn->Add(new RadioButton(&destFolderChoice_, i, GetFriendlyPath(destFolders_[i])));
}
} else if (destFolders_.size() == 1 && zipFileInfo_.contents != ZipFileContents::SAVE_DATA) {
leftColumn->Add(new TextView(iz->T("Install into folder")));
leftColumn->Add(new TextView(GetFriendlyPath(destFolders_[0])))->SetAlign(FLAG_WRAP_TEXT);
}
if (overwrite) {
overwriteWarning_ = leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), ""));
}
}
void InstallZipScreen::BeforeCreateViews() {
// The two panes are created in a different order depending on orientation, so neither can reset the other's views.
returnToHomebrew_ = false;
installChoice_ = nullptr;
playChoice_ = nullptr;
doneView_ = nullptr;
existingSaveView_ = nullptr;
overwriteWarning_ = nullptr;
}
bool InstallZipScreen::key(const KeyInput &key) {
// Ignore all key presses during download and installation to avoid user escape
if (g_GameManager.GetState() == GameManagerState::IDLE) {
return UIDialogScreen::key(key);
}
return false;
}
void InstallZipScreen::OnInstall(UI::EventParams &params) {
ZipFileTask task;
task.url = zipPath_;
task.fileName = zipPath_;
task.deleteAfter = deleteZipFile_;
task.zipFileInfo = zipFileInfo_;
if (!destFolders_.empty() && destFolderChoice_ < destFolders_.size()) {
task.destination = destFolders_[destFolderChoice_];
}
if (g_GameManager.InstallZipOnThread(task)) {
installStarted_ = true;
if (playChoice_) {
playChoice_->SetEnabled(false); // need to exit this screen to played the installed one. We could make this smarter.
}
if (installChoice_) {
installChoice_->SetEnabled(false);
}
if (overwriteWarning_) {
overwriteWarning_->SetVisibility(UI::Visibility::V_GONE);
}
}
}
void InstallZipScreen::OnPlay(UI::EventParams &params) {
screenManager()->switchScreen(new EmuScreen(zipPath_));
}
void InstallZipScreen::update() {
auto iz = GetI18NCategory(I18NCat::INSTALLZIP);
using namespace UI;
if (g_GameManager.GetState() != GameManagerState::IDLE) {
if (backChoice_) {
backChoice_->SetEnabled(false);
}
} else {
if (backChoice_) {
backChoice_->SetEnabled(true);
}
std::string err = g_GameManager.GetInstallError();
if (!err.empty()) {
if (doneView_) {
doneView_->SetLevelAndText(NoticeLevel::ERROR, err);
doneView_->SetVisibility(Visibility::V_VISIBLE);
}
} else if (installStarted_) {
if (doneView_) {
doneView_->SetLevelAndText(NoticeLevel::SUCCESS, iz->T("Installed!"));
doneView_->SetVisibility(Visibility::V_VISIBLE);
}
if (playChoice_) {
// Encourage the user to back out and play directly from the main screen,
// instead of unpacking into memory.
playChoice_->SetEnabled(false);
}
MainScreen::showHomebrewTab = returnToHomebrew_;
}
if (zipFileInfo_.contents == ZipFileContents::FRAME_DUMP) {
// For frame dumps, just launch them immediately.
screenManager()->switchScreen(new EmuScreen(zipPath_));
}
}
if (existingSaveView_) {
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(screenManager()->getDrawContext(), savedataToOverwrite_, GameInfoFlags::FILE_TYPE | GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON | GameInfoFlags::SIZE);
existingSaveView_->UpdateGame(ginfo.get());
}
UIBaseDialogScreen::update();
}