From 666df1eb4325133f68c9da85c61b51ea2ebbd580 Mon Sep 17 00:00:00 2001 From: sspacelynx <37104290+sspacelynx@users.noreply.github.com> Date: Sat, 27 Feb 2021 23:11:09 +0100 Subject: [PATCH] Add XCI file format support --- app/CMakeLists.txt | 1 + app/src/main/cpp/loader_jni.cpp | 4 + app/src/main/cpp/skyline/loader/nsp.cpp | 2 +- app/src/main/cpp/skyline/loader/xci.cpp | 90 +++++++++++++ app/src/main/cpp/skyline/loader/xci.h | 124 ++++++++++++++++++ app/src/main/cpp/skyline/os.cpp | 3 + app/src/main/java/emu/skyline/MainActivity.kt | 1 + 7 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 app/src/main/cpp/skyline/loader/xci.cpp create mode 100644 app/src/main/cpp/skyline/loader/xci.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b32201bb..541eb51c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -72,6 +72,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nso.cpp ${source_DIR}/skyline/loader/nca.cpp + ${source_DIR}/skyline/loader/xci.cpp ${source_DIR}/skyline/loader/nsp.cpp ${source_DIR}/skyline/vfs/os_filesystem.cpp ${source_DIR}/skyline/vfs/partition_filesystem.cpp diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index 48fbcb1d..f472bb89 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -7,6 +7,7 @@ #include "skyline/loader/nro.h" #include "skyline/loader/nso.h" #include "skyline/loader/nca.h" +#include "skyline/loader/xci.h" #include "skyline/loader/nsp.h" #include "skyline/jvm.h" @@ -31,6 +32,9 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn case skyline::loader::RomFormat::NCA: loader = std::make_unique(backing, keyStore); break; + case skyline::loader::RomFormat::XCI: + loader = std::make_unique(backing, keyStore); + break; case skyline::loader::RomFormat::NSP: loader = std::make_unique(backing, keyStore); break; diff --git a/app/src/main/cpp/skyline/loader/nsp.cpp b/app/src/main/cpp/skyline/loader/nsp.cpp index 1a8527f8..15eef529 100644 --- a/app/src/main/cpp/skyline/loader/nsp.cpp +++ b/app/src/main/cpp/skyline/loader/nsp.cpp @@ -45,7 +45,7 @@ namespace skyline::loader { return std::vector(); auto root{controlRomFs->OpenDirectory("", {false, true})}; - std::shared_ptr icon; + std::shared_ptr icon{}; // Use the first icon file available for (const auto &entry : root->Read()) { diff --git a/app/src/main/cpp/skyline/loader/xci.cpp b/app/src/main/cpp/skyline/loader/xci.cpp new file mode 100644 index 00000000..782994f1 --- /dev/null +++ b/app/src/main/cpp/skyline/loader/xci.cpp @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "nca.h" +#include "xci.h" + +namespace skyline::loader { + XciLoader::XciLoader(const std::shared_ptr &backing, const std::shared_ptr &keyStore) { + header = backing->Read(); + + if (header.magic != util::MakeMagic("HEAD")) + throw exception("Invalid XCI file"); + + xci = std::make_shared(std::make_shared(backing, header.hfs0PartitionOffset, header.hfs0HeaderSize * sizeof(u32))); + + auto root{xci->OpenDirectory("", {false, true})}; + for (const auto &entry : root->Read()) { + auto entryDir{std::make_shared(xci->OpenFile(entry.name))}; + if (entry.name == "secure") + secure = entryDir; + else if (entry.name == "normal") + normal = entryDir; + else if (entry.name == "update") + update = entryDir; + else if (entry.name == "logo") + logo = entryDir; + } + + if (secure) { + root = secure->OpenDirectory("", {false, true}); + for (const auto &entry : root->Read()) { + if (entry.name.substr(entry.name.find_last_of('.') + 1) != "nca") + continue; + + try { + auto nca{vfs::NCA(secure->OpenFile(entry.name), keyStore)}; + + if (nca.contentType == vfs::NcaContentType::Program && nca.romFs != nullptr && nca.exeFs != nullptr) + programNca = std::move(nca); + else if (nca.contentType == vfs::NcaContentType::Control && nca.romFs != nullptr) + controlNca = std::move(nca); + } catch (const loader_exception &e) { + throw loader_exception(e.error); + } catch (const std::exception &e) { + continue; + } + } + } else { + throw exception("Corrupted secure partition"); + } + + if (!programNca || !controlNca) + throw exception("Incomplete XCI file"); + + romFs = programNca->romFs; + controlRomFs = std::make_shared(controlNca->romFs); + nacp.emplace(controlRomFs->OpenFile("control.nacp")); + } + + void *XciLoader::LoadProcessData(const std::shared_ptr &process, const DeviceState &state) { + process->npdm = vfs::NPDM(programNca->exeFs->OpenFile("main.npdm"), state); + return NcaLoader::LoadExeFs(this, programNca->exeFs, process, state); + } + + std::vector XciLoader::GetIcon() { + if (romFs == nullptr) + return std::vector(); + + auto root{controlRomFs->OpenDirectory("", {false, true})}; + std::shared_ptr icon{}; + + // Use the first icon file available + for (const auto &entry : root->Read()) { + if (entry.name.rfind("icon") == 0) { + icon = controlRomFs->OpenFile(entry.name); + break; + } + } + + if (icon == nullptr) + return std::vector(); + + std::vector buffer(icon->size); + icon->Read(buffer); + return buffer; + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/loader/xci.h b/app/src/main/cpp/skyline/loader/xci.h new file mode 100644 index 00000000..a3ef69cb --- /dev/null +++ b/app/src/main/cpp/skyline/loader/xci.h @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include +#include +#include "loader.h" + +namespace skyline::loader { + /** + * @brief The XciLoader class abstracts access to an XCI file through the Loader interface + * @url https://switchbrew.org/wiki/XCI + */ + class XciLoader : public Loader { + private: + enum class GamecardSize : u8 { + Size1GB = 0xFA, + Size2GB = 0xF8, + Size4GB = 0xF0, + Size8GB = 0xE0, + Size16GB = 0xE1, + Size32GB = 0xE2 + }; + + enum class GamecardFlags : u8 { + AutoBoot = 0, + HistoryErase = 1, + RepairTool = 2, + DifferentRegionCupToTerraDevice = 3, + DifferentRegionCupToGlobalDevice = 4 + }; + + /** + * @brief The encryption type used for gamecard verification + */ + enum class SecurityMode : u8 { + T1 = 0x01, + T2 = 0x02 + }; + + enum class FirmwareVersion : u64 { + Development = 0x00, + Retail = 0x01, + Retail400 = 0x02, //!< [4.0.0+] Retail + Retail1100 = 0x04 //!< [11.0.0+] Retail + }; + + /** + * @brief The speed at which the gamecard is accessed + */ + enum class AccessControl : u32 { + ClockRate25Mhz = 0x00A10011, + ClockRate50Mhz = 0x00A10010 + }; + + /** + * @brief [9.0.0+] The region of Switch HW the gamecard is compatible with + */ + enum class CompatType : u8 { + Global = 0x00, //!< Normal + China = 0x01 //!< Terra + }; + + struct GamecardInfo { + FirmwareVersion firmwareVersion; + AccessControl accessControl; //!< The speed at which the gamecard is accessed + u32 readTimeWait1; //!< Read Time Wait1, always 0x1388 + u32 readTimeWait2; //!< Read Time Wait2, always 0 + u32 writeTimeWait1; //!< Write Time Wait1, always 0 + u32 writeTimeWait2; //!< Write Time Wait2, always 0 + u32 firmwareMode; + u32 cupVersion; + CompatType compatType; + u8 _pad0_[0x3]; + u64 updatePartitionHash; + u64 cupId; //!< CUP ID, always 0x0100000000000816, which is the title-listing data archive's title ID + u8 _pad1_[0x38]; + }; + static_assert(sizeof(GamecardInfo) == 0x70); + + struct GamecardHeader { + u8 signature[0x100]; //!< RSA-2048 PKCS #1 signature over the header + u32 magic; //!< The magic of the gamecard format: 'HEAD' + u32 secureAreaStartAddress; //!< Secure Area Start Address in media units + u32 backupAreaStartAddress; //!< Backup Area Start Address, always 0xFFFFFFFF + u8 titleKeyDecKekIndex; //!< TitleKeyDec Index (high nibble) and KEK Index (low nibble) + GamecardSize size; + u8 version; //!< Gamecard header version + GamecardFlags flags; //!< GameCardAttribute + u64 packageId; //!< The package ID, used for challenge–response authentication + u64 validDataEndAddress; //!< Valid Data End Address in media units + std::array infoIv; //!< Gamecard Info IV (reversed) + u64 hfs0PartitionOffset; //!< The HFS0 header partition offset + u64 hfs0HeaderSize; + std::array hfs0HeaderSha256; //!< SHA-256 hash of the HFS0 Header + std::array initialDataSha256; //!< SHA-256 hash of the Initial Data + SecurityMode securityMode; + u32 t1KeyIndex; //!< T1 Key Index, always 2 + u32 keyIndex; //!< Key Index, always 0 + u32 normalAreaEndAddress; //!< Normal Area End Address in media units + GamecardInfo gamecardInfo; //!< Gamecard Info (AES-128-CBC encrypted) + } header{}; + static_assert(sizeof(GamecardHeader) == 0x200); + + std::shared_ptr xci; //!< A shared pointer to XCI HFS0 header partition + std::shared_ptr secure; //!< A shared pointer to the secure HFS0 partition + std::shared_ptr update; //!< A shared pointer to the update HFS0 partition + std::shared_ptr normal; //!< A shared pointer to the normal HFS0 partition + std::shared_ptr logo; //!< A shared pointer to the logo HFS0 partition + std::shared_ptr controlRomFs; //!< A shared pointer to the control NCA's RomFS + std::optional programNca; //!< The main program NCA within the secure partition + std::optional controlNca; //!< The main control NCA within the secure partition + + public: + XciLoader(const std::shared_ptr &backing, const std::shared_ptr &keyStore); + + std::vector GetIcon() override; + + void *LoadProcessData(const std::shared_ptr &process, const DeviceState &state) override; + }; +} diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index fe7544f6..30f0e080 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -9,6 +9,7 @@ #include "loader/nso.h" #include "loader/nca.h" #include "loader/nsp.h" +#include "loader/xci.h" #include "os.h" namespace skyline::kernel { @@ -28,6 +29,8 @@ namespace skyline::kernel { return std::make_shared(std::move(romFile), std::move(keyStore)); case loader::RomFormat::NSP: return std::make_shared(romFile, keyStore); + case loader::RomFormat::XCI: + return std::make_shared(romFile, keyStore); default: throw exception("Unsupported ROM extension."); } diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index 1ef1f260..4cbcce9a 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -139,6 +139,7 @@ class MainActivity : AppCompatActivity() { addEntries("nro", RomFormat.NRO, searchLocation, romElements) addEntries("nso", RomFormat.NSO, searchLocation, romElements) addEntries("nca", RomFormat.NCA, searchLocation, romElements) + addEntries("xci", RomFormat.XCI, searchLocation, romElements) addEntries("nsp", RomFormat.NSP, searchLocation, romElements) runOnUiThread {