summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/CMakeLists.txt2
-rw-r--r--src/common/file_util.cpp6
-rw-r--r--src/common/file_util.h2
-rw-r--r--src/common/hex_util.cpp27
-rw-r--r--src/common/hex_util.h37
-rw-r--r--src/core/CMakeLists.txt8
-rw-r--r--src/core/core.cpp35
-rw-r--r--src/core/crypto/key_manager.cpp35
-rw-r--r--src/core/crypto/key_manager.h3
-rw-r--r--src/core/file_sys/bis_factory.cpp31
-rw-r--r--src/core/file_sys/bis_factory.h30
-rw-r--r--src/core/file_sys/card_image.cpp4
-rw-r--r--src/core/file_sys/card_image.h1
-rw-r--r--src/core/file_sys/control_metadata.cpp2
-rw-r--r--src/core/file_sys/control_metadata.h1
-rw-r--r--src/core/file_sys/nca_metadata.cpp131
-rw-r--r--src/core/file_sys/nca_metadata.h111
-rw-r--r--src/core/file_sys/registered_cache.cpp476
-rw-r--r--src/core/file_sys/registered_cache.h124
-rw-r--r--src/core/file_sys/romfs.cpp8
-rw-r--r--src/core/file_sys/vfs_concat.cpp94
-rw-r--r--src/core/file_sys/vfs_concat.h41
-rw-r--r--src/core/file_sys/vfs_real.cpp20
-rw-r--r--src/core/file_sys/vfs_real.h1
-rw-r--r--src/core/file_sys/vfs_vector.cpp4
-rw-r--r--src/core/file_sys/vfs_vector.h4
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp19
-rw-r--r--src/core/hle/service/filesystem/filesystem.h8
-rw-r--r--src/core/loader/loader.cpp2
-rw-r--r--src/yuzu/game_list.cpp96
-rw-r--r--src/yuzu/game_list_p.h3
-rw-r--r--src/yuzu/main.cpp143
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui7
34 files changed, 1437 insertions, 80 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 939b8a7d3..d9424ea91 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -38,6 +38,8 @@ add_library(common STATIC
38 file_util.cpp 38 file_util.cpp
39 file_util.h 39 file_util.h
40 hash.h 40 hash.h
41 hex_util.cpp
42 hex_util.h
41 logging/backend.cpp 43 logging/backend.cpp
42 logging/backend.h 44 logging/backend.h
43 logging/filter.cpp 45 logging/filter.cpp
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 3ce590062..b30a67ff9 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -750,6 +750,12 @@ std::string GetHactoolConfigurationPath() {
750#endif 750#endif
751} 751}
752 752
753std::string GetNANDRegistrationDir(bool system) {
754 if (system)
755 return GetUserPath(UserPath::NANDDir) + "system/Contents/registered/";
756 return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/";
757}
758
753size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { 759size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) {
754 return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); 760 return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size());
755} 761}
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 2711872ae..2f13d0b6b 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -129,6 +129,8 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path = "");
129 129
130std::string GetHactoolConfigurationPath(); 130std::string GetHactoolConfigurationPath();
131 131
132std::string GetNANDRegistrationDir(bool system = false);
133
132// Returns the path to where the sys file are 134// Returns the path to where the sys file are
133std::string GetSysDirectory(); 135std::string GetSysDirectory();
134 136
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
new file mode 100644
index 000000000..ae17c89d4
--- /dev/null
+++ b/src/common/hex_util.cpp
@@ -0,0 +1,27 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/hex_util.h"
6
7u8 ToHexNibble(char c1) {
8 if (c1 >= 65 && c1 <= 70)
9 return c1 - 55;
10 if (c1 >= 97 && c1 <= 102)
11 return c1 - 87;
12 if (c1 >= 48 && c1 <= 57)
13 return c1 - 48;
14 throw std::logic_error("Invalid hex digit");
15}
16
17std::array<u8, 16> operator""_array16(const char* str, size_t len) {
18 if (len != 32)
19 throw std::logic_error("Not of correct size.");
20 return HexStringToArray<16>(str);
21}
22
23std::array<u8, 32> operator""_array32(const char* str, size_t len) {
24 if (len != 64)
25 throw std::logic_error("Not of correct size.");
26 return HexStringToArray<32>(str);
27}
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
new file mode 100644
index 000000000..13d586015
--- /dev/null
+++ b/src/common/hex_util.h
@@ -0,0 +1,37 @@
1// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <cstddef>
9#include <string>
10#include <fmt/format.h>
11#include "common/common_types.h"
12
13u8 ToHexNibble(char c1);
14
15template <size_t Size, bool le = false>
16std::array<u8, Size> HexStringToArray(std::string_view str) {
17 std::array<u8, Size> out{};
18 if constexpr (le) {
19 for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2)
20 out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
21 } else {
22 for (size_t i = 0; i < 2 * Size; i += 2)
23 out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
24 }
25 return out;
26}
27
28template <size_t Size>
29std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
30 std::string out;
31 for (u8 c : array)
32 out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
33 return out;
34}
35
36std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
37std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 0b0ae5ccc..67ad6109a 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -20,6 +20,8 @@ add_library(core STATIC
20 crypto/key_manager.h 20 crypto/key_manager.h
21 crypto/ctr_encryption_layer.cpp 21 crypto/ctr_encryption_layer.cpp
22 crypto/ctr_encryption_layer.h 22 crypto/ctr_encryption_layer.h
23 file_sys/bis_factory.cpp
24 file_sys/bis_factory.h
23 file_sys/card_image.cpp 25 file_sys/card_image.cpp
24 file_sys/card_image.h 26 file_sys/card_image.h
25 file_sys/content_archive.cpp 27 file_sys/content_archive.cpp
@@ -29,10 +31,14 @@ add_library(core STATIC
29 file_sys/directory.h 31 file_sys/directory.h
30 file_sys/errors.h 32 file_sys/errors.h
31 file_sys/mode.h 33 file_sys/mode.h
34 file_sys/nca_metadata.cpp
35 file_sys/nca_metadata.h
32 file_sys/partition_filesystem.cpp 36 file_sys/partition_filesystem.cpp
33 file_sys/partition_filesystem.h 37 file_sys/partition_filesystem.h
34 file_sys/program_metadata.cpp 38 file_sys/program_metadata.cpp
35 file_sys/program_metadata.h 39 file_sys/program_metadata.h
40 file_sys/registered_cache.cpp
41 file_sys/registered_cache.h
36 file_sys/romfs.cpp 42 file_sys/romfs.cpp
37 file_sys/romfs.h 43 file_sys/romfs.h
38 file_sys/romfs_factory.cpp 44 file_sys/romfs_factory.cpp
@@ -43,6 +49,8 @@ add_library(core STATIC
43 file_sys/sdmc_factory.h 49 file_sys/sdmc_factory.h
44 file_sys/vfs.cpp 50 file_sys/vfs.cpp
45 file_sys/vfs.h 51 file_sys/vfs.h
52 file_sys/vfs_concat.cpp
53 file_sys/vfs_concat.h
46 file_sys/vfs_offset.cpp 54 file_sys/vfs_offset.cpp
47 file_sys/vfs_offset.h 55 file_sys/vfs_offset.h
48 file_sys/vfs_real.cpp 56 file_sys/vfs_real.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 83d4d742b..28038ff6f 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -5,6 +5,7 @@
5#include <memory> 5#include <memory>
6#include <utility> 6#include <utility>
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/string_util.h"
8#include "core/core.h" 9#include "core/core.h"
9#include "core/core_timing.h" 10#include "core/core_timing.h"
10#include "core/gdbstub/gdbstub.h" 11#include "core/gdbstub/gdbstub.h"
@@ -17,6 +18,7 @@
17#include "core/hle/service/sm/sm.h" 18#include "core/hle/service/sm/sm.h"
18#include "core/loader/loader.h" 19#include "core/loader/loader.h"
19#include "core/settings.h" 20#include "core/settings.h"
21#include "file_sys/vfs_concat.h"
20#include "file_sys/vfs_real.h" 22#include "file_sys/vfs_real.h"
21#include "video_core/renderer_base.h" 23#include "video_core/renderer_base.h"
22#include "video_core/video_core.h" 24#include "video_core/video_core.h"
@@ -88,8 +90,39 @@ System::ResultStatus System::SingleStep() {
88 return RunLoop(false); 90 return RunLoop(false);
89} 91}
90 92
93static FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
94 const std::string& path) {
95 // To account for split 00+01+etc files.
96 std::string dir_name;
97 std::string filename;
98 Common::SplitPath(path, &dir_name, &filename, nullptr);
99 if (filename == "00") {
100 const auto dir = vfs->OpenDirectory(dir_name, FileSys::Mode::Read);
101 std::vector<FileSys::VirtualFile> concat;
102 for (u8 i = 0; i < 0x10; ++i) {
103 auto next = dir->GetFile(fmt::format("{:02X}", i));
104 if (next != nullptr)
105 concat.push_back(std::move(next));
106 else {
107 next = dir->GetFile(fmt::format("{:02x}", i));
108 if (next != nullptr)
109 concat.push_back(std::move(next));
110 else
111 break;
112 }
113 }
114
115 if (concat.empty())
116 return nullptr;
117
118 return FileSys::ConcatenateFiles(concat, dir->GetName());
119 }
120
121 return vfs->OpenFile(path, FileSys::Mode::Read);
122}
123
91System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) { 124System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
92 app_loader = Loader::GetLoader(virtual_filesystem->OpenFile(filepath, FileSys::Mode::Read)); 125 app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath));
93 126
94 if (!app_loader) { 127 if (!app_loader) {
95 LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); 128 LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index fc45e7ab5..94d92579f 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -10,44 +10,13 @@
10#include <string_view> 10#include <string_view>
11#include "common/common_paths.h" 11#include "common/common_paths.h"
12#include "common/file_util.h" 12#include "common/file_util.h"
13#include "common/hex_util.h"
14#include "common/logging/log.h"
13#include "core/crypto/key_manager.h" 15#include "core/crypto/key_manager.h"
14#include "core/settings.h" 16#include "core/settings.h"
15 17
16namespace Core::Crypto { 18namespace Core::Crypto {
17 19
18static u8 ToHexNibble(char c1) {
19 if (c1 >= 65 && c1 <= 70)
20 return c1 - 55;
21 if (c1 >= 97 && c1 <= 102)
22 return c1 - 87;
23 if (c1 >= 48 && c1 <= 57)
24 return c1 - 48;
25 throw std::logic_error("Invalid hex digit");
26}
27
28template <size_t Size>
29static std::array<u8, Size> HexStringToArray(std::string_view str) {
30 std::array<u8, Size> out{};
31 for (size_t i = 0; i < 2 * Size; i += 2) {
32 auto d1 = str[i];
33 auto d2 = str[i + 1];
34 out[i / 2] = (ToHexNibble(d1) << 4) | ToHexNibble(d2);
35 }
36 return out;
37}
38
39std::array<u8, 16> operator""_array16(const char* str, size_t len) {
40 if (len != 32)
41 throw std::logic_error("Not of correct size.");
42 return HexStringToArray<16>(str);
43}
44
45std::array<u8, 32> operator""_array32(const char* str, size_t len) {
46 if (len != 64)
47 throw std::logic_error("Not of correct size.");
48 return HexStringToArray<32>(str);
49}
50
51KeyManager::KeyManager() { 20KeyManager::KeyManager() {
52 // Initialize keys 21 // Initialize keys
53 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath(); 22 const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index c4c53cefc..0c62d4421 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -87,9 +87,6 @@ struct hash<Core::Crypto::KeyIndex<KeyType>> {
87 87
88namespace Core::Crypto { 88namespace Core::Crypto {
89 89
90std::array<u8, 0x10> operator"" _array16(const char* str, size_t len);
91std::array<u8, 0x20> operator"" _array32(const char* str, size_t len);
92
93class KeyManager { 90class KeyManager {
94public: 91public:
95 KeyManager(); 92 KeyManager();
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
new file mode 100644
index 000000000..ae4e33800
--- /dev/null
+++ b/src/core/file_sys/bis_factory.cpp
@@ -0,0 +1,31 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/file_sys/bis_factory.h"
6
7namespace FileSys {
8
9static VirtualDir GetOrCreateDirectory(const VirtualDir& dir, std::string_view path) {
10 const auto res = dir->GetDirectoryRelative(path);
11 if (res == nullptr)
12 return dir->CreateDirectoryRelative(path);
13 return res;
14}
15
16BISFactory::BISFactory(VirtualDir nand_root_)
17 : nand_root(std::move(nand_root_)),
18 sysnand_cache(std::make_shared<RegisteredCache>(
19 GetOrCreateDirectory(nand_root, "/system/Contents/registered"))),
20 usrnand_cache(std::make_shared<RegisteredCache>(
21 GetOrCreateDirectory(nand_root, "/user/Contents/registered"))) {}
22
23std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
24 return sysnand_cache;
25}
26
27std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
28 return usrnand_cache;
29}
30
31} // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
new file mode 100644
index 000000000..a970a5e2e
--- /dev/null
+++ b/src/core/file_sys/bis_factory.h
@@ -0,0 +1,30 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/loader/loader.h"
9#include "registered_cache.h"
10
11namespace FileSys {
12
13/// File system interface to the Built-In Storage
14/// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND
15/// registered caches.
16class BISFactory {
17public:
18 explicit BISFactory(VirtualDir nand_root);
19
20 std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
21 std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
22
23private:
24 VirtualDir nand_root;
25
26 std::shared_ptr<RegisteredCache> sysnand_cache;
27 std::shared_ptr<RegisteredCache> usrnand_cache;
28};
29
30} // namespace FileSys
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 060948f9e..1d7c7fb10 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -96,6 +96,10 @@ VirtualDir XCI::GetLogoPartition() const {
96 return GetPartition(XCIPartition::Logo); 96 return GetPartition(XCIPartition::Logo);
97} 97}
98 98
99const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
100 return ncas;
101}
102
99std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const { 103std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
100 const auto iter = 104 const auto iter =
101 std::find_if(ncas.begin(), ncas.end(), 105 std::find_if(ncas.begin(), ncas.end(),
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 4618d9c00..a03d5264e 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -68,6 +68,7 @@ public:
68 VirtualDir GetUpdatePartition() const; 68 VirtualDir GetUpdatePartition() const;
69 VirtualDir GetLogoPartition() const; 69 VirtualDir GetLogoPartition() const;
70 70
71 const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
71 std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; 72 std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
72 VirtualFile GetNCAFileByType(NCAContentType type) const; 73 VirtualFile GetNCAFileByType(NCAContentType type) const;
73 74
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 3ddc9f162..ae21ad5b9 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -16,7 +16,7 @@ std::string LanguageEntry::GetDeveloperName() const {
16 return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100); 16 return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
17} 17}
18 18
19NACP::NACP(VirtualFile file_) : file(std::move(file_)), raw(std::make_unique<RawNACP>()) { 19NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
20 file->ReadObject(raw.get()); 20 file->ReadObject(raw.get());
21} 21}
22 22
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 6582cc240..8c2cc1a2a 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -81,7 +81,6 @@ public:
81 std::string GetVersionString() const; 81 std::string GetVersionString() const;
82 82
83private: 83private:
84 VirtualFile file;
85 std::unique_ptr<RawNACP> raw; 84 std::unique_ptr<RawNACP> raw;
86}; 85};
87 86
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
new file mode 100644
index 000000000..449244444
--- /dev/null
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -0,0 +1,131 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include "common/common_funcs.h"
7#include "common/logging/log.h"
8#include "common/swap.h"
9#include "content_archive.h"
10#include "core/file_sys/nca_metadata.h"
11
12namespace FileSys {
13
14bool operator>=(TitleType lhs, TitleType rhs) {
15 return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs);
16}
17
18bool operator<=(TitleType lhs, TitleType rhs) {
19 return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs);
20}
21
22CNMT::CNMT(VirtualFile file) {
23 if (file->ReadObject(&header) != sizeof(CNMTHeader))
24 return;
25
26 // If type is {Application, Update, AOC} has opt-header.
27 if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
28 if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
29 LOG_WARNING(Loader, "Failed to read optional header.");
30 }
31 }
32
33 for (u16 i = 0; i < header.number_content_entries; ++i) {
34 auto& next = content_records.emplace_back(ContentRecord{});
35 if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
36 header.table_offset) != sizeof(ContentRecord)) {
37 content_records.erase(content_records.end() - 1);
38 }
39 }
40
41 for (u16 i = 0; i < header.number_meta_entries; ++i) {
42 auto& next = meta_records.emplace_back(MetaRecord{});
43 if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
44 header.table_offset) != sizeof(MetaRecord)) {
45 meta_records.erase(meta_records.end() - 1);
46 }
47 }
48}
49
50CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
51 std::vector<MetaRecord> meta_records)
52 : header(std::move(header)), opt_header(std::move(opt_header)),
53 content_records(std::move(content_records)), meta_records(std::move(meta_records)) {}
54
55u64 CNMT::GetTitleID() const {
56 return header.title_id;
57}
58
59u32 CNMT::GetTitleVersion() const {
60 return header.title_version;
61}
62
63TitleType CNMT::GetType() const {
64 return header.type;
65}
66
67const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
68 return content_records;
69}
70
71const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
72 return meta_records;
73}
74
75bool CNMT::UnionRecords(const CNMT& other) {
76 bool change = false;
77 for (const auto& rec : other.content_records) {
78 const auto iter = std::find_if(content_records.begin(), content_records.end(),
79 [&rec](const ContentRecord& r) {
80 return r.nca_id == rec.nca_id && r.type == rec.type;
81 });
82 if (iter == content_records.end()) {
83 content_records.emplace_back(rec);
84 ++header.number_content_entries;
85 change = true;
86 }
87 }
88 for (const auto& rec : other.meta_records) {
89 const auto iter =
90 std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
91 return r.title_id == rec.title_id && r.title_version == rec.title_version &&
92 r.type == rec.type;
93 });
94 if (iter == meta_records.end()) {
95 meta_records.emplace_back(rec);
96 ++header.number_meta_entries;
97 change = true;
98 }
99 }
100 return change;
101}
102
103std::vector<u8> CNMT::Serialize() const {
104 const bool has_opt_header =
105 header.type >= TitleType::Application && header.type <= TitleType::AOC;
106 const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
107 std::vector<u8> out(
108 std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
109 content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
110 memcpy(out.data(), &header, sizeof(CNMTHeader));
111
112 // Optional Header
113 if (has_opt_header) {
114 memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
115 }
116
117 auto offset = header.table_offset;
118
119 for (const auto& rec : content_records) {
120 memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
121 offset += sizeof(ContentRecord);
122 }
123
124 for (const auto& rec : meta_records) {
125 memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
126 offset += sizeof(MetaRecord);
127 }
128
129 return out;
130}
131} // namespace FileSys
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
new file mode 100644
index 000000000..88e66d4da
--- /dev/null
+++ b/src/core/file_sys/nca_metadata.h
@@ -0,0 +1,111 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <cstring>
8#include <memory>
9#include <vector>
10#include "common/common_types.h"
11#include "common/swap.h"
12#include "core/file_sys/vfs.h"
13
14namespace FileSys {
15class CNMT;
16
17struct CNMTHeader;
18struct OptionalHeader;
19
20enum class TitleType : u8 {
21 SystemProgram = 0x01,
22 SystemDataArchive = 0x02,
23 SystemUpdate = 0x03,
24 FirmwarePackageA = 0x04,
25 FirmwarePackageB = 0x05,
26 Application = 0x80,
27 Update = 0x81,
28 AOC = 0x82,
29 DeltaTitle = 0x83,
30};
31
32bool operator>=(TitleType lhs, TitleType rhs);
33bool operator<=(TitleType lhs, TitleType rhs);
34
35enum class ContentRecordType : u8 {
36 Meta = 0,
37 Program = 1,
38 Data = 2,
39 Control = 3,
40 Manual = 4,
41 Legal = 5,
42 Patch = 6,
43};
44
45struct ContentRecord {
46 std::array<u8, 0x20> hash;
47 std::array<u8, 0x10> nca_id;
48 std::array<u8, 0x6> size;
49 ContentRecordType type;
50 INSERT_PADDING_BYTES(1);
51};
52static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
53
54constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
55
56struct MetaRecord {
57 u64_le title_id;
58 u32_le title_version;
59 TitleType type;
60 u8 install_byte;
61 INSERT_PADDING_BYTES(2);
62};
63static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size.");
64
65struct OptionalHeader {
66 u64_le title_id;
67 u64_le minimum_version;
68};
69static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size.");
70
71struct CNMTHeader {
72 u64_le title_id;
73 u32_le title_version;
74 TitleType type;
75 INSERT_PADDING_BYTES(1);
76 u16_le table_offset;
77 u16_le number_content_entries;
78 u16_le number_meta_entries;
79 INSERT_PADDING_BYTES(12);
80};
81static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size.");
82
83// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or
84// meta0.ncd. These describe which NCA's belong with which titles in the registered cache.
85class CNMT {
86public:
87 explicit CNMT(VirtualFile file);
88 CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records,
89 std::vector<MetaRecord> meta_records);
90
91 u64 GetTitleID() const;
92 u32 GetTitleVersion() const;
93 TitleType GetType() const;
94
95 const std::vector<ContentRecord>& GetContentRecords() const;
96 const std::vector<MetaRecord>& GetMetaRecords() const;
97
98 bool UnionRecords(const CNMT& other);
99 std::vector<u8> Serialize() const;
100
101private:
102 CNMTHeader header;
103 OptionalHeader opt_header;
104 std::vector<ContentRecord> content_records;
105 std::vector<MetaRecord> meta_records;
106
107 // TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data
108 // after the table. This is not documented, unfortunately.
109};
110
111} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
new file mode 100644
index 000000000..a5e935f64
--- /dev/null
+++ b/src/core/file_sys/registered_cache.cpp
@@ -0,0 +1,476 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <regex>
6#include <mbedtls/sha256.h>
7#include "common/assert.h"
8#include "common/hex_util.h"
9#include "common/logging/log.h"
10#include "core/crypto/encryption_layer.h"
11#include "core/file_sys/card_image.h"
12#include "core/file_sys/nca_metadata.h"
13#include "core/file_sys/registered_cache.h"
14#include "core/file_sys/vfs_concat.h"
15
16namespace FileSys {
17std::string RegisteredCacheEntry::DebugInfo() const {
18 return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
19}
20
21bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
22 return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
23}
24
25static bool FollowsTwoDigitDirFormat(std::string_view name) {
26 static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
27 std::regex_constants::icase);
28 return std::regex_match(name.begin(), name.end(), two_digit_regex);
29}
30
31static bool FollowsNcaIdFormat(std::string_view name) {
32 static const std::regex nca_id_regex("[0-9A-F]{32}\\.nca", std::regex_constants::ECMAScript |
33 std::regex_constants::icase);
34 return name.size() == 36 && std::regex_match(name.begin(), name.end(), nca_id_regex);
35}
36
37static std::string GetRelativePathFromNcaID(const std::array<u8, 16>& nca_id, bool second_hex_upper,
38 bool within_two_digit) {
39 if (!within_two_digit)
40 return fmt::format("/{}.nca", HexArrayToString(nca_id, second_hex_upper));
41
42 Core::Crypto::SHA256Hash hash{};
43 mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0);
44 return fmt::format("/000000{:02X}/{}.nca", hash[0], HexArrayToString(nca_id, second_hex_upper));
45}
46
47static std::string GetCNMTName(TitleType type, u64 title_id) {
48 constexpr std::array<const char*, 9> TITLE_TYPE_NAMES{
49 "SystemProgram",
50 "SystemData",
51 "SystemUpdate",
52 "BootImagePackage",
53 "BootImagePackageSafe",
54 "Application",
55 "Patch",
56 "AddOnContent",
57 "" ///< Currently unknown 'DeltaTitle'
58 };
59
60 auto index = static_cast<size_t>(type);
61 // If the index is after the jump in TitleType, subtract it out.
62 if (index >= static_cast<size_t>(TitleType::Application)) {
63 index -= static_cast<size_t>(TitleType::Application) -
64 static_cast<size_t>(TitleType::FirmwarePackageB);
65 }
66 return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id);
67}
68
69static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
70 switch (type) {
71 case NCAContentType::Program:
72 // TODO(DarkLordZach): Differentiate between Program and Patch
73 return ContentRecordType::Program;
74 case NCAContentType::Meta:
75 return ContentRecordType::Meta;
76 case NCAContentType::Control:
77 return ContentRecordType::Control;
78 case NCAContentType::Data:
79 return ContentRecordType::Data;
80 case NCAContentType::Manual:
81 // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
82 return ContentRecordType::Manual;
83 default:
84 UNREACHABLE();
85 }
86}
87
88VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
89 std::string_view path) const {
90 if (dir->GetFileRelative(path) != nullptr)
91 return dir->GetFileRelative(path);
92 if (dir->GetDirectoryRelative(path) != nullptr) {
93 const auto nca_dir = dir->GetDirectoryRelative(path);
94 VirtualFile file = nullptr;
95
96 const auto files = nca_dir->GetFiles();
97 if (files.size() == 1 && files[0]->GetName() == "00") {
98 file = files[0];
99 } else {
100 std::vector<VirtualFile> concat;
101 // Since the files are a two-digit hex number, max is FF.
102 for (size_t i = 0; i < 0x100; ++i) {
103 auto next = nca_dir->GetFile(fmt::format("{:02X}", i));
104 if (next != nullptr) {
105 concat.push_back(std::move(next));
106 } else {
107 next = nca_dir->GetFile(fmt::format("{:02x}", i));
108 if (next != nullptr)
109 concat.push_back(std::move(next));
110 else
111 break;
112 }
113 }
114
115 if (concat.empty())
116 return nullptr;
117
118 file = FileSys::ConcatenateFiles(concat);
119 }
120
121 return file;
122 }
123 return nullptr;
124}
125
126VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
127 VirtualFile file;
128 // Try all four modes of file storage:
129 // (bit 1 = uppercase/lower, bit 0 = within a two-digit dir)
130 // 00: /000000**/{:032X}.nca
131 // 01: /{:032X}.nca
132 // 10: /000000**/{:032x}.nca
133 // 11: /{:032x}.nca
134 for (u8 i = 0; i < 4; ++i) {
135 const auto path = GetRelativePathFromNcaID(id, (i & 0b10) == 0, (i & 0b01) == 0);
136 file = OpenFileOrDirectoryConcat(dir, path);
137 if (file != nullptr)
138 return file;
139 }
140 return file;
141}
142
143static boost::optional<NcaID> CheckMapForContentRecord(
144 const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
145 if (map.find(title_id) == map.end())
146 return boost::none;
147
148 const auto& cnmt = map.at(title_id);
149
150 const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
151 [type](const ContentRecord& rec) { return rec.type == type; });
152 if (iter == cnmt.GetContentRecords().end())
153 return boost::none;
154
155 return boost::make_optional(iter->nca_id);
156}
157
158boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
159 ContentRecordType type) const {
160 if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
161 return meta_id.at(title_id);
162
163 const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
164 if (res1 != boost::none)
165 return res1;
166 return CheckMapForContentRecord(meta, title_id, type);
167}
168
169std::vector<NcaID> RegisteredCache::AccumulateFiles() const {
170 std::vector<NcaID> ids;
171 for (const auto& d2_dir : dir->GetSubdirectories()) {
172 if (FollowsNcaIdFormat(d2_dir->GetName())) {
173 ids.push_back(HexStringToArray<0x10, true>(d2_dir->GetName().substr(0, 0x20)));
174 continue;
175 }
176
177 if (!FollowsTwoDigitDirFormat(d2_dir->GetName()))
178 continue;
179
180 for (const auto& nca_dir : d2_dir->GetSubdirectories()) {
181 if (!FollowsNcaIdFormat(nca_dir->GetName()))
182 continue;
183
184 ids.push_back(HexStringToArray<0x10, true>(nca_dir->GetName().substr(0, 0x20)));
185 }
186
187 for (const auto& nca_file : d2_dir->GetFiles()) {
188 if (!FollowsNcaIdFormat(nca_file->GetName()))
189 continue;
190
191 ids.push_back(HexStringToArray<0x10, true>(nca_file->GetName().substr(0, 0x20)));
192 }
193 }
194
195 for (const auto& d2_file : dir->GetFiles()) {
196 if (FollowsNcaIdFormat(d2_file->GetName()))
197 ids.push_back(HexStringToArray<0x10, true>(d2_file->GetName().substr(0, 0x20)));
198 }
199 return ids;
200}
201
202void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) {
203 for (const auto& id : ids) {
204 const auto file = GetFileAtID(id);
205
206 if (file == nullptr)
207 continue;
208 const auto nca = std::make_shared<NCA>(parser(file, id));
209 if (nca->GetStatus() != Loader::ResultStatus::Success ||
210 nca->GetType() != NCAContentType::Meta) {
211 continue;
212 }
213
214 const auto section0 = nca->GetSubdirectories()[0];
215
216 for (const auto& file : section0->GetFiles()) {
217 if (file->GetExtension() != "cnmt")
218 continue;
219
220 meta.insert_or_assign(nca->GetTitleId(), CNMT(file));
221 meta_id.insert_or_assign(nca->GetTitleId(), id);
222 break;
223 }
224 }
225}
226
227void RegisteredCache::AccumulateYuzuMeta() {
228 const auto dir = this->dir->GetSubdirectory("yuzu_meta");
229 if (dir == nullptr)
230 return;
231
232 for (const auto& file : dir->GetFiles()) {
233 if (file->GetExtension() != "cnmt")
234 continue;
235
236 CNMT cnmt(file);
237 yuzu_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt));
238 }
239}
240
241void RegisteredCache::Refresh() {
242 if (dir == nullptr)
243 return;
244 const auto ids = AccumulateFiles();
245 ProcessFiles(ids);
246 AccumulateYuzuMeta();
247}
248
249RegisteredCache::RegisteredCache(VirtualDir dir_, RegisteredCacheParsingFunction parsing_function)
250 : dir(std::move(dir_)), parser(std::move(parsing_function)) {
251 Refresh();
252}
253
254bool RegisteredCache::HasEntry(u64 title_id, ContentRecordType type) const {
255 return GetEntryRaw(title_id, type) != nullptr;
256}
257
258bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
259 return GetEntryRaw(entry) != nullptr;
260}
261
262VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
263 const auto id = GetNcaIDFromMetadata(title_id, type);
264 if (id == boost::none)
265 return nullptr;
266
267 return parser(GetFileAtID(id.get()), id.get());
268}
269
270VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
271 return GetEntryRaw(entry.title_id, entry.type);
272}
273
274std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
275 const auto raw = GetEntryRaw(title_id, type);
276 if (raw == nullptr)
277 return nullptr;
278 return std::make_shared<NCA>(raw);
279}
280
281std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
282 return GetEntry(entry.title_id, entry.type);
283}
284
285template <typename T>
286void RegisteredCache::IterateAllMetadata(
287 std::vector<T>& out, std::function<T(const CNMT&, const ContentRecord&)> proc,
288 std::function<bool(const CNMT&, const ContentRecord&)> filter) const {
289 for (const auto& kv : meta) {
290 const auto& cnmt = kv.second;
291 if (filter(cnmt, EMPTY_META_CONTENT_RECORD))
292 out.push_back(proc(cnmt, EMPTY_META_CONTENT_RECORD));
293 for (const auto& rec : cnmt.GetContentRecords()) {
294 if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
295 out.push_back(proc(cnmt, rec));
296 }
297 }
298 }
299 for (const auto& kv : yuzu_meta) {
300 const auto& cnmt = kv.second;
301 for (const auto& rec : cnmt.GetContentRecords()) {
302 if (GetFileAtID(rec.nca_id) != nullptr && filter(cnmt, rec)) {
303 out.push_back(proc(cnmt, rec));
304 }
305 }
306 }
307}
308
309std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
310 std::vector<RegisteredCacheEntry> out;
311 IterateAllMetadata<RegisteredCacheEntry>(
312 out,
313 [](const CNMT& c, const ContentRecord& r) {
314 return RegisteredCacheEntry{c.GetTitleID(), r.type};
315 },
316 [](const CNMT& c, const ContentRecord& r) { return true; });
317 return out;
318}
319
320std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
321 boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
322 boost::optional<u64> title_id) const {
323 std::vector<RegisteredCacheEntry> out;
324 IterateAllMetadata<RegisteredCacheEntry>(
325 out,
326 [](const CNMT& c, const ContentRecord& r) {
327 return RegisteredCacheEntry{c.GetTitleID(), r.type};
328 },
329 [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
330 if (title_type != boost::none && title_type.get() != c.GetType())
331 return false;
332 if (record_type != boost::none && record_type.get() != r.type)
333 return false;
334 if (title_id != boost::none && title_id.get() != c.GetTitleID())
335 return false;
336 return true;
337 });
338 return out;
339}
340
341static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
342 const auto filename = fmt::format("{}.nca", HexArrayToString(id, false));
343 const auto iter =
344 std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
345 [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
346 return iter == xci->GetNCAs().end() ? nullptr : *iter;
347}
348
349InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
350 const VfsCopyFunction& copy) {
351 const auto& ncas = xci->GetNCAs();
352 const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
353 return nca->GetType() == NCAContentType::Meta;
354 });
355
356 if (meta_iter == ncas.end()) {
357 LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and "
358 "is therefore malformed. Double check your encryption keys.");
359 return InstallResult::ErrorMetaFailed;
360 }
361
362 // Install Metadata File
363 const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32);
364 const auto meta_id = HexStringToArray<16>(meta_id_raw);
365
366 const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id);
367 if (res != InstallResult::Success)
368 return res;
369
370 // Install all the other NCAs
371 const auto section0 = (*meta_iter)->GetSubdirectories()[0];
372 const auto cnmt_file = section0->GetFiles()[0];
373 const CNMT cnmt(cnmt_file);
374 for (const auto& record : cnmt.GetContentRecords()) {
375 const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
376 if (nca == nullptr)
377 return InstallResult::ErrorCopyFailed;
378 const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
379 if (res2 != InstallResult::Success)
380 return res2;
381 }
382
383 Refresh();
384 return InstallResult::Success;
385}
386
387InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
388 bool overwrite_if_exists, const VfsCopyFunction& copy) {
389 CNMTHeader header{
390 nca->GetTitleId(), ///< Title ID
391 0, ///< Ignore/Default title version
392 type, ///< Type
393 {}, ///< Padding
394 0x10, ///< Default table offset
395 1, ///< 1 Content Entry
396 0, ///< No Meta Entries
397 {}, ///< Padding
398 };
399 OptionalHeader opt_header{0, 0};
400 ContentRecord c_rec{{}, {}, {}, GetCRTypeFromNCAType(nca->GetType()), {}};
401 const auto& data = nca->GetBaseFile()->ReadBytes(0x100000);
402 mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0);
403 memcpy(&c_rec.nca_id, &c_rec.hash, 16);
404 const CNMT new_cnmt(header, opt_header, {c_rec}, {});
405 if (!RawInstallYuzuMeta(new_cnmt))
406 return InstallResult::ErrorMetaFailed;
407 return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
408}
409
410InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
411 bool overwrite_if_exists,
412 boost::optional<NcaID> override_id) {
413 const auto in = nca->GetBaseFile();
414 Core::Crypto::SHA256Hash hash{};
415
416 // Calculate NcaID
417 // NOTE: Because computing the SHA256 of an entire NCA is quite expensive (especially if the
418 // game is massive), we're going to cheat and only hash the first MB of the NCA.
419 // Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
420 NcaID id{};
421 if (override_id == boost::none) {
422 const auto& data = in->ReadBytes(0x100000);
423 mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
424 memcpy(id.data(), hash.data(), 16);
425 } else {
426 id = override_id.get();
427 }
428
429 std::string path = GetRelativePathFromNcaID(id, false, true);
430
431 if (GetFileAtID(id) != nullptr && !overwrite_if_exists) {
432 LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping...");
433 return InstallResult::ErrorAlreadyExists;
434 }
435
436 if (GetFileAtID(id) != nullptr) {
437 LOG_WARNING(Loader, "Overwriting existing NCA...");
438 VirtualDir c_dir;
439 { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); }
440 c_dir->DeleteFile(FileUtil::GetFilename(path));
441 }
442
443 auto out = dir->CreateFileRelative(path);
444 if (out == nullptr)
445 return InstallResult::ErrorCopyFailed;
446 return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
447}
448
449bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
450 // Reasoning behind this method can be found in the comment for InstallEntry, NCA overload.
451 const auto dir = this->dir->CreateDirectoryRelative("yuzu_meta");
452 const auto filename = GetCNMTName(cnmt.GetType(), cnmt.GetTitleID());
453 if (dir->GetFile(filename) == nullptr) {
454 auto out = dir->CreateFile(filename);
455 const auto buffer = cnmt.Serialize();
456 out->Resize(buffer.size());
457 out->WriteBytes(buffer);
458 } else {
459 auto out = dir->GetFile(filename);
460 CNMT old_cnmt(out);
461 // Returns true on change
462 if (old_cnmt.UnionRecords(cnmt)) {
463 out->Resize(0);
464 const auto buffer = old_cnmt.Serialize();
465 out->Resize(buffer.size());
466 out->WriteBytes(buffer);
467 }
468 }
469 Refresh();
470 return std::find_if(yuzu_meta.begin(), yuzu_meta.end(),
471 [&cnmt](const std::pair<u64, CNMT>& kv) {
472 return kv.second.GetType() == cnmt.GetType() &&
473 kv.second.GetTitleID() == cnmt.GetTitleID();
474 }) != yuzu_meta.end();
475}
476} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
new file mode 100644
index 000000000..a7c51a59c
--- /dev/null
+++ b/src/core/file_sys/registered_cache.h
@@ -0,0 +1,124 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <functional>
9#include <map>
10#include <memory>
11#include <string>
12#include <vector>
13#include <boost/container/flat_map.hpp>
14#include "common/common_funcs.h"
15#include "common/common_types.h"
16#include "content_archive.h"
17#include "core/file_sys/nca_metadata.h"
18#include "core/file_sys/vfs.h"
19
20namespace FileSys {
21class XCI;
22class CNMT;
23
24using NcaID = std::array<u8, 0x10>;
25using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
26using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
27
28enum class InstallResult {
29 Success,
30 ErrorAlreadyExists,
31 ErrorCopyFailed,
32 ErrorMetaFailed,
33};
34
35struct RegisteredCacheEntry {
36 u64 title_id;
37 ContentRecordType type;
38
39 std::string DebugInfo() const;
40};
41
42// boost flat_map requires operator< for O(log(n)) lookups.
43bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
44
45/*
46 * A class that catalogues NCAs in the registered directory structure.
47 * Nintendo's registered format follows this structure:
48 *
49 * Root
50 * | 000000XX <- XX is the ____ two digits of the NcaID
51 * | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
52 * | 00
53 * | 01 <- Actual content split along 4GB boundaries. (optional)
54 *
55 * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
56 * 4GB splitting can be ignored.)
57 */
58class RegisteredCache {
59public:
60 // Parsing function defines the conversion from raw file to NCA. If there are other steps
61 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
62 // parsing function.
63 explicit RegisteredCache(VirtualDir dir,
64 RegisteredCacheParsingFunction parsing_function =
65 [](const VirtualFile& file, const NcaID& id) { return file; });
66
67 void Refresh();
68
69 bool HasEntry(u64 title_id, ContentRecordType type) const;
70 bool HasEntry(RegisteredCacheEntry entry) const;
71
72 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
73 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
74
75 std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
76 std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
77
78 std::vector<RegisteredCacheEntry> ListEntries() const;
79 // If a parameter is not boost::none, it will be filtered for from all entries.
80 std::vector<RegisteredCacheEntry> ListEntriesFilter(
81 boost::optional<TitleType> title_type = boost::none,
82 boost::optional<ContentRecordType> record_type = boost::none,
83 boost::optional<u64> title_id = boost::none) const;
84
85 // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
86 // is a meta NCA and all of them are accessible.
87 InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
88 const VfsCopyFunction& copy = &VfsRawCopy);
89
90 // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
91 // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
92 // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
93 // TODO(DarkLordZach): Author real meta-type NCAs and install those.
94 InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
95 bool overwrite_if_exists = false,
96 const VfsCopyFunction& copy = &VfsRawCopy);
97
98private:
99 template <typename T>
100 void IterateAllMetadata(std::vector<T>& out,
101 std::function<T(const CNMT&, const ContentRecord&)> proc,
102 std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
103 std::vector<NcaID> AccumulateFiles() const;
104 void ProcessFiles(const std::vector<NcaID>& ids);
105 void AccumulateYuzuMeta();
106 boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
107 VirtualFile GetFileAtID(NcaID id) const;
108 VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
109 InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
110 bool overwrite_if_exists,
111 boost::optional<NcaID> override_id = boost::none);
112 bool RawInstallYuzuMeta(const CNMT& cnmt);
113
114 VirtualDir dir;
115 RegisteredCacheParsingFunction parser;
116 // maps tid -> NcaID of meta
117 boost::container::flat_map<u64, NcaID> meta_id;
118 // maps tid -> meta
119 boost::container::flat_map<u64, CNMT> meta;
120 // maps tid -> meta for CNMT in yuzu_meta
121 boost::container::flat_map<u64, CNMT> yuzu_meta;
122};
123
124} // namespace FileSys
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index ff3ddb29c..e490c8ace 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -65,7 +65,7 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t
65 auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset); 65 auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset);
66 66
67 parent->AddFile(std::make_shared<OffsetVfsFile>( 67 parent->AddFile(std::make_shared<OffsetVfsFile>(
68 file, entry.first.size, entry.first.offset + data_offset, entry.second, parent)); 68 file, entry.first.size, entry.first.offset + data_offset, entry.second));
69 69
70 if (entry.first.sibling == ROMFS_ENTRY_EMPTY) 70 if (entry.first.sibling == ROMFS_ENTRY_EMPTY)
71 break; 71 break;
@@ -79,7 +79,7 @@ void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, s
79 while (true) { 79 while (true) {
80 auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset); 80 auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset);
81 auto current = std::make_shared<VectorVfsDirectory>( 81 auto current = std::make_shared<VectorVfsDirectory>(
82 std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, parent, entry.second); 82 std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, entry.second);
83 83
84 if (entry.first.child_file != ROMFS_ENTRY_EMPTY) { 84 if (entry.first.child_file != ROMFS_ENTRY_EMPTY) {
85 ProcessFile(file, file_offset, data_offset, entry.first.child_file, current); 85 ProcessFile(file, file_offset, data_offset, entry.first.child_file, current);
@@ -108,9 +108,9 @@ VirtualDir ExtractRomFS(VirtualFile file) {
108 const u64 file_offset = header.file_meta.offset; 108 const u64 file_offset = header.file_meta.offset;
109 const u64 dir_offset = header.directory_meta.offset + 4; 109 const u64 dir_offset = header.directory_meta.offset + 4;
110 110
111 const auto root = 111 auto root =
112 std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{}, 112 std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{}, std::vector<VirtualDir>{},
113 file->GetContainingDirectory(), file->GetName()); 113 file->GetName(), file->GetContainingDirectory());
114 114
115 ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root); 115 ProcessDirectory(file, dir_offset, file_offset, header.data_offset, 0, root);
116 116
diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp
new file mode 100644
index 000000000..e6bf586a3
--- /dev/null
+++ b/src/core/file_sys/vfs_concat.cpp
@@ -0,0 +1,94 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <utility>
7
8#include "core/file_sys/vfs_concat.h"
9
10namespace FileSys {
11
12VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
13 if (files.empty())
14 return nullptr;
15 if (files.size() == 1)
16 return files[0];
17
18 return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
19}
20
21ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
22 : name(std::move(name)) {
23 size_t next_offset = 0;
24 for (const auto& file : files_) {
25 files[next_offset] = file;
26 next_offset += file->GetSize();
27 }
28}
29
30std::string ConcatenatedVfsFile::GetName() const {
31 if (files.empty())
32 return "";
33 if (!name.empty())
34 return name;
35 return files.begin()->second->GetName();
36}
37
38size_t ConcatenatedVfsFile::GetSize() const {
39 if (files.empty())
40 return 0;
41 return files.rbegin()->first + files.rbegin()->second->GetSize();
42}
43
44bool ConcatenatedVfsFile::Resize(size_t new_size) {
45 return false;
46}
47
48std::shared_ptr<VfsDirectory> ConcatenatedVfsFile::GetContainingDirectory() const {
49 if (files.empty())
50 return nullptr;
51 return files.begin()->second->GetContainingDirectory();
52}
53
54bool ConcatenatedVfsFile::IsWritable() const {
55 return false;
56}
57
58bool ConcatenatedVfsFile::IsReadable() const {
59 return true;
60}
61
62size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const {
63 auto entry = files.end();
64 for (auto iter = files.begin(); iter != files.end(); ++iter) {
65 if (iter->first > offset) {
66 entry = --iter;
67 break;
68 }
69 }
70
71 // Check if the entry should be the last one. The loop above will make it end().
72 if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
73 --entry;
74
75 if (entry == files.end())
76 return 0;
77
78 const auto remaining = entry->second->GetSize() + offset - entry->first;
79 if (length > remaining) {
80 return entry->second->Read(data, remaining, offset - entry->first) +
81 Read(data + remaining, length - remaining, offset + remaining);
82 }
83
84 return entry->second->Read(data, length, offset - entry->first);
85}
86
87size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) {
88 return 0;
89}
90
91bool ConcatenatedVfsFile::Rename(std::string_view name) {
92 return false;
93}
94} // namespace FileSys
diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h
new file mode 100644
index 000000000..686d32515
--- /dev/null
+++ b/src/core/file_sys/vfs_concat.h
@@ -0,0 +1,41 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <string_view>
9#include <boost/container/flat_map.hpp>
10#include "core/file_sys/vfs.h"
11
12namespace FileSys {
13
14// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
15VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
16
17// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
18// read-only.
19class ConcatenatedVfsFile : public VfsFile {
20 friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
21
22 ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
23
24public:
25 std::string GetName() const override;
26 size_t GetSize() const override;
27 bool Resize(size_t new_size) override;
28 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
29 bool IsWritable() const override;
30 bool IsReadable() const override;
31 size_t Read(u8* data, size_t length, size_t offset) const override;
32 size_t Write(const u8* data, size_t length, size_t offset) override;
33 bool Rename(std::string_view name) override;
34
35private:
36 // Maps starting offset to file -- more efficient.
37 boost::container::flat_map<u64, VirtualFile> files;
38 std::string name;
39};
40
41} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 1b5919737..0afe515f0 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -83,8 +83,12 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) {
83 83
84VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { 84VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) {
85 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 85 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
86 if (!FileUtil::Exists(path) && !FileUtil::CreateEmptyFile(path)) 86 const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
87 return nullptr; 87 if (!FileUtil::Exists(path)) {
88 FileUtil::CreateFullPath(path_fwd);
89 if (!FileUtil::CreateEmptyFile(path))
90 return nullptr;
91 }
88 return OpenFile(path, perms); 92 return OpenFile(path, perms);
89} 93}
90 94
@@ -140,8 +144,12 @@ VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms)
140 144
141VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) { 145VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms) {
142 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault); 146 const auto path = FileUtil::SanitizePath(path_, FileUtil::DirectorySeparator::PlatformDefault);
143 if (!FileUtil::Exists(path) && !FileUtil::CreateDir(path)) 147 const auto path_fwd = FileUtil::SanitizePath(path, FileUtil::DirectorySeparator::ForwardSlash);
144 return nullptr; 148 if (!FileUtil::Exists(path)) {
149 FileUtil::CreateFullPath(path_fwd);
150 if (!FileUtil::CreateDir(path))
151 return nullptr;
152 }
145 // Cannot use make_shared as RealVfsDirectory constructor is private 153 // Cannot use make_shared as RealVfsDirectory constructor is private
146 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); 154 return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms));
147} 155}
@@ -306,14 +314,14 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string&
306 314
307std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { 315std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const {
308 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 316 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
309 if (!FileUtil::Exists(full_path)) 317 if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path))
310 return nullptr; 318 return nullptr;
311 return base.OpenFile(full_path, perms); 319 return base.OpenFile(full_path, perms);
312} 320}
313 321
314std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const { 322std::shared_ptr<VfsDirectory> RealVfsDirectory::GetDirectoryRelative(std::string_view path) const {
315 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); 323 const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path));
316 if (!FileUtil::Exists(full_path)) 324 if (!FileUtil::Exists(full_path) || !FileUtil::IsDirectory(full_path))
317 return nullptr; 325 return nullptr;
318 return base.OpenDirectory(full_path, perms); 326 return base.OpenDirectory(full_path, perms);
319} 327}
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 8a1e79ef6..989803d43 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -5,7 +5,6 @@
5#pragma once 5#pragma once
6 6
7#include <string_view> 7#include <string_view>
8
9#include <boost/container/flat_map.hpp> 8#include <boost/container/flat_map.hpp>
10#include "common/file_util.h" 9#include "common/file_util.h"
11#include "core/file_sys/mode.h" 10#include "core/file_sys/mode.h"
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index fda603960..98e7c4598 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -8,8 +8,8 @@
8 8
9namespace FileSys { 9namespace FileSys {
10VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, 10VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
11 std::vector<VirtualDir> dirs_, VirtualDir parent_, 11 std::vector<VirtualDir> dirs_, std::string name_,
12 std::string name_) 12 VirtualDir parent_)
13 : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), 13 : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)),
14 name(std::move(name_)) {} 14 name(std::move(name_)) {}
15 15
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index b3b468233..179f62e4b 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -13,8 +13,8 @@ namespace FileSys {
13class VectorVfsDirectory : public VfsDirectory { 13class VectorVfsDirectory : public VfsDirectory {
14public: 14public:
15 explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, 15 explicit VectorVfsDirectory(std::vector<VirtualFile> files = {},
16 std::vector<VirtualDir> dirs = {}, VirtualDir parent = nullptr, 16 std::vector<VirtualDir> dirs = {}, std::string name = "",
17 std::string name = ""); 17 VirtualDir parent = nullptr);
18 18
19 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; 19 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
20 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; 20 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index 5e416cde2..da658cbe6 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -226,6 +226,7 @@ ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType(
226static std::unique_ptr<FileSys::RomFSFactory> romfs_factory; 226static std::unique_ptr<FileSys::RomFSFactory> romfs_factory;
227static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory; 227static std::unique_ptr<FileSys::SaveDataFactory> save_data_factory;
228static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory; 228static std::unique_ptr<FileSys::SDMCFactory> sdmc_factory;
229static std::unique_ptr<FileSys::BISFactory> bis_factory;
229 230
230ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) { 231ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory) {
231 ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS"); 232 ASSERT_MSG(romfs_factory == nullptr, "Tried to register a second RomFS");
@@ -248,6 +249,13 @@ ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory) {
248 return RESULT_SUCCESS; 249 return RESULT_SUCCESS;
249} 250}
250 251
252ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
253 ASSERT_MSG(bis_factory == nullptr, "Tried to register a second BIS");
254 bis_factory = std::move(factory);
255 LOG_DEBUG(Service_FS, "Registred BIS");
256 return RESULT_SUCCESS;
257}
258
251ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) { 259ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id) {
252 LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id); 260 LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}", title_id);
253 261
@@ -281,6 +289,14 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
281 return sdmc_factory->Open(); 289 return sdmc_factory->Open();
282} 290}
283 291
292std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
293 return bis_factory->GetSystemNANDContents();
294}
295
296std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
297 return bis_factory->GetUserNANDContents();
298}
299
284void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) { 300void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
285 romfs_factory = nullptr; 301 romfs_factory = nullptr;
286 save_data_factory = nullptr; 302 save_data_factory = nullptr;
@@ -291,6 +307,9 @@ void RegisterFileSystems(const FileSys::VirtualFilesystem& vfs) {
291 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 307 auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
292 FileSys::Mode::ReadWrite); 308 FileSys::Mode::ReadWrite);
293 309
310 if (bis_factory == nullptr)
311 bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory);
312
294 auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory)); 313 auto savedata = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
295 save_data_factory = std::move(savedata); 314 save_data_factory = std::move(savedata);
296 315
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 462c13f20..1d6f922dd 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -6,6 +6,7 @@
6 6
7#include <memory> 7#include <memory>
8#include "common/common_types.h" 8#include "common/common_types.h"
9#include "core/file_sys/bis_factory.h"
9#include "core/file_sys/directory.h" 10#include "core/file_sys/directory.h"
10#include "core/file_sys/mode.h" 11#include "core/file_sys/mode.h"
11#include "core/file_sys/romfs_factory.h" 12#include "core/file_sys/romfs_factory.h"
@@ -24,16 +25,15 @@ namespace FileSystem {
24ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory); 25ResultCode RegisterRomFS(std::unique_ptr<FileSys::RomFSFactory>&& factory);
25ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory); 26ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory);
26ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory); 27ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
28ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
27 29
28// TODO(DarkLordZach): BIS Filesystem
29// ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
30ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id); 30ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id);
31ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, 31ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
32 FileSys::SaveDataDescriptor save_struct); 32 FileSys::SaveDataDescriptor save_struct);
33ResultVal<FileSys::VirtualDir> OpenSDMC(); 33ResultVal<FileSys::VirtualDir> OpenSDMC();
34 34
35// TODO(DarkLordZach): BIS Filesystem 35std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
36// ResultVal<std::unique_ptr<FileSys::FileSystemBackend>> OpenBIS(); 36std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
37 37
38/// Registers all Filesystem services with the specified service manager. 38/// Registers all Filesystem services with the specified service manager.
39void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs); 39void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 5e07a3f10..70ef5d240 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -41,6 +41,8 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
41FileType GuessFromFilename(const std::string& name) { 41FileType GuessFromFilename(const std::string& name) {
42 if (name == "main") 42 if (name == "main")
43 return FileType::DeconstructedRomDirectory; 43 return FileType::DeconstructedRomDirectory;
44 if (name == "00")
45 return FileType::NCA;
44 46
45 const std::string extension = 47 const std::string extension =
46 Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name))); 48 Common::ToLower(std::string(FileUtil::GetExtensionFromFilename(name)));
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 85cb12594..f867118d9 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <regex>
5#include <QApplication> 6#include <QApplication>
6#include <QDir> 7#include <QDir>
7#include <QFileInfo> 8#include <QFileInfo>
@@ -402,12 +403,72 @@ void GameList::RefreshGameDirectory() {
402 } 403 }
403} 404}
404 405
405void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { 406static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca,
406 boost::container::flat_map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; 407 std::vector<u8>& icon, std::string& name) {
408 const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS());
409 if (control_dir == nullptr)
410 return;
411
412 const auto nacp_file = control_dir->GetFile("control.nacp");
413 if (nacp_file == nullptr)
414 return;
415 FileSys::NACP nacp(nacp_file);
416 name = nacp.GetApplicationName();
417
418 FileSys::VirtualFile icon_file = nullptr;
419 for (const auto& language : FileSys::LANGUAGE_NAMES) {
420 icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
421 if (icon_file != nullptr) {
422 icon = icon_file->ReadAllBytes();
423 break;
424 }
425 }
426}
427
428void GameListWorker::AddInstalledTitlesToGameList() {
429 const auto usernand = Service::FileSystem::GetUserNANDContents();
430 const auto installed_games = usernand->ListEntriesFilter(FileSys::TitleType::Application,
431 FileSys::ContentRecordType::Program);
432
433 for (const auto& game : installed_games) {
434 const auto& file = usernand->GetEntryRaw(game);
435 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
436 if (!loader)
437 continue;
438
439 std::vector<u8> icon;
440 std::string name;
441 u64 program_id;
442 loader->ReadProgramId(program_id);
443
444 const auto& control =
445 usernand->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
446 if (control != nullptr)
447 GetMetadataFromControlNCA(control, icon, name);
448 emit EntryReady({
449 new GameListItemPath(
450 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
451 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
452 program_id),
453 new GameListItem(
454 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
455 new GameListItemSize(file->GetSize()),
456 });
457 }
458
459 const auto control_data = usernand->ListEntriesFilter(FileSys::TitleType::Application,
460 FileSys::ContentRecordType::Control);
461
462 for (const auto& entry : control_data) {
463 const auto nca = usernand->GetEntry(entry);
464 if (nca != nullptr)
465 nca_control_map.insert_or_assign(entry.title_id, nca);
466 }
467}
407 468
408 const auto nca_control_callback = 469void GameListWorker::FillControlMap(const std::string& dir_path) {
409 [this, &nca_control_map](u64* num_entries_out, const std::string& directory, 470 const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory,
410 const std::string& virtual_name) -> bool { 471 const std::string& virtual_name) -> bool {
411 std::string physical_name = directory + DIR_SEP + virtual_name; 472 std::string physical_name = directory + DIR_SEP + virtual_name;
412 473
413 if (stop_processing) 474 if (stop_processing)
@@ -425,10 +486,11 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
425 }; 486 };
426 487
427 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); 488 FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback);
489}
428 490
429 const auto callback = [this, recursion, 491void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) {
430 &nca_control_map](u64* num_entries_out, const std::string& directory, 492 const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory,
431 const std::string& virtual_name) -> bool { 493 const std::string& virtual_name) -> bool {
432 std::string physical_name = directory + DIR_SEP + virtual_name; 494 std::string physical_name = directory + DIR_SEP + virtual_name;
433 495
434 if (stop_processing) 496 if (stop_processing)
@@ -458,20 +520,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
458 // Use from metadata pool. 520 // Use from metadata pool.
459 if (nca_control_map.find(program_id) != nca_control_map.end()) { 521 if (nca_control_map.find(program_id) != nca_control_map.end()) {
460 const auto nca = nca_control_map[program_id]; 522 const auto nca = nca_control_map[program_id];
461 const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); 523 GetMetadataFromControlNCA(nca, icon, name);
462
463 const auto nacp_file = control_dir->GetFile("control.nacp");
464 FileSys::NACP nacp(nacp_file);
465 name = nacp.GetApplicationName();
466
467 FileSys::VirtualFile icon_file = nullptr;
468 for (const auto& language : FileSys::LANGUAGE_NAMES) {
469 icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
470 if (icon_file != nullptr) {
471 icon = icon_file->ReadAllBytes();
472 break;
473 }
474 }
475 } 524 }
476 } 525 }
477 526
@@ -498,7 +547,10 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
498void GameListWorker::run() { 547void GameListWorker::run() {
499 stop_processing = false; 548 stop_processing = false;
500 watch_list.append(dir_path); 549 watch_list.append(dir_path);
550 FillControlMap(dir_path.toStdString());
551 AddInstalledTitlesToGameList();
501 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 552 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
553 nca_control_map.clear();
502 emit Finished(watch_list); 554 emit Finished(watch_list);
503} 555}
504 556
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 8fe5e8b80..10c2ef075 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -163,10 +163,13 @@ signals:
163 163
164private: 164private:
165 FileSys::VirtualFilesystem vfs; 165 FileSys::VirtualFilesystem vfs;
166 std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
166 QStringList watch_list; 167 QStringList watch_list;
167 QString dir_path; 168 QString dir_path;
168 bool deep_scan; 169 bool deep_scan;
169 std::atomic_bool stop_processing; 170 std::atomic_bool stop_processing;
170 171
172 void AddInstalledTitlesToGameList();
173 void FillControlMap(const std::string& dir_path);
171 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); 174 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
172}; 175};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 4bbea3f3c..f7eee7769 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -27,6 +27,7 @@
27#include "common/string_util.h" 27#include "common/string_util.h"
28#include "core/core.h" 28#include "core/core.h"
29#include "core/crypto/key_manager.h" 29#include "core/crypto/key_manager.h"
30#include "core/file_sys/card_image.h"
30#include "core/file_sys/vfs_real.h" 31#include "core/file_sys/vfs_real.h"
31#include "core/gdbstub/gdbstub.h" 32#include "core/gdbstub/gdbstub.h"
32#include "core/loader/loader.h" 33#include "core/loader/loader.h"
@@ -117,6 +118,9 @@ GMainWindow::GMainWindow()
117 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); 118 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
118 show(); 119 show();
119 120
121 // Necessary to load titles from nand in gamelist.
122 Service::FileSystem::RegisterBIS(std::make_unique<FileSys::BISFactory>(vfs->OpenDirectory(
123 FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), FileSys::Mode::ReadWrite)));
120 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); 124 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
121 125
122 // Show one-time "callout" messages to the user 126 // Show one-time "callout" messages to the user
@@ -312,6 +316,8 @@ void GMainWindow::ConnectMenuEvents() {
312 // File 316 // File
313 connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile); 317 connect(ui.action_Load_File, &QAction::triggered, this, &GMainWindow::OnMenuLoadFile);
314 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); 318 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
319 connect(ui.action_Install_File_NAND, &QAction::triggered, this,
320 &GMainWindow::OnMenuInstallToNAND);
315 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, 321 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
316 &GMainWindow::OnMenuSelectGameListRoot); 322 &GMainWindow::OnMenuSelectGameListRoot);
317 connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); 323 connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
@@ -615,6 +621,143 @@ void GMainWindow::OnMenuLoadFolder() {
615 } 621 }
616} 622}
617 623
624void GMainWindow::OnMenuInstallToNAND() {
625 const QString file_filter =
626 tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
627 "Image (*.xci)");
628 QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
629 UISettings::values.roms_path, file_filter);
630
631 const auto qt_raw_copy = [this](FileSys::VirtualFile src, FileSys::VirtualFile dest) {
632 if (src == nullptr || dest == nullptr)
633 return false;
634 if (!dest->Resize(src->GetSize()))
635 return false;
636
637 QProgressDialog progress(fmt::format("Installing file \"{}\"...", src->GetName()).c_str(),
638 "Cancel", 0, src->GetSize() / 0x1000, this);
639 progress.setWindowModality(Qt::WindowModal);
640
641 std::array<u8, 0x1000> buffer{};
642 for (size_t i = 0; i < src->GetSize(); i += 0x1000) {
643 if (progress.wasCanceled()) {
644 dest->Resize(0);
645 return false;
646 }
647
648 progress.setValue(i / 0x1000);
649 const auto read = src->Read(buffer.data(), buffer.size(), i);
650 dest->Write(buffer.data(), read, i);
651 }
652
653 return true;
654 };
655
656 const auto success = [this]() {
657 QMessageBox::information(this, tr("Successfully Installed"),
658 tr("The file was successfully installed."));
659 game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
660 };
661
662 const auto failed = [this]() {
663 QMessageBox::warning(
664 this, tr("Failed to Install"),
665 tr("There was an error while attempting to install the provided file. It "
666 "could have an incorrect format or be missing metadata. Please "
667 "double-check your file and try again."));
668 };
669
670 const auto overwrite = [this]() {
671 return QMessageBox::question(this, "Failed to Install",
672 "The file you are attempting to install already exists "
673 "in the cache. Would you like to overwrite it?") ==
674 QMessageBox::Yes;
675 };
676
677 if (!filename.isEmpty()) {
678 if (filename.endsWith("xci", Qt::CaseInsensitive)) {
679 const auto xci = std::make_shared<FileSys::XCI>(
680 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
681 if (xci->GetStatus() != Loader::ResultStatus::Success) {
682 failed();
683 return;
684 }
685 const auto res =
686 Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
687 if (res == FileSys::InstallResult::Success) {
688 success();
689 } else {
690 if (res == FileSys::InstallResult::ErrorAlreadyExists) {
691 if (overwrite()) {
692 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
693 xci, true, qt_raw_copy);
694 if (res2 == FileSys::InstallResult::Success) {
695 success();
696 } else {
697 failed();
698 }
699 }
700 } else {
701 failed();
702 }
703 }
704 } else {
705 const auto nca = std::make_shared<FileSys::NCA>(
706 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
707 if (nca->GetStatus() != Loader::ResultStatus::Success) {
708 failed();
709 return;
710 }
711
712 static const QStringList tt_options{"System Application",
713 "System Archive",
714 "System Application Update",
715 "Firmware Package (Type A)",
716 "Firmware Package (Type B)",
717 "Game",
718 "Game Update",
719 "Game DLC",
720 "Delta Title"};
721 bool ok;
722 const auto item = QInputDialog::getItem(
723 this, tr("Select NCA Install Type..."),
724 tr("Please select the type of title you would like to install this NCA as:\n(In "
725 "most instances, the default 'Game' is fine.)"),
726 tt_options, 5, false, &ok);
727
728 auto index = tt_options.indexOf(item);
729 if (!ok || index == -1) {
730 QMessageBox::warning(this, tr("Failed to Install"),
731 tr("The title type you selected for the NCA is invalid."));
732 return;
733 }
734
735 if (index >= 5)
736 index += 0x7B;
737
738 const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry(
739 nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy);
740 if (res == FileSys::InstallResult::Success) {
741 success();
742 } else {
743 if (res == FileSys::InstallResult::ErrorAlreadyExists) {
744 if (overwrite()) {
745 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
746 nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy);
747 if (res2 == FileSys::InstallResult::Success) {
748 success();
749 } else {
750 failed();
751 }
752 }
753 } else {
754 failed();
755 }
756 }
757 }
758 }
759}
760
618void GMainWindow::OnMenuSelectGameListRoot() { 761void GMainWindow::OnMenuSelectGameListRoot() {
619 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); 762 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
620 if (!dir_path.isEmpty()) { 763 if (!dir_path.isEmpty()) {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 74487c58c..5f4d2ab9a 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -125,6 +125,7 @@ private slots:
125 void OnGameListOpenSaveFolder(u64 program_id); 125 void OnGameListOpenSaveFolder(u64 program_id);
126 void OnMenuLoadFile(); 126 void OnMenuLoadFile();
127 void OnMenuLoadFolder(); 127 void OnMenuLoadFolder();
128 void OnMenuInstallToNAND();
128 /// Called whenever a user selects the "File->Select Game List Root" menu item 129 /// Called whenever a user selects the "File->Select Game List Root" menu item
129 void OnMenuSelectGameListRoot(); 130 void OnMenuSelectGameListRoot();
130 void OnMenuRecentFile(); 131 void OnMenuRecentFile();
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 22c4cad08..a3bfb2af3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -57,6 +57,8 @@
57 <string>Recent Files</string> 57 <string>Recent Files</string>
58 </property> 58 </property>
59 </widget> 59 </widget>
60 <addaction name="action_Install_File_NAND" />
61 <addaction name="separator"/>
60 <addaction name="action_Load_File"/> 62 <addaction name="action_Load_File"/>
61 <addaction name="action_Load_Folder"/> 63 <addaction name="action_Load_Folder"/>
62 <addaction name="separator"/> 64 <addaction name="separator"/>
@@ -102,6 +104,11 @@
102 <addaction name="menu_View"/> 104 <addaction name="menu_View"/>
103 <addaction name="menu_Help"/> 105 <addaction name="menu_Help"/>
104 </widget> 106 </widget>
107 <action name="action_Install_File_NAND">
108 <property name="text">
109 <string>Install File to NAND...</string>
110 </property>
111 </action>
105 <action name="action_Load_File"> 112 <action name="action_Load_File">
106 <property name="text"> 113 <property name="text">
107 <string>Load File...</string> 114 <string>Load File...</string>