mirror of
https://github.com/yuzu-emu/yuzu-android.git
synced 2025-07-03 22:47:52 -05:00
Merge pull request #1005 from DarkLordZach/registered-fmt
file_sys: Add support for registration format
This commit is contained in:
@ -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 nca_control_callback =
|
||||
[this, &nca_control_map](u64* num_entries_out, const std::string& directory,
|
||||
const std::string& virtual_name) -> bool {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -27,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"
|
||||
@ -117,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
|
||||
@ -312,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);
|
||||
@ -615,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()) {
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
Reference in New Issue
Block a user