summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/game_list.cpp96
-rw-r--r--src/yuzu/game_list_p.h3
-rw-r--r--src/yuzu/main.cpp148
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui7
5 files changed, 232 insertions, 23 deletions
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 85cb12594..f867118d9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <regex>
#include <QApplication>
#include <QDir>
#include <QFileInfo>
@@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {
}
}
-void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
- boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
+static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
+ std::vector<u8>& icon, std::string& name) {
+ const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
+ if (control_dir == nullptr)
+ return;
+
+ const auto nacp_file = control_dir->GetFile("control.nacp");
+ if (nacp_file == nullptr)
+ return;
+ FileSys::NACP nacp(nacp_file);
+ name = nacp.GetApplicationName();
+
+ FileSys::VirtualFile icon_file = nullptr;
+ for (const auto& language : FileSys::LANGUAGE_NAMES) {
+ icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
+ if (icon_file != nullptr) {
+ icon = icon_file->ReadAllBytes();
+ break;
+ }
+ }
+}
+
+void GameListWorker::AddInstalledTitlesToGameList() {
+ const auto usernand = Service::FileSystem::GetUserNANDContents();
+ const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
+ FileSys::ContentRecordType::Program);
+
+ for (const auto& game : installed_games) {
+ const auto& file = usernand->GetEntryRaw(game);
+ std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
+ if (!loader)
+ continue;
+
+ std::vector<u8> icon;
+ std::string name;
+ u64 program_id;
+ loader->ReadProgramId(program_id);
+
+ const auto& control =
+ usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
+ if (control != nullptr)
+ GetMetadataFromControlNCA(control, icon, name);
+ emit EntryReady({
+ new GameListItemPath(
+ FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
+ program_id),
+ new GameListItem(
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
+ new GameListItemSize(file->GetSize()),
+ });
+ }
+
+ const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
+ FileSys::ContentRecordType::Control);
+
+ for (const auto& entry : control_data) {
+ const auto nca = usernand->GetEntry(entry);
+ if (nca != nullptr)
+ nca_control_map.insert_or_assign(entry.title_id, nca);
+ }
+}
- const auto nca_control_callback =
- [this, &nca_control_map](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+void GameListWorker::FillControlMap(const std::string& dir_path) {
+ const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing)
@@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
};
FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
+}
- const auto callback = [this, recursion,
- &nca_control_map](u64* num_entries_out, const std::string& directory,
- const std::string& virtual_name) -> bool {
+void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
+ const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
+ const std::string& virtual_name) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing)
@@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
// Use from metadata pool.
if (nca_control_map.find(program_id) != nca_control_map.end()) {
const auto nca = nca_control_map[program_id];
- const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
-
- const auto nacp_file = control_dir->GetFile("control.nacp");
- FileSys::NACP nacp(nacp_file);
- name = nacp.GetApplicationName();
-
- FileSys::VirtualFile icon_file = nullptr;
- for (const auto& language : FileSys::LANGUAGE_NAMES) {
- icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
- if (icon_file != nullptr) {
- icon = icon_file->ReadAllBytes();
- break;
- }
- }
+ GetMetadataFromControlNCA(nca, icon, name);
}
}
@@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
void GameListWorker::run() {
stop_processing = false;
watch_list.append(dir_path);
+ FillControlMap(dir_path.toStdString());
+ AddInstalledTitlesToGameList();
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
+ nca_control_map.clear();
emit Finished(watch_list);
}
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 8fe5e8b80..10c2ef075 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -163,10 +163,13 @@ signals:
private:
FileSys::VirtualFilesystem vfs;
+ std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
QStringList watch_list;
QString dir_path;
bool deep_scan;
std::atomic_bool stop_processing;
+ void AddInstalledTitlesToGameList();
+ void FillControlMap(const std::string& dir_path);
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 94fb8ae6a..f7eee7769 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -6,7 +6,10 @@
#include <clocale>
#include <memory>
#include <thread>
+
+#include <fmt/ostream.h>
#include <glad/glad.h>
+
#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QFileDialog>
@@ -24,6 +27,7 @@
#include "common/string_util.h"
#include "core/core.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/card_image.h"
#include "core/file_sys/vfs_real.h"
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
@@ -114,6 +118,9 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
show();
+ // Necessary to load titles from nand in gamelist.
+ Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
// Show one-time "callout" messages to the user
@@ -309,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() {
// File
connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
+ connect(ui.action_Install_File_NAND, &QAction::triggered, this,
+ &GMainWindow::OnMenuInstallToNAND);
connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
&GMainWindow::OnMenuSelectGameListRoot);
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
@@ -454,7 +463,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
"While attempting to load the ROM requested, an error occured. Please "
"refer to the yuzu wiki for more information or the yuzu discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
- loader_id, error_id, Loader::GetMessageForResultStatus(error_id))));
+ loader_id, error_id, static_cast<Loader::ResultStatus>(error_id))));
} else {
QMessageBox::critical(
this, tr("Error while loading ROM!"),
@@ -612,6 +621,143 @@ void GMainWindow::OnMenuLoadFolder() {
}
}
+void GMainWindow::OnMenuInstallToNAND() {
+ const QString file_filter =
+ tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
+ "Image (*.xci)");
+ QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
+ UISettings::values.roms_path, file_filter);
+
+ const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
+ if (src == nullptr || dest == nullptr)
+ return false;
+ if (!dest->Resize(src->GetSize()))
+ return false;
+
+ QProgressDialog progress(fmt::format("Installing file \"{}\"...", src->GetName()).c_str(),
+ "Cancel", 0, src->GetSize() / 0x1000, this);
+ progress.setWindowModality(Qt::WindowModal);
+
+ std::array<u8, 0x1000> buffer{};
+ for (size_t i = 0; i < src->GetSize(); i += 0x1000) {
+ if (progress.wasCanceled()) {
+ dest->Resize(0);
+ return false;
+ }
+
+ progress.setValue(i / 0x1000);
+ const auto read = src->Read(buffer.data(), buffer.size(), i);
+ dest->Write(buffer.data(), read, i);
+ }
+
+ return true;
+ };
+
+ const auto success = [this]() {
+ QMessageBox::information(this, tr("Successfully Installed"),
+ tr("The file was successfully installed."));
+ game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ };
+
+ const auto failed = [this]() {
+ QMessageBox::warning(
+ this, tr("Failed to Install"),
+ tr("There was an error while attempting to install the provided file. It "
+ "could have an incorrect format or be missing metadata. Please "
+ "double-check your file and try again."));
+ };
+
+ const auto overwrite = [this]() {
+ return QMessageBox::question(this, "Failed to Install",
+ "The file you are attempting to install already exists "
+ "in the cache. Would you like to overwrite it?") ==
+ QMessageBox::Yes;
+ };
+
+ if (!filename.isEmpty()) {
+ if (filename.endsWith("xci", Qt::CaseInsensitive)) {
+ const auto xci = std::make_shared<FileSys::XCI>(
+ vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ if (xci->GetStatus() != Loader::ResultStatus::Success) {
+ failed();
+ return;
+ }
+ const auto res =
+ Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
+ if (res == FileSys::InstallResult::Success) {
+ success();
+ } else {
+ if (res == FileSys::InstallResult::ErrorAlreadyExists) {
+ if (overwrite()) {
+ const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
+ xci, true, qt_raw_copy);
+ if (res2 == FileSys::InstallResult::Success) {
+ success();
+ } else {
+ failed();
+ }
+ }
+ } else {
+ failed();
+ }
+ }
+ } else {
+ const auto nca = std::make_shared<FileSys::NCA>(
+ vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+ if (nca->GetStatus() != Loader::ResultStatus::Success) {
+ failed();
+ return;
+ }
+
+ static const QStringList tt_options{"System Application",
+ "System Archive",
+ "System Application Update",
+ "Firmware Package (Type A)",
+ "Firmware Package (Type B)",
+ "Game",
+ "Game Update",
+ "Game DLC",
+ "Delta Title"};
+ bool ok;
+ const auto item = QInputDialog::getItem(
+ this, tr("Select NCA Install Type..."),
+ tr("Please select the type of title you would like to install this NCA as:\n(In "
+ "most instances, the default 'Game' is fine.)"),
+ tt_options, 5, false, &ok);
+
+ auto index = tt_options.indexOf(item);
+ if (!ok || index == -1) {
+ QMessageBox::warning(this, tr("Failed to Install"),
+ tr("The title type you selected for the NCA is invalid."));
+ return;
+ }
+
+ if (index >= 5)
+ index += 0x7B;
+
+ 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) {
+ if (overwrite()) {
+ const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
+ nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
+ if (res2 == FileSys::InstallResult::Success) {
+ success();
+ } else {
+ failed();
+ }
+ }
+ } else {
+ failed();
+ }
+ }
+ }
+ }
+}
+
void GMainWindow::OnMenuSelectGameListRoot() {
QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
if (!dir_path.isEmpty()) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 74487c58c..5f4d2ab9a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -125,6 +125,7 @@ private slots:
void OnGameListOpenSaveFolder(u64 program_id);
void OnMenuLoadFile();
void OnMenuLoadFolder();
+ void OnMenuInstallToNAND();
/// Called whenever a user selects the "File->Select Game List Root" menu item
void OnMenuSelectGameListRoot();
void OnMenuRecentFile();
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 22c4cad08..a3bfb2af3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -57,6 +57,8 @@
<string>Recent Files</string>
</property>
</widget>
+ <addaction name="action_Install_File_NAND" />
+ <addaction name="separator"/>
<addaction name="action_Load_File"/>
<addaction name="action_Load_Folder"/>
<addaction name="separator"/>
@@ -102,6 +104,11 @@
<addaction name="menu_View"/>
<addaction name="menu_Help"/>
</widget>
+ <action name="action_Install_File_NAND">
+ <property name="text">
+ <string>Install File to NAND...</string>
+ </property>
+ </action>
<action name="action_Load_File">
<property name="text">
<string>Load File...</string>