summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/debugger/wait_tree.cpp1
-rw-r--r--src/yuzu/game_list.cpp33
-rw-r--r--src/yuzu/game_list.h39
-rw-r--r--src/yuzu/game_list_p.h42
-rw-r--r--src/yuzu/main.cpp167
-rw-r--r--src/yuzu/main.h2
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();