From 5e0a9a0113825d9934d3c207b512829980b7f30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 27 Mar 2026 12:12:28 -0600 Subject: [PATCH] Add really simple 7z extraction, using the 7z VFS --- Common/File/VFS/VFS.cpp | 2 ++ Common/System/OSD.cpp | 2 ++ Core/Loaders.cpp | 19 +++++++++++++++++++ Core/Loaders.h | 9 ++++++++- Core/Util/GameManager.cpp | 29 +++++++++++++++++++++++++++-- UI/InstallZipScreen.cpp | 20 ++++++++++++++++---- UI/MainScreen.cpp | 3 ++- 7 files changed, 76 insertions(+), 8 deletions(-) diff --git a/Common/File/VFS/VFS.cpp b/Common/File/VFS/VFS.cpp index a46a862f56..d2098fbb0e 100644 --- a/Common/File/VFS/VFS.cpp +++ b/Common/File/VFS/VFS.cpp @@ -36,6 +36,8 @@ static bool IsLocalAbsolutePath(std::string_view path) { return isUnixLocal || isWindowsLocal || isContentURI; } +// This allows empty prefixes for multiple systems, which effectively becomes a union mount. +// Which is useful for PPSSPP's asset files, but not much else. We should use prefixes for everything. template inline RetType RouteVFSOperation(const std::vector &entries, std::string_view filename, Func func) { const int fn_len = (int)filename.length(); diff --git a/Common/System/OSD.cpp b/Common/System/OSD.cpp index 1e810e4986..9580be3409 100644 --- a/Common/System/OSD.cpp +++ b/Common/System/OSD.cpp @@ -59,6 +59,8 @@ void OnScreenDisplay::Show(OSDType type, std::string_view text, std::string_view return; } + INFO_LOG(Log::UI, "OSD: %.*s (%.*s)", STR_VIEW(text), STR_VIEW(text2)); + // Automatic duration based on type. if (duration_s <= 0.0f) { switch (type) { diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 041070e7b9..5b5ab932e1 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -451,7 +451,26 @@ static bool ZipExtractFileToMemory(struct zip *z, int fileIndex, std::string *da } } +// TODO: Make this generic and handle all the things. +// For now, it'll just handle unpacking ISOs. +bool DetectArchiveContents(VFSInterface *vfs, ZipFileInfo *info) { + info->archiveType = ArchiveType::SevenZ; + info->contents = ZipFileContents::UNKNOWN; + std::vector listing; + vfs->GetFileListing("", &listing, nullptr); + for (auto &file : listing) { + std::string ext = file.fullName.GetFileExtension(); // always lowercase + if (ext == ".iso" || ext == ".cso") { + info->contents = ZipFileContents::ISO_FILE; + info->isoFilename = file.fullName.ToString(); + return true; + } + } + return false; +} + void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { + info->archiveType = ArchiveType::ZIP; int numFiles = zip_get_num_files(z); if (numFiles < 0) { // Broken zip archive? diff --git a/Core/Loaders.h b/Core/Loaders.h index 5eb1e57e91..2aa3d1c31f 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -162,6 +162,10 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &error); +enum class ArchiveType { + ZIP, + SevenZ, +}; enum class ZipFileContents { NOT_A_ZIP_FILE = 0, @@ -176,7 +180,9 @@ enum class ZipFileContents { PRX_PLUGIN, }; +// TODO: Rename to ArchiveContentsInfo or something. struct ZipFileInfo { + ArchiveType archiveType; ZipFileContents contents = ZipFileContents::NOT_A_ZIP_FILE; int numFiles = 0; int stripChars = 0; // for PSP game - how much to strip from the path. @@ -189,6 +195,7 @@ struct ZipFileInfo { std::string savedataDir; std::string mTime; std::string iniContents; + std::string isoFilename; // Used by SevenZ only right now. s64 totalFileSize = 0; std::string contentName; @@ -197,5 +204,5 @@ struct ZipFileInfo { ZipContainer ZipOpenPath(const Path &fileName); void ZipClose(ZipContainer &z); -bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info); void DetectZipFileContents(zip_t *z, ZipFileInfo *info); +bool DetectArchiveContents(VFSInterface *vfs, ZipFileInfo *info); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index ecb0af9026..656c7fbd7b 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -40,6 +40,8 @@ #include "Common/System/Request.h" #include "Common/System/OSD.h" #include "Common/File/FileUtil.h" +#include "Common/File/VFS/VFS.h" +#include "Common/File/VFS/SevenZipFileReader.h" #include "Common/StringUtils.h" #include "Common/Thread/ThreadUtil.h" #include "Core/Config.h" @@ -257,6 +259,25 @@ void GameManager::InstallZipContents(ZipFileTask task) { return; } + // Check for 7z. We don't support very many scenarios here yet, but we do support ISO install. + if (task.zipFileInfo->archiveType == ArchiveType::SevenZ) { + Path destPath = task.destination; + g_OSD.SetProgressBar("install", di->T("Installing..."), 0.0f, 0.0f, 0.0f, 0.1f); + VFSInterface *reader = SevenZipFileReader::Create(task.fileName, "", true); + // TODO: We need streaming reads here. + size_t size; + u8 *data = reader->ReadFile(task.zipFileInfo->isoFilename, &size); + + Path fn(task.zipFileInfo->isoFilename); + Path destFile = destPath / fn.GetFilename(); + + File::WriteDataToFile(false, data, size, destFile); + g_OSD.RemoveProgressBar("install", true, 0.5f); + installProgress_ = 1.0f; + InstallDone(); + return; + } + int error = 0; ZipContainer z = ZipOpenPath(task.fileName); @@ -739,7 +760,9 @@ bool GameManager::InstallZipOnThread(ZipFileTask task) { return false; } - installThread_ = std::thread(std::bind(&GameManager::InstallZipContents, this, task)); + installThread_ = std::thread([this, task]() { + InstallZipContents(task); + }); return true; } @@ -751,7 +774,9 @@ bool GameManager::UninstallGameOnThread(const std::string &name) { if (InstallInProgress() || installDonePending_ || curDownload_.get() != nullptr) { return false; } - installThread_ = std::thread(std::bind(&GameManager::UninstallGame, this, name)); + installThread_ = std::thread([this, name]() { + UninstallGame(name); + }); return true; } diff --git a/UI/InstallZipScreen.cpp b/UI/InstallZipScreen.cpp index 1e2e17fa1a..1befd99d91 100644 --- a/UI/InstallZipScreen.cpp +++ b/UI/InstallZipScreen.cpp @@ -24,6 +24,8 @@ #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" @@ -40,15 +42,25 @@ InstallZipScreen::InstallZipScreen(const Path &zipPath) : UITwoPaneBaseDialogScreen(Path(), TwoPaneFlags::SettingsToTheRight | TwoPaneFlags::ContentsCanScroll), zipPath_(zipPath) { g_GameManager.ResetInstallError(); - ZipContainer zipFile = ZipOpenPath(zipPath_); - if (zipFile) { - DetectZipFileContents(zipFile, &zipFileInfo_); // Even if this fails, it sets zipInfo->contents. - ZipClose(zipFile); + + if (zipPath.GetFileExtension() == ".7z") { + SevenZipFileReader *sevenZipFile = SevenZipFileReader::Create(zipPath, ""); + 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"); } diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index c7cc7070b0..1280f88868 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -75,7 +75,8 @@ bool MainScreen::showHomebrewTab = false; static void LaunchFile(ScreenManager *screenManager, Screen *currentScreen, const Path &path) { - if (path.GetFileExtension() == ".zip") { + std::string extension = path.GetFileExtension(); + if (extension == ".zip" || extension == ".7z") { // If is a zip file, we have a screen for that. screenManager->push(new InstallZipScreen(path)); } else {