diff options
Diffstat (limited to 'src/yuzu')
-rw-r--r-- | src/yuzu/debugger/wait_tree.cpp | 1 | ||||
-rw-r--r-- | src/yuzu/game_list.cpp | 33 | ||||
-rw-r--r-- | src/yuzu/game_list.h | 39 | ||||
-rw-r--r-- | src/yuzu/game_list_p.h | 42 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 167 | ||||
-rw-r--r-- | src/yuzu/main.h | 2 |
6 files changed, 230 insertions, 54 deletions
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index f2a7e23f0..a3b1fd357 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -15,6 +15,7 @@ #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" #include "core/hle/kernel/wait_object.h" +#include "core/memory.h" WaitTreeItem::WaitTreeItem() = default; WaitTreeItem::~WaitTreeItem() = default; diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index 3b3b551bb..67890455a 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -26,10 +26,10 @@ #include "yuzu/main.h" #include "yuzu/ui_settings.h" -GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} +GameListSearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) : gamelist{gamelist} {} // EventFilter in order to process systemkeys while editing the searchfield -bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { +bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { // If it isn't a KeyRelease event then continue with standard event processing if (event->type() != QEvent::KeyRelease) return QObject::eventFilter(obj, event); @@ -88,29 +88,21 @@ bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* e return QObject::eventFilter(obj, event); } -void GameList::SearchField::setFilterResult(int visible, int total) { - QString result_of_text = tr("of"); - QString result_text; - if (total == 1) { - result_text = tr("result"); - } else { - result_text = tr("results"); - } - label_filter_result->setText( - QString("%1 %2 %3 %4").arg(visible).arg(result_of_text).arg(total).arg(result_text)); +void GameListSearchField::setFilterResult(int visible, int total) { + label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } -void GameList::SearchField::clear() { +void GameListSearchField::clear() { edit_filter->setText(""); } -void GameList::SearchField::setFocus() { +void GameListSearchField::setFocus() { if (edit_filter->isVisible()) { edit_filter->setFocus(); } } -GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { +GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} { KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); layout_filter = new QHBoxLayout; layout_filter->setMargin(8); @@ -210,7 +202,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) this->main_window = parent; layout = new QVBoxLayout; tree_view = new QTreeView; - search_field = new SearchField(this); + search_field = new GameListSearchField(this); item_model = new QStandardItemModel(tree_view); tree_view->setModel(item_model); @@ -326,9 +318,14 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { int row = item_model->itemFromIndex(item)->row(); QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME); u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong(); + std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString(); QMenu context_menu; 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")); open_save_location->setEnabled(program_id != 0); @@ -337,6 +334,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { connect(open_save_location, &QAction::triggered, [&]() { 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, [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 2713e7b54..05e115e19 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -22,13 +22,17 @@ #include "yuzu/compatibility_list.h" class GameListWorker; +class GameListSearchField; class GMainWindow; namespace FileSys { class VfsFilesystem; } -enum class GameListOpenTarget { SaveData }; +enum class GameListOpenTarget { + SaveData, + ModData, +}; class GameList : public QWidget { Q_OBJECT @@ -43,33 +47,6 @@ public: COLUMN_COUNT, // Number of columns }; - class SearchField : public QWidget { - public: - void setFilterResult(int visible, int total); - void clear(); - void setFocus(); - explicit SearchField(GameList* parent = nullptr); - - private: - class KeyReleaseEater : public QObject { - public: - explicit KeyReleaseEater(GameList* gamelist); - - private: - GameList* gamelist = nullptr; - QString edit_filter_text_old; - - protected: - bool eventFilter(QObject* obj, QEvent* event) override; - }; - QHBoxLayout* layout_filter = nullptr; - QTreeView* tree_view = nullptr; - QLabel* label_filter = nullptr; - QLineEdit* edit_filter = nullptr; - QLabel* label_filter_result = nullptr; - QToolButton* button_filter_close = nullptr; - }; - explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs, GMainWindow* parent = nullptr); ~GameList() override; @@ -89,6 +66,8 @@ signals: void GameChosen(QString game_path); void ShouldCancelWorker(); 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, const CompatibilityList& compatibility_list); @@ -105,7 +84,7 @@ private: void RefreshGameDirectory(); std::shared_ptr<FileSys::VfsFilesystem> vfs; - SearchField* search_field; + GameListSearchField* search_field; GMainWindow* main_window = nullptr; QVBoxLayout* layout = nullptr; QTreeView* tree_view = nullptr; @@ -113,6 +92,8 @@ private: GameListWorker* current_worker = nullptr; QFileSystemWatcher* watcher = nullptr; CompatibilityList compatibility_list; + + friend class GameListSearchField; }; Q_DECLARE_METATYPE(GameListOpenTarget); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index b6272d536..3db0e90da 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -16,6 +16,7 @@ #include <QObject> #include <QStandardItem> #include <QString> +#include <QWidget> #include "common/common_types.h" #include "common/logging/log.h" @@ -68,7 +69,7 @@ public: if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { picture = GetDefaultIcon(size); } - picture = picture.scaled(size, size); + picture = picture.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); setData(picture, Qt::DecorationRole); } @@ -176,3 +177,42 @@ public: return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong(); } }; + +class GameList; +class QHBoxLayout; +class QTreeView; +class QLabel; +class QLineEdit; +class QToolButton; + +class GameListSearchField : public QWidget { + Q_OBJECT + +public: + explicit GameListSearchField(GameList* parent = nullptr); + + void setFilterResult(int visible, int total); + + void clear(); + void setFocus(); + +private: + class KeyReleaseEater : public QObject { + public: + explicit KeyReleaseEater(GameList* gamelist); + + private: + GameList* gamelist = nullptr; + QString edit_filter_text_old; + + protected: + // EventFilter in order to process systemkeys while editing the searchfield + bool eventFilter(QObject* obj, QEvent* event) override; + }; + QHBoxLayout* layout_filter = nullptr; + QTreeView* tree_view = nullptr; + QLabel* label_filter = nullptr; + QLineEdit* edit_filter = nullptr; + QLabel* label_filter_result = nullptr; + QToolButton* button_filter_close = nullptr; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 45bb1d1d1..27015d02c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -7,6 +7,22 @@ #include <memory> #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 <glad/glad.h> @@ -30,16 +46,18 @@ #include "common/telemetry.h" #include "core/core.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/content_archive.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.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/submission_package.h" -#include "core/file_sys/vfs_real.h" #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/filesystem/fsp_ldr.h" #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -362,6 +380,8 @@ void GMainWindow::RestoreUIState() { void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 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, &GMainWindow::OnGameListNavigateToGamedbEntry); @@ -602,9 +622,9 @@ void GMainWindow::BootGame(const QString& filename) { std::string title_name; const auto res = Core::System::GetInstance().GetGameName(title_name); if (res != Loader::ResultStatus::Success) { - const u64 program_id = Core::System::GetInstance().CurrentProcess()->program_id; + const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); - const auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata(); + const auto [nacp, icon_file] = FileSys::PatchManager(title_id).GetControlMetadata(); if (nacp != nullptr) title_name = nacp->GetApplicationName(); @@ -713,6 +733,12 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target program_id, user_id, 0); 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: UNIMPLEMENTED(); } @@ -730,6 +756,120 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); } +static std::size_t CalculateRomFSEntrySize(const FileSys::VirtualDir& dir, bool full) { + std::size_t out = 0; + + for (const auto& subdir : dir->GetSubdirectories()) { + out += 1 + CalculateRomFSEntrySize(subdir, full); + } + + return out + (full ? dir->GetFiles().size() : 0); +} + +static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src, + const FileSys::VirtualDir& dest, std::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 (!RomFSRawCopy(dialog, dir, out, block_size, full)) + return false; + dialog.setValue(dialog.value() + 1); + if (dialog.wasCanceled()) + return false; + } + + return true; +} + +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); + + const 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, FileSys::RomFSExtractionType::Full); + 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 auto entry_size = CalculateRomFSEntrySize(extracted, full); + + QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, entry_size, this); + progress.setWindowModality(Qt::WindowModal); + progress.setMinimumDuration(100); + + if (RomFSRawCopy(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, const CompatibilityList& compatibility_list) { const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); @@ -790,7 +930,8 @@ void GMainWindow::OnMenuInstallToNAND() { 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, std::size_t block_size) { if (src == nullptr || dest == nullptr) return false; if (!dest->Resize(src->GetSize())) @@ -914,11 +1055,21 @@ void GMainWindow::OnMenuInstallToNAND() { return; } - if (index >= 5) - index += 0x7B; + // If index is equal to or past Game, add the jump in TitleType. + if (index >= 5) { + index += static_cast<size_t>(FileSys::TitleType::Application) - + static_cast<size_t>(FileSys::TitleType::FirmwarePackageB); + } + + FileSys::InstallResult res; + if (index >= static_cast<size_t>(FileSys::TitleType::Application)) { + res = Service::FileSystem::GetUserNANDContents()->InstallEntry( + nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + } else { + res = Service::FileSystem::GetSystemNANDContents()->InstallEntry( + nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); + } - const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry( - nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); if (res == FileSys::InstallResult::Success) { success(); } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 552e3e61c..8ee9242b1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -138,6 +138,8 @@ private slots: /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); 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, const CompatibilityList& compatibility_list); void OnMenuLoadFile(); |