Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More zip file install fixes #19443

Merged
merged 2 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Common/File/AndroidContentURI.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ class AndroidContentURI {
return root.empty() ? file : root;
}

const std::string &Provider() const {
return provider;
}

bool IsTreeURI() const {
return !root.empty();
}
Expand Down
17 changes: 17 additions & 0 deletions Common/File/FileUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1276,4 +1276,21 @@ void ChangeMTime(const Path &path, time_t mtime) {
#endif
}

bool IsProbablyInDownloadsFolder(const Path &filename) {
INFO_LOG(Log::Common, "IsProbablyInDownloadsFolder: Looking at %s (%s)...", filename.c_str(), filename.ToVisualString().c_str());
switch (filename.Type()) {
case PathType::CONTENT_URI:
{
AndroidContentURI uri(filename.ToString());
INFO_LOG(Log::Common, "Content URI provider: %s", uri.Provider().c_str());
if (containsNoCase(uri.Provider(), "download")) {
// like com.android.providers.downloads.documents
return true;
}
break;
}
}
return filename.FilePathContainsNoCase("download");
}

} // namespace File
4 changes: 4 additions & 0 deletions Common/File/FileUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ bool CreateEmptyFile(const Path &filename);
// TODO: Belongs in System or something.
bool OpenFileInEditor(const Path &fileName);

// Uses some heuristics to determine if this is a folder that we would want to
// write to.
bool IsProbablyInDownloadsFolder(const Path &folder);

// TODO: Belongs in System or something.
const Path &GetExeDirectory();

Expand Down
3 changes: 3 additions & 0 deletions Common/File/Path.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class Path {
PathType Type() const {
return type_;
}
bool IsLocalType() const {
return type_ == PathType::NATIVE || type_ == PathType::CONTENT_URI;
}

bool Valid() const { return !path_.empty(); }
bool IsRoot() const { return path_ == "/"; } // Special value - only path that can end in a slash.
Expand Down
6 changes: 6 additions & 0 deletions Common/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ long parseLong(std::string s) {
return value;
}

bool containsNoCase(std::string_view haystack, std::string_view needle) {
auto pred = [](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); };
auto found = std::search(haystack.begin(), haystack.end(), needle.begin(), needle.end(), pred);
return found != haystack.end();
}

bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list args)
{
int writtenCount = vsnprintf(out, outsize, format, args);
Expand Down
2 changes: 2 additions & 0 deletions Common/StringUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ inline bool equalsNoCase(std::string_view str, std::string_view key) {
return strncasecmp(str.data(), key.data(), key.size()) == 0;
}

bool containsNoCase(std::string_view haystack, std::string_view needle);

void DataToHexString(const uint8_t *data, size_t size, std::string *output);
void DataToHexString(int indent, uint32_t startAddr, const uint8_t* data, size_t size, std::string* output);

Expand Down
77 changes: 40 additions & 37 deletions Core/Util/GameManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,23 +389,41 @@ void GameManager::InstallZipContents(ZipFileTask task) {
// Examine the URL to guess out what we're installing.
// TODO: Bad idea due to Android content api where we don't always get the filename.
if (urlExtension == ".cso" || urlExtension == ".iso" || urlExtension == ".chd") {
// It's a raw ISO or CSO file. We just copy it to the destination.
std::string shortFilename = task.url.GetFilename();
bool success = InstallRawISO(task.fileName, shortFilename, task.deleteAfter);
// It's a raw ISO or CSO file. We just copy it to the destination, which is the
// currently selected directory in the game browser. Note: This might not be a good option!
Path destPath = Path(g_Config.currentDirectory) / task.url.GetFilename();
if (!File::Exists(destPath)) {
// Fall back to the root of the memstick.
destPath = g_Config.memStickDirectory;
}
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);

// TODO: To save disk space, we should probably attempt a move first, if deleteAfter is true.
// TODO: Update the progress bar continuously.
bool success = File::Copy(task.fileName, destPath);

if (!success) {
ERROR_LOG(Log::HLE, "Raw ISO install failed");
// This shouldn't normally happen at all (only when putting ISOs in a store, which is not a normal use case), so skipping the translation string
SetInstallError("Failed to install raw ISO");
}
if (task.deleteAfter) {
File::Delete(task.fileName);
}
g_OSD.RemoveProgressBar("install", success, 0.5f);
installProgress_ = 1.0f;
InstallDone();
return;
}

int error = 0;

struct zip *z = ZipOpenPath(task.fileName);
if (!z) {
g_OSD.RemoveProgressBar("install", false, 0.5f);
g_OSD.RemoveProgressBar("install", false, 1.5f);
SetInstallError(sy->T("Unable to open zip file"));
installProgress_ = 1.0f;
InstallDone();
return;
}

Expand All @@ -424,15 +442,17 @@ void GameManager::InstallZipContents(ZipFileTask task) {
{
Path pspGame = GetSysDirectory(DIRECTORY_GAME);
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), pspGame.c_str());
// InstallZipContents contains code to close (and delete) z.
// InstallZipContents contains code to close z.
success = ExtractZipContents(z, pspGame, zipInfo, false);
break;
}
case ZipFileContents::ISO_FILE:
INFO_LOG(Log::HLE, "Installing '%s' into its containing directory", task.fileName.c_str());
{
INFO_LOG(Log::HLE, "Installing '%s' into '%s'", task.fileName.c_str(), task.destination.c_str());
// InstallZippedISO contains code to close z.
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.fileName, task.deleteAfter);
success = InstallZippedISO(z, zipInfo.isoFileIndex, task.destination);
break;
}
case ZipFileContents::TEXTURE_PACK:
{
// InstallMemstickGame contains code to close z, and works for textures too.
Expand Down Expand Up @@ -468,10 +488,16 @@ void GameManager::InstallZipContents(ZipFileTask task) {
break;
}

// Common functionality.
if (task.deleteAfter && success) {
File::Delete(task.fileName);
}
g_OSD.RemoveProgressBar("install", success, 0.5f);
installProgress_ = 1.0f;
InstallDone();
if (success) {
ResetInstallError();
}
}

bool GameManager::DetectTexturePackDest(struct zip *z, int iniIndex, Path &dest) {
Expand Down Expand Up @@ -765,10 +791,6 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
INFO_LOG(Log::HLE, "Unzipped %d files (%d bytes / %d).", info.numFiles, (int)bytesCopied, (int)allBytes);
zip_close(z);
z = nullptr;
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
g_OSD.RemoveProgressBar("install", true, 0.5f);
return true;

bail:
Expand All @@ -782,7 +804,6 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
File::DeleteDir(iter);
}
SetInstallError(sy->T("Storage full"));
g_OSD.RemoveProgressBar("install", false, 0.5f);
return false;
}

Expand Down Expand Up @@ -842,7 +863,7 @@ bool GameManager::InstallMemstickZip(struct zip *z, const Path &zipfile, const P
return true;
}

bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter) {
bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir) {
// Let's place the output file in the currently selected Games directory.
std::string fn = zip_get_name(z, isoFileIndex, 0);
size_t nameOffset = fn.rfind('/');
Expand All @@ -866,7 +887,12 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
name = name.substr(2);
}

Path outputISOFilename = Path(g_Config.currentDirectory) / name;
Path outputISOFilename = destDir;
if (outputISOFilename.empty()) {
outputISOFilename = Path(g_Config.currentDirectory);
}
outputISOFilename = outputISOFilename / name;

size_t bytesCopied = 0;
bool success = false;
auto di = GetI18NCategory(I18NCat::DIALOG);
Expand All @@ -876,10 +902,6 @@ bool GameManager::InstallZippedISO(struct zip *z, int isoFileIndex, const Path &
success = true;
}
zip_close(z);
if (success && deleteAfter) {
File::Delete(zipfile);
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
}
g_OSD.RemoveProgressBar("install", success, 0.5f);

z = 0;
Expand Down Expand Up @@ -910,25 +932,6 @@ bool GameManager::UninstallGameOnThread(const std::string &name) {
return true;
}

bool GameManager::InstallRawISO(const Path &file, const std::string &originalName, bool deleteAfter) {
Path destPath = Path(g_Config.currentDirectory) / originalName;
auto di = GetI18NCategory(I18NCat::DIALOG);
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
// TODO: To save disk space, we should probably attempt a move first.
if (File::Copy(file, destPath)) {
if (deleteAfter) {
File::Delete(file);
}
g_OSD.RemoveProgressBar("install", true, 0.5f);
} else {
g_OSD.RemoveProgressBar("install", false, 0.5f);
}
installProgress_ = 1.0f;
InstallDone();
ResetInstallError();
return true;
}

void GameManager::ResetInstallError() {
if (!InstallInProgress()) {
installError_.clear();
Expand Down
4 changes: 2 additions & 2 deletions Core/Util/GameManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ struct ZipFileTask {
std::optional<ZipFileInfo> zipFileInfo;
Path url; // Same as filename if installing from disk. Probably not really useful.
Path fileName;
Path destination; // If set, will override the default destination.
bool deleteAfter;
};

Expand Down Expand Up @@ -117,8 +118,7 @@ class GameManager {

bool ExtractZipContents(struct zip *z, const Path &dest, const ZipFileInfo &info, bool allowRoot);
bool InstallMemstickZip(struct zip *z, const Path &zipFile, const Path &dest, const ZipFileInfo &info);
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &zipfile, bool deleteAfter);
bool InstallRawISO(const Path &zipFile, const std::string &originalName, bool deleteAfter);
bool InstallZippedISO(struct zip *z, int isoFileIndex, const Path &destDir);
void UninstallGame(const std::string &name);

void InstallDone();
Expand Down
35 changes: 35 additions & 0 deletions UI/InstallZipScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
#include "Common/UI/ViewGroup.h"

#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Text/Parsers.h"
#include "Core/Config.h"
#include "Core/System.h"
#include "Core/Util/GameManager.h"
#include "Core/Loaders.h"
Expand Down Expand Up @@ -65,6 +67,10 @@ void InstallZipScreen::CreateViews() {
doneView_ = nullptr;
installChoice_ = nullptr;
existingSaveView_ = nullptr;
destFolders_.clear();

std::vector<Path> destOptions;

if (z) {
DetectZipFileContents(z, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents.
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE || zipFileInfo_.contents == ZipFileContents::PSP_GAME_DIR) {
Expand All @@ -78,6 +84,21 @@ void InstallZipScreen::CreateViews() {

doneView_ = leftColumn->Add(new TextView(""));

if (zipFileInfo_.contents == ZipFileContents::ISO_FILE) {
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) {
destFolders_.push_back(g_Config.currentDirectory);
}
destFolders_.push_back(g_Config.memStickDirectory);
} else {
destFolders_.push_back(GetSysDirectory(DIRECTORY_GAME));
}

installChoice_ = rightColumnItems->Add(new Choice(iz->T("Install")));
installChoice_->OnClick.Handle(this, &InstallZipScreen::OnInstall);
returnToHomebrew_ = true;
Expand All @@ -102,6 +123,8 @@ void InstallZipScreen::CreateViews() {
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
bool overwrite = !CanExtractWithoutOverwrite(z, savedataDir, 50);

destFolders_.push_back(savedataDir);

leftColumn->Add(new NoticeView(NoticeLevel::WARN, di->T("Confirm Overwrite"), ""));

int columnWidth = 300;
Expand Down Expand Up @@ -143,6 +166,15 @@ void InstallZipScreen::CreateViews() {
leftColumn->Add(new TextView(er->T("Error reading file"), ALIGN_LEFT, false, new AnchorLayoutParams(10, 10, NONE, NONE)));
}

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, destFolders_[i].ToVisualString()));
}
} else if (destFolders_.size() == 1 && zipFileInfo_.contents != ZipFileContents::SAVE_DATA) {
leftColumn->Add(new TextView(StringFromFormat("%s %s", iz->T_cstr("Install into folder:"), destFolders_[0].ToVisualString().c_str())));
}

// OK so that EmuScreen will handle it right.
backChoice_ = rightColumnItems->Add(new Choice(di->T("Back")));
backChoice_->OnClick.Handle<UIScreen>(this, &UIScreen::OnOK);
Expand All @@ -166,6 +198,9 @@ UI::EventReturn InstallZipScreen::OnInstall(UI::EventParams &params) {
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 (installChoice_) {
Expand Down
4 changes: 4 additions & 0 deletions UI/InstallZipScreen.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include <functional>

#include "Common/File/Path.h"

#include "Common/UI/View.h"
#include "Common/UI/UIScreen.h"

Expand Down Expand Up @@ -46,6 +48,8 @@ class InstallZipScreen : public UIDialogScreenWithBackground {
SavedataView *existingSaveView_ = nullptr;
Path savedataToOverwrite_;
Path zipPath_;
std::vector<Path> destFolders_;
int destFolderChoice_ = 0;
ZipFileInfo zipFileInfo_{};
bool returnToHomebrew_ = true;
bool installStarted_ = false;
Expand Down
1 change: 1 addition & 0 deletions assets/lang/ar_AE.ini
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,7 @@ Delete ZIP file = ‎مسح الملف المضغوط
Existing data = Existing data
Install = ‎تثبيت
Install game from ZIP file? = ‎تثبيت اللعبة من الملف المضغوط ?
Install in folder = Install in folder
Install textures from ZIP file? = Install textures from ZIP file?
Installation failed = فشل في التثبيت
Installed! = ‎مثبت!
Expand Down
1 change: 1 addition & 0 deletions assets/lang/az_AZ.ini
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ Delete ZIP file = Delete ZIP file
Existing data = Existing data
Install = Install
Install game from ZIP file? = Install game from ZIP file?
Install in folder = Install in folder
Install textures from ZIP file? = Install textures from ZIP file?
Installation failed = Installation failed
Installed! = Installed!
Expand Down
1 change: 1 addition & 0 deletions assets/lang/bg_BG.ini
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ Delete ZIP file = Изтрий ZIP архива
Existing data = Existing data
Install = Инсталирай
Install game from ZIP file? = Инсталирай игра от ZIP архив?
Install in folder = Install in folder
Install textures from ZIP file? = Install textures from ZIP file?
Installation failed = Installation failed
Installed! = Инсталирано!
Expand Down
1 change: 1 addition & 0 deletions assets/lang/ca_ES.ini
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ Delete ZIP file = Delete ZIP file
Existing data = Existing data
Install = Install
Install game from ZIP file? = Install game from ZIP file?
Install in folder = Install in folder
Install textures from ZIP file? = Install textures from ZIP file?
Installation failed = Installation failed
Installed! = Installed!
Expand Down
1 change: 1 addition & 0 deletions assets/lang/cz_CZ.ini
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ Delete ZIP file = Smazat soubor ZIP
Existing data = Existing data
Install = Nainstalovat
Install game from ZIP file? = Instalovat hru ze souboru ZIP?
Install in folder = Install in folder
Install textures from ZIP file? = Install textures from ZIP file?
Installation failed = Installation failed
Installed! = Instalováno!
Expand Down
Loading
Loading