Support auto-installing plugins from ZIPs with a deeper directory structure

This commit is contained in:
Henrik Rydgård
2026-04-14 11:55:13 -06:00
parent 00526a9964
commit bfe6debcfd
6 changed files with 49 additions and 36 deletions

View File

@@ -30,6 +30,10 @@ public:
void SetLevel(NoticeLevel level) {
level_ = level;
}
void SetLevelAndText(NoticeLevel level, std::string_view text) {
level_ = level;
text_ = text;
}
void SetSquishy(bool squishy) {
squishy_ = squishy;
}

View File

@@ -509,21 +509,22 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
totalFileSize += stat.size;
std::string fileName(fn);
std::string zippedName = fileName; // actually, lowercase-name
std::transform(zippedName.begin(), zippedName.end(), zippedName.begin(),
[](unsigned char c) { return asciitolower(c); }); // Not using std::tolower to avoid Turkish I->ı conversion.
// Ignore macos metadata stuff
if (startsWith(zippedName, "__macosx/")) {
continue;
}
if (endsWith(zippedName, "/")) {
if (endsWith(fileName, "/")) {
// A directory. Not all zips bother including these.
continue;
}
std::string lowerCaseName = fileName; // actually, lowercase-name
std::transform(lowerCaseName.begin(), lowerCaseName.end(), lowerCaseName.begin(),
[](unsigned char c) { return asciitolower(c); }); // Not using std::tolower to avoid Turkish I->ı conversion.
// Ignore macos metadata stuff
if (startsWith(lowerCaseName, "__macosx/")) {
continue;
}
int prevSlashLocation = -1;
int slashCount = countSlashes(zippedName, &prevSlashLocation);
if (zippedName.find("eboot.pbp") != std::string::npos) {
int slashCount = countSlashes(lowerCaseName, &prevSlashLocation);
if (lowerCaseName.find("eboot.pbp") != std::string::npos) {
if (slashCount >= 1 && (!isPSPMemstickGame || prevSlashLocation < stripChars + 1)) {
stripChars = prevSlashLocation + 1;
isPSPMemstickGame = true;
@@ -531,11 +532,11 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
INFO_LOG(Log::HLE, "Wrong number of slashes (%i) in '%s'", slashCount, fn);
}
// TODO: Extract icon and param.sfo from the pbp to be able to display it on the install screen.
} else if (endsWith(zippedName, ".iso") || endsWith(zippedName, ".cso") || endsWith(zippedName, ".chd")) {
} else if (endsWith(lowerCaseName, ".iso") || endsWith(lowerCaseName, ".cso") || endsWith(lowerCaseName, ".chd")) {
if (slashCount <= 1) {
// We only do this if the ISO file is in the root or one level down.
isZippedISO = true;
INFO_LOG(Log::HLE, "ISO found in zip: %s", zippedName.c_str());
INFO_LOG(Log::HLE, "ISO found in zip: %s", lowerCaseName.c_str());
if (info->isoFileIndex != -1) {
INFO_LOG(Log::HLE, "More than one ISO file found in zip. Ignoring additional ones.");
} else {
@@ -543,27 +544,27 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
info->contentName = fn;
}
}
} else if (zippedName.find("textures.ini") != std::string::npos) {
int slashLocation = (int)zippedName.find_last_of('/');
} else if (lowerCaseName.find("textures.ini") != std::string::npos) {
int slashLocation = (int)lowerCaseName.find_last_of('/');
if (stripCharsTexturePack == -1 || slashLocation < stripCharsTexturePack + 1) {
stripCharsTexturePack = slashLocation + 1;
isTexturePack = true;
info->textureIniIndex = i;
}
} else if (endsWith(zippedName, ".ppdmp")) {
} else if (endsWith(lowerCaseName, ".ppdmp")) {
isFrameDump = true;
info->isoFileIndex = i;
info->contentName = fn;
} else if (endsWith(zippedName, ".ppst")) {
int slashLocation = (int)zippedName.find_last_of('/');
} else if (endsWith(lowerCaseName, ".ppst")) {
int slashLocation = (int)lowerCaseName.find_last_of('/');
if (stripChars == 0 || slashLocation < stripChars + 1) {
stripChars = slashLocation + 1;
}
isSaveStates = true;
info->gameTitle = fn;
} else if (endsWith(zippedName, "psp_game/sysdir/eboot.bin") || endsWith(zippedName, "psp_game/sysdir/boot.bin")) {
} else if (endsWith(lowerCaseName, "psp_game/sysdir/eboot.bin") || endsWith(lowerCaseName, "psp_game/sysdir/boot.bin")) {
isExtractedISO = true;
} else if (endsWith(zippedName, "/param.sfo")) {
} else if (endsWith(lowerCaseName, "/param.sfo")) {
// Get the game name so we can display it.
std::string paramSFOContents;
if (ZipExtractFileToMemory(z, i, &paramSFOContents)) {
@@ -581,13 +582,18 @@ void DetectZipFileContents(zip_t *z, ZipFileInfo *info) {
}
}
}
} else if (endsWith(zippedName, "/icon0.png")) {
} else if (endsWith(lowerCaseName, "/icon0.png")) {
hasIcon0PNG = true;
} else if (endsWith(zippedName, "/plugin.ini") && slashCount == 1) {
} else if (endsWith(lowerCaseName, "/plugin.ini") && slashCount >= 1) {
int slashLocation = (int)lowerCaseName.find_last_of('/');
// Find previous slash to determine the root of the plugin, so we can display it properly.
int prevSlashLocation = (int)lowerCaseName.find_last_of('/', slashLocation - 1);
_dbg_assert_(prevSlashLocation != std::string::npos);
stripChars = prevSlashLocation + 1;
hasPluginIni = true;
ZipExtractFileToMemory(z, i, &info->iniContents);
info->contentName = fileName.substr(0, fileName.find_last_of('/'));
} else if (endsWith(zippedName, ".prx") && slashCount == 1) {
} else if (endsWith(lowerCaseName, ".prx")) {
hasPRX = true;
}
if (slashCount == 0) {

View File

@@ -175,7 +175,7 @@ void GameManager::Update() {
}
}
bool ZipCanExtractWithoutOverwrite(struct zip *z, const Path &destination, int maxOkFiles) {
bool ZipCanExtractWithoutOverwrite(struct zip *z, const Path &destination, int stripChars, int maxOkFiles) {
int numFiles = zip_get_num_files(z);
if (numFiles > maxOkFiles && maxOkFiles >= 0) {
// Ignore the check, just assume we can't.
@@ -260,7 +260,7 @@ void GameManager::InstallZipContents(ZipFileTask task) {
}
// Check for 7z. We don't support very many scenarios here yet, but we do support ISO install.
if (task.zipFileInfo->archiveType == ArchiveType::SevenZ) {
if (task.zipFileInfo && task.zipFileInfo->archiveType == ArchiveType::SevenZ) {
Path destPath = task.destination;
g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f);
Path fn(task.zipFileInfo->isoFilename);
@@ -662,7 +662,7 @@ bool GameManager::ExtractZipContents(struct zip *z, const Path &dest, const ZipF
bool isDir = zippedName.empty() || zippedName.back() == '/';
if (!isDir && zippedName.find('/') != std::string::npos) {
outFilename = dest / zippedName.substr(0, zippedName.rfind('/'));
outFilename = dest / zippedName.substr(info.stripChars, zippedName.rfind('/') - info.stripChars);
} else if (!isDir) {
outFilename = dest;
}

View File

@@ -119,4 +119,4 @@ private:
extern GameManager g_GameManager;
bool ZipCanExtractWithoutOverwrite(struct zip *z, const Path &destination, int maxOkFiles);
bool ZipCanExtractWithoutOverwrite(struct zip *z, const Path &destination, int stripChars, int maxOkFiles);

View File

@@ -165,8 +165,6 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
leftColumn->Add(new TextView(zipFileInfo_.contentName));
}
doneView_ = leftColumn->Add(new TextView(""));
if (zipFileInfo_.contents == ZipFileContents::ISO_FILE) {
const bool isInDownloads = File::IsProbablyInDownloadsFolder(zipPath_);
Path parent;
@@ -205,7 +203,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
leftColumn->Add(new TextView(GetFriendlyPath(zipPath_)));
Path pluginsDir = GetSysDirectory(DIRECTORY_PLUGINS);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, pluginsDir, 50);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, pluginsDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
std::stringstream sstream(zipFileInfo_.iniContents);
IniFile ini;
@@ -236,7 +234,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
Path savestateDir = GetSysDirectory(DIRECTORY_SAVESTATE);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, savestateDir, 50);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, savestateDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
destFolders_.push_back(savestateDir);
@@ -253,7 +251,7 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
Path savedataDir = GetSysDirectory(DIRECTORY_SAVEDATA);
ZipContainer zipFile = ZipOpenPath(zipPath_);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, savedataDir, 50);
overwrite = !ZipCanExtractWithoutOverwrite(zipFile, savedataDir, zipFileInfo_.stripChars, 50);
ZipClose(zipFile);
destFolders_.push_back(savedataDir);
@@ -307,7 +305,8 @@ void InstallZipScreen::CreateContentViews(UI::ViewGroup *parent) {
break;
}
doneView_ = leftColumn->Add(new TextView(""));
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")));
@@ -374,11 +373,14 @@ void InstallZipScreen::update() {
}
std::string err = g_GameManager.GetInstallError();
if (!err.empty()) {
if (doneView_)
doneView_->SetText(iz->T(err));
if (doneView_) {
doneView_->SetLevelAndText(NoticeLevel::ERROR, iz->T(err));
doneView_->SetVisibility(Visibility::V_VISIBLE);
}
} else if (installStarted_) {
if (doneView_) {
doneView_->SetText(iz->T("Installed!"));
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,

View File

@@ -24,6 +24,7 @@
#include "UI/BaseScreens.h"
#include "UI/SimpleDialogScreen.h"
#include "Common/UI/Notice.h"
class SavedataView;
@@ -49,7 +50,7 @@ private:
UI::Choice *installChoice_ = nullptr;
UI::Choice *playChoice_ = nullptr;
UI::Choice *backChoice_ = nullptr;
UI::TextView *doneView_ = nullptr;
NoticeView *doneView_ = nullptr;
SavedataView *existingSaveView_ = nullptr;
Path savedataToOverwrite_;
Path zipPath_;