Compare commits

...

7 Commits

Author SHA1 Message Date
Zach Hilman
a8119a47a1 qt: Add Tools commands for system archive management 2019-10-15 14:39:47 -04:00
Zach Hilman
bf5c2371a2 qt: Add question when booting XCI to import archives
Adds convenience.
2019-10-15 14:39:26 -04:00
Zach Hilman
a85d0b6712 qt: Add UI to view sources of system archives
Displays, color coded, the origin for each of the 0x28 archives, allowing for easy debugging.
2019-10-15 14:38:52 -04:00
Zach Hilman
f06c541440 fsp_srv: Load imported system archives when available
Occurs after NAND, but before OSS
2019-10-15 14:38:07 -04:00
Zach Hilman
db3ddd80da filesystem: Add accessors for sysdata imported directory
Stores imported system archives
2019-10-15 14:37:38 -04:00
Zach Hilman
cedcaed581 system_archive: Expose count and base ID constants 2019-10-15 14:37:11 -04:00
Zach Hilman
7fa34900e7 file_sys: Add functions to manage system archive importing
Provides a couple of functions that simply clearing and adding to imported sysdata.
2019-10-15 14:36:52 -04:00
15 changed files with 567 additions and 4 deletions

View File

@@ -86,6 +86,8 @@ add_library(core STATIC
file_sys/system_archive/data/font_nintendo_extended.h
file_sys/system_archive/data/font_standard.cpp
file_sys/system_archive/data/font_standard.h
file_sys/system_archive/importer.cpp
file_sys/system_archive/importer.h
file_sys/system_archive/mii_model.cpp
file_sys/system_archive/mii_model.h
file_sys/system_archive/ng_word.cpp

View File

@@ -0,0 +1,46 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/file_util.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/system_archive/importer.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
namespace FileSys::SystemArchive {
VirtualFile GetImportedSystemArchive(const VirtualDir& sysdata, u64 title_id) {
const auto filename = fmt::format("{:016X}.arc", title_id);
return sysdata->GetFile(filename);
}
bool ImportSystemArchive(const VirtualDir& sysdata, u64 title_id, const VirtualFile& data) {
const auto filename = fmt::format("{:016X}.arc", title_id);
const auto out = sysdata->CreateFile(filename);
return out != nullptr && VfsRawCopy(data, out);
}
bool ImportDirectorySystemUpdate(const VirtualDir& sysdata, const VirtualDir& dir) {
Core::Crypto::KeyManager keys;
for (const auto& file : dir->GetFiles()) {
NCA nca{file, nullptr, 0, keys};
if (nca.GetStatus() == Loader::ResultStatus::Success &&
nca.GetType() == NCAContentType::Data && nca.GetRomFS() != nullptr) {
if (!ImportSystemArchive(sysdata, nca.GetTitleId(), nca.GetRomFS())) {
return false;
}
}
}
return true;
}
bool ImportXCISystemUpdate(const VirtualDir& sysdata, XCI& xci) {
return ImportDirectorySystemUpdate(sysdata, xci.GetUpdatePartition());
}
} // namespace FileSys::SystemArchive

View File

@@ -0,0 +1,31 @@
// Copyright 2019 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
namespace FileSys {
class NSP;
class XCI;
namespace SystemArchive {
/// Returns the file corresponding to the title_id if it exists in sysdata.
VirtualFile GetImportedSystemArchive(const VirtualDir& sysdata, u64 title_id);
/// Copies the provided file into sysdata, overwriting current data.
bool ImportSystemArchive(const VirtualDir& sysdata, u64 title_id, const VirtualFile& data);
/// Copies all system archives in the directory to sysdata.
bool ImportDirectorySystemUpdate(const VirtualDir& sysdata, const VirtualDir& dir);
/// Calls ImportDirectorySystemUpdate on the update partition of the XCI.
bool ImportXCISystemUpdate(const VirtualDir& sysdata, XCI& xci);
} // namespace SystemArchive
} // namespace FileSys

View File

@@ -12,9 +12,6 @@
namespace FileSys::SystemArchive {
constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
using SystemArchiveSupplier = VirtualDir (*)();
struct SystemArchiveDescriptor {

View File

@@ -9,6 +9,9 @@
namespace FileSys::SystemArchive {
constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
VirtualFile SynthesizeSystemArchive(u64 title_id);
} // namespace FileSys::SystemArchive

View File

@@ -717,6 +717,14 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC,
sdmc_factory->GetSDMCContents());
}
sysdata_imported_dir =
vfs.CreateDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "imported",
FileSys::Mode::ReadWrite);
}
FileSys::VirtualDir FileSystemController::GetSysdataImportedDirectory() const {
return sysdata_imported_dir;
}
void InstallInterfaces(Core::System& system) {

View File

@@ -116,6 +116,8 @@ public:
FileSys::VirtualDir GetBCATDirectory(u64 title_id) const;
FileSys::VirtualDir GetSysdataImportedDirectory() const;
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
@@ -130,6 +132,8 @@ private:
std::unique_ptr<FileSys::RegisteredCache> gamecard_registered;
std::unique_ptr<FileSys::PlaceholderCache> gamecard_placeholder;
FileSys::VirtualDir sysdata_imported_dir;
Core::System& system;
};

View File

@@ -21,6 +21,7 @@
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/system_archive/importer.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
@@ -914,7 +915,12 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (data.Failed()) {
const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
auto archive = FileSys::SystemArchive::GetImportedSystemArchive(
fsc.GetSystemNANDContentDirectory(), title_id);
if (archive == nullptr) {
archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
}
if (archive != nullptr) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};

View File

@@ -103,6 +103,9 @@ add_executable(yuzu
main.cpp
main.h
main.ui
status/system_archive.cpp
status/system_archive.h
status/system_archive.ui
uisettings.cpp
uisettings.h
util/limitable_input_dialog.cpp

View File

@@ -27,6 +27,7 @@
#include "core/hle/service/am/applets/applets.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "yuzu/status/system_archive.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -40,6 +41,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
return dir->CreateFile(path);
}
static bool VfsDirectoryDeleteFileWrapper(const FileSys::VirtualDir& dir, const std::string& path) {
return dir->DeleteFile(path);
}
#include <fmt/ostream.h>
#include <glad/glad.h>
@@ -83,6 +88,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/system_archive/importer.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/am/am.h"
@@ -760,6 +766,16 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Capture_Screenshot, &QAction::triggered, this,
&GMainWindow::OnCaptureScreenshot);
// Tools
connect(ui.actionImport_Directory, &QAction::triggered, this,
&GMainWindow::OnImportDirectorySystemUpdate);
connect(ui.actionImport_Cartridge, &QAction::triggered, this,
&GMainWindow::OnImportCartridgeSystemUpdate);
connect(ui.actionClear_Imported, &QAction::triggered, this,
&GMainWindow::OnClearImportedSysdata);
connect(ui.actionView_Status, &QAction::triggered, this,
&GMainWindow::OnViewSystemArchiveStatus);
// Help
connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
connect(ui.action_Rederive, &QAction::triggered, this,
@@ -897,6 +913,23 @@ bool GMainWindow::LoadROM(const QString& filename) {
"wiki</a>. This message will not be shown again."));
}
const auto no_imported =
system.GetFileSystemController().GetSysdataImportedDirectory()->GetFiles().empty();
if (result == Core::System::ResultStatus::Success &&
system.GetAppLoader().GetFileType() == Loader::FileType::XCI && no_imported) {
if (QMessageBox::question(this, tr("Import System Archives"),
tr("The game type you are using includes additional system files "
"that may improve yuzu's compatibility with this and other "
"games. Would you like to import these files?")) ==
QMessageBox::Yes) {
const auto game = Core::GetGameFileFromPath(vfs, filename.toStdString());
FileSys::XCI xci{game};
FileSys::SystemArchive::ImportXCISystemUpdate(
system.GetFileSystemController().GetSysdataImportedDirectory(), xci);
}
}
if (result != Core::System::ResultStatus::Success) {
switch (result) {
case Core::System::ResultStatus::ErrorGetLoader:
@@ -1341,6 +1374,71 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
void GMainWindow::OnClearImportedSysdata() {
Core::System& system{Core::System::GetInstance()};
const auto path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + "imported" DIR_SEP;
QDir dir(QString::fromStdString(path));
const auto list = dir.entryList(QDir::Files);
for (const auto& file : list) {
if (!dir.remove(file)) {
QMessageBox::warning(this, tr("Clear Failed"),
tr("The imported sysdata directory was not able to be cleared."));
return;
}
}
QMessageBox::information(this, tr("Clear Successful"),
tr("The imported sysdata directory was cleared successfully."));
}
void GMainWindow::OnImportDirectorySystemUpdate() {
Core::System& system{Core::System::GetInstance()};
const auto dir = QFileDialog::getExistingDirectory(this, tr("Select System Update Directory"));
if (dir.isEmpty()) {
return;
}
const auto vdir = vfs->OpenDirectory(dir.toStdString(), FileSys::Mode::Read);
if (FileSys::SystemArchive::ImportDirectorySystemUpdate(
system.GetFileSystemController().GetSysdataImportedDirectory(), vdir)) {
QMessageBox::information(this, tr("Import Successful"),
tr("The system update import was successful."));
} else {
QMessageBox::warning(this, tr("Import Failed"), tr("The system update import failed."));
}
}
void GMainWindow::OnImportCartridgeSystemUpdate() {
Core::System& system{Core::System::GetInstance()};
const auto file = QFileDialog::getOpenFileName(this, tr("Select Cartridge File"), QString{},
QStringLiteral("Cartridge Images (*.xci)"));
if (file.isEmpty()) {
return;
}
FileSys::XCI xci{vfs->OpenFile(file.toStdString(), FileSys::Mode::Read)};
if (FileSys::SystemArchive::ImportXCISystemUpdate(
system.GetFileSystemController().GetSysdataImportedDirectory(), xci)) {
QMessageBox::information(this, tr("Import Successful"),
tr("The system update import was successful."));
} else {
QMessageBox::warning(this, tr("Import Failed"), tr("The system update import failed."));
}
}
void GMainWindow::OnViewSystemArchiveStatus() {
Core::System& system{Core::System::GetInstance()};
system.GetFileSystemController().CreateFactories(*vfs);
SystemArchiveDialog dialog(this, system.GetFileSystemController());
dialog.exec();
}
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
QString path;
if (directory == QStringLiteral("SDMC")) {

View File

@@ -190,6 +190,10 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
void OnClearImportedSysdata();
void OnImportDirectorySystemUpdate();
void OnImportCartridgeSystemUpdate();
void OnViewSystemArchiveStatus();
void OnGameListOpenDirectory(const QString& directory);
void OnGameListAddDirectory();
void OnGameListShowList(bool show);

View File

@@ -104,9 +104,22 @@
<property name="title">
<string>Tools</string>
</property>
<widget class="QMenu" name="menuSystem_Archives">
<property name="title">
<string>System Archives</string>
</property>
<addaction name="actionImport_Directory"/>
<addaction name="actionImport_Cartridge"/>
<addaction name="separator"/>
<addaction name="actionClear_Imported"/>
<addaction name="separator"/>
<addaction name="actionView_Status"/>
</widget>
<addaction name="action_Rederive"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="separator"/>
<addaction name="menuSystem_Archives"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
@@ -293,6 +306,26 @@
<string>Capture Screenshot</string>
</property>
</action>
<action name="actionImport_Directory">
<property name="text">
<string>Import Directory</string>
</property>
</action>
<action name="actionImport_Cartridge">
<property name="text">
<string>Import Cartridge</string>
</property>
</action>
<action name="actionClear_Imported">
<property name="text">
<string>Clear Imported</string>
</property>
</action>
<action name="actionView_Status">
<property name="text">
<string>View Status</string>
</property>
</action>
</widget>
<resources/>
<connections/>

View File

@@ -0,0 +1,76 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QLabel>
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/system_archive/importer.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "ui_system_archive.h"
#include "yuzu/status/system_archive.h"
namespace {
enum class Source {
NAND,
XCI,
OSS,
None,
};
constexpr std::array<const char*, 4> SOURCE_FORMAT = {
"<html><head/><body><p><span style=\"color:#00aa00;\"><b>8{:02X}</b></span></p></body></html>",
"<html><head/><body><p><span style=\"color:#0055ff;\"><b>8{:02X}</b></span></p></body></html>",
"<html><head/><body><p><span style=\"color:#aa00ff;\"><b>8{:02X}</b></span></p></body></html>",
"<html><head/><body><p><span style=\"color:#aa0000;\"><b>8{:02X}</b></span></p></body></html>",
};
QWidget* CreateItemForSourceAndId(QWidget* parent, Source source, std::size_t id) {
const auto text = fmt::format(SOURCE_FORMAT.at(static_cast<std::size_t>(source)), id);
auto* out = new QLabel(QString::fromStdString(text), parent);
out->setAlignment(Qt::AlignHCenter);
return out;
}
QWidget* CreateItem(QWidget* parent, const Service::FileSystem::FileSystemController& fsc,
std::size_t suffix) {
const auto title_id = FileSys::SystemArchive::SYSTEM_ARCHIVE_BASE_TITLE_ID + suffix;
const auto nand =
fsc.OpenRomFS(title_id, FileSys::StorageId::NandSystem, FileSys::ContentRecordType::Data);
if (nand.Succeeded()) {
return CreateItemForSourceAndId(parent, Source::NAND, suffix);
}
auto archive = FileSys::SystemArchive::GetImportedSystemArchive(
fsc.GetSysdataImportedDirectory(), title_id);
if (archive != nullptr) {
return CreateItemForSourceAndId(parent, Source::XCI, suffix);
}
archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
if (archive != nullptr) {
return CreateItemForSourceAndId(parent, Source::OSS, suffix);
}
return CreateItemForSourceAndId(parent, Source::None, suffix);
}
} // Anonymous namespace
SystemArchiveDialog::SystemArchiveDialog(QWidget* parent,
const Service::FileSystem::FileSystemController& fsc)
: QDialog(parent), ui(new Ui::SystemArchiveDialog) {
ui->setupUi(this);
for (std::size_t i = 0; i < FileSys::SystemArchive::SYSTEM_ARCHIVE_COUNT; ++i) {
ui->grid->addWidget(CreateItem(this, fsc, i), i / 6, i % 6, 1, 1);
}
}
SystemArchiveDialog::~SystemArchiveDialog() = default;

View File

@@ -0,0 +1,28 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
namespace Service::FileSystem {
class FileSystemController;
} // namespace Service::FileSystem
namespace Ui {
class SystemArchiveDialog;
} // namespace Ui
class SystemArchiveDialog : public QDialog {
Q_OBJECT
public:
explicit SystemArchiveDialog(QWidget* parent,
const Service::FileSystem::FileSystemController& fsc);
~SystemArchiveDialog() override;
private:
std::unique_ptr<Ui::SystemArchiveDialog> ui;
};

View File

@@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SystemArchiveDialog</class>
<widget class="QDialog" name="SystemArchiveDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>422</height>
</rect>
</property>
<property name="windowTitle">
<string>System Archive Status</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>System Archive Status</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>System archives are special data files games and applications can use for common functions, such as filtering for bad words or showing Miis on screen.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>yuzu can load system archives from 3 sources:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string> 1. A real NAND dump placed into yuzu's NAND directory</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string> 2. Archives imported from an XCI/cartridge game</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string> 3. Open source reimplementations from the yuzu team</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_7">
<property name="text">
<string>The following table shows the source for all of the system archives:</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="grid"/>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_8">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#00aa00;&quot;&gt;NAND Dump&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_9">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#0055ff;&quot;&gt;Cartridge Dump&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#aa00ff;&quot;&gt;Open Source&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_11">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; color:#aa0000;&quot;&gt;Missing&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SystemArchiveDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SystemArchiveDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>