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 <array>
6 #include <string>
7 
8 #include <fmt/ostream.h>
9 
10 #include "common/logging/log.h"
11 #include "core/crypto/key_manager.h"
12 #include "core/file_sys/card_image.h"
13 #include "core/file_sys/content_archive.h"
14 #include "core/file_sys/nca_metadata.h"
15 #include "core/file_sys/partition_filesystem.h"
16 #include "core/file_sys/submission_package.h"
17 #include "core/file_sys/vfs_concat.h"
18 #include "core/file_sys/vfs_offset.h"
19 #include "core/file_sys/vfs_vector.h"
20 #include "core/loader/loader.h"
21 
22 namespace FileSys {
23 
24 constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
25 constexpr std::array partition_names{
26     "update",
27     "normal",
28     "secure",
29     "logo",
30 };
31 
XCI(VirtualFile file_,std::size_t program_index)32 XCI::XCI(VirtualFile file_, std::size_t program_index)
33     : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
34       partitions(partition_names.size()),
35       partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
36     if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
37         status = Loader::ResultStatus::ErrorBadXCIHeader;
38         return;
39     }
40 
41     if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
42         status = Loader::ResultStatus::ErrorBadXCIHeader;
43         return;
44     }
45 
46     PartitionFilesystem main_hfs(std::make_shared<OffsetVfsFile>(
47         file, file->GetSize() - header.hfs_offset, header.hfs_offset));
48 
49     update_normal_partition_end = main_hfs.GetFileOffsets()["secure"];
50 
51     if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
52         status = main_hfs.GetStatus();
53         return;
54     }
55 
56     for (XCIPartition partition :
57          {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
58         const auto partition_idx = static_cast<std::size_t>(partition);
59         auto raw = main_hfs.GetFile(partition_names[partition_idx]);
60 
61         partitions_raw[static_cast<std::size_t>(partition)] = std::move(raw);
62     }
63 
64     secure_partition = std::make_shared<NSP>(
65         main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]),
66         program_index);
67 
68     ncas = secure_partition->GetNCAsCollapsed();
69     program =
70         secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
71     program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
72     if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
73         program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
74     }
75 
76     auto result = AddNCAFromPartition(XCIPartition::Normal);
77     if (result != Loader::ResultStatus::Success) {
78         status = result;
79         return;
80     }
81 
82     if (GetFormatVersion() >= 0x2) {
83         result = AddNCAFromPartition(XCIPartition::Logo);
84         if (result != Loader::ResultStatus::Success) {
85             status = result;
86             return;
87         }
88     }
89 
90     status = Loader::ResultStatus::Success;
91 }
92 
93 XCI::~XCI() = default;
94 
GetStatus() const95 Loader::ResultStatus XCI::GetStatus() const {
96     return status;
97 }
98 
GetProgramNCAStatus() const99 Loader::ResultStatus XCI::GetProgramNCAStatus() const {
100     return program_nca_status;
101 }
102 
GetPartition(XCIPartition partition)103 VirtualDir XCI::GetPartition(XCIPartition partition) {
104     const auto id = static_cast<std::size_t>(partition);
105     if (partitions[id] == nullptr && partitions_raw[id] != nullptr) {
106         partitions[id] = std::make_shared<PartitionFilesystem>(partitions_raw[id]);
107     }
108 
109     return partitions[static_cast<std::size_t>(partition)];
110 }
111 
GetPartitions()112 std::vector<VirtualDir> XCI::GetPartitions() {
113     std::vector<VirtualDir> out;
114     for (const auto& id :
115          {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
116         const auto part = GetPartition(id);
117         if (part != nullptr) {
118             out.push_back(part);
119         }
120     }
121     return out;
122 }
123 
GetSecurePartitionNSP() const124 std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
125     return secure_partition;
126 }
127 
GetSecurePartition()128 VirtualDir XCI::GetSecurePartition() {
129     return GetPartition(XCIPartition::Secure);
130 }
131 
GetNormalPartition()132 VirtualDir XCI::GetNormalPartition() {
133     return GetPartition(XCIPartition::Normal);
134 }
135 
GetUpdatePartition()136 VirtualDir XCI::GetUpdatePartition() {
137     return GetPartition(XCIPartition::Update);
138 }
139 
GetLogoPartition()140 VirtualDir XCI::GetLogoPartition() {
141     return GetPartition(XCIPartition::Logo);
142 }
143 
GetPartitionRaw(XCIPartition partition) const144 VirtualFile XCI::GetPartitionRaw(XCIPartition partition) const {
145     return partitions_raw[static_cast<std::size_t>(partition)];
146 }
147 
GetSecurePartitionRaw() const148 VirtualFile XCI::GetSecurePartitionRaw() const {
149     return GetPartitionRaw(XCIPartition::Secure);
150 }
151 
GetStoragePartition0() const152 VirtualFile XCI::GetStoragePartition0() const {
153     return std::make_shared<OffsetVfsFile>(file, update_normal_partition_end, 0, "partition0");
154 }
155 
GetStoragePartition1() const156 VirtualFile XCI::GetStoragePartition1() const {
157     return std::make_shared<OffsetVfsFile>(file, file->GetSize() - update_normal_partition_end,
158                                            update_normal_partition_end, "partition1");
159 }
160 
GetNormalPartitionRaw() const161 VirtualFile XCI::GetNormalPartitionRaw() const {
162     return GetPartitionRaw(XCIPartition::Normal);
163 }
164 
GetUpdatePartitionRaw() const165 VirtualFile XCI::GetUpdatePartitionRaw() const {
166     return GetPartitionRaw(XCIPartition::Update);
167 }
168 
GetLogoPartitionRaw() const169 VirtualFile XCI::GetLogoPartitionRaw() const {
170     return GetPartitionRaw(XCIPartition::Logo);
171 }
172 
GetProgramTitleID() const173 u64 XCI::GetProgramTitleID() const {
174     return secure_partition->GetProgramTitleID();
175 }
176 
GetSystemUpdateVersion()177 u32 XCI::GetSystemUpdateVersion() {
178     const auto update = GetPartition(XCIPartition::Update);
179     if (update == nullptr)
180         return 0;
181 
182     for (const auto& file : update->GetFiles()) {
183         NCA nca{file, nullptr, 0};
184 
185         if (nca.GetStatus() != Loader::ResultStatus::Success)
186             continue;
187 
188         if (nca.GetType() == NCAContentType::Meta && nca.GetTitleId() == 0x0100000000000816) {
189             const auto dir = nca.GetSubdirectories()[0];
190             const auto cnmt = dir->GetFile("SystemUpdate_0100000000000816.cnmt");
191             if (cnmt == nullptr)
192                 continue;
193 
194             CNMT cnmt_data{cnmt};
195 
196             const auto metas = cnmt_data.GetMetaRecords();
197             if (metas.empty())
198                 continue;
199 
200             return metas[0].title_version;
201         }
202     }
203 
204     return 0;
205 }
206 
GetSystemUpdateTitleID() const207 u64 XCI::GetSystemUpdateTitleID() const {
208     return 0x0100000000000816;
209 }
210 
HasProgramNCA() const211 bool XCI::HasProgramNCA() const {
212     return program != nullptr;
213 }
214 
GetProgramNCAFile() const215 VirtualFile XCI::GetProgramNCAFile() const {
216     if (!HasProgramNCA()) {
217         return nullptr;
218     }
219 
220     return program->GetBaseFile();
221 }
222 
GetNCAs() const223 const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
224     return ncas;
225 }
226 
GetNCAByType(NCAContentType type) const227 std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
228     const auto iter =
229         std::find_if(ncas.begin(), ncas.end(),
230                      [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
231     return iter == ncas.end() ? nullptr : *iter;
232 }
233 
GetNCAFileByType(NCAContentType type) const234 VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
235     auto nca = GetNCAByType(type);
236     if (nca != nullptr) {
237         return nca->GetBaseFile();
238     }
239     return nullptr;
240 }
241 
GetFiles() const242 std::vector<VirtualFile> XCI::GetFiles() const {
243     return {};
244 }
245 
GetSubdirectories() const246 std::vector<VirtualDir> XCI::GetSubdirectories() const {
247     return {};
248 }
249 
GetName() const250 std::string XCI::GetName() const {
251     return file->GetName();
252 }
253 
GetParentDirectory() const254 VirtualDir XCI::GetParentDirectory() const {
255     return file->GetContainingDirectory();
256 }
257 
ConcatenatedPseudoDirectory()258 VirtualDir XCI::ConcatenatedPseudoDirectory() {
259     const auto out = std::make_shared<VectorVfsDirectory>();
260     for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
261         const auto& part = GetPartition(part_id);
262         if (part == nullptr)
263             continue;
264 
265         for (const auto& file : part->GetFiles())
266             out->AddFile(file);
267     }
268 
269     return out;
270 }
271 
GetCertificate() const272 std::array<u8, 0x200> XCI::GetCertificate() const {
273     std::array<u8, 0x200> out;
274     file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
275     return out;
276 }
277 
AddNCAFromPartition(XCIPartition part)278 Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
279     const auto partition_index = static_cast<std::size_t>(part);
280     const auto partition = GetPartition(part);
281 
282     if (partition == nullptr) {
283         return Loader::ResultStatus::ErrorXCIMissingPartition;
284     }
285 
286     for (const VirtualFile& file : partition->GetFiles()) {
287         if (file->GetExtension() != "nca") {
288             continue;
289         }
290 
291         auto nca = std::make_shared<NCA>(file, nullptr, 0);
292         if (nca->IsUpdate()) {
293             continue;
294         }
295         if (nca->GetType() == NCAContentType::Program) {
296             program_nca_status = nca->GetStatus();
297         }
298         if (nca->GetStatus() == Loader::ResultStatus::Success) {
299             ncas.push_back(std::move(nca));
300         } else {
301             const u16 error_id = static_cast<u16>(nca->GetStatus());
302             LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
303                          partition_names[partition_index], nca->GetName(), error_id,
304                          nca->GetStatus());
305         }
306     }
307 
308     return Loader::ResultStatus::Success;
309 }
310 
GetFormatVersion()311 u8 XCI::GetFormatVersion() {
312     return GetLogoPartition() == nullptr ? 0x1 : 0x2;
313 }
314 } // namespace FileSys
315