diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index cb1176ae0..d5d8d969f 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -265,6 +265,9 @@ add_library(skyline SHARED ${source_DIR}/skyline/vfs/npdm.cpp ${source_DIR}/skyline/vfs/nca.cpp ${source_DIR}/skyline/vfs/ticket.cpp + ${source_DIR}/skyline/vfs/cnmt.cpp + ${source_DIR}/skyline/vfs/bktr.cpp + ${source_DIR}/skyline/vfs/patch_manager.cpp ${source_DIR}/skyline/services/serviceman.cpp ${source_DIR}/skyline/services/base_service.cpp ${source_DIR}/skyline/services/sm/IUserInterface.cpp diff --git a/app/src/main/cpp/skyline/vfs/bktr.cpp b/app/src/main/cpp/skyline/vfs/bktr.cpp new file mode 100644 index 000000000..d1028bdfa --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/bktr.cpp @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2024 Strato Team and Contributors (https://github.com/strato-emu/) + +#include "bktr.h" +#include "region_backing.h" + +namespace skyline::vfs { + template + std::pair SearchBucketEntry(u64 offset, const BlockType &block, const BucketType &buckets, bool isSubsection) { + if (isSubsection) { + const auto &lastBucket{buckets[block.numberBuckets - 1]}; + if (offset >= lastBucket.entries[lastBucket.numberEntries].addressPatch) { + return {block.numberBuckets - 1, lastBucket.numberEntries}; + } + } + + u64 bucketId{static_cast(std::distance(block.baseOffsets.begin(), + std::upper_bound(block.baseOffsets.begin() + 1, + block.baseOffsets.begin() + block.numberBuckets, offset)) - 1)}; + + const auto &bucket{buckets[bucketId]}; + + if (bucket.numberEntries == 1) + return {bucketId, 0}; + + auto entryIt{std::upper_bound(bucket.entries.begin(), bucket.entries.begin() + bucket.numberEntries, offset, [](u64 offset, const auto& entry) { + return offset < entry.addressPatch; + })}; + + if (entryIt != bucket.entries.begin()) { + u64 entryIndex{static_cast(std::distance(bucket.entries.begin(), entryIt) - 1)}; + return {bucketId, entryIndex}; + } + Logger::Error("Offset could not be found."); + return {0, 0}; + } + + BKTR::BKTR(std::shared_ptr pBaseRomfs, std::shared_ptr pBktrRomfs, RelocationBlock pRelocation, + std::vector pRelocationBuckets, SubsectionBlock pSubsection, + std::vector pSubsectionBuckets, bool pIsEncrypted, std::array pKey, + u64 pBaseOffset, u64 pIvfcOffset, std::array pSectionCtr) + : baseRomFs(std::move(pBaseRomfs)), bktrRomFs(std::move(pBktrRomfs)), + relocation(pRelocation), relocationBuckets(std::move(pRelocationBuckets)), + subsection(pSubsection), subsectionBuckets(std::move(pSubsectionBuckets)), + isEncrypted(pIsEncrypted), key(pKey), baseOffset(pBaseOffset), ivfcOffset(pIvfcOffset), + sectionCtr(pSectionCtr) { + + for (std::size_t i = 0; i < relocation.numberBuckets - 1; ++i) + relocationBuckets[i].entries.push_back({relocation.baseOffsets[i + 1], 0, 0}); + + for (std::size_t i = 0; i < subsection.numberBuckets - 1; ++i) + subsectionBuckets[i].entries.push_back({subsectionBuckets[i + 1].entries[0].addressPatch, {0}, subsectionBuckets[i + 1].entries[0].ctr}); + + relocationBuckets.back().entries.push_back({relocation.size, 0, 0}); + } + + size_t BKTR::ReadImpl(span output, size_t offset) { + if (offset >= relocation.size) + return 0; + + const auto relocationEntry{GetRelocationEntry(offset)}; + const auto sectionOffset{offset - relocationEntry.addressPatch + relocationEntry.addressSource}; + + const auto nextRelocation{GetNextRelocationEntry(offset)}; + + if (offset + output.size() > nextRelocation.addressPatch) { + const u64 partition{nextRelocation.addressPatch - offset}; + span data(output.data() + partition, output.size() - partition); + return ReadWithPartition(data, output.size() - partition, offset + partition) + ReadWithPartition(output, partition, offset); + } + + if (!relocationEntry.fromPatch) { + auto regionBacking{std::make_shared(baseRomFs, sectionOffset - ivfcOffset, output.size())}; + return regionBacking->Read(output); + } + + if (!isEncrypted) + return bktrRomFs->Read(output, sectionOffset); + + const auto subsectionEntry{GetSubsectionEntry(sectionOffset)}; + + crypto::AesCipher cipher(key, MBEDTLS_CIPHER_AES_128_CTR); + cipher.SetIV(GetCipherIV(subsectionEntry, sectionOffset)); + + const auto nextSubsection{GetNextSubsectionEntry(sectionOffset)}; + + if (sectionOffset + output.size() > nextSubsection.addressPatch) { + const u64 partition{nextSubsection.addressPatch - sectionOffset}; + span data(output.data() + partition, output.size() - partition); + return ReadWithPartition(data, output.size() - partition, offset + partition) + + ReadWithPartition(output, partition, offset); + } + + const auto blockOffset{sectionOffset & 0xF}; + if (blockOffset != 0) { + std::vector block(0x10); + auto regionBacking{std::make_shared(bktrRomFs, sectionOffset & static_cast(~0xF), 0x10)}; + regionBacking->Read(block); + + cipher.Decrypt(block.data(), block.data(), block.size()); + if (output.size() + blockOffset < 0x10) { + std::memcpy(output.data(), block.data() + blockOffset, std::min(output.size(), block.size())); + return std::min(output.size(), block.size()); + } + + const auto read{0x10 - blockOffset}; + std::memcpy(output.data(), block.data() + blockOffset, read); + span data(output.data() + read, output.size() - read); + return read + ReadWithPartition(data, output.size() - read, offset + read); + } + + auto regionBacking{std::make_shared(bktrRomFs, sectionOffset, output.size())}; + auto readSize{regionBacking->Read(output)}; + cipher.Decrypt(output.data(), output.data(), readSize); + return readSize; + } + + size_t BKTR::ReadWithPartition(span output, size_t length, size_t offset) { + if (offset >= relocation.size) + return 0; + + const auto relocationEntry{GetRelocationEntry(offset)}; + const auto sectionOffset{offset - relocationEntry.addressPatch + relocationEntry.addressSource}; + + const auto nextRelocation{GetNextRelocationEntry(offset)}; + + if (offset + length > nextRelocation.addressPatch) { + const u64 partition{nextRelocation.addressPatch - offset}; + span data(output.data() + partition, length - partition); + return ReadWithPartition(data, length - partition, offset + partition) + ReadWithPartition(output, partition, offset); + } + + if (!relocationEntry.fromPatch) { + span data(output.data(), length); + auto regionBacking{std::make_shared(baseRomFs, sectionOffset - ivfcOffset, length)}; + return regionBacking->Read(data); + } + + if (!isEncrypted) + return bktrRomFs->Read(output, sectionOffset); + + const auto subsectionEntry{GetSubsectionEntry(sectionOffset)}; + + crypto::AesCipher cipher(key, MBEDTLS_CIPHER_AES_128_CTR); + cipher.SetIV(GetCipherIV(subsectionEntry, sectionOffset)); + + const auto nextSubsection{GetNextSubsectionEntry(sectionOffset)}; + + if (sectionOffset + length > nextSubsection.addressPatch) { + const u64 partition{nextSubsection.addressPatch - sectionOffset}; + span data(output.data() + partition, length - partition); + return ReadWithPartition(data, length - partition, offset + partition) + + ReadWithPartition(output, partition, offset); + } + + const auto blockOffset{sectionOffset & 0xF}; + if (blockOffset != 0) { + std::vector block(0x10); + auto regionBacking{std::make_shared(bktrRomFs, sectionOffset & static_cast(~0xF), 0x10)}; + regionBacking->Read(block); + + cipher.Decrypt(block.data(), block.data(), block.size()); + if (length + blockOffset < 0x10) { + std::memcpy(output.data(), block.data() + blockOffset, std::min(length, block.size())); + return std::min(length, block.size()); + } + + const auto read{0x10 - blockOffset}; + std::memcpy(output.data(), block.data() + blockOffset, read); + span data(output.data() + read, length - read); + return read + ReadWithPartition(data, length - read, offset + read); + } + + auto regionBacking{std::make_shared(bktrRomFs, sectionOffset, length)}; + span data(output.data(), length); + size_t readSize{0}; + if (length) + readSize = regionBacking->Read(data); + cipher.Decrypt(data.data(), data.data(), readSize); + return readSize; + } + + SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) { + const auto entry{SearchBucketEntry(offset, subsection, subsectionBuckets, true)}; + const auto bucket{subsectionBuckets[entry.first]}; + if (entry.second + 1 < bucket.entries.size()) + return bucket.entries[entry.second + 1]; + return subsectionBuckets[entry.first + 1].entries[0]; + } + + RelocationEntry BKTR::GetRelocationEntry(u64 offset) { + const auto entry{SearchBucketEntry(offset, relocation, relocationBuckets, false)}; + return relocationBuckets[entry.first].entries[entry.second]; + } + + SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) { + const auto entry{SearchBucketEntry(offset, subsection, subsectionBuckets, true)}; + return subsectionBuckets[entry.first].entries[entry.second]; + } + + RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) { + const auto entry{SearchBucketEntry(offset, relocation, relocationBuckets, false)}; + const auto bucket{relocationBuckets[entry.first]}; + if (entry.second + 1 < bucket.entries.size()) + return bucket.entries[entry.second + 1]; + return relocationBuckets[entry.first + 1].entries[0]; + } + + std::array BKTR::GetCipherIV(SubsectionEntry subsectionEntry, u64 sectionOffset) { + std::array iv{}; + auto subsectionCtr{subsectionEntry.ctr}; + auto offset_iv{sectionOffset + baseOffset}; + for (std::size_t i = 0; i < sectionCtr.size(); ++i) { + iv[i] = sectionCtr[0x8 - i - 1]; + } + offset_iv >>= 4; + for (std::size_t i = 0; i < sizeof(u64); ++i) { + iv[0xF - i] = static_cast(offset_iv & 0xFF); + offset_iv >>= 8; + } + for (std::size_t i = 0; i < sizeof(u32); ++i) { + iv[0x7 - i] = static_cast(subsectionCtr & 0xFF); + subsectionCtr >>= 8; + } + return iv; + } +} diff --git a/app/src/main/cpp/skyline/vfs/bktr.h b/app/src/main/cpp/skyline/vfs/bktr.h new file mode 100644 index 000000000..cb5043209 --- /dev/null +++ b/app/src/main/cpp/skyline/vfs/bktr.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2024 Strato Team and Contributors (https://github.com/strato-emu/) + +#pragma once + +#include "filesystem.h" +#include "nca.h" + +namespace skyline::vfs { + + /** + * @brief Allows reading patched RomFs + * @url https://switchbrew.org/wiki/NCA#RomFs_Patching + */ + class BKTR : public Backing { + private: + std::shared_ptr baseRomFs; + std::shared_ptr bktrRomFs; + RelocationBlock relocation; + SubsectionBlock subsection; + std::vector relocationBuckets; + std::vector subsectionBuckets; + bool isEncrypted; + u64 baseOffset; + u64 ivfcOffset; + std::array sectionCtr; + std::array key; + + SubsectionEntry GetNextSubsectionEntry(u64 offset); + + RelocationEntry GetRelocationEntry(u64 offset); + + RelocationEntry GetNextRelocationEntry(u64 offset); + + SubsectionEntry GetSubsectionEntry(u64 offset); + + std::array GetCipherIV(SubsectionEntry subsectionEntry, u64 sectionOffset); + + public: + + BKTR(std::shared_ptr pBaseRomfs, std::shared_ptr pBktrRomfs, RelocationBlock pRelocation, + std::vector pRelocationBuckets, SubsectionBlock pSubsection, + std::vector pSubsectionBuckets, bool pIsEncrypted, std::array pKey, + u64 pBaseOffset, u64 pIvfcOffset, std::array pSectionCtr); + + size_t ReadImpl(span output, size_t offset) override; + size_t ReadWithPartition(span output, size_t length, size_t offset); + }; +}