Просмотр исходного кода

qt: Add UI elements for LayeredFS and related tools

Zach Hilman 7 лет назад
Родитель
Сommit
ba0873d33c

+ 1 - 1
src/core/file_sys/registered_cache.cpp

@@ -480,7 +480,7 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
     auto out = dir->CreateFileRelative(path);
     auto out = dir->CreateFileRelative(path);
     if (out == nullptr)
     if (out == nullptr)
         return InstallResult::ErrorCopyFailed;
         return InstallResult::ErrorCopyFailed;
-    return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
+    return copy(in, out, 0x400000) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
 }
 }
 
 
 bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
 bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {

+ 1 - 1
src/core/file_sys/registered_cache.h

@@ -27,7 +27,7 @@ struct ContentRecord;
 
 
 using NcaID = std::array<u8, 0x10>;
 using NcaID = std::array<u8, 0x10>;
 using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
 using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
-using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
+using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
 
 
 enum class InstallResult {
 enum class InstallResult {
     Success,
     Success,

+ 9 - 0
src/yuzu/game_list.cpp

@@ -318,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
     int row = item_model->itemFromIndex(item)->row();
     int row = item_model->itemFromIndex(item)->row();
     QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
     QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
     u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
     u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
+    std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
 
 
     QMenu context_menu;
     QMenu context_menu;
     QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
     QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
+    QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
+    context_menu.addSeparator();
+    QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
+    QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
     QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
 
 
     open_save_location->setEnabled(program_id != 0);
     open_save_location->setEnabled(program_id != 0);
@@ -329,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
 
 
     connect(open_save_location, &QAction::triggered,
     connect(open_save_location, &QAction::triggered,
             [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
             [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); });
+    connect(open_lfs_location, &QAction::triggered,
+            [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); });
+    connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); });
+    connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
     connect(navigate_to_gamedb_entry, &QAction::triggered,
     connect(navigate_to_gamedb_entry, &QAction::triggered,
             [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
             [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
 
 

+ 6 - 1
src/yuzu/game_list.h

@@ -28,7 +28,10 @@ namespace FileSys {
 class VfsFilesystem;
 class VfsFilesystem;
 }
 }
 
 
-enum class GameListOpenTarget { SaveData };
+enum class GameListOpenTarget {
+    SaveData,
+    ModData,
+};
 
 
 class GameList : public QWidget {
 class GameList : public QWidget {
     Q_OBJECT
     Q_OBJECT
@@ -89,6 +92,8 @@ signals:
     void GameChosen(QString game_path);
     void GameChosen(QString game_path);
     void ShouldCancelWorker();
     void ShouldCancelWorker();
     void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
     void OpenFolderRequested(u64 program_id, GameListOpenTarget target);
+    void DumpRomFSRequested(u64 program_id, const std::string& game_path);
+    void CopyTIDRequested(u64 program_id);
     void NavigateToGamedbEntryRequested(u64 program_id,
     void NavigateToGamedbEntryRequested(u64 program_id,
                                         const CompatibilityList& compatibility_list);
                                         const CompatibilityList& compatibility_list);
 
 

+ 143 - 2
src/yuzu/main.cpp

@@ -7,6 +7,22 @@
 #include <memory>
 #include <memory>
 #include <thread>
 #include <thread>
 
 
+// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_real.h"
+
+// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
+// defines.
+static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper(
+    const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) {
+    return vfs->CreateDirectory(path, mode);
+}
+
+static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::VirtualDir& dir,
+                                                          const std::string& path) {
+    return dir->CreateFile(path);
+}
+
 #include <fmt/ostream.h>
 #include <fmt/ostream.h>
 #include <glad/glad.h>
 #include <glad/glad.h>
 
 
@@ -30,16 +46,18 @@
 #include "common/telemetry.h"
 #include "common/telemetry.h"
 #include "core/core.h"
 #include "core/core.h"
 #include "core/crypto/key_manager.h"
 #include "core/crypto/key_manager.h"
+#include "core/file_sys/bis_factory.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
 #include "core/file_sys/savedata_factory.h"
 #include "core/file_sys/savedata_factory.h"
 #include "core/file_sys/submission_package.h"
 #include "core/file_sys/submission_package.h"
-#include "core/file_sys/vfs_real.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/filesystem/filesystem.h"
+#include "core/hle/service/filesystem/fsp_ldr.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
 #include "core/perf_stats.h"
 #include "core/settings.h"
 #include "core/settings.h"
@@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() {
 void GMainWindow::ConnectWidgetEvents() {
 void GMainWindow::ConnectWidgetEvents() {
     connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
     connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
     connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
     connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
+    connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
+    connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
     connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
     connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
             &GMainWindow::OnGameListNavigateToGamedbEntry);
             &GMainWindow::OnGameListNavigateToGamedbEntry);
 
 
@@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
                                                                 program_id, user_id, 0);
                                                                 program_id, user_id, 0);
         break;
         break;
     }
     }
+    case GameListOpenTarget::ModData: {
+        open_target = "Mod Data";
+        const auto load_dir = FileUtil::GetUserPath(FileUtil::UserPath::LoadDir);
+        path = fmt::format("{}{:016X}", load_dir, program_id);
+        break;
+    }
     default:
     default:
         UNIMPLEMENTED();
         UNIMPLEMENTED();
     }
     }
@@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
     QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
     QDesktopServices::openUrl(QUrl::fromLocalFile(qpath));
 }
 }
 
 
+void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
+    const auto path = fmt::format("{}{:016X}/romfs",
+                                  FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
+
+    auto failed = [this, &path]() {
+        QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
+                             tr("There was an error copying the RomFS files or the user "
+                                "cancelled the operation."));
+        vfs->DeleteDirectory(path);
+    };
+
+    const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
+    if (loader == nullptr) {
+        failed();
+        return;
+    }
+
+    FileSys::VirtualFile file;
+    if (loader->ReadRomFS(file) != Loader::ResultStatus::Success) {
+        failed();
+        return;
+    }
+
+    const auto romfs =
+        loader->IsRomFSUpdatable()
+            ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
+            : file;
+
+    const auto extracted = FileSys::ExtractRomFS(romfs, false);
+    if (extracted == nullptr) {
+        failed();
+        return;
+    }
+
+    const auto out = VfsFilesystemCreateDirectoryWrapper(vfs, path, FileSys::Mode::ReadWrite);
+
+    if (out == nullptr) {
+        failed();
+        return;
+    }
+
+    bool ok;
+    const auto res = QInputDialog::getItem(
+        this, tr("Select RomFS Dump Mode"),
+        tr("Please select the how you would like the RomFS dumped.<br>Full will copy all of the "
+           "files into the new directory while <br>skeleton will only create the directory "
+           "structure."),
+        {"Full", "Skeleton"}, 0, false, &ok);
+    if (!ok)
+        failed();
+
+    const auto full = res == "Full";
+
+    const static std::function<size_t(const FileSys::VirtualDir&, bool)> calculate_entry_size =
+        [](const FileSys::VirtualDir& dir, bool full) {
+            size_t out = 0;
+            for (const auto& subdir : dir->GetSubdirectories())
+                out += 1 + calculate_entry_size(subdir, full);
+            return out + full ? dir->GetFiles().size() : 0;
+        };
+    const auto entry_size = calculate_entry_size(extracted, full);
+
+    QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this);
+    progress.setWindowModality(Qt::WindowModal);
+    progress.setMinimumDuration(100);
+
+    const static std::function<bool(QProgressDialog&, const FileSys::VirtualDir&,
+                                    const FileSys::VirtualDir&, size_t, bool)>
+        qt_raw_copy = [](QProgressDialog& dialog, const FileSys::VirtualDir& src,
+                         const FileSys::VirtualDir& dest, size_t block_size, bool full) {
+            if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
+                return false;
+            if (dialog.wasCanceled())
+                return false;
+
+            if (full) {
+                for (const auto& file : src->GetFiles()) {
+                    const auto out = VfsDirectoryCreateFileWrapper(dest, file->GetName());
+                    if (!FileSys::VfsRawCopy(file, out, block_size))
+                        return false;
+                    dialog.setValue(dialog.value() + 1);
+                    if (dialog.wasCanceled())
+                        return false;
+                }
+            }
+
+            for (const auto& dir : src->GetSubdirectories()) {
+                const auto out = dest->CreateSubdirectory(dir->GetName());
+                if (!qt_raw_copy(dialog, dir, out, block_size, full))
+                    return false;
+                dialog.setValue(dialog.value() + 1);
+                if (dialog.wasCanceled())
+                    return false;
+            }
+
+            return true;
+        };
+
+    if (qt_raw_copy(progress, extracted, out, 0x400000, full)) {
+        progress.close();
+        QMessageBox::information(this, tr("RomFS Extraction Succeeded!"),
+                                 tr("The operation completed successfully."));
+        QDesktopServices::openUrl(QUrl::fromLocalFile(QString::fromStdString(path)));
+    } else {
+        progress.close();
+        failed();
+    }
+}
+
+void GMainWindow::OnGameListCopyTID(u64 program_id) {
+    QClipboard* clipboard = QGuiApplication::clipboard();
+    clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id)));
+}
+
 void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
 void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
                                                   const CompatibilityList& compatibility_list) {
                                                   const CompatibilityList& compatibility_list) {
     const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
     const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() {
         return;
         return;
     }
     }
 
 
-    const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
+    const auto qt_raw_copy = [this](const FileSys::VirtualFile& src,
+                                    const FileSys::VirtualFile& dest, size_t block_size) {
         if (src == nullptr || dest == nullptr)
         if (src == nullptr || dest == nullptr)
             return false;
             return false;
         if (!dest->Resize(src->GetSize()))
         if (!dest->Resize(src->GetSize()))

+ 2 - 0
src/yuzu/main.h

@@ -138,6 +138,8 @@ private slots:
     /// Called whenever a user selects a game in the game list widget.
     /// Called whenever a user selects a game in the game list widget.
     void OnGameListLoadFile(QString game_path);
     void OnGameListLoadFile(QString game_path);
     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
     void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);
+    void OnGameListDumpRomFS(u64 program_id, const std::string& game_path);
+    void OnGameListCopyTID(u64 program_id);
     void OnGameListNavigateToGamedbEntry(u64 program_id,
     void OnGameListNavigateToGamedbEntry(u64 program_id,
                                          const CompatibilityList& compatibility_list);
                                          const CompatibilityList& compatibility_list);
     void OnMenuLoadFile();
     void OnMenuLoadFile();