summaryrefslogtreecommitdiffstats
path: root/src/core/file_sys
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/file_sys/bis_factory.cpp9
-rw-r--r--src/core/file_sys/bis_factory.h4
-rw-r--r--src/core/file_sys/card_image.cpp2
-rw-r--r--src/core/file_sys/card_image.h16
-rw-r--r--src/core/file_sys/content_archive.cpp38
-rw-r--r--src/core/file_sys/content_archive.h10
-rw-r--r--src/core/file_sys/control_metadata.cpp56
-rw-r--r--src/core/file_sys/control_metadata.h31
-rw-r--r--src/core/file_sys/directory.h4
-rw-r--r--src/core/file_sys/errors.h25
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h1
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/patch_manager.cpp147
-rw-r--r--src/core/file_sys/patch_manager.h5
-rw-r--r--src/core/file_sys/program_metadata.cpp21
-rw-r--r--src/core/file_sys/program_metadata.h6
-rw-r--r--src/core/file_sys/registered_cache.cpp170
-rw-r--r--src/core/file_sys/registered_cache.h36
-rw-r--r--src/core/file_sys/romfs_factory.cpp2
-rw-r--r--src/core/file_sys/savedata_factory.cpp71
-rw-r--r--src/core/file_sys/savedata_factory.h15
-rw-r--r--src/core/file_sys/submission_package.cpp2
-rw-r--r--src/core/file_sys/submission_package.h2
-rw-r--r--src/core/file_sys/system_archive/ng_word.cpp81
-rw-r--r--src/core/file_sys/system_archive/ng_word.h14
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp90
-rw-r--r--src/core/file_sys/system_archive/system_archive.h14
-rw-r--r--src/core/file_sys/vfs.cpp50
-rw-r--r--src/core/file_sys/vfs.h32
-rw-r--r--src/core/file_sys/vfs_offset.cpp4
-rw-r--r--src/core/file_sys/vfs_offset.h2
-rw-r--r--src/core/file_sys/vfs_static.h4
-rw-r--r--src/core/file_sys/vfs_vector.cpp1
-rw-r--r--src/core/file_sys/vfs_vector.h53
34 files changed, 775 insertions, 247 deletions
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 76a2b7e86..e29f70b3a 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -8,8 +8,9 @@
namespace FileSys {
-BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
+BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir dump_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
+ dump_root(std::move(dump_root_)),
sysnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_unique<RegisteredCache>(
@@ -32,4 +33,10 @@ VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
}
+VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
+ if (title_id == 0)
+ return nullptr;
+ return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
+}
+
} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 364d309bd..453c11ad2 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -17,17 +17,19 @@ class RegisteredCache;
/// registered caches.
class BISFactory {
public:
- explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
+ explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root);
~BISFactory();
RegisteredCache* GetSystemNANDContents() const;
RegisteredCache* GetUserNANDContents() const;
VirtualDir GetModificationLoadRoot(u64 title_id) const;
+ VirtualDir GetModificationDumpRoot(u64 title_id) const;
private:
VirtualDir nand_root;
VirtualDir load_root;
+ VirtualDir dump_root;
std::unique_ptr<RegisteredCache> sysnand_cache;
std::unique_ptr<RegisteredCache> usrnand_cache;
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 1ece55731..2c145bd09 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -176,7 +176,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) {
if (file->GetExtension() != "nca")
continue;
- auto nca = std::make_shared<NCA>(file);
+ auto nca = std::make_shared<NCA>(file, nullptr, 0, keys);
// TODO(DarkLordZach): Add proper Rev1+ Support
if (nca->IsUpdate())
continue;
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 8f62571cf..a350496f7 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -9,6 +9,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs.h"
namespace Loader {
@@ -31,7 +32,18 @@ enum class GamecardSize : u8 {
};
struct GamecardInfo {
- std::array<u8, 0x70> data;
+ u64_le firmware_version;
+ u32_le access_control_flags;
+ u32_le read_wait_time1;
+ u32_le read_wait_time2;
+ u32_le write_wait_time1;
+ u32_le write_wait_time2;
+ u32_le firmware_mode;
+ u32_le cup_version;
+ std::array<u8, 4> reserved1;
+ u64_le update_partition_hash;
+ u64_le cup_id;
+ std::array<u8, 0x38> reserved2;
};
static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size.");
@@ -107,5 +119,7 @@ private:
std::shared_ptr<NSP> secure_partition;
std::shared_ptr<NCA> program;
std::vector<std::shared_ptr<NCA>> ncas;
+
+ Core::Crypto::KeyManager keys;
};
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 77e04704e..19b6f8600 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -4,10 +4,9 @@
#include <algorithm>
#include <cstring>
+#include <optional>
#include <utility>
-#include <boost/optional.hpp>
-
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
@@ -102,8 +101,9 @@ static bool IsValidNCA(const NCAHeader& header) {
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset,
+ Core::Crypto::KeyManager keys_)
+ : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)), keys(std::move(keys_)) {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
return;
@@ -306,18 +306,18 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
subsection_buckets.back().entries.push_back({size, {0}, 0});
- boost::optional<Core::Crypto::Key128> key = boost::none;
+ std::optional<Core::Crypto::Key128> key = {};
if (encrypted) {
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
- if (key == boost::none) {
+ if (!key) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
return false;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
- if (key == boost::none) {
+ if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return false;
}
@@ -332,7 +332,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
auto bktr = std::make_shared<BKTR>(
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
- encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
+ encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
section.raw.section_ctr);
// BKTR applies to entire IVFC, so make an offset version to level 6
@@ -388,11 +388,11 @@ u8 NCA::GetCryptoRevision() const {
return master_key_id;
}
-boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
+std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
- return boost::none;
+ return {};
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -416,25 +416,25 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
return out;
}
-boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
+std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
const auto master_key_id = GetCryptoRevision();
u128 rights_id{};
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
- return boost::none;
+ return {};
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
- return boost::none;
+ return {};
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
- return boost::none;
+ return {};
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -458,25 +458,25 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
case NCASectionCryptoType::BKTR:
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
- boost::optional<Core::Crypto::Key128> key = boost::none;
+ std::optional<Core::Crypto::Key128> key = {};
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
- if (key == boost::none) {
+ if (!key) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
- if (key == boost::none) {
+ if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
}
- auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
- std::move(in), key.value(), starting_offset);
+ auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
+ starting_offset);
std::vector<u8> iv(16);
for (u8 i = 0; i < 8; ++i)
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 211946686..99294cbb4 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -6,9 +6,10 @@
#include <array>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -78,7 +79,8 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
class NCA : public ReadOnlyVfsDirectory {
public:
explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
- u64 bktr_base_ivfc_offset = 0);
+ u64 bktr_base_ivfc_offset = 0,
+ Core::Crypto::KeyManager keys = Core::Crypto::KeyManager());
~NCA() override;
Loader::ResultStatus GetStatus() const;
@@ -111,8 +113,8 @@ private:
bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
u8 GetCryptoRevision() const;
- boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
- boost::optional<Core::Crypto::Key128> GetTitlekey();
+ std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
+ std::optional<Core::Crypto::Key128> GetTitlekey();
VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
std::vector<VirtualDir> dirs;
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index a012c2be9..83c184750 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -8,13 +8,23 @@
namespace FileSys {
-const std::array<const char*, 15> LANGUAGE_NAMES = {
- "AmericanEnglish", "BritishEnglish", "Japanese",
- "French", "German", "LatinAmericanSpanish",
- "Spanish", "Italian", "Dutch",
- "CanadianFrench", "Portugese", "Russian",
- "Korean", "Taiwanese", "Chinese",
-};
+const std::array<const char*, 15> LANGUAGE_NAMES{{
+ "AmericanEnglish",
+ "BritishEnglish",
+ "Japanese",
+ "French",
+ "German",
+ "LatinAmericanSpanish",
+ "Spanish",
+ "Italian",
+ "Dutch",
+ "CanadianFrench",
+ "Portuguese",
+ "Russian",
+ "Korean",
+ "Taiwanese",
+ "Chinese",
+}};
std::string LanguageEntry::GetApplicationName() const {
return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
@@ -26,18 +36,20 @@ std::string LanguageEntry::GetDeveloperName() const {
developer_name.size());
}
-NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
- file->ReadObject(raw.get());
+NACP::NACP() = default;
+
+NACP::NACP(VirtualFile file) {
+ file->ReadObject(&raw);
}
NACP::~NACP() = default;
const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
if (language != Language::Default) {
- return raw->language_entries.at(static_cast<u8>(language));
+ return raw.language_entries.at(static_cast<u8>(language));
}
- for (const auto& language_entry : raw->language_entries) {
+ for (const auto& language_entry : raw.language_entries) {
if (!language_entry.GetApplicationName().empty())
return language_entry;
}
@@ -55,15 +67,29 @@ std::string NACP::GetDeveloperName(Language language) const {
}
u64 NACP::GetTitleId() const {
- return raw->title_id;
+ return raw.title_id;
}
u64 NACP::GetDLCBaseTitleId() const {
- return raw->dlc_base_title_id;
+ return raw.dlc_base_title_id;
}
std::string NACP::GetVersionString() const {
- return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
- raw->version_string.size());
+ return Common::StringFromFixedZeroTerminatedBuffer(raw.version_string.data(),
+ raw.version_string.size());
+}
+
+u64 NACP::GetDefaultNormalSaveSize() const {
+ return raw.normal_save_data_size;
+}
+
+u64 NACP::GetDefaultJournalSaveSize() const {
+ return raw.journal_sava_data_size;
+}
+
+std::vector<u8> NACP::GetRawBytes() const {
+ std::vector<u8> out(sizeof(RawNACP));
+ std::memcpy(out.data(), &raw, sizeof(RawNACP));
+ return out;
}
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 141f7e056..7b9cdc910 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -28,17 +28,30 @@ static_assert(sizeof(LanguageEntry) == 0x300, "LanguageEntry has incorrect size.
// The raw file format of a NACP file.
struct RawNACP {
std::array<LanguageEntry, 16> language_entries;
- INSERT_PADDING_BYTES(0x38);
+ std::array<u8, 0x25> isbn;
+ u8 startup_user_account;
+ INSERT_PADDING_BYTES(2);
+ u32_le application_attribute;
+ u32_le supported_languages;
+ u32_le parental_control;
+ bool screenshot_enabled;
+ u8 video_capture_mode;
+ bool data_loss_confirmation;
+ INSERT_PADDING_BYTES(1);
u64_le title_id;
- INSERT_PADDING_BYTES(0x20);
+ std::array<u8, 0x20> rating_age;
std::array<char, 0x10> version_string;
u64_le dlc_base_title_id;
u64_le title_id_2;
- INSERT_PADDING_BYTES(0x28);
+ u64_le normal_save_data_size;
+ u64_le journal_sava_data_size;
+ INSERT_PADDING_BYTES(0x18);
u64_le product_code;
- u64_le title_id_3;
- std::array<u64_le, 0x7> title_id_array;
- INSERT_PADDING_BYTES(0x8);
+ std::array<u64_le, 0x8> local_communication;
+ u8 logo_type;
+ u8 logo_handling;
+ bool runtime_add_on_content_install;
+ INSERT_PADDING_BYTES(5);
u64_le title_id_update;
std::array<u8, 0x40> bcat_passphrase;
INSERT_PADDING_BYTES(0xEC0);
@@ -72,6 +85,7 @@ extern const std::array<const char*, 15> LANGUAGE_NAMES;
// These store application name, dev name, title id, and other miscellaneous data.
class NACP {
public:
+ explicit NACP();
explicit NACP(VirtualFile file);
~NACP();
@@ -81,9 +95,12 @@ public:
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
+ u64 GetDefaultNormalSaveSize() const;
+ u64 GetDefaultJournalSaveSize() const;
+ std::vector<u8> GetRawBytes() const;
private:
- std::unique_ptr<RawNACP> raw;
+ RawNACP raw{};
};
} // namespace FileSys
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index 12bb90ec8..6690aa575 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -29,8 +29,8 @@ struct Entry {
filename[copy_size] = '\0';
}
- char filename[0x300];
- INSERT_PADDING_BYTES(4);
+ char filename[0x301];
+ INSERT_PADDING_BYTES(3);
EntryType type;
INSERT_PADDING_BYTES(3);
u64 file_size;
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index fea0593c7..e4a4ee4ab 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -8,25 +8,10 @@
namespace FileSys {
-namespace ErrCodes {
-enum {
- NotFound = 1,
- TitleNotFound = 1002,
- SdCardNotFound = 2001,
- RomFSNotFound = 2520,
-};
-}
-
-constexpr ResultCode ERROR_PATH_NOT_FOUND(ErrorModule::FS, ErrCodes::NotFound);
-
-// TODO(bunnei): Replace these with correct errors for Switch OS
-constexpr ResultCode ERROR_INVALID_PATH(-1);
-constexpr ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(-1);
-constexpr ResultCode ERROR_INVALID_OPEN_FLAGS(-1);
-constexpr ResultCode ERROR_FILE_NOT_FOUND(-1);
-constexpr ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(-1);
-constexpr ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(-1);
-constexpr ResultCode ERROR_FILE_ALREADY_EXISTS(-1);
-constexpr ResultCode ERROR_DIRECTORY_NOT_EMPTY(-1);
+constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
+constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
+constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
+constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
+constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
} // namespace FileSys
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index 3d377b0af..a62502193 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -27,7 +27,6 @@
#include <map>
#include <memory>
#include <string>
-#include <boost/detail/container_fwd.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 999939d5a..485c4913a 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -103,12 +103,12 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);
- if (data == boost::none)
+ if (!data)
return nullptr;
if (real_offset + rle_size > in_data.size())
rle_size = static_cast<u16>(in_data.size() - real_offset);
- std::memset(in_data.data() + real_offset, data.get(), rle_size);
+ std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch
auto read = data_size;
if (real_offset + read > in_data.size())
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index cb457b987..61706966e 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -19,12 +19,18 @@
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
+#include "core/settings.h"
namespace FileSys {
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
+constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
+ "main", "main.npdm", "rtld", "sdk", "subsdk0", "subsdk1", "subsdk2",
+ "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
+};
+
struct NSOBuildHeader {
u32_le magic;
INSERT_PADDING_BYTES(0x3C);
@@ -50,33 +56,82 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
+u64 PatchManager::GetTitleID() const {
+ return title_id;
+}
+
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
if (exefs == nullptr)
return exefs;
+ if (Settings::values.dump_exefs) {
+ LOG_INFO(Loader, "Dumping ExeFS for title_id={:016X}", title_id);
+ const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
+ if (dump_dir != nullptr) {
+ const auto exefs_dir = GetOrCreateDirectoryRelative(dump_dir, "/exefs");
+ VfsRawCopyD(exefs, exefs_dir);
+ }
+ }
+
const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
+ const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
- if (update != nullptr && update->GetExeFS() != nullptr &&
+ if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+ FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
exefs = update->GetExeFS();
}
+ // LayeredExeFS
+ const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
+ if (load_dir != nullptr && load_dir->GetSize() > 0) {
+ auto patch_dirs = load_dir->GetSubdirectories();
+ std::sort(
+ patch_dirs.begin(), patch_dirs.end(),
+ [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
+
+ std::vector<VirtualDir> layers;
+ layers.reserve(patch_dirs.size() + 1);
+ for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
+ auto exefs_dir = subdir->GetSubdirectory("exefs");
+ if (exefs_dir != nullptr)
+ layers.push_back(std::move(exefs_dir));
+ }
+ layers.push_back(exefs);
+
+ auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
+ if (layered != nullptr) {
+ LOG_INFO(Loader, " ExeFS: LayeredExeFS patches applied successfully");
+ exefs = std::move(layered);
+ }
+ }
+
return exefs;
}
-static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) {
+std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const {
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
@@ -119,6 +174,18 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
const auto build_id_raw = Common::HexArrayToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
+ if (Settings::values.dump_nso) {
+ LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
+ const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
+ if (dump_dir != nullptr) {
+ const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
+ const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
+
+ file->Resize(nso.size());
+ file->WriteBytes(nso);
+ }
+ }
+
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
@@ -177,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
+ const auto& disabled = Settings::values.disabled_addons[title_id];
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -186,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
@@ -215,13 +286,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
- title_id, static_cast<u8>(type))
- .c_str();
+ title_id, static_cast<u8>(type));
if (type == ContentRecordType::Program || type == ContentRecordType::Data)
- LOG_INFO(Loader, log_string);
+ LOG_INFO(Loader, "{}", log_string);
else
- LOG_DEBUG(Loader, log_string);
+ LOG_DEBUG(Loader, "{}", log_string);
if (romfs == nullptr)
return romfs;
@@ -230,16 +300,21 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
- const auto update = installed->GetEntryRaw(update_tid, type);
- if (update != nullptr) {
+ const auto update = installed.GetEntryRaw(update_tid, type);
+
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+
+ if (!update_disabled && update != nullptr) {
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+ FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
- } else if (update_raw != nullptr) {
+ } else if (!update_disabled && update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -269,26 +344,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid};
auto [nacp, discard_icon_file] = update.GetControlMetadata();
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+ const auto update_label = update_disabled ? "[D] Update" : "Update";
+
if (nacp != nullptr) {
- out.insert_or_assign("Update", nacp->GetVersionString());
+ out.insert_or_assign(update_label, nacp->GetVersionString());
} else {
- if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
- const auto meta_ver = installed->GetEntryVersion(update_tid);
- if (meta_ver == boost::none || meta_ver.get() == 0) {
- out.insert_or_assign("Update", "");
+ if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
+ const auto meta_ver = installed.GetEntryVersion(update_tid);
+ if (meta_ver.value_or(0) == 0) {
+ out.insert_or_assign(update_label, "");
} else {
out.insert_or_assign(
- "Update",
- FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
+ update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
- out.insert_or_assign("Update", "PACKED");
+ out.insert_or_assign(update_label, "PACKED");
}
}
@@ -302,18 +381,25 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (IsDirValidAndNonEmpty(exefs_dir)) {
bool ips = false;
bool ipswitch = false;
+ bool layeredfs = false;
for (const auto& file : exefs_dir->GetFiles()) {
- if (file->GetExtension() == "ips")
+ if (file->GetExtension() == "ips") {
ips = true;
- else if (file->GetExtension() == "pchtxt")
+ } else if (file->GetExtension() == "pchtxt") {
ipswitch = true;
+ } else if (std::find(EXEFS_FILE_NAMES.begin(), EXEFS_FILE_NAMES.end(),
+ file->GetName()) != EXEFS_FILE_NAMES.end()) {
+ layeredfs = true;
+ }
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
+ if (layeredfs)
+ AppendCommaIfNotEmpty(types, "LayeredExeFS");
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
@@ -321,19 +407,20 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (types.empty())
continue;
- out.insert_or_assign(mod->GetName(), types);
+ const auto mod_disabled =
+ std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
+ out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
}
}
// DLC
- const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
+ const auto dlc_entries = installed.ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<RegisteredCacheEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[this, &installed](const RegisteredCacheEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
- installed->GetEntry(entry)->GetStatus() ==
- Loader::ResultStatus::Success;
+ installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
});
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
@@ -345,7 +432,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
- out.insert_or_assign("DLC", std::move(list));
+ const auto dlc_disabled =
+ std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
+ out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
}
return out;
@@ -354,7 +443,7 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
const auto installed{Service::FileSystem::GetUnionContents()};
- const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
+ const auto base_control_nca = installed.GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
return {};
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 7d168837f..b8a1652fd 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -30,6 +30,8 @@ public:
explicit PatchManager(u64 title_id);
~PatchManager();
+ u64 GetTitleID() const;
+
// Currently tracked ExeFS patches:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
@@ -63,6 +65,9 @@ public:
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
+ std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const;
+
u64 title_id;
};
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 8903ed1d3..d3e00437f 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -40,6 +40,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
if (sizeof(FileAccessHeader) != file->ReadObject(&aci_file_access, aci_header.fah_offset))
return Loader::ResultStatus::ErrorBadFileAccessHeader;
+ aci_kernel_capabilities.resize(aci_header.kac_size / sizeof(u32));
+ const u64 read_size = aci_header.kac_size;
+ const u64 read_offset = npdm_header.aci_offset + aci_header.kac_offset;
+ if (file->ReadBytes(aci_kernel_capabilities.data(), read_size, read_offset) != read_size) {
+ return Loader::ResultStatus::ErrorBadKernelCapabilityDescriptors;
+ }
+
return Loader::ResultStatus::Success;
}
@@ -71,6 +78,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
return aci_file_access.permissions;
}
+const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
+ return aci_kernel_capabilities;
+}
+
void ProgramMetadata::Print() const {
LOG_DEBUG(Service_FS, "Magic: {:.4}", npdm_header.magic.data());
LOG_DEBUG(Service_FS, "Main thread priority: 0x{:02X}", npdm_header.main_thread_priority);
@@ -81,16 +92,20 @@ void ProgramMetadata::Print() const {
LOG_DEBUG(Service_FS, " > 64-bit instructions: {}",
npdm_header.has_64_bit_instructions ? "YES" : "NO");
- auto address_space = "Unknown";
+ const char* address_space = "Unknown";
switch (npdm_header.address_space_type) {
case ProgramAddressSpaceType::Is36Bit:
+ address_space = "64-bit (36-bit address space)";
+ break;
case ProgramAddressSpaceType::Is39Bit:
- address_space = "64-bit";
+ address_space = "64-bit (39-bit address space)";
break;
case ProgramAddressSpaceType::Is32Bit:
- case ProgramAddressSpaceType::Is32BitNoMap:
address_space = "32-bit";
break;
+ case ProgramAddressSpaceType::Is32BitNoMap:
+ address_space = "32-bit (no map region)";
+ break;
}
LOG_DEBUG(Service_FS, " > Address space: {}\n", address_space);
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index e4470d6f0..0033ba347 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -38,6 +39,8 @@ enum class ProgramFilePermission : u64 {
*/
class ProgramMetadata {
public:
+ using KernelCapabilityDescriptors = std::vector<u32>;
+
ProgramMetadata();
~ProgramMetadata();
@@ -50,6 +53,7 @@ public:
u32 GetMainThreadStackSize() const;
u64 GetTitleID() const;
u64 GetFilesystemPermissions() const;
+ const KernelCapabilityDescriptors& GetKernelCapabilities() const;
void Print() const;
@@ -154,6 +158,8 @@ private:
FileAccessControl acid_file_access;
FileAccessHeader aci_file_access;
+
+ KernelCapabilityDescriptors aci_kernel_capabilities;
};
} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 29b100414..128199063 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -106,40 +106,42 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
- if (dir->GetFileRelative(path) != nullptr)
- return dir->GetFileRelative(path);
- if (dir->GetDirectoryRelative(path) != nullptr) {
- const auto nca_dir = dir->GetDirectoryRelative(path);
- VirtualFile file = nullptr;
-
- const auto files = nca_dir->GetFiles();
- if (files.size() == 1 && files[0]->GetName() == "00") {
- file = files[0];
- } else {
- std::vector<VirtualFile> concat;
- // Since the files are a two-digit hex number, max is FF.
- for (std::size_t i = 0; i < 0x100; ++i) {
- auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
- if (next != nullptr) {
- concat.push_back(std::move(next));
- } else {
- next = nca_dir->GetFile(fmt::format("{:02x}", i));
- if (next != nullptr)
- concat.push_back(std::move(next));
- else
- break;
- }
- }
+ const auto file = dir->GetFileRelative(path);
+ if (file != nullptr) {
+ return file;
+ }
- if (concat.empty())
- return nullptr;
+ const auto nca_dir = dir->GetDirectoryRelative(path);
+ if (nca_dir == nullptr) {
+ return nullptr;
+ }
- file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
+ const auto files = nca_dir->GetFiles();
+ if (files.size() == 1 && files[0]->GetName() == "00") {
+ return files[0];
+ }
+
+ std::vector<VirtualFile> concat;
+ // Since the files are a two-digit hex number, max is FF.
+ for (std::size_t i = 0; i < 0x100; ++i) {
+ auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
+ if (next != nullptr) {
+ concat.push_back(std::move(next));
+ } else {
+ next = nca_dir->GetFile(fmt::format("{:02x}", i));
+ if (next != nullptr) {
+ concat.push_back(std::move(next));
+ } else {
+ break;
+ }
}
+ }
- return file;
+ if (concat.empty()) {
+ return nullptr;
}
- return nullptr;
+
+ return ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
}
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
@@ -159,28 +161,28 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
-static boost::optional<NcaID> CheckMapForContentRecord(
+static std::optional<NcaID> CheckMapForContentRecord(
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
if (map.find(title_id) == map.end())
- return boost::none;
+ return {};
const auto& cnmt = map.at(title_id);
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (iter == cnmt.GetContentRecords().end())
- return boost::none;
+ return {};
- return boost::make_optional(iter->nca_id);
+ return std::make_optional(iter->nca_id);
}
-boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
- ContentRecordType type) const {
+std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+ ContentRecordType type) const {
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
return meta_id.at(title_id);
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
- if (res1 != boost::none)
+ if (res1)
return res1;
return CheckMapForContentRecord(meta, title_id, type);
}
@@ -225,7 +227,7 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
if (file == nullptr)
continue;
- const auto nca = std::make_shared<NCA>(parser(file, id));
+ const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0, keys);
if (nca->GetStatus() != Loader::ResultStatus::Success ||
nca->GetType() != NCAContentType::Meta) {
continue;
@@ -283,17 +285,14 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return GetFileAtID(id.get());
+ return id ? GetFileAtID(*id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
return GetEntryUnparsed(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
if (meta_iter != meta.end())
return meta_iter->second.GetTitleVersion();
@@ -302,15 +301,12 @@ boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
if (yuzu_meta_iter != yuzu_meta.end())
return yuzu_meta_iter->second.GetTitleVersion();
- return boost::none;
+ return {};
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return parser(GetFileAtID(id.get()), id.get());
+ return id ? parser(GetFileAtID(*id), *id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
@@ -321,7 +317,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_unique<NCA>(raw);
+ return std::make_unique<NCA>(raw, nullptr, 0, keys);
}
std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
@@ -364,8 +360,8 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
IterateAllMetadata<RegisteredCacheEntry>(
out,
@@ -373,33 +369,33 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
return out;
}
-static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) {
- const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
+static std::shared_ptr<NCA> GetNCAFromNSPForID(const NSP& nsp, const NcaID& id) {
+ const auto file = nsp.GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
if (file == nullptr)
return nullptr;
return std::make_shared<NCA>(file);
}
-InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
+InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
- return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy);
+ return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy);
}
-InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists,
+InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists,
const VfsCopyFunction& copy) {
- const auto& ncas = nsp->GetNCAsCollapsed();
- const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
+ const auto ncas = nsp.GetNCAsCollapsed();
+ const auto meta_iter = std::find_if(ncas.begin(), ncas.end(), [](const auto& nca) {
return nca->GetType() == NCAContentType::Meta;
});
@@ -413,7 +409,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw
const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
const auto meta_id = Common::HexStringToArray<16>(meta_id_raw);
- const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
+ const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id);
if (res != InstallResult::Success)
return res;
@@ -425,7 +421,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw
const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
if (nca == nullptr)
return InstallResult::ErrorCopyFailed;
- const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
+ const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id);
if (res2 != InstallResult::Success)
return res2;
}
@@ -434,21 +430,21 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overw
return InstallResult::Success;
}
-InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
+InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
bool overwrite_if_exists, const VfsCopyFunction& copy) {
CNMTHeader header{
- nca->GetTitleId(), ///< Title ID
- 0, ///< Ignore/Default title version
- type, ///< Type
- {}, ///< Padding
- 0x10, ///< Default table offset
- 1, ///< 1 Content Entry
- 0, ///< No Meta Entries
- {}, ///< Padding
+ nca.GetTitleId(), ///< Title ID
+ 0, ///< Ignore/Default title version
+ type, ///< Type
+ {}, ///< Padding
+ 0x10, ///< Default table offset
+ 1, ///< 1 Content Entry
+ 0, ///< No Meta Entries
+ {}, ///< Padding
};
OptionalHeader opt_header{0, 0};
- ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
- const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
+ ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca.GetType()), {}};
+ const auto& data = nca.GetBaseFile()->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
memcpy(&c_rec.nca_id, &c_rec.hash, 16);
const CNMT new_cnmt(header, opt_header, {c_rec}, {});
@@ -457,10 +453,10 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
}
-InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
+InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
- boost::optional<NcaID> override_id) {
- const auto in = nca->GetBaseFile();
+ std::optional<NcaID> override_id) {
+ const auto in = nca.GetBaseFile();
Core::Crypto::SHA256Hash hash{};
// Calculate NcaID
@@ -468,12 +464,12 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
// game is massive), we're going to cheat and only hash the first MB of the NCA.
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
NcaID id{};
- if (override_id == boost::none) {
+ if (override_id) {
+ id = *override_id;
+ } else {
const auto& data = in->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
- } else {
- id = override_id.get();
}
std::string path = GetRelativePathFromNcaID(id, false, true);
@@ -543,14 +539,14 @@ bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
return HasEntry(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
for (const auto& c : caches) {
const auto res = c->GetEntryVersion(title_id);
- if (res != boost::none)
+ if (res)
return res;
}
- return boost::none;
+ return {};
}
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
@@ -609,8 +605,8 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
}
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
for (const auto& c : caches) {
c->IterateAllMetadata<RegisteredCacheEntry>(
@@ -619,11 +615,11 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 5beceffb3..3b77af4e0 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -6,12 +6,12 @@
#include <array>
#include <functional>
-#include <map>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
+#include "core/crypto/key_manager.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
@@ -84,7 +84,7 @@ public:
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -96,25 +96,23 @@ public:
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
- InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
+ InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
- InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false,
+ InstallResult InstallEntry(const NSP& nsp, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
// Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
// poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
// dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
// TODO(DarkLordZach): Author real meta-type NCAs and install those.
- InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
- bool overwrite_if_exists = false,
+ InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy);
private:
@@ -125,16 +123,17 @@ private:
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateYuzuMeta();
- boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
+ std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
- InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
- bool overwrite_if_exists,
- boost::optional<NcaID> override_id = boost::none);
+ InstallResult RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy,
+ bool overwrite_if_exists, std::optional<NcaID> override_id = {});
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
RegisteredCacheParsingFunction parser;
+ Core::Crypto::KeyManager keys;
+
// maps tid -> NcaID of meta
boost::container::flat_map<u64, NcaID> meta_id;
// maps tid -> meta
@@ -153,7 +152,7 @@ public:
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -165,11 +164,10 @@ public:
std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
private:
std::vector<RegisteredCache*> caches;
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 0b645b106..6ad1e4f86 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -48,7 +48,7 @@ ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, Conte
switch (storage) {
case StorageId::None:
- res = Service::FileSystem::GetUnionContents()->GetEntry(title_id, type);
+ res = Service::FileSystem::GetUnionContents().GetEntry(title_id, type);
break;
case StorageId::NandSystem:
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index ef1aaebbb..1913dc956 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -13,12 +13,18 @@
namespace FileSys {
+constexpr char SAVE_DATA_SIZE_FILENAME[] = ".yuzu_save_size";
+
std::string SaveDataDescriptor::DebugInfo() const {
return fmt::format("[type={:02X}, title_id={:016X}, user_id={:016X}{:016X}, save_id={:016X}]",
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
}
-SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
+SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
+ // Delete all temporary storages
+ // On hardware, it is expected that temporary storage be empty at first use.
+ dir->DeleteSubdirectoryRecursive("temp");
+}
SaveDataFactory::~SaveDataFactory() = default;
@@ -83,28 +89,32 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
return MakeResult<VirtualDir>(std::move(out));
}
-std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
- u128 user_id, u64 save_id) {
- // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
- // be interpreted as the title id of the current process.
- if (type == SaveDataType::SaveData && title_id == 0)
- title_id = Core::CurrentProcess()->GetTitleID();
-
- std::string out;
+VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
+ return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
+}
+std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
switch (space) {
case SaveDataSpaceId::NandSystem:
- out = "/system/";
- break;
+ return "/system/";
case SaveDataSpaceId::NandUser:
- out = "/user/";
- break;
+ return "/user/";
case SaveDataSpaceId::TemporaryStorage:
- out = "/temp/";
- break;
+ return "/temp/";
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
+ return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
}
+}
+
+std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
+ u128 user_id, u64 save_id) {
+ // According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
+ // be interpreted as the title id of the current process.
+ if (type == SaveDataType::SaveData && title_id == 0)
+ title_id = Core::CurrentProcess()->GetTitleID();
+
+ std::string out = GetSaveDataSpaceIdPath(space);
switch (type) {
case SaveDataType::SystemSaveData:
@@ -116,9 +126,40 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::TemporaryStorage:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
+ case SaveDataType::CacheStorage:
+ return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
+ return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id);
}
}
+SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
+ u128 user_id) const {
+ const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
+
+ const auto size_file = dir->GetFile(SAVE_DATA_SIZE_FILENAME);
+ if (size_file == nullptr || size_file->GetSize() < sizeof(SaveDataSize))
+ return {0, 0};
+
+ SaveDataSize out;
+ if (size_file->ReadObject(&out) != sizeof(SaveDataSize))
+ return {0, 0};
+ return out;
+}
+
+void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
+ SaveDataSize new_value) {
+ const auto path = GetFullPath(SaveDataSpaceId::NandUser, type, title_id, user_id, 0);
+ const auto dir = GetOrCreateDirectoryRelative(this->dir, path);
+
+ const auto size_file = dir->CreateFile(SAVE_DATA_SIZE_FILENAME);
+ if (size_file == nullptr)
+ return;
+
+ size_file->Resize(sizeof(SaveDataSize));
+ size_file->WriteObject(new_value);
+}
+
} // namespace FileSys
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index d69ef6741..3a1caf292 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -17,8 +17,10 @@ namespace FileSys {
enum class SaveDataSpaceId : u8 {
NandSystem = 0,
NandUser = 1,
- SdCard = 2,
+ SdCardSystem = 2,
TemporaryStorage = 3,
+ SdCardUser = 4,
+ ProperSystem = 100,
};
enum class SaveDataType : u8 {
@@ -44,6 +46,11 @@ struct SaveDataDescriptor {
};
static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorrect size.");
+struct SaveDataSize {
+ u64 normal;
+ u64 journal;
+};
+
/// File system interface to the SaveData archive
class SaveDataFactory {
public:
@@ -52,9 +59,15 @@ public:
ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta);
+ VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const;
+
+ static std::string GetSaveDataSpaceIdPath(SaveDataSpaceId space);
static std::string GetFullPath(SaveDataSpaceId space, SaveDataType type, u64 title_id,
u128 user_id, u64 save_id);
+ SaveDataSize ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const;
+ void WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value);
+
private:
VirtualDir dir;
};
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 2aaba4179..e1a4210db 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -252,7 +252,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
continue;
}
- auto next_nca = std::make_shared<NCA>(next_file);
+ auto next_nca = std::make_shared<NCA>(next_file, nullptr, 0, keys);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 338080b7e..9a28ed5bb 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -70,6 +70,8 @@ private:
std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
std::vector<VirtualFile> ticket_files;
+ Core::Crypto::KeyManager keys;
+
VirtualFile romfs;
VirtualDir exefs;
};
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
new file mode 100644
index 000000000..f4443784d
--- /dev/null
+++ b/src/core/file_sys/system_archive/ng_word.cpp
@@ -0,0 +1,81 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+#include "common/common_types.h"
+#include "core/file_sys/system_archive/ng_word.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys::SystemArchive {
+
+namespace NgWord1Data {
+
+constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10;
+
+// Should this archive replacement mysteriously not work on a future game, consider updating.
+constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x19}; // 5.1.0 System Version
+
+constexpr std::array<u8, 30> WORD_TXT{
+ 0xFE, 0xFF, 0x00, 0x5E, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x62, 0x00,
+ 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0A,
+}; // "^verybadword$" in UTF-16
+
+} // namespace NgWord1Data
+
+VirtualDir NgWord1() {
+ std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES);
+
+ for (std::size_t i = 0; i < files.size(); ++i) {
+ files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
+ NgWord1Data::WORD_TXT, fmt::format("{}.txt", i));
+ }
+
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
+ NgWord1Data::WORD_TXT, "common.txt"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>(
+ NgWord1Data::VERSION_DAT, "version.dat"));
+
+ return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+}
+
+namespace NgWord2Data {
+
+constexpr std::size_t NUMBER_AC_NX_FILES = 0x10;
+
+// Should this archive replacement mysteriously not work on a future game, consider updating.
+constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x15}; // 5.1.0 System Version
+
+constexpr std::array<u8, 0x2C> AC_NX_DATA{
+ 0x1F, 0x8B, 0x08, 0x08, 0xD5, 0x2C, 0x09, 0x5C, 0x04, 0x00, 0x61, 0x63, 0x72, 0x61, 0x77,
+ 0x00, 0xED, 0xC1, 0x01, 0x0D, 0x00, 0x00, 0x00, 0xC2, 0x20, 0xFB, 0xA7, 0xB6, 0xC7, 0x07,
+ 0x0C, 0x00, 0x00, 0x00, 0xC8, 0x3B, 0x11, 0x00, 0x1C, 0xC7, 0x00, 0x10, 0x00, 0x00,
+}; // Deserializes to no bad words
+
+} // namespace NgWord2Data
+
+VirtualDir NgWord2() {
+ std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3);
+
+ for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) {
+ files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i));
+ files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i));
+ files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i));
+ }
+
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>(
+ NgWord2Data::VERSION_DAT, "version.dat"));
+
+ return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h
new file mode 100644
index 000000000..cd81e0abb
--- /dev/null
+++ b/src/core/file_sys/system_archive/ng_word.h
@@ -0,0 +1,14 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+VirtualDir NgWord1();
+VirtualDir NgWord2();
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
new file mode 100644
index 000000000..e3e79f40a
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -0,0 +1,90 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/system_archive/ng_word.h"
+#include "core/file_sys/system_archive/system_archive.h"
+
+namespace FileSys::SystemArchive {
+
+constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
+constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
+
+using SystemArchiveSupplier = VirtualDir (*)();
+
+struct SystemArchiveDescriptor {
+ u64 title_id;
+ const char* name;
+ SystemArchiveSupplier supplier;
+};
+
+constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
+ {0x0100000000000800, "CertStore", nullptr},
+ {0x0100000000000801, "ErrorMessage", nullptr},
+ {0x0100000000000802, "MiiModel", nullptr},
+ {0x0100000000000803, "BrowserDll", nullptr},
+ {0x0100000000000804, "Help", nullptr},
+ {0x0100000000000805, "SharedFont", nullptr},
+ {0x0100000000000806, "NgWord", &NgWord1},
+ {0x0100000000000807, "SsidList", nullptr},
+ {0x0100000000000808, "Dictionary", nullptr},
+ {0x0100000000000809, "SystemVersion", nullptr},
+ {0x010000000000080A, "AvatarImage", nullptr},
+ {0x010000000000080B, "LocalNews", nullptr},
+ {0x010000000000080C, "Eula", nullptr},
+ {0x010000000000080D, "UrlBlackList", nullptr},
+ {0x010000000000080E, "TimeZoneBinary", nullptr},
+ {0x010000000000080F, "CertStoreCruiser", nullptr},
+ {0x0100000000000810, "FontNintendoExtension", nullptr},
+ {0x0100000000000811, "FontStandard", nullptr},
+ {0x0100000000000812, "FontKorean", nullptr},
+ {0x0100000000000813, "FontChineseTraditional", nullptr},
+ {0x0100000000000814, "FontChineseSimple", nullptr},
+ {0x0100000000000815, "FontBfcpx", nullptr},
+ {0x0100000000000816, "SystemUpdate", nullptr},
+ {0x0100000000000817, "0100000000000817", nullptr},
+ {0x0100000000000818, "FirmwareDebugSettings", nullptr},
+ {0x0100000000000819, "BootImagePackage", nullptr},
+ {0x010000000000081A, "BootImagePackageSafe", nullptr},
+ {0x010000000000081B, "BootImagePackageExFat", nullptr},
+ {0x010000000000081C, "BootImagePackageExFatSafe", nullptr},
+ {0x010000000000081D, "FatalMessage", nullptr},
+ {0x010000000000081E, "ControllerIcon", nullptr},
+ {0x010000000000081F, "PlatformConfigIcosa", nullptr},
+ {0x0100000000000820, "PlatformConfigCopper", nullptr},
+ {0x0100000000000821, "PlatformConfigHoag", nullptr},
+ {0x0100000000000822, "ControllerFirmware", nullptr},
+ {0x0100000000000823, "NgWord2", &NgWord2},
+ {0x0100000000000824, "PlatformConfigIcosaMariko", nullptr},
+ {0x0100000000000825, "ApplicationBlackList", nullptr},
+ {0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr},
+ {0x0100000000000827, "ContentActionTable", nullptr},
+}};
+
+VirtualFile SynthesizeSystemArchive(const u64 title_id) {
+ if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id)
+ return nullptr;
+
+ const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID];
+
+ LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id);
+
+ if (desc.supplier == nullptr)
+ return nullptr;
+
+ const auto dir = desc.supplier();
+
+ if (dir == nullptr)
+ return nullptr;
+
+ const auto romfs = CreateRomFS(dir);
+
+ if (romfs == nullptr)
+ return nullptr;
+
+ LOG_INFO(Service_FS, " - System archive generation successful!");
+ return romfs;
+}
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h
new file mode 100644
index 000000000..724a8eb17
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_archive.h
@@ -0,0 +1,14 @@
+// Copyright 2018 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::SystemArchive {
+
+VirtualFile SynthesizeSystemArchive(u64 title_id);
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index 3824c74e0..e33327ef0 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -167,13 +167,13 @@ std::string VfsFile::GetExtension() const {
VfsDirectory::~VfsDirectory() = default;
-boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
u8 out{};
std::size_t size = Read(&out, 1, offset);
if (size == 1)
return out;
- return boost::none;
+ return {};
}
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
@@ -384,6 +384,28 @@ bool VfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
return success;
}
+bool VfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+ auto dir = GetSubdirectory(name);
+ if (dir == nullptr) {
+ return false;
+ }
+
+ bool success = true;
+ for (const auto& file : dir->GetFiles()) {
+ if (!dir->DeleteFile(file->GetName())) {
+ success = false;
+ }
+ }
+
+ for (const auto& sdir : dir->GetSubdirectories()) {
+ if (!dir->DeleteSubdirectoryRecursive(sdir->GetName())) {
+ success = false;
+ }
+ }
+
+ return success;
+}
+
bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
const auto f1 = GetFile(src);
auto f2 = CreateFile(dest);
@@ -431,10 +453,34 @@ std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFile(std::string_view name)
return nullptr;
}
+std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileAbsolute(std::string_view path) {
+ return nullptr;
+}
+
+std::shared_ptr<VfsFile> ReadOnlyVfsDirectory::CreateFileRelative(std::string_view path) {
+ return nullptr;
+}
+
+std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryAbsolute(std::string_view path) {
+ return nullptr;
+}
+
+std::shared_ptr<VfsDirectory> ReadOnlyVfsDirectory::CreateDirectoryRelative(std::string_view path) {
+ return nullptr;
+}
+
bool ReadOnlyVfsDirectory::DeleteSubdirectory(std::string_view name) {
return false;
}
+bool ReadOnlyVfsDirectory::DeleteSubdirectoryRecursive(std::string_view name) {
+ return false;
+}
+
+bool ReadOnlyVfsDirectory::CleanSubdirectoryRecursive(std::string_view name) {
+ return false;
+}
+
bool ReadOnlyVfsDirectory::DeleteFile(std::string_view name) {
return false;
}
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 09dc9f288..954094772 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,13 +4,15 @@
#pragma once
+#include <functional>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
@@ -103,8 +105,8 @@ public:
// into file. Returns number of bytes successfully written.
virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
- // Reads exactly one byte at the offset provided, returning boost::none on error.
- virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const;
+ // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+ virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
// Reads size bytes starting at offset in file into a vector.
virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
@@ -148,7 +150,7 @@ public:
template <typename T>
std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
- return Write(data, number_elements * sizeof(T), offset);
+ return Write(reinterpret_cast<const u8*>(data), number_elements * sizeof(T), offset);
}
// Writes size bytes starting at memory location data to offset in file.
@@ -164,7 +166,7 @@ public:
template <typename T>
std::size_t WriteObject(const T& data, std::size_t offset = 0) {
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
- return Write(&data, sizeof(T), offset);
+ return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
}
// Renames the file to name. Returns whether or not the operation was successsful.
@@ -243,12 +245,18 @@ public:
// any failure.
virtual std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path);
- // Deletes the subdirectory with name and returns true on success.
+ // Deletes the subdirectory with the given name and returns true on success.
virtual bool DeleteSubdirectory(std::string_view name) = 0;
- // Deletes all subdirectories and files of subdirectory with name recirsively and then deletes
- // the subdirectory. Returns true on success.
+
+ // Deletes all subdirectories and files within the provided directory and then deletes
+ // the directory itself. Returns true on success.
virtual bool DeleteSubdirectoryRecursive(std::string_view name);
- // Returnes whether or not the file with name name was deleted successfully.
+
+ // Deletes all subdirectories and files within the provided directory.
+ // Unlike DeleteSubdirectoryRecursive, this does not delete the provided directory.
+ virtual bool CleanSubdirectoryRecursive(std::string_view name);
+
+ // Returns whether or not the file with name name was deleted successfully.
virtual bool DeleteFile(std::string_view name) = 0;
// Returns whether or not this directory was renamed to name.
@@ -274,7 +282,13 @@ public:
bool IsReadable() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
+ std::shared_ptr<VfsFile> CreateFileAbsolute(std::string_view path) override;
+ std::shared_ptr<VfsFile> CreateFileRelative(std::string_view path) override;
+ std::shared_ptr<VfsDirectory> CreateDirectoryAbsolute(std::string_view path) override;
+ std::shared_ptr<VfsDirectory> CreateDirectoryRelative(std::string_view path) override;
bool DeleteSubdirectory(std::string_view name) override;
+ bool DeleteSubdirectoryRecursive(std::string_view name) override;
+ bool CleanSubdirectoryRecursive(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
};
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index a4c6719a0..c96f88488 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -57,11 +57,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
}
-boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
if (r_offset < size)
return file->ReadByte(offset + r_offset);
- return boost::none;
+ return {};
}
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index 8062702a7..f7b7a3256 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.h
@@ -29,7 +29,7 @@ public:
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
- boost::optional<u8> ReadByte(std::size_t offset) const override;
+ std::optional<u8> ReadByte(std::size_t offset) const override;
std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
std::vector<u8> ReadAllBytes() const override;
bool WriteByte(u8 data, std::size_t offset) override;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 44fab51d1..9f5a90b1b 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -53,10 +53,10 @@ public:
return 0;
}
- boost::optional<u8> ReadByte(std::size_t offset) const override {
+ std::optional<u8> ReadByte(std::size_t offset) const override {
if (offset < size)
return value;
- return boost::none;
+ return {};
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 808f31e81..515626658 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include <cstring>
#include <utility>
#include "core/file_sys/vfs_vector.h"
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 3e3f790c3..ac36cb2ee 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -4,10 +4,63 @@
#pragma once
+#include <cstring>
#include "core/file_sys/vfs.h"
namespace FileSys {
+// An implementation of VfsFile that is backed by a statically-sized array
+template <std::size_t size>
+class ArrayVfsFile : public VfsFile {
+public:
+ ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr)
+ : data(data), name(std::move(name)), parent(std::move(parent)) {}
+
+ std::string GetName() const override {
+ return name;
+ }
+
+ std::size_t GetSize() const override {
+ return size;
+ }
+
+ bool Resize(std::size_t new_size) override {
+ return false;
+ }
+
+ std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
+ return parent;
+ }
+
+ bool IsWritable() const override {
+ return false;
+ }
+
+ bool IsReadable() const override {
+ return true;
+ }
+
+ std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
+ const auto read = std::min(length, size - offset);
+ std::memcpy(data_, data.data() + offset, read);
+ return read;
+ }
+
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
+ return 0;
+ }
+
+ bool Rename(std::string_view name) override {
+ this->name = name;
+ return true;
+ }
+
+private:
+ std::array<u8, size> data;
+ std::string name;
+ VirtualDir parent;
+};
+
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public: