diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 2babc86f..15d65623 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nso.cpp + ${source_DIR}/skyline/loader/nca.cpp ${source_DIR}/skyline/kernel/memory.cpp ${source_DIR}/skyline/kernel/ipc.cpp ${source_DIR}/skyline/kernel/svc.cpp @@ -104,6 +105,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/vfs/rom_filesystem.cpp ${source_DIR}/skyline/vfs/os_backing.cpp ${source_DIR}/skyline/vfs/nacp.cpp + ${source_DIR}/skyline/vfs/nca.cpp ) target_link_libraries(skyline vulkan android fmt tinyxml2 oboe lz4_static) diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index aa83cff0..c1ce0f6a 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -4,6 +4,7 @@ #include "skyline/vfs/os_backing.h" #include "skyline/loader/nro.h" #include "skyline/loader/nso.h" +#include "skyline/loader/nca.h" #include "skyline/jvm.h" extern "C" JNIEXPORT jlong JNICALL Java_emu_skyline_loader_RomFile_initialize(JNIEnv *env, jobject thiz, jint jformat, jint fd) { @@ -17,6 +18,8 @@ extern "C" JNIEXPORT jlong JNICALL Java_emu_skyline_loader_RomFile_initialize(JN return reinterpret_cast(new skyline::loader::NroLoader(backing)); case skyline::loader::RomFormat::NSO: return reinterpret_cast(new skyline::loader::NsoLoader(backing)); + case skyline::loader::RomFormat::NCA: + return reinterpret_cast(new skyline::loader::NcaLoader(backing)); default: return 0; } @@ -52,4 +55,4 @@ extern "C" JNIEXPORT jstring JNICALL Java_emu_skyline_loader_RomFile_getApplicat extern "C" JNIEXPORT void JNICALL Java_emu_skyline_loader_RomFile_destroy(JNIEnv *env, jobject thiz, jlong instance) { delete reinterpret_cast(instance); -} \ No newline at end of file +} diff --git a/app/src/main/cpp/skyline/kernel/memory.h b/app/src/main/cpp/skyline/kernel/memory.h index 3fb091cd..1d90955c 100644 --- a/app/src/main/cpp/skyline/kernel/memory.h +++ b/app/src/main/cpp/skyline/kernel/memory.h @@ -213,6 +213,7 @@ namespace skyline { namespace loader { class NroLoader; class NsoLoader; + class NcaLoader; } namespace kernel { @@ -324,6 +325,7 @@ namespace skyline { friend class type::KProcess; friend class loader::NroLoader; friend class loader::NsoLoader; + friend class loader::NcaLoader; friend void svc::SetMemoryAttribute(DeviceState &state); diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index 1fe4baf1..9ed7df7b 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -15,6 +15,7 @@ namespace skyline::loader { enum class RomFormat { NRO, //!< The NRO format: https://switchbrew.org/wiki/NRO NSO, //!< The NSO format: https://switchbrew.org/wiki/NSO + NCA, //!< The NCA format: https://switchbrew.org/wiki/NCA XCI, //!< The XCI format: https://switchbrew.org/wiki/XCI NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws }; diff --git a/app/src/main/cpp/skyline/loader/nca.cpp b/app/src/main/cpp/skyline/loader/nca.cpp new file mode 100644 index 00000000..d61d11b3 --- /dev/null +++ b/app/src/main/cpp/skyline/loader/nca.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "nso.h" +#include "nca.h" + +namespace skyline::loader { + NcaLoader::NcaLoader(const std::shared_ptr &backing) : nca(backing) { + if (nca.exeFs == nullptr) + throw exception("Only NCAs with an ExeFS can be loaded directly"); + } + + void NcaLoader::LoadExeFs(const std::shared_ptr &exeFs, const std::shared_ptr process, const DeviceState &state) { + if (exeFs == nullptr) + throw exception("Cannot load a null ExeFS"); + + auto nsoFile = exeFs->OpenFile("rtld"); + if (nsoFile == nullptr) + throw exception("Cannot load an ExeFS that doesn't contain rtld"); + + auto loadInfo = NsoLoader::LoadNso(nsoFile, process, state); + u64 offset = loadInfo.size; + u64 base = loadInfo.base; + + state.logger->Info("Loaded nso 'rtld' at 0x{:X}", base); + + for (const auto &nso : {"main", "subsdk0", "subsdk1", "subsdk2", "subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) { + nsoFile = exeFs->OpenFile(nso); + + if (nsoFile == nullptr) + continue; + + loadInfo = NsoLoader::LoadNso(nsoFile, process, state, offset); + state.logger->Info("Loaded nso '{}' at 0x{:X}", nso, base + offset); + offset += loadInfo.size; + } + + state.os->memory.InitializeRegions(base, offset, memory::AddressSpaceType::AddressSpace39Bit); + } + + void NcaLoader::LoadProcessData(const std::shared_ptr process, const DeviceState &state) { + LoadExeFs(nca.exeFs, process, state); + } +} diff --git a/app/src/main/cpp/skyline/loader/nca.h b/app/src/main/cpp/skyline/loader/nca.h new file mode 100644 index 00000000..71bcd90c --- /dev/null +++ b/app/src/main/cpp/skyline/loader/nca.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include "loader.h" + +namespace skyline::loader { + /** + * @brief The NcaLoader class allows loading an NCA's ExeFS through the Loader interface (https://switchbrew.org/wiki/NSO) + */ + class NcaLoader : public Loader { + private: + vfs::NCA nca; //!< The backing NCA of the loader + + public: + NcaLoader(const std::shared_ptr &backing); + + /** + * @brief This loads an ExeFS into memory + * @param exefs A filesystem object containing the ExeFS filesystem to load into memory + * @param process The process to load the ExeFS into + */ + static void LoadExeFs(const std::shared_ptr &exefs, const std::shared_ptr process, const DeviceState &state); + + void LoadProcessData(const std::shared_ptr process, const DeviceState &state); + }; +} diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index d6916a5c..6cbb662a 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -4,6 +4,7 @@ #include "vfs/os_backing.h" #include "loader/nro.h" #include "loader/nso.h" +#include "loader/nca.h" #include "nce/guest.h" #include "os.h" @@ -17,6 +18,8 @@ namespace skyline::kernel { state.loader = std::make_shared(romFile); } else if (romType == loader::RomFormat::NSO) { state.loader = std::make_shared(romFile); + } else if (romType == loader::RomFormat::NCA) { + state.loader = std::make_shared(romFile); } else { throw exception("Unsupported ROM extension."); } diff --git a/app/src/main/cpp/skyline/vfs/nca.cpp b/app/src/main/cpp/skyline/vfs/nca.cpp new file mode 100644 index 00000000..fec41842 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/nca.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include "region_backing.h" +#include "partition_filesystem.h" +#include "nca.h" +#include "rom_filesystem.h" +#include "directory.h" + +namespace skyline::vfs { + NCA::NCA(const std::shared_ptr &backing) : backing(backing) { + backing->Read(&header); + + if (header.magic != util::MakeMagic("NCA3")) + throw exception("Attempting to load an encrypted or invalid NCA"); + + contentType = header.contentType; + + for (size_t i = 0; i < header.sectionHeaders.size(); i++) { + auto §ionHeader = header.sectionHeaders.at(i); + auto §ionEntry = header.fsEntries.at(i); + + if (sectionHeader.fsType == NcaSectionFsType::PFS0 && sectionHeader.hashType == NcaSectionHashType::HierarchicalSha256) + ReadPfs0(sectionHeader, sectionEntry); + else if (sectionHeader.fsType == NcaSectionFsType::RomFs && sectionHeader.hashType == NcaSectionHashType::HierarchicalIntegrity) + ReadRomFs(sectionHeader, sectionEntry); + } + } + + void NCA::ReadPfs0(const NcaSectionHeader &header, const NcaFsEntry &entry) { + size_t offset = static_cast(entry.startOffset) * constant::MediaUnitSize + header.sha256HashInfo.pfs0Offset; + size_t size = constant::MediaUnitSize * static_cast(entry.endOffset - entry.startOffset); + + auto pfs = std::make_shared(std::make_shared(backing, offset, size)); + + if (contentType == NcaContentType::Program) { + // An ExeFS must always contain an NPDM and a main NSO, whereas the logo section will always contain a logo and a startup movie + if (pfs->FileExists("main") && pfs->FileExists("main.npdm")) + exeFs = std::move(pfs); + else if (pfs->FileExists("NintendoLogo.png") && pfs->FileExists("StartupMovie.gif")) + logo = std::move(pfs); + } else if (contentType == NcaContentType::Meta) { + cnmt = std::move(pfs); + } + } + + void NCA::ReadRomFs(const NcaSectionHeader &header, const NcaFsEntry &entry) { + size_t offset = static_cast(entry.startOffset) * constant::MediaUnitSize + header.integrityHashInfo.levels.back().offset; + size_t size = header.integrityHashInfo.levels.back().size; + + romFs = std::make_shared(backing, offset, size); + } +} \ No newline at end of file diff --git a/app/src/main/cpp/skyline/vfs/nca.h b/app/src/main/cpp/skyline/vfs/nca.h new file mode 100644 index 00000000..4e12b74d --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/nca.h @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include "filesystem.h" + +namespace skyline { + namespace constant { + constexpr size_t MediaUnitSize = 0x200; //!< The unit size of entries in an NCA + } + + namespace vfs { + /** + * @brief This enumerates the various content types of an NCA + */ + enum class NcaContentType : u8 { + Program = 0x0, //!< This is a program NCA + Meta = 0x1, //!< This is a metadata NCA + Control = 0x2, //!< This is a control NCA + Manual = 0x3, //!< This is a manual NCA + Data = 0x4, //!< This is a data NCA + PublicData = 0x5, //!< This is a public data NCA + }; + + /** + * @brief The NCA class provides an easy way to access the contents of an NCA file (https://switchbrew.org/wiki/NCA_Format) + */ + class NCA { + private: + /** + * @brief This enumerates the distribution types of an NCA + */ + enum class NcaDistributionType : u8 { + System = 0x0, //!< This NCA was distributed on the EShop or is part of the system + GameCard = 0x1, //!< This NCA was distributed on a GameCard + }; + + /** + * @brief This enumerates the key generation version in NCAs before HOS 3.0.1 + */ + enum class NcaLegacyKeyGenerationType : u8 { + Fw100 = 0x0, //!< 1.0.0 + Fw300 = 0x2, //!< 3.0.0 + }; + + /** + * @brief This enumerates the key generation version in NCAs after HOS 3.0.0 + */ + enum class NcaKeyGenerationType : u8 { + Fw301 = 0x3, //!< 3.0.1 + Fw400 = 0x4, //!< 4.0.0 + Fw500 = 0x5, //!< 5.0.0 + Fw600 = 0x6, //!< 6.0.0 + Fw620 = 0x7, //!< 6.2.0 + Fw700 = 0x8, //!< 7.0.0 + Fw810 = 0x9, //!< 8.1.0 + Fw900 = 0xa, //!< 9.0.0 + Fw910 = 0xb, //!< 9.1.0 + Invalid = 0xff, //!< An invalid key generation type + }; + + /** + * @brief This enumerates the key area encryption key types + */ + enum class NcaKeyAreaEncryptionKeyType : u8 { + Application = 0x0, //!< This NCA uses the application key encryption area + Ocean = 0x1, //!< This NCA uses the ocean key encryption area + System = 0x2, //!< This NCA uses the system key encryption area + }; + + /** + * @brief This hold the entry of a single filesystem in an NCA + */ + struct NcaFsEntry { + u32 startOffset; //!< The start offset of the filesystem in units of 0x200 bytes + u32 endOffset; //!< The start offset of the filesystem in units of 0x200 bytes + u64 _pad_; + }; + + /** + * @brief This enumerates the possible filesystem types a section of an NCA can contain + */ + enum class NcaSectionFsType : u8 { + RomFs = 0x0, //!< This section contains a RomFs filesystem + PFS0 = 0x1, //!< This section contains a PFS0 filesystem + }; + + /** + * @brief This enumerates the possible hash header types of an NCA section + */ + enum class NcaSectionHashType : u8 { + HierarchicalSha256 = 0x2, //!< The hash header for this section is that of a PFS0 + HierarchicalIntegrity = 0x3, //!< The hash header for this section is that of a RomFS + }; + + /** + * @brief This enumerates the possible encryption types of an NCA section + */ + enum class NcaSectionEncryptionType : u8 { + None = 0x1, //!< This NCA doesn't use any encryption + XTS = 0x2, //!< This NCA uses AES-XTS encryption + CTR = 0x3, //!< This NCA uses AES-CTR encryption + BKTR = 0x4, //!< This NCA uses BKTR together AES-CTR encryption + }; + + /** + * @brief This holds the data for a single level of the hierarchical integrity scheme + */ + struct HierarchicalIntegrityLevel { + u64 offset; //!< The offset of the level data + u64 size; //!< The size of the level data + u32 blockSize; //!< The block size of the level data + u32 _pad_; + }; + static_assert(sizeof(HierarchicalIntegrityLevel) == 0x18); + + /** + * @brief This holds the hash info header of the hierarchical integrity scheme + */ + struct HierarchicalIntegrityHashInfo { + u32 magic; //!< The hierarchical integrity magic, 'IVFC' + u32 magicNumber; //!< The magic number 0x2000 + u32 masterHashSize; //!< The size of the master hash + u32 numLevels; //!< The number of levels + std::array levels; //!< An array of the hierarchical integrity levels + u8 _pad0_[0x20]; + std::array masterHash; //!< The master hash of the hierarchical integrity system + u8 _pad1_[0x18]; + }; + static_assert(sizeof(HierarchicalIntegrityHashInfo) == 0xf8); + + /** + * @brief This holds the hash info header of the SHA256 hashing scheme for PFS0 + */ + struct HierarchicalSha256HashInfo { + std::array hashTableHash; //!< A SHA256 hash over the hash table + u32 blockSize; //!< The block size of the filesystem + u32 _pad_; + u64 hashTableOffset; //!< The offset from the end of the section header of the hash table + u64 hashTableSize; //!< The size of the hash table + u64 pfs0Offset; //!< The offset from the end of the section header of the PFS0 + u64 pfs0Size; //!< The size of the PFS0 + u8 _pad1_[0xb0]; + }; + static_assert(sizeof(HierarchicalSha256HashInfo) == 0xf8); + + /** + * @brief This holds the header of each specific section in an NCA + */ + struct NcaSectionHeader { + u16 version; //!< The version, always 2 + NcaSectionFsType fsType; //!< The type of the filesystem in the section + NcaSectionHashType hashType; //!< The type of hash header that is used for this section + NcaSectionEncryptionType encryptionType; //!< The type of encryption that is used for this section + u8 _pad0_[0x3]; + union { + HierarchicalIntegrityHashInfo integrityHashInfo; //!< The HashInfo used for RomFS + HierarchicalSha256HashInfo sha256HashInfo; //!< The HashInfo used for PFS0 + }; + u8 _pad1_[0x40]; // PatchInfo + u32 generation; //!< The generation of the NCA section + u32 secureValue; //!< The secure value of the section + u8 _pad2_[0x30]; //!< SparseInfo + u8 _pad3_[0x88]; + }; + static_assert(sizeof(NcaSectionHeader) == 0x200); + + /** + * @brief This struct holds the header of a Nintendo Content Archive + */ + struct NcaHeader { + std::array fixed_key_sig; //!< An RSA-PSS signature over the header with fixed key + std::array npdm_key_sig; //!< An RSA-PSS signature over header with key in NPDM + u32 magic; //!< The magic of the NCA: 'NCA3' + NcaDistributionType distributionType; //!< Whether this NCA is from a gamecard or the E-Shop + NcaContentType contentType; //!< The content type of the NCA + NcaLegacyKeyGenerationType legacyKeyGenerationType; //!< The keyblob to use for decryption + NcaKeyAreaEncryptionKeyType keyAreaEncryptionKeyType; //!< The index of the key area encryption key that is needed + u64 size; //!< The total size of the NCA + u64 programId; //!< The program ID of the NCA + u32 contentIndex; //!< The index of the content + u32 sdkVersion; //!< The version of the SDK the NCA was built with + NcaKeyGenerationType keyGenerationType; //!< The keyblob to use for decryption + u8 fixedKeyGeneration; //!< The fixed key index + u8 _pad0_[0xe]; + std::array rightsId; //!< The NCA's rights ID + std::array fsEntries; //!< The filesystem entries for this NCA + std::array, 4> sectionHashes; //!< This contains SHA-256 hashes for each filesystem header + std::array, 4> encryptedKeyArea; //!< The encrypted key area for each filesystem + u8 _pad1_[0xc0]; + std::array sectionHeaders; + } header{}; + static_assert(sizeof(NcaHeader) == 0xc00); + + std::shared_ptr backing; //!< The backing for the NCA + + void ReadPfs0(const NcaSectionHeader &header, const NcaFsEntry &entry); + + void ReadRomFs(const NcaSectionHeader &header, const NcaFsEntry &entry); + + public: + std::shared_ptr exeFs; //!< The PFS0 filesystem for this NCA's ExeFS section + std::shared_ptr logo; //!< The PFS0 filesystem for this NCA's logo section + std::shared_ptr cnmt; //!< The PFS0 filesystem for this NCA's CNMT section + std::shared_ptr romFs; //!< The backing for this NCA's RomFS section + NcaContentType contentType; //!< The content type of the NCA + + NCA(const std::shared_ptr &backing); + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index 0784da0c..239ce437 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -113,6 +113,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener, View.OnLongClick var foundRoms = addEntries("nro", RomFormat.NRO, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!) foundRoms = foundRoms or addEntries("nso", RomFormat.NSO, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!) + foundRoms = foundRoms or addEntries("nca", RomFormat.NCA, DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!) runOnUiThread { if (!foundRoms) diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt index 7ad9d920..fd869a55 100644 --- a/app/src/main/java/emu/skyline/loader/RomFile.kt +++ b/app/src/main/java/emu/skyline/loader/RomFile.kt @@ -25,8 +25,9 @@ import java.util.* enum class RomFormat(val format: Int){ NRO(0), NSO(1), - XCI(2), - NSP(3), + NCA(2), + XCI(3), + NSP(4), } /**