diff --git a/libraries/libstratosphere/include/stratosphere/fs.hpp b/libraries/libstratosphere/include/stratosphere/fs.hpp index d7f89ad64..96093da58 100644 --- a/libraries/libstratosphere/include/stratosphere/fs.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs.hpp @@ -16,6 +16,7 @@ #pragma once #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp index a6b3855a6..3d8f44ec7 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_file_storage.hpp @@ -17,11 +17,14 @@ #include #include #include +#include #include namespace ams::fs { class FileStorage : public IStorage, public impl::Newable { + NON_COPYABLE(FileStorage); + NON_MOVEABLE(FileStorage); private: static constexpr s64 InvalidSize = -1; private: @@ -43,8 +46,25 @@ namespace ams::fs { } virtual ~FileStorage() { /* ... */ } - protected: + private: Result UpdateSize(); + protected: + constexpr FileStorage() : unique_file(), shared_file(), base_file(nullptr), size(InvalidSize) { /* ... */ } + + void SetFile(fs::fsa::IFile *file) { + AMS_ASSERT(file != nullptr); + AMS_ASSERT(this->base_file == nullptr); + this->base_file = file; + } + + void SetFile(std::unique_ptr &&file) { + AMS_ASSERT(file != nullptr); + AMS_ASSERT(this->base_file == nullptr); + AMS_ASSERT(this->unique_file == nullptr); + + this->unique_file = std::move(file); + this->base_file = this->unique_file.get(); + } public: virtual Result Read(s64 offset, void *buffer, size_t size) override; virtual Result Write(s64 offset, const void *buffer, size_t size) override; @@ -54,6 +74,17 @@ namespace ams::fs { virtual Result OperateRange(void *dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; }; + class FileStorageBasedFileSystem : public FileStorage { + NON_COPYABLE(FileStorageBasedFileSystem); + NON_MOVEABLE(FileStorageBasedFileSystem); + private: + std::shared_ptr base_file_system; + public: + constexpr FileStorageBasedFileSystem() : FileStorage(), base_file_system(nullptr) { /* ... */ } + + Result Initialize(std::shared_ptr base_file_system, const char *path, fs::OpenMode mode); + }; + class FileHandleStorage : public IStorage, public impl::Newable { private: static constexpr s64 InvalidSize = -1; diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp index 03e344745..abe362650 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_memory_management.hpp @@ -46,6 +46,17 @@ namespace ams::fs { return std::unique_ptr(static_cast(::ams::fs::impl::Allocate(sizeof(T))), Deleter(sizeof(T))); } + template + std::unique_ptr MakeUnique(size_t size) { + using T = typename std::remove_extent::type; + + static_assert(std::is_pod::value); + static_assert(std::is_array::value); + + const size_t alloc_size = sizeof(T) * size; + return std::unique_ptr(static_cast(::ams::fs::impl::Allocate(alloc_size)), Deleter(alloc_size)); + } + } } diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp index 1c0fbdebf..2456f48db 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_query_range.hpp @@ -47,6 +47,8 @@ namespace ams::fs { enum class AesCtrKeyTypeFlag : s32 { InternalKeyForSoftwareAes = (1 << 0), + InternalKeyForHardwareAes = (1 << 1), + ExternalKeyForHardwareAes = (1 << 2), }; } diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp index e663d67dc..83387f837 100644 --- a/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_save_data_types.hpp @@ -156,4 +156,14 @@ namespace ams::fs { static_assert(sizeof(SaveDataExtraData) == 0x200); static_assert(util::is_pod::value); + struct HashSalt { + static constexpr size_t Size = 32; + + u8 value[Size]; + }; + static_assert(std::is_pod::value); + static_assert(sizeof(HashSalt) == HashSalt::Size); + + using SaveDataHashSalt = std::optional; + } diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp new file mode 100644 index 000000000..73155a8f4 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_speed_emulation.hpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fs { + + enum class SpeedEmulationMode { + None = 0, + Faster = 1, + Slower = 2, + Random = 3, + }; + + /* TODO */ + /* Result SetSpeedEmulationMode(SpeedEmulationMode mode); */ + /* Result GetSpeedEmulationMode(SpeedEmulationMode *out); */ + +} diff --git a/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp b/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp new file mode 100644 index 000000000..a5ebbb643 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fs/fs_storage_type.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fs { + + enum StorageType : s32 { + StorageType_SaveData = 0, + StorageType_RomFs = 1, + StorageType_Authoring = 2, + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv.hpp b/libraries/libstratosphere/include/stratosphere/fssrv.hpp index f0f74dde0..91d839512 100644 --- a/libraries/libstratosphere/include/stratosphere/fssrv.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssrv.hpp @@ -15,5 +15,13 @@ */ #pragma once -#include "fssrv/fssrv_sf_path.hpp" -#include "fssrv/fssrv_path_normalizer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp new file mode 100644 index 000000000..8c1ab29d8 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_partition_file_system_creator.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssrv::fscreator { + + class PartitionFileSystemCreator : public IPartitionFileSystemCreator { + NON_COPYABLE(PartitionFileSystemCreator); + NON_MOVEABLE(PartitionFileSystemCreator); + public: + PartitionFileSystemCreator() { /* ... */ } + + virtual Result Create(std::shared_ptr *out, std::shared_ptr storage) override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp new file mode 100644 index 000000000..339983c35 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_rom_file_system_creator.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssrv::fscreator { + + class RomFileSystemCreator : public IRomFileSystemCreator { + NON_COPYABLE(RomFileSystemCreator); + NON_MOVEABLE(RomFileSystemCreator); + private: + MemoryResource *allocator; + public: + explicit RomFileSystemCreator(MemoryResource *mr) : allocator(mr) { /* ... */ } + + virtual Result Create(std::shared_ptr *out, std::shared_ptr storage) override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp new file mode 100644 index 000000000..a95edfc43 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fscreator/fssrv_storage_on_nca_creator.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::fssystem { + + struct NcaCryptoConfiguration; + +} + +namespace ams::fssrv::fscreator { + + class StorageOnNcaCreator : public IStorageOnNcaCreator { + NON_COPYABLE(StorageOnNcaCreator); + NON_MOVEABLE(StorageOnNcaCreator); + private: + MemoryResource *allocator; + fssystem::IBufferManager * const buffer_manager; + const fssystem::NcaCryptoConfiguration &nca_crypto_cfg; + bool is_prod; + bool is_enabled_program_verification; + private: + Result VerifyNcaHeaderSign2(fssystem::NcaReader *nca_reader, fs::IStorage *storage); + public: + explicit StorageOnNcaCreator(MemoryResource *mr, const fssystem::NcaCryptoConfiguration &cfg, bool prod, fssystem::IBufferManager *bm) : allocator(mr), buffer_manager(bm), nca_crypto_cfg(cfg), is_prod(prod), is_enabled_program_verification(true) { + /* ... */ + } + + virtual Result Create(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr nca_reader, s32 index, bool verify_header_sign_2) override; + virtual Result CreateWithPatch(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr original_nca_reader, std::shared_ptr current_nca_reader, s32 index, bool verify_header_sign_2) override; + virtual Result CreateNcaReader(std::shared_ptr *out, std::shared_ptr storage) override; + virtual Result VerifyAcid(fs::fsa::IFileSystem *fs, fssystem::NcaReader *nca_reader) override; + virtual void SetEnabledProgramVerification(bool en) override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp new file mode 100644 index 000000000..cc7fdf410 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_file_system_proxy_api.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssrv::fscreator { + + struct FileSystemCreatorInterfaces; + +} + +namespace ams::fssystem { + + class IBufferManager; + +} + +namespace ams::fssrv { + + void InitializeForFileSystemProxy(fscreator::FileSystemCreatorInterfaces *fs_creator_interfaces, fssystem::IBufferManager *buffer_manager, bool is_development_function_enabled); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp new file mode 100644 index 000000000..9b6235980 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_i_file_system_creator.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fs { + + class IStorage; + enum class BisPartitionId; + + namespace fsa { + + class IFileSystem; + + } + +} + +namespace ams::fssystem { + + class NcaReader; + class NcaFsHeaderReader; + + namespace save { + + /* TODO */ + + } + +} + +namespace ams::fssrv::fscreator { + + class IRomFileSystemCreator { + public: + virtual ~IRomFileSystemCreator() { /* ... */ } + virtual Result Create(std::shared_ptr *out, std::shared_ptr storage) = 0; + }; + + class IPartitionFileSystemCreator { + public: + virtual ~IPartitionFileSystemCreator() { /* ... */ } + virtual Result Create(std::shared_ptr *out, std::shared_ptr storage) = 0; + }; + + class IStorageOnNcaCreator { + public: + virtual ~IStorageOnNcaCreator() { /* ... */ } + virtual Result Create(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr nca_reader, s32 index, bool verify_header_sign_2) = 0; + virtual Result CreateWithPatch(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr original_nca_reader, std::shared_ptr current_nca_reader, s32 index, bool verify_header_sign_2) = 0; + virtual Result CreateNcaReader(std::shared_ptr *out, std::shared_ptr storage) = 0; + virtual Result VerifyAcid(fs::fsa::IFileSystem *fs, fssystem::NcaReader *nca_reader) = 0; + virtual void SetEnabledProgramVerification(bool en) = 0; + }; + + struct FileSystemCreatorInterfaces { + IRomFileSystemCreator *rom_fs_creator; + IPartitionFileSystemCreator *partition_fs_creator; + IStorageOnNcaCreator *storage_on_nca_creator; + /* TODO: More creators. */ + }; + static_assert(std::is_pod::value); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp new file mode 100644 index 000000000..08540f29e --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_exp_heap.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::fssrv { + + class MemoryResourceFromExpHeap : public ams::MemoryResource { + private: + lmem::HeapHandle heap_handle; + public: + constexpr explicit MemoryResourceFromExpHeap(lmem::HeapHandle handle) : heap_handle(handle) { /* ... */ } + protected: + virtual void *AllocateImpl(size_t size, size_t align) override { + return lmem::AllocateFromExpHeap(this->heap_handle, size, static_cast(align)); + } + + virtual void DeallocateImpl(void *p, size_t size, size_t align) override { + return lmem::FreeToExpHeap(this->heap_handle, p); + } + + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + return false; + } + }; + + class PeakCheckableMemoryResourceFromExpHeap : public ams::MemoryResource { + private: + lmem::HeapHandle heap_handle; + os::Mutex mutex; + size_t peak_free_size; + size_t current_free_size; + public: + constexpr explicit PeakCheckableMemoryResourceFromExpHeap(size_t heap_size) : heap_handle(nullptr), mutex(false), peak_free_size(heap_size), current_free_size(heap_size) { /* ... */ } + + void SetHeapHandle(lmem::HeapHandle handle) { + this->heap_handle = handle; + } + + size_t GetPeakFreeSize() const { return this->peak_free_size; } + size_t GetCurrentFreeSize() const { return this->current_free_size; } + + void ClearPeak() { this->peak_free_size = this->current_free_size; } + + std::scoped_lock GetScopedLock() { + return std::scoped_lock(this->mutex); + } + + void OnAllocate(void *p, size_t size); + void OnDeallocate(void *p, size_t size); + protected: + virtual void *AllocateImpl(size_t size, size_t align) override; + virtual void DeallocateImpl(void *p, size_t size, size_t align) override; + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + return false; + } + }; + +} \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp new file mode 100644 index 000000000..7c8119883 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_memory_resource_from_standard_allocator.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::mem { + + class StandardAllocator; + +} + +namespace ams::fssrv { + + class MemoryResourceFromStandardAllocator : public ams::MemoryResource { + private: + mem::StandardAllocator *allocator; + os::SdkMutex mutex; + size_t peak_free_size; + size_t current_free_size; + size_t peak_allocated_size; + public: + explicit MemoryResourceFromStandardAllocator(mem::StandardAllocator *allocator); + public: + size_t GetPeakFreeSize() const { return this->peak_free_size; } + size_t GetCurrentFreeSize() const { return this->current_free_size; } + size_t GetPeakAllocatedSize() const { return this->peak_allocated_size; } + + void ClearPeak(); + protected: + virtual void *AllocateImpl(size_t size, size_t align) override; + virtual void DeallocateImpl(void *p, size_t size, size_t align) override; + virtual bool IsEqualImpl(const MemoryResource &rhs) const override { + return false; + } + }; + +} \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp new file mode 100644 index 000000000..3199ddc5d --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssrv/fssrv_nca_crypto_configuration.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssrv { + + const ::ams::fssystem::NcaCryptoConfiguration *GetDefaultNcaCryptoConfiguration(bool prod); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem.hpp b/libraries/libstratosphere/include/stratosphere/fssystem.hpp index 10385e0e6..2be11f318 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem.hpp @@ -15,9 +15,10 @@ */ #pragma once +#include #include +#include #include -#include #include #include #include @@ -28,6 +29,22 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp index a2a69ac6c..db1e01bce 100644 --- a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_buffer_manager_utils.hpp @@ -87,4 +87,40 @@ namespace ams::fssystem::buffers { } }; + template + Result AllocateBufferUsingBufferManagerContext(std::pair *out, fssystem::IBufferManager *buffer_manager, size_t size, const IBufferManager::BufferAttribute attribute, IsValidBufferFunction is_valid_buffer, const char *func_name) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(buffer_manager != nullptr); + AMS_ASSERT(func_name != nullptr); + + /* Clear the output. */ + *out = std::pair(0, 0); + + /* Get the context. */ + auto context = GetBufferManagerContext(); + + auto AllocateBufferImpl = [=]() -> Result { + auto buffer = buffer_manager->AllocateBuffer(size, attribute); + if (!is_valid_buffer(buffer)) { + if (buffer.first != 0) { + buffer_manager->DeallocateBuffer(buffer.first, buffer.second); + } + return fs::ResultBufferAllocationFailed(); + } + *out = buffer; + return ResultSuccess(); + }; + + if (context == nullptr || !context->IsNeedBlocking()) { + /* If there's no context (or we don't need to block), just allocate the buffer. */ + R_TRY(AllocateBufferImpl()); + } else { + /* Otherwise, try to allocate repeatedly. */ + R_TRY(DoContinuouslyUntilBufferIsAllocated(AllocateBufferImpl, func_name)); + } + + AMS_ASSERT(out->first != 0); + return ResultSuccess(); + } + } diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp new file mode 100644 index 000000000..507fe6803 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_file_system_buffer_manager.hpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::fssystem { + + class FileSystemBufferManager : public IBufferManager { + NON_COPYABLE(FileSystemBufferManager); + NON_MOVEABLE(FileSystemBufferManager); + public: + using BuddyHeap = FileSystemBuddyHeap; + private: + class CacheHandleTable { + NON_COPYABLE(CacheHandleTable); + NON_MOVEABLE(CacheHandleTable); + private: + class Entry { + private: + CacheHandle handle; + uintptr_t address; + size_t size; + BufferAttribute attr; + public: + constexpr void Initialize(CacheHandle h, uintptr_t a, size_t sz, BufferAttribute t) { + this->handle = h; + this->address = a; + this->size = sz; + this->attr = t; + } + + constexpr CacheHandle GetHandle() const { + return this->handle; + } + + constexpr uintptr_t GetAddress() const { + return this->address; + } + + constexpr size_t GetSize() const { + return this->size; + } + + constexpr BufferAttribute GetBufferAttribute() const { + return this->attr; + } + }; + + class AttrInfo : public util::IntrusiveListBaseNode, public ::ams::fs::impl::Newable { + NON_COPYABLE(AttrInfo); + NON_MOVEABLE(AttrInfo); + private: + s32 level; + s32 cache_count; + size_t cache_size; + public: + constexpr AttrInfo(s32 l, s32 cc, size_t cs) : level(l), cache_count(cc), cache_size(cs) { + /* ... */ + } + + constexpr s32 GetLevel() const { + return this->level; + } + + constexpr s32 GetCacheCount() const { + return this->cache_count; + } + + constexpr void IncrementCacheCount() { + ++this->cache_count; + } + + constexpr void DecrementCacheCount() { + --this->cache_count; + } + + constexpr size_t GetCacheSize() const { + return this->cache_size; + } + + constexpr void AddCacheSize(size_t diff) { + this->cache_size += diff; + } + + constexpr void SubtractCacheSize(size_t diff) { + AMS_ASSERT(this->cache_size >= diff); + this->cache_size -= diff; + } + + using Newable::operator new; + using Newable::operator delete; + static void *operator new(size_t, void *p) { + return p; + } + + static void operator delete(void *, size_t, void*) { /* ... */ } + }; + + using AttrListTraits = util::IntrusiveListBaseTraits; + using AttrList = typename AttrListTraits::ListType; + private: + std::unique_ptr internal_entry_buffer; + char *external_entry_buffer; + size_t entry_buffer_size; + Entry *entries; + s32 entry_count; + s32 entry_count_max; + AttrList attr_list; + char *external_attr_info_buffer; + s32 external_attr_info_count; + s32 cache_count_min; + size_t cache_size_min; + size_t total_cache_size; + CacheHandle current_handle; + public: + static constexpr size_t QueryWorkBufferSize(s32 max_cache_count) { + AMS_ASSERT(max_cache_count > 0); + const auto entry_size = sizeof(Entry) * max_cache_count; + const auto attr_list_size = sizeof(AttrInfo) * 0x100; + return util::AlignUp(entry_size + attr_list_size + alignof(Entry) + alignof(AttrInfo), 8); + } + public: + CacheHandleTable() : internal_entry_buffer(), external_entry_buffer(), entry_buffer_size(), entries(), entry_count(), entry_count_max(), attr_list(), external_attr_info_buffer(), external_attr_info_count(), cache_count_min(), cache_size_min(), total_cache_size(), current_handle() { + /* ... */ + } + + ~CacheHandleTable() { + this->Finalize(); + } + + Result Initialize(s32 max_cache_count); + Result Initialize(s32 max_cache_count, void *work, size_t work_size) { + const auto aligned_entry_buf = util::AlignUp(reinterpret_cast(work), alignof(Entry)); + this->external_entry_buffer = reinterpret_cast(aligned_entry_buf); + this->entry_buffer_size = sizeof(Entry) * max_cache_count; + + const auto aligned_attr_info_buf = util::AlignUp(reinterpret_cast(this->external_entry_buffer + this->entry_buffer_size), alignof(AttrInfo)); + const auto work_end = reinterpret_cast(work) + work_size; + this->external_attr_info_buffer = reinterpret_cast(aligned_attr_info_buf); + this->external_attr_info_count = static_cast((work_end - aligned_attr_info_buf) / sizeof(AttrInfo)); + + return ResultSuccess(); + } + + void Finalize(); + + bool Register(CacheHandle *out, uintptr_t address, size_t size, const BufferAttribute &attr); + bool Unregister(uintptr_t *out_address, size_t *out_size, CacheHandle handle); + + + bool UnregisterOldest(uintptr_t *out_address, size_t *out_size, const BufferAttribute &attr, size_t required_size = 0); + + CacheHandle PublishCacheHandle(); + + size_t GetTotalCacheSize() const; + private: + void UnregisterCore(uintptr_t *out_address, size_t *out_size, Entry *entry); + + Entry *AcquireEntry(uintptr_t address, size_t size, const BufferAttribute &attr); + + void ReleaseEntry(Entry *entry); + + AttrInfo *FindAttrInfo(const BufferAttribute &attr); + + s32 GetCacheCountMin(const BufferAttribute &attr) { + return this->cache_count_min; + } + + size_t GetCacheSizeMin(const BufferAttribute &attr) { + return this->cache_size_min; + } + }; + private: + BuddyHeap buddy_heap; + CacheHandleTable cache_handle_table; + size_t total_size; + size_t peak_free_size; + size_t peak_total_allocatable_size; + size_t retried_count; + mutable os::Mutex mutex; + public: + static constexpr size_t QueryWorkBufferSize(s32 max_cache_count, s32 max_order) { + const auto buddy_size = FileSystemBuddyHeap::QueryWorkBufferSize(max_order); + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + return buddy_size + table_size; + } + public: + FileSystemBufferManager() : total_size(), peak_free_size(), peak_total_allocatable_size(), retried_count(), mutex(true) { /* ... */ } + + virtual ~FileSystemBufferManager() { /* ... */ } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size) { + AMS_ASSERT(buffer_size > 0); + R_TRY(this->cache_handle_table.Initialize(max_cache_count)); + R_TRY(this->buddy_heap.Initialize(address, buffer_size, block_size)); + + this->total_size = this->buddy_heap.GetTotalFreeSize(); + this->peak_free_size = this->total_size; + this->peak_total_allocatable_size = this->total_size; + + return ResultSuccess(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, s32 max_order) { + AMS_ASSERT(buffer_size > 0); + R_TRY(this->cache_handle_table.Initialize(max_cache_count)); + R_TRY(this->buddy_heap.Initialize(address, buffer_size, block_size, max_order)); + + this->total_size = this->buddy_heap.GetTotalFreeSize(); + this->peak_free_size = this->total_size; + this->peak_total_allocatable_size = this->total_size; + + return ResultSuccess(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, void *work, size_t work_size) { + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + const auto buddy_size = work_size - table_size; + AMS_ASSERT(work_size > table_size); + const auto table_buffer = static_cast(work); + const auto buddy_buffer = table_buffer + table_size; + + R_TRY(this->cache_handle_table.Initialize(max_cache_count, table_buffer, table_size)); + R_TRY(this->buddy_heap.Initialize(address, buffer_size, block_size, buddy_buffer, buddy_size)); + + this->total_size = this->buddy_heap.GetTotalFreeSize(); + this->peak_free_size = this->total_size; + this->peak_total_allocatable_size = this->total_size; + + return ResultSuccess(); + } + + Result Initialize(s32 max_cache_count, uintptr_t address, size_t buffer_size, size_t block_size, s32 max_order, void *work, size_t work_size) { + const auto table_size = CacheHandleTable::QueryWorkBufferSize(max_cache_count); + const auto buddy_size = work_size - table_size; + AMS_ASSERT(work_size > table_size); + const auto table_buffer = static_cast(work); + const auto buddy_buffer = table_buffer + table_size; + + R_TRY(this->cache_handle_table.Initialize(max_cache_count, table_buffer, table_size)); + R_TRY(this->buddy_heap.Initialize(address, buffer_size, block_size, max_order, buddy_buffer, buddy_size)); + + this->total_size = this->buddy_heap.GetTotalFreeSize(); + this->peak_free_size = this->total_size; + this->peak_total_allocatable_size = this->total_size; + + return ResultSuccess(); + } + + void Finalize() { + this->buddy_heap.Finalize(); + this->cache_handle_table.Finalize(); + } + private: + virtual const std::pair AllocateBufferImpl(size_t size, const BufferAttribute &attr) override; + + virtual void DeallocateBufferImpl(uintptr_t address, size_t size) override; + + virtual CacheHandle RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr) override; + + virtual const std::pair AcquireCacheImpl(CacheHandle handle) override; + + virtual size_t GetTotalSizeImpl() const override; + + virtual size_t GetFreeSizeImpl() const override; + + virtual size_t GetTotalAllocatableSizeImpl() const override; + + virtual size_t GetPeakFreeSizeImpl() const override; + + virtual size_t GetPeakTotalAllocatableSizeImpl() const override; + + virtual size_t GetRetriedCountImpl() const override; + + virtual void ClearPeakImpl() override; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_i_buffer_manager.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_i_buffer_manager.hpp new file mode 100644 index 000000000..b5d8140fd --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/buffers/fssystem_i_buffer_manager.hpp @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + class IBufferManager { + public: + class BufferAttribute { + private: + s32 level; + public: + constexpr BufferAttribute() : level(0) { /* ... */ } + constexpr explicit BufferAttribute(s32 l) : level(l) { /* ... */ } + + constexpr s32 GetLevel() const { return this->level; } + }; + + using CacheHandle = s64; + + static constexpr s32 BufferLevelMin = 0; + public: + virtual ~IBufferManager() { /* ... */ } + + const std::pair AllocateBuffer(size_t size, const BufferAttribute &attr) { + return this->AllocateBufferImpl(size, attr); + } + + const std::pair AllocateBuffer(size_t size) { + return this->AllocateBufferImpl(size, BufferAttribute()); + } + + void DeallocateBuffer(uintptr_t address, size_t size) { + return this->DeallocateBufferImpl(address, size); + } + + CacheHandle RegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) { + return this->RegisterCacheImpl(address, size, attr); + } + + const std::pair AcquireCache(CacheHandle handle) { + return this->AcquireCacheImpl(handle); + } + + size_t GetTotalSize() const { + return this->GetTotalSizeImpl(); + } + + size_t GetFreeSize() const { + return this->GetFreeSizeImpl(); + } + + size_t GetTotalAllocatableSize() const { + return this->GetTotalAllocatableSizeImpl(); + } + + size_t GetPeakFreeSize() const { + return this->GetPeakFreeSizeImpl(); + } + + size_t GetPeakTotalAllocatableSize() const { + return this->GetPeakTotalAllocatableSizeImpl(); + } + + size_t GetRetriedCount() const { + return this->GetRetriedCountImpl(); + } + + void ClearPeak() const { + return this->ClearPeak(); + } + protected: + virtual const std::pair AllocateBufferImpl(size_t size, const BufferAttribute &attr) = 0; + + virtual void DeallocateBufferImpl(uintptr_t address, size_t size) = 0; + + virtual CacheHandle RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr) = 0; + + virtual const std::pair AcquireCacheImpl(CacheHandle handle) = 0; + + virtual size_t GetTotalSizeImpl() const = 0; + + virtual size_t GetFreeSizeImpl() const = 0; + + virtual size_t GetTotalAllocatableSizeImpl() const = 0; + + virtual size_t GetPeakFreeSizeImpl() const = 0; + + virtual size_t GetPeakTotalAllocatableSizeImpl() const = 0; + + virtual size_t GetRetriedCountImpl() const = 0; + + virtual void ClearPeakImpl() = 0; + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/dbm/fssystem_dbm_utils.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/dbm/fssystem_dbm_utils.hpp new file mode 100644 index 000000000..984509aff --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/dbm/fssystem_dbm_utils.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem::dbm { + + namespace { + + constexpr inline s32 CountLeadingZeros(u32 val) { + return util::CountLeadingZeros(val); + } + + constexpr inline s32 CountLeadingOnes(u32 val) { + return CountLeadingZeros(~val); + } + + inline u32 ReadU32(const u8 *buf, size_t index) { + u32 val; + std::memcpy(std::addressof(val), buf + index, sizeof(u32)); + return val; + } + + inline void WriteU32(u8 *buf, size_t index, u32 val) { + std::memcpy(buf + index, std::addressof(val), sizeof(u32)); + } + + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_acid_sign_key.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_acid_sign_key.hpp deleted file mode 100644 index 716d50611..000000000 --- a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_acid_sign_key.hpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -namespace ams::fssystem { - - constexpr inline size_t AcidSignatureKeyGenerationMax = 1; - - constexpr inline size_t AcidSignatureKeyModulusSize = 0x100; - - constexpr inline const u8 AcidSignatureKeyModulusDev[AcidSignatureKeyGenerationMax + 1][AcidSignatureKeyModulusSize] = { - { - 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, - 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, - 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, - 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, - 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, - 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, - 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, - 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, - 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, - 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, - 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, - 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, - 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, - 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, - 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, - 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 - }, - { - 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, - 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, - 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, - 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, - 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, - 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, - 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, - 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, - 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, - 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, - 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, - 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, - 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, - 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, - 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, - 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B - } - }; - - constexpr inline const u8 AcidSignatureKeyModulusProd[AcidSignatureKeyGenerationMax + 1][AcidSignatureKeyModulusSize] = { - { - 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, - 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, - 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, - 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, - 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, - 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, - 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, - 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, - 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, - 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, - 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, - 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, - 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, - 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, - 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, - 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD - }, - { - 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, - 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, - 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, - 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, - 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, - 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, - 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, - 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, - 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, - 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, - 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, - 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, - 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, - 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, - 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, - 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 - } - }; - - static_assert(sizeof(AcidSignatureKeyModulusProd) == sizeof(AcidSignatureKeyModulusDev)); - - constexpr inline const u8 AcidSignatureKeyExponent[] = { - 0x01, 0x00, 0x01 - }; - - constexpr inline size_t AcidSignatureKeyExponentSize = util::size(AcidSignatureKeyExponent); - -} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp new file mode 100644 index 000000000..d1e198c85 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_aes_ctr_counter_extended_storage.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::fssystem { + + class AesCtrCounterExtendedStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrCounterExtendedStorage); + NON_MOVEABLE(AesCtrCounterExtendedStorage); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + static constexpr size_t NodeSize = 16_KB; + + using IAllocator = BucketTree::IAllocator; + + using DecryptFunction = void(*)(void *dst, size_t dst_size, s32 index, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + class IDecryptor { + public: + virtual ~IDecryptor() { /* ... */ } + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) = 0; + virtual bool HasExternalDecryptionKey() const = 0; + }; + + struct Entry { + u8 offset[sizeof(s64)]; + s32 reserved; + s32 generation; + + void SetOffset(s64 value) { + std::memcpy(this->offset, std::addressof(value), sizeof(s64)); + } + + s64 GetOffset() const { + s64 value; + std::memcpy(std::addressof(value), this->offset, sizeof(s64)); + return value; + } + }; + static_assert(sizeof(Entry) == 0x10); + static_assert(alignof(Entry) == 4); + static_assert(std::is_pod::value); + public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static Result CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index); + static Result CreateSoftwareDecryptor(std::unique_ptr *out); + private: + BucketTree table; + fs::SubStorage data_storage; + u8 key[KeySize]; + u32 secure_value; + s64 counter_offset; + std::unique_ptr decryptor; + public: + AesCtrCounterExtendedStorage() : table(), data_storage(), secure_value(), counter_offset(), decryptor() { /* ... */ } + virtual ~AesCtrCounterExtendedStorage() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, s64 counter_offset, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count, std::unique_ptr &&decryptor); + void Finalize(); + + bool IsInitialized() const { return this->table.IsInitialized(); } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + *out = this->table.GetSize(); + return ResultSuccess(); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperationInAesCtrCounterExtendedStorageA(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInAesCtrCounterExtendedStorageB(); + } + private: + Result Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, fs::SubStorage data_storage, fs::SubStorage table_storage); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp new file mode 100644 index 000000000..2ff67cea3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage.hpp @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::fssystem { + + template + class AlignmentMatchingStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStorage); + NON_MOVEABLE(AlignmentMatchingStorage); + public: + static constexpr size_t DataAlign = _DataAlign; + static constexpr size_t BufferAlign = _BufferAlign; + + static constexpr size_t DataAlignMax = 0x200; + static_assert(DataAlign <= DataAlignMax); + static_assert(util::IsPowerOfTwo(DataAlign)); + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + std::shared_ptr shared_base_storage; + fs::IStorage * const base_storage; + s64 base_storage_size; + bool is_base_storage_size_dirty; + public: + explicit AlignmentMatchingStorage(fs::IStorage *bs) : base_storage(bs), is_base_storage_size_dirty(true) { + /* ... */ + } + + explicit AlignmentMatchingStorage(std::shared_ptr bs) : shared_base_storage(bs), base_storage(shared_base_storage.get()), is_base_storage_size_dirty(true) { + /* ... */ + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Allocate a work buffer on stack. */ + __attribute__((aligned(DataAlignMax))) char work_buf[DataAlign]; + static_assert(util::IsAligned(alignof(work_buf), BufferAlign)); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + return AlignmentMatchingStorageImpl::Read(this->base_storage, work_buf, sizeof(work_buf), DataAlign, BufferAlign, offset, static_cast(buffer), size); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Allocate a work buffer on stack. */ + __attribute__((aligned(DataAlignMax))) char work_buf[DataAlign]; + static_assert(util::IsAligned(alignof(work_buf), BufferAlign)); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + return AlignmentMatchingStorageImpl::Write(this->base_storage, work_buf, sizeof(work_buf), DataAlign, BufferAlign, offset, static_cast(buffer), size); + } + + virtual Result Flush() override { + return this->base_storage->Flush(); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { this->is_base_storage_size_dirty = true; }; + return this->base_storage->SetSize(util::AlignUp(size, DataAlign)); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (this->is_base_storage_size_dirty) { + s64 size; + R_TRY(this->base_storage->GetSize(std::addressof(size))); + + this->base_storage_size = size; + this->is_base_storage_size_dirty = false; + } + + *out = this->base_storage_size; + return ResultSuccess(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange()); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, DataAlign); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, DataAlign); + const auto aligned_size = aligned_offset_end - aligned_offset; + + return this->base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size); + } + }; + + template + class AlignmentMatchingStoragePooledBuffer : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); + NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); + public: + static constexpr size_t BufferAlign = _BufferAlign; + + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + fs::IStorage * const base_storage; + s64 base_storage_size; + size_t data_align; + bool is_base_storage_size_dirty; + public: + explicit AlignmentMatchingStoragePooledBuffer(fs::IStorage *bs, size_t da) : base_storage(bs), data_align(da), is_base_storage_size_dirty(true) { + AMS_ASSERT(util::IsPowerOfTwo(da)); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(this->data_align, this->data_align); + + return AlignmentMatchingStorageImpl::Read(this->base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), this->data_align, BufferAlign, offset, static_cast(buffer), size); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(this->data_align, this->data_align); + + return AlignmentMatchingStorageImpl::Write(this->base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), this->data_align, BufferAlign, offset, static_cast(buffer), size); + } + + virtual Result Flush() override { + return this->base_storage->Flush(); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { this->is_base_storage_size_dirty = true; }; + return this->base_storage->SetSize(util::AlignUp(size, this->data_align)); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (this->is_base_storage_size_dirty) { + s64 size; + R_TRY(this->base_storage->GetSize(std::addressof(size))); + + this->base_storage_size = size; + this->is_base_storage_size_dirty = false; + } + + *out = this->base_storage_size; + return ResultSuccess(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange()); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, this->data_align); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, this->data_align); + const auto aligned_size = aligned_offset_end - aligned_offset; + + return this->base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size); + } + }; + + template + class AlignmentMatchingStorageInBulkRead : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AlignmentMatchingStorageInBulkRead); + NON_MOVEABLE(AlignmentMatchingStorageInBulkRead); + public: + static constexpr size_t BufferAlign = _BufferAlign; + + static_assert(util::IsPowerOfTwo(BufferAlign)); + private: + std::shared_ptr shared_base_storage; + fs::IStorage * const base_storage; + s64 base_storage_size; + size_t data_align; + public: + explicit AlignmentMatchingStorageInBulkRead(fs::IStorage *bs, size_t da) : shared_base_storage(), base_storage(bs), base_storage_size(-1), data_align(da) { + AMS_ASSERT(util::IsPowerOfTwo(this->data_align)); + } + + explicit AlignmentMatchingStorageInBulkRead(std::shared_ptr bs, size_t da) : shared_base_storage(bs), base_storage(shared_base_storage.get()), base_storage_size(-1), data_align(da) { + AMS_ASSERT(util::IsPowerOfTwo(da)); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + /* Allocate a pooled buffer. */ + PooledBuffer pooled_buffer(this->data_align, this->data_align); + return AlignmentMatchingStorageImpl::Write(this->base_storage, pooled_buffer.GetBuffer(), pooled_buffer.GetSize(), this->data_align, BufferAlign, offset, static_cast(buffer), size); + } + + virtual Result Flush() override { + return this->base_storage->Flush(); + } + + virtual Result SetSize(s64 size) override { + ON_SCOPE_EXIT { this->base_storage_size = -1; }; + return this->base_storage->SetSize(util::AlignUp(size, this->data_align)); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + + if (this->base_storage_size < 0) { + s64 size; + R_TRY(this->base_storage->GetSize(std::addressof(size))); + + this->base_storage_size = size; + } + + *out = this->base_storage_size; + return ResultSuccess(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Get the base storage size. */ + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultOutOfRange()); + + /* Operate on the base storage. */ + const auto valid_size = std::min(size, bs_size - offset); + const auto aligned_offset = util::AlignDown(offset, this->data_align); + const auto aligned_offset_end = util::AlignUp(offset + valid_size, this->data_align); + const auto aligned_size = aligned_offset_end - aligned_offset; + + return this->base_storage->OperateRange(dst, dst_size, op_id, aligned_offset, aligned_size, src, src_size); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp new file mode 100644 index 000000000..4cb698134 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_alignment_matching_storage_impl.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + class AlignmentMatchingStorageImpl { + public: + static Result Read(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size); + static Result Write(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp new file mode 100644 index 000000000..0ea327e6a --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_allocator_utility.hpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + using AllocateFunction = void *(*)(size_t size); + using DeallocateFunction = void (*)(void *ptr, size_t size); + + void InitializeAllocator(AllocateFunction allocate_func, DeallocateFunction deallocate_func); + + void *Allocate(size_t size); + void Deallocate(void *ptr, size_t size); + + template + class StdAllocator : public std::allocator { + public: + StdAllocator() { /* ... */ } + StdAllocator(const StdAllocator &) { /* ... */ } + template + StdAllocator(const StdAllocator &) { /* ... */ } + + template + struct rebind { + using other = StdAllocator; + }; + + T *Allocate(size_t size, const T *hint = nullptr) { + return static_cast(::ams::fssystem::Allocate(sizeof(T) * size)); + } + + void Deallocate(T *ptr, size_t size) { + return ::ams::fssystem::Deallocate(ptr, sizeof(T) * size); + } + + ALWAYS_INLINE T *allocate(size_t size, const T *hint = nullptr) { return this->Allocate(size, hint); } + ALWAYS_INLINE void deallocate(T *ptr, size_t size) { return this->Deallocate(ptr, size); } + }; + + template + std::shared_ptr AllocateShared() { + return std::allocate_shared(StdAllocator{}); + } + + template + std::shared_ptr AllocateShared(Args &&... args) { + return std::allocate_shared(StdAllocator{}, std::forward(args)...); + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp new file mode 100644 index 000000000..d01168e0b --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree.hpp @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + class BucketTree { + NON_COPYABLE(BucketTree); + NON_MOVEABLE(BucketTree); + public: + static constexpr u32 Magic = util::FourCC<'B','K','T','R'>::Code; + static constexpr u32 Version = 1; + + static constexpr size_t NodeSizeMin = 1_KB; + static constexpr size_t NodeSizeMax = 512_KB; + public: + class Visitor; + + struct Header { + u32 magic; + u32 version; + s32 entry_count; + s32 reserved; + + void Format(s32 entry_count); + Result Verify() const; + }; + static_assert(std::is_pod
::value); + static_assert(sizeof(Header) == 0x10); + + struct NodeHeader { + s32 index; + s32 count; + s64 offset; + + Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; + }; + static_assert(std::is_pod::value); + static_assert(sizeof(NodeHeader) == 0x10); + + class ContinuousReadingInfo { + private: + size_t read_size; + s32 skip_count; + bool done; + public: + constexpr ContinuousReadingInfo() : read_size(), skip_count(), done() { /* ... */ } + + constexpr void Reset() { this->read_size = 0; this->skip_count = 0; this->done = false; } + + constexpr void SetSkipCount(s32 count) { AMS_ASSERT(count >= 0); this->skip_count = count; } + constexpr s32 GetSkipCount() const { return this->skip_count; } + constexpr bool CheckNeedScan() { return (--this->skip_count) <= 0; } + + constexpr void Done() { this->read_size = 0; this->done = true; } + constexpr bool IsDone() const { return this->done; } + + constexpr void SetReadSize(size_t size) { this->read_size = size; } + constexpr size_t GetReadSize() const { return this->read_size; } + constexpr bool CanDo() const { return this->read_size > 0; } + }; + + using IAllocator = MemoryResource; + private: + class NodeBuffer { + NON_COPYABLE(NodeBuffer); + private: + IAllocator *allocator; + void *header; + public: + NodeBuffer() : allocator(), header() { /* ... */ } + + ~NodeBuffer() { + AMS_ASSERT(this->header == nullptr); + } + + NodeBuffer(NodeBuffer &&rhs) : allocator(rhs.allocator), header(rhs.allocator) { + rhs.allocator = nullptr; + rhs.header = nullptr; + } + + NodeBuffer &operator=(NodeBuffer &&rhs) { + if (this != std::addressof(rhs)) { + AMS_ASSERT(this->header == nullptr); + + this->allocator = rhs.allocator; + this->header = rhs.header; + + rhs.allocator = nullptr; + rhs.header = nullptr; + } + return *this; + } + + bool Allocate(IAllocator *allocator, size_t node_size) { + AMS_ASSERT(this->header == nullptr); + + this->allocator = allocator; + this->header = allocator->Allocate(node_size, sizeof(s64)); + + AMS_ASSERT(util::IsAligned(this->header, sizeof(s64))); + + return this->header != nullptr; + } + + void Free(size_t node_size) { + if (this->header) { + this->allocator->Deallocate(this->header, node_size); + this->header = nullptr; + } + this->allocator = nullptr; + } + + void FillSzero(size_t node_size) const { + if (this->header) { + std::memset(this->header, 0, node_size); + } + } + + NodeHeader *Get() const { + return reinterpret_cast(this->header); + } + + NodeHeader *operator->() const { return this->Get(); } + + template + T *Get() const { + static_assert(std::is_pod::value); + static_assert(sizeof(T) == sizeof(NodeHeader)); + return reinterpret_cast(this->header); + } + + IAllocator *GetAllocator() const { + return this->allocator; + } + }; + private: + static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { + return static_cast((node_size - sizeof(NodeHeader)) / entry_size); + } + + static constexpr s32 GetOffsetCount(size_t node_size) { + return static_cast((node_size - sizeof(NodeHeader)) / sizeof(s64)); + } + + static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); + return util::DivideUp(entry_count, entry_count_per_node); + } + + static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 offset_count_per_node = GetOffsetCount(node_size); + const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + + if (entry_set_count <= offset_count_per_node) { + return 0; + } + + const s32 node_l2_count = util::DivideUp(entry_set_count, offset_count_per_node); + AMS_ABORT_UNLESS(node_l2_count <= offset_count_per_node); + + return util::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), offset_count_per_node); + } + public: + static constexpr s64 QueryHeaderStorageSize() { return sizeof(Header); } + + static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, s32 entry_count) { + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * static_cast(node_size); + } + + static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, s32 entry_count) { + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast(node_size); + } + private: + mutable fs::SubStorage node_storage; + mutable fs::SubStorage entry_storage; + NodeBuffer node_l1; + size_t node_size; + size_t entry_size; + s32 entry_count; + s32 offset_count; + s32 entry_set_count; + s64 start_offset; + s64 end_offset; + public: + BucketTree() : node_storage(), entry_storage(), node_l1(), node_size(), entry_size(), entry_count(), offset_count(), entry_set_count(), start_offset(), end_offset() { /* ... */ } + ~BucketTree() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, size_t node_size, size_t entry_size, s32 entry_count); + void Initialize(size_t node_size, s64 end_offset); + void Finalize(); + + bool IsInitialized() const { return this->node_size > 0; } + bool IsEmpty() const { return this->entry_size == 0; } + + Result Find(Visitor *visitor, s64 virtual_address) const; + Result InvalidateCache(); + + s32 GetEntryCount() const { return this->entry_count; } + IAllocator *GetAllocator() const { return this->node_l1.GetAllocator(); } + + s64 GetStart() const { return this->start_offset; } + s64 GetEnd() const { return this->end_offset; } + s64 GetSize() const { return this->end_offset - this->start_offset; } + + bool Includes(s64 offset) const { + return this->start_offset <= offset && offset < this->end_offset; + } + + bool Includes(s64 offset, s64 size) const { + return size > 0 && this->start_offset <= offset && size <= this->end_offset - offset; + } + private: + template + struct ContinuousReadingParam { + s64 offset; + size_t size; + NodeHeader entry_set; + s32 entry_index; + EntryType entry; + }; + private: + template + Result ScanContinuousReading(ContinuousReadingInfo *out_info, const ContinuousReadingParam ¶m) const; + + bool IsExistL2() const { return this->offset_count < this->entry_set_count; } + bool IsExistOffsetL2OnL1() const { return this->IsExistL2() && this->node_l1->count < this->offset_count; } + + s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { + return (this->offset_count - this->node_l1->count) + (this->offset_count * node_index) + offset_index; + } + }; + + class BucketTree::Visitor { + NON_COPYABLE(Visitor); + NON_MOVEABLE(Visitor); + private: + friend class BucketTree; + + union EntrySetHeader { + NodeHeader header; + struct Info { + s32 index; + s32 count; + s64 end; + s64 start; + } info; + static_assert(std::is_pod::value); + }; + static_assert(std::is_pod::value); + private: + const BucketTree *tree; + void *entry; + s32 entry_index; + s32 entry_set_count; + EntrySetHeader entry_set; + public: + constexpr Visitor() : tree(), entry(), entry_index(-1), entry_set_count(), entry_set{} { /* ... */ } + ~Visitor() { + if (this->entry != nullptr) { + this->tree->GetAllocator()->Deallocate(this->entry, this->tree->entry_size); + this->tree = nullptr; + this->entry = nullptr; + } + } + + bool IsValid() const { return this->entry_index >= 0; } + bool CanMoveNext() const { return this->IsValid() && (this->entry_index + 1 < this->entry_set.info.count || this->entry_set.info.index + 1 < this->entry_set.info.count); } + bool CanMovePrevious() const { return this->IsValid() && (this->entry_index > 0 || this->entry_set.info.index > 0); } + + Result MoveNext(); + Result MovePrevious(); + + template + Result ScanContinuousReading(ContinuousReadingInfo *out_info, s64 offset, size_t size) const; + + const void *Get() const { AMS_ASSERT(this->IsValid()); return this->entry; } + + template + const T *Get() const { AMS_ASSERT(this->IsValid()); return reinterpret_cast(this->entry); } + + const BucketTree *GetTree() const { return this->tree; } + private: + Result Initialize(const BucketTree *tree); + + Result Find(s64 virtual_address); + + Result FindEntrySet(s32 *out_index, s64 virtual_address, s32 node_index); + Result FindEntrySetWithBuffer(s32 *out_index, s64 virtual_address, s32 node_index, char *buffer); + Result FindEntrySetWithoutBuffer(s32 *out_index, s64 virtual_address, s32 node_index); + + Result FindEntry(s64 virtual_address, s32 entry_set_index); + Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char *buffer); + Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp new file mode 100644 index 000000000..1fcb6d903 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_template_impl.hpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::fssystem { + + template + Result BucketTree::ScanContinuousReading(ContinuousReadingInfo *out_info, const ContinuousReadingParam ¶m) const { + static_assert(std::is_pod>::value); + + /* Validate our preconditions. */ + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(out_info != nullptr); + AMS_ASSERT(this->entry_size == sizeof(EntryType)); + + /* Reset the output. */ + out_info->Reset(); + + /* If there's nothing to read, we're done. */ + R_SUCCEED_IF(param.size == 0); + + /* If we're reading a fragment, we're done. */ + R_SUCCEED_IF(param.entry.IsFragment()); + + /* Validate the first entry. */ + auto entry = param.entry; + auto cur_offset = param.offset; + R_UNLESS(entry.GetVirtualOffset() <= cur_offset, fs::ResultOutOfRange()); + + /* Create a pooled buffer for our scan. */ + PooledBuffer pool(this->node_size, 1); + char *buffer = nullptr; + + /* Read the node. */ + if (this->node_size <= pool.GetSize()) { + buffer = pool.GetBuffer(); + const auto ofs = param.entry_set.index * static_cast(this->node_size); + R_TRY(this->entry_storage.Read(ofs, buffer, this->node_size)); + } + + /* Calculate extents. */ + const auto end_offset = cur_offset + static_cast(param.size); + s64 phys_offset = entry.GetPhysicalOffset(); + + /* Start merge tracking. */ + s64 merge_size = 0; + s64 readable_size = 0; + bool merged = false; + + /* Iterate. */ + auto entry_index = param.entry_index; + for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { + /* If we're past the end, we're done. */ + if (end_offset <= cur_offset) { + break; + } + + /* Validate the entry offset. */ + const auto entry_offset = entry.GetVirtualOffset(); + R_UNLESS(entry_offset <= cur_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Get the next entry. */ + EntryType next_entry = {}; + s64 next_entry_offset; + + if (entry_index + 1 < entry_count) { + if (buffer != nullptr) { + const auto ofs = impl::GetBucketTreeEntryOffset(0, this->entry_size, entry_index + 1); + std::memcpy(std::addressof(next_entry), buffer + ofs, this->entry_size); + } else { + const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, this->node_size, this->entry_size, entry_index + 1); + R_TRY(this->entry_storage.Read(ofs, std::addressof(next_entry), this->entry_size)); + } + + next_entry_offset = next_entry.GetVirtualOffset(); + R_UNLESS(this->Includes(next_entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } else { + next_entry_offset = param.entry_set.offset; + } + + /* Validate the next entry offset. */ + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Determine the much data there is. */ + const auto data_size = next_entry_offset - cur_offset; + AMS_ASSERT(data_size > 0); + + /* Determine how much data we should read. */ + const auto remaining_size = end_offset - cur_offset; + const size_t read_size = static_cast(std::min(data_size, remaining_size)); + AMS_ASSERT(read_size <= param.size); + + /* Update our merge tracking. */ + if (entry.IsFragment()) { + /* If we can't merge, stop looping. */ + if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { + break; + } + + /* Otherwise, add the current size to the merge size. */ + merge_size += read_size; + } else { + /* If we can't merge, stop looping. */ + if (phys_offset != entry.GetPhysicalOffset()) { + break; + } + + /* Add the size to the readable amount. */ + readable_size += merge_size + read_size; + AMS_ASSERT(readable_size <= static_cast(param.size)); + + /* Update whether we've merged. */ + merged |= merge_size > 0; + merge_size = 0; + } + + /* Advance. */ + cur_offset += read_size; + AMS_ASSERT(cur_offset <= end_offset); + + phys_offset += next_entry_offset - entry_offset; + entry = next_entry; + } + + /* If we merged, set our readable size. */ + if (merged) { + out_info->SetReadSize(static_cast(readable_size)); + } + out_info->SetSkipCount(entry_index - param.entry_index); + + return ResultSuccess(); + } + + template + Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo *out_info, s64 offset, size_t size) const { + static_assert(std::is_pod::value); + AMS_ASSERT(this->IsValid()); + + /* Create our parameters. */ + ContinuousReadingParam param = { + offset, size, this->entry_set.header, this->entry_index + }; + std::memcpy(std::addressof(param.entry), this->entry, sizeof(EntryType)); + + /* Scan. */ + return this->tree->ScanContinuousReading(out_info, param); + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp new file mode 100644 index 000000000..576dc7d5a --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_bucket_tree_utils.hpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem::impl { + + class SafeValue { + public: + static ALWAYS_INLINE s64 GetInt64(const void *ptr) { + s64 value; + std::memcpy(std::addressof(value), ptr, sizeof(s64)); + return value; + } + + static ALWAYS_INLINE s64 GetInt64(const s64 *ptr) { + return GetInt64(static_cast(ptr)); + } + + static ALWAYS_INLINE s64 GetInt64(const s64 &v) { + return GetInt64(std::addressof(v)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const void *src) { + std::memcpy(dst, src, sizeof(s64)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const s64 *src) { + return SetInt64(dst, static_cast(src)); + } + + static ALWAYS_INLINE void SetInt64(void *dst, const s64 &v) { + return SetInt64(dst, std::addressof(v)); + } + }; + + template + struct BucketTreeNode { + using Header = BucketTree::NodeHeader; + + Header header; + + s32 GetCount() const { return this->header.count; } + + void *GetArray() { return std::addressof(this->header) + 1; } + template T *GetArray() { return reinterpret_cast(this->GetArray()); } + const void *GetArray() const { return std::addressof(this->header) + 1; } + template const T *GetArray() const { return reinterpret_cast(this->GetArray()); } + + s64 GetBeginOffset() const { return *this->GetArray(); } + s64 GetEndOffset() const { return this->header.offset; } + + IteratorType GetBegin() { return IteratorType(this->GetArray()); } + IteratorType GetEnd() { return IteratorType(this->GetArray()) + this->header.count; } + IteratorType GetBegin() const { return IteratorType(this->GetArray()); } + IteratorType GetEnd() const { return IteratorType(this->GetArray()) + this->header.count; } + + IteratorType GetBegin(size_t entry_size) { return IteratorType(this->GetArray(), entry_size); } + IteratorType GetEnd(size_t entry_size) { return IteratorType(this->GetArray(), entry_size) + this->header.count; } + IteratorType GetBegin(size_t entry_size) const { return IteratorType(this->GetArray(), entry_size); } + IteratorType GetEnd(size_t entry_size) const { return IteratorType(this->GetArray(), entry_size) + this->header.count; } + }; + + constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, s32 entry_index) { + return entry_set_offset + sizeof(BucketTree::NodeHeader) + entry_index * static_cast(entry_size); + } + + constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, size_t entry_size, s32 entry_index) { + return GetBucketTreeEntryOffset(entry_set_index * static_cast(node_size), entry_size, entry_index); + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp new file mode 100644 index 000000000..2b047ffc6 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_crypto_configuration.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod); + + void SetUpKekAccessKeys(bool prod); + + void InvalidateHardwareAesKey(); + + const u8 *GetAcidSignatureKeyModulus(bool prod, size_t key_generation); + const u8 *GetAcidSignatureKeyPublicExponent(); + + constexpr inline size_t AcidSignatureKeyModulusSize = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + constexpr inline size_t AcidSignatureKeyPublicExponentSize = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp new file mode 100644 index 000000000..ab4c55615 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_file_system_proxy_api.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssrv::fscreator { + + struct FileSystemCreatorInterfaces; + +} + +namespace ams::fssystem { + + /* TODO: This is kind of really a fs process function/tied into fs main. */ + /* This should be re-examined when FS is reimplemented. */ + void InitializeForFileSystemProxy(); + + const ::ams::fssrv::fscreator::FileSystemCreatorInterfaces *GetFileSystemCreatorInterfaces(); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp new file mode 100644 index 000000000..0f35e1640 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage.hpp @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + class IndirectStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(IndirectStorage); + NON_MOVEABLE(IndirectStorage); + public: + static constexpr s32 StorageCount = 2; + static constexpr size_t NodeSize = 16_KB; + + using IAllocator = MemoryResource; + + struct Entry { + u8 virt_offset[sizeof(s64)]; + u8 phys_offset[sizeof(s64)]; + s32 storage_index; + + void SetVirtualOffset(const s64 &ofs) { + std::memcpy(this->virt_offset, std::addressof(ofs), sizeof(s64)); + } + + s64 GetVirtualOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->virt_offset, sizeof(s64)); + return offset; + } + + void SetPhysicalOffset(const s64 &ofs) { + std::memcpy(this->phys_offset, std::addressof(ofs), sizeof(s64)); + } + + s64 GetPhysicalOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->phys_offset, sizeof(s64)); + return offset; + } + }; + static_assert(std::is_pod::value); + static_assert(sizeof(Entry) == 0x14); + + struct EntryData { + s64 virt_offset; + s64 phys_offset; + s32 storage_index; + + void Set(const Entry &entry) { + this->virt_offset = entry.GetVirtualOffset(); + this->phys_offset = entry.GetPhysicalOffset(); + this->storage_index = entry.storage_index; + } + }; + static_assert(std::is_pod::value); + private: + struct ContinuousReadingEntry { + static constexpr size_t FragmentSizeMax = 4_KB; + + IndirectStorage::Entry entry; + + s64 GetVirtualOffset() const { + return this->entry.GetVirtualOffset(); + } + + s64 GetPhysicalOffset() const { + return this->entry.GetPhysicalOffset(); + } + + bool IsFragment() const { + return this->entry.storage_index != 0; + } + }; + static_assert(std::is_pod::value); + public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + private: + BucketTree table; + fs::SubStorage data_storage[StorageCount]; + public: + IndirectStorage() : table(), data_storage() { /* ... */ } + virtual ~IndirectStorage() { this->Finalize(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage table_storage); + void Finalize(); + + bool IsInitialized() const { return this->table.IsInitialized(); } + + Result Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count) { + return this->table.Initialize(allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count); + } + + void SetStorage(s32 idx, fs::SubStorage storage) { + AMS_ASSERT(0 <= idx && idx < StorageCount); + this->data_storage[idx] = storage; + } + + template + void SetStorage(s32 idx, T storage, s64 offset, s64 size) { + AMS_ASSERT(0 <= idx && idx < StorageCount); + this->data_storage[idx] = fs::SubStorage(storage, offset, size); + } + + Result GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + *out = this->table.GetSize(); + return ResultSuccess(); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperationInIndirectStorageA(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInIndirectStorageB(); + } + protected: + BucketTree &GetEntryTable() { return this->table; } + + fs::SubStorage &GetDataStorage(s32 index) { + AMS_ASSERT(0 <= index && index < StorageCount); + return this->data_storage[index]; + } + + template + Result OperatePerEntry(s64 offset, s64 size, F func); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp new file mode 100644 index 000000000..01ddcc8b5 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_indirect_storage_template_impl.hpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + template + Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to operate on. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(this->table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && this->table.Includes(entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } + + /* Prepare to operate in chunks. */ + auto cur_offset = offset; + const auto end_offset = offset + static_cast(size); + BucketTree::ContinuousReadingInfo cr_info; + + while (cur_offset < end_offset) { + /* Get the current entry. */ + const auto cur_entry = *visitor.Get(); + + /* Get and validate the entry's offset. */ + const auto cur_entry_offset = cur_entry.GetVirtualOffset(); + R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Validate the storage index. */ + R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, fs::ResultInvalidIndirectEntryStorageIndex()); + + /* If we need to check the continuous info, do so. */ + if constexpr (ContinuousCheck) { + /* Scan, if we need to. */ + if (cr_info.CheckNeedScan()) { + R_TRY(visitor.ScanContinuousReading(std::addressof(cr_info), cur_offset, static_cast(end_offset - cur_offset))); + } + + /* Process a base storage entry. */ + if (cr_info.CanDo()) { + R_UNLESS(cur_entry.storage_index == 0, fs::ResultInvalidIndirectEntryStorageIndex()); + + const auto data_offset = cur_offset - cur_entry_offset; + R_TRY(func(std::addressof(this->data_storage[0]), cur_entry.GetPhysicalOffset() + data_offset, cur_offset, static_cast(cr_info.GetReadSize()))); + } + } + + /* Get and validate the next entry offset. */ + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get()->GetVirtualOffset(); + R_UNLESS(this->table.Includes(next_entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } else { + next_entry_offset = this->table.GetEnd(); + } + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidIndirectEntryOffset()); + + /* Get the offset of the entry in the data we read. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; + AMS_ASSERT(data_size > 0); + + /* Determine how much is left. */ + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = std::min(remaining_size, data_size); + AMS_ASSERT(cur_size <= size); + + /* Operate, if we need to. */ + bool needs_operate; + if constexpr (!ContinuousCheck) { + needs_operate = true; + } else { + needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; + } + + if (needs_operate) { + R_TRY(func(std::addressof(this->data_storage[cur_entry.storage_index]), cur_entry.GetPhysicalOffset() + data_offset, cur_offset, cur_size)); + } + + cur_offset += cur_size; + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp new file mode 100644 index 000000000..fdc9d15b9 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_integrity_romfs_storage.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace ams::fssystem { + + constexpr inline size_t IntegrityLayerCountRomFs = 7; + constexpr inline size_t IntegrityHashLayerBlockSize = 16_KB; + + class IntegrityRomFsStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + private: + save::HierarchicalIntegrityVerificationStorage integrity_storage; + save::FileSystemBufferManagerSet buffers; + os::Mutex mutex; + Hash master_hash; + std::unique_ptr master_hash_storage; + public: + IntegrityRomFsStorage() : mutex(true) { /* ... */ } + virtual ~IntegrityRomFsStorage() override { this->Finalize(); } + + Result Initialize(save::HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, save::HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, IBufferManager *bm); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + return this->integrity_storage.Read(offset, buffer, size); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return this->integrity_storage.Write(offset, buffer, size); + } + + virtual Result SetSize(s64 size) override { return fs::ResultUnsupportedOperationInIntegrityRomFsStorageA(); } + + virtual Result GetSize(s64 *out) override { + return this->integrity_storage.GetSize(out); + } + + virtual Result Flush() override { + return this->integrity_storage.Flush(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + return this->integrity_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + } + + Result Commit() { + return this->integrity_storage.Commit(); + } + + save::FileSystemBufferManagerSet *GetBuffers() { + return this->integrity_storage.GetBuffers(); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp new file mode 100644 index 000000000..10460c363 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver.hpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::fssystem { + + class AesCtrCounterExtendedStorage; + class IndirectStorage; + class SparseStorage; + + struct NcaCryptoConfiguration; + + using KeyGenerationFunction = void (*)(void *dst_key, size_t dst_key_size, const void *src_key, size_t src_key_size, s32 key_type, const NcaCryptoConfiguration &cfg); + using DecryptAesCtrFunction = void (*)(void *dst, size_t dst_size, s32 key_type, const void *src_key, size_t src_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size); + + struct NcaCryptoConfiguration { + static constexpr size_t Rsa2048KeyModulusSize = crypto::Rsa2048PssSha256Verifier::ModulusSize; + static constexpr size_t Rsa2048KeyPublicExponentSize = crypto::Rsa2048PssSha256Verifier::MaximumExponentSize; + static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; + + static constexpr size_t Aes128KeySize = crypto::AesEncryptor128::KeySize; + + static constexpr size_t Header1SignatureKeyGenerationMax = 1; + + static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; + static constexpr s32 HeaderEncryptionKeyCount = 2; + + static constexpr size_t KeyGenerationMax = 32; + + const u8 *header_1_sign_key_moduli[Header1SignatureKeyGenerationMax + 1]; + u8 header_1_sign_key_public_exponent[Rsa2048KeyPublicExponentSize]; + u8 key_area_encryption_key_source[KeyAreaEncryptionKeyIndexCount][Aes128KeySize]; + u8 header_encryption_key_source[Aes128KeySize]; + u8 header_encrypted_encryption_keys[HeaderEncryptionKeyCount][Aes128KeySize]; + KeyGenerationFunction generate_key; + DecryptAesCtrFunction decrypt_aes_ctr; + DecryptAesCtrFunction decrypt_aes_ctr_external; + bool is_plaintext_header_available; + }; + static_assert(std::is_pod::value); + + constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { + return key_type < 0; + } + + constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { + constexpr s32 InvalidKeyTypeValue = -1; + static_assert(IsInvalidKeyTypeValue(InvalidKeyTypeValue)); + + if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { + return InvalidKeyTypeValue; + } + + return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; + } + + constexpr inline s32 KeyAreaEncryptionKeyCount = NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * NcaCryptoConfiguration::KeyGenerationMax; + + enum class KeyType : s32 { + NcaHeaderKey = KeyAreaEncryptionKeyCount + 0, + NcaExternalKey = KeyAreaEncryptionKeyCount + 1, + SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 2, + SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 3, + }; + + class NcaReader : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaReader); + NON_MOVEABLE(NcaReader); + private: + NcaHeader header; + u8 decryption_keys[NcaHeader::DecryptionKey_Count][NcaCryptoConfiguration::Aes128KeySize]; + std::shared_ptr shared_base_storage; + std::unique_ptr header_storage; + fs::IStorage *body_storage; + u8 external_decryption_key[NcaCryptoConfiguration::Aes128KeySize]; + DecryptAesCtrFunction decrypt_aes_ctr; + DecryptAesCtrFunction decrypt_aes_ctr_external; + bool is_software_aes_prioritized; + NcaHeader::EncryptionType header_encryption_type; + public: + NcaReader(); + ~NcaReader(); + + Result Initialize(fs::IStorage *base_storage, const NcaCryptoConfiguration &crypto_cfg); + Result Initialize(std::shared_ptr base_storage, const NcaCryptoConfiguration &crypto_cfg); + + fs::IStorage *GetBodyStorage(); + u32 GetMagic() const; + NcaHeader::DistributionType GetDistributionType() const; + NcaHeader::ContentType GetContentType() const; + u8 GetKeyGeneration() const; + u8 GetKeyIndex() const; + u64 GetContentSize() const; + u64 GetProgramId() const; + u32 GetContentIndex() const; + u32 GetSdkAddonVersion() const; + void GetRightsId(u8 *dst, size_t dst_size) const; + bool HasFsInfo(s32 index) const; + s32 GetFsCount() const; + const Hash &GetFsHeaderHash(s32 index) const; + void GetFsHeaderHash(Hash *dst, s32 index) const; + void GetFsInfo(NcaHeader::FsInfo *dst, s32 index) const; + u64 GetFsOffset(s32 index) const; + u64 GetFsEndOffset(s32 index) const; + u64 GetFsSize(s32 index) const; + void GetEncryptedKey(void *dst, size_t size) const; + const void *GetDecryptionKey(s32 index) const; + bool HasValidInternalKey() const; + bool HasInternalDecryptionKeyForAesHardwareSpeedEmulation() const; + bool IsSoftwareAesPrioritized() const; + void PrioritizeSoftwareAes(); + bool HasExternalDecryptionKey() const; + const void *GetExternalDecryptionKey() const; + void SetExternalDecryptionKey(const void *src, size_t size); + void GetRawData(void *dst, size_t dst_size) const; + DecryptAesCtrFunction GetExternalDecryptAesCtrFunction() const; + DecryptAesCtrFunction GetExternalDecryptAesCtrFunctionForExternalKey() const; + NcaHeader::EncryptionType GetEncryptionType() const; + Result ReadHeader(NcaFsHeader *dst, s32 index) const; + + Result VerifyHeaderSign2(const void *key, size_t key_size); + }; + + class NcaFsHeaderReader : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaFsHeaderReader); + NON_MOVEABLE(NcaFsHeaderReader); + private: + NcaFsHeader data; + s32 fs_index; + public: + NcaFsHeaderReader() : fs_index(-1) { + std::memset(std::addressof(this->data), 0, sizeof(this->data)); + } + + Result Initialize(const NcaReader &reader, s32 index); + bool IsInitialized() const { return this->fs_index >= 0; } + + NcaFsHeader &GetData() { return this->data; } + const NcaFsHeader &GetData() const { return this->data; } + void GetRawData(void *dst, size_t dst_size) const; + + NcaFsHeader::HashData &GetHashData(); + const NcaFsHeader::HashData &GetHashData() const; + u16 GetVersion() const; + s32 GetFsIndex() const; + NcaFsHeader::FsType GetFsType() const; + NcaFsHeader::HashType GetHashType() const; + NcaFsHeader::EncryptionType GetEncryptionType() const; + NcaPatchInfo &GetPatchInfo(); + const NcaPatchInfo &GetPatchInfo() const; + const NcaAesCtrUpperIv GetAesCtrUpperIv() const; + bool ExistsSparseLayer() const; + NcaSparseInfo &GetSparseInfo(); + const NcaSparseInfo &GetSparseInfo() const; + }; + + class NcaFileSystemDriver : public ::ams::fs::impl::Newable { + NON_COPYABLE(NcaFileSystemDriver); + NON_MOVEABLE(NcaFileSystemDriver); + public: + class StorageOption; + class StorageOptionWithHeaderReader; + private: + std::shared_ptr original_reader; + std::shared_ptr reader; + MemoryResource * const allocator; + fssystem::IBufferManager * const buffer_manager; + public: + static Result SetupFsHeaderReader(NcaFsHeaderReader *out, const NcaReader &reader, s32 fs_index); + public: + NcaFileSystemDriver(std::shared_ptr reader, MemoryResource *allocator, IBufferManager *buffer_manager) : original_reader(), reader(reader), allocator(allocator), buffer_manager(buffer_manager) { + AMS_ASSERT(this->reader != nullptr); + } + + NcaFileSystemDriver(std::shared_ptr original_reader, std::shared_ptr reader, MemoryResource *allocator, IBufferManager *buffer_manager) : original_reader(original_reader), reader(reader), allocator(allocator), buffer_manager(buffer_manager) { + AMS_ASSERT(this->reader != nullptr); + } + + Result OpenRawStorage(std::shared_ptr *out, s32 fs_index); + + Result OpenStorage(std::shared_ptr *out, NcaFsHeaderReader *out_header_reader, s32 fs_index); + Result OpenStorage(std::shared_ptr *out, StorageOption *option); + + Result OpenStorage(std::shared_ptr *out, s32 fs_index) { + NcaFsHeaderReader dummy; + return this->OpenStorage(out, std::addressof(dummy), fs_index); + } + + Result OpenDecryptableStorage(std::shared_ptr *out, StorageOption *option, bool indirect_needed); + + private: + class BaseStorage; + + Result CreateBaseStorage(BaseStorage *out, StorageOption *option); + + Result CreateDecryptableStorage(std::unique_ptr *out, StorageOption *option, BaseStorage *base_storage); + Result CreateAesXtsStorage(std::unique_ptr *out, BaseStorage *base_storage); + Result CreateAesCtrStorage(std::unique_ptr *out, BaseStorage *base_storage); + Result CreateAesCtrExStorage(std::unique_ptr *out, StorageOption *option, BaseStorage *base_storage); + + Result CreateIndirectStorage(std::unique_ptr *out, StorageOption *option, std::unique_ptr base_storage); + + Result CreateVerificationStorage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader); + Result CreateSha256Storage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader); + Result CreateIntegrityVerificationStorage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver_impl.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver_impl.hpp new file mode 100644 index 000000000..5e96ea97d --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_file_system_driver_impl.hpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include + +namespace ams::fssystem { + + class NcaFileSystemDriver::StorageOption { + private: + friend class NcaFileSystemDriver; + private: + const s32 fs_index; + NcaFsHeaderReader * const header_reader; + fs::IStorage *data_storage; + s64 data_storage_size; + fs::IStorage *aes_ctr_ex_table_storage; + AesCtrCounterExtendedStorage *aes_ctr_ex_storage_raw; + fs::IStorage *aes_ctr_ex_storage; + IndirectStorage *indirect_storage; + SparseStorage *sparse_storage; + public: + explicit StorageOption(NcaFsHeaderReader *reader) : fs_index(reader->GetFsIndex()), header_reader(reader), data_storage(), data_storage_size(), aes_ctr_ex_table_storage(), aes_ctr_ex_storage_raw(), aes_ctr_ex_storage(), indirect_storage(), sparse_storage() { + AMS_ASSERT(this->header_reader != nullptr); + } + + StorageOption(NcaFsHeaderReader *reader, s32 index) : fs_index(index), header_reader(reader), data_storage(), data_storage_size(), aes_ctr_ex_table_storage(), aes_ctr_ex_storage_raw(), aes_ctr_ex_storage(), indirect_storage(), sparse_storage() { + AMS_ASSERT(this->header_reader != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + } + + s32 GetFsIndex() const { return this->fs_index; } + NcaFsHeaderReader &GetHeaderReader() { return *this->header_reader; } + const NcaFsHeaderReader &GetHeaderReader() const { return *this->header_reader; } + fs::SubStorage GetDataStorage() const { return fs::SubStorage(this->data_storage, 0, this->data_storage_size); } + fs::IStorage *GetAesCtrExTableStorage() const { return this->aes_ctr_ex_table_storage; } + fs::IStorage *GetAesCtrExStorage() const { return this->aes_ctr_ex_storage; } + AesCtrCounterExtendedStorage *GetAesCtrExStorageRaw() const { return this->aes_ctr_ex_storage_raw; } + IndirectStorage *GetIndirectStorage() const { return this->indirect_storage; } + SparseStorage *GetSparseStorage() const { return this->sparse_storage; } + private: + void SetDataStorage(fs::IStorage *storage, s64 size) { + AMS_ASSERT(storage != nullptr); + AMS_ASSERT(size >= 0); + this->data_storage = storage; + this->data_storage_size = size; + } + + void SetAesCtrExTableStorage(fs::IStorage *storage) { AMS_ASSERT(storage != nullptr); this->aes_ctr_ex_table_storage = storage; } + void SetAesCtrExStorage(fs::IStorage *storage) { AMS_ASSERT(storage != nullptr); this->aes_ctr_ex_storage = storage; } + void SetAesCtrExStorageRaw(AesCtrCounterExtendedStorage *storage) { AMS_ASSERT(storage != nullptr); this->aes_ctr_ex_storage_raw = storage; } + void SetIndirectStorage(IndirectStorage *storage) { AMS_ASSERT(storage != nullptr); this->indirect_storage = storage; } + void SetSparseStorage(SparseStorage *storage) { AMS_ASSERT(storage != nullptr); this->sparse_storage = storage; } + }; + + class NcaFileSystemDriver::StorageOptionWithHeaderReader : public NcaFileSystemDriver::StorageOption { + private: + NcaFsHeaderReader header_reader_data; + public: + explicit StorageOptionWithHeaderReader(s32 index) : StorageOption(std::addressof(header_reader_data), index) { /* ... */ } + }; + + class NcaFileSystemDriver::BaseStorage { + private: + std::unique_ptr storage; + fs::SubStorage sub_storage; + s64 storage_offset; + NcaAesCtrUpperIv aes_ctr_upper_iv; + public: + BaseStorage() : storage(), sub_storage(), storage_offset(0) { + this->aes_ctr_upper_iv.value = 0; + } + + explicit BaseStorage(const fs::SubStorage &ss) : storage(), sub_storage(ss), storage_offset(0) { + this->aes_ctr_upper_iv.value = 0; + } + + template + BaseStorage(T s, s64 offset, s64 size) : storage(), sub_storage(s, offset, size), storage_offset(0) { + this->aes_ctr_upper_iv.value = 0; + } + + void SetStorage(std::unique_ptr &&storage) { + this->storage = std::move(storage); + } + + template + void SetStorage(T storage, s64 offset, s64 size) { + this->sub_storage = fs::SubStorage(storage, offset, size); + } + + std::unique_ptr MakeStorage() { + if (this->storage != nullptr) { + return std::move(this->storage); + } + return std::make_unique(this->sub_storage); + } + + std::unique_ptr GetStorage() { + return std::move(this->storage); + } + + Result GetSubStorage(fs::SubStorage *out, s64 offset, s64 size) { + s64 storage_size = 0; + + if (this->storage != nullptr) { + R_TRY(this->storage->GetSize(std::addressof(storage_size))); + R_UNLESS(offset + size <= storage_size, fs::ResultNcaBaseStorageOutOfRangeA()); + *out = fs::SubStorage(this->storage.get(), offset, size); + } else { + R_TRY(this->sub_storage.GetSize(std::addressof(storage_size))); + R_UNLESS(offset + size <= storage_size, fs::ResultNcaBaseStorageOutOfRangeA()); + *out = fs::SubStorage(std::addressof(this->sub_storage), offset, size); + } + + return ResultSuccess(); + } + + void SetStorageOffset(s64 offset) { + this->storage_offset = offset; + } + + s64 GetStorageOffset() const { + return this->storage_offset; + } + + void SetAesCtrUpperIv(NcaAesCtrUpperIv v) { + this->aes_ctr_upper_iv = v; + } + + const NcaAesCtrUpperIv GetAesCtrUpperIv() const { + return this->aes_ctr_upper_iv; + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp new file mode 100644 index 000000000..e949dc976 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_nca_header.hpp @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + struct Hash { + static constexpr size_t Size = crypto::Sha256Generator::HashSize; + u8 value[Size]; + }; + static_assert(sizeof(Hash) == Hash::Size); + static_assert(std::is_pod::value); + + using NcaDigest = Hash; + + struct NcaHeader { + enum class ContentType : u8 { + Program = 0, + Meta = 1, + Control = 2, + Manual = 3, + Data = 4, + PublicData = 5, + + Start = Program, + End = PublicData, + }; + + enum class DistributionType : u8 { + Download = 0, + GameCard = 1, + + Start = Download, + End = GameCard, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + }; + + enum DecryptionKey { + DecryptionKey_AesXts = 0, + DecryptionKey_AesXts1 = DecryptionKey_AesXts, + DecryptionKey_AesXts2 = 1, + DecryptionKey_AesCtr = 2, + DecryptionKey_AesCtrEx = 3, + DecryptionKey_AesCtrHw = 4, + DecryptionKey_Count, + }; + + struct FsInfo { + u32 start_sector; + u32 end_sector; + u32 hash_sectors; + u32 reserved; + }; + static_assert(sizeof(FsInfo) == 0x10); + static_assert(std::is_pod::value); + + static constexpr u32 Magic0 = util::FourCC<'N','C','A','0'>::Code; + static constexpr u32 Magic1 = util::FourCC<'N','C','A','1'>::Code; + static constexpr u32 Magic2 = util::FourCC<'N','C','A','2'>::Code; + static constexpr u32 Magic3 = util::FourCC<'N','C','A','3'>::Code; + + static constexpr u32 Magic = Magic3; + + static constexpr size_t Size = 1_KB; + static constexpr s32 FsCountMax = 4; + static constexpr size_t HeaderSignCount = 2; + static constexpr size_t HeaderSignSize = 0x100; + static constexpr size_t EncryptedKeyAreaSize = 0x100; + static constexpr size_t SectorSize = 0x200; + static constexpr size_t SectorShift = 9; + static constexpr size_t RightsIdSize = 0x10; + static constexpr size_t XtsBlockSize = 0x200; + static constexpr size_t CtrBlockSize = 0x10; + + static_assert(SectorSize == (1 << SectorShift)); + + /* Data members. */ + u8 header_sign_1[HeaderSignSize]; + u8 header_sign_2[HeaderSignSize]; + u32 magic; + DistributionType distribution_type; + ContentType content_type; + u8 key_generation; + u8 key_index; + u64 content_size; + u64 program_id; + u32 content_index; + u32 sdk_addon_version; + u8 key_generation_2; + u8 header1_signature_key_generation; + u8 reserved_222[2]; + u32 reserved_224[3]; + u8 rights_id[RightsIdSize]; + FsInfo fs_info[FsCountMax]; + Hash fs_header_hash[FsCountMax]; + u8 encrypted_key_area[EncryptedKeyAreaSize]; + + static constexpr u64 SectorToByte(u32 sector) { + return static_cast(sector) << SectorShift; + } + + static constexpr u32 ByteToSector(u64 byte) { + return static_cast(byte >> SectorShift); + } + + u8 GetProperKeyGeneration() const; + }; + static_assert(sizeof(NcaHeader) == NcaHeader::Size); + static_assert(std::is_pod::value); + + struct NcaBucketInfo { + static constexpr size_t HeaderSize = 0x10; + s64 offset; + s64 size; + u8 header[HeaderSize]; + }; + static_assert(std::is_pod::value); + + struct NcaPatchInfo { + static constexpr size_t Size = 0x40; + static constexpr size_t Offset = 0x100; + + s64 indirect_offset; + s64 indirect_size; + u8 indirect_header[NcaBucketInfo::HeaderSize]; + s64 aes_ctr_ex_offset; + s64 aes_ctr_ex_size; + u8 aes_ctr_ex_header[NcaBucketInfo::HeaderSize]; + + bool HasIndirectTable() const; + bool HasAesCtrExTable() const; + }; + static_assert(std::is_pod::value); + + union NcaAesCtrUpperIv { + u64 value; + struct { + u32 generation; + u32 secure_value; + } part; + }; + static_assert(std::is_pod::value); + + struct NcaSparseInfo { + NcaBucketInfo bucket; + s64 physical_offset; + u16 generation; + u8 reserved[6]; + + s64 GetPhysicalSize() const { + return this->bucket.offset + this->bucket.size; + } + + u32 GetGeneration() const { + return static_cast(this->generation) << 16; + } + + const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { + NcaAesCtrUpperIv sparse_upper_iv = upper_iv; + sparse_upper_iv.part.generation = this->GetGeneration(); + return sparse_upper_iv; + } + }; + static_assert(std::is_pod::value); + + struct NcaFsHeader { + static constexpr size_t Size = 0x200; + static constexpr size_t HashDataOffset = 0x8; + + struct Region { + s64 offset; + s64 size; + }; + static_assert(std::is_pod::value); + + enum class FsType : u8 { + RomFs = 0, + PartitionFs = 1, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + AesXts = 2, + AesCtr = 3, + AesCtrEx = 4, + }; + + enum class HashType : u8 { + Auto = 0, + None = 1, + HierarchicalSha256Hash = 2, + HierarchicalIntegrityHash = 3, + }; + + union HashData { + struct HierarchicalSha256Data { + static constexpr size_t HashLayerCountMax = 5; + static const size_t MasterHashOffset; + + Hash fs_data_master_hash; + s32 hash_block_size; + s32 hash_layer_count; + Region hash_layer_region[HashLayerCountMax]; + } hierarchical_sha256_data; + static_assert(std::is_pod::value); + + struct IntegrityMetaInfo { + static const size_t MasterHashOffset; + + u32 magic; + u32 version; + u32 master_hash_size; + + struct LevelHashInfo { + u32 max_layers; + + struct HierarchicalIntegrityVerificationLevelInformation { + static constexpr size_t IntegrityMaxLayerCount = 7; + s64 offset; + s64 size; + s32 block_order; + u8 reserved[4]; + } info[HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1]; + + struct SignatureSalt { + static constexpr size_t Size = 0x20; + u8 value[Size]; + } seed; + } level_hash_info; + + Hash master_hash; + } integrity_meta_info; + static_assert(std::is_pod::value); + + u8 padding[NcaPatchInfo::Offset - HashDataOffset]; + }; + + u16 version; + FsType fs_type; + HashType hash_type; + EncryptionType encryption_type; + u8 reserved[3]; + HashData hash_data; + NcaPatchInfo patch_info; + NcaAesCtrUpperIv aes_ctr_upper_iv; + NcaSparseInfo sparse_info; + u8 pad[0x88]; + }; + static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); + static_assert(std::is_pod::value); + static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); + + inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); + inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp new file mode 100644 index 000000000..276b5f030 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_sparse_storage.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + class SparseStorage : public IndirectStorage { + NON_COPYABLE(SparseStorage); + NON_MOVEABLE(SparseStorage); + private: + class ZeroStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + public: + ZeroStorage() { /* ... */ } + virtual ~ZeroStorage() { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buffer != nullptr || size == 0); + if (size > 0) { + std::memset(buffer, 0, size); + } + return ResultSuccess(); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + return ResultSuccess(); + } + + virtual Result GetSize(s64 *out) override { + AMS_ASSERT(out != nullptr); + *out = std::numeric_limits::max(); + return ResultSuccess(); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperationInZeroStorageA(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInZeroStorageB(); + } + }; + private: + ZeroStorage zero_storage; + public: + SparseStorage() : IndirectStorage(), zero_storage() { /* ... */ } + virtual ~SparseStorage() { /* ... */ } + + using IndirectStorage::Initialize; + + void Initialize(s64 end_offset) { + this->GetEntryTable().Initialize(NodeSize, end_offset); + this->SetZeroStorage(); + } + + void SetDataStorage(fs::SubStorage storage) { + AMS_ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage); + this->SetZeroStorage(); + } + + template + void SetDataStorage(T storage, s64 offset, s64 size) { + AMS_ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage, offset, size); + this->SetZeroStorage(); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + private: + void SetZeroStorage() { + return this->SetStorage(1, std::addressof(this->zero_storage), 0, std::numeric_limits::max()); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp new file mode 100644 index 000000000..3b6a84704 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/fssystem_speed_emulation_configuration.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include + +namespace ams::fssystem { + + class SpeedEmulationConfiguration { + public: + static void SetSpeedEmulationMode(::ams::fs::SpeedEmulationMode mode); + static ::ams::fs::SpeedEmulationMode GetSpeedEmulationMode(); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_block_cache_buffered_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_block_cache_buffered_storage.hpp new file mode 100644 index 000000000..e708fb8a9 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_block_cache_buffered_storage.hpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace ams::fssystem::save { + + constexpr inline size_t IntegrityMinLayerCount = 2; + constexpr inline size_t IntegrityMaxLayerCount = 7; + constexpr inline size_t IntegrityLayerCountSave = 5; + constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; + + struct FileSystemBufferManagerSet { + IBufferManager *buffers[IntegrityMaxLayerCount]; + }; + static_assert(std::is_pod::value); + + class BlockCacheBufferedStorage : public ::ams::fs::IStorage { + NON_COPYABLE(BlockCacheBufferedStorage); + NON_MOVEABLE(BlockCacheBufferedStorage); + public: + static constexpr size_t DefaultMaxCacheEntryCount = 24; + private: + using MemoryRange = std::pair; + using CacheIndex = s32; + + struct CacheEntry { + size_t size; + bool is_valid; + bool is_write_back; + bool is_cached; + bool is_flushing; + s64 offset; + IBufferManager::CacheHandle handle; + uintptr_t memory_address; + size_t memory_size; + }; + static_assert(std::is_pod::value); + + enum Flag : s32 { + Flag_KeepBurstMode = (1 << 8), + Flag_RealData = (1 << 10), + }; + private: + IBufferManager *buffer_manager; + os::Mutex *mutex; + std::unique_ptr entries; + IStorage *data_storage; + Result last_result; + s64 data_size; + size_t verification_block_size; + size_t verification_block_shift; + CacheIndex invalidate_index; + s32 max_cache_entry_count; + s32 flags; + s32 buffer_level; + fs::StorageType storage_type; + public: + BlockCacheBufferedStorage(); + virtual ~BlockCacheBufferedStorage() override; + + Result Initialize(IBufferManager *bm, os::Mutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64 size) override { return fs::ResultUnsupportedOperationInBlockCacheBufferedStorageA(); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + Result Commit(); + Result OnRollback(); + + bool IsEnabledKeepBurstMode() const { + return (this->flags & Flag_KeepBurstMode) != 0; + } + + bool IsRealDataCache() const { + return (this->flags & Flag_RealData) != 0; + } + + void SetKeepBurstMode(bool en) { + if (en) { + this->flags |= Flag_KeepBurstMode; + } else { + this->flags &= ~Flag_KeepBurstMode; + } + } + + void SetRealDataCache(bool en) { + if (en) { + this->flags |= Flag_RealData; + } else { + this->flags &= ~Flag_RealData; + } + } + private: + s32 GetMaxCacheEntryCount() const { + return this->max_cache_entry_count; + } + + Result ClearImpl(s64 offset, s64 size); + Result ClearSignatureImpl(s64 offset, s64 size); + Result InvalidateCacheImpl(s64 offset, s64 size); + Result QueryRangeImpl(void *dst, size_t dst_size, s64 offset, s64 size); + + bool ExistsRedundantCacheEntry(const CacheEntry &entry) const; + + Result GetAssociateBuffer(MemoryRange *out_range, CacheEntry *out_entry, s64 offset, size_t ideal_size, bool is_allocate_for_write); + + void DestroyBuffer(CacheEntry *entry, const MemoryRange &range); + + Result StoreAssociateBuffer(CacheIndex *out, const MemoryRange &range, const CacheEntry &entry); + Result StoreAssociateBuffer(const MemoryRange &range, const CacheEntry &entry) { + CacheIndex dummy; + return this->StoreAssociateBuffer(std::addressof(dummy), range, entry); + } + + Result StoreOrDestroyBuffer(const MemoryRange &range, CacheEntry *entry) { + AMS_ASSERT(entry != nullptr); + + auto buf_guard = SCOPE_GUARD { this->DestroyBuffer(entry, range); }; + + R_TRY(this->StoreAssociateBuffer(range, *entry)); + + buf_guard.Cancel(); + return ResultSuccess(); + } + + Result FlushCacheEntry(CacheIndex index, bool invalidate); + Result FlushRangeCacheEntries(s64 offset, s64 size, bool invalidate); + void InvalidateRangeCacheEntries(s64 offset, s64 size); + + Result FlushAllCacheEntries(); + Result InvalidateAllCacheEntries(); + Result ControlDirtiness(); + + Result UpdateLastResult(Result result); + + Result ReadHeadCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 *offset, s64 *aligned_offset, s64 aligned_offset_end, char **buffer, size_t *size); + Result ReadTailCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 offset, s64 aligned_offset, s64 *aligned_offset_end, char *buffer, size_t *size); + + Result BulkRead(s64 offset, void *buffer, size_t size, MemoryRange *range_head, MemoryRange *range_tail, CacheEntry *entry_head, CacheEntry *entry_tail, bool head_cache_needed, bool tail_cache_needed); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_buffered_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_buffered_storage.hpp new file mode 100644 index 000000000..eb30a8a7e --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_buffered_storage.hpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::fssystem::save { + + class BufferedStorage : public ::ams::fs::IStorage { + NON_COPYABLE(BufferedStorage); + NON_MOVEABLE(BufferedStorage); + private: + class Cache; + class UniqueCache; + class SharedCache; + private: + fs::SubStorage base_storage; + IBufferManager *buffer_manager; + size_t block_size; + s64 base_storage_size; + std::unique_ptr caches; + s32 cache_count; + Cache *next_acquire_cache; + Cache *next_fetch_cache; + os::Mutex mutex; + bool bulk_read_enabled; + public: + BufferedStorage(); + virtual ~BufferedStorage(); + + Result Initialize(fs::SubStorage base_storage, IBufferManager *buffer_manager, size_t block_size, s32 buffer_count); + void Finalize(); + + bool IsInitialized() const { return this->caches != nullptr; } + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result GetSize(s64 *out) override; + virtual Result SetSize(s64 size) override; + + virtual Result Flush() override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + void InvalidateCaches(); + + IBufferManager *GetBufferManager() const { return this->buffer_manager; } + + void EnableBulkRead() { this->bulk_read_enabled = true; } + private: + Result PrepareAllocation(); + Result ControlDirtiness(); + Result ReadCore(s64 offset, void *buffer, size_t size); + + bool ReadHeadCache(s64 *offset, void *buffer, size_t *size, s64 *buffer_offset); + bool ReadTailCache(s64 offset, void *buffer, size_t *size, s64 buffer_offset); + + Result BulkRead(s64 offset, void *buffer, size_t size, bool head_cache_needed, bool tail_cache_needed); + + Result WriteCore(s64 offset, const void *buffer, size_t size); + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_hierarchical_integrity_verification_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_hierarchical_integrity_verification_storage.hpp new file mode 100644 index 000000000..1140e3641 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_hierarchical_integrity_verification_storage.hpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ams::fssystem::save { + + struct HierarchicalIntegrityVerificationLevelInformation { + fs::Int64 offset; + fs::Int64 size; + s32 block_order; + u8 reserved[4]; + }; + static_assert(std::is_pod::value); + static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); + static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); + + struct HierarchicalIntegrityVerificationInformation { + u32 max_layers; + HierarchicalIntegrityVerificationLevelInformation info[IntegrityMaxLayerCount - 1]; + fs::HashSalt seed; + + s64 GetLayeredHashSize() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataOffset() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataSize() const { + return this->info[this->max_layers - 2].size; + } + }; + static_assert(std::is_pod::value); + + struct HierarchicalIntegrityVerificationMetaInformation { + u32 magic; + u32 version; + u32 master_hash_size; + HierarchicalIntegrityVerificationInformation level_hash_info; + + /* TODO: Format */ + }; + static_assert(std::is_pod::value); + + struct HierarchicalIntegrityVerificationSizeSet { + s64 control_size; + s64 master_hash_size; + s64 layered_hash_sizes[IntegrityMaxLayerCount - 1]; + }; + static_assert(std::is_pod::value); + + class HierarchicalIntegrityVerificationStorageControlArea { + NON_COPYABLE(HierarchicalIntegrityVerificationStorageControlArea); + NON_MOVEABLE(HierarchicalIntegrityVerificationStorageControlArea); + public: + static constexpr size_t HashSize = crypto::Sha256Generator::HashSize; + + struct InputParam { + size_t level_block_size[IntegrityMaxLayerCount - 1]; + }; + static_assert(std::is_pod::value); + private: + fs::SubStorage storage; + HierarchicalIntegrityVerificationMetaInformation meta; + public: + static Result QuerySize(HierarchicalIntegrityVerificationSizeSet *out, const InputParam &input_param, s32 layer_count, s64 data_size); + /* TODO Format */ + static Result Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta); + public: + HierarchicalIntegrityVerificationStorageControlArea() { /* ... */ } + + Result Initialize(fs::SubStorage meta_storage); + void Finalize(); + + u32 GetMasterHashSize() const { return this->meta.master_hash_size; } + void GetLevelHashInfo(HierarchicalIntegrityVerificationInformation *out) { + AMS_ASSERT(out != nullptr); + *out = this->meta.level_hash_info; + } + }; + + class HierarchicalIntegrityVerificationStorage : public ::ams::fs::IStorage { + NON_COPYABLE(HierarchicalIntegrityVerificationStorage); + NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); + private: + friend class HierarchicalIntegrityVerificationMetaInformation; + protected: + static constexpr s64 HashSize = crypto::Sha256Generator::HashSize; + static constexpr size_t MaxLayers = IntegrityMaxLayerCount; + public: + using GenerateRandomFunction = void (*)(void *dst, size_t size); + + class HierarchicalStorageInformation { + public: + enum { + MasterStorage = 0, + Layer1Storage = 1, + Layer2Storage = 2, + Layer3Storage = 3, + Layer4Storage = 4, + Layer5Storage = 5, + DataStorage = 6, + }; + private: + fs::SubStorage storages[DataStorage + 1]; + public: + void SetMasterHashStorage(fs::SubStorage s) { this->storages[MasterStorage] = s; } + void SetLayer1HashStorage(fs::SubStorage s) { this->storages[Layer1Storage] = s; } + void SetLayer2HashStorage(fs::SubStorage s) { this->storages[Layer2Storage] = s; } + void SetLayer3HashStorage(fs::SubStorage s) { this->storages[Layer3Storage] = s; } + void SetLayer4HashStorage(fs::SubStorage s) { this->storages[Layer4Storage] = s; } + void SetLayer5HashStorage(fs::SubStorage s) { this->storages[Layer5Storage] = s; } + void SetDataStorage(fs::SubStorage s) { this->storages[DataStorage] = s; } + + fs::SubStorage &operator[](s32 index) { + AMS_ASSERT(MasterStorage <= index && index <= DataStorage); + return this->storages[index]; + } + }; + private: + static GenerateRandomFunction s_generate_random; + + static void SetGenerateRandomFunction(GenerateRandomFunction func) { + s_generate_random = func; + } + private: + FileSystemBufferManagerSet *buffers; + os::Mutex *mutex; + IntegrityVerificationStorage verify_storages[MaxLayers - 1]; + BlockCacheBufferedStorage buffer_storages[MaxLayers - 1]; + s64 data_size; + s32 max_layers; + bool is_written_for_rollback; + public: + HierarchicalIntegrityVerificationStorage() : buffers(nullptr), mutex(nullptr), data_size(-1), is_written_for_rollback(false) { /* ... */ } + virtual ~HierarchicalIntegrityVerificationStorage() override { this->Finalize(); } + + Result Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, os::Mutex *mtx, fs::StorageType storage_type); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64 size) override { return fs::ResultUnsupportedOperationInHierarchicalIntegrityVerificationStorageA(); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + Result Commit(); + Result OnRollback(); + + bool IsInitialized() const { + return this->data_size >= 0; + } + + bool IsWrittenForRollback() const { + return this->is_written_for_rollback; + } + + FileSystemBufferManagerSet *GetBuffers() { + return this->buffers; + } + + void GetParameters(HierarchicalIntegrityVerificationStorageControlArea::InputParam *out) const { + AMS_ASSERT(out != nullptr); + for (auto level = 0; level <= this->max_layers - 2; ++level) { + out->level_block_size[level] = static_cast(this->verify_storages[level].GetBlockSize()); + } + } + + s64 GetL1HashVerificationBlockSize() const { + return this->verify_storages[this->max_layers - 2].GetBlockSize(); + } + + fs::SubStorage GetL1HashStorage() { + return fs::SubStorage(std::addressof(this->buffer_storages[this->max_layers - 3]), 0, util::DivideUp(this->data_size, this->GetL1HashVerificationBlockSize())); + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.hpp new file mode 100644 index 000000000..69a4f5ec3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem::save { + + /* TODO */ + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.hpp new file mode 100644 index 000000000..69a4f5ec3 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_i_save_file_system_driver.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem::save { + + /* TODO */ + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_integrity_verification_storage.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_integrity_verification_storage.hpp new file mode 100644 index 000000000..13871b9fc --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_integrity_verification_storage.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ams::fssystem::save { + + class IntegrityVerificationStorage : public ::ams::fs::IStorage { + NON_COPYABLE(IntegrityVerificationStorage); + NON_MOVEABLE(IntegrityVerificationStorage); + public: + static constexpr s64 HashSize = crypto::Sha256Generator::HashSize; + + struct BlockHash { + u8 hash[HashSize]; + }; + static_assert(std::is_pod::value); + private: + fs::SubStorage hash_storage; + fs::SubStorage data_storage; + s64 verification_block_size; + s64 verification_block_order; + s64 upper_layer_verification_block_size; + s64 upper_layer_verification_block_order; + IBufferManager *buffer_manager; + fs::HashSalt salt; + bool is_real_data; + fs::StorageType storage_type; + public: + IntegrityVerificationStorage() : verification_block_size(0), verification_block_order(0), upper_layer_verification_block_size(0), upper_layer_verification_block_order(0), buffer_manager(nullptr) { /* ... */ } + virtual ~IntegrityVerificationStorage() override { this->Finalize(); } + + Result Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, IBufferManager *bm, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type); + void Finalize(); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + + virtual Result SetSize(s64 size) override { return fs::ResultUnsupportedOperationInIntegrityVerificationStorageA(); } + virtual Result GetSize(s64 *out) override; + + virtual Result Flush() override; + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + using IStorage::OperateRange; + + void CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size) const; + + s64 GetBlockSize() const { + return this->verification_block_size; + } + private: + Result ReadBlockSignature(void *dst, size_t dst_size, s64 offset, size_t size); + Result WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size); + Result VerifyHash(const void *buf, BlockHash *hash); + + void CalcBlockHash(BlockHash *out, const void *buffer) const { + return this->CalcBlockHash(out, buffer, static_cast(this->verification_block_size)); + } + + Result IsCleared(bool *is_cleared, const BlockHash &hash); + private: + static void SetValidationBit(BlockHash *hash) { + AMS_ASSERT(hash != nullptr); + hash->hash[HashSize - 1] |= 0x80; + } + + static bool IsValidationBit(const BlockHash *hash) { + AMS_ASSERT(hash != nullptr); + return (hash->hash[HashSize - 1] & 0x80) != 0; + } + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_save_types.hpp b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_save_types.hpp new file mode 100644 index 000000000..4657a4e60 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/fssystem/save/fssystem_save_types.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include + +namespace ams::fssystem::save { + + constexpr inline bool IsPowerOfTwo(s32 val) { + return util::IsPowerOfTwo(val); + } + + constexpr inline u32 ILog2(u32 val) { + AMS_ASSERT(val > 0); + return (BITSIZEOF(u32) - 1 - dbm::CountLeadingZeros(val)); + } + + constexpr inline u32 CeilPowerOfTwo(u32 val) { + if (val == 0) { + return 1; + } + return ((1u << (BITSIZEOF(u32) - 1)) >> (dbm::CountLeadingZeros(val - 1) - 1)); + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp b/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp index 30e090e4c..6cc3147d5 100644 --- a/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/hos/hos_stratosphere_api.hpp @@ -20,5 +20,6 @@ namespace ams::hos { void InitializeForStratosphere(); + void InitializeForStratosphereDebug(hos::Version debug_version); } diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp index 3b9639f82..506290b39 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp @@ -15,19 +15,83 @@ */ #pragma once -#include "spl_types.hpp" +#include namespace ams::spl { - HardwareType GetHardwareType(); - MemoryArrangement GetMemoryArrangement(); - bool IsDisabledProgramVerification(); - bool IsDevelopmentHardware(); - bool IsDevelopmentFunctionEnabled(); - bool IsMariko(); - bool IsRecoveryBoot(); + void Initialize(); + void InitializeForCrypto(); + void InitializeForSsl(); + void InitializeForEs(); + void InitializeForFs(); + void InitializeForManu(); - Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, u32 generation, u32 option); + void Finalize(); + + Result AllocateAesKeySlot(s32 *out_slot); + Result DeallocateAesKeySlot(s32 slot); + + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option); + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size); Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size); + Result GenerateSpecificAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option); + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + Result DecryptAesKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 generation, u32 option); + + Result GetConfig(u64 *out, ConfigItem item); + bool IsDevelopment(); + MemoryArrangement GetMemoryArrangement(); + + inline bool GetConfigBool(ConfigItem item) { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), item)); + return v != 0; + } + + inline HardwareType GetHardwareType() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::HardwareType)); + return static_cast(v); + } + + inline HardwareState GetHardwareState() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::HardwareState)); + return static_cast(v); + } + + inline u64 GetDeviceIdLow() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::DeviceId)); + return v; + } + + inline bool IsRecoveryBoot() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::IsRecoveryBoot); + } + + inline bool IsDevelopmentFunctionEnabled() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::IsDevelopmentFunctionEnabled); + } + + inline bool IsDisabledProgramVerification() { + return ::ams::spl::GetConfigBool(::ams::spl::ConfigItem::DisableProgramVerification); + } + + Result SetBootReason(BootReasonValue boot_reason); + Result GetBootReason(BootReasonValue *out); + + inline BootReasonValue GetBootReason() { + BootReasonValue br; + R_ABORT_UNLESS(::ams::spl::GetBootReason(std::addressof(br))); + return br; + } + + SocType GetSocType(); + + Result GetPackage2Hash(void *dst, size_t dst_size); + Result GenerateRandomBytes(void *out, size_t buffer_size); + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key); } diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp index 43dbb54ed..b02bf1fd8 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp @@ -112,6 +112,17 @@ namespace ams::spl { Hoag = 2, Iowa = 3, Calcio = 4, + _Five_ = 5, + }; + + enum SocType { + SocType_Erista = 0, + SocType_Mariko = 1, + }; + + enum HardwareState { + HardwareState_Development = 0, + HardwareState_Production = 1, }; enum MemoryArrangement { @@ -185,23 +196,23 @@ namespace ams::spl { enum class ConfigItem : u32 { /* Standard config items. */ - DisableProgramVerification = 1, - DramId = 2, - SecurityEngineIrqNumber = 3, - Version = 4, - HardwareType = 5, - IsRetail = 6, - IsRecoveryBoot = 7, - DeviceId = 8, - BootReason = 9, - MemoryMode = 10, - IsDebugMode = 11, - KernelConfiguration = 12, - IsChargerHiZModeEnabled = 13, - IsQuest = 14, - RegulatorType = 15, - DeviceUniqueKeyGeneration = 16, - Package2Hash = 17, + DisableProgramVerification = 1, + DramId = 2, + SecurityEngineIrqNumber = 3, + FuseVersion = 4, + HardwareType = 5, + HardwareState = 6, + IsRecoveryBoot = 7, + DeviceId = 8, + BootReason = 9, + MemoryMode = 10, + IsDevelopmentFunctionEnabled = 11, + KernelConfiguration = 12, + IsChargerHiZModeEnabled = 13, + IsQuest = 14, + RegulatorType = 15, + DeviceUniqueKeyGeneration = 16, + Package2Hash = 17, /* Extension config items for exosphere. */ ExosphereApiVersion = 65000, diff --git a/libraries/libstratosphere/source/fs/fs_file_storage.cpp b/libraries/libstratosphere/source/fs/fs_file_storage.cpp index f7ee4c14c..b56153fb0 100644 --- a/libraries/libstratosphere/source/fs/fs_file_storage.cpp +++ b/libraries/libstratosphere/source/fs/fs_file_storage.cpp @@ -90,6 +90,18 @@ namespace ams::fs { } } + Result FileStorageBasedFileSystem::Initialize(std::shared_ptr base_file_system, const char *path, fs::OpenMode mode) { + /* Open the file. */ + std::unique_ptr base_file; + R_TRY(base_file_system->OpenFile(std::addressof(base_file), path, mode)); + + /* Set the file. */ + this->SetFile(std::move(base_file)); + this->base_file_system = std::move(base_file_system); + + return ResultSuccess(); + } + Result FileHandleStorage::UpdateSize() { R_SUCCEED_IF(this->size != InvalidSize); return GetFileSize(std::addressof(this->size), this->handle); diff --git a/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp new file mode 100644 index 000000000..b64447ac8 --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_partition_file_system_creator.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv::fscreator { + + Result PartitionFileSystemCreator::Create(std::shared_ptr *out, std::shared_ptr storage) { + /* Allocate a filesystem. */ + std::shared_ptr fs = fssystem::AllocateShared(); + R_UNLESS(fs != nullptr, fs::ResultAllocationFailureInPartitionFileSystemCreatorA()); + + /* Initialize the filesystem. */ + R_TRY(fs->Initialize(std::move(storage))); + + /* Set the output. */ + *out = std::move(fs); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp new file mode 100644 index 000000000..c17e7d24e --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_rom_file_system_creator.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv::fscreator { + + namespace { + + class RomFileSystemWithBuffer : public ::ams::fssystem::RomFsFileSystem { + private: + void *meta_cache_buffer; + size_t meta_cache_buffer_size; + MemoryResource *allocator; + public: + explicit RomFileSystemWithBuffer(MemoryResource *mr) : meta_cache_buffer(nullptr), allocator(mr) { /* ... */ } + + ~RomFileSystemWithBuffer() { + if (this->meta_cache_buffer != nullptr) { + this->allocator->Deallocate(this->meta_cache_buffer, this->meta_cache_buffer_size); + } + } + + Result Initialize(std::shared_ptr storage) { + /* Check if the buffer is eligible for cache. */ + size_t buffer_size = 0; + if (R_FAILED(RomFsFileSystem::GetRequiredWorkingMemorySize(std::addressof(buffer_size), storage.get())) || buffer_size == 0 || buffer_size >= 128_KB) { + return RomFsFileSystem::Initialize(std::move(storage), nullptr, 0, false); + } + + /* Allocate a buffer. */ + this->meta_cache_buffer = this->allocator->Allocate(buffer_size); + if (this->meta_cache_buffer == nullptr) { + return RomFsFileSystem::Initialize(std::move(storage), nullptr, 0, false); + } + + /* Initialize with cache buffer. */ + this->meta_cache_buffer_size = buffer_size; + return RomFsFileSystem::Initialize(std::move(storage), this->meta_cache_buffer, this->meta_cache_buffer_size, true); + } + }; + + } + + Result RomFileSystemCreator::Create(std::shared_ptr *out, std::shared_ptr storage) { + /* Allocate a filesystem. */ + std::shared_ptr fs = fssystem::AllocateShared(this->allocator); + R_UNLESS(fs != nullptr, fs::ResultAllocationFailureInRomFileSystemCreatorA()); + + /* Initialize the filesystem. */ + R_TRY(fs->Initialize(std::move(storage))); + + /* Set the output. */ + *out = std::move(fs); + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp new file mode 100644 index 000000000..cbaa0dfa8 --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fscreator/fssrv_storage_on_nca_creator.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv::fscreator { + + Result StorageOnNcaCreator::VerifyAcid(fs::fsa::IFileSystem *fs, fssystem::NcaReader *nca_reader) { + /* Open the npdm. */ + constexpr const char MetaFilePath[] = "/main.npdm"; + std::unique_ptr file; + R_TRY(fs->OpenFile(std::addressof(file), MetaFilePath, fs::OpenMode_Read)); + + size_t size; + + /* Read the Acid signature key generation. */ + constexpr s64 AcidSignatureKeyGenerationOffset = offsetof(ldr::Npdm, signature_key_generation); + u32 acid_signature_key_generation; + R_TRY(file->Read(std::addressof(size), AcidSignatureKeyGenerationOffset, std::addressof(acid_signature_key_generation), sizeof(acid_signature_key_generation), fs::ReadOption())); + R_UNLESS(size == sizeof(acid_signature_key_generation), fs::ResultInvalidAcidFileSize()); + + /* Read the Acid offset. */ + constexpr s64 AcidOffsetOffset = offsetof(ldr::Npdm, acid_offset); + s32 acid_offset; + R_TRY(file->Read(std::addressof(size), AcidOffsetOffset, std::addressof(acid_offset), sizeof(acid_offset), fs::ReadOption())); + R_UNLESS(size == sizeof(acid_offset), fs::ResultInvalidAcidFileSize()); + + /* Read the Acid size. */ + constexpr s64 AcidSizeOffset = offsetof(ldr::Npdm, acid_size); + s32 acid_size; + R_TRY(file->Read(std::addressof(size), AcidSizeOffset, std::addressof(acid_size), sizeof(acid_size), fs::ReadOption())); + R_UNLESS(size == sizeof(acid_size), fs::ResultInvalidAcidFileSize()); + + /* Allocate memory for the acid. */ + u8 *acid = static_cast(this->allocator->Allocate(acid_size)); + R_UNLESS(acid != nullptr, fs::ResultAllocationFailureInStorageOnNcaCreatorA()); + ON_SCOPE_EXIT { this->allocator->Deallocate(acid, acid_size); }; + + /* Read the acid. */ + R_TRY(file->Read(std::addressof(size), acid_offset, acid, acid_size, fs::ReadOption())); + R_UNLESS(size == static_cast(acid_size), fs::ResultInvalidAcidSize()); + + /* Define interesting extents. */ + constexpr s32 AcidSignOffset = 0x000; + constexpr s32 AcidSignSize = 0x100; + constexpr s32 HeaderSign2KeyOffset = 0x100; + constexpr s32 HeaderSign2KeySize = 0x100; + constexpr s32 AcidSignTargetOffset = 0x100; + constexpr s32 AcidSignTargetSizeOffset = 0x204; + + /* Read the sign target size. */ + R_UNLESS(acid_size >= static_cast(AcidSignTargetSizeOffset + sizeof(s32)), fs::ResultInvalidAcidSize()); + const s32 acid_sign_target_size = *reinterpret_cast(acid + AcidSignTargetSizeOffset); + + /* Validate the sign target size. */ + R_UNLESS(acid_size >= static_cast(acid_sign_target_size + sizeof(s32)), fs::ResultInvalidAcidSize()); + R_UNLESS(acid_size >= AcidSignTargetOffset + acid_sign_target_size, fs::ResultInvalidAcidSize()); + + /* Verify the signature. */ + { + const u8 *sig = acid + AcidSignOffset; + const size_t sig_size = static_cast(AcidSignSize); + const u8 *mod = fssystem::GetAcidSignatureKeyModulus(this->is_prod, acid_signature_key_generation); + const size_t mod_size = fssystem::AcidSignatureKeyModulusSize; + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; + const u8 *msg = acid + AcidSignTargetOffset; + const size_t msg_size = acid_sign_target_size; + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + if (!is_signature_valid) { + /* If the signature is invalid, then unless program verification is disabled error out. */ + R_UNLESS(!this->is_enabled_program_verification, fs::ResultAcidVerificationFailed()); + + /* If program verification is disabled, then we're fine. */ + return ResultSuccess(); + } + } + + /* If we have an nca reader, verify the header signature using the key from the acid. */ + if (nca_reader) { + /* Verify that the acid contains a key to validate the second signature with. */ + R_UNLESS(acid_size >= HeaderSign2KeyOffset + HeaderSign2KeySize, fs::ResultInvalidAcidSize()); + + /* Validate that this key has its top byte set (and is thus approximately 2048 bits). */ + R_UNLESS(*(acid + HeaderSign2KeyOffset + HeaderSign2KeySize - 1) != 0x00, fs::ResultInvalidAcid()); + + R_TRY(nca_reader->VerifyHeaderSign2(reinterpret_cast(acid) + HeaderSign2KeyOffset, HeaderSign2KeySize)); + } + + return ResultSuccess(); + } + + Result StorageOnNcaCreator::Create(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr nca_reader, s32 index, bool verify_header_sign_2) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(nca_reader, this->allocator, this->buffer_manager); + + /* Open the storage. */ + std::shared_ptr storage; + R_TRY(nca_fs_driver.OpenStorage(std::addressof(storage), out_header_reader, index)); + + /* If we should, verify the header signature. */ + if (verify_header_sign_2) { + R_TRY(this->VerifyNcaHeaderSign2(nca_reader.get(), storage.get())); + } + + /* Set the out storage. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result StorageOnNcaCreator::CreateWithPatch(std::shared_ptr *out, fssystem::NcaFsHeaderReader *out_header_reader, std::shared_ptr original_nca_reader, std::shared_ptr current_nca_reader, s32 index, bool verify_header_sign_2) { + /* Create a fs driver. */ + fssystem::NcaFileSystemDriver nca_fs_driver(original_nca_reader, current_nca_reader, this->allocator, this->buffer_manager); + + /* Open the storage. */ + std::shared_ptr storage; + R_TRY(nca_fs_driver.OpenStorage(std::addressof(storage), out_header_reader, index)); + + /* If we should, verify the header signature. */ + if (verify_header_sign_2) { + R_TRY(this->VerifyNcaHeaderSign2(current_nca_reader.get(), storage.get())); + } + + /* Set the out storage. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result StorageOnNcaCreator::CreateNcaReader(std::shared_ptr *out, std::shared_ptr storage) { + /* Create a reader. */ + std::shared_ptr reader = fssystem::AllocateShared(); + R_UNLESS(reader != nullptr, fs::ResultAllocationFailureInStorageOnNcaCreatorB()); + + /* Initialize the reader. */ + R_TRY(reader->Initialize(std::move(storage), this->nca_crypto_cfg)); + + /* Set the output. */ + *out = std::move(reader); + return ResultSuccess(); + } + + void StorageOnNcaCreator::SetEnabledProgramVerification(bool en) { + if (!this->is_prod) { + this->is_enabled_program_verification = en; + } + } + + Result StorageOnNcaCreator::VerifyNcaHeaderSign2(fssystem::NcaReader *nca_reader, fs::IStorage *storage) { + fssystem::PartitionFileSystem part_fs; + R_TRY(part_fs.Initialize(storage)); + return this->VerifyAcid(std::addressof(part_fs), nca_reader); + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp b/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp new file mode 100644 index 000000000..7861a2717 --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fssrv_file_system_proxy_api.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv { + + void InitializeForFileSystemProxy(fscreator::FileSystemCreatorInterfaces *fs_creator_interfaces, fssystem::IBufferManager *buffer_manager, bool is_development_function_enabled) { + /* TODO FS-REIMPL */ + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp b/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp new file mode 100644 index 000000000..fcb611df9 --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_exp_heap.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv { + + namespace { + + size_t GetUsedSize(void *p) { + const auto block_head = reinterpret_cast(reinterpret_cast(p) - sizeof(lmem::impl::ExpHeapMemoryBlockHead)); + return block_head->block_size + ((block_head->attributes >> 8) & 0x7F) + sizeof(lmem::impl::ExpHeapMemoryBlockHead); + } + + } + + void PeakCheckableMemoryResourceFromExpHeap::OnAllocate(void *p, size_t size) { + if (p != nullptr) { + this->current_free_size = GetUsedSize(p); + this->peak_free_size = std::min(this->peak_free_size, this->current_free_size); + } + } + + void PeakCheckableMemoryResourceFromExpHeap::OnDeallocate(void *p, size_t size) { + if (p != nullptr) { + this->current_free_size += GetUsedSize(p); + } + } + + void *PeakCheckableMemoryResourceFromExpHeap::AllocateImpl(size_t size, size_t align) { + std::scoped_lock lk(this->mutex); + + void *p = lmem::AllocateFromExpHeap(this->heap_handle, size, static_cast(align)); + this->OnAllocate(p, size); + return p; + } + + void PeakCheckableMemoryResourceFromExpHeap::DeallocateImpl(void *p, size_t size, size_t align) { + std::scoped_lock lk(this->mutex); + + this->OnDeallocate(p, size); + lmem::FreeToExpHeap(this->heap_handle, p); + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp b/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp new file mode 100644 index 000000000..5270331fa --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fssrv_memory_resource_from_standard_allocator.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv { + + MemoryResourceFromStandardAllocator::MemoryResourceFromStandardAllocator(mem::StandardAllocator *allocator) : allocator(allocator), mutex() { + this->current_free_size = this->allocator->GetTotalFreeSize(); + this->ClearPeak(); + } + + void MemoryResourceFromStandardAllocator::ClearPeak() { + std::scoped_lock lk(this->mutex); + this->peak_free_size = this->current_free_size; + this->peak_allocated_size = 0; + } + + void *MemoryResourceFromStandardAllocator::AllocateImpl(size_t size, size_t align) { + std::scoped_lock lk(this->mutex); + + void *p = this->allocator->Allocate(size, align); + + if (p != nullptr) { + this->current_free_size -= this->allocator->GetSizeOf(p); + this->peak_free_size = std::min(this->peak_free_size, this->current_free_size); + } + + this->peak_allocated_size = std::max(this->peak_allocated_size, size); + + return p; + } + + void MemoryResourceFromStandardAllocator::DeallocateImpl(void *p, size_t size, size_t align) { + std::scoped_lock lk(this->mutex); + + this->current_free_size += this->allocator->GetSizeOf(p); + this->allocator->Free(p); + } + +} diff --git a/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp b/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp new file mode 100644 index 000000000..0597a6156 --- /dev/null +++ b/libraries/libstratosphere/source/fssrv/fssrv_nca_crypto_configuration.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssrv { + + namespace { + + constexpr inline const u8 HeaderSign1KeyModulusDev[fssystem::NcaCryptoConfiguration::Header1SignatureKeyGenerationMax + 1][fssystem::NcaCryptoConfiguration::Rsa2048KeyModulusSize] = { + { + 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, + 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, + 0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C, + 0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B, + 0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4, + 0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47, + 0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE, + 0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D, + 0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E, + 0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0, + 0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D, + 0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E, + 0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B, + 0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E, + 0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62, + 0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9 + }, + { + 0x9A, 0xBC, 0x88, 0xBD, 0x0A, 0xBE, 0xD7, 0x0C, 0x9B, 0x42, 0x75, 0x65, 0x38, 0x5E, 0xD1, 0x01, + 0xCD, 0x12, 0xAE, 0xEA, 0xE9, 0x4B, 0xDB, 0xB4, 0x5E, 0x36, 0x10, 0x96, 0xDA, 0x3D, 0x2E, 0x66, + 0xD3, 0x99, 0x13, 0x8A, 0xBE, 0x67, 0x41, 0xC8, 0x93, 0xD9, 0x3E, 0x42, 0xCE, 0x34, 0xCE, 0x96, + 0xFA, 0x0B, 0x23, 0xCC, 0x2C, 0xDF, 0x07, 0x3F, 0x3B, 0x24, 0x4B, 0x12, 0x67, 0x3A, 0x29, 0x36, + 0xA3, 0xAA, 0x06, 0xF0, 0x65, 0xA5, 0x85, 0xBA, 0xFD, 0x12, 0xEC, 0xF1, 0x60, 0x67, 0xF0, 0x8F, + 0xD3, 0x5B, 0x01, 0x1B, 0x1E, 0x84, 0xA3, 0x5C, 0x65, 0x36, 0xF9, 0x23, 0x7E, 0xF3, 0x26, 0x38, + 0x64, 0x98, 0xBA, 0xE4, 0x19, 0x91, 0x4C, 0x02, 0xCF, 0xC9, 0x6D, 0x86, 0xEC, 0x1D, 0x41, 0x69, + 0xDD, 0x56, 0xEA, 0x5C, 0xA3, 0x2A, 0x58, 0xB4, 0x39, 0xCC, 0x40, 0x31, 0xFD, 0xFB, 0x42, 0x74, + 0xF8, 0xEC, 0xEA, 0x00, 0xF0, 0xD9, 0x28, 0xEA, 0xFA, 0x2D, 0x00, 0xE1, 0x43, 0x53, 0xC6, 0x32, + 0xF4, 0xA2, 0x07, 0xD4, 0x5F, 0xD4, 0xCB, 0xAC, 0xCA, 0xFF, 0xDF, 0x84, 0xD2, 0x86, 0x14, 0x3C, + 0xDE, 0x22, 0x75, 0xA5, 0x73, 0xFF, 0x68, 0x07, 0x4A, 0xF9, 0x7C, 0x2C, 0xCC, 0xDE, 0x45, 0xB6, + 0x54, 0x82, 0x90, 0x36, 0x1F, 0x2C, 0x51, 0x96, 0xC5, 0x0A, 0x53, 0x5B, 0xF0, 0x8B, 0x4A, 0xAA, + 0x3B, 0x68, 0x97, 0x19, 0x17, 0x1F, 0x01, 0xB8, 0xED, 0xB9, 0x9A, 0x5E, 0x08, 0xC5, 0x20, 0x1E, + 0x6A, 0x09, 0xF0, 0xE9, 0x73, 0xA3, 0xBE, 0x10, 0x06, 0x02, 0xE9, 0xFB, 0x85, 0xFA, 0x5F, 0x01, + 0xAC, 0x60, 0xE0, 0xED, 0x7D, 0xB9, 0x49, 0xA8, 0x9E, 0x98, 0x7D, 0x91, 0x40, 0x05, 0xCF, 0xF9, + 0x1A, 0xFC, 0x40, 0x22, 0xA8, 0x96, 0x5B, 0xB0, 0xDC, 0x7A, 0xF5, 0xB7, 0xE9, 0x91, 0x4C, 0x49 + } + }; + + constexpr inline const u8 HeaderSign1KeyModulusProd[fssystem::NcaCryptoConfiguration::Header1SignatureKeyGenerationMax + 1][fssystem::NcaCryptoConfiguration::Rsa2048KeyModulusSize] = { + { + 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, + 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, + 0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16, + 0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2, + 0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF, + 0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67, + 0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26, + 0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C, + 0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE, + 0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86, + 0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35, + 0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B, + 0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29, + 0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40, + 0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81, + 0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03 + }, + { + 0xAD, 0xE3, 0xE1, 0xFA, 0x04, 0x35, 0xE5, 0xB6, 0xDD, 0x49, 0xEA, 0x89, 0x29, 0xB1, 0xFF, 0xB6, + 0x43, 0xDF, 0xCA, 0x96, 0xA0, 0x4A, 0x13, 0xDF, 0x43, 0xD9, 0x94, 0x97, 0x96, 0x43, 0x65, 0x48, + 0x70, 0x58, 0x33, 0xA2, 0x7D, 0x35, 0x7B, 0x96, 0x74, 0x5E, 0x0B, 0x5C, 0x32, 0x18, 0x14, 0x24, + 0xC2, 0x58, 0xB3, 0x6C, 0x22, 0x7A, 0xA1, 0xB7, 0xCB, 0x90, 0xA7, 0xA3, 0xF9, 0x7D, 0x45, 0x16, + 0xA5, 0xC8, 0xED, 0x8F, 0xAD, 0x39, 0x5E, 0x9E, 0x4B, 0x51, 0x68, 0x7D, 0xF8, 0x0C, 0x35, 0xC6, + 0x3F, 0x91, 0xAE, 0x44, 0xA5, 0x92, 0x30, 0x0D, 0x46, 0xF8, 0x40, 0xFF, 0xD0, 0xFF, 0x06, 0xD2, + 0x1C, 0x7F, 0x96, 0x18, 0xDC, 0xB7, 0x1D, 0x66, 0x3E, 0xD1, 0x73, 0xBC, 0x15, 0x8A, 0x2F, 0x94, + 0xF3, 0x00, 0xC1, 0x83, 0xF1, 0xCD, 0xD7, 0x81, 0x88, 0xAB, 0xDF, 0x8C, 0xEF, 0x97, 0xDD, 0x1B, + 0x17, 0x5F, 0x58, 0xF6, 0x9A, 0xE9, 0xE8, 0xC2, 0x2F, 0x38, 0x15, 0xF5, 0x21, 0x07, 0xF8, 0x37, + 0x90, 0x5D, 0x2E, 0x02, 0x40, 0x24, 0x15, 0x0D, 0x25, 0xB7, 0x26, 0x5D, 0x09, 0xCC, 0x4C, 0xF4, + 0xF2, 0x1B, 0x94, 0x70, 0x5A, 0x9E, 0xEE, 0xED, 0x77, 0x77, 0xD4, 0x51, 0x99, 0xF5, 0xDC, 0x76, + 0x1E, 0xE3, 0x6C, 0x8C, 0xD1, 0x12, 0xD4, 0x57, 0xD1, 0xB6, 0x83, 0xE4, 0xE4, 0xFE, 0xDA, 0xE9, + 0xB4, 0x3B, 0x33, 0xE5, 0x37, 0x8A, 0xDF, 0xB5, 0x7F, 0x89, 0xF1, 0x9B, 0x9E, 0xB0, 0x15, 0xB2, + 0x3A, 0xFE, 0xEA, 0x61, 0x84, 0x5B, 0x7D, 0x4B, 0x23, 0x12, 0x0B, 0x83, 0x12, 0xF2, 0x22, 0x6B, + 0xB9, 0x22, 0x96, 0x4B, 0x26, 0x0B, 0x63, 0x5E, 0x96, 0x57, 0x52, 0xA3, 0x67, 0x64, 0x22, 0xCA, + 0xD0, 0x56, 0x3E, 0x74, 0xB5, 0x98, 0x1F, 0x0D, 0xF8, 0xB3, 0x34, 0xE6, 0x98, 0x68, 0x5A, 0xAD + } + }; + + constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationDev = { + /* Header1 Signature Key Moduli */ + { HeaderSign1KeyModulusDev[0], HeaderSign1KeyModulusDev[1] }, + + /* Header 1 Signature Key Public Exponent */ + { 0x01, 0x00, 0x01 }, + + /* Key Area Encryption Key Sources */ + { + /* Application */ + { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D }, + + /* Ocean */ + { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 }, + + /* System */ + { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A }, + }, + + /* Header Encryption Key Source */ + { 0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A }, + + /* Encrypted Header Encryption Key */ + { + { 0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0 }, + { 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 } + }, + + /* Key Generation Function */ + nullptr, + + /* Decrypt Aes Ctr Function */ + nullptr, + + /* Decrypt Aes Ctr External Function */ + nullptr, + + /* Plaintext Header Available */ + false, + }; + + constexpr inline const ::ams::fssystem::NcaCryptoConfiguration DefaultNcaCryptoConfigurationProd = { + /* Header1 Signature Key Moduli */ + { HeaderSign1KeyModulusProd[0], HeaderSign1KeyModulusProd[1] }, + + /* Header 1 Signature Key Public Exponent */ + { 0x01, 0x00, 0x01 }, + + /* Key Area Encryption Key Sources */ + { + /* Application */ + { 0x7F, 0x59, 0x97, 0x1E, 0x62, 0x9F, 0x36, 0xA1, 0x30, 0x98, 0x06, 0x6F, 0x21, 0x44, 0xC3, 0x0D }, + + /* Ocean */ + { 0x32, 0x7D, 0x36, 0x08, 0x5A, 0xD1, 0x75, 0x8D, 0xAB, 0x4E, 0x6F, 0xBA, 0xA5, 0x55, 0xD8, 0x82 }, + + /* System */ + { 0x87, 0x45, 0xF1, 0xBB, 0xA6, 0xBE, 0x79, 0x64, 0x7D, 0x04, 0x8B, 0xA6, 0x7B, 0x5F, 0xDA, 0x4A }, + }, + + /* Header Encryption Key Source */ + { 0x1F, 0x12, 0x91, 0x3A, 0x4A, 0xCB, 0xF0, 0x0D, 0x4C, 0xDE, 0x3A, 0xF6, 0xD5, 0x23, 0x88, 0x2A }, + + /* Encrypted Header Encryption Key */ + { + { 0x5A, 0x3E, 0xD8, 0x4F, 0xDE, 0xC0, 0xD8, 0x26, 0x31, 0xF7, 0xE2, 0x5D, 0x19, 0x7B, 0xF5, 0xD0 }, + { 0x1C, 0x9B, 0x7B, 0xFA, 0xF6, 0x28, 0x18, 0x3D, 0x71, 0xF6, 0x4D, 0x73, 0xF1, 0x50, 0xB9, 0xD2 } + }, + + /* Key Generation Function */ + nullptr, + + /* Decrypt Aes Ctr Function */ + nullptr, + + /* Decrypt Aes Ctr External Function */ + nullptr, + + /* Plaintext Header Available */ + false, + }; + + } + + const ::ams::fssystem::NcaCryptoConfiguration *GetDefaultNcaCryptoConfiguration(bool prod) { + return prod ? std::addressof(DefaultNcaCryptoConfigurationProd) : std::addressof(DefaultNcaCryptoConfigurationDev); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp b/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp new file mode 100644 index 000000000..34bdb7b11 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/buffers/fssystem_file_system_buffer_manager.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + Result FileSystemBufferManager::CacheHandleTable::Initialize(s32 max_cache_count) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries == nullptr); + AMS_ASSERT(this->internal_entry_buffer == nullptr); + + /* If we don't have an external buffer, try to allocate an internal one. */ + if (this->external_entry_buffer == nullptr) { + this->entry_buffer_size = sizeof(Entry) * max_cache_count; + this->internal_entry_buffer = fs::impl::MakeUnique(this->entry_buffer_size); + } + + /* We need to have at least one entry buffer. */ + R_UNLESS(this->internal_entry_buffer != nullptr || this->external_entry_buffer != nullptr, fs::ResultAllocationFailureInFileSystemBufferManagerA()); + + /* Set entries. */ + this->entries = reinterpret_cast(this->external_entry_buffer != nullptr ? this->external_entry_buffer : this->internal_entry_buffer.get()); + this->entry_count = 0; + this->entry_count_max = max_cache_count; + AMS_ASSERT(this->entries != nullptr); + + this->cache_count_min = max_cache_count / 16; + this->cache_size_min = this->cache_count_min * 0x100; + + return ResultSuccess(); + } + + void FileSystemBufferManager::CacheHandleTable::Finalize() { + if (this->entries != nullptr) { + AMS_ASSERT(this->entry_count == 0); + + if (this->external_attr_info_buffer == nullptr) { + auto it = this->attr_list.begin(); + while (it != this->attr_list.end()) { + const auto attr_info = std::addressof(*it); + it = this->attr_list.erase(it); + delete attr_info; + } + } + + this->internal_entry_buffer.reset(); + this->external_entry_buffer = nullptr; + this->entry_buffer_size = 0; + this->entries = nullptr; + this->total_cache_size = 0; + } + } + + bool FileSystemBufferManager::CacheHandleTable::Register(CacheHandle *out, uintptr_t address, size_t size, const BufferAttribute &attr) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + AMS_ASSERT(out != nullptr); + + /* Get the entry. */ + auto entry = this->AcquireEntry(address, size, attr); + + /* If we don't have an entry, we can't register. */ + if (entry == nullptr) { + return false; + } + + /* Get the attr info. If we have one, increment. */ + if (const auto attr_info = this->FindAttrInfo(attr); attr_info != nullptr) { + attr_info->IncrementCacheCount(); + attr_info->AddCacheSize(size); + } else { + /* Make a new attr info and add it to the list. */ + AttrInfo *new_info = nullptr; + + if (this->external_attr_info_buffer == nullptr) { + new_info = new AttrInfo(attr.GetLevel(), 1, size); + } else if (0 <= attr.GetLevel() && attr.GetLevel() < this->external_attr_info_count) { + const auto buffer = this->external_attr_info_buffer + attr.GetLevel() * sizeof(AttrInfo); + new_info = new (buffer) AttrInfo(attr.GetLevel(), 1, size); + } + + /* If we failed to make a new attr info, we can't register. */ + if (new_info == nullptr) { + this->ReleaseEntry(entry); + return false; + } + + this->attr_list.push_back(*new_info); + } + + this->total_cache_size += size; + *out = entry->GetHandle(); + return true; + } + + bool FileSystemBufferManager::CacheHandleTable::Unregister(uintptr_t *out_address, size_t *out_size, CacheHandle handle) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* Find the lower bound for the entry. */ + const auto entry = std::lower_bound(this->entries, this->entries + this->entry_count, handle, [](const Entry &entry, CacheHandle handle) { + return entry.GetHandle() < handle; + }); + + /* If the entry is a match, unregister it. */ + if (entry != this->entries + this->entry_count && entry->GetHandle() == handle) { + this->UnregisterCore(out_address, out_size, entry); + return true; + } else { + return false; + } + } + + bool FileSystemBufferManager::CacheHandleTable::UnregisterOldest(uintptr_t *out_address, size_t *out_size, const BufferAttribute &attr, size_t required_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + + /* If we have no entries, we can't unregister any. */ + if (this->entry_count == 0) { + return false; + } + + const auto CanUnregister = [this](const Entry &entry) { + const auto attr_info = this->FindAttrInfo(entry.GetBufferAttribute()); + AMS_ASSERT(attr_info != nullptr); + + const auto ccm = this->GetCacheCountMin(entry.GetBufferAttribute()); + const auto csm = this->GetCacheSizeMin(entry.GetBufferAttribute()); + + return ccm < attr_info->GetCacheCount() && csm + entry.GetSize() <= attr_info->GetCacheSize(); + }; + + /* Find an entry, falling back to the first entry. */ + auto entry = std::find_if(this->entries, this->entries + this->entry_count, CanUnregister); + if (entry == this->entries + this->entry_count) { + entry = this->entries; + } + + AMS_ASSERT(entry != this->entries + this->entry_count); + this->UnregisterCore(out_address, out_size, entry); + return true; + } + + void FileSystemBufferManager::CacheHandleTable::UnregisterCore(uintptr_t *out_address, size_t *out_size, Entry *entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + AMS_ASSERT(out_address != nullptr); + AMS_ASSERT(out_size != nullptr); + AMS_ASSERT(entry != nullptr); + + /* Get the attribute info. */ + const auto attr_info = this->FindAttrInfo(entry->GetBufferAttribute()); + AMS_ASSERT(attr_info != nullptr); + AMS_ASSERT(attr_info->GetCacheCount() > 0); + AMS_ASSERT(attr_info->GetCacheSize() >= entry->GetSize()); + + /* Release from the attr info. */ + attr_info->DecrementCacheCount(); + attr_info->SubtractCacheSize(entry->GetSize()); + + /* Release from cached size. */ + AMS_ASSERT(this->total_cache_size >= entry->GetSize()); + this->total_cache_size -= entry->GetSize(); + + /* Release the entry. */ + *out_address = entry->GetAddress(); + *out_size = entry->GetSize(); + this->ReleaseEntry(entry); + } + + FileSystemBufferManager::CacheHandle FileSystemBufferManager::CacheHandleTable::PublishCacheHandle() { + AMS_ASSERT(this->entries != nullptr); + return (++this->current_handle); + } + + size_t FileSystemBufferManager::CacheHandleTable::GetTotalCacheSize() const { + return this->total_cache_size; + } + + FileSystemBufferManager::CacheHandleTable::Entry *FileSystemBufferManager::CacheHandleTable::AcquireEntry(uintptr_t address, size_t size, const BufferAttribute &attr) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + + Entry *entry = nullptr; + if (this->entry_count < this->entry_count_max) { + entry = this->entries + this->entry_count; + entry->Initialize(this->PublishCacheHandle(), address, size, attr); + ++this->entry_count; + AMS_ASSERT(this->entry_count == 1 || (entry-1)->GetHandle() < entry->GetHandle()); + } + + return entry; + } + + void FileSystemBufferManager::CacheHandleTable::ReleaseEntry(Entry *entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->entries != nullptr); + AMS_ASSERT(entry != nullptr); + + /* Ensure the entry is valid. */ + const auto entry_buffer = this->external_entry_buffer != nullptr ? this->external_entry_buffer : this->internal_entry_buffer.get(); + AMS_ASSERT(static_cast(entry_buffer) <= static_cast(entry)); + AMS_ASSERT(static_cast(entry) < static_cast(entry_buffer + this->entry_buffer_size)); + + /* Copy the entries back by one. */ + std::memmove(entry, entry + 1, sizeof(Entry) * (this->entry_count - ((entry + 1) - this->entries))); + + /* Decrement our entry count. */ + --this->entry_count; + } + + FileSystemBufferManager::CacheHandleTable::AttrInfo *FileSystemBufferManager::CacheHandleTable::FindAttrInfo(const BufferAttribute &attr) { + const auto it = std::find_if(this->attr_list.begin(), this->attr_list.end(), [&attr](const AttrInfo &info) { + return attr.GetLevel() == attr.GetLevel(); + }); + + return it != this->attr_list.end() ? std::addressof(*it) : nullptr; + } + + const std::pair FileSystemBufferManager::AllocateBufferImpl(size_t size, const BufferAttribute &attr) { + std::scoped_lock lk(this->mutex); + + std::pair range = {}; + const auto order = this->buddy_heap.GetOrderFromBytes(size); + AMS_ASSERT(order >= 0); + + while (true) { + if (auto address = this->buddy_heap.AllocateByOrder(order); address != 0) { + const auto allocated_size = this->buddy_heap.GetBytesFromOrder(order); + AMS_ASSERT(size <= allocated_size); + + range.first = reinterpret_cast(address); + range.second = allocated_size; + + const size_t free_size = this->buddy_heap.GetTotalFreeSize(); + this->peak_free_size = std::min(this->peak_free_size, free_size); + + const size_t total_allocatable_size = free_size + this->cache_handle_table.GetTotalCacheSize(); + this->peak_total_allocatable_size = std::min(this->peak_total_allocatable_size, total_allocatable_size); + break; + } + + /* Deallocate a buffer. */ + uintptr_t deallocate_address = 0; + size_t deallocate_size = 0; + + ++this->retried_count; + if (this->cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr, size)) { + this->DeallocateBuffer(deallocate_address, deallocate_size); + } else { + break; + } + } + + return range; + } + + void FileSystemBufferManager::DeallocateBufferImpl(uintptr_t address, size_t size) { + AMS_ASSERT(util::IsPowerOfTwo(size)); + + std::scoped_lock lk(this->mutex); + + this->buddy_heap.Free(reinterpret_cast(address), this->buddy_heap.GetOrderFromBytes(size)); + } + + FileSystemBufferManager::CacheHandle FileSystemBufferManager::RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr) { + std::scoped_lock lk(this->mutex); + + CacheHandle handle = 0; + while (true) { + /* Try to register the handle. */ + if (this->cache_handle_table.Register(std::addressof(handle), address, size, attr)) { + break; + } + + /* Deallocate a buffer. */ + uintptr_t deallocate_address = 0; + size_t deallocate_size = 0; + + ++this->retried_count; + if (this->cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr)) { + this->DeallocateBuffer(deallocate_address, deallocate_size); + } else { + this->DeallocateBuffer(address, size); + handle = this->cache_handle_table.PublishCacheHandle(); + break; + } + } + + return handle; + } + + const std::pair FileSystemBufferManager::AcquireCacheImpl(CacheHandle handle) { + std::scoped_lock lk(this->mutex); + + std::pair range = {}; + if (this->cache_handle_table.Unregister(std::addressof(range.first), std::addressof(range.second), handle)) { + const size_t total_allocatable_size = this->buddy_heap.GetTotalFreeSize() + this->cache_handle_table.GetTotalCacheSize(); + this->peak_total_allocatable_size = std::min(this->peak_total_allocatable_size, total_allocatable_size); + } else { + range.first = 0; + range.second = 0; + } + + return range; + } + + size_t FileSystemBufferManager::GetTotalSizeImpl() const { + return this->total_size; + } + + size_t FileSystemBufferManager::GetFreeSizeImpl() const { + std::scoped_lock lk(this->mutex); + + return this->buddy_heap.GetTotalFreeSize(); + } + + size_t FileSystemBufferManager::GetTotalAllocatableSizeImpl() const { + return this->GetFreeSize() + this->cache_handle_table.GetTotalCacheSize(); + } + + size_t FileSystemBufferManager::GetPeakFreeSizeImpl() const { + return this->peak_free_size; + } + + size_t FileSystemBufferManager::GetPeakTotalAllocatableSizeImpl() const { + return this->peak_total_allocatable_size; + } + + size_t FileSystemBufferManager::GetRetriedCountImpl() const { + return this->retried_count; + } + + void FileSystemBufferManager::ClearPeakImpl() { + this->peak_free_size = this->GetFreeSize(); + this->retried_count = 0; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..a0177d1c8 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { + public: + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final; + virtual bool HasExternalDecryptionKey() const override final { return false; } + }; + + class ExternalDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { + public: + static constexpr size_t BlockSize = AesCtrCounterExtendedStorage::BlockSize; + static constexpr size_t KeySize = AesCtrCounterExtendedStorage::KeySize; + static constexpr size_t IvSize = AesCtrCounterExtendedStorage::IvSize; + private: + AesCtrCounterExtendedStorage::DecryptFunction decrypt_function; + s32 key_index; + public: + ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx) : decrypt_function(df), key_index(key_idx) { + AMS_ASSERT(this->decrypt_function != nullptr); + } + public: + virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final; + virtual bool HasExternalDecryptionKey() const override final { return this->key_index < 0; } + }; + + } + + Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr *out, DecryptFunction func, s32 key_index) { + std::unique_ptr decryptor = std::make_unique(func, key_index); + R_UNLESS(decryptor != nullptr, fs::ResultAllocationFailureInAesCtrCounterExtendedStorageA()); + *out = std::move(decryptor); + return ResultSuccess(); + } + + Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr *out) { + std::unique_ptr decryptor = std::make_unique(); + R_UNLESS(decryptor != nullptr, fs::ResultAllocationFailureInAesCtrCounterExtendedStorageA()); + *out = std::move(decryptor); + return ResultSuccess(); + } + + Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, fs::SubStorage data_storage, fs::SubStorage table_storage) { + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + R_TRY(table_storage.Read(0, std::addressof(header), sizeof(header))); + R_TRY(header.Verify()); + + /* Determine extents. */ + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + /* Create a software decryptor. */ + std::unique_ptr sw_decryptor; + R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + /* Initialize. */ + return this->Initialize(allocator, key, key_size, secure_value, 0, data_storage, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count, std::move(sw_decryptor)); + } + + Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, s64 counter_offset, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count, std::unique_ptr &&decryptor) { + /* Validate preconditions. */ + AMS_ASSERT(key != nullptr); + AMS_ASSERT(key_size == KeySize); + AMS_ASSERT(counter_offset >= 0); + AMS_ASSERT(decryptor != nullptr); + + /* Initialize the bucket tree table. */ + R_TRY(this->table.Initialize(allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); + + /* Set members. */ + this->data_storage = data_storage; + std::memcpy(this->key, key, key_size); + this->secure_value = secure_value; + this->counter_offset = counter_offset; + this->decryptor = std::move(decryptor); + + return ResultSuccess(); + } + + void AesCtrCounterExtendedStorage::Finalize() { + if (this->IsInitialized()) { + this->table.Finalize(); + this->data_storage = fs::SubStorage(); + } + } + + Result AesCtrCounterExtendedStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize()); + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + + /* Read the data. */ + R_TRY(this->data_storage.Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(this->table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get()->GetOffset(); + R_UNLESS(util::IsAligned(entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + R_UNLESS(0 <= entry_offset && this->table.Includes(entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + } + + /* Prepare to read in chunks. */ + u8 *cur_data = static_cast(buffer); + auto cur_offset = offset; + const auto end_offset = offset + static_cast(size); + + while (cur_offset < end_offset) { + /* Get the current entry. */ + const auto cur_entry = *visitor.Get(); + + /* Get and validate the entry's offset. */ + const auto cur_entry_offset = cur_entry.GetOffset(); + R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + + /* Get and validate the next entry offset. */ + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get()->GetOffset(); + R_UNLESS(this->table.Includes(next_entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + } else { + next_entry_offset = this->table.GetEnd(); + } + R_UNLESS(util::IsAligned(next_entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset()); + + /* Get the offset of the entry in the data we read. */ + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; + AMS_ASSERT(data_size > 0); + + /* Determine how much is left. */ + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = static_cast(std::min(remaining_size, data_size)); + AMS_ASSERT(cur_size <= size); + + /* Make the CTR for the data we're decrypting. */ + const auto counter_offset = this->counter_offset + cur_entry_offset + data_offset; + NcaAesCtrUpperIv upper_iv = { .part = { .generation = static_cast(cur_entry.generation), .secure_value = this->secure_value } }; + + u8 iv[IvSize]; + AesCtrStorage::MakeIv(iv, IvSize, upper_iv.value, counter_offset); + + /* Decrypt. */ + this->decryptor->Decrypt(cur_data, cur_size, this->key, KeySize, iv, IvSize); + + /* Advance. */ + cur_data += cur_size; + cur_offset += cur_size; + } + + return ResultSuccess(); + } + + Result AesCtrCounterExtendedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::InvalidateCache: + { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to operate on. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize()); + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + + /* Invalidate our table's cache. */ + R_TRY(this->table.InvalidateCache()); + + /* Operate on our data storage. */ + R_TRY(this->data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + return ResultSuccess(); + } + case fs::OperationId::QueryRange: + { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + /* Succeed if there's nothing to operate on. */ + if (size == 0) { + reinterpret_cast(dst)->Clear(); + return ResultSuccess(); + } + + /* Validate arguments. */ + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize()); + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + + /* Operate on our data storage. */ + R_TRY(this->data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + /* Add in new flags. */ + fs::QueryRangeInfo new_info; + new_info.Clear(); + new_info.aes_ctr_key_type = static_cast(this->decryptor->HasExternalDecryptionKey() ? fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes); + + /* Merge in the new info. */ + reinterpret_cast(dst)->Merge(new_info); + + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInAesCtrCounterExtendedStorageC(); + } + } + + void SoftwareDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) { + crypto::DecryptAes128Ctr(buf, buf_size, enc_key, enc_key_size, iv, iv_size, buf, buf_size); + } + + void ExternalDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) { + /* Validate preconditions. */ + AMS_ASSERT(buf != nullptr); + AMS_ASSERT(enc_key != nullptr); + AMS_ASSERT(enc_key_size == KeySize); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(iv_size == IvSize); + + /* Copy the ctr. */ + u8 ctr[IvSize]; + std::memcpy(ctr, iv, IvSize); + + /* Setup tracking. */ + size_t remaining_size = buf_size; + s64 cur_offset = 0; + + /* Allocate a pooled buffer for decryption. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(buf_size, BlockSize); + AMS_ASSERT(pooled_buffer.GetSize() > 0 && util::IsAligned(pooled_buffer.GetSize(), BlockSize)); + + /* Read and decrypt in chunks. */ + while (remaining_size > 0) { + size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); + u8 *dst = static_cast(buf) + cur_offset; + + this->decrypt_function(pooled_buffer.GetBuffer(), cur_size, this->key_index, enc_key, enc_key_size, ctr, IvSize, dst, cur_size); + + std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); + + cur_offset += cur_size; + remaining_size -= cur_size; + + if (remaining_size > 0) { + AddCounter(ctr, IvSize, cur_size / BlockSize); + } + } + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp b/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..225a9656d --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_alignment_matching_storage_impl.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + template + constexpr ALWAYS_INLINE size_t GetRoundDownDifference(T x, size_t align) { + return static_cast(x - util::AlignDown(x, align)); + } + + template + constexpr ALWAYS_INLINE size_t GetRoundUpDifference(T x, size_t align) { + return static_cast(util::AlignUp(x, align) - x); + } + + template + ALWAYS_INLINE size_t GetRoundUpDifference(T *x, size_t align) { + return GetRoundUpDifference(reinterpret_cast(x), align); + } + + } + + Result AlignmentMatchingStorageImpl::Read(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size) { + /* Check preconditions. */ + AMS_ASSERT(work_buf_size >= data_alignment); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine extents. */ + char *aligned_core_buffer; + s64 core_offset; + size_t core_size; + size_t buffer_gap; + size_t offset_gap; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (util::IsAligned(reinterpret_cast(buffer) + offset_round_up_difference, buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = util::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment); + buffer_gap = 0; + offset_gap = 0; + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); + + aligned_core_buffer = buffer + buffer_round_up_difference; + + core_offset = util::AlignDown(offset, data_alignment); + core_size = (size < buffer_round_up_difference) ? 0 : util::AlignDown(size - buffer_round_up_difference, data_alignment); + buffer_gap = buffer_round_up_difference; + offset_gap = GetRoundDownDifference(offset, data_alignment); + } + + /* Read the core portion. */ + if (core_size > 0) { + R_TRY(base_storage->Read(core_offset, aligned_core_buffer, core_size)); + + if (offset_gap != 0 || buffer_gap != 0) { + std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, core_size - offset_gap); + core_size -= offset_gap; + } + } + + /* Handle the head portion. */ + if (offset < covered_offset) { + const s64 head_offset = util::AlignDown(offset, data_alignment); + const size_t head_size = static_cast(covered_offset - offset); + + AMS_ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); + + R_TRY(base_storage->Read(head_offset, work_buf, data_alignment)); + std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); + } + + /* Handle the tail portion. */ + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment); + const auto cur_size = std::min(static_cast(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size); + R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment)); + + AMS_ASSERT((tail_offset - offset) + cur_size <= size); + AMS_ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); + std::memcpy(static_cast(buffer) + (tail_offset - offset), work_buf + (tail_offset - aligned_tail_offset), cur_size); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + return ResultSuccess(); + } + + Result AlignmentMatchingStorageImpl::Write(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size) { + /* Check preconditions. */ + AMS_ASSERT(work_buf_size >= data_alignment); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine extents. */ + const char *aligned_core_buffer; + s64 core_offset; + size_t core_size; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (util::IsAligned(reinterpret_cast(buffer) + offset_round_up_difference, buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = util::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment); + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + aligned_core_buffer = nullptr; + + core_offset = util::AlignDown(offset, data_alignment); + core_size = 0; + + covered_offset = offset; + } + + /* Write the core portion. */ + if (core_size > 0) { + R_TRY(base_storage->Write(core_offset, aligned_core_buffer, core_size)); + } + + /* Handle the head portion. */ + if (offset < covered_offset) { + const s64 head_offset = util::AlignDown(offset, data_alignment); + const size_t head_size = static_cast(covered_offset - offset); + + AMS_ASSERT((offset - head_offset) + head_size <= data_alignment); + + R_TRY(base_storage->Read(head_offset, work_buf, data_alignment)); + std::memcpy(work_buf + (offset - head_offset), buffer, head_size); + R_TRY(base_storage->Write(head_offset, work_buf, data_alignment)); + } + + /* Handle the tail portion. */ + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + AMS_ASSERT(static_cast(tail_offset - offset) < size); + + const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment); + const auto cur_size = std::min(static_cast(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size); + + R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment)); + std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), buffer + (tail_offset - offset), cur_size); + R_TRY(base_storage->Write(aligned_tail_offset, work_buf, data_alignment)); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + return ResultSuccess(); + } + + template<> + Result AlignmentMatchingStorageInBulkRead<1>::Read(s64 offset, void *buffer, size_t size) { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + s64 bs_size = 0; + R_TRY(this->GetSize(std::addressof(bs_size))); + R_UNLESS(fs::IStorage::IsRangeValid(offset, size, bs_size), fs::ResultOutOfRange()); + + /* Determine extents. */ + const auto offset_end = offset + static_cast(size); + const auto aligned_offset = util::AlignDown(offset, this->data_align); + const auto aligned_offset_end = util::AlignUp(offset_end, this->data_align); + const auto aligned_size = static_cast(aligned_offset_end - aligned_offset); + + /* If we aren't aligned, we need to allocate a buffer. */ + PooledBuffer pooled_buffer; + if (aligned_offset != offset || aligned_size != size) { + if (aligned_size <= pooled_buffer.GetAllocatableSizeMax()) { + pooled_buffer.Allocate(aligned_size, this->data_align); + + if (aligned_size <= pooled_buffer.GetSize()) { + R_TRY(this->base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), aligned_size)); + std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), size); + return ResultSuccess(); + } else { + pooled_buffer.Shrink(this->data_align); + } + } else { + pooled_buffer.Allocate(this->data_align, this->data_align); + } + + AMS_ASSERT(pooled_buffer.GetSize() >= static_cast(this->data_align)); + } + + /* Determine read extents for the aligned portion. */ + const auto core_offset = util::AlignUp(offset, this->data_align); + const auto core_offset_end = util::AlignDown(offset_end, this->data_align); + + /* Handle any data before the aligned portion. */ + if (offset < core_offset) { + const auto head_size = static_cast(core_offset - offset); + AMS_ASSERT(head_size < size); + R_TRY(this->base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), this->data_align)); + std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), head_size); + } + + /* Handle the aligned portion. */ + if (core_offset < core_offset_end) { + const auto core_buffer = static_cast(buffer) + (core_offset - offset); + const auto core_size = static_cast(core_offset_end - core_offset); + + R_TRY(this->base_storage->Read(core_offset, core_buffer, core_size)); + } + + /* Handle any data after the aligned portion. */ + if (core_offset_end < offset_end) { + const auto tail_size = static_cast(offset_end - core_offset_end); + R_TRY(this->base_storage->Read(core_offset_end, pooled_buffer.GetBuffer(), this->data_align)); + std::memcpy(buffer, pooled_buffer.GetBuffer(), tail_size); + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp b/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp new file mode 100644 index 000000000..8a8151537 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_allocator_utility.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + constexpr bool UseDefaultAllocators = false; + + bool g_used_default_allocator; + + void *DefaultAllocate(size_t size) { + g_used_default_allocator = true; + return std::malloc(size); + } + + void DefaultDeallocate(void *ptr, size_t size) { + std::free(ptr); + } + + AllocateFunction g_allocate_func = UseDefaultAllocators ? DefaultAllocate : nullptr; + DeallocateFunction g_deallocate_func = UseDefaultAllocators ? DefaultDeallocate : nullptr; + + } + + void *Allocate(size_t size) { + AMS_ASSERT(g_allocate_func != nullptr); + return g_allocate_func(size); + } + + void Deallocate(void *ptr, size_t size) { + AMS_ASSERT(g_deallocate_func != nullptr); + return g_deallocate_func(ptr, size); + } + + void InitializeAllocator(AllocateFunction allocate_func, DeallocateFunction deallocate_func) { + AMS_ASSERT(allocate_func != nullptr); + AMS_ASSERT(deallocate_func != nullptr); + + if constexpr (UseDefaultAllocators) { + AMS_ASSERT(g_used_default_allocator == false); + } else { + AMS_ASSERT(g_allocate_func == nullptr); + AMS_ASSERT(g_deallocate_func == nullptr); + } + + g_allocate_func = allocate_func; + g_deallocate_func = deallocate_func; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp b/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..a8f8046e6 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_bucket_tree.cpp @@ -0,0 +1,544 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + using Node = impl::BucketTreeNode; + static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); + static_assert(std::is_pod::value); + + constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); + + class StorageNode { + private: + class Offset { + public: + using difference_type = s64; + private: + s64 offset; + s32 stride; + public: + constexpr Offset(s64 offset, s32 stride) : offset(offset), stride(stride) { /* ... */ } + + constexpr Offset &operator++() { this->offset += this->stride; return *this; } + constexpr Offset operator++(int) { Offset ret(*this); this->offset += this->stride; return ret; } + + constexpr Offset &operator--() { this->offset -= this->stride; return *this; } + constexpr Offset operator--(int) { Offset ret(*this); this->offset -= this->stride; return ret; } + + constexpr difference_type operator-(const Offset &rhs) const { return (this->offset - rhs.offset) / this->stride; } + + constexpr Offset operator+(difference_type ofs) const { return Offset(this->offset + ofs * this->stride, this->stride); } + constexpr Offset operator-(difference_type ofs) const { return Offset(this->offset - ofs * this->stride, this->stride); } + + constexpr Offset &operator+=(difference_type ofs) { this->offset += ofs * this->stride; return *this; } + constexpr Offset &operator-=(difference_type ofs) { this->offset -= ofs * this->stride; return *this; } + + constexpr bool operator==(const Offset &rhs) const { return this->offset == rhs.offset; } + constexpr bool operator!=(const Offset &rhs) const { return this->offset != rhs.offset; } + + constexpr s64 Get() const { return this->offset; } + }; + private: + const Offset start; + const s32 count; + s32 index; + public: + StorageNode(size_t size, s32 count) : start(NodeHeaderSize, static_cast(size)), count(count), index(-1) { /* ... */ } + StorageNode(s64 ofs, size_t size, s32 count) : start(NodeHeaderSize + ofs, static_cast(size)), count(count), index(-1) { /* ... */ } + + s32 GetIndex() const { return this->index; } + + void Find(const char *buffer, s64 virtual_address) { + s32 end = this->count; + auto pos = this->start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + this->index = static_cast(pos - this->start) - 1; + } + + Result Find(fs::SubStorage &storage, s64 virtual_address) { + s32 end = this->count; + auto pos = this->start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + R_TRY(storage.Read(mid.Get(), std::addressof(offset), sizeof(s64))); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + this->index = static_cast(pos - this->start) - 1; + return ResultSuccess(); + } + }; + + } + + void BucketTree::Header::Format(s32 entry_count) { + AMS_ASSERT(entry_count >= 0); + + this->magic = Magic; + this->version = Version; + this->entry_count = entry_count; + this->reserved = 0; + } + + Result BucketTree::Header::Verify() const { + R_UNLESS(this->magic == Magic, fs::ResultInvalidBucketTreeSignature()); + R_UNLESS(this->entry_count >= 0, fs::ResultInvalidBucketTreeEntryCount()); + R_UNLESS(this->version <= Version, fs::ResultUnsupportedVersion()); + return ResultSuccess(); + } + + Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { + R_UNLESS(this->index == node_index, fs::ResultInvalidArgument()); + R_UNLESS(entry_size == 0 || node_size < entry_size + NodeHeaderSize, fs::ResultInvalidSize()); + + const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; + R_UNLESS(this->count > 0 && static_cast(this->count) <= max_entry_count, fs::ResultInvalidBucketTreeNodeEntryCount()); + R_UNLESS(this->offset > 0, fs::ResultInvalidBucketTreeNodeOffset()); + + return ResultSuccess(); + } + + Result BucketTree::Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, size_t node_size, size_t entry_size, s32 entry_count) { + /* Validate preconditions. */ + AMS_ASSERT(allocator != nullptr); + AMS_ASSERT(entry_size >= sizeof(s64)); + AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(!this->IsInitialized()); + + /* Ensure valid entry count. */ + R_UNLESS(entry_count > 0, fs::ResultInvalidArgument()); + + /* Allocate node. */ + R_UNLESS(this->node_l1.Allocate(allocator, node_size), fs::ResultBufferAllocationFailed()); + auto node_guard = SCOPE_GUARD { this->node_l1.Free(node_size); }; + + /* Read node. */ + R_TRY(node_storage.Read(0, this->node_l1.Get(), node_size)); + + /* Verify node. */ + R_TRY(this->node_l1->Verify(0, node_size, sizeof(s64))); + + /* Validate offsets. */ + const auto offset_count = GetOffsetCount(node_size); + const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + const auto * const node = this->node_l1.Get(); + + s64 start_offset; + if (offset_count < entry_set_count && node->GetCount() < offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset()); + R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset()); + + /* Set member variables. */ + this->node_storage = node_storage; + this->entry_storage = entry_storage; + this->node_size = node_size; + this->entry_size = entry_size; + this->entry_count = entry_count; + this->offset_count = offset_count; + this->entry_set_count = entry_set_count; + this->start_offset = start_offset; + this->end_offset = end_offset; + + /* Cancel guard. */ + node_guard.Cancel(); + return ResultSuccess(); + } + + void BucketTree::Initialize(size_t node_size, s64 end_offset) { + AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + AMS_ASSERT(util::IsPowerOfTwo(node_size)); + AMS_ASSERT(end_offset > 0); + AMS_ASSERT(!this->IsInitialized()); + + this->node_size = node_size; + this->end_offset = end_offset; + } + + void BucketTree::Finalize() { + if (this->IsInitialized()) { + this->node_storage = fs::SubStorage(); + this->entry_storage = fs::SubStorage(); + this->node_l1.Free(this->node_size); + this->node_size = 0; + this->entry_size = 0; + this->entry_count = 0; + this->offset_count = 0; + this->entry_set_count = 0; + this->start_offset = 0; + this->end_offset = 0; + } + } + + Result BucketTree::Find(Visitor *visitor, s64 virtual_address) const { + AMS_ASSERT(visitor != nullptr); + AMS_ASSERT(this->IsInitialized()); + + R_UNLESS(virtual_address >= 0, fs::ResultInvalidOffset()); + R_UNLESS(!this->IsEmpty(), fs::ResultOutOfRange()); + + R_TRY(visitor->Initialize(this)); + + return visitor->Find(virtual_address); + } + + Result BucketTree::InvalidateCache() { + /* Invalidate the node storage cache. */ + { + s64 storage_size; + R_TRY(this->node_storage.GetSize(std::addressof(storage_size))); + R_TRY(this->node_storage.OperateRange(fs::OperationId::InvalidateCache, 0, storage_size)); + } + + /* Refresh start/end offsets. */ + { + /* Read node. */ + R_TRY(node_storage.Read(0, this->node_l1.Get(), this->node_size)); + + /* Verify node. */ + R_TRY(this->node_l1->Verify(0, this->node_size, sizeof(s64))); + + /* Validate offsets. */ + const auto * const node = this->node_l1.Get(); + + s64 start_offset; + if (offset_count < this->entry_set_count && node->GetCount() < this->offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset()); + R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset()); + + /* Set refreshed offsets. */ + this->start_offset = start_offset; + this->end_offset = end_offset; + } + + /* Invalidate the entry storage cache. */ + { + s64 storage_size; + R_TRY(this->entry_storage.GetSize(std::addressof(storage_size))); + R_TRY(this->entry_storage.OperateRange(fs::OperationId::InvalidateCache, 0, storage_size)); + } + + return ResultSuccess(); + } + + Result BucketTree::Visitor::Initialize(const BucketTree *tree) { + AMS_ASSERT(tree != nullptr); + AMS_ASSERT(this->tree == nullptr || this->tree == tree); + + if (this->entry == nullptr) { + this->entry = tree->GetAllocator()->Allocate(tree->entry_size); + R_UNLESS(this->entry != nullptr, fs::ResultBufferAllocationFailed()); + + this->tree = tree; + } + + return ResultSuccess(); + } + + Result BucketTree::Visitor::MoveNext() { + R_UNLESS(this->IsValid(), fs::ResultOutOfRange()); + + /* Invalidate our index, and read the header for the next index. */ + auto entry_index = this->entry_index + 1; + if (entry_index == this->entry_set.info.count) { + const auto entry_set_index = this->entry_set.info.index + 1; + R_UNLESS(entry_set_index < this->entry_set_count, fs::ResultOutOfRange()); + + this->entry_index = -1; + + const auto end = this->entry_set.info.end; + + const auto entry_set_size = this->tree->node_size; + const auto entry_set_offset = entry_set_index * static_cast(entry_set_size); + + R_TRY(this->tree->entry_storage.Read(entry_set_offset, std::addressof(this->entry_set), sizeof(EntrySetHeader))); + R_TRY(this->entry_set.header.Verify(entry_set_index, entry_set_size, this->tree->entry_size)); + + R_UNLESS(this->entry_set.info.start == end && this->entry_set.info.start < this->entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset()); + + entry_index = 0; + } else { + this->entry_index = 1; + } + + /* Read the new entry. */ + const auto entry_size = this->tree->entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset(this->entry_set.info.index, this->tree->node_size, entry_size, entry_index); + R_TRY(this->tree->entry_storage.Read(entry_offset, std::addressof(this->entry), entry_size)); + + /* Note that we changed index. */ + this->entry_index = entry_index; + return ResultSuccess(); + } + + Result BucketTree::Visitor::MovePrevious() { + R_UNLESS(this->IsValid(), fs::ResultOutOfRange()); + + /* Invalidate our index, and read the heasder for the previous index. */ + auto entry_index = this->entry_index; + if (entry_index == 0) { + R_UNLESS(this->entry_set.info.index > 0, fs::ResultOutOfRange()); + + this->entry_index = -1; + + const auto start = this->entry_set.info.start; + + const auto entry_set_size = this->tree->node_size; + const auto entry_set_index = this->entry_set.info.index - 1; + const auto entry_set_offset = entry_set_index * static_cast(entry_set_size); + + R_TRY(this->tree->entry_storage.Read(entry_set_offset, std::addressof(this->entry_set), sizeof(EntrySetHeader))); + R_TRY(this->entry_set.header.Verify(entry_set_index, entry_set_size, this->tree->entry_size)); + + R_UNLESS(this->entry_set.info.end == start && this->entry_set.info.start < this->entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset()); + + entry_index = this->entry_set.info.count; + } else { + this->entry_index = -1; + } + + --entry_index; + + /* Read the new entry. */ + const auto entry_size = this->tree->entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset(this->entry_set.info.index, this->tree->node_size, entry_size, entry_index); + R_TRY(this->tree->entry_storage.Read(entry_offset, std::addressof(this->entry), entry_size)); + + /* Note that we changed index. */ + this->entry_index = entry_index; + return ResultSuccess(); + } + + Result BucketTree::Visitor::Find(s64 virtual_address) { + AMS_ASSERT(this->tree != nullptr); + + /* Get the node. */ + const auto * const node = this->tree->node_l1.Get(); + R_UNLESS(virtual_address < node->GetEndOffset(), fs::ResultOutOfRange()); + + /* Get the entry set index. */ + s32 entry_set_index = -1; + if (this->tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { + const auto start = node->GetEnd(); + const auto end = node->GetBegin() + tree->offset_count; + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, fs::ResultOutOfRange()); + --pos; + + entry_set_index = static_cast(pos - start); + } else { + const auto start = node->GetBegin(); + const auto end = node->GetEnd(); + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, fs::ResultOutOfRange()); + --pos; + + if (this->tree->IsExistL2()) { + const auto node_index = static_cast(pos - start); + R_UNLESS(0 <= node_index && node_index < this->tree->offset_count, fs::ResultInvalidBucketTreeNodeOffset()); + + R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); + } else { + entry_set_index = static_cast(pos - start); + } + } + + /* Validate the entry set index. */ + R_UNLESS(0 <= entry_set_index && entry_set_index < this->tree->entry_set_count, fs::ResultInvalidBucketTreeNodeOffset()); + + /* Find the entry. */ + R_TRY(this->FindEntry(virtual_address, entry_set_index)); + + /* Set count. */ + this->entry_set_count = this->tree->entry_set_count; + return ResultSuccess(); + } + + Result BucketTree::Visitor::FindEntrySet(s32 *out_index, s64 virtual_address, s32 node_index) { + const auto node_size = this->tree->node_size; + + PooledBuffer pool(node_size, 1); + if (node_size <= pool.GetSize()) { + return this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()); + } else { + pool.Deallocate(); + return this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index); + } + } + + Result BucketTree::Visitor::FindEntrySetWithBuffer(s32 *out_index, s64 virtual_address, s32 node_index, char *buffer) { + /* Calculate node extents. */ + const auto node_size = this->tree->node_size; + const auto node_offset = (node_index + 1) * static_cast(node_size); + fs::SubStorage &storage = tree->node_storage; + + /* Read the node. */ + R_TRY(storage.Read(node_offset, buffer, node_size)); + + /* Validate the header. */ + NodeHeader header; + std::memcpy(std::addressof(header), buffer, NodeHeaderSize); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + /* Create the node, and find. */ + StorageNode node(sizeof(s64), header.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Return the index. */ + *out_index = this->tree->GetEntrySetIndex(header.index, node.GetIndex()); + return ResultSuccess(); + } + + Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32 *out_index, s64 virtual_address, s32 node_index) { + /* Calculate node extents. */ + const auto node_size = this->tree->node_size; + const auto node_offset = (node_index + 1) * static_cast(node_size); + fs::SubStorage &storage = tree->node_storage; + + /* Read and validate the header. */ + NodeHeader header; + R_TRY(storage.Read(node_offset, std::addressof(header), NodeHeaderSize)); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + /* Create the node, and find. */ + StorageNode node(node_offset, sizeof(s64), header.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Return the index. */ + *out_index = this->tree->GetEntrySetIndex(header.index, node.GetIndex()); + return ResultSuccess(); + } + + Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { + const auto entry_set_size = this->tree->node_size; + + PooledBuffer pool(entry_set_size, 1); + if (entry_set_size <= pool.GetSize()) { + return this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()); + } else { + pool.Deallocate(); + return this->FindEntryWithoutBuffer(virtual_address, entry_set_index); + } + } + + Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char *buffer) { + /* Calculate entry set extents. */ + const auto entry_size = this->tree->entry_size; + const auto entry_set_size = this->tree->node_size; + const auto entry_set_offset = entry_set_index * static_cast(entry_set_size); + fs::SubStorage &storage = tree->node_storage; + + /* Read the entry set. */ + R_TRY(storage.Read(entry_set_offset, buffer, entry_set_size)); + + /* Validate the entry_set. */ + EntrySetHeader entry_set; + std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + /* Create the node, and find. */ + StorageNode node(entry_size, entry_set.info.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Copy the data into entry. */ + const auto entry_index = node.GetIndex(); + const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); + std::memcpy(this->entry, buffer + entry_offset, entry_size); + + /* Set our entry set/index. */ + this->entry_set = entry_set; + this->entry_index = entry_index; + + return ResultSuccess(); + } + + Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { + /* Calculate entry set extents. */ + const auto entry_size = this->tree->entry_size; + const auto entry_set_size = this->tree->node_size; + const auto entry_set_offset = entry_set_index * static_cast(entry_set_size); + fs::SubStorage &storage = tree->node_storage; + + /* Read and validate the entry_set. */ + EntrySetHeader entry_set; + R_TRY(storage.Read(entry_set_offset, std::addressof(entry_set), sizeof(EntrySetHeader))); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + /* Create the node, and find. */ + StorageNode node(entry_set_offset, entry_size, entry_set.info.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange()); + + /* Copy the data into entry. */ + const auto entry_index = node.GetIndex(); + const auto entry_offset = impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); + R_TRY(storage.Read(entry_offset, this->entry, entry_size)); + + /* Set our entry set/index. */ + this->entry_set = entry_set; + this->entry_index = entry_index; + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp b/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..76be00e1d --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_crypto_configuration.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fssystem_key_slot_cache.hpp" + +namespace ams::fssystem { + + namespace { + + constexpr inline const size_t KeySize = crypto::AesDecryptor128::KeySize; + + constexpr inline const size_t AcidSignatureKeyGenerationMax = 1; + + constexpr inline const u8 AcidSignatureKeyModulusDev[AcidSignatureKeyGenerationMax + 1][AcidSignatureKeyModulusSize] = { + { + 0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89, + 0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87, + 0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C, + 0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B, + 0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5, + 0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32, + 0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53, + 0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4, + 0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA, + 0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B, + 0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F, + 0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33, + 0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C, + 0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3, + 0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0, + 0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69 + }, + { + 0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0, + 0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E, + 0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70, + 0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A, + 0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76, + 0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD, + 0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F, + 0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE, + 0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D, + 0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7, + 0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03, + 0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62, + 0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A, + 0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2, + 0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36, + 0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B + } + }; + + constexpr inline const u8 AcidSignatureKeyModulusProd[AcidSignatureKeyGenerationMax + 1][AcidSignatureKeyModulusSize] = { + { + 0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D, + 0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50, + 0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57, + 0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20, + 0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21, + 0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2, + 0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4, + 0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE, + 0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10, + 0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53, + 0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD, + 0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08, + 0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A, + 0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA, + 0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B, + 0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD + }, + { + 0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90, + 0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5, + 0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF, + 0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E, + 0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A, + 0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D, + 0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01, + 0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8, + 0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43, + 0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6, + 0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4, + 0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C, + 0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D, + 0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83, + 0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03, + 0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03 + } + }; + + static_assert(sizeof(AcidSignatureKeyModulusProd) == sizeof(AcidSignatureKeyModulusDev)); + + constexpr inline const u8 AcidSignatureKeyPublicExponent[] = { + 0x01, 0x00, 0x01 + }; + + NcaCryptoConfiguration g_nca_crypto_configuration_dev; + NcaCryptoConfiguration g_nca_crypto_configuration_prod; + + constexpr inline s32 KeySlotCacheEntryCount = 3; + KeySlotCache g_key_slot_cache; + std::optional g_key_slot_cache_entry[KeySlotCacheEntryCount]; + + spl::AccessKey &GetNcaKekAccessKey(s32 key_type) { + static spl::AccessKey s_nca_kek_access_key_array[KeyAreaEncryptionKeyCount] = {}; + static spl::AccessKey s_nca_header_kek_access_key = {}; + static spl::AccessKey s_invalid_nca_kek_access_key = {}; + + if (key_type > static_cast(KeyType::NcaHeaderKey) || IsInvalidKeyTypeValue(key_type)) { + return s_invalid_nca_kek_access_key; + } else if (key_type == static_cast(KeyType::NcaHeaderKey)) { + return s_nca_header_kek_access_key; + } else { + return s_nca_kek_access_key_array[key_type]; + } + } + + void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type, const NcaCryptoConfiguration &cfg) { + R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, GetNcaKekAccessKey(key_type), src, src_size)); + } + + void DecryptAesCtr(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + std::unique_ptr accessor; + + R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { + R_CATCH(fs::ResultTargetNotFound) { + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type)); + R_ABORT_UNLESS(spl::LoadAesKey(accessor->GetKeySlotIndex(), GetNcaKekAccessKey(key_type), enc_key, enc_key_size)); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size)); + } + + void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, s32 key_type, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) { + std::unique_ptr accessor; + + key_type = static_cast(KeyType::NcaExternalKey); + + R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) { + R_CATCH(fs::ResultTargetNotFound) { + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type)); + + spl::AccessKey access_key; + AMS_ABORT_UNLESS(enc_key_size == sizeof(access_key)); + std::memcpy(std::addressof(access_key), enc_key, sizeof(access_key)); + R_ABORT_UNLESS(spl::LoadPreparedAesKey(accessor->GetKeySlotIndex(), access_key)); + } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + + R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size)); + } + + } + + const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod) { + /* Decide which configuration to use. */ + NcaCryptoConfiguration *cfg = prod ? std::addressof(g_nca_crypto_configuration_prod) : std::addressof(g_nca_crypto_configuration_dev); + std::memcpy(cfg, fssrv::GetDefaultNcaCryptoConfiguration(prod), sizeof(NcaCryptoConfiguration)); + + /* Set the key generation functions. */ + cfg->generate_key = GenerateNcaKey; + cfg->decrypt_aes_ctr = DecryptAesCtr; + cfg->decrypt_aes_ctr_external = DecryptAesCtrForPreparedKey; + cfg->is_plaintext_header_available = !prod; + + return cfg; + } + + void SetUpKekAccessKeys(bool prod) { + /* Get the crypto configuration. */ + const NcaCryptoConfiguration *nca_crypto_cfg = GetNcaCryptoConfiguration(prod); + + /* Setup the nca keys. */ + { + constexpr s32 Option = 0; + + /* Setup the key area encryption keys. */ + for (u8 i = 0; i < NcaCryptoConfiguration::KeyGenerationMax; ++i) { + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(0, i))), nca_crypto_cfg->key_area_encryption_key_source[0], KeySize, i, Option); + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(1, i))), nca_crypto_cfg->key_area_encryption_key_source[1], KeySize, i, Option); + spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(2, i))), nca_crypto_cfg->key_area_encryption_key_source[2], KeySize, i, Option); + } + + /* Setup the header encryption key. */ + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast(KeyType::NcaHeaderKey))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option)); + } + + /* TODO FS-REIMPL: Save stuff. */ + + /* Setup the keyslot cache. */ + for (s32 i = 0; i < KeySlotCacheEntryCount; i++) { + s32 slot_index; + R_ABORT_UNLESS(spl::AllocateAesKeySlot(std::addressof(slot_index))); + g_key_slot_cache_entry[i].emplace(slot_index); + g_key_slot_cache.AddEntry(std::addressof(g_key_slot_cache_entry[i].value())); + } + } + + void InvalidateHardwareAesKey() { + constexpr u8 InvalidKey[KeySize] = {}; + for (s32 i = 0; i < KeySlotCacheEntryCount; ++i) { + std::unique_ptr accessor; + R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), InvalidKey, KeySize, -1 - i)); + } + } + + const u8 *GetAcidSignatureKeyModulus(bool prod, size_t key_generation) { + AMS_ASSERT(key_generation <= AcidSignatureKeyGenerationMax); + const size_t used_keygen = (key_generation % (AcidSignatureKeyGenerationMax + 1)); + return prod ? AcidSignatureKeyModulusProd[used_keygen] : AcidSignatureKeyModulusDev[used_keygen]; + } + + const u8 *GetAcidSignatureKeyPublicExponent() { + return AcidSignatureKeyPublicExponent; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp b/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp new file mode 100644 index 000000000..31011c0c1 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + /* TODO: All of this should really be inside fs process, but ams.mitm wants it to. */ + /* How should we handle this? */ + + namespace { + + /* Official FS has a 4.5 MB exp heap, a 6 MB buffer pool, an 8 MB device buffer manager heap, and a 14 MB buffer manager heap. */ + /* We don't need so much memory for ams.mitm (as we're servicing a much more limited context). */ + /* We'll give ourselves a 2.5 MB exp heap, a 2 MB buffer pool, a 2 MB device buffer manager heap, and a 2 MB buffer manager heap. */ + /* These numbers match signed-system-partition safe FS. */ + constexpr size_t ExpHeapSize = 2_MB + 512_KB; + constexpr size_t BufferPoolSize = 2_MB; + constexpr size_t DeviceBufferSize = 2_MB; + constexpr size_t BufferManagerHeapSize = 2_MB; + + constexpr size_t MaxCacheCount = 1024; + constexpr size_t BlockSize = 16_KB; + + alignas(os::MemoryPageSize) u8 g_exp_heap_buffer[ExpHeapSize]; + lmem::HeapHandle g_exp_heap_handle = nullptr; + fssrv::PeakCheckableMemoryResourceFromExpHeap g_exp_allocator(ExpHeapSize); + + void InitializeExpHeap() { + if (g_exp_heap_handle == nullptr) { + g_exp_heap_handle = lmem::CreateExpHeap(g_exp_heap_buffer, ExpHeapSize, lmem::CreateOption_ThreadSafe); + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + g_exp_allocator.SetHeapHandle(g_exp_heap_handle); + } + } + + void *AllocateForFileSystemProxy(size_t size) { + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + + auto scoped_lock = g_exp_allocator.GetScopedLock(); + + void *p = lmem::AllocateFromExpHeap(g_exp_heap_handle, size); + g_exp_allocator.OnAllocate(p, size); + return p; + } + + void DeallocateForFileSystemProxy(void *p, size_t size) { + AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr); + + auto scoped_lock = g_exp_allocator.GetScopedLock(); + + g_exp_allocator.OnDeallocate(p, size); + lmem::FreeToExpHeap(g_exp_heap_handle, p); + } + + alignas(os::MemoryPageSize) u8 g_device_buffer[DeviceBufferSize]; + + alignas(os::MemoryPageSize) u8 g_buffer_pool[BufferPoolSize]; + TYPED_STORAGE(mem::StandardAllocator) g_buffer_allocator; + TYPED_STORAGE(fssrv::MemoryResourceFromStandardAllocator) g_allocator; + + /* TODO: Nintendo uses os::SetMemoryHeapSize (svc::SetHeapSize) and os::AllocateMemoryBlock for the BufferManager heap. */ + /* It's unclear how we should handle this in ams.mitm (especially hoping to reuse some logic for fs reimpl). */ + /* Should we be doing the same(?) */ + TYPED_STORAGE(fssystem::FileSystemBufferManager) g_buffer_manager; + alignas(os::MemoryPageSize) u8 g_buffer_manager_heap[BufferManagerHeapSize]; + + /* FileSystem creators. */ + TYPED_STORAGE(fssrv::fscreator::RomFileSystemCreator) g_rom_fs_creator; + TYPED_STORAGE(fssrv::fscreator::PartitionFileSystemCreator) g_partition_fs_creator; + TYPED_STORAGE(fssrv::fscreator::StorageOnNcaCreator) g_storage_on_nca_creator; + + fssrv::fscreator::FileSystemCreatorInterfaces g_fs_creator_interfaces = {}; + + } + + void InitializeForFileSystemProxy() { + /* TODO FS-REIMPL: fssystem::RegisterServiceContext */ + + /* TODO FS-REIMPL: spl::InitializeForFs(); */ + + /* Determine whether we're prod or dev. */ + bool is_prod = !spl::IsDevelopment(); + bool is_development_function_enabled = spl::IsDevelopmentFunctionEnabled(); + + /* Setup our crypto configuration. */ + SetUpKekAccessKeys(is_prod); + + /* Setup our heap. */ + InitializeExpHeap(); + + /* Initialize buffer allocator. */ + new (GetPointer(g_buffer_allocator)) mem::StandardAllocator(g_buffer_pool, BufferPoolSize); + new (GetPointer(g_allocator)) fssrv::MemoryResourceFromStandardAllocator(GetPointer(g_buffer_allocator)); + + /* Set allocators. */ + fs::SetAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + fssystem::InitializeAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy); + + /* Initialize the buffer manager. */ + /* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */ + new (GetPointer(g_buffer_manager)) fssystem::FileSystemBufferManager; + GetReference(g_buffer_manager).Initialize(MaxCacheCount, reinterpret_cast(g_buffer_manager_heap), BufferManagerHeapSize, BlockSize); + + /* TODO FS-REIMPL: Memory Report Creators, fssrv::SetMemoryReportCreator */ + + /* TODO FS-REIMPL: Create Pooled Threads, fssystem::RegisterThreadPool. */ + + /* Initialize fs creators. */ + new (GetPointer(g_rom_fs_creator)) fssrv::fscreator::RomFileSystemCreator(GetPointer(g_allocator)); + new (GetPointer(g_partition_fs_creator)) fssrv::fscreator::PartitionFileSystemCreator; + new (GetPointer(g_storage_on_nca_creator)) fssrv::fscreator::StorageOnNcaCreator(GetPointer(g_allocator), *GetNcaCryptoConfiguration(is_prod), is_prod, GetPointer(g_buffer_manager)); + + /* TODO FS-REIMPL: Initialize other creators. */ + + g_fs_creator_interfaces = { + .rom_fs_creator = GetPointer(g_rom_fs_creator), + .partition_fs_creator = GetPointer(g_partition_fs_creator), + .storage_on_nca_creator = GetPointer(g_storage_on_nca_creator), + }; + + /* TODO FS-REIMPL: Sd Card detection, speed emulation. */ + + /* Initialize fssrv. TODO FS-REIMPL: More arguments, more actions taken. */ + fssrv::InitializeForFileSystemProxy(std::addressof(g_fs_creator_interfaces), GetPointer(g_buffer_manager), is_development_function_enabled); + + /* Disable auto-abort in fs library code. */ + /* TODO: fs::SetEnabledAutoAbort(false); */ + + /* TODO FS-REIMPL: Initialize fsp server. */ + + /* NOTE: This is done in fsp server init, normally. */ + fssystem::InitializeBufferPool(reinterpret_cast(g_device_buffer), DeviceBufferSize); + } + + const ::ams::fssrv::fscreator::FileSystemCreatorInterfaces *GetFileSystemCreatorInterfaces() { + return std::addressof(g_fs_creator_interfaces); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..ac88b7cd1 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fssystem_hierarchical_sha256_storage.hpp" + +namespace ams::fssystem { + + namespace { + + s32 Log2(s32 value) { + AMS_ASSERT(value > 0); + AMS_ASSERT(util::IsPowerOfTwo(value)); + + s32 log = 0; + while ((value >>= 1) > 0) { + ++log; + } + return log; + } + + } + + Result HierarchicalSha256Storage::Initialize(IStorage **base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size) { + /* Validate preconditions. */ + AMS_ASSERT(layer_count == LayerCount); + AMS_ASSERT(util::IsPowerOfTwo(htbs)); + AMS_ASSERT(hash_buf != nullptr); + + /* Set size tracking members. */ + this->hash_target_block_size = htbs; + this->log_size_ratio = Log2(this->hash_target_block_size / HashSize); + + /* Get the base storage size. */ + R_TRY(base_storages[2]->GetSize(std::addressof(this->base_storage_size))); + { + auto size_guard = SCOPE_GUARD { this->base_storage_size = 0; }; + R_UNLESS(this->base_storage_size <= static_cast(HashSize) << log_size_ratio << log_size_ratio, fs::ResultHierarchicalSha256BaseStorageTooLarge()); + size_guard.Cancel(); + } + + /* Set hash buffer tracking members. */ + this->base_storage = base_storages[2]; + this->hash_buffer = static_cast(hash_buf); + this->hash_buffer_size = hash_buf_size; + + /* Read the master hash. */ + u8 master_hash[HashSize]; + R_TRY(base_storages[0]->Read(0, master_hash, HashSize)); + + /* Read and validate the data being hashed. */ + s64 hash_storage_size; + R_TRY(base_storages[1]->GetSize(std::addressof(hash_storage_size))); + AMS_ASSERT(util::IsAligned(hash_storage_size, HashSize)); + AMS_ASSERT(hash_storage_size <= this->hash_target_block_size); + AMS_ASSERT(hash_storage_size <= static_cast(this->hash_buffer_size)); + + R_TRY(base_storages[1]->Read(0, this->hash_buffer, static_cast(hash_storage_size))); + + /* Calculate and verify the master hash. */ + u8 calc_hash[HashSize]; + crypto::GenerateSha256Hash(calc_hash, sizeof(calc_hash), this->hash_buffer, static_cast(hash_storage_size)); + R_UNLESS(crypto::IsSameBytes(master_hash, calc_hash, HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); + + return ResultSuccess(); + } + + Result HierarchicalSha256Storage::Read(s64 offset, void *buffer, size_t size) { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate that we have a buffer to read into. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, this->hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, this->hash_target_block_size), fs::ResultInvalidArgument()); + + /* Read the data. */ + const size_t reduced_size = static_cast(std::min(this->base_storage_size, util::AlignUp(offset + size, this->hash_target_block_size) - offset)); + R_TRY(this->base_storage->Read(offset, buffer, reduced_size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Setup tracking variables. */ + auto cur_offset = offset; + auto remaining_size = reduced_size; + while (remaining_size > 0) { + /* Generate the hash of the region we're validating. */ + u8 hash[HashSize]; + const auto cur_size = static_cast(std::min(this->hash_target_block_size, remaining_size)); + crypto::GenerateSha256Hash(hash, sizeof(hash), static_cast(buffer) + (cur_offset - offset), cur_size); + + AMS_ASSERT(static_cast(cur_offset >> this->log_size_ratio) < this->hash_buffer_size); + + /* Check the hash. */ + { + std::scoped_lock lk(this->mutex); + auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); }; + + R_UNLESS(crypto::IsSameBytes(hash, std::addressof(this->hash_buffer[cur_offset >> this->log_size_ratio]), HashSize), fs::ResultHierarchicalSha256HashVerificationFailed()); + + clear_guard.Cancel(); + } + + /* Advance. */ + cur_offset += cur_size; + remaining_size -= cur_size; + } + + return ResultSuccess(); + } + + Result HierarchicalSha256Storage::Write(s64 offset, const void *buffer, size_t size) { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate that we have a buffer to read into. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, this->hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, this->hash_target_block_size), fs::ResultInvalidArgument()); + + /* Setup tracking variables. */ + const size_t reduced_size = static_cast(std::min(this->base_storage_size, util::AlignUp(offset + size, this->hash_target_block_size) - offset)); + auto cur_offset = offset; + auto remaining_size = reduced_size; + while (remaining_size > 0) { + /* Generate the hash of the region we're validating. */ + u8 hash[HashSize]; + const auto cur_size = static_cast(std::min(this->hash_target_block_size, remaining_size)); + { + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + crypto::GenerateSha256Hash(hash, sizeof(hash), static_cast(buffer) + (cur_offset - offset), cur_size); + } + + /* Write the data. */ + R_TRY(this->base_storage->Write(cur_offset, static_cast(buffer) + (cur_offset - offset), cur_size)); + + /* Write the hash. */ + { + std::scoped_lock lk(this->mutex); + std::memcpy(std::addressof(this->hash_buffer[cur_offset >> this->log_size_ratio]), hash, HashSize); + } + + /* Advance. */ + cur_offset += cur_size; + remaining_size -= cur_size; + } + + return ResultSuccess(); + } + + Result HierarchicalSha256Storage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate preconditions. */ + R_UNLESS(util::IsAligned(offset, this->hash_target_block_size), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, this->hash_target_block_size), fs::ResultInvalidArgument()); + + /* Determine size to use. */ + const auto reduced_size = std::min(this->base_storage_size, util::AlignUp(offset + size, this->hash_target_block_size) - offset); + + /* Operate on the base storage. */ + return this->base_storage->OperateRange(dst, dst_size, op_id, offset, reduced_size, src, src_size); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp new file mode 100644 index 000000000..5ef4a0082 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_hierarchical_sha256_storage.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + class HierarchicalSha256Storage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(HierarchicalSha256Storage); + NON_MOVEABLE(HierarchicalSha256Storage); + public: + static constexpr s32 LayerCount = 3; + static constexpr size_t HashSize = crypto::Sha256Generator::HashSize; + private: + os::Mutex mutex; + IStorage *base_storage; + s64 base_storage_size; + char *hash_buffer; + size_t hash_buffer_size; + s32 hash_target_block_size; + s32 log_size_ratio; + public: + HierarchicalSha256Storage() : mutex(false) { /* ... */ } + + Result Initialize(IStorage **base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size); + + virtual Result Read(s64 offset, void *buffer, size_t size) override; + virtual Result Write(s64 offset, const void *buffer, size_t size) override; + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override; + + virtual Result GetSize(s64 *out) override { + return this->base_storage->GetSize(out); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInHierarchicalSha256StorageA(); + } + }; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..e9c33646b --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_indirect_storage.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + Result IndirectStorage::Initialize(IAllocator *allocator, fs::SubStorage table_storage) { + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + R_TRY(table_storage.Read(0, std::addressof(header), sizeof(header))); + R_TRY(header.Verify()); + + /* Determine extents. */ + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + /* Initialize. */ + return this->Initialize(allocator, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count); + } + + void IndirectStorage::Finalize() { + if (this->IsInitialized()) { + this->table.Finalize(); + for (auto i = 0; i < StorageCount; i++) { + this->data_storage[i] = fs::SubStorage(); + } + } + } + + Result IndirectStorage::GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(size >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Clear the out count. */ + R_UNLESS(out_entry_count != nullptr, fs::ResultNullptrArgument()); + *out_entry_count = 0; + + /* Succeed if there's no range. */ + R_SUCCEED_IF(size == 0); + + /* If we have an output array, we need it to be non-null. */ + R_UNLESS(out_entries != nullptr || entry_count == 0, fs::ResultNullptrArgument()); + + /* Check that our range is valid. */ + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + + /* Find the offset in our tree. */ + BucketTree::Visitor visitor; + R_TRY(this->table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && this->table.Includes(entry_offset), fs::ResultInvalidIndirectEntryOffset()); + } + + /* Prepare to loop over entries. */ + const auto end_offset = offset + static_cast(size); + s32 count = 0; + + auto cur_entry = *visitor.Get(); + while (cur_entry.GetVirtualOffset() < end_offset) { + /* Try to write the entry to the out list */ + if (entry_count != 0) { + if (count >= entry_count) { + break; + } + std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); + } + + count++; + + /* Advance. */ + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + cur_entry = *visitor.Get(); + } else { + break; + } + } + + /* Write the output count. */ + *out_entry_count = count; + return ResultSuccess(); + } + + Result IndirectStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if there's nothing to read. */ + R_SUCCEED_IF(size == 0); + + /* Ensure that we have a buffer to read to. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + R_TRY(this->OperatePerEntry(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + R_TRY(storage->Read(data_offset, reinterpret_cast(buffer) + (cur_offset - offset), static_cast(cur_size))); + return ResultSuccess(); + })); + + return ResultSuccess(); + } + + Result IndirectStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::InvalidateCache: + { + if (size > 0) { + /* Validate arguments. */ + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + if (!this->table.IsEmpty()) { + /* Invalidate our table's cache. */ + R_TRY(this->table.InvalidateCache()); + + /* Operate on our entries. */ + R_TRY(this->OperatePerEntry(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + R_TRY(storage->OperateRange(dst, dst_size, op_id, data_offset, cur_size, src, src_size)); + return ResultSuccess(); + })); + } + return ResultSuccess(); + } + return ResultSuccess(); + } + case fs::OperationId::QueryRange: + { + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + if (size > 0) { + /* Validate arguments. */ + R_UNLESS(this->table.Includes(offset, size), fs::ResultOutOfRange()); + if (!this->table.IsEmpty()) { + /* Create a new info. */ + fs::QueryRangeInfo merged_info; + merged_info.Clear(); + + /* Operate on our entries. */ + R_TRY(this->OperatePerEntry(offset, size, [=, &merged_info](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + fs::QueryRangeInfo cur_info; + R_TRY(storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, data_offset, cur_size, src, src_size)); + merged_info.Merge(cur_info); + return ResultSuccess(); + })); + + /* Write the merged info. */ + *reinterpret_cast(dst) = merged_info; + } + } + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInIndirectStorageC(); + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..89973aeca --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_integrity_romfs_storage.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + Result IntegrityRomFsStorage::Initialize(save::HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, save::HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, IBufferManager *bm) { + /* Validate preconditions. */ + AMS_ASSERT(bm != nullptr); + + /* Set master hash. */ + this->master_hash = master_hash; + this->master_hash_storage = std::make_unique(std::addressof(this->master_hash), sizeof(Hash)); + R_UNLESS(this->master_hash_storage != nullptr, fs::ResultAllocationFailureInIntegrityRomFsStorageA()); + + /* Set the master hash storage. */ + storage_info[0] = fs::SubStorage(this->master_hash_storage.get(), 0, sizeof(Hash)); + + /* Set buffers. */ + for (size_t i = 0; i < util::size(this->buffers.buffers); ++i) { + this->buffers.buffers[i] = bm; + } + + /* Initialize our integrity storage. */ + return this->integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(this->buffers), std::addressof(this->mutex), fs::StorageType_RomFs); + } + + void IntegrityRomFsStorage::Finalize() { + this->integrity_storage.Finalize(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp b/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp new file mode 100644 index 000000000..89cad629e --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_key_slot_cache.hpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + class KeySlotCacheAccessor : public ::ams::fs::impl::Newable { + NON_COPYABLE(KeySlotCacheAccessor); + NON_MOVEABLE(KeySlotCacheAccessor); + private: + std::unique_lock lk; + const s32 slot_index; + public: + KeySlotCacheAccessor(s32 idx, std::unique_lock &&l) : lk(std::move(l)), slot_index(idx) { /* ... */ } + + s32 GetKeySlotIndex() const { return this->slot_index; } + }; + + class KeySlotCacheEntry : public util::IntrusiveListBaseNode { + NON_COPYABLE(KeySlotCacheEntry); + NON_MOVEABLE(KeySlotCacheEntry); + public: + static constexpr size_t KeySize = crypto::AesDecryptor128::KeySize; + private: + const s32 slot_index; + u8 key1[KeySize]; + s32 key2; + public: + explicit KeySlotCacheEntry(s32 idx) : slot_index(idx), key2(-1) { + std::memset(this->key1, 0, sizeof(this->key1)); + } + + bool Contains(const void *key, size_t key_size, s32 key2) const { + AMS_ASSERT(key_size == KeySize); + return key2 == this->key2 && std::memcmp(this->key1, key, KeySize) == 0; + } + + s32 GetKeySlotIndex() const { return this->slot_index; } + + void SetKey(const void *key, size_t key_size, s32 key2) { + AMS_ASSERT(key_size == KeySize); + std::memcpy(this->key1, key, key_size); + this->key2 = key2; + } + }; + + class KeySlotCache { + NON_COPYABLE(KeySlotCache); + NON_MOVEABLE(KeySlotCache); + private: + using KeySlotCacheEntryList = util::IntrusiveListBaseTraits::ListType; + private: + os::Mutex mutex; + KeySlotCacheEntryList high_priority_mru_list; + KeySlotCacheEntryList low_priority_mru_list; + public: + constexpr KeySlotCache() : mutex(false), high_priority_mru_list(), low_priority_mru_list() { /* ... */ } + + Result AllocateHighPriority(std::unique_ptr *out, const void *key, size_t key_size, s32 key2) { + return this->AllocateFromLru(out, this->high_priority_mru_list, key, key_size, key2); + } + + Result AllocateLowPriority(std::unique_ptr *out, const void *key, size_t key_size, s32 key2) { + return this->AllocateFromLru(out, this->high_priority_mru_list, key, key_size, key2); + } + + Result Find(std::unique_ptr *out, const void *key, size_t key_size, s32 key2) { + std::unique_lock lk(this->mutex); + + KeySlotCacheEntryList *lists[2] = { std::addressof(this->high_priority_mru_list), std::addressof(this->low_priority_mru_list) }; + for (auto list : lists) { + for (auto it = list->begin(); it != list->end(); ++it) { + if (it->Contains(key, key_size, key2)) { + std::unique_ptr accessor = std::make_unique(it->GetKeySlotIndex(), std::move(lk)); + R_UNLESS(accessor != nullptr, fs::ResultAllocationFailure()); + + *out = std::move(accessor); + + this->UpdateMru(list, it); + return ResultSuccess(); + } + } + } + + return fs::ResultTargetNotFound(); + } + + void AddEntry(KeySlotCacheEntry *entry) { + std::unique_lock lk(this->mutex); + this->low_priority_mru_list.push_front(*entry); + } + private: + Result AllocateFromLru(std::unique_ptr *out, KeySlotCacheEntryList &dst_list, const void *key, size_t key_size, s32 key2) { + std::unique_lock lk(this->mutex); + + KeySlotCacheEntryList &src_list = this->low_priority_mru_list.empty() ? this->high_priority_mru_list : this->low_priority_mru_list; + AMS_ASSERT(!src_list.empty()); + + auto it = src_list.rbegin(); + std::unique_ptr accessor = std::make_unique(it->GetKeySlotIndex(), std::move(lk)); + *out = std::move(accessor); + + it->SetKey(key, key_size, key2); + + auto *entry = std::addressof(*it); + src_list.pop_back(); + dst_list.push_front(*entry); + + return ResultSuccess(); + } + + void UpdateMru(KeySlotCacheEntryList *list, KeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + list->erase(it); + list->push_front(*entry); + } + }; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp b/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp new file mode 100644 index 000000000..f90f0facd --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_lru_list_cache.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::fssystem { + + template + class LruListCache { + NON_COPYABLE(LruListCache); + NON_MOVEABLE(LruListCache); + public: + class Node : public ::ams::fs::impl::Newable { + NON_COPYABLE(Node); + NON_MOVEABLE(Node); + public: + Key key; + Value value; + util::IntrusiveListNode mru_list_node; + public: + explicit Node(const Value &value) : value(value) { /* ... */ } + }; + private: + using MruList = typename util::IntrusiveListMemberTraits<&Node::mru_list_node>::ListType; + private: + MruList mru_list; + public: + constexpr LruListCache() : mru_list() { /* ... */ } + + bool FindValueAndUpdateMru(Value *out, const Key &key) { + for (auto it = this->mru_list.begin(); it != this->mru_list.end(); ++it) { + if (it->key == key) { + *out = it->value; + + this->mru_list.erase(it); + this->mru_list.push_front(*it); + + return true; + } + } + + return false; + } + + std::unique_ptr PopLruNode() { + AMS_ABORT_UNLESS(!this->mru_list.empty()); + Node *lru = std::addressof(*this->mru_list.rbegin()); + this->mru_list.pop_back(); + + return std::unique_ptr(lru); + } + + void PushMruNode(std::unique_ptr &&node, const Key &key) { + node->key = key; + this->mru_list.push_front(*node); + node.release(); + } + + void DeleteAllNodes() { + while (!this->mru_list.empty()) { + Node *lru = std::addressof(*this->mru_list.rbegin()); + this->mru_list.erase(this->mru_list.iterator_to(*lru)); + delete lru; + } + } + + size_t GetSize() const { + return this->mru_list.size(); + } + + bool IsEmpty() const { + return this->mru_list.empty(); + } + }; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..171ac9317 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_file_system_driver.cpp @@ -0,0 +1,1053 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fssystem_read_only_block_cache_storage.hpp" +#include "fssystem_hierarchical_sha256_storage.hpp" + +namespace ams::fssystem { + + namespace { + + constexpr inline s32 AesCtrExTableCacheBlockSize = AesCtrCounterExtendedStorage::NodeSize; + constexpr inline s32 AesCtrExTableCacheCount = 8; + constexpr inline s32 IndirectTableCacheBlockSize = IndirectStorage::NodeSize; + constexpr inline s32 IndirectTableCacheCount = 8; + constexpr inline s32 IndirectDataCacheBlockSize = 32_KB; + constexpr inline s32 IndirectDataCacheCount = 16; + constexpr inline s32 SparseTableCacheBlockSize = SparseStorage::NodeSize; + constexpr inline s32 SparseTableCacheCount = 4; + + class BufferHolder { + NON_COPYABLE(BufferHolder); + private: + MemoryResource *allocator; + char *buffer; + size_t buffer_size; + public: + BufferHolder() : allocator(), buffer(), buffer_size() { /* ... */ } + BufferHolder(MemoryResource *a, size_t sz) : allocator(a), buffer(static_cast(a->Allocate(sz))), buffer_size(sz) { /* ... */ } + ~BufferHolder() { + if (this->buffer != nullptr) { + this->allocator->Deallocate(this->buffer, this->buffer_size); + this->buffer = nullptr; + } + } + + BufferHolder(BufferHolder &&rhs) : allocator(rhs.allocator), buffer(rhs.buffer), buffer_size(rhs.buffer_size) { + rhs.buffer = nullptr; + } + + BufferHolder &operator=(BufferHolder &&rhs) { + if (this != std::addressof(rhs)) { + AMS_ASSERT(this->buffer == nullptr); + this->allocator = rhs.allocator; + this->buffer = rhs.buffer; + this->buffer_size = rhs.buffer_size; + + rhs.buffer = nullptr; + } + return *this; + } + + bool IsValid() const { return this->buffer != nullptr; } + char *Get() const { return this->buffer; } + size_t GetSize() const { return this->buffer_size; } + }; + + template + class DerivedStorageHolderImpl; + + template + class DerivedStorageHolderImpl> : public Base { + NON_COPYABLE(DerivedStorageHolderImpl); + public: + using StoragePointer = std::unique_ptr; + + template + using IndexedStoragePointer = StoragePointer; + private: + std::shared_ptr nca_reader; + std::array storages; + private: + + template + void SetImpl(IndexedStoragePointer &&ptr) { + static_assert(N < sizeof...(Is)); + this->storages[N] = std::move(ptr); + } + public: + DerivedStorageHolderImpl() : Base(), nca_reader(), storages() { /* ... */ } + explicit DerivedStorageHolderImpl(std::shared_ptr nr) : Base(), nca_reader(nr), storages() { /* ... */ } + + #define DEFINE_CONSTRUCTORS(n) \ + template \ + explicit DerivedStorageHolderImpl(AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS##n (T, t)) : Base(AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS##n (T, t)), nca_reader(), storages() { /* ... */ } \ + template \ + explicit DerivedStorageHolderImpl(AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS##n (T, t), std::shared_ptr nr) : Base(AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS##n (T, t)), nca_reader(nr), storages() { /* ... */ } + + AMS_UTIL_VARIADIC_INVOKE_MACRO(DEFINE_CONSTRUCTORS) + + #undef DEFINE_CONSTRUCTORS + + void Set(IndexedStoragePointer &&... ptrs) { + (this->SetImpl(std::forward>(ptrs)), ...); + } + }; + + template + using DerivedStorageHolder = DerivedStorageHolderImpl>; + + template + class DerivedStorageHolderWithBuffer : public DerivedStorageHolder { + NON_COPYABLE(DerivedStorageHolderWithBuffer); + private: + using BaseHolder = DerivedStorageHolder; + private: + BufferHolder buffer; + public: + DerivedStorageHolderWithBuffer() : BaseHolder(), buffer() { /* ... */ } + + template + DerivedStorageHolderWithBuffer(Args &&... args) : BaseHolder(std::forward(args)...), buffer() { /* ... */ } + + using BaseHolder::Set; + + void Set(BufferHolder &&buf) { + this->buffer = std::move(buf); + } + }; + + class AesCtrStorageExternal : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(AesCtrStorageExternal); + NON_MOVEABLE(AesCtrStorageExternal); + public: + static constexpr size_t BlockSize = crypto::Aes128CtrEncryptor::BlockSize; + static constexpr size_t KeySize = crypto::Aes128CtrEncryptor::KeySize; + static constexpr size_t IvSize = crypto::Aes128CtrEncryptor::IvSize; + private: + IStorage * const base_storage; + u8 iv[IvSize]; + DecryptAesCtrFunction decrypt_function; + s32 key_index; + u8 encrypted_key[KeySize]; + public: + AesCtrStorageExternal(fs::IStorage *bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx) : base_storage(bs), decrypt_function(df), key_index(kidx) { + AMS_ASSERT(bs != nullptr); + AMS_ASSERT(enc_key_size == KeySize); + AMS_ASSERT(iv != nullptr); + AMS_ASSERT(iv_size == IvSize); + + std::memcpy(this->iv, iv, IvSize); + std::memcpy(this->encrypted_key, enc_key, enc_key_size); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + /* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument()); + R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument()); + + /* Read the data. */ + R_TRY(this->base_storage->Read(offset, buffer, size)); + + /* Temporarily increase our thread priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Allocate a pooled buffer for decryption. */ + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(size, BlockSize); + AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize); + + /* Setup the counter. */ + u8 ctr[IvSize]; + std::memcpy(ctr, this->iv, IvSize); + AddCounter(ctr, IvSize, offset / BlockSize); + + /* Setup tracking. */ + size_t remaining_size = size; + s64 cur_offset = 0; + + while (remaining_size > 0) { + /* Get the current size to process. */ + size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size); + char *dst = static_cast(buffer) + cur_offset; + + /* Decrypt into the temporary buffer */ + this->decrypt_function(pooled_buffer.GetBuffer(), cur_size, this->key_index, this->encrypted_key, KeySize, ctr, IvSize, dst, cur_size); + + /* Copy to the destination. */ + std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size); + + /* Update tracking. */ + cur_offset += cur_size; + remaining_size -= cur_size; + + if (remaining_size > 0) { + AddCounter(ctr, IvSize, cur_size / BlockSize); + } + } + + return ResultSuccess(); + } + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::QueryRange: + { + /* Validate that we have an output range info. */ + R_UNLESS(dst != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize()); + + /* Operate on our base storage. */ + R_TRY(this->base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + + /* Add in new flags. */ + fs::QueryRangeInfo new_info; + new_info.Clear(); + new_info.aes_ctr_key_type = static_cast(this->key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes); + } + default: + { + /* Operate on our base storage. */ + R_TRY(this->base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + return ResultSuccess(); + } + } + } + + virtual Result GetSize(s64 *out) override { + return this->base_storage->GetSize(out); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperationInAesCtrStorageExternalA(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInAesCtrStorageExternalB(); + } + }; + + template + class SwitchStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(SwitchStorage); + NON_MOVEABLE(SwitchStorage); + private: + std::unique_ptr true_storage; + std::unique_ptr false_storage; + F truth_function; + private: + ALWAYS_INLINE std::unique_ptr &SelectStorage() { + return this->truth_function() ? this->true_storage : this->false_storage; + } + public: + SwitchStorage(std::unique_ptr &&t, std::unique_ptr &&f, F func) : true_storage(std::move(t)), false_storage(std::move(f)), truth_function(func) { /* ... */ } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + return this->SelectStorage()->Read(offset, buffer, size); + } + + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + switch (op_id) { + case fs::OperationId::InvalidateCache: + { + R_TRY(this->true_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + R_TRY(this->false_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + return ResultSuccess(); + } + case fs::OperationId::QueryRange: + { + R_TRY(this->SelectStorage()->OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInSwitchStorageA(); + } + } + + virtual Result GetSize(s64 *out) override { + return this->SelectStorage()->GetSize(out); + } + + virtual Result Flush() override { + return this->SelectStorage()->Flush(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return this->SelectStorage()->Write(offset, buffer, size); + } + + virtual Result SetSize(s64 size) override { + return this->SelectStorage()->SetSize(size); + } + }; + + inline s64 GetFsOffset(const NcaReader &reader, s32 fs_index) { + return static_cast(reader.GetFsOffset(fs_index)); + } + + inline s64 GetFsEndOffset(const NcaReader &reader, s32 fs_index) { + return static_cast(reader.GetFsEndOffset(fs_index)); + } + + inline void MakeAesXtsIv(void *ctr, s64 base_offset) { + util::StoreBigEndian(static_cast(ctr) + 1, base_offset / NcaHeader::XtsBlockSize); + } + + inline bool IsUsingHardwareAesCtrForSpeedEmulation() { + auto mode = fssystem::SpeedEmulationConfiguration::GetSpeedEmulationMode(); + return mode == fs::SpeedEmulationMode::None || mode == fs::SpeedEmulationMode::Slower; + } + + using Sha256DataRegion = NcaFsHeader::Region; + using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; + using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; + + inline const Sha256DataRegion &GetSha256DataRegion(const NcaFsHeader::HashData &hash_data) { + return hash_data.hierarchical_sha256_data.hash_layer_region[1]; + } + + inline const IntegrityDataInfo &GetIntegrityDataInfo(const NcaFsHeader::HashData &hash_data) { + return hash_data.integrity_meta_info.level_hash_info.info[hash_data.integrity_meta_info.level_hash_info.max_layers - 2]; + } + + } + + Result NcaFileSystemDriver::OpenRawStorage(std::shared_ptr *out, s32 fs_index) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + AMS_ASSERT(this->reader != nullptr); + + /* Get storage extents. */ + const auto storage_offset = GetFsOffset(*this->reader, fs_index); + const auto storage_size = GetFsEndOffset(*this->reader, fs_index) - storage_offset; + R_UNLESS(storage_size > 0, fs::ResultInvalidNcaHeader()); + + /* Allocate a substorage. */ + *out = AllocateShared>(this->reader->GetBodyStorage(), storage_offset, storage_size, this->reader); + R_UNLESS(*out != nullptr, fs::ResultAllocationFailureInAllocateShared()); + + return ResultSuccess(); + } + + Result NcaFileSystemDriver::OpenStorage(std::shared_ptr *out, NcaFsHeaderReader *out_header_reader, s32 fs_index) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out_header_reader != nullptr); + AMS_ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + /* Open a reader with the appropriate option. */ + StorageOption option(out_header_reader, fs_index); + R_TRY(this->OpenStorage(out, std::addressof(option))); + + return ResultSuccess(); + } + + Result NcaFileSystemDriver::OpenStorage(std::shared_ptr *out, StorageOption *option) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + AMS_ASSERT(this->reader != nullptr); + + /* Get and validate fs index. */ + const auto fs_index = option->GetFsIndex(); + R_UNLESS(this->reader->HasFsInfo(fs_index), fs::ResultPartitionNotFound()); + + /* Initialize a reader for the fs header. */ + auto &header_reader = option->GetHeaderReader(); + R_TRY(header_reader.Initialize(*this->reader, fs_index)); + + /* Create the storage. */ + std::unique_ptr storage; + { + BaseStorage base_storage; + R_TRY(this->CreateBaseStorage(std::addressof(base_storage), option)); + R_TRY(this->CreateDecryptableStorage(std::addressof(storage), option, std::addressof(base_storage))); + } + R_TRY(this->CreateIndirectStorage(std::addressof(storage), option, std::move(storage))); + R_TRY(this->CreateVerificationStorage(std::addressof(storage), std::move(storage), std::addressof(header_reader))); + + /* Set the output. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::OpenDecryptableStorage(std::shared_ptr *out, StorageOption *option, bool indirect_needed) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + AMS_ASSERT(this->reader != nullptr); + + /* Get and validate fs index. */ + const auto fs_index = option->GetFsIndex(); + R_UNLESS(this->reader->HasFsInfo(fs_index), fs::ResultPartitionNotFound()); + + /* Initialize a reader for the fs header. */ + auto &header_reader = option->GetHeaderReader(); + if (!header_reader.IsInitialized()) { + R_TRY(header_reader.Initialize(*this->reader, fs_index)); + } + + /* Create the storage. */ + std::unique_ptr storage; + { + BaseStorage base_storage; + R_TRY(this->CreateBaseStorage(std::addressof(base_storage), option)); + R_TRY(this->CreateDecryptableStorage(std::addressof(storage), option, std::addressof(base_storage))); + } + + /* Set the data storage. */ + { + const auto &patch_info = header_reader.GetPatchInfo(); + s64 data_storage_size = 0; + + if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { + data_storage_size = patch_info.aes_ctr_ex_offset; + } else { + switch (header_reader.GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + { + const auto ®ion = GetSha256DataRegion(header_reader.GetHashData()); + data_storage_size = region.offset + region.size; + } + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + { + const auto &info = GetIntegrityDataInfo(header_reader.GetHashData()); + data_storage_size = info.offset + info.size; + } + break; + default: + return fs::ResultInvalidNcaFsHeaderHashType(); + } + + data_storage_size = util::AlignUp(data_storage_size, NcaHeader::XtsBlockSize); + } + + /* Set the data storage in option. */ + option->SetDataStorage(storage.get(), data_storage_size); + } + + /* Create the indirect storage if needed. */ + if (indirect_needed) { + R_TRY(this->CreateIndirectStorage(std::addressof(storage), option, std::move(storage))); + } + + /* Set the output. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateBaseStorage(BaseStorage *out, StorageOption *option) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + + /* Get the header reader. */ + const auto fs_index = option->GetFsIndex(); + const auto &header_reader = option->GetHeaderReader(); + + /* Get storage extents. */ + const auto storage_offset = GetFsOffset(*this->reader, fs_index); + const auto storage_size = GetFsEndOffset(*this->reader, fs_index) - storage_offset; + R_UNLESS(storage_size > 0, fs::ResultInvalidNcaHeader()); + + /* Set up the sparse storage if we need to, otherwise use body storage directly. */ + if (header_reader.ExistsSparseLayer()) { + const auto &sparse_info = header_reader.GetSparseInfo(); + + /* Read and verify the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header, sizeof(header)); + R_TRY(header.Verify()); + + /* Create a new holder for the storages. */ + std::unique_ptr storage = std::make_unique>(this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* If there are no entries, there's nothing to actually do. */ + if (header.entry_count == 0) { + storage->Initialize(storage_size); + } else { + /* Prepare to create the decryptable storage. */ + const auto raw_storage = this->reader->GetBodyStorage(); + const auto raw_storage_offset = sparse_info.physical_offset; + const auto raw_storage_size = sparse_info.GetPhysicalSize(); + + /* Validate that we're within range. */ + s64 body_storage_size = 0; + R_TRY(raw_storage->GetSize(std::addressof(body_storage_size))); + R_UNLESS(raw_storage_offset + raw_storage_size <= body_storage_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Create the decryptable storage. */ + std::unique_ptr decryptable_storage; + { + BaseStorage base_storage(raw_storage, raw_storage_offset, raw_storage_size); + base_storage.SetStorageOffset(raw_storage_offset); + base_storage.SetAesCtrUpperIv(sparse_info.MakeAesCtrUpperIv(header_reader.GetAesCtrUpperIv())); + R_TRY(this->CreateAesCtrStorage(std::addressof(decryptable_storage), std::addressof(base_storage))); + } + + /* Create the table storage. */ + std::unique_ptr table_storage = std::make_unique(); + R_UNLESS(table_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the table storage. */ + R_TRY(table_storage->Initialize(fs::SubStorage(decryptable_storage.get(), 0, raw_storage_size), this->buffer_manager, SparseTableCacheBlockSize, SparseTableCacheCount)); + + /* Determine storage extents. */ + const auto node_offset = sparse_info.bucket.offset; + const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_offset = node_offset + node_size; + const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); + + /* Initialize the storage. */ + R_TRY(storage->Initialize(this->allocator, fs::SubStorage(table_storage.get(), node_offset, node_size), fs::SubStorage(table_storage.get(), entry_offset, entry_size), header.entry_count)); + + /* Set the data/decryptable storage. */ + storage->SetDataStorage(raw_storage, raw_storage_offset, node_offset); + storage->Set(std::move(decryptable_storage), std::move(table_storage)); + } + + /* Set the sparse storage. */ + option->SetSparseStorage(storage.get()); + + /* Set the out storage. */ + out->SetStorage(std::move(storage)); + } else { + /* Validate that we're within range. */ + s64 body_storage_size; + R_TRY(this->reader->GetBodyStorage()->GetSize(std::addressof(body_storage_size))); + R_UNLESS(storage_offset + storage_size <= body_storage_size, fs::ResultNcaBaseStorageOutOfRangeB()); + + /* Set the out storage. */ + out->SetStorage(this->reader->GetBodyStorage(), storage_offset, storage_size); + } + + /* Set the crypto variables. */ + out->SetStorageOffset(storage_offset); + out->SetAesCtrUpperIv(header_reader.GetAesCtrUpperIv()); + + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateDecryptableStorage(std::unique_ptr *out, StorageOption *option, BaseStorage *base_storage) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Get the header reader. */ + const auto &header_reader = option->GetHeaderReader(); + + /* Create the appropriate storage for the encryption type. */ + switch (header_reader.GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + *out = base_storage->MakeStorage(); + R_UNLESS(*out != nullptr, fs::ResultAllocationFailureInNew()); + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY(this->CreateAesXtsStorage(out, base_storage)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(out, base_storage)); + break; + case NcaFsHeader::EncryptionType::AesCtrEx: + R_TRY(this->CreateAesCtrExStorage(out, option, base_storage)); + break; + default: + return fs::ResultInvalidNcaFsHeaderEncryptionType(); + } + + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateAesXtsStorage(std::unique_ptr *out, BaseStorage *base_storage) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Create the iv. */ + u8 iv[AesXtsStorage::IvSize] = {}; + MakeAesXtsIv(iv, base_storage->GetStorageOffset()); + + /* Allocate a new raw storage. */ + std::unique_ptr raw_storage = base_storage->MakeStorage(); + R_UNLESS(raw_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Make the aes xts storage. */ + const auto *key1 = this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); + const auto *key2 = this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); + std::unique_ptr xts_storage = std::make_unique(raw_storage.get(), key1, key2, AesXtsStorage::KeySize, iv, AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(xts_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Make the out storage. */ + std::unique_ptr storage = std::make_unique, 2>>(xts_storage.get(), this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Set the substorages. */ + storage->Set(std::move(raw_storage), std::move(xts_storage)); + + /* Set the output. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateAesCtrStorage(std::unique_ptr *out, BaseStorage *base_storage) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Create the iv. */ + u8 iv[AesCtrStorage::IvSize] = {}; + AesCtrStorage::MakeIv(iv, sizeof(iv), base_storage->GetAesCtrUpperIv().value, base_storage->GetStorageOffset()); + + /* Create the raw storage. */ + std::unique_ptr raw_storage = base_storage->MakeStorage(); + + /* Create the decrypt storage. */ + const bool has_external_key = reader->HasExternalDecryptionKey(); + std::unique_ptr decrypt_storage; + if (has_external_key) { + decrypt_storage = std::make_unique(raw_storage.get(), this->reader->GetExternalDecryptionKey(), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, this->reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1); + R_UNLESS(decrypt_storage != nullptr, fs::ResultAllocationFailureInNew()); + } else { + /* Check if we have a hardware key. */ + const bool has_hardware_key = this->reader->HasInternalDecryptionKeyForAesHardwareSpeedEmulation(); + + /* Create the software decryption storage. */ + std::unique_ptr aes_ctr_sw_storage = std::make_unique(raw_storage.get(), this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), AesCtrStorage::KeySize, iv, AesCtrStorage::IvSize); + R_UNLESS(aes_ctr_sw_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* If we have a hardware key and should use it, make the hardware decryption storage. */ + if (has_hardware_key && !this->reader->IsSoftwareAesPrioritized()) { + std::unique_ptr aes_ctr_hw_storage = std::make_unique(raw_storage.get(), this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorageExternal::KeySize, iv, AesCtrStorageExternal::IvSize, this->reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(this->reader->GetKeyIndex(), this->reader->GetKeyGeneration())); + R_UNLESS(aes_ctr_hw_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Create the selection storage. */ + decrypt_storage = std::make_unique>(std::move(aes_ctr_hw_storage), std::move(aes_ctr_sw_storage), IsUsingHardwareAesCtrForSpeedEmulation); + R_UNLESS(decrypt_storage != nullptr, fs::ResultAllocationFailureInNew()); + } else { + /* Otherwise, just use the software decryption storage. */ + decrypt_storage = std::move(aes_ctr_sw_storage); + } + } + + /* Create the storage holder. */ + std::unique_ptr storage = std::make_unique, 2>>(decrypt_storage.get(), this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Set the storage holder's storages. */ + storage->Set(std::move(raw_storage), std::move(decrypt_storage)); + + /* Set the out storage. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateAesCtrExStorage(std::unique_ptr *out, StorageOption *option, BaseStorage *base_storage) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Check if indirection is needed. */ + const auto &header_reader = option->GetHeaderReader(); + const auto &patch_info = header_reader.GetPatchInfo(); + + /* Read the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header, sizeof(header)); + R_TRY(header.Verify()); + + /* Validate patch info extents. */ + R_UNLESS(patch_info.indirect_size > 0, fs::ResultInvalidNcaPatchInfoIndirectSize()); + R_UNLESS(patch_info.aes_ctr_ex_size > 0, fs::ResultInvalidNcaPatchInfoAesCtrExSize()); + + /* Make new base storage. */ + const auto base_storage_offset = base_storage->GetStorageOffset(); + const auto base_storage_size = util::AlignUp(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize); + fs::SubStorage new_base_storage; + R_TRY(base_storage->GetSubStorage(std::addressof(new_base_storage), 0, base_storage_size)); + + /* Create the table storage. */ + std::unique_ptr table_storage; + { + BaseStorage aes_ctr_base_storage(std::addressof(new_base_storage), patch_info.aes_ctr_ex_offset, patch_info.aes_ctr_ex_size); + aes_ctr_base_storage.SetStorageOffset(base_storage_offset + patch_info.aes_ctr_ex_offset); + aes_ctr_base_storage.SetAesCtrUpperIv(header_reader.GetAesCtrUpperIv()); + R_TRY(this->CreateAesCtrStorage(std::addressof(table_storage), std::addressof(aes_ctr_base_storage))); + } + + /* Get the table size. */ + s64 table_size = 0; + R_TRY(table_storage->GetSize(std::addressof(table_size))); + + /* Create the buffered storage. */ + std::unique_ptr buffered_storage = std::make_unique(); + R_UNLESS(buffered_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the buffered storage. */ + R_TRY(buffered_storage->Initialize(fs::SubStorage(table_storage.get(), 0, table_size), this->buffer_manager, AesCtrExTableCacheBlockSize, AesCtrExTableCacheCount)); + + /* Create an aligned storage for the buffered storage. */ + using AlignedStorage = AlignmentMatchingStorage; + std::unique_ptr aligned_storage = std::make_unique(buffered_storage.get()); + R_UNLESS(aligned_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Determine the bucket extents. */ + const auto entry_count = header.entry_count; + const s64 data_offset = 0; + const s64 data_size = patch_info.aes_ctr_ex_offset; + const s64 node_offset = 0; + const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); + const s64 entry_offset = node_offset + node_size; + const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); + + /* Create bucket storages. */ + fs::SubStorage data_storage(std::addressof(new_base_storage), data_offset, data_size); + fs::SubStorage node_storage(aligned_storage.get(), node_offset, node_size); + fs::SubStorage entry_storage(aligned_storage.get(), entry_offset, entry_size); + + /* Get the secure value. */ + const auto secure_value = header_reader.GetAesCtrUpperIv().part.secure_value; + + /* Create the aes ctr ex storage. */ + std::unique_ptr aes_ctr_ex_storage; + const bool has_external_key = this->reader->HasExternalDecryptionKey(); + if (has_external_key) { + /* Create the decryptor. */ + std::unique_ptr decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(decryptor), this->reader->GetExternalDecryptAesCtrFunctionForExternalKey(), -1)); + + /* Create the aes ctr ex storage. */ + std::unique_ptr impl_storage = std::make_unique(); + R_UNLESS(impl_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the aes ctr ex storage. */ + R_TRY(impl_storage->Initialize(this->allocator, this->reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, secure_value, base_storage_offset, data_storage, node_storage, entry_storage, entry_count, std::move(decryptor))); + + /* Set the option's aes ctr ex storage. */ + option->SetAesCtrExStorageRaw(impl_storage.get()); + + aes_ctr_ex_storage = std::move(impl_storage); + } else { + /* Check if we have a hardware key. */ + const bool has_hardware_key = this->reader->HasInternalDecryptionKeyForAesHardwareSpeedEmulation(); + + /* Create the software decryptor. */ + std::unique_ptr sw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + /* Make the software storage. */ + std::unique_ptr sw_storage = std::make_unique(); + R_UNLESS(sw_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the software storage. */ + R_TRY(sw_storage->Initialize(this->allocator, this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), AesCtrStorage::KeySize, secure_value, base_storage_offset, data_storage, node_storage, entry_storage, entry_count, std::move(sw_decryptor))); + + /* Set the option's aes ctr ex storage. */ + option->SetAesCtrExStorageRaw(sw_storage.get()); + + /* If we have a hardware key and should use it, make the hardware decryption storage. */ + if (has_hardware_key && !this->reader->IsSoftwareAesPrioritized()) { + /* Create the hardware decryptor. */ + std::unique_ptr hw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::addressof(hw_decryptor), this->reader->GetExternalDecryptAesCtrFunction(), GetKeyTypeValue(this->reader->GetKeyIndex(), this->reader->GetKeyGeneration()))); + + /* Create the hardware storage. */ + std::unique_ptr hw_storage = std::make_unique(); + R_UNLESS(hw_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the hardware storage. */ + R_TRY(hw_storage->Initialize(this->allocator, this->reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), AesCtrStorage::KeySize, secure_value, base_storage_offset, data_storage, node_storage, entry_storage, entry_count, std::move(hw_decryptor))); + + /* Create the selection storage. */ + std::unique_ptr switch_storage = std::make_unique>(std::move(hw_storage), std::move(sw_storage), IsUsingHardwareAesCtrForSpeedEmulation); + R_UNLESS(switch_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Set the aes ctr ex storage. */ + aes_ctr_ex_storage = std::move(switch_storage); + } else { + /* Set the aes ctr ex storage. */ + aes_ctr_ex_storage = std::move(sw_storage); + } + } + + /* Create the storage holder. */ + std::unique_ptr storage = std::make_unique>(aes_ctr_ex_storage.get(), this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Set the aes ctr ex storages in the option. */ + option->SetAesCtrExTableStorage(table_storage.get()); + option->SetAesCtrExStorage(storage.get()); + + /* Set the storage holder's storages. */ + storage->Set(std::move(base_storage->GetStorage()), std::move(table_storage), std::move(buffered_storage), std::move(aligned_storage), std::move(aes_ctr_ex_storage)); + + /* Set the out storage. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateIndirectStorage(std::unique_ptr *out, StorageOption *option, std::unique_ptr base_storage) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(option != nullptr); + AMS_ASSERT(base_storage != nullptr); + + /* Check if indirection is needed. */ + const auto &header_reader = option->GetHeaderReader(); + const auto &patch_info = header_reader.GetPatchInfo(); + + if (!patch_info.HasIndirectTable()) { + *out = std::move(base_storage); + return ResultSuccess(); + } + + /* Read the bucket tree header. */ + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.indirect_header, sizeof(header)); + R_TRY(header.Verify()); + + /* Determine the storage sizes. */ + const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= patch_info.indirect_size, fs::ResultInvalidIndirectStorageSize()); + + /* Open the original storage. */ + std::unique_ptr original_storage; + { + const s32 fs_index = header_reader.GetFsIndex(); + + if (this->original_reader != nullptr && this->original_reader->HasFsInfo(fs_index)) { + NcaFsHeaderReader original_header_reader; + R_TRY(original_header_reader.Initialize(*this->original_reader, fs_index)); + + NcaFileSystemDriver original_driver(this->original_reader, this->allocator, this->buffer_manager); + StorageOption original_option(std::addressof(original_header_reader), fs_index); + + BaseStorage original_base_storage; + R_TRY(original_driver.CreateBaseStorage(std::addressof(original_base_storage), std::addressof(original_option))); + R_TRY(original_driver.CreateDecryptableStorage(std::addressof(original_storage), std::addressof(original_option), std::addressof(original_base_storage))); + } else { + original_storage = std::make_unique(nullptr, 0); + R_UNLESS(original_storage != nullptr, fs::ResultAllocationFailureInNew()); + } + } + + /* Get the original data size. */ + s64 original_data_size = 0; + R_TRY(original_storage->GetSize(std::addressof(original_data_size))); + + /* Get the indirect data size. */ + s64 indirect_data_size = patch_info.indirect_offset; + AMS_ASSERT(util::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); + + /* Create the indirect table storage. */ + std::unique_ptr indirect_table_storage = std::make_unique(); + R_UNLESS(indirect_table_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the indirect table storage. */ + R_TRY(indirect_table_storage->Initialize(fs::SubStorage(base_storage.get(), indirect_data_size, node_size + entry_size), this->buffer_manager, IndirectTableCacheBlockSize, IndirectTableCacheCount)); + + /* Create the indirect data storage. */ + std::unique_ptr indirect_data_storage = std::make_unique(); + R_UNLESS(indirect_data_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the indirect data storage. */ + R_TRY(indirect_data_storage->Initialize(fs::SubStorage(base_storage.get(), 0, indirect_data_size), this->buffer_manager, IndirectDataCacheBlockSize, IndirectDataCacheCount)); + + /* Create the storage holder. */ + std::unique_ptr storage = std::make_unique>(this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the storage holder. */ + R_TRY(storage->Initialize(this->allocator, fs::SubStorage(indirect_table_storage.get(), 0, node_size), fs::SubStorage(indirect_table_storage.get(), node_size, entry_size), header.entry_count)); + + /* Set the storage holder's storages. */ + storage->SetStorage(0, original_storage.get(), 0, original_data_size); + storage->SetStorage(1, indirect_table_storage.get(), 0, indirect_data_size); + storage->Set(std::move(base_storage), std::move(original_storage), std::move(indirect_table_storage), std::move(indirect_data_storage)); + + /* Set the indirect storage. */ + option->SetIndirectStorage(storage.get()); + + /* Set the out storage. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateVerificationStorage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(header_reader != nullptr); + + /* Create the appropriate storage for the encryption type. */ + switch (header_reader->GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + R_TRY(this->CreateSha256Storage(out, std::move(base_storage), header_reader)); + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + R_TRY(this->CreateIntegrityVerificationStorage(out, std::move(base_storage), header_reader)); + break; + default: + return fs::ResultInvalidNcaFsHeaderHashType(); + } + + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateSha256Storage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(header_reader != nullptr); + + /* Define storage types. */ + using VerificationStorage = HierarchicalSha256Storage; + using CacheStorage = ReadOnlyBlockCacheStorage; + using AlignedStorage = AlignmentMatchingStoragePooledBuffer<1>; + using StorageHolder = DerivedStorageHolderWithBuffer; + + /* Get and validate the hash data. */ + auto &hash_data = header_reader->GetHashData().hierarchical_sha256_data; + R_UNLESS(util::IsPowerOfTwo(hash_data.hash_block_size), fs::ResultInvalidHierarchicalSha256BlockSize()); + R_UNLESS(hash_data.hash_layer_count == HierarchicalSha256Storage::LayerCount - 1, fs::ResultInvalidHierarchicalSha256LayerCount()); + + /* Get the regions. */ + const auto &hash_region = hash_data.hash_layer_region[0]; + const auto &data_region = hash_data.hash_layer_region[1]; + + /* Determine buffer sizes. */ + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + /* Make a buffer holder. */ + BufferHolder buffer_holder(this->allocator, total_buffer_size); + R_UNLESS(buffer_holder.IsValid(), fs::ResultAllocationFailureInNcaFileSystemDriverI()); + + /* Make the data storage. */ + std::unique_ptr data_storage = std::make_unique(base_storage.get(), data_region.offset, data_region.size); + R_UNLESS(data_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Make the verification storage. */ + std::unique_ptr verification_storage = std::make_unique(); + R_UNLESS(verification_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Make layer storages. */ + fs::MemoryStorage master_hash_storage(std::addressof(hash_data.fs_data_master_hash), sizeof(Hash)); + fs::SubStorage layer_hash_storage(base_storage.get(), hash_region.offset, hash_region.size); + fs::IStorage *storages[VerificationStorage::LayerCount] = { + std::addressof(master_hash_storage), + std::addressof(layer_hash_storage), + data_storage.get() + }; + + /* Initialize the verification storage. */ + R_TRY(verification_storage->Initialize(storages, VerificationStorage::LayerCount, hash_data.hash_block_size, buffer_holder.Get(), hash_buffer_size)); + + /* Make the cache storage. */ + std::unique_ptr cache_storage = std::make_unique(verification_storage.get(), hash_data.hash_block_size, buffer_holder.Get() + hash_buffer_size, cache_buffer_size, CacheBlockCount); + R_UNLESS(cache_storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Make the storage holder. */ + std::unique_ptr storage = std::make_unique(cache_storage.get(), hash_data.hash_block_size, this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Set the storage holder's data. */ + storage->Set(std::move(base_storage), std::move(data_storage), std::move(verification_storage), std::move(cache_storage)); + storage->Set(std::move(buffer_holder)); + + /* Set the output. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::CreateIntegrityVerificationStorage(std::unique_ptr *out, std::unique_ptr base_storage, NcaFsHeaderReader *header_reader) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(header_reader != nullptr); + + /* Define storage types. */ + using VerificationStorage = save::HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + using StorageHolder = DerivedStorageHolder; + + /* Get and validate the hash data. */ + auto &hash_data = header_reader->GetHashData().integrity_meta_info; + save::HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(hash_data.level_hash_info), sizeof(level_hash_info)); + + R_UNLESS(save::IntegrityMinLayerCount <= level_hash_info.max_layers, fs::ResultInvalidHierarchicalIntegrityVerificationLayerCount()); + R_UNLESS(level_hash_info.max_layers <= save::IntegrityMaxLayerCount, fs::ResultInvalidHierarchicalIntegrityVerificationLayerCount()); + + /* Create storage info. */ + StorageInfo storage_info; + for (s32 i = 0; i < static_cast(level_hash_info.max_layers - 2); ++i) { + const auto &layer_info = level_hash_info.info[i]; + storage_info[i + 1] = fs::SubStorage(base_storage.get(), layer_info.offset, layer_info.size); + } + + /* Set the last layer info. */ + const auto &layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + storage_info.SetDataStorage(fs::SubStorage(base_storage.get(), layer_info.offset, layer_info.size)); + + /* Make the storage holder. */ + std::unique_ptr storage = std::make_unique(this->reader); + R_UNLESS(storage != nullptr, fs::ResultAllocationFailureInNew()); + + /* Initialize the integrity storage. */ + R_TRY(storage->Initialize(level_hash_info, hash_data.master_hash, storage_info, this->buffer_manager)); + + /* Set the storage holder's data. */ + storage->Set(std::move(base_storage)); + + /* Set the output. */ + *out = std::move(storage); + return ResultSuccess(); + } + + Result NcaFileSystemDriver::SetupFsHeaderReader(NcaFsHeaderReader *out, const NcaReader &reader, s32 fs_index) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + /* Validate magic. */ + R_UNLESS(reader.GetMagic() == NcaHeader::Magic, fs::ResultUnsupportedVersion()); + + /* Check that the fs header exists. */ + R_UNLESS(reader.HasFsInfo(fs_index), fs::ResultPartitionNotFound()); + + /* Initialize the reader. */ + R_TRY(out->Initialize(reader, fs_index)); + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..32ebf7f3e --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_header.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + u8 NcaHeader::GetProperKeyGeneration() const { + return std::max(this->key_generation, this->key_generation_2); + } + bool NcaPatchInfo::HasIndirectTable() const { + return this->indirect_size != 0; + } + + bool NcaPatchInfo::HasAesCtrExTable() const { + return this->aes_ctr_ex_size != 0; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp b/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..93bcfbaa2 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_nca_reader.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + constexpr inline u32 SdkAddonVersionMin = 0x000B0000; + + constexpr Result CheckNcaMagic(u32 magic) { + /* Verify the magic is not a deprecated one. */ + R_UNLESS(magic != NcaHeader::Magic0, fs::ResultUnsupportedSdkVersion()); + R_UNLESS(magic != NcaHeader::Magic1, fs::ResultUnsupportedSdkVersion()); + R_UNLESS(magic != NcaHeader::Magic2, fs::ResultUnsupportedSdkVersion()); + + /* Verify the magic is the current one. */ + R_UNLESS(magic == NcaHeader::Magic3, fs::ResultInvalidNcaSignature()); + + return ResultSuccess(); + } + + } + + NcaReader::NcaReader() : shared_base_storage(), header_storage(), body_storage(), decrypt_aes_ctr(), decrypt_aes_ctr_external(), is_software_aes_prioritized(false), header_encryption_type(NcaHeader::EncryptionType::Auto) { + std::memset(std::addressof(this->header), 0, sizeof(this->header)); + std::memset(std::addressof(this->decryption_keys), 0, sizeof(this->decryption_keys)); + std::memset(std::addressof(this->external_decryption_key), 0, sizeof(this->external_decryption_key)); + } + + NcaReader::~NcaReader() { + /* ... */ + } + + Result NcaReader::Initialize(std::shared_ptr base_storage, const NcaCryptoConfiguration &crypto_cfg) { + this->shared_base_storage = base_storage; + return this->Initialize(this->shared_base_storage.get(), crypto_cfg); + } + + Result NcaReader::Initialize(fs::IStorage *base_storage, const NcaCryptoConfiguration &crypto_cfg) { + /* Validate preconditions. */ + AMS_ASSERT(base_storage != nullptr); + AMS_ASSERT(this->body_storage == nullptr); + R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument()); + + /* Generate keys for header. */ + u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize]; + for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { + crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorage::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorage::KeySize, static_cast(KeyType::NcaHeaderKey), crypto_cfg); + } + + /* Create the header storage. */ + const u8 header_iv[AesXtsStorage::IvSize] = {}; + std::unique_ptr work_header_storage = std::make_unique(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorage::KeySize, header_iv, AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationFailureInNcaReaderA()); + + /* Read the header. */ + R_TRY(work_header_storage->Read(0, std::addressof(this->header), sizeof(this->header))); + + /* Validate the magic. */ + if (Result magic_result = CheckNcaMagic(this->header.magic); R_FAILED(magic_result)) { + /* If we're not allowed to use plaintext headers, stop here. */ + R_UNLESS(crypto_cfg.is_plaintext_header_available, magic_result); + + /* Try to use a plaintext header. */ + R_TRY(base_storage->Read(0, std::addressof(this->header), sizeof(this->header))); + R_UNLESS(R_SUCCEEDED(CheckNcaMagic(this->header.magic)), magic_result); + + /* Configure to use the plaintext header. */ + s64 base_storage_size; + R_TRY(base_storage->GetSize(std::addressof(base_storage_size))); + work_header_storage.reset(new fs::SubStorage(base_storage, 0, base_storage_size)); + R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationFailureInNcaReaderA()); + + this->header_encryption_type = NcaHeader::EncryptionType::None; + } + + /* Validate the fixed key signature. */ + R_UNLESS(this->header.header1_signature_key_generation <= NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, fs::ResultInvalidNcaHeader1SignatureKeyGeneration()); + const u8 *header_1_sign_key_modulus = crypto_cfg.header_1_sign_key_moduli[this->header.header1_signature_key_generation]; + AMS_ABORT_UNLESS(header_1_sign_key_modulus != nullptr); + { + const u8 *sig = this->header.header_sign_1; + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8 *mod = header_1_sign_key_modulus; + const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize; + const u8 *exp = crypto_cfg.header_1_sign_key_public_exponent; + const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize; + const u8 *msg = static_cast(static_cast(std::addressof(this->header.magic))); + const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid, fs::ResultNcaHeaderSignature1VerificationFailed()); + } + + /* Validate the sdk version. */ + R_UNLESS(this->header.sdk_addon_version >= SdkAddonVersionMin, fs::ResultUnsupportedSdkVersion()); + + /* Validate the key index. */ + R_UNLESS(this->header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, fs::ResultInvalidNcaKeyIndex()); + + /* Check if we have a rights id. */ + constexpr const u8 ZeroRightsId[NcaHeader::RightsIdSize] = {}; + if (crypto::IsSameBytes(ZeroRightsId, this->header.rights_id, NcaHeader::RightsIdSize)) { + /* If we do, then we don't have an external key, so we need to generate decryption keys. */ + crypto_cfg.generate_key(this->decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, this->header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(this->header.key_index, this->header.GetProperKeyGeneration()), crypto_cfg); + + /* Copy the hardware speed emulation key. */ + std::memcpy(this->decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], this->header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrHw * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize); + } + + /* Clear the external decryption key. */ + std::memset(this->external_decryption_key, 0, sizeof(this->external_decryption_key)); + + /* Set our decryptor functions. */ + this->decrypt_aes_ctr = crypto_cfg.decrypt_aes_ctr; + this->decrypt_aes_ctr_external = crypto_cfg.decrypt_aes_ctr_external; + + /* Set our storages. */ + this->header_storage = std::move(work_header_storage); + this->body_storage = base_storage; + + return ResultSuccess(); + } + + fs::IStorage *NcaReader::GetBodyStorage() { + return this->body_storage; + } + + u32 NcaReader::GetMagic() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.magic; + } + + NcaHeader::DistributionType NcaReader::GetDistributionType() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.distribution_type; + } + + NcaHeader::ContentType NcaReader::GetContentType() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.content_type; + } + + u8 NcaReader::GetKeyGeneration() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.GetProperKeyGeneration(); + } + + u8 NcaReader::GetKeyIndex() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.key_index; + } + + u64 NcaReader::GetContentSize() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.content_size; + } + + u64 NcaReader::GetProgramId() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.program_id; + } + + u32 NcaReader::GetContentIndex() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.content_index; + } + + u32 NcaReader::GetSdkAddonVersion() const { + AMS_ASSERT(this->body_storage != nullptr); + return this->header.sdk_addon_version; + } + + void NcaReader::GetRightsId(u8 *dst, size_t dst_size) const { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= NcaHeader::RightsIdSize); + std::memcpy(dst, this->header.rights_id, NcaHeader::RightsIdSize); + } + + bool NcaReader::HasFsInfo(s32 index) const { + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return this->header.fs_info[index].start_sector != 0 || this->header.fs_info[index].end_sector != 0; + } + + s32 NcaReader::GetFsCount() const { + AMS_ASSERT(this->body_storage != nullptr); + for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { + if (!this->HasFsInfo(i)) { + return i; + } + } + return NcaHeader::FsCountMax; + } + + const Hash &NcaReader::GetFsHeaderHash(s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return this->header.fs_header_hash[index]; + } + + void NcaReader::GetFsHeaderHash(Hash *dst, s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + AMS_ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(this->header.fs_header_hash[index]), sizeof(*dst)); + } + + void NcaReader::GetFsInfo(NcaHeader::FsInfo *dst, s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + AMS_ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(this->header.fs_info[index]), sizeof(*dst)); + } + + u64 NcaReader::GetFsOffset(s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(this->header.fs_info[index].start_sector); + } + + u64 NcaReader::GetFsEndOffset(s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(this->header.fs_info[index].end_sector); + } + + u64 NcaReader::GetFsSize(s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(this->header.fs_info[index].end_sector - this->header.fs_info[index].start_sector); + } + + void NcaReader::GetEncryptedKey(void *dst, size_t size) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); + std::memcpy(dst, this->header.encrypted_key_area, NcaHeader::EncryptedKeyAreaSize); + } + + const void *NcaReader::GetDecryptionKey(s32 index) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); + return this->decryption_keys[index]; + } + + bool NcaReader::HasValidInternalKey() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { + if (!crypto::IsSameBytes(ZeroKey, this->header.encrypted_key_area + i * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize)) { + return true; + } + } + return false; + } + + bool NcaReader::HasInternalDecryptionKeyForAesHardwareSpeedEmulation() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + return !crypto::IsSameBytes(ZeroKey, this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), crypto::AesDecryptor128::KeySize); + } + + bool NcaReader::IsSoftwareAesPrioritized() const { + return this->is_software_aes_prioritized; + } + + void NcaReader::PrioritizeSoftwareAes() { + this->is_software_aes_prioritized = true; + } + + bool NcaReader::HasExternalDecryptionKey() const { + constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {}; + return !crypto::IsSameBytes(ZeroKey, this->GetExternalDecryptionKey(), crypto::AesDecryptor128::KeySize); + } + + const void *NcaReader::GetExternalDecryptionKey() const { + return this->external_decryption_key; + } + + void NcaReader::SetExternalDecryptionKey(const void *src, size_t size) { + AMS_ASSERT(src != nullptr); + AMS_ASSERT(size == sizeof(this->external_decryption_key)); + std::memcpy(this->external_decryption_key, src, sizeof(this->external_decryption_key)); + } + + void NcaReader::GetRawData(void *dst, size_t dst_size) const { + AMS_ASSERT(this->body_storage != nullptr); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= sizeof(NcaHeader)); + + std::memcpy(dst, std::addressof(this->header), sizeof(NcaHeader)); + } + + DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunction() const { + AMS_ASSERT(this->decrypt_aes_ctr != nullptr); + return this->decrypt_aes_ctr; + } + + DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunctionForExternalKey() const { + AMS_ASSERT(this->decrypt_aes_ctr_external != nullptr); + return this->decrypt_aes_ctr_external; + } + + NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { + return this->header_encryption_type; + } + + Result NcaReader::ReadHeader(NcaFsHeader *dst, s32 index) const { + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax); + + const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; + return this->header_storage->Read(offset, dst, sizeof(NcaFsHeader)); + } + + Result NcaReader::VerifyHeaderSign2(const void *mod, size_t mod_size) { + AMS_ASSERT(this->body_storage != nullptr); + constexpr const u8 HeaderSign2KeyPublicExponent[] = { 0x01, 0x00, 0x01 }; + + const u8 *sig = this->header.header_sign_2; + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8 *exp = HeaderSign2KeyPublicExponent; + const size_t exp_size = sizeof(HeaderSign2KeyPublicExponent); + const u8 *msg = static_cast(static_cast(std::addressof(this->header.magic))); + const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); + R_UNLESS(is_signature_valid, fs::ResultNcaHeaderSignature2VerificationFailed()); + + return ResultSuccess(); + } + + Result NcaFsHeaderReader::Initialize(const NcaReader &reader, s32 index) { + /* Reset ourselves to uninitialized. */ + this->fs_index = -1; + + /* Read the header. */ + R_TRY(reader.ReadHeader(std::addressof(this->data), index)); + + /* Generate the hash. */ + Hash hash; + crypto::GenerateSha256Hash(std::addressof(hash), sizeof(hash), std::addressof(this->data), sizeof(NcaFsHeader)); + + /* Validate the hash. */ + R_UNLESS(crypto::IsSameBytes(std::addressof(reader.GetFsHeaderHash(index)), std::addressof(hash), sizeof(Hash)), fs::ResultNcaFsHeaderHashVerificationFailed()); + + /* Set our index. */ + this->fs_index = index; + return ResultSuccess(); + } + + void NcaFsHeaderReader::GetRawData(void *dst, size_t dst_size) const { + AMS_ASSERT(this->IsInitialized()); + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(dst_size >= sizeof(NcaFsHeader)); + std::memcpy(dst, std::addressof(this->data), sizeof(NcaFsHeader)); + } + + NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() { + AMS_ASSERT(this->IsInitialized()); + return this->data.hash_data; + } + + const NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.hash_data; + } + + u16 NcaFsHeaderReader::GetVersion() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.version; + } + + s32 NcaFsHeaderReader::GetFsIndex() const { + AMS_ASSERT(this->IsInitialized()); + return this->fs_index; + } + + NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.fs_type; + } + + NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.hash_type; + } + + NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.encryption_type; + } + + NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() { + AMS_ASSERT(this->IsInitialized()); + return this->data.patch_info; + } + + const NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.patch_info; + } + + const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.aes_ctr_upper_iv; + } + + bool NcaFsHeaderReader::ExistsSparseLayer() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.sparse_info.generation != 0; + } + + NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() { + AMS_ASSERT(this->IsInitialized()); + return this->data.sparse_info; + } + + const NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() const { + AMS_ASSERT(this->IsInitialized()); + return this->data.sparse_info; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp b/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp new file mode 100644 index 000000000..52445c640 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_read_only_block_cache_storage.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "fssystem_lru_list_cache.hpp" + +namespace ams::fssystem { + + class ReadOnlyBlockCacheStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable { + NON_COPYABLE(ReadOnlyBlockCacheStorage); + NON_MOVEABLE(ReadOnlyBlockCacheStorage); + private: + using BlockCache = LruListCache; + private: + os::Mutex mutex; + BlockCache block_cache; + fs::IStorage * const base_storage; + s32 block_size; + public: + ReadOnlyBlockCacheStorage(IStorage *bs, s32 bsz, char *buf, size_t buf_size, s32 cache_block_count) : mutex(false), base_storage(bs), block_size(bsz) { + /* Validate preconditions. */ + AMS_ASSERT(buf_size >= static_cast(this->block_size)); + AMS_ASSERT(util::IsPowerOfTwo(this->block_size)); + AMS_ASSERT(cache_block_count > 0); + AMS_ASSERT(buf_size >= static_cast(this->block_size * cache_block_count)); + + /* Create a node for each cache block. */ + for (auto i = 0; i < cache_block_count; i++) { + std::unique_ptr node = std::make_unique(buf + this->block_size * i); + AMS_ASSERT(node != nullptr); + + if (node != nullptr) { + this->block_cache.PushMruNode(std::move(node), -1); + } + } + } + + ~ReadOnlyBlockCacheStorage() { + this->block_cache.DeleteAllNodes(); + } + + virtual Result Read(s64 offset, void *buffer, size_t size) override { + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, this->block_size)); + AMS_ASSERT(util::IsAligned(size, this->block_size)); + + if (size == static_cast(this->block_size)) { + char *cached_buffer = nullptr; + + /* Try to find a cached copy of the data. */ + { + std::scoped_lock lk(this->mutex); + bool found = this->block_cache.FindValueAndUpdateMru(std::addressof(cached_buffer), offset / this->block_size); + if (found) { + std::memcpy(buffer, cached_buffer, size); + return ResultSuccess(); + } + } + + /* We failed to get a cache hit, so read in the data. */ + R_TRY(this->base_storage->Read(offset, buffer, size)); + + /* Add the block to the cache. */ + { + std::scoped_lock lk(this->mutex); + auto lru = this->block_cache.PopLruNode(); + std::memcpy(lru->value, buffer, this->block_size); + this->block_cache.PushMruNode(std::move(lru), offset / this->block_size); + } + + return ResultSuccess(); + } else { + return this->base_storage->Read(offset, buffer, size); + } + } + virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override { + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, this->block_size)); + AMS_ASSERT(util::IsAligned(size, this->block_size)); + + /* If invalidating cache, invalidate our blocks. */ + if (op_id == fs::OperationId::InvalidateCache) { + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + + std::scoped_lock lk(this->mutex); + + const size_t cache_block_count = this->block_cache.GetSize(); + BlockCache valid_cache; + + for (size_t count = 0; count < cache_block_count; ++count) { + auto lru = this->block_cache.PopLruNode(); + if (offset <= lru->key && lru->key < offset + size) { + this->block_cache.PushMruNode(std::move(lru), -1); + } else { + valid_cache.PushMruNode(std::move(lru), lru->key); + } + } + + while (!valid_cache.IsEmpty()) { + auto lru = valid_cache.PopLruNode(); + this->block_cache.PushMruNode(std::move(lru), lru->key); + } + } + + /* Operate on the base storage. */ + return this->base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + } + + virtual Result GetSize(s64 *out) override { + return this->base_storage->GetSize(out); + } + + virtual Result Flush() override { + return ResultSuccess(); + } + + virtual Result Write(s64 offset, const void *buffer, size_t size) override { + return fs::ResultUnsupportedOperationInReadOnlyBlockCacheStorageA(); + } + + virtual Result SetSize(s64 size) override { + return fs::ResultUnsupportedOperationInReadOnlyBlockCacheStorageB(); + } + }; + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp b/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..8681c18f3 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_sparse_storage.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + Result SparseStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(this->IsInitialized()); + + /* Allow zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + if (this->GetEntryTable().IsEmpty()) { + R_UNLESS(this->GetEntryTable().Includes(offset, size), fs::ResultOutOfRange()); + std::memset(buffer, 0, size); + } else { + R_TRY(this->OperatePerEntry(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + R_TRY(storage->Read(data_offset, reinterpret_cast(buffer) + (cur_offset - offset), static_cast(cur_size))); + return ResultSuccess(); + })); + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp b/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp new file mode 100644 index 000000000..c0b5a7c5e --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/fssystem_speed_emulation_configuration.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem { + + namespace { + + std::atomic<::ams::fs::SpeedEmulationMode> g_speed_emulation_mode = ::ams::fs::SpeedEmulationMode::None; + + } + + void SpeedEmulationConfiguration::SetSpeedEmulationMode(::ams::fs::SpeedEmulationMode mode) { + g_speed_emulation_mode = mode; + } + + ::ams::fs::SpeedEmulationMode SpeedEmulationConfiguration::GetSpeedEmulationMode() { + return g_speed_emulation_mode; + } + +} diff --git a/libraries/libstratosphere/source/fssystem/save/fssystem_block_cache_buffered_storage.cpp b/libraries/libstratosphere/source/fssystem/save/fssystem_block_cache_buffered_storage.cpp new file mode 100644 index 000000000..225171c92 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/save/fssystem_block_cache_buffered_storage.cpp @@ -0,0 +1,1234 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem::save { + + BlockCacheBufferedStorage::BlockCacheBufferedStorage() + : buffer_manager(), mutex(), entries(), data_storage(), last_result(ResultSuccess()), data_size(), verification_block_size(), verification_block_shift(), invalidate_index(), max_cache_entry_count(), flags(), buffer_level(-1) + { + /* ... */ + } + + BlockCacheBufferedStorage::~BlockCacheBufferedStorage() { + this->Finalize(); + } + + Result BlockCacheBufferedStorage::Initialize(IBufferManager *bm, os::Mutex *mtx, IStorage *data, s64 data_size, size_t verif_block_size, s32 max_cache_entries, bool is_real_data, s8 buffer_level, bool is_keep_burst_mode, fs::StorageType storage_type) { + /* Validate preconditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(bm != nullptr); + AMS_ASSERT(mtx != nullptr); + AMS_ASSERT(this->buffer_manager == nullptr); + AMS_ASSERT(this->mutex == nullptr); + AMS_ASSERT(this->data_storage == nullptr); + AMS_ASSERT(this->entries == nullptr); + AMS_ASSERT(max_cache_entries > 0); + + /* Create the entry. */ + this->entries = fs::impl::MakeUnique(static_cast(max_cache_entries)); + R_UNLESS(this->entries != nullptr, fs::ResultAllocationFailureInBlockCacheBufferedStorageA()); + + /* Set members. */ + this->buffer_manager = bm; + this->mutex = mtx; + this->data_storage = data; + this->data_size = data_size; + this->verification_block_size = verif_block_size; + this->last_result = ResultSuccess(); + this->invalidate_index = 0; + this->max_cache_entry_count = max_cache_entries; + this->flags = 0; + this->buffer_level = buffer_level; + this->storage_type = storage_type; + + /* Calculate block shift. */ + this->verification_block_shift = ILog2(static_cast(verif_block_size)); + AMS_ASSERT(static_cast(1ull << this->verification_block_size) == this->verification_block_size); + + /* Clear the entry. */ + std::memset(this->entries.get(), 0, sizeof(CacheEntry) * this->max_cache_entry_count); + + /* Set burst mode. */ + this->SetKeepBurstMode(is_keep_burst_mode); + + /* Set real data cache. */ + this->SetRealDataCache(is_real_data); + + return ResultSuccess(); + } + + void BlockCacheBufferedStorage::Finalize() { + if (this->entries != nullptr) { + /* Invalidate all cache entries. */ + this->InvalidateAllCacheEntries(); + + /* Clear members. */ + this->buffer_manager = nullptr; + this->mutex = nullptr; + this->data_storage = nullptr; + this->data_size = 0; + this->verification_block_size = 0; + this->verification_block_shift = 0; + this->invalidate_index = 0; + this->max_cache_entry_count = 0; + + this->entries.reset(); + } + } + + Result BlockCacheBufferedStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine the extents to read. */ + s64 read_offset = offset; + size_t read_size = size; + + R_UNLESS(read_offset < this->data_size, fs::ResultInvalidOffset()); + + if (static_cast(read_offset + read_size) > this->data_size) { + read_size = static_cast(this->data_size - read_offset); + } + + /* Determine the aligned range to read. */ + const size_t block_alignment = this->verification_block_size; + s64 aligned_offset = util::AlignDown(read_offset, block_alignment); + s64 aligned_offset_end = util::AlignUp(read_offset + read_size, block_alignment); + + AMS_ASSERT(0 <= aligned_offset && aligned_offset_end <= static_cast(util::AlignUp(this->data_size, block_alignment))); + + /* Try to read using cache. */ + char *dst = static_cast(buffer); + { + /* Determine if we can do bulk reads. */ + constexpr s64 BulkReadSizeMax = 2_MB; + const bool bulk_read_enabled = (read_offset != aligned_offset || static_cast(read_offset + read_size) != aligned_offset_end) && aligned_offset_end - aligned_offset <= BulkReadSizeMax; + + /* Read the head cache. */ + CacheEntry head_entry = {}; + MemoryRange head_range = {}; + bool head_cache_needed = true; + R_TRY(this->ReadHeadCache(std::addressof(head_range), std::addressof(head_entry), std::addressof(head_cache_needed), std::addressof(read_offset), std::addressof(aligned_offset), aligned_offset_end, std::addressof(dst), std::addressof(read_size))); + + /* We may be done after reading the head cache, so check if we are. */ + R_SUCCEED_IF(aligned_offset >= aligned_offset_end); + + /* Ensure we destroy the head buffer. */ + auto head_guard = SCOPE_GUARD { this->DestroyBuffer(std::addressof(head_entry), head_range); }; + + /* Read the tail cache. */ + CacheEntry tail_entry = {}; + MemoryRange tail_range = {}; + bool tail_cache_needed = true; + R_TRY(this->ReadTailCache(std::addressof(tail_range), std::addressof(tail_entry), std::addressof(tail_cache_needed), read_offset, aligned_offset, std::addressof(aligned_offset_end), dst, std::addressof(read_size))); + + /* We may be done after reading the tail cache, so check if we are. */ + R_SUCCEED_IF(aligned_offset >= aligned_offset_end); + + /* Ensure that we destroy the tail buffer. */ + auto tail_guard = SCOPE_GUARD { this->DestroyBuffer(std::addressof(tail_entry), tail_range); }; + + /* Try to do a bulk read. */ + if (bulk_read_enabled) { + /* The bulk read will destroy our head/tail buffers. */ + head_guard.Cancel(); + tail_guard.Cancel(); + + do { + /* Do the bulk read. If we fail due to pooled buffer allocation failing, fall back to the normal read codepath. */ + R_TRY_CATCH(this->BulkRead(read_offset, dst, read_size, std::addressof(head_range), std::addressof(tail_range), std::addressof(head_entry), std::addressof(tail_entry), head_cache_needed, tail_cache_needed)) { + R_CATCH(fs::ResultAllocationFailurePooledBufferNotEnoughSize) { break; } + } R_END_TRY_CATCH; + + /* Se successfully did a bulk read, so we're done. */ + return ResultSuccess(); + } while (0); + } + } + + /* Read the data using non-bulk reads. */ + while (aligned_offset < aligned_offset_end) { + /* Ensure that there is data for us to read. */ + AMS_ASSERT(read_size > 0); + + /* If conditions allow us to, read in burst mode. This doesn't use the cache. */ + if (this->IsEnabledKeepBurstMode() && read_offset == aligned_offset && (block_alignment * 2 <= read_size)) { + const size_t aligned_size = util::AlignDown(read_size, block_alignment); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(read_offset, aligned_size, false))); + + /* Read the data. */ + R_TRY(this->UpdateLastResult(this->data_storage->Read(read_offset, dst, aligned_size))); + + /* Advance. */ + dst += aligned_size; + read_offset += aligned_size; + read_size -= aligned_size; + aligned_offset += aligned_size; + } else { + /* Get the buffer associated with what we're reading. */ + CacheEntry entry; + MemoryRange range; + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(range), std::addressof(entry), aligned_offset, static_cast(aligned_offset_end - aligned_offset), true))); + + /* Determine where to read data into, and ensure that our entry is aligned. */ + char *src = reinterpret_cast(range.first); + AMS_ASSERT(util::IsAligned(entry.size, block_alignment)); + + /* If the entry isn't cached, read the data. */ + if (!entry.is_cached) { + if (Result result = this->data_storage->Read(entry.offset, src, entry.size); R_FAILED(result)) { + this->DestroyBuffer(std::addressof(entry), range); + return this->UpdateLastResult(result); + } + entry.is_cached = true; + } + + /* Validate the entry extents. */ + AMS_ASSERT(static_cast(entry.offset) <= aligned_offset); + AMS_ASSERT(aligned_offset < static_cast(entry.offset + entry.size)); + AMS_ASSERT(aligned_offset <= read_offset); + + /* Copy the data. */ + { + /* Determine where and how much to copy. */ + const s64 buffer_offset = read_offset - entry.offset; + const size_t copy_size = std::min(read_size, static_cast(entry.offset + entry.size - read_offset)); + + /* Actually copy the data. */ + std::memcpy(dst, src + buffer_offset, copy_size); + + /* Advance. */ + dst += copy_size; + read_offset += copy_size; + read_size -= copy_size; + } + + /* Release the cache entry. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(range, std::addressof(entry)))); + aligned_offset = entry.offset + entry.size; + } + } + + /* Ensure that we read all the data. */ + AMS_ASSERT(read_size == 0); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Determine the extents to read. */ + R_UNLESS(offset < this->data_size, fs::ResultInvalidOffset()); + + if (static_cast(offset + size) > this->data_size) { + size = static_cast(this->data_size - offset); + } + + /* The actual extents may be zero-size, so succeed if that's the case. */ + R_SUCCEED_IF(size == 0); + + /* Determine the aligned range to read. */ + const size_t block_alignment = this->verification_block_size; + s64 aligned_offset = util::AlignDown(offset, block_alignment); + const s64 aligned_offset_end = util::AlignUp(offset + size, block_alignment); + + AMS_ASSERT(0 <= aligned_offset && aligned_offset_end <= static_cast(util::AlignUp(this->data_size, block_alignment))); + + /* Write the data. */ + const u8 *src = static_cast(buffer); + while (aligned_offset < aligned_offset_end) { + /* If conditions allow us to, write in burst mode. This doesn't use the cache. */ + if (this->IsEnabledKeepBurstMode() && offset == aligned_offset && (block_alignment * 2 <= size)) { + const size_t aligned_size = util::AlignDown(size, block_alignment); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, aligned_size, true))); + + /* Read the data. */ + R_TRY(this->UpdateLastResult(this->data_storage->Write(offset, src, aligned_size))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + /* Advance. */ + src += aligned_size; + offset += aligned_size; + size -= aligned_size; + aligned_offset += aligned_size; + } else { + /* Get the buffer associated with what we're writing. */ + CacheEntry entry; + MemoryRange range; + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(range), std::addressof(entry), aligned_offset, static_cast(aligned_offset_end - aligned_offset), true))); + + /* Determine where to write data into. */ + char *dst = reinterpret_cast(range.first); + + /* If the entry isn't cached and we're writing a partial entry, read in the entry. */ + if (!entry.is_cached && ((offset != entry.offset) || (offset + size < entry.offset + entry.size))) { + if (Result result = this->data_storage->Read(entry.offset, dst, entry.size); R_FAILED(result)) { + this->DestroyBuffer(std::addressof(entry), range); + return this->UpdateLastResult(result); + } + } + entry.is_cached = true; + + /* Validate the entry extents. */ + AMS_ASSERT(static_cast(entry.offset) <= aligned_offset); + AMS_ASSERT(aligned_offset < static_cast(entry.offset + entry.size)); + AMS_ASSERT(aligned_offset <= offset); + + /* Copy the data. */ + { + /* Determine where and how much to copy. */ + const s64 buffer_offset = offset - entry.offset; + const size_t copy_size = std::min(size, static_cast(entry.offset + entry.size - offset)); + + /* Actually copy the data. */ + std::memcpy(dst + buffer_offset, src, copy_size); + + /* Advance. */ + src += copy_size; + offset += copy_size; + size -= copy_size; + } + + /* Set the entry as write-back. */ + entry.is_write_back = true; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + /* Store the associated buffer. */ + CacheIndex index; + R_TRY(this->UpdateLastResult(this->StoreAssociateBuffer(std::addressof(index), range, entry))); + + /* If we need to, flush the cache entry. */ + if (index >= 0 && IsEnabledKeepBurstMode() && offset == aligned_offset && (block_alignment * 2 <= size)) { + R_TRY(this->UpdateLastResult(this->FlushCacheEntry(index, false))); + } + } + } + + /* Ensure that didn't end up in a failure state. */ + R_TRY(this->last_result); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::GetSize(s64 *out) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->data_storage != nullptr); + + /* Set the size. */ + *out = this->data_size; + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::Flush() { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Flush all cache entries. */ + R_TRY(this->UpdateLastResult(this->FlushAllCacheEntries())); + + /* Flush the data storage. */ + R_TRY(this->UpdateLastResult(this->data_storage->Flush())); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + + switch (op_id) { + case fs::OperationId::Clear: + { + R_TRY(this->ClearImpl(offset, size)); + return ResultSuccess(); + } + case fs::OperationId::ClearSignature: + { + R_TRY(this->ClearSignatureImpl(offset, size)); + return ResultSuccess(); + } + case fs::OperationId::InvalidateCache: + { + R_UNLESS(this->storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperationInBlockCacheBufferedStorageB()); + R_TRY(this->InvalidateCacheImpl(offset, size)); + return ResultSuccess(); + } + case fs::OperationId::QueryRange: + { + R_TRY(this->QueryRangeImpl(dst, dst_size, offset, size)); + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInBlockCacheBufferedStorageC(); + } + } + + Result BlockCacheBufferedStorage::Commit() { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Flush all cache entries. */ + R_TRY(this->UpdateLastResult(this->FlushAllCacheEntries())); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::OnRollback() { + /* Validate pre-conditions. */ + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Release all valid entries back to the buffer manager. */ + const auto max_cache_entry_count = this->GetMaxCacheEntryCount(); + for (s32 i = 0; i < max_cache_entry_count; i++) { + const auto &entry = this->entries[i]; + if (entry.is_valid) { + if (entry.is_write_back) { + AMS_ASSERT(entry.memory_address != 0 && entry.handle == 0); + this->buffer_manager->DeallocateBuffer(entry.memory_address, entry.memory_size); + } else { + AMS_ASSERT(entry.memory_address == 0 && entry.handle != 0); + const auto memory_range = this->buffer_manager->AcquireCache(entry.handle); + if (memory_range.first != 0) { + this->buffer_manager->DeallocateBuffer(memory_range.first, memory_range.second); + } + } + } + } + + /* Clear all entries. */ + std::memset(this->entries.get(), 0, sizeof(CacheEntry) * max_cache_entry_count); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::ClearImpl(s64 offset, s64 size) { + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->GetSize(std::addressof(storage_size))); + + /* Check the access range. */ + R_UNLESS(0 <= offset && offset < storage_size, fs::ResultInvalidOffset()); + + /* Determine the extents to data signature for. */ + auto start_offset = util::AlignDown(offset, this->verification_block_size); + auto end_offset = util::AlignUp(std::min(offset + size, storage_size), this->verification_block_size); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, size, true))); + + /* Handle any data before or after the aligned range. */ + if (start_offset < offset || offset + size < end_offset) { + /* Allocate a work buffer. */ + std::unique_ptr work = fs::impl::MakeUnique(this->verification_block_size); + R_UNLESS(work != nullptr, fs::ResultAllocationFailureInBlockCacheBufferedStorageB()); + + /* Handle data before the aligned range. */ + if (start_offset < offset) { + /* Read the block. */ + R_TRY(this->UpdateLastResult(this->data_storage->Read(start_offset, work.get(), this->verification_block_size))); + + /* Determine the partial extents to clear. */ + const auto clear_offset = static_cast(offset - start_offset); + const auto clear_size = static_cast(std::min(static_cast(this->verification_block_size - clear_offset), size)); + + /* Clear the partial block. */ + std::memset(work.get() + clear_offset, 0, clear_size); + + /* Write the partially cleared block. */ + R_TRY(this->UpdateLastResult(this->data_storage->Write(start_offset, work.get(), this->verification_block_size))); + + /* Update the start offset. */ + start_offset += this->verification_block_size; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + } + + /* Handle data after the aligned range. */ + if (start_offset < offset + size && offset + size < end_offset) { + /* Read the block. */ + const auto last_offset = end_offset - this->verification_block_size; + R_TRY(this->UpdateLastResult(this->data_storage->Read(last_offset, work.get(), this->verification_block_size))); + + /* Clear the partial block. */ + const auto clear_size = static_cast((offset + size) - last_offset); + std::memset(work.get(), 0, clear_size); + + /* Write the partially cleared block. */ + R_TRY(this->UpdateLastResult(this->data_storage->Write(last_offset, work.get(), this->verification_block_size))); + + /* Update the end offset. */ + end_offset -= this->verification_block_size; + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + } + } + + /* We're done if there's no data to clear. */ + R_SUCCEED_IF(start_offset == end_offset); + + /* Clear the signature for the aligned range. */ + R_TRY(this->UpdateLastResult(this->data_storage->OperateRange(fs::OperationId::Clear, start_offset, end_offset - start_offset))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::ClearSignatureImpl(s64 offset, s64 size) { + /* Ensure we aren't already in a failed state. */ + R_TRY(this->last_result); + + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->GetSize(std::addressof(storage_size))); + + /* Check the access range. */ + R_UNLESS(0 <= offset && offset < storage_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear signature for. */ + const auto start_offset = util::AlignUp(offset, this->verification_block_size); + const auto end_offset = util::AlignDown(std::min(offset + size, storage_size), this->verification_block_size); + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(offset, size, true))); + + /* Clear the signature for the aligned range. */ + R_TRY(this->UpdateLastResult(this->data_storage->OperateRange(fs::OperationId::ClearSignature, start_offset, end_offset - start_offset))); + + /* Set blocking buffer manager allocations. */ + buffers::EnableBlockingBufferManagerAllocation(); + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::InvalidateCacheImpl(s64 offset, s64 size) { + /* Invalidate the entries corresponding to the range. */ + /* NOTE: Nintendo does not check the result of this invalidation. */ + this->InvalidateRangeCacheEntries(offset, size); + + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->GetSize(std::addressof(storage_size))); + + /* Determine the extents we can actually query. */ + const auto actual_size = std::min(size, storage_size - offset); + const auto aligned_offset = util::AlignDown(offset, this->verification_block_size); + const auto aligned_offset_end = util::AlignUp(offset + actual_size, this->verification_block_size); + const auto aligned_size = aligned_offset_end - aligned_offset; + + /* Invalidate the aligned range. */ + { + Result result = this->data_storage->OperateRange(fs::OperationId::InvalidateCache, aligned_offset, aligned_size); + AMS_ASSERT(!fs::ResultBufferAllocationFailed::Includes(result)); + R_TRY(result); + } + + /* Clear our last result if we should. */ + if (fs::ResultIntegrityVerificationStorageCorrupted::Includes(this->last_result)) { + this->last_result = ResultSuccess(); + } + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::QueryRangeImpl(void *dst, size_t dst_size, s64 offset, s64 size) { + /* Get our storage size. */ + s64 storage_size = 0; + R_TRY(this->GetSize(std::addressof(storage_size))); + + /* Determine the extents we can actually query. */ + const auto actual_size = std::min(size, storage_size - offset); + const auto aligned_offset = util::AlignDown(offset, this->verification_block_size); + const auto aligned_offset_end = util::AlignUp(offset + actual_size, this->verification_block_size); + const auto aligned_size = aligned_offset_end - aligned_offset; + + /* Query the aligned range. */ + R_TRY(this->UpdateLastResult(this->data_storage->OperateRange(dst, dst_size, fs::OperationId::QueryRange, aligned_offset, aligned_size, nullptr, 0))); + + return ResultSuccess(); + } + + bool BlockCacheBufferedStorage::ExistsRedundantCacheEntry(const CacheEntry &entry) const { + /* Get the entry's extents. */ + const s64 offset = entry.offset; + const size_t size = entry.size; + + /* Lock our mutex. */ + std::scoped_lock lk(*this->mutex); + + /* Iterate over all entries, checking if any overlap our extents. */ + const auto max_cache_entry_count = this->GetMaxCacheEntryCount(); + for (auto i = 0; i < max_cache_entry_count; ++i) { + const auto &entry = this->entries[i]; + if (entry.is_valid && (entry.is_write_back ? entry.memory_address != 0 : entry.handle != 0)) { + if (entry.offset < static_cast(offset + size) && offset < static_cast(entry.offset + entry.size)) { + return true; + } + } + } + + return false; + } + + Result BlockCacheBufferedStorage::GetAssociateBuffer(MemoryRange *out_range, CacheEntry *out_entry, s64 offset, size_t ideal_size, bool is_allocate_for_write) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + + /* Lock our mutex. */ + std::scoped_lock lk(*this->mutex); + + /* Get the maximum cache entry count. */ + const CacheIndex max_cache_entry_count = static_cast(this->GetMaxCacheEntryCount()); + + /* Locate the index of the cache entry, if present. */ + CacheIndex index; + size_t actual_size = ideal_size; + for (index = 0; index < max_cache_entry_count; ++index) { + const auto &entry = this->entries[index]; + if (entry.is_valid && (entry.is_write_back ? entry.memory_address != 0 : entry.handle != 0)) { + const s64 entry_offset = entry.offset; + if (entry_offset <= offset && offset < static_cast(entry_offset + entry.size)) { + break; + } + + if (offset <= entry_offset && entry_offset < static_cast(offset + actual_size)) { + actual_size = static_cast(entry_offset - offset); + } + } + } + + /* Clear the out range. */ + out_range->first = 0; + out_range->second = 0; + + /* If we located an entry, use it. */ + if (index != max_cache_entry_count) { + auto &entry = this->entries[index]; + + /* Get the range of the found entry. */ + if (entry.is_write_back) { + *out_range = std::make_pair(entry.memory_address, entry.memory_size); + } else { + *out_range = this->buffer_manager->AcquireCache(entry.handle); + } + + /* Get the found entry. */ + *out_entry = entry; + AMS_ASSERT(out_entry->is_valid); + AMS_ASSERT(out_entry->is_cached); + + /* Clear the entry in the cache. */ + entry.is_valid = false; + entry.handle = 0; + entry.memory_address = 0; + entry.memory_size = 0; + + /* Set the output entry. */ + out_entry->is_valid = true; + out_entry->handle = 0; + out_entry->memory_address = 0; + out_entry->memory_size = 0; + } + + /* If we don't have an out entry, allocate one. */ + if (out_range->first == 0) { + /* Ensure that the allocatable size is above a threshold. */ + const auto size_threshold = this->buffer_manager->GetTotalSize() / 8; + if (this->buffer_manager->GetTotalAllocatableSize() < size_threshold) { + R_TRY(this->FlushAllCacheEntries()); + } + + /* Decide in advance on a block alignment. */ + const size_t block_alignment = this->verification_block_size; + + /* Ensure that the size we request is valid. */ + { + AMS_ASSERT(actual_size >= 1); + actual_size = std::min(actual_size, block_alignment * 2); + } + AMS_ASSERT(actual_size >= block_alignment); + + /* Allocate a buffer. */ + R_TRY(buffers::AllocateBufferUsingBufferManagerContext(out_range, this->buffer_manager, actual_size, IBufferManager::BufferAttribute(this->buffer_level), [=](const MemoryRange &buffer) { + return buffer.first != 0 && block_alignment <= buffer.second; + }, AMS_CURRENT_FUNCTION_NAME)); + + /* Ensure our size is accurate. */ + actual_size = std::min(actual_size, out_range->second); + + /* Set the output entry. */ + out_entry->is_valid = true; + out_entry->is_write_back = false; + out_entry->is_cached = false; + out_entry->is_flushing = false; + out_entry->handle = false; + out_entry->memory_address = 0; + out_entry->memory_size = 0; + out_entry->offset = offset; + out_entry->size = actual_size; + } + + /* Ensure that we ended up with a coherent out range. */ + AMS_ASSERT(out_range->second >= out_entry->size); + + return ResultSuccess(); + } + + void BlockCacheBufferedStorage::DestroyBuffer(CacheEntry *entry, const MemoryRange &range) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->buffer_manager != nullptr); + AMS_ASSERT(entry != nullptr); + + /* Set the entry as invalid and not cached. */ + entry->is_cached = false; + entry->is_valid = false; + + /* Release the entry. */ + this->buffer_manager->DeallocateBuffer(range.first, range.second); + } + + Result BlockCacheBufferedStorage::StoreAssociateBuffer(CacheIndex *out, const MemoryRange &range, const CacheEntry &entry) { + /* Validate pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Lock our mutex. */ + std::scoped_lock lk(*this->mutex); + + /* If the entry is write-back, ensure we don't exceed certain dirtiness thresholds. */ + if (entry.is_write_back) { + R_TRY(this->ControlDirtiness()); + } + + /* Get the maximum cache entry count. */ + const CacheIndex max_cache_entry_count = static_cast(this->GetMaxCacheEntryCount()); + AMS_ASSERT(max_cache_entry_count > 0); + + /* Locate the index of an unused cache entry. */ + CacheIndex index; + for (index = 0; index < max_cache_entry_count; ++index) { + if (!this->entries[index].is_valid) { + break; + } + } + + /* If all entries are valid, we need to invalidate one. */ + if (index == max_cache_entry_count) { + /* Increment the index to invalidate. */ + this->invalidate_index = (this->invalidate_index + 1) % max_cache_entry_count; + + /* Get the entry to invalidate. */ + const CacheEntry *entry_to_invalidate = std::addressof(this->entries[this->invalidate_index]); + + /* Ensure that the entry can be invalidated. */ + AMS_ASSERT(entry_to_invalidate->is_valid); + AMS_ASSERT(!entry_to_invalidate->is_flushing); + + /* Invalidate the entry. */ + R_TRY(this->FlushCacheEntry(this->invalidate_index, true)); + + /* Check that the entry was invalidated successfully. */ + AMS_ASSERT(!entry_to_invalidate->is_valid); + AMS_ASSERT(!entry_to_invalidate->is_flushing); + + index = this->invalidate_index; + } + + /* Store the entry. */ + CacheEntry *entry_ptr = std::addressof(this->entries[index]); + *entry_ptr = entry; + + /* Assert that the entry is valid to store. */ + AMS_ASSERT(entry_ptr->is_valid); + AMS_ASSERT(entry_ptr->is_cached); + AMS_ASSERT(entry_ptr->handle == 0); + AMS_ASSERT(entry_ptr->memory_address == 0); + + /* Ensure that the new entry isn't redundant. */ + if (!ExistsRedundantCacheEntry(*entry_ptr)) { + /* Store the cache's buffer. */ + if (entry_ptr->is_write_back) { + entry_ptr->handle = 0; + entry_ptr->memory_address = range.first; + entry_ptr->memory_size = range.second; + } else { + entry_ptr->handle = this->buffer_manager->RegisterCache(range.first, range.second, IBufferManager::BufferAttribute(this->buffer_level)); + entry_ptr->memory_address = 0; + entry_ptr->memory_size = 0; + } + + /* Set the out index. */ + AMS_ASSERT(entry_ptr->is_valid); + *out = index; + this->invalidate_index = index; + } else { + /* If a redundant entry exists, we don't need the newly stored entry. */ + this->buffer_manager->DeallocateBuffer(range.first, range.second); + entry_ptr->is_valid = false; + *out = -1; + } + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::FlushCacheEntry(CacheIndex index, bool invalidate) { + /* Lock our mutex. */ + std::scoped_lock lk(*this->mutex); + + /* Get the entry. */ + CacheEntry *entry = std::addressof(this->entries[index]); + MemoryRange memory_range; + + /* Check that the entry's state allows for flush. */ + AMS_ASSERT(entry->is_valid); + AMS_ASSERT(!entry->is_flushing); + + /* If we're not write back (i.e. an invalidate is happening), just release the buffer. */ + if (!entry->is_write_back) { + AMS_ASSERT(invalidate); + + /* Get and release the buffer. */ + memory_range = this->buffer_manager->AcquireCache(entry->handle); + if (memory_range.first != 0) { + this->buffer_manager->DeallocateBuffer(memory_range.first, memory_range.second); + } + + /* The entry is no longer valid. */ + entry->is_valid = false; + + return ResultSuccess(); + } + + /* Note that we've started flushing. */ + entry->is_flushing = true; + + /* Create and check our memory range. */ + memory_range = std::make_pair(entry->memory_address, entry->memory_size); + AMS_ASSERT(memory_range.first != 0); + AMS_ASSERT(memory_range.second >= entry->size); + + /* Validate the entry's offset. */ + AMS_ASSERT(entry->offset >= 0); + AMS_ASSERT(entry->offset < this->data_size); + AMS_ASSERT(util::IsAligned(entry->offset, this->verification_block_size)); + + /* Write back the data. */ + Result result = ResultSuccess(); + size_t write_size = entry->size; + if (R_SUCCEEDED(this->last_result)) { + /* Set blocking buffer manager allocations. */ + result = this->data_storage->Write(entry->offset, reinterpret_cast(memory_range.first), write_size); + + /* Check the result. */ + AMS_ASSERT(!fs::ResultBufferAllocationFailed::Includes(result)); + } else { + result = this->last_result; + } + + /* Set that we're not write-back. */ + entry->is_write_back = false; + + /* If we're invalidating, release the buffer. Otherwise, register the flushed data. */ + if (invalidate) { + this->buffer_manager->DeallocateBuffer(memory_range.first, memory_range.second); + entry->is_valid = false; + entry->is_flushing = false; + } else { + AMS_ASSERT(entry->is_valid); + + entry->handle = this->buffer_manager->RegisterCache(memory_range.first, memory_range.second, IBufferManager::BufferAttribute(this->buffer_level)); + + entry->memory_address = 0; + entry->memory_size = 0; + entry->is_flushing = false; + } + + /* Try to succeed. */ + R_TRY(result); + + /* We succeeded. */ + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::FlushRangeCacheEntries(s64 offset, s64 size, bool invalidate) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Iterate over all entries that fall within the range. */ + Result result = ResultSuccess(); + const auto max_cache_entry_count = this->GetMaxCacheEntryCount(); + for (auto i = 0; i < max_cache_entry_count; ++i) { + auto &entry = this->entries[i]; + if (entry.is_valid && (entry.is_write_back || invalidate) && (entry.offset < (offset + size)) && (offset < static_cast(entry.offset + entry.size))) { + const auto cur_result = this->FlushCacheEntry(i, invalidate); + if (R_FAILED(cur_result) && R_SUCCEEDED(result)) { + result = cur_result; + } + } + } + + /* Try to succeed. */ + R_TRY(result); + + /* We succeeded. */ + return ResultSuccess(); + } + + void BlockCacheBufferedStorage::InvalidateRangeCacheEntries(s64 offset, s64 size) { + /* Validate pre-conditions. */ + AMS_ASSERT(this->data_storage != nullptr); + AMS_ASSERT(this->buffer_manager != nullptr); + + /* Iterate over all entries that fall within the range. */ + const auto max_cache_entry_count = this->GetMaxCacheEntryCount(); + for (auto i = 0; i < max_cache_entry_count; ++i) { + auto &entry = this->entries[i]; + if (entry.is_valid && (entry.offset < (offset + size)) && (offset < static_cast(entry.offset + entry.size))) { + if (entry.is_write_back) { + AMS_ASSERT(entry.memory_address != 0 && entry.handle == 0); + this->buffer_manager->DeallocateBuffer(entry.memory_address, entry.memory_size); + } else { + AMS_ASSERT(entry.memory_address == 0 && entry.handle != 0); + const auto memory_range = this->buffer_manager->AcquireCache(entry.handle); + if (memory_range.first != 0) { + this->buffer_manager->DeallocateBuffer(memory_range.first, memory_range.second); + } + } + + /* Mark the entry as invalidated. */ + entry.is_valid = false; + entry.is_write_back = false; + entry.is_flushing = true; + } + } + } + + Result BlockCacheBufferedStorage::FlushAllCacheEntries() { + R_TRY(this->FlushRangeCacheEntries(0, std::numeric_limits::max(), false)); + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::InvalidateAllCacheEntries() { + R_TRY(this->FlushRangeCacheEntries(0, std::numeric_limits::max(), true)); + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::ControlDirtiness() { + /* Get and validate the max cache entry count. */ + const auto max_cache_entry_count = this->GetMaxCacheEntryCount(); + AMS_ASSERT(max_cache_entry_count > 0); + + /* Get size metrics from the buffer manager. */ + const auto total_size = this->buffer_manager->GetTotalSize(); + const auto allocatable_size = this->buffer_manager->GetTotalAllocatableSize(); + + /* If we have enough allocatable space, we don't need to do anything. */ + R_SUCCEED_IF(allocatable_size >= total_size / 4); + + /* Setup for flushing dirty entries. */ + auto threshold = 2; + auto dirty_count = 0; + auto flushed_index = this->invalidate_index; + + /* Iterate over all entries (starting with the invalidate index), and flush dirty entries once threshold is met. */ + for (auto i = 0; i < max_cache_entry_count; ++i) { + auto index = (this->invalidate_index + 1 + i) % max_cache_entry_count; + if (this->entries[index].is_valid && this->entries[index].is_write_back) { + ++dirty_count; + if (threshold <= dirty_count) { + R_TRY(this->FlushCacheEntry(index, false)); + flushed_index = index; + } + } + } + + /* Update the invalidate index. */ + this->invalidate_index = flushed_index; + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::UpdateLastResult(Result result) { + /* Update the last result. */ + if (R_FAILED(result) && !fs::ResultBufferAllocationFailed::Includes(result) && R_SUCCEEDED(this->last_result)) { + this->last_result = result; + } + + /* Try to succeed with the result. */ + R_TRY(result); + + /* We succeeded. */ + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::ReadHeadCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 *offset, s64 *aligned_offset, s64 aligned_offset_end, char **buffer, size_t *size) { + /* Valdiate pre-conditions. */ + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + AMS_ASSERT(out_cache_needed != nullptr); + AMS_ASSERT(offset != nullptr); + AMS_ASSERT(aligned_offset != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(*buffer != nullptr); + AMS_ASSERT(size != nullptr); + + AMS_ASSERT(*aligned_offset < aligned_offset_end); + + /* Iterate over the region. */ + CacheEntry entry = {}; + MemoryRange memory_range = {}; + *out_cache_needed = true; + + while (*aligned_offset < aligned_offset_end) { + /* Get the associated buffer for the offset. */ + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(memory_range), std::addressof(entry), *aligned_offset, this->verification_block_size, true))); + + /* If the entry isn't cached, we're done. */ + if (!entry.is_cached) { + break; + } + + /* Set cache not needed. */ + *out_cache_needed = false; + + /* Determine the size to copy. */ + const s64 buffer_offset = *offset - entry.offset; + const size_t copy_size = std::min(*size, static_cast(entry.offset + entry.size - *offset)); + + /* Copy data from the entry. */ + std::memcpy(*buffer, reinterpret_cast(memory_range.first + buffer_offset), copy_size); + + /* Advance. */ + *buffer += copy_size; + *offset += copy_size; + *size -= copy_size; + *aligned_offset = entry.offset + entry.size; + + /* Handle the buffer. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(memory_range, std::addressof(entry)))); + } + + /* Set the output entry. */ + *out_entry = entry; + *out_range = memory_range; + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::ReadTailCache(MemoryRange *out_range, CacheEntry *out_entry, bool *out_cache_needed, s64 offset, s64 aligned_offset, s64 *aligned_offset_end, char *buffer, size_t *size) { + /* Valdiate pre-conditions. */ + AMS_ASSERT(out_range != nullptr); + AMS_ASSERT(out_entry != nullptr); + AMS_ASSERT(out_cache_needed != nullptr); + AMS_ASSERT(aligned_offset_end != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + + AMS_ASSERT(aligned_offset < *aligned_offset_end); + + /* Iterate over the region. */ + CacheEntry entry = {}; + MemoryRange memory_range = {}; + *out_cache_needed = true; + + while (aligned_offset < *aligned_offset_end) { + /* Get the associated buffer for the offset. */ + R_TRY(this->UpdateLastResult(this->GetAssociateBuffer(std::addressof(memory_range), std::addressof(entry), *aligned_offset_end - this->verification_block_size, this->verification_block_size, true))); + + /* If the entry isn't cached, we're done. */ + if (!entry.is_cached) { + break; + } + + /* Set cache not needed. */ + *out_cache_needed = false; + + /* Determine the size to copy. */ + const s64 buffer_offset = std::max(static_cast(0), offset - entry.offset); + const size_t copy_size = std::min(*size, static_cast(offset + *size - entry.offset)); + + /* Copy data from the entry. */ + std::memcpy(buffer + *size - copy_size, reinterpret_cast(memory_range.first + buffer_offset), copy_size); + + /* Advance. */ + *size -= copy_size; + *aligned_offset_end = entry.offset; + + /* Handle the buffer. */ + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(memory_range, std::addressof(entry)))); + } + + /* Set the output entry. */ + *out_entry = entry; + *out_range = memory_range; + + return ResultSuccess(); + } + + Result BlockCacheBufferedStorage::BulkRead(s64 offset, void *buffer, size_t size, MemoryRange *range_head, MemoryRange *range_tail, CacheEntry *entry_head, CacheEntry *entry_tail, bool head_cache_needed, bool tail_cache_needed) { + /* Validate pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(range_head != nullptr); + AMS_ASSERT(range_tail != nullptr); + AMS_ASSERT(entry_head != nullptr); + AMS_ASSERT(entry_tail != nullptr); + + /* Determine bulk read offsets. */ + const s64 read_offset = offset; + const size_t read_size = size; + const s64 aligned_offset = util::AlignDown(read_offset, this->verification_block_size); + const s64 aligned_offset_end = util::AlignUp(read_offset + read_size, this->verification_block_size); + char *dst = static_cast(buffer); + + /* Prepare to do our reads. */ + auto head_guard = SCOPE_GUARD { this->DestroyBuffer(entry_head, *range_head); }; + auto tail_guard = SCOPE_GUARD { this->DestroyBuffer(entry_tail, *range_tail); }; + + /* Flush the entries. */ + R_TRY(this->UpdateLastResult(this->FlushRangeCacheEntries(aligned_offset, aligned_offset_end - aligned_offset, false))); + + /* Determine the buffer to read into. */ + PooledBuffer pooled_buffer; + const size_t buffer_size = static_cast(aligned_offset_end - aligned_offset); + char *read_buffer = nullptr; + if (read_offset == aligned_offset && read_size == buffer_size) { + read_buffer = dst; + } else if (tail_cache_needed && entry_tail->offset == aligned_offset && entry_tail->size == buffer_size) { + read_buffer = reinterpret_cast(range_tail->first); + } else if (head_cache_needed && entry_head->offset == aligned_offset && entry_head->size == buffer_size) { + read_buffer = reinterpret_cast(range_head->first); + } else { + pooled_buffer.AllocateParticularlyLarge(buffer_size, 1); + R_UNLESS(pooled_buffer.GetSize() >= buffer_size, fs::ResultAllocationFailurePooledBufferNotEnoughSize()); + read_buffer = pooled_buffer.GetBuffer(); + } + + /* Read the data. */ + R_TRY(this->data_storage->Read(aligned_offset, read_buffer, buffer_size)); + + /* Copy the data out. */ + if (dst != read_buffer) { + std::memcpy(dst, read_buffer + read_offset - aligned_offset, read_size); + } + + /* Create a helper to populate our caches. */ + const auto PopulateCacheFromPooledBuffer = [&](CacheEntry *entry, MemoryRange *range) { + AMS_ASSERT(entry != nullptr); + AMS_ASSERT(range != nullptr); + + if (aligned_offset <= entry->offset && entry->offset + entry->size <= aligned_offset + buffer_size) { + AMS_ASSERT(!entry->is_cached); + if (reinterpret_cast(range->first) != read_buffer) { + std::memcpy(reinterpret_cast(range->first), read_buffer + entry->offset - aligned_offset, entry->size); + } + entry->is_cached = true; + } + }; + + /* Populate tail cache if needed. */ + if (tail_cache_needed) { + PopulateCacheFromPooledBuffer(entry_tail, range_tail); + } + + /* Populate head cache if needed. */ + if (head_cache_needed) { + PopulateCacheFromPooledBuffer(entry_head, range_head); + } + + /* If both entries are cached, one may contain the other; in that case, we need only the larger entry. */ + if (entry_head->is_cached && entry_tail->is_cached) { + if (entry_tail->offset <= entry_head->offset && entry_head->offset + entry_head->size <= entry_tail->offset + entry_tail->size) { + entry_head->is_cached = false; + } else if (entry_head->offset <= entry_tail->offset && entry_tail->offset + entry_tail->size <= entry_head->offset + entry_head->size) { + entry_tail->is_cached = false; + } + } + + /* Destroy the tail cache. */ + tail_guard.Cancel(); + if (entry_tail->is_cached) { + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(*range_tail, entry_tail))); + } else { + this->DestroyBuffer(entry_tail, *range_tail); + } + + /* Destroy the head cache. */ + head_guard.Cancel(); + if (entry_head->is_cached) { + R_TRY(this->UpdateLastResult(this->StoreOrDestroyBuffer(*range_head, entry_head))); + } else { + this->DestroyBuffer(entry_head, *range_head); + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/save/fssystem_buffered_storage.cpp b/libraries/libstratosphere/source/fssystem/save/fssystem_buffered_storage.cpp new file mode 100644 index 000000000..d776fdba9 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/save/fssystem_buffered_storage.cpp @@ -0,0 +1,1082 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem::save { + + namespace { + + constexpr inline uintptr_t InvalidAddress = 0; + constexpr inline s64 InvalidOffset = std::numeric_limits::max(); + + } + + class BufferedStorage::Cache : public ::ams::fs::impl::Newable { + private: + struct FetchParameter { + s64 offset; + void *buffer; + size_t size; + }; + static_assert(std::is_pod::value); + private: + BufferedStorage *buffered_storage; + std::pair memory_range; + IBufferManager::CacheHandle cache_handle; + s64 offset; + std::atomic is_valid; + std::atomic is_dirty; + u8 reserved[2]; + s32 reference_count; + Cache *next; + Cache *prev; + public: + Cache() : buffered_storage(nullptr), memory_range(InvalidAddress, 0), cache_handle(), offset(InvalidOffset), is_valid(false), is_dirty(false), reference_count(1), next(nullptr), prev(nullptr) { + /* ... */ + } + + ~Cache() { + this->Finalize(); + } + + void Initialize(BufferedStorage *bs) { + AMS_ASSERT(bs != nullptr); + AMS_ASSERT(this->buffered_storage == nullptr); + + this->buffered_storage = bs; + this->Link(); + } + + void Finalize() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->reference_count == 0); + + /* If we're valid, acquire our cache handle and free our buffer. */ + if (this->IsValid()) { + const auto buffer_manager = this->buffered_storage->buffer_manager; + if (!this->is_dirty) { + AMS_ASSERT(this->memory_range.first == InvalidAddress); + this->memory_range = buffer_manager->AcquireCache(this->cache_handle); + } + if (this->memory_range.first != InvalidAddress) { + buffer_manager->DeallocateBuffer(this->memory_range.first, this->memory_range.second); + this->memory_range.first = InvalidAddress; + this->memory_range.second = 0; + } + } + + /* Clear all our members. */ + this->buffered_storage = nullptr; + this->offset = InvalidOffset; + this->is_valid = false; + this->is_dirty = false; + this->next = nullptr; + this->prev = nullptr; + } + + void Link() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->reference_count > 0); + + if ((--this->reference_count) == 0) { + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + + if (this->buffered_storage->next_fetch_cache == nullptr) { + this->buffered_storage->next_fetch_cache = this; + this->next = this; + this->prev = this; + } else { + /* Check against a cache being registered twice. */ + { + auto cache = this->buffered_storage->next_fetch_cache; + do { + if (cache->IsValid() && this->Hits(cache->offset, this->buffered_storage->block_size)) { + this->is_valid = false; + break; + } + cache = cache->next; + } while (cache != this->buffered_storage->next_fetch_cache); + } + + /* Link into the fetch list. */ + { + AMS_ASSERT(this->buffered_storage->next_fetch_cache->prev != nullptr); + AMS_ASSERT(this->buffered_storage->next_fetch_cache->prev->next == this->buffered_storage->next_fetch_cache); + this->next = this->buffered_storage->next_fetch_cache; + this->prev = this->buffered_storage->next_fetch_cache->prev; + this->next->prev = this; + this->prev->next = this; + } + + /* Insert invalid caches at the start of the list. */ + if (!this->IsValid()) { + this->buffered_storage->next_fetch_cache = this; + } + } + + /* If we're not valid, clear our offset. */ + if (!this->IsValid()) { + this->offset = InvalidOffset; + this->is_dirty = false; + } + + /* Ensure our buffer state is coherent. */ + if (this->memory_range.first != InvalidAddress && !this->is_dirty) { + if (this->IsValid()) { + this->cache_handle = this->buffered_storage->buffer_manager->RegisterCache(this->memory_range.first, this->memory_range.second, IBufferManager::BufferAttribute()); + } else { + this->buffered_storage->buffer_manager->DeallocateBuffer(this->memory_range.first, this->memory_range.second); + } + this->memory_range.first = InvalidAddress; + this->memory_range.second = 0; + } + } + } + + void Unlink() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->reference_count >= 0); + + if ((++this->reference_count) == 1) { + AMS_ASSERT(this->next != nullptr); + AMS_ASSERT(this->prev != nullptr); + AMS_ASSERT(this->next->prev == this); + AMS_ASSERT(this->prev->next == this); + + if (this->buffered_storage->next_fetch_cache == this) { + if (this->next != this) { + this->buffered_storage->next_fetch_cache = this->next; + } else { + this->buffered_storage->next_fetch_cache = nullptr; + } + } + + this->buffered_storage->next_acquire_cache = this; + + this->next->prev = this->prev; + this->prev->next = this->next; + this->next = nullptr; + this->prev = nullptr; + } else { + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + } + } + + void Read(s64 offset, void *buffer, size_t size) const { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(this->Hits(offset, 1)); + AMS_ASSERT(this->memory_range.first != InvalidAddress); + + const auto read_offset = offset - this->offset; + const auto readable_offset_max = this->buffered_storage->block_size - size; + const auto cache_buffer = reinterpret_cast(this->memory_range.first) + read_offset; + AMS_ASSERT(read_offset >= 0); + AMS_ASSERT(static_cast(read_offset) <= readable_offset_max); + + std::memcpy(buffer, cache_buffer, size); + } + + void Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(this->Hits(offset, 1)); + AMS_ASSERT(this->memory_range.first != InvalidAddress); + + const auto write_offset = offset - this->offset; + const auto writable_offset_max = this->buffered_storage->block_size - size; + const auto cache_buffer = reinterpret_cast(this->memory_range.first) + write_offset; + AMS_ASSERT(write_offset >= 0); + AMS_ASSERT(static_cast(write_offset) <= writable_offset_max); + + std::memcpy(cache_buffer, buffer, size); + this->is_dirty = true; + } + + Result Flush() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(this->IsValid()); + + if (this->is_dirty) { + AMS_ASSERT(this->memory_range.first != InvalidAddress); + + const auto base_size = this->buffered_storage->base_storage_size; + const auto block_size = static_cast(this->buffered_storage->block_size); + const auto flush_size = static_cast(std::min(block_size, base_size - this->offset)); + + auto &base_storage = this->buffered_storage->base_storage; + const auto cache_buffer = reinterpret_cast(this->memory_range.first); + + R_TRY(base_storage.Write(this->offset, cache_buffer, flush_size)); + this->is_dirty = false; + + buffers::EnableBlockingBufferManagerAllocation(); + } + + return ResultSuccess(); + } + + const std::pair PrepareFetch() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(this->IsValid()); + AMS_ASSERT(this->buffered_storage->mutex.IsLockedByCurrentThread()); + + std::pair result(ResultSuccess(), false); + if (this->reference_count == 1) { + result.first = this->Flush(); + if (R_SUCCEEDED(result.first)) { + this->is_valid = false; + this->reference_count = 0; + result.second = true; + } + } + + return result; + } + + void UnprepareFetch() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!this->is_dirty); + AMS_ASSERT(this->buffered_storage->mutex.IsLockedByCurrentThread()); + + this->is_valid = true; + this->reference_count = 1; + } + + Result Fetch(s64 offset) { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!this->is_dirty); + + if (this->memory_range.first == InvalidAddress) { + R_TRY(this->AllocateFetchBuffer()); + } + + FetchParameter fetch_param = {}; + this->CalcFetchParameter(std::addressof(fetch_param), offset); + + auto &base_storage = this->buffered_storage->base_storage; + R_TRY(base_storage.Read(fetch_param.offset, fetch_param.buffer, fetch_param.size)); + this->offset = fetch_param.offset; + AMS_ASSERT(this->Hits(offset, 1)); + + return ResultSuccess(); + } + + Result FetchFromBuffer(s64 offset, const void *buffer, size_t buffer_size) { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->next == nullptr); + AMS_ASSERT(this->prev == nullptr); + AMS_ASSERT(!this->IsValid()); + AMS_ASSERT(!this->is_dirty); + AMS_ASSERT(util::IsAligned(offset, this->buffered_storage->block_size)); + + if (this->memory_range.first == InvalidAddress) { + R_TRY(this->AllocateFetchBuffer()); + } + + FetchParameter fetch_param = {}; + this->CalcFetchParameter(std::addressof(fetch_param), offset); + AMS_ASSERT(fetch_param.offset == offset); + AMS_ASSERT(fetch_param.size <= buffer_size); + + std::memcpy(fetch_param.buffer, buffer, fetch_param.size); + this->offset = fetch_param.offset; + AMS_ASSERT(this->Hits(offset, 1)); + + return ResultSuccess(); + } + + bool TryAcquireCache() { + AMS_ASSERT(this->buffered_storage != nullptr); + AMS_ASSERT(this->buffered_storage->buffer_manager != nullptr); + AMS_ASSERT(this->IsValid()); + + if (this->memory_range.first != InvalidAddress) { + return true; + } else { + this->memory_range = this->buffered_storage->buffer_manager->AcquireCache(this->cache_handle); + this->is_valid = this->memory_range.first != InvalidAddress; + return this->is_valid; + } + } + + void Invalidate() { + AMS_ASSERT(this->buffered_storage != nullptr); + this->is_valid = false; + } + + bool IsValid() const { + AMS_ASSERT(this->buffered_storage != nullptr); + return this->is_valid || this->reference_count > 0; + } + + bool IsDirty() const { + AMS_ASSERT(this->buffered_storage != nullptr); + return this->is_dirty; + } + + bool Hits(s64 offset, s64 size) const { + AMS_ASSERT(this->buffered_storage != nullptr); + const auto block_size = static_cast(this->buffered_storage->block_size); + return (offset < this->offset + block_size) && (this->offset < offset + size); + } + private: + Result AllocateFetchBuffer() { + IBufferManager *buffer_manager = this->buffered_storage->buffer_manager; + AMS_ASSERT(buffer_manager->AcquireCache(this->cache_handle).first == InvalidAddress); + + auto range_guard = SCOPE_GUARD { this->memory_range.first = InvalidAddress; }; + R_TRY(buffers::AllocateBufferUsingBufferManagerContext(std::addressof(this->memory_range), buffer_manager, this->buffered_storage->block_size, IBufferManager::BufferAttribute(), [](const std::pair &buffer) { + return buffer.first != 0; + }, AMS_CURRENT_FUNCTION_NAME)); + + range_guard.Cancel(); + return ResultSuccess(); + } + + void CalcFetchParameter(FetchParameter *out, s64 offset) const { + AMS_ASSERT(out != nullptr); + + const auto block_size = static_cast(this->buffered_storage->block_size); + const auto cache_offset = util::AlignDown(offset, this->buffered_storage->block_size); + const auto base_size = this->buffered_storage->base_storage_size; + const auto cache_size = static_cast(std::min(block_size, base_size - cache_offset)); + const auto cache_buffer = reinterpret_cast(this->memory_range.first); + AMS_ASSERT(offset >= 0); + AMS_ASSERT(offset < base_size); + + out->offset = cache_offset; + out->buffer = cache_buffer; + out->size = cache_size; + } + }; + + class BufferedStorage::SharedCache { + NON_COPYABLE(SharedCache); + NON_MOVEABLE(SharedCache); + friend class UniqueCache; + private: + Cache *cache; + Cache *start_cache; + BufferedStorage *buffered_storage; + public: + explicit SharedCache(BufferedStorage *bs) : cache(nullptr), start_cache(bs->next_acquire_cache), buffered_storage(bs) { + AMS_ASSERT(this->buffered_storage != nullptr); + } + + ~SharedCache() { + std::scoped_lock lk(buffered_storage->mutex); + this->Release(); + } + + bool AcquireNextOverlappedCache(s64 offset, s64 size) { + AMS_ASSERT(this->buffered_storage != nullptr); + + auto is_first = this->cache == nullptr; + const auto start = is_first ? this->start_cache : this->cache + 1; + + AMS_ASSERT(start >= this->buffered_storage->caches.get()); + AMS_ASSERT(start <= this->buffered_storage->caches.get() + this->buffered_storage->cache_count); + + std::scoped_lock lk(this->buffered_storage->mutex); + + this->Release(); + AMS_ASSERT(this->cache == nullptr); + + for (auto cache = start; true; ++cache) { + if (this->buffered_storage->caches.get() + this->buffered_storage->cache_count <= cache) { + cache = this->buffered_storage->caches.get(); + } + if (!is_first && cache == this->start_cache) { + break; + } + if (cache->IsValid() && cache->Hits(offset, size) && cache->TryAcquireCache()) { + cache->Unlink(); + this->cache = cache; + return true; + } + is_first = false; + } + + this->cache = nullptr; + return false; + } + + bool AcquireNextDirtyCache() { + AMS_ASSERT(this->buffered_storage != nullptr); + const auto start = this->cache != nullptr ? this->cache + 1 : this->buffered_storage->caches.get(); + const auto end = this->buffered_storage->caches.get() + this->buffered_storage->cache_count; + + AMS_ASSERT(start >= this->buffered_storage->caches.get()); + AMS_ASSERT(start <= end); + + this->Release(); + AMS_ASSERT(this->cache == nullptr); + + for (auto cache = start; cache < end; ++cache) { + if (cache->IsValid() && cache->IsDirty() && cache->TryAcquireCache()) { + cache->Unlink(); + this->cache = cache; + return true; + } + } + + this->cache = nullptr; + return false; + } + + bool AcquireNextValidCache() { + AMS_ASSERT(this->buffered_storage != nullptr); + const auto start = this->cache != nullptr ? this->cache + 1 : this->buffered_storage->caches.get(); + const auto end = this->buffered_storage->caches.get() + this->buffered_storage->cache_count; + + AMS_ASSERT(start >= this->buffered_storage->caches.get()); + AMS_ASSERT(start <= end); + + this->Release(); + AMS_ASSERT(this->cache == nullptr); + + for (auto cache = start; cache < end; ++cache) { + if (cache->IsValid() && cache->TryAcquireCache()) { + cache->Unlink(); + this->cache = cache; + return true; + } + } + + this->cache = nullptr; + return false; + } + + bool AcquireFetchableCache() { + AMS_ASSERT(this->buffered_storage != nullptr); + + std::scoped_lock lk(this->buffered_storage->mutex); + + this->Release(); + AMS_ASSERT(this->cache == nullptr); + + this->cache = this->buffered_storage->next_fetch_cache; + if (this->cache != nullptr) { + if (this->cache->IsValid()) { + this->cache->TryAcquireCache(); + } + this->cache->Unlink(); + } + + return this->cache != nullptr; + } + + void Read(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(this->cache != nullptr); + this->cache->Read(offset, buffer, size); + } + + void Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(this->cache != nullptr); + this->cache->Write(offset, buffer, size); + } + + Result Flush() { + AMS_ASSERT(this->cache != nullptr); + return this->cache->Flush(); + } + + void Invalidate() { + AMS_ASSERT(this->cache != nullptr); + return this->cache->Invalidate(); + } + + bool Hits(s64 offset, s64 size) const { + AMS_ASSERT(this->cache != nullptr); + return this->cache->Hits(offset, size); + } + private: + void Release() { + if (this->cache != nullptr) { + AMS_ASSERT(this->buffered_storage->caches.get() <= this->cache); + AMS_ASSERT(this->cache <= this->buffered_storage->caches.get() + this->buffered_storage->cache_count); + + this->cache->Link(); + this->cache = nullptr; + } + } + }; + + class BufferedStorage::UniqueCache { + NON_COPYABLE(UniqueCache); + NON_MOVEABLE(UniqueCache); + private: + Cache *cache; + BufferedStorage *buffered_storage; + public: + explicit UniqueCache(BufferedStorage *bs) : cache(nullptr), buffered_storage(bs) { + AMS_ASSERT(this->buffered_storage != nullptr); + } + + ~UniqueCache() { + if (this->cache != nullptr) { + std::scoped_lock lk(this->buffered_storage->mutex); + this->cache->UnprepareFetch(); + } + } + + const std::pair Upgrade(const SharedCache &shared_cache) { + AMS_ASSERT(this->buffered_storage == shared_cache.buffered_storage); + AMS_ASSERT(shared_cache.cache != nullptr); + + std::scoped_lock lk(this->buffered_storage->mutex); + const auto result = shared_cache.cache->PrepareFetch(); + if (R_SUCCEEDED(result.first) && result.second) { + this->cache = shared_cache.cache; + } + return result; + } + + Result Fetch(s64 offset) { + AMS_ASSERT(this->cache != nullptr); + return this->cache->Fetch(offset); + } + + Result FetchFromBuffer(s64 offset, const void *buffer, size_t buffer_size) { + AMS_ASSERT(this->cache != nullptr); + R_TRY(this->cache->FetchFromBuffer(offset, buffer, buffer_size)); + return ResultSuccess(); + } + }; + + BufferedStorage::BufferedStorage() : base_storage(), buffer_manager(), block_size(), base_storage_size(), caches(), cache_count(), next_acquire_cache(), next_fetch_cache(), mutex(false), bulk_read_enabled() { + /* ... */ + } + + BufferedStorage::~BufferedStorage() { + this->Finalize(); + } + + Result BufferedStorage::Initialize(fs::SubStorage base_storage, IBufferManager *buffer_manager, size_t block_size, s32 buffer_count) { + AMS_ASSERT(buffer_manager != nullptr); + AMS_ASSERT(block_size > 0); + AMS_ASSERT(util::IsPowerOfTwo(block_size)); + AMS_ASSERT(buffer_count > 0); + + /* Get the base storage size. */ + R_TRY(base_storage.GetSize(std::addressof(this->base_storage_size))); + + /* Set members. */ + this->base_storage = base_storage; + this->buffer_manager = buffer_manager; + this->block_size = block_size; + this->cache_count = cache_count; + + /* Allocate the caches. */ + this->caches.reset(new Cache[buffer_count]); + R_UNLESS(this->caches != nullptr, fs::ResultAllocationFailureInBufferedStorageA()); + + /* Initialize the caches. */ + for (auto i = 0; i < buffer_count; i++) { + this->caches[i].Initialize(this); + } + + this->next_acquire_cache = std::addressof(this->caches[0]); + return ResultSuccess(); + } + + void BufferedStorage::Finalize() { + this->base_storage = fs::SubStorage(); + this->base_storage_size = 0; + this->caches.reset(); + this->cache_count = 0; + this->next_fetch_cache = nullptr; + } + + Result BufferedStorage::Read(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Do the read. */ + R_TRY(this->ReadCore(offset, buffer, size)); + return ResultSuccess(); + } + + Result BufferedStorage::Write(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(this->IsInitialized()); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Do the write. */ + R_TRY(this->WriteCore(offset, buffer, size)); + return ResultSuccess(); + } + + Result BufferedStorage::GetSize(s64 *out) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->IsInitialized()); + + *out = this->base_storage_size; + return ResultSuccess(); + } + + Result BufferedStorage::SetSize(s64 size) { + AMS_ASSERT(this->IsInitialized()); + const s64 prev_size = this->base_storage_size; + if (prev_size < size) { + /* Prepare to expand. */ + if (!util::IsAligned(prev_size, this->block_size)) { + SharedCache cache(this); + const auto invalidate_offset = prev_size; + const auto invalidate_size = size - prev_size; + if (cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + /* AMS_ASSERT(!cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)); */ + } + } else if (size < prev_size) { + /* Prepare to do a shrink. */ + SharedCache cache(this); + const auto invalidate_offset = prev_size; + const auto invalidate_size = size - prev_size; + const auto is_fragment = util::IsAligned(size, this->block_size); + while (cache.AcquireNextOverlappedCache(invalidate_offset, invalidate_size)) { + if (is_fragment && cache.Hits(invalidate_offset, 1)) { + R_TRY(cache.Flush()); + } + cache.Invalidate(); + } + } + + /* Set the size. */ + R_TRY(this->base_storage.SetSize(size)); + + /* Get our new size. */ + s64 new_size = 0; + R_TRY(this->base_storage.GetSize(std::addressof(new_size))); + + this->base_storage_size = new_size; + return ResultSuccess(); + } + + Result BufferedStorage::Flush() { + AMS_ASSERT(this->IsInitialized()); + + /* Flush caches. */ + SharedCache cache(this); + while (cache.AcquireNextDirtyCache()) { + R_TRY(cache.Flush()); + } + + /* Flush the base storage. */ + R_TRY(this->base_storage.Flush()); + return ResultSuccess(); + } + + Result BufferedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + AMS_ASSERT(this->IsInitialized()); + + /* Invalidate caches, if we should. */ + if (op_id == fs::OperationId::InvalidateCache) { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(offset, size)) { + cache.Invalidate(); + } + } + + return this->base_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size); + } + + void BufferedStorage::InvalidateCaches() { + AMS_ASSERT(this->IsInitialized()); + + SharedCache cache(this); + while (cache.AcquireNextValidCache()) { + cache.Invalidate(); + } + } + + Result BufferedStorage::PrepareAllocation() { + const auto flush_threshold = this->buffer_manager->GetTotalSize() / 8; + if (this->buffer_manager->GetTotalAllocatableSize() < flush_threshold) { + R_TRY(this->Flush()); + } + return ResultSuccess(); + } + + Result BufferedStorage::ControlDirtiness() { + const auto flush_threshold = this->buffer_manager->GetTotalSize() / 4; + if (this->buffer_manager->GetTotalAllocatableSize() < flush_threshold) { + s32 dirty_count = 0; + SharedCache cache(this); + while (cache.AcquireNextDirtyCache()) { + if ((++dirty_count) > 1) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + } + return ResultSuccess(); + } + + Result BufferedStorage::ReadCore(s64 offset, void *buffer, size_t size) { + AMS_ASSERT(this->caches != nullptr); + AMS_ASSERT(buffer != nullptr); + + /* Validate the offset. */ + const auto base_storage_size = this->base_storage_size; + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset <= base_storage_size, fs::ResultInvalidOffset()); + + /* Setup tracking variables. */ + size_t remaining_size = static_cast(std::min(size, base_storage_size - offset)); + s64 cur_offset = offset; + s64 buf_offset = 0; + + /* Determine what caches are needed, if we have bulk read set. */ + if (this->bulk_read_enabled) { + /* Check head cache. */ + const auto head_cache_needed = this->ReadHeadCache(std::addressof(cur_offset), buffer, std::addressof(remaining_size), std::addressof(buf_offset)); + R_SUCCEED_IF(remaining_size == 0); + + /* Check tail cache. */ + const auto tail_cache_needed = this->ReadTailCache(cur_offset, buffer, std::addressof(remaining_size), buf_offset); + R_SUCCEED_IF(remaining_size == 0); + + /* Perform bulk reads. */ + constexpr size_t BulkReadSizeMax = 2_MB; + if (remaining_size <= BulkReadSizeMax) { + do { + /* Try to do a bulk read. */ + R_TRY_CATCH(this->BulkRead(cur_offset, static_cast(buffer) + buf_offset, remaining_size, head_cache_needed, tail_cache_needed)) { + R_CATCH(fs::ResultAllocationFailurePooledBufferNotEnoughSize) { + /* If the read fails due to insufficient pooled buffer size, */ + /* then we want to fall back to the normal read path. */ + break; + } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } while(0); + } + } + + /* Repeatedly read until we're done. */ + while (remaining_size > 0) { + /* Determine how much to read this iteration. */ + auto *cur_dst = static_cast(buffer) + buf_offset; + size_t cur_size = 0; + + if (!util::IsAligned(cur_offset, this->block_size)) { + const size_t aligned_size = this->block_size - (cur_offset & (this->block_size - 1)); + cur_size = std::min(aligned_size, remaining_size); + } else if (remaining_size < this->block_size) { + cur_size = remaining_size; + } else { + cur_size = util::AlignDown(remaining_size, this->block_size); + } + + if (cur_size <= this->block_size) { + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(this->PrepareAllocation()); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.Fetch(cur_offset)); + break; + } + } + R_TRY(this->ControlDirtiness()); + } + cache.Read(cur_offset, cur_dst, cur_size); + } else { + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + R_TRY(this->base_storage.Read(cur_offset, cur_dst, cur_size)); + } + + remaining_size -= cur_size; + cur_offset += cur_size; + buf_offset += cur_size; + } + + return ResultSuccess(); + } + + bool BufferedStorage::ReadHeadCache(s64 *offset, void *buffer, size_t *size, s64 *buffer_offset) { + AMS_ASSERT(offset != nullptr); + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + AMS_ASSERT(buffer_offset != nullptr); + + bool is_cache_needed = !util::IsAligned(*offset, this->block_size); + + while (*size > 0) { + size_t cur_size = 0; + + if (!util::IsAligned(*offset, this->block_size)) { + const s64 aligned_size = util::AlignUp(*offset, this->block_size) - *offset; + cur_size = std::min(aligned_size, static_cast(*size)); + } else if (*size < this->block_size) { + cur_size = *size; + } else { + cur_size = this->block_size; + } + + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(*offset, cur_size)) { + break; + } + + cache.Read(*offset, static_cast(buffer) + *buffer_offset, cur_size); + *offset += cur_size; + *buffer_offset += cur_size; + *size -= cur_size; + is_cache_needed = false; + } + + return is_cache_needed; + } + + bool BufferedStorage::ReadTailCache(s64 offset, void *buffer, size_t *size, s64 buffer_offset) { + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size != nullptr); + + bool is_cache_needed = !util::IsAligned(offset + *size, this->block_size); + + while (*size > 0) { + const s64 cur_offset_end = offset + *size; + size_t cur_size = 0; + + if (!util::IsAligned(offset, this->block_size)) { + const s64 aligned_size = cur_offset_end - util::AlignDown(cur_offset_end, this->block_size); + cur_size = std::min(aligned_size, static_cast(*size)); + } else if (*size < this->block_size) { + cur_size = *size; + } else { + cur_size = this->block_size; + } + + const s64 cur_offset = cur_offset_end - static_cast(cur_size); + AMS_ASSERT(cur_offset >= 0); + + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + break; + } + + cache.Read(cur_offset, static_cast(buffer) + buffer_offset + cur_offset - offset, cur_size); + *size -= cur_size; + is_cache_needed = false; + } + + return is_cache_needed; + } + + Result BufferedStorage::BulkRead(s64 offset, void *buffer, size_t size, bool head_cache_needed, bool tail_cache_needed) { + /* Determine aligned extents. */ + const s64 aligned_offset = util::AlignDown(offset, this->block_size); + const s64 aligned_offset_end = std::min(util::AlignUp(offset + static_cast(size), this->block_size), this->base_storage_size); + const s64 aligned_size = aligned_offset_end - aligned_offset; + + /* Allocate a work buffer. */ + char *work_buffer = nullptr; + PooledBuffer pooled_buffer; + if (offset == aligned_offset && size == static_cast(aligned_size)) { + work_buffer = static_cast(buffer); + } else { + pooled_buffer.AllocateParticularlyLarge(static_cast(aligned_size), 1); + R_UNLESS(static_cast(pooled_buffer.GetSize()) >= aligned_size, fs::ResultAllocationFailurePooledBufferNotEnoughSize()); + work_buffer = pooled_buffer.GetBuffer(); + } + + /* Ensure cache is coherent. */ + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(aligned_offset, aligned_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + + /* Read from the base storage. */ + R_TRY(this->base_storage.Read(aligned_offset, work_buffer, static_cast(aligned_size))); + if (work_buffer != static_cast(buffer)) { + std::memcpy(buffer, work_buffer + offset - aligned_offset, size); + } + + bool cached = false; + + /* Handle head cache if needed. */ + if (head_cache_needed) { + R_TRY(this->PrepareAllocation()); + + SharedCache cache(this); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.FetchFromBuffer(aligned_offset, work_buffer, static_cast(aligned_size))); + break; + } + } + + cached = true; + } + + /* Handle tail cache if needed. */ + if (tail_cache_needed && (!head_cache_needed || aligned_size > static_cast(this->block_size))) { + if (!cached) { + R_TRY(this->PrepareAllocation()); + } + + SharedCache cache(this); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + const s64 tail_cache_offset = util::AlignDown(offset + static_cast(size), this->block_size); + const size_t tail_cache_size = static_cast(aligned_size - tail_cache_offset + aligned_offset); + R_TRY(fetch_cache.FetchFromBuffer(tail_cache_offset, work_buffer + tail_cache_offset - aligned_offset, tail_cache_size)); + break; + } + } + } + + if (cached) { + R_TRY(this->ControlDirtiness()); + } + + return ResultSuccess(); + } + + Result BufferedStorage::WriteCore(s64 offset, const void *buffer, size_t size) { + AMS_ASSERT(this->caches != nullptr); + AMS_ASSERT(buffer != nullptr); + + /* Validate the offset. */ + const auto base_storage_size = this->base_storage_size; + R_UNLESS(offset >= 0, fs::ResultInvalidOffset()); + R_UNLESS(offset <= base_storage_size, fs::ResultInvalidOffset()); + + /* Setup tracking variables. */ + size_t remaining_size = static_cast(std::min(size, base_storage_size - offset)); + s64 cur_offset = offset; + s64 buf_offset = 0; + + /* Repeatedly read until we're done. */ + while (remaining_size > 0) { + /* Determine how much to read this iteration. */ + const auto *cur_src = static_cast(buffer) + buf_offset; + size_t cur_size = 0; + + if (!util::IsAligned(cur_offset, this->block_size)) { + const size_t aligned_size = this->block_size - (cur_offset & (this->block_size - 1)); + cur_size = std::min(aligned_size, remaining_size); + } else if (remaining_size < this->block_size) { + cur_size = remaining_size; + } else { + cur_size = util::AlignDown(remaining_size, this->block_size); + } + + if (cur_size <= this->block_size) { + SharedCache cache(this); + if (!cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(this->PrepareAllocation()); + while (true) { + R_UNLESS(cache.AcquireFetchableCache(), fs::ResultOutOfResource()); + + UniqueCache fetch_cache(this); + const auto upgrade_result = fetch_cache.Upgrade(cache); + R_TRY(upgrade_result.first); + if (upgrade_result.second) { + R_TRY(fetch_cache.Fetch(cur_offset)); + break; + } + } + } + cache.Write(cur_offset, cur_src, cur_size); + + buffers::EnableBlockingBufferManagerAllocation(); + + R_TRY(this->ControlDirtiness()); + } else { + { + SharedCache cache(this); + while (cache.AcquireNextOverlappedCache(cur_offset, cur_size)) { + R_TRY(cache.Flush()); + cache.Invalidate(); + } + } + + R_TRY(this->base_storage.Write(cur_offset, cur_src, cur_size)); + + buffers::EnableBlockingBufferManagerAllocation(); + } + + remaining_size -= cur_size; + cur_offset += cur_size; + buf_offset += cur_size; + } + + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/save/fssystem_hierarchical_integrity_verification_storage.cpp b/libraries/libstratosphere/source/fssystem/save/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..d4dbb1c4b --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/save/fssystem_hierarchical_integrity_verification_storage.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem::save { + + namespace { + + constexpr inline u32 IntegrityVerificationStorageMagic = util::FourCC<'I','V','F','C'>::Code; + constexpr inline u32 IntegrityVerificationStorageVersion = 0x00020000; + constexpr inline u32 IntegrityVerificationStorageVersionMask = 0xFFFF0000; + + constexpr inline auto MaxSaveDataFsDataCacheEntryCount = 32; + constexpr inline auto MaxSaveDataFsHashCacheEntryCount = 4; + constexpr inline auto MaxRomFsDataCacheEntryCount = 24; + constexpr inline auto MaxRomFsHashCacheEntryCount = 8; + + constexpr inline auto AccessCountMax = 5; + constexpr inline auto AccessTimeout = TimeSpan::FromMilliSeconds(10); + + os::Semaphore g_read_semaphore(AccessCountMax, AccessCountMax); + os::Semaphore g_write_semaphore(AccessCountMax, AccessCountMax); + + constexpr inline const char MasterKey[] = "HierarchicalIntegrityVerificationStorage::Master"; + constexpr inline const char L1Key[] = "HierarchicalIntegrityVerificationStorage::L1"; + constexpr inline const char L2Key[] = "HierarchicalIntegrityVerificationStorage::L2"; + constexpr inline const char L3Key[] = "HierarchicalIntegrityVerificationStorage::L3"; + constexpr inline const char L4Key[] = "HierarchicalIntegrityVerificationStorage::L4"; + constexpr inline const char L5Key[] = "HierarchicalIntegrityVerificationStorage::L5"; + + constexpr inline const struct { + const char *key; + size_t size; + } KeyArray[] = { + { MasterKey, sizeof(MasterKey) }, + { L1Key, sizeof(L1Key) }, + { L2Key, sizeof(L2Key) }, + { L3Key, sizeof(L3Key) }, + { L4Key, sizeof(L4Key) }, + { L5Key, sizeof(L5Key) }, + }; + + } + + /* Instantiate the global random generation function. */ + HierarchicalIntegrityVerificationStorage::GenerateRandomFunction HierarchicalIntegrityVerificationStorage::s_generate_random = nullptr; + + Result HierarchicalIntegrityVerificationStorageControlArea::QuerySize(HierarchicalIntegrityVerificationSizeSet *out, const InputParam &input_param, s32 layer_count, s64 data_size) { + /* Validate preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT((static_cast(IntegrityMinLayerCount) <= layer_count) && (layer_count <= static_cast(IntegrityMaxLayerCount))); + for (s32 level = 0; level < (layer_count - 1); ++level) { + AMS_ASSERT(input_param.level_block_size[level] > 0); + AMS_ASSERT(IsPowerOfTwo(static_cast(input_param.level_block_size[level]))); + } + + /* Set the control size. */ + out->control_size = sizeof(HierarchicalIntegrityVerificationMetaInformation); + + /* Determine the level sizes. */ + s64 level_size[IntegrityMaxLayerCount]; + s32 level = layer_count - 1; + + level_size[level] = util::AlignUp(data_size, input_param.level_block_size[level - 1]); + --level; + + for (/* ... */; level > 0; --level) { + level_size[level] = util::AlignUp(level_size[level + 1] / input_param.level_block_size[level] * HashSize, input_param.level_block_size[level - 1]); + } + + /* Determine the master size. */ + level_size[0] = level_size[1] / input_param.level_block_size[0] * HashSize; + + /* Set the master size. */ + out->master_hash_size = level_size[0]; + + /* Set the level sizes. */ + for (level = 1; level < layer_count - 1; ++level) { + out->layered_hash_sizes[level - 1] = level_size[level]; + } + + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorageControlArea::Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta) { + /* Check the meta size. */ + { + s64 meta_size = 0; + R_TRY(meta_storage.GetSize(std::addressof(meta_size))); + R_UNLESS(meta_size >= static_cast(sizeof(meta)), fs::ResultInvalidSize()); + } + + /* Validate both the previous and new metas. */ + { + /* Read the previous meta. */ + HierarchicalIntegrityVerificationMetaInformation prev_meta = {}; + R_TRY(meta_storage.Read(0, std::addressof(prev_meta), sizeof(prev_meta))); + + /* Validate both magics. */ + R_UNLESS(prev_meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic()); + R_UNLESS(prev_meta.magic == meta.magic, fs::ResultIncorrectIntegrityVerificationMagic()); + + /* Validate both versions. */ + R_UNLESS(prev_meta.version == IntegrityVerificationStorageVersion, fs::ResultUnsupportedVersion()); + R_UNLESS(prev_meta.version == meta.version, fs::ResultUnsupportedVersion()); + } + + /* Write the new meta. */ + R_TRY(meta_storage.Write(0, std::addressof(meta), sizeof(meta))); + R_TRY(meta_storage.Flush()); + + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorageControlArea::Initialize(fs::SubStorage meta_storage) { + /* Check the meta size. */ + { + s64 meta_size = 0; + R_TRY(meta_storage.GetSize(std::addressof(meta_size))); + R_UNLESS(meta_size >= static_cast(sizeof(this->meta)), fs::ResultInvalidSize()); + } + + /* Set the storage and read the meta. */ + this->storage = meta_storage; + R_TRY(this->storage.Read(0, std::addressof(this->meta), sizeof(this->meta))); + + /* Validate the meta magic. */ + R_UNLESS(this->meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic()); + + /* Validate the meta version. */ + R_UNLESS((this->meta.version & IntegrityVerificationStorageVersionMask) == (IntegrityVerificationStorageVersion & IntegrityVerificationStorageVersionMask), fs::ResultUnsupportedVersion()); + + return ResultSuccess(); + } + + void HierarchicalIntegrityVerificationStorageControlArea::Finalize() { + this->storage = fs::SubStorage(); + } + + Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, os::Mutex *mtx, fs::StorageType storage_type) { + /* Validate preconditions. */ + AMS_ASSERT(bufs != nullptr); + AMS_ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); + + /* Set member variables. */ + this->max_layers = info.max_layers; + this->buffers = bufs; + this->mutex = mtx; + + /* Determine our cache counts. */ + const auto max_data_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsDataCacheEntryCount : MaxRomFsDataCacheEntryCount; + const auto max_hash_cache_entry_count = (storage_type == fs::StorageType_SaveData) ? MaxSaveDataFsHashCacheEntryCount : MaxRomFsHashCacheEntryCount; + + /* Initialize the top level verification storage. */ + { + fs::HashSalt mac; + crypto::GenerateHmacSha256Mac(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size); + this->verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast(1) << info.info[0].block_order, HashSize, this->buffers->buffers[this->max_layers - 2], mac, false, storage_type); + } + + /* Ensure we don't leak state if further initialization goes wrong. */ + auto top_verif_guard = SCOPE_GUARD { + this->verify_storages[0].Finalize(); + + this->data_size = -1; + this->buffers = nullptr; + this->mutex = nullptr; + }; + + /* Initialize the top level buffer storage. */ + R_TRY(this->buffer_storages[0].Initialize(this->buffers->buffers[0], this->mutex, std::addressof(this->verify_storages[0]), info.info[0].size, static_cast(1) << info.info[0].block_order, max_hash_cache_entry_count, false, 0x10, false, storage_type)); + auto top_buffer_guard = SCOPE_GUARD { this->buffer_storages[0].Finalize(); }; + + /* Prepare to initialize the level storages. */ + s32 level = 0; + + /* Ensure we don't leak state if further initialization goes wrong. */ + auto level_guard = SCOPE_GUARD { + this->verify_storages[level + 1].Finalize(); + for (/* ... */; level > 0; --level) { + this->buffer_storages[level].Finalize(); + this->verify_storages[level].Finalize(); + } + }; + + /* Initialize the level storages. */ + for (/* ... */; level < this->max_layers - 3; ++level) { + /* Initialize the verification storage. */ + { + fs::SubStorage buffer_storage(std::addressof(this->buffer_storages[level]), 0, info.info[level].size); + fs::HashSalt mac; + crypto::GenerateHmacSha256Mac(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); + this->verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, this->buffers->buffers[this->max_layers - 2], mac, false, storage_type); + } + + /* Initialize the buffer storage. */ + R_TRY(this->buffer_storages[level + 1].Initialize(this->buffers->buffers[level + 1], this->mutex, std::addressof(this->verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_hash_cache_entry_count, false, 0x11 + static_cast(level), false, storage_type)); + } + + /* Initialize the final level storage. */ + { + /* Initialize the verification storage. */ + { + fs::SubStorage buffer_storage(std::addressof(this->buffer_storages[level]), 0, info.info[level].size); + fs::HashSalt mac; + crypto::GenerateHmacSha256Mac(mac.value, sizeof(mac), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size); + this->verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast(1) << info.info[level + 1].block_order, static_cast(1) << info.info[level].block_order, this->buffers->buffers[this->max_layers - 2], mac, true, storage_type); + } + + /* Initialize the buffer storage. */ + R_TRY(this->buffer_storages[level + 1].Initialize(this->buffers->buffers[level + 1], this->mutex, std::addressof(this->verify_storages[level + 1]), info.info[level + 1].size, static_cast(1) << info.info[level + 1].block_order, max_data_cache_entry_count, true, 0x11 + static_cast(level), true, storage_type)); + } + + /* Set the data size. */ + this->data_size = info.info[level + 1].size; + + /* We succeeded. */ + level_guard.Cancel(); + top_buffer_guard.Cancel(); + top_verif_guard.Cancel(); + return ResultSuccess(); + } + + void HierarchicalIntegrityVerificationStorage::Finalize() { + if (this->data_size >= 0) { + this->data_size = 0; + + this->buffers = nullptr; + this->mutex = nullptr; + + for (s32 level = this->max_layers - 2; level >= 0; --level) { + this->buffer_storages[level].Finalize(); + this->verify_storages[level].Finalize(); + } + + this->data_size = -1; + } + } + + Result HierarchicalIntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(this->data_size >= 0); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Acquire access to the read semaphore. */ + if (!g_read_semaphore.TimedAcquire(AccessTimeout)) { + for (auto level = this->max_layers - 2; level >= 0; --level) { + R_TRY(this->buffer_storages[level].Flush()); + } + g_read_semaphore.Acquire(); + } + + /* Ensure that we release the semaphore when done. */ + ON_SCOPE_EXIT { g_read_semaphore.Release(); }; + + /* Read the data. */ + R_TRY(this->buffer_storages[this->max_layers - 2].Read(offset, buffer, size)); + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(this->data_size >= 0); + + /* Succeed if zero-size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Acquire access to the write semaphore. */ + if (!g_write_semaphore.TimedAcquire(AccessTimeout)) { + for (auto level = this->max_layers - 2; level >= 0; --level) { + R_TRY(this->buffer_storages[level].Flush()); + } + g_write_semaphore.Acquire(); + } + + /* Ensure that we release the semaphore when done. */ + ON_SCOPE_EXIT { g_write_semaphore.Release(); }; + + /* Write the data. */ + R_TRY(this->buffer_storages[this->max_layers - 2].Write(offset, buffer, size)); + this->is_written_for_rollback = true; + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorage::GetSize(s64 *out) { + AMS_ASSERT(out != nullptr); + AMS_ASSERT(this->data_size >= 0); + *out = this->data_size; + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorage::Flush() { + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + switch (op_id) { + case fs::OperationId::Clear: + case fs::OperationId::ClearSignature: + { + R_TRY(this->buffer_storages[this->max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + this->is_written_for_rollback = true; + return ResultSuccess(); + } + case fs::OperationId::InvalidateCache: + case fs::OperationId::QueryRange: + { + R_TRY(this->buffer_storages[this->max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size)); + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInHierarchicalIntegrityVerificationStorageB(); + } + } + + Result HierarchicalIntegrityVerificationStorage::Commit() { + for (s32 level = this->max_layers - 2; level >= 0; --level) { + R_TRY(this->buffer_storages[level].Commit()); + } + return ResultSuccess(); + } + + Result HierarchicalIntegrityVerificationStorage::OnRollback() { + for (s32 level = this->max_layers - 2; level >= 0; --level) { + R_TRY(this->buffer_storages[level].OnRollback()); + } + this->is_written_for_rollback = false; + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/fssystem/save/fssystem_integrity_verification_storage.cpp b/libraries/libstratosphere/source/fssystem/save/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..c2f5737b0 --- /dev/null +++ b/libraries/libstratosphere/source/fssystem/save/fssystem_integrity_verification_storage.cpp @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::fssystem::save { + + Result IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, IBufferManager *bm, const fs::HashSalt &salt, bool is_real_data, fs::StorageType storage_type) { + /* Validate preconditions. */ + AMS_ASSERT(verif_block_size >= HashSize); + AMS_ASSERT(bm != nullptr); + + /* Set storages. */ + this->hash_storage = hs; + this->data_storage = ds; + + /* Set verification block sizes. */ + this->verification_block_size = verif_block_size; + this->verification_block_order = ILog2(static_cast(verif_block_size)); + AMS_ASSERT(this->verification_block_size == (1l << this->verification_block_order)); + + /* Set buffer manager. */ + this->buffer_manager = bm; + + /* Set upper layer block sizes. */ + upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); + this->upper_layer_verification_block_size = upper_layer_verif_block_size; + this->upper_layer_verification_block_order = ILog2(static_cast(upper_layer_verif_block_size)); + AMS_ASSERT(this->upper_layer_verification_block_size == (1l << this->upper_layer_verification_block_order)); + + /* Validate sizes. */ + { + s64 hash_size = 0; + s64 data_size = 0; + AMS_ASSERT(R_SUCCEEDED(hash_storage.GetSize(std::addressof(hash_size)))); + AMS_ASSERT(R_SUCCEEDED(data_storage.GetSize(std::addressof(hash_size)))); + AMS_ASSERT(((hash_size / HashSize) * this->verification_block_size) >= data_size); + } + + /* Set salt. */ + std::memcpy(this->salt.value, salt.value, fs::HashSalt::Size); + + /* Set data and storage type. */ + this->is_real_data = is_real_data; + this->storage_type = storage_type; + return ResultSuccess(); + } + + void IntegrityVerificationStorage::Finalize() { + if (this->buffer_manager != nullptr) { + this->hash_storage = fs::SubStorage(); + this->data_storage = fs::SubStorage(); + this->buffer_manager = nullptr; + } + } + + Result IntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) { + /* Although we support zero-size reads, we expect non-zero sizes. */ + AMS_ASSERT(size != 0); + + /* Validate other preconditions. */ + AMS_ASSERT(util::IsAligned(offset, static_cast(this->verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast(this->verification_block_size))); + + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + + /* Validate the offset. */ + s64 data_size; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(offset <= data_size, fs::ResultInvalidOffset()); + + /* Validate the access range. */ + R_UNLESS(IStorage::IsRangeValid(offset, size, util::AlignUp(data_size, static_cast(this->verification_block_size))), fs::ResultOutOfRange()); + + /* Determine the read extents. */ + size_t read_size = size; + if (static_cast(offset + read_size) > data_size) { + /* Determine the padding sizes. */ + s64 padding_offset = data_size - offset; + size_t padding_size = static_cast(this->verification_block_size - (padding_offset & (this->verification_block_size - 1))); + AMS_ASSERT(static_cast(padding_size) < this->verification_block_size); + + /* Clear the padding. */ + std::memset(static_cast(buffer) + padding_offset, 0, padding_size); + + /* Set the new in-bounds size. */ + read_size = static_cast(data_size - offset); + } + + /* Perform the read. */ + { + auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); }; + R_TRY(this->data_storage.Read(offset, buffer, read_size)); + clear_guard.Cancel(); + } + + /* Prepare to validate the signatures. */ + const auto signature_count = size >> this->verification_block_order; + PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash)); + const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash)); + + /* Verify the signatures. */ + Result verify_hash_result = ResultSuccess(); + + size_t verified_count = 0; + while (verified_count < signature_count) { + /* Read the current signatures. */ + const auto cur_count = std::min(buffer_count, signature_count - verified_count); + auto cur_result = this->ReadBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (verified_count << this->verification_block_order), cur_count << this->verification_block_order); + + /* Temporarily increase our priority. */ + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + /* Loop over each signature we read. */ + for (size_t i = 0; i < cur_count && R_SUCCEEDED(cur_result); ++i) { + const auto verified_size = (verified_count + i) << this->verification_block_order; + u8 *cur_buf = static_cast(buffer) + verified_size; + cur_result = this->VerifyHash(cur_buf, reinterpret_cast(signature_buffer.GetBuffer()) + i); + + /* If the data is corrupted, clear the corrupted parts. */ + if (fs::ResultIntegrityVerificationStorageCorrupted::Includes(cur_result)) { + std::memset(cur_buf, 0, this->verification_block_size); + + /* Set the result if we should. */ + if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && this->storage_type != fs::StorageType_Authoring) { + verify_hash_result = cur_result; + } + + cur_result = ResultSuccess(); + } + } + + /* If we failed, clear and return. */ + if (R_FAILED(cur_result)) { + std::memset(buffer, 0, size); + return cur_result; + } + + /* Advance. */ + verified_count += cur_count; + } + + return verify_hash_result; + } + + Result IntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) { + /* Succeed if zero size. */ + R_SUCCEED_IF(size == 0); + + /* Validate arguments. */ + R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument()); + R_UNLESS(IStorage::IsOffsetAndSizeValid(offset, size), fs::ResultInvalidOffset()); + + /* Validate the offset. */ + s64 data_size; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(offset < data_size, fs::ResultInvalidOffset()); + + /* Validate the access range. */ + R_UNLESS(IStorage::IsRangeValid(offset, size, util::AlignUp(data_size, static_cast(this->verification_block_size))), fs::ResultOutOfRange()); + + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, this->verification_block_size)); + AMS_ASSERT(util::IsAligned(size, this->verification_block_size)); + AMS_ASSERT(offset <= data_size); + AMS_ASSERT(static_cast(offset + size) < data_size + this->verification_block_size); + + /* Validate that if writing past the end, all extra data is zero padding. */ + if (static_cast(offset + size) > data_size) { + const u8 *padding_cur = static_cast(buffer) + data_size - offset; + const u8 *padding_end = padding_cur + (offset + size - data_size); + + while (padding_cur < padding_end) { + AMS_ASSERT((*padding_cur) == 0); + ++padding_cur; + } + } + + /* Determine the unpadded size to write. */ + auto write_size = size; + if (static_cast(offset + write_size) > data_size) { + write_size = static_cast(data_size - offset); + R_SUCCEED_IF(write_size == 0); + } + + /* Determine the size we're writing in blocks. */ + const auto aligned_write_size = util::AlignUp(write_size, this->verification_block_size); + + /* Write the updated block signatures. */ + Result update_result = ResultSuccess(); + size_t updated_count = 0; + { + const auto signature_count = aligned_write_size >> this->verification_block_order; + PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash)); + const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash)); + + while (updated_count < signature_count) { + const auto cur_count = std::min(buffer_count, signature_count - updated_count); + + /* Calculate the hash with temporarily increased priority. */ + { + ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative); + + for (size_t i = 0; i < cur_count; ++i) { + const auto updated_size = (updated_count + i) << this->verification_block_order; + this->CalcBlockHash(reinterpret_cast(signature_buffer.GetBuffer()) + i, reinterpret_cast(buffer) + updated_size); + } + } + + /* Write the new block signatures. */ + if (R_FAILED((update_result = this->WriteBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (updated_count << this->verification_block_order), cur_count << this->verification_block_order)))) { + break; + } + + /* Advance. */ + updated_count += cur_count; + } + } + + /* Write the data. */ + R_TRY(this->data_storage.Write(offset, buffer, std::min(write_size, updated_count << this->verification_block_order))); + + return update_result; + } + + Result IntegrityVerificationStorage::GetSize(s64 *out) { + return this->data_storage.GetSize(out); + } + + Result IntegrityVerificationStorage::Flush() { + /* Flush both storages. */ + R_TRY(this->hash_storage.Flush()); + R_TRY(this->data_storage.Flush()); + return ResultSuccess(); + } + + Result IntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) { + /* Validate preconditions. */ + AMS_ASSERT(util::IsAligned(offset, static_cast(this->verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast(this->verification_block_size))); + + switch (op_id) { + case fs::OperationId::Clear: + { + /* Clear should only be called for save data. */ + AMS_ASSERT(this->storage_type == fs::StorageType_SaveData); + + /* Validate the range. */ + s64 data_size = 0; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear. */ + const auto sign_offset = (offset >> this->verification_block_order) * HashSize; + const auto sign_size = (std::min(size, data_size - offset) >> this->verification_block_order) * HashSize; + + /* Allocate a work buffer. */ + const auto buf_size = static_cast(std::min(sign_size, static_cast(1) << (this->upper_layer_verification_block_order + 2))); + std::unique_ptr buf = fs::impl::MakeUnique(buf_size); + R_UNLESS(buf != nullptr, fs::ResultAllocationFailureInIntegrityVerificationStorageA()); + + /* Clear the work buffer. */ + std::memset(buf.get(), 0, buf_size); + + /* Clear in chunks. */ + auto remaining_size = sign_size; + + while (remaining_size > 0) { + const auto cur_size = static_cast(std::min(remaining_size, static_cast(buf_size))); + R_TRY(this->hash_storage.Write(sign_offset + sign_size - remaining_size, buf.get(), cur_size)); + remaining_size -= cur_size; + } + + return ResultSuccess(); + } + case fs::OperationId::ClearSignature: + { + /* Clear Signature should only be called for save data. */ + AMS_ASSERT(this->storage_type == fs::StorageType_SaveData); + + /* Validate the range. */ + s64 data_size = 0; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the extents to clear the signature for. */ + const auto sign_offset = (offset >> this->verification_block_order) * HashSize; + const auto sign_size = (std::min(size, data_size - offset) >> this->verification_block_order) * HashSize; + + /* Allocate a work buffer. */ + std::unique_ptr buf = fs::impl::MakeUnique(sign_size); + R_UNLESS(buf != nullptr, fs::ResultAllocationFailureInIntegrityVerificationStorageB()); + + /* Read the existing signature. */ + R_TRY(this->hash_storage.Read(sign_offset, buf.get(), sign_size)); + + /* Clear the signature. */ + /* This sets all bytes to FF, with the verification bit cleared. */ + for (auto i = 0; i < sign_size; ++i) { + buf[i] ^= ((i + 1) % HashSize == 0 ? 0x7F : 0xFF); + } + + /* Write the cleared signature. */ + return this->hash_storage.Write(sign_offset, buf.get(), sign_size); + } + case fs::OperationId::InvalidateCache: + { + /* Only allow cache invalidation for RomFs. */ + R_UNLESS(this->storage_type != fs::StorageType_SaveData, fs::ResultUnsupportedOperationInIntegrityVerificationStorageB()); + + /* Validate the range. */ + s64 data_size = 0; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the extents to invalidate. */ + const auto sign_offset = (offset >> this->verification_block_order) * HashSize; + const auto sign_size = (std::min(size, data_size - offset) >> this->verification_block_order) * HashSize; + + /* Operate on our storages. */ + R_TRY(this->hash_storage.OperateRange(dst, dst_size, op_id, sign_offset, sign_size, src, src_size)); + R_TRY(this->data_storage.OperateRange(dst, dst_size, op_id, sign_offset, sign_size, src, src_size)); + + return ResultSuccess(); + } + case fs::OperationId::QueryRange: + { + /* Validate the range. */ + s64 data_size = 0; + R_TRY(this->data_storage.GetSize(std::addressof(data_size))); + R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset()); + + /* Determine the real size to query. */ + const auto actual_size = std::min(size, data_size - offset); + + /* Query the data storage. */ + R_TRY(this->data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size)); + + return ResultSuccess(); + } + default: + return fs::ResultUnsupportedOperationInIntegrityVerificationStorageC(); + } + } + + void IntegrityVerificationStorage::CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size) const { + /* Create a sha256 generator. */ + crypto::Sha256Generator sha; + sha.Initialize(); + + /* If calculating for save data, hash the salt. */ + if (this->storage_type == fs::StorageType_SaveData) { + sha.Update(this->salt.value, sizeof(this->salt)); + } + + /* Update with the buffer and get the hash. */ + sha.Update(buffer, block_size); + sha.GetHash(out, sizeof(*out)); + + /* Set the validation bit, if the hash is for save data. */ + if (this->storage_type == fs::StorageType_SaveData) { + SetValidationBit(out); + } + } + + Result IntegrityVerificationStorage::ReadBlockSignature(void *dst, size_t dst_size, s64 offset, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(dst != nullptr); + AMS_ASSERT(util::IsAligned(offset, static_cast(this->verification_block_size))); + AMS_ASSERT(util::IsAligned(size, static_cast(this->verification_block_size))); + + /* Determine where to read the signature. */ + const s64 sign_offset = (offset >> this->verification_block_order) * HashSize; + const auto sign_size = static_cast((size >> this->verification_block_order) * HashSize); + AMS_ASSERT(dst_size >= sign_size); + + /* Create a guard in the event of failure. */ + auto clear_guard = SCOPE_GUARD { std::memset(dst, 0, sign_size); }; + + /* Validate that we can read the signature. */ + s64 hash_size; + R_TRY(this->hash_storage.GetSize(std::addressof(hash_size))); + const bool range_valid = static_cast(sign_offset + sign_size) <= hash_size; + AMS_ASSERT(range_valid); + R_UNLESS(range_valid, fs::ResultOutOfRange()); + + /* Read the signature. */ + R_TRY(this->hash_storage.Read(sign_offset, dst, sign_size)); + + /* We succeeded. */ + clear_guard.Cancel(); + return ResultSuccess(); + } + + Result IntegrityVerificationStorage::WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size) { + /* Validate preconditions. */ + AMS_ASSERT(src != nullptr); + AMS_ASSERT(util::IsAligned(offset, static_cast(this->verification_block_size))); + + /* Determine where to write the signature. */ + const s64 sign_offset = (offset >> this->verification_block_order) * HashSize; + const auto sign_size = static_cast((size >> this->verification_block_order) * HashSize); + AMS_ASSERT(src_size >= sign_size); + + /* Write the signature. */ + R_TRY(this->hash_storage.Write(sign_offset, src, sign_size)); + + /* We succeeded. */ + return ResultSuccess(); + } + + Result IntegrityVerificationStorage::VerifyHash(const void *buf, BlockHash *hash) { + /* Validate preconditions. */ + AMS_ASSERT(buf != nullptr); + AMS_ASSERT(hash != nullptr); + + /* Get the comparison hash. */ + auto &cmp_hash = *hash; + + /* If save data, check if the data is uninitialized. */ + if (this->storage_type == fs::StorageType_SaveData) { + bool is_cleared = false; + R_TRY(this->IsCleared(std::addressof(is_cleared), cmp_hash)); + R_UNLESS(!is_cleared, fs::ResultClearedRealDataVerificationFailed()); + } + + /* Get the calculated hash. */ + BlockHash calc_hash; + this->CalcBlockHash(std::addressof(calc_hash), buf); + + /* Check that the signatures are equal. */ + if (!crypto::IsSameBytes(std::addressof(cmp_hash), std::addressof(calc_hash), sizeof(BlockHash))) { + /* Clear the comparison hash. */ + std::memset(std::addressof(cmp_hash), 0, sizeof(cmp_hash)); + + /* Return the appropriate result. */ + if (this->is_real_data) { + return fs::ResultUnclearedRealDataVerificationFailed(); + } else { + return fs::ResultNonRealDataVerificationFailed(); + } + } + + return ResultSuccess(); + } + + Result IntegrityVerificationStorage::IsCleared(bool *is_cleared, const BlockHash &hash) { + /* Validate preconditions. */ + AMS_ASSERT(is_cleared != nullptr); + AMS_ASSERT(this->storage_type == fs::StorageType_SaveData); + + /* Default to uncleared. */ + *is_cleared = false; + + /* Succeed if the validation bit is set. */ + R_SUCCEED_IF(IsValidationBit(std::addressof(hash))); + + /* Otherwise, we expect the hash to be all zero. */ + for (size_t i = 0; i < sizeof(hash.hash); ++i) { + R_UNLESS(hash.hash[i] == 0, fs::ResultInvalidZeroHash()); + } + + /* Set cleared. */ + *is_cleared = true; + return ResultSuccess(); + } + +} diff --git a/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp b/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp index f0a4a029c..092211ffb 100644 --- a/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp +++ b/libraries/libstratosphere/source/hos/hos_stratosphere_api.cpp @@ -32,4 +32,12 @@ namespace ams::hos { hos::SetVersionForLibnxInternal(); } + void InitializeForStratosphereDebug(hos::Version debug_version) { + /* Initialize the global os resource managers. This *must* be done before anything else in stratosphere. */ + os::InitializeForStratosphereInternal(); + + /* Initialize hos::Version API. */ + hos::SetVersionForLibnxInternalDebug(debug_version); + } + } diff --git a/libraries/libstratosphere/source/hos/hos_version_api.cpp b/libraries/libstratosphere/source/hos/hos_version_api.cpp index 56f4b65b8..2d31bbbe6 100644 --- a/libraries/libstratosphere/source/hos/hos_version_api.cpp +++ b/libraries/libstratosphere/source/hos/hos_version_api.cpp @@ -49,6 +49,14 @@ namespace ams::hos { return g_hos_version; } + + void SetVersionForLibnxInternalDebug(hos::Version debug_version) { + std::scoped_lock lk(g_mutex); + g_hos_version = debug_version; + __atomic_store_n(&g_has_cached, true, __ATOMIC_SEQ_CST); + SetVersionForLibnxInternal(); + } + void SetVersionForLibnxInternal() { const u32 hos_version_val = static_cast(hos::GetVersion()); const u32 major = (hos_version_val >> 24) & 0xFF; diff --git a/libraries/libstratosphere/source/hos/hos_version_api_private.hpp b/libraries/libstratosphere/source/hos/hos_version_api_private.hpp index ed0cb8953..80f1c6fcd 100644 --- a/libraries/libstratosphere/source/hos/hos_version_api_private.hpp +++ b/libraries/libstratosphere/source/hos/hos_version_api_private.hpp @@ -19,5 +19,6 @@ namespace ams::hos { void SetVersionForLibnxInternal(); + void SetVersionForLibnxInternalDebug(hos::Version debug_version); } diff --git a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp index 53beb516e..59c3cd507 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_storage_impl.cpp @@ -627,7 +627,7 @@ namespace ams::ncm { R_TRY(this->EnsureEnabled()); /* This command is for development hardware only. */ - AMS_ABORT_UNLESS(spl::IsDevelopmentHardware()); + AMS_ABORT_UNLESS(spl::IsDevelopment()); /* Close any cached file. */ this->InvalidateFileCache(); diff --git a/libraries/libstratosphere/source/spl/spl_api.cpp b/libraries/libstratosphere/source/spl/spl_api.cpp index 7ffa4c333..6e8240244 100644 --- a/libraries/libstratosphere/source/spl/spl_api.cpp +++ b/libraries/libstratosphere/source/spl/spl_api.cpp @@ -17,17 +17,204 @@ namespace ams::spl { - HardwareType GetHardwareType() { - u64 out_val = 0; - R_ABORT_UNLESS(splGetConfig(SplConfigItem_HardwareType, &out_val)); - return static_cast(out_val); + namespace { + + enum class InitializeMode { + None, + General, + Crypto, + Ssl, + Es, + Fs, + Manu + }; + + os::Mutex g_mutex(false); + s32 g_initialize_count = 0; + InitializeMode g_initialize_mode = InitializeMode::None; + + Result AllocateAesKeySlotImpl(s32 *out) { + return serviceDispatchOut(splCryptoGetServiceSession(), 21, *out); + } + + Result DeallocateAesKeySlotImpl(s32 slot) { + return serviceDispatchIn(splCryptoGetServiceSession(), 22, slot); + } + + Result GetAesKeySlotAvailableEventImpl(Handle *out) { + return serviceDispatch(splCryptoGetServiceSession(), 23, + .out_handle_attrs = { SfOutHandleAttr_HipcCopy }, + .out_handles = out, + ); + } + + void GetAesKeySlotAvailableEvent(os::SystemEvent *out) { + /* Get libnx event. */ + Handle handle = svc::InvalidHandle; + R_ABORT_UNLESS(GetAesKeySlotAvailableEventImpl(std::addressof(handle))); + + /* Attach to event. */ + out->AttachReadableHandle(handle, true, os::EventClearMode_ManualClear); + } + + template + Result WaitAvailableKeySlotAndExecute(F f) { + os::SystemEvent event; + auto is_event_initialized = false; + while (true) { + R_TRY_CATCH(static_cast<::ams::Result>(f())) { + R_CATCH(spl::ResultOutOfKeyslots) { + if (!is_event_initialized) { + GetAesKeySlotAvailableEvent(std::addressof(event)); + is_event_initialized = true; + } + event.Wait(); + continue; + } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + } + + template + void Initialize(InitializeMode mode, F f) { + std::scoped_lock lk(g_mutex); + + AMS_ASSERT(g_initialize_count >= 0); + AMS_ABORT_UNLESS(mode != InitializeMode::None); + + if (g_initialize_count == 0) { + AMS_ABORT_UNLESS(g_initialize_mode == InitializeMode::None); + f(); + g_initialize_mode = mode; + } else { + AMS_ABORT_UNLESS(g_initialize_mode == mode); + } + + ++g_initialize_count; + } + + } + + void Initialize() { + return Initialize(InitializeMode::General, [&]() { + R_ABORT_UNLESS(splInitialize()); + }); + } + + void InitializeForCrypto() { + return Initialize(InitializeMode::Crypto, [&]() { + R_ABORT_UNLESS(splCryptoInitialize()); + }); + } + + void InitializeForSsl() { + return Initialize(InitializeMode::Ssl, [&]() { + R_ABORT_UNLESS(splSslInitialize()); + }); + } + + void InitializeForEs() { + return Initialize(InitializeMode::Es, [&]() { + R_ABORT_UNLESS(splEsInitialize()); + }); + } + + void InitializeForFs() { + return Initialize(InitializeMode::Fs, [&]() { + R_ABORT_UNLESS(splFsInitialize()); + }); + } + + void InitializeForManu() { + return Initialize(InitializeMode::Manu, [&]() { + R_ABORT_UNLESS(splManuInitialize()); + }); + } + + void Finalize() { + std::scoped_lock lk(g_mutex); + AMS_ASSERT(g_initialize_count > 0); + AMS_ABORT_UNLESS(g_initialize_mode != InitializeMode::None); + + if ((--g_initialize_count) == 0) { + switch (g_initialize_mode) { + case InitializeMode::General: splExit(); break; + case InitializeMode::Crypto: splCryptoExit(); break; + case InitializeMode::Ssl: splSslExit(); break; + case InitializeMode::Es: splEsExit(); break; + case InitializeMode::Fs: splFsExit(); break; + case InitializeMode::Manu: splManuExit(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + g_initialize_mode = InitializeMode::None; + } + } + + Result AllocateAesKeySlot(s32 *out_slot) { + return WaitAvailableKeySlotAndExecute([&]() -> Result { + return AllocateAesKeySlotImpl(out_slot); + }); + } + + Result DeallocateAesKeySlot(s32 slot) { + return DeallocateAesKeySlotImpl(slot); + } + + Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return splCryptoGenerateAesKek(key_source, generation, option, static_cast(access_key)); + } + + Result LoadAesKey(s32 slot, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return splCryptoLoadAesKey(std::addressof(access_key), key_source, static_cast(slot)); + } + + Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return WaitAvailableKeySlotAndExecute([&]() -> Result { + return splCryptoGenerateAesKey(std::addressof(access_key), key_source, dst); + }); + } + + Result GenerateSpecificAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return splFsGenerateSpecificAesKey(key_source, static_cast(generation), option, dst); + } + + Result ComputeCtr(void *dst, size_t dst_size, s32 slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + AMS_ASSERT(iv_size >= 0x10); + AMS_ASSERT(dst_size >= src_size); + + return splCryptoCryptAesCtr(src, dst, src_size, static_cast(slot), iv); + } + + Result DecryptAesKey(void *dst, size_t dst_size, const void *key_source, size_t key_source_size, s32 generation, u32 option) { + AMS_ASSERT(dst_size >= crypto::AesEncryptor128::KeySize); + AMS_ASSERT(key_source_size == sizeof(KeySource)); + return WaitAvailableKeySlotAndExecute([&]() -> Result { + return splCryptoDecryptAesKey(key_source, static_cast(generation), option, dst); + }); + } + + Result GetConfig(u64 *out, ConfigItem item) { + return splGetConfig(static_cast<::SplConfigItem>(item), out); + } + + bool IsDevelopment() { + bool is_dev; + R_ABORT_UNLESS(splIsDevelopment(std::addressof(is_dev))); + return is_dev; } MemoryArrangement GetMemoryArrangement() { - u64 arrange = 0; - R_ABORT_UNLESS(splGetConfig(SplConfigItem_MemoryArrange, &arrange)); - arrange &= 0x3F; - switch (arrange) { + u64 mode = 0; + R_ABORT_UNLESS(spl::GetConfig(std::addressof(mode), spl::ConfigItem::MemoryMode)); + switch (mode & 0x3F) { case 2: return MemoryArrangement_StandardForAppletDev; case 3: @@ -41,52 +228,55 @@ namespace ams::spl { } } - bool IsDisabledProgramVerification() { - u64 val = 0; - R_ABORT_UNLESS(splGetConfig(SplConfigItem_DisableProgramVerification, &val)); - return val != 0; + Result SetBootReason(BootReasonValue boot_reason) { + static_assert(sizeof(boot_reason) == sizeof(u32)); + + u32 v; + std::memcpy(std::addressof(v), std::addressof(boot_reason), sizeof(v)); + + return splSetBootReason(v); } - bool IsDevelopmentHardware() { - bool is_dev_hardware; - R_ABORT_UNLESS(splIsDevelopment(&is_dev_hardware)); - return is_dev_hardware; + Result GetBootReason(BootReasonValue *out) { + static_assert(sizeof(*out) == sizeof(u32)); + + u32 v; + R_TRY(splGetBootReason(std::addressof(v))); + + std::memcpy(out, std::addressof(v), sizeof(*out)); + return ResultSuccess(); } - bool IsDevelopmentFunctionEnabled() { - u64 val = 0; - R_ABORT_UNLESS(splGetConfig(SplConfigItem_IsDebugMode, &val)); - return val != 0; - } - - bool IsRecoveryBoot() { - u64 val = 0; - R_ABORT_UNLESS(splGetConfig(SplConfigItem_IsRecoveryBoot, &val)); - return val != 0; - } - - bool IsMariko() { - const auto hw_type = GetHardwareType(); - switch (hw_type) { + SocType GetSocType() { + switch (GetHardwareType()) { case HardwareType::Icosa: case HardwareType::Copper: - return false; + return SocType_Erista; case HardwareType::Hoag: case HardwareType::Iowa: - return true; + case HardwareType::_Five_: + return SocType_Mariko; AMS_UNREACHABLE_DEFAULT_CASE(); } } - Result GenerateAesKek(AccessKey *access_key, const void *key_source, size_t key_source_size, u32 generation, u32 option) { - AMS_ASSERT(key_source_size == sizeof(KeySource)); - return splCryptoGenerateAesKek(key_source, generation, option, static_cast(access_key)); + Result GetPackage2Hash(void *dst, size_t dst_size) { + AMS_ASSERT(dst_size >= crypto::Sha256Generator::HashSize); + return splFsGetPackage2Hash(dst); } - Result GenerateAesKey(void *dst, size_t dst_size, const AccessKey &access_key, const void *key_source, size_t key_source_size) { - AMS_ASSERT(dst_size == crypto::AesEncryptor128::KeySize); - AMS_ASSERT(key_source_size == sizeof(KeySource)); - return splCryptoGenerateAesKey(std::addressof(access_key), key_source, dst); + Result GenerateRandomBytes(void *out, size_t buffer_size) { + return splGetRandomBytes(out, buffer_size); + } + + Result LoadPreparedAesKey(s32 slot, const AccessKey &access_key) { + if (g_initialize_mode == InitializeMode::Fs) { + return splFsLoadTitlekey(std::addressof(access_key), static_cast(slot)); + } else { + /* TODO: libnx binding not available. */ + /* return splEsLoadTitlekey(std::addressof(access_key), static_cast(slot)); */ + AMS_ABORT_UNLESS(false); + } } } diff --git a/libraries/libvapours/include/vapours/crypto.hpp b/libraries/libvapours/include/vapours/crypto.hpp index 6fd9c0487..2aa531f67 100644 --- a/libraries/libvapours/include/vapours/crypto.hpp +++ b/libraries/libvapours/include/vapours/crypto.hpp @@ -29,4 +29,6 @@ #include #include #include +#include +#include #include diff --git a/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp b/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp new file mode 100644 index 000000000..f1e8cefc7 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_hmac_generator.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include + +namespace ams::crypto { + + template /* requires HashFunction */ + class HmacGenerator { + NON_COPYABLE(HmacGenerator); + NON_MOVEABLE(HmacGenerator); + private: + using Impl = impl::HmacImpl; + public: + static constexpr size_t HashSize = Impl::HashSize; + static constexpr size_t BlockSize = Impl::BlockSize; + private: + Impl impl; + public: + HmacGenerator() { /* ... */ } + + void Initialize(const void *key, size_t key_size) { + return this->impl.Initialize(key, key_size); + } + + void Update(const void *data, size_t size) { + return this->impl.Update(data, size); + } + + void GetMac(void *dst, size_t dst_size) { + return this->impl.GetMac(dst, dst_size); + } + }; +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp b/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp new file mode 100644 index 000000000..44faf7dcf --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha1_generator.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::crypto { + + using HmacSha1Generator = HmacGenerator; + + void GenerateHmacSha1Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size); + +} diff --git a/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp b/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp new file mode 100644 index 000000000..8d4f24a85 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/crypto_hmac_sha256_generator.hpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams::crypto { + + using HmacSha256Generator = HmacGenerator; + + void GenerateHmacSha256Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size); + +} diff --git a/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp b/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp new file mode 100644 index 000000000..961506b40 --- /dev/null +++ b/libraries/libvapours/include/vapours/crypto/impl/crypto_hmac_impl.hpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace ams::crypto::impl { + + template /* requires HashFunction */ + class HmacImpl { + NON_COPYABLE(HmacImpl); + NON_MOVEABLE(HmacImpl); + public: + static constexpr size_t MacSize = Hash::HashSize; + static constexpr size_t BlockSize = Hash::BlockSize; + private: + static constexpr u32 IpadMagic = 0x36363636; + static constexpr u32 OpadMagic = 0x5c5c5c5c; + + static constexpr u32 IpadMagicXorOpadMagic = IpadMagic ^ OpadMagic; + static_assert(IpadMagicXorOpadMagic == 0x6a6a6a6a); + private: + enum State { + State_None = 0, + State_Initialized = 1, + State_Done = 2, + }; + private: + Hash hash_function; + u32 key[BlockSize / sizeof(u32)]; + u32 mac[MacSize / sizeof(u32)]; + State state; + public: + HmacImpl() : state(State_None) { /* ... */ } + ~HmacImpl() { + static_assert(offsetof(HmacImpl, hash_function) == 0); + + /* Clear everything except for the hash function. */ + ClearMemory(reinterpret_cast(this) + sizeof(this->hash_function), sizeof(*this) - sizeof(this->hash_function)); + } + + void Initialize(const void *key, size_t key_size); + void Update(const void *data, size_t data_size); + void GetMac(void *dst, size_t dst_size); + }; + + template + inline void HmacImpl::Initialize(const void *key, size_t key_size) { + /* Clear the key storage. */ + std::memset(this->key, 0, sizeof(this->key)); + + /* Set the key storage. */ + if (key_size > BlockSize) { + this->hash_function.Initialize(); + this->hash_function.Update(key, key_size); + this->hash_function.GetHash(this->key, this->hash_function.HashSize); + } else { + std::memcpy(this->key, key, key_size); + } + + /* Xor the key with the ipad. */ + for (size_t i = 0; i < util::size(this->key); i++) { + this->key[i] ^= IpadMagic; + } + + /* Update the hash function with the xor'd key. */ + this->hash_function.Initialize(); + this->hash_function.Update(this->key, BlockSize); + + /* Mark initialized. */ + this->state = State_Initialized; + } + + template + inline void HmacImpl::Update(const void *data, size_t data_size) { + AMS_ASSERT(this->state == State_Initialized); + + this->hash_function.Update(data, data_size); + } + + template + inline void HmacImpl::GetMac(void *dst, size_t dst_size) { + AMS_ASSERT(this->state == State_Initialized || this->state == State_Done); + AMS_ASSERT(dst_size >= MacSize); + + /* If we're not already finalized, get the final mac. */ + if (this->state == State_Initialized) { + /* Get the hash of ((key ^ ipad) || data). */ + this->hash_function.GetHash(this->mac, MacSize); + + /* Xor the key with the opad. */ + for (size_t i = 0; i < util::size(this->key); i++) { + this->key[i] ^= IpadMagicXorOpadMagic; + } + + /* Calculate the final mac as hash of ((key ^ opad) || hash((key ^ ipad) || data)) */ + this->hash_function.Initialize(); + this->hash_function.Update(this->key, BlockSize); + this->hash_function.Update(this->mac, MacSize); + this->hash_function.GetHash(this->mac, MacSize); + + /* Set our state as done. */ + this->state = State_Done; + } + + std::memcpy(dst, this->mac, MacSize); + } + +} diff --git a/libraries/libvapours/include/vapours/results/fs_results.hpp b/libraries/libvapours/include/vapours/results/fs_results.hpp index 5d5ae5eee..c4e9962ec 100644 --- a/libraries/libvapours/include/vapours/results/fs_results.hpp +++ b/libraries/libvapours/include/vapours/results/fs_results.hpp @@ -35,9 +35,12 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceBisSystem, 38); R_DEFINE_ERROR_RESULT(NotEnoughFreeSpaceSdCard, 39); + R_DEFINE_ERROR_RESULT(UnsupportedSdkVersion, 50); + R_DEFINE_ERROR_RESULT(MountNameAlreadyExists, 60); - R_DEFINE_ERROR_RESULT(TargetNotFound, 1002); + R_DEFINE_ERROR_RESULT(PartitionNotFound, 1001); + R_DEFINE_ERROR_RESULT(TargetNotFound, 1002); R_DEFINE_ERROR_RANGE(SdCardAccessFailed, 2000, 2499); R_DEFINE_ERROR_RESULT(SdCardNotPresent, 2001); @@ -51,48 +54,65 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(SystemPartitionNotReady, 3100); R_DEFINE_ERROR_RANGE(AllocationFailure, 3200, 3499); - R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorA, 3211); - R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorB, 3212); - R_DEFINE_ERROR_RESULT(AllocationFailureInApplicationA, 3213); - R_DEFINE_ERROR_RESULT(AllocationFailureInBisA, 3215); - R_DEFINE_ERROR_RESULT(AllocationFailureInBisB, 3216); - R_DEFINE_ERROR_RESULT(AllocationFailureInBisC, 3217); - R_DEFINE_ERROR_RESULT(AllocationFailureInCodeA, 3218); - R_DEFINE_ERROR_RESULT(AllocationFailureInContentA, 3219); - R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageA, 3220); - R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageB, 3221); - R_DEFINE_ERROR_RESULT(AllocationFailureInDataA, 3222); - R_DEFINE_ERROR_RESULT(AllocationFailureInDataB, 3223); - R_DEFINE_ERROR_RESULT(AllocationFailureInDeviceSaveDataA, 3224); - R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardA, 3225); - R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardB, 3226); - R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardC, 3227); - R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardD, 3228); - R_DEFINE_ERROR_RESULT(AllocationFailureInImageDirectoryA, 3232); - R_DEFINE_ERROR_RESULT(AllocationFailureInSdCardA, 3244); - R_DEFINE_ERROR_RESULT(AllocationFailureInSdCardB, 3245); - R_DEFINE_ERROR_RESULT(AllocationFailureInSystemSaveDataA, 3246); - R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemA, 3247); - R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemB, 3248); - R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemC, 3249); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemCreatorA, 3280); - R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemBuddyHeapA, 3294); - R_DEFINE_ERROR_RESULT(AllocationFailureInDirectorySaveDataFileSystem, 3321); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemA, 3347); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemB, 3348); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemC, 3349); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaA, 3350); - R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaB, 3351); - R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemD, 3352); - R_DEFINE_ERROR_RESULT(AllocationFailureInSubDirectoryFileSystem, 3355); - R_DEFINE_ERROR_RESULT(AllocationFailureInRegisterA, 3365); - R_DEFINE_ERROR_RESULT(AllocationFailureInRegisterB, 3366); - R_DEFINE_ERROR_RESULT(AllocationFailureInPathNormalizer, 3367); - R_DEFINE_ERROR_RESULT(AllocationFailureInDbmRomKeyValueStorage, 3375); - R_DEFINE_ERROR_RESULT(AllocationFailureInReadOnlyFileSystemA, 3386); - R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemE, 3377); - R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemInterfaceAdapter, 3407); - R_DEFINE_ERROR_RESULT(AllocationFailureInNew, 3420); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorA, 3211); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemAccessorB, 3212); + R_DEFINE_ERROR_RESULT(AllocationFailureInApplicationA, 3213); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisA, 3215); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisB, 3216); + R_DEFINE_ERROR_RESULT(AllocationFailureInBisC, 3217); + R_DEFINE_ERROR_RESULT(AllocationFailureInCodeA, 3218); + R_DEFINE_ERROR_RESULT(AllocationFailureInContentA, 3219); + R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageA, 3220); + R_DEFINE_ERROR_RESULT(AllocationFailureInContentStorageB, 3221); + R_DEFINE_ERROR_RESULT(AllocationFailureInDataA, 3222); + R_DEFINE_ERROR_RESULT(AllocationFailureInDataB, 3223); + R_DEFINE_ERROR_RESULT(AllocationFailureInDeviceSaveDataA, 3224); + R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardA, 3225); + R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardB, 3226); + R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardC, 3227); + R_DEFINE_ERROR_RESULT(AllocationFailureInGameCardD, 3228); + R_DEFINE_ERROR_RESULT(AllocationFailureInImageDirectoryA, 3232); + R_DEFINE_ERROR_RESULT(AllocationFailureInSdCardA, 3244); + R_DEFINE_ERROR_RESULT(AllocationFailureInSdCardB, 3245); + R_DEFINE_ERROR_RESULT(AllocationFailureInSystemSaveDataA, 3246); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemA, 3247); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemB, 3248); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemC, 3249); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemCreatorA, 3280); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFileSystemCreatorA, 3281); + R_DEFINE_ERROR_RESULT(AllocationFailureInStorageOnNcaCreatorA, 3288); + R_DEFINE_ERROR_RESULT(AllocationFailureInStorageOnNcaCreatorB, 3289); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemBuddyHeapA, 3294); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemBufferManagerA, 3295); + R_DEFINE_ERROR_RESULT(AllocationFailureInBlockCacheBufferedStorageA, 3296); + R_DEFINE_ERROR_RESULT(AllocationFailureInBlockCacheBufferedStorageB, 3297); + R_DEFINE_ERROR_RESULT(AllocationFailureInIntegrityVerificationStorageA, 3304); + R_DEFINE_ERROR_RESULT(AllocationFailureInIntegrityVerificationStorageB, 3305); + R_DEFINE_ERROR_RESULT(AllocationFailureInDirectorySaveDataFileSystem, 3321); + R_DEFINE_ERROR_RESULT(AllocationFailureInNcaFileSystemDriverI, 3341); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemA, 3347); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemB, 3348); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemC, 3349); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaA, 3350); + R_DEFINE_ERROR_RESULT(AllocationFailureInPartitionFileSystemMetaB, 3351); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemD, 3352); + R_DEFINE_ERROR_RESULT(AllocationFailureInSubDirectoryFileSystem, 3355); + R_DEFINE_ERROR_RESULT(AllocationFailureInNcaReaderA, 3363); + R_DEFINE_ERROR_RESULT(AllocationFailureInRegisterA, 3365); + R_DEFINE_ERROR_RESULT(AllocationFailureInRegisterB, 3366); + R_DEFINE_ERROR_RESULT(AllocationFailureInPathNormalizer, 3367); + R_DEFINE_ERROR_RESULT(AllocationFailureInDbmRomKeyValueStorage, 3375); + R_DEFINE_ERROR_RESULT(AllocationFailureInRomFsFileSystemE, 3377); + R_DEFINE_ERROR_RESULT(AllocationFailureInReadOnlyFileSystemA, 3386); + R_DEFINE_ERROR_RESULT(AllocationFailureInAesCtrCounterExtendedStorageA, 3399); + R_DEFINE_ERROR_RESULT(AllocationFailureInAesCtrCounterExtendedStorageB, 3400); + R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemInterfaceAdapter, 3407); + R_DEFINE_ERROR_RESULT(AllocationFailureInBufferedStorageA, 3411); + R_DEFINE_ERROR_RESULT(AllocationFailureInIntegrityRomFsStorageA, 3412); + R_DEFINE_ERROR_RESULT(AllocationFailureInNew, 3420); + R_DEFINE_ERROR_RESULT(AllocationFailureInMakeUnique, 3422); + R_DEFINE_ERROR_RESULT(AllocationFailureInAllocateShared, 3423); + R_DEFINE_ERROR_RESULT(AllocationFailurePooledBufferNotEnoughSize, 3424); R_DEFINE_ERROR_RANGE(MmcAccessFailed, 3500, 3999); @@ -100,6 +120,28 @@ namespace ams::fs { R_DEFINE_ERROR_RANGE(RomCorrupted, 4001, 4299); R_DEFINE_ERROR_RESULT(UnsupportedRomVersion, 4002); + R_DEFINE_ERROR_RANGE(AesCtrCounterExtendedStorageCorrupted, 4011, 4019); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedEntryOffset, 4012); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedTableSize, 4013); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedGeneration, 4014); + R_DEFINE_ERROR_RESULT(InvalidAesCtrCounterExtendedOffset, 4015); + + R_DEFINE_ERROR_RANGE(IndirectStorageCorrupted, 4021, 4029); + R_DEFINE_ERROR_RESULT(InvalidIndirectEntryOffset, 4022); + R_DEFINE_ERROR_RESULT(InvalidIndirectEntryStorageIndex, 4023); + R_DEFINE_ERROR_RESULT(InvalidIndirectStorageSize, 4024); + R_DEFINE_ERROR_RESULT(InvalidIndirectVirtualOffset, 4025); + R_DEFINE_ERROR_RESULT(InvalidIndirectPhysicalOffset, 4026); + R_DEFINE_ERROR_RESULT(InvalidIndirectStorageIndex, 4027); + + R_DEFINE_ERROR_RANGE(BucketTreeCorrupted, 4031, 4039); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeSignature, 4032); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntryCount, 4033); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeNodeEntryCount, 4034); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeNodeOffset, 4035); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntryOffset, 4036); + R_DEFINE_ERROR_RESULT(InvalidBucketTreeEntrySetOffset, 4037); + R_DEFINE_ERROR_RANGE(RomNcaCorrupted, 4041, 4139); R_DEFINE_ERROR_RANGE(RomNcaFileSystemCorrupted, 4051, 4069); R_DEFINE_ERROR_RESULT(InvalidRomNcaFileSystemType, 4052); @@ -154,6 +196,9 @@ namespace ams::fs { R_DEFINE_ERROR_RANGE(SaveDataCorrupted, 4301, 4499); R_DEFINE_ERROR_RANGE(NcaCorrupted, 4501, 4599); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeA, 4508); + R_DEFINE_ERROR_RESULT(NcaBaseStorageOutOfRangeB, 4509); + R_DEFINE_ERROR_RANGE(NcaFileSystemCorrupted, 4511, 4529); R_DEFINE_ERROR_RESULT(InvalidNcaFileSystemType, 4512); R_DEFINE_ERROR_RESULT(InvalidAcidFileSize, 4513); @@ -167,6 +212,12 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(InvalidNcaKeyIndex, 4521); R_DEFINE_ERROR_RESULT(InvalidNcaFsHeaderHashType, 4522); R_DEFINE_ERROR_RESULT(InvalidNcaFsHeaderEncryptionType, 4523); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoIndirectSize, 4524); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoAesCtrExSize, 4525); + R_DEFINE_ERROR_RESULT(InvalidNcaPatchInfoAesCtrExOffset, 4526); + R_DEFINE_ERROR_RESULT(InvalidNcaId, 4527); + R_DEFINE_ERROR_RESULT(InvalidNcaHeader, 4528); + R_DEFINE_ERROR_RESULT(InvalidNcaFsHeader, 4529); R_DEFINE_ERROR_RANGE(NcaHierarchicalSha256StorageCorrupted, 4531, 4539); R_DEFINE_ERROR_RESULT(InvalidHierarchicalSha256BlockSize, 4532); @@ -174,6 +225,9 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(HierarchicalSha256BaseStorageTooLarge, 4534); R_DEFINE_ERROR_RESULT(HierarchicalSha256HashVerificationFailed, 4535); + /* TODO: Range? */ + R_DEFINE_ERROR_RESULT(InvalidNcaHeader1SignatureKeyGeneration, 4543); + R_DEFINE_ERROR_RANGE(IntegrityVerificationStorageCorrupted, 4601, 4639); R_DEFINE_ERROR_RESULT(IncorrectIntegrityVerificationMagic, 4602); R_DEFINE_ERROR_RESULT(InvalidZeroHash, 4603); @@ -254,28 +308,51 @@ namespace ams::fs { R_DEFINE_ERROR_RESULT(WriteNotPermitted, 6203); R_DEFINE_ERROR_RANGE(UnsupportedOperation, 6300, 6399); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInSubStorageA, 6302); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInSubStorageB, 6303); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInMemoryStorageA, 6304); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInMemoryStorageB, 6305); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileStorageA, 6306); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileStorageB, 6307); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrStorageA, 6315); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileServiceObjectAdapterA, 6362); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemA, 6364); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemB, 6365); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemC, 6366); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileA, 6367); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileB, 6368); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateA, 6369); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateB, 6370); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateC, 6371); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileA, 6372); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileB, 6373); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemA, 6374); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemB, 6375); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileA, 6376); - R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileB, 6377); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInSubStorageA, 6302); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInSubStorageB, 6303); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInMemoryStorageA, 6304); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInMemoryStorageB, 6305); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileStorageA, 6306); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileStorageB, 6307); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInSwitchStorageA, 6308); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrCounterExtendedStorageA, 6310); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrCounterExtendedStorageB, 6311); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrCounterExtendedStorageC, 6312); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrStorageExternalA, 6313); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrStorageExternalB, 6314); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInAesCtrStorageA, 6315); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInHierarchicalIntegrityVerificationStorageA, 6316); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInHierarchicalIntegrityVerificationStorageB, 6317); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIntegrityVerificationStorageA, 6318); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIntegrityVerificationStorageB, 6319); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIntegrityVerificationStorageC, 6320); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInBlockCacheBufferedStorageA, 6321); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInBlockCacheBufferedStorageB, 6322); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInBlockCacheBufferedStorageC, 6323); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIndirectStorageA, 6324); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIndirectStorageB, 6325); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIndirectStorageC, 6326); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInZeroStorageA, 6327); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInZeroStorageB, 6328); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInHierarchicalSha256StorageA, 6329); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyBlockCacheStorageA, 6330); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyBlockCacheStorageB, 6331); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInIntegrityRomFsStorageA , 6332); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInFileServiceObjectAdapterA, 6362); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemA, 6364); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemB, 6365); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileSystemC, 6366); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileA, 6367); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInRomFsFileB, 6368); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateA, 6369); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateB, 6370); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileSystemTemplateC, 6371); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileA, 6372); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInReadOnlyFileB, 6373); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemA, 6374); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileSystemB, 6375); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileA, 6376); + R_DEFINE_ERROR_RESULT(UnsupportedOperationInPartitionFileB, 6377); R_DEFINE_ERROR_RANGE(PermissionDenied, 6400, 6449); diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 867668f83..43e2806e1 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -36,3 +36,4 @@ #include #include #include +#include diff --git a/libraries/libvapours/include/vapours/util/util_bitutil.hpp b/libraries/libvapours/include/vapours/util/util_bitutil.hpp index cf974f62d..bc52b45f4 100644 --- a/libraries/libvapours/include/vapours/util/util_bitutil.hpp +++ b/libraries/libvapours/include/vapours/util/util_bitutil.hpp @@ -208,4 +208,11 @@ namespace ams::util { return T(1) << (BITSIZEOF(T) - CountLeadingZeros(x) - 1); } + template + constexpr ALWAYS_INLINE T DivideUp(T v, U d) { + using Unsigned = typename std::make_unsigned::type; + const Unsigned add = static_cast(d) - 1; + return static_cast((v + add) / d); + } + } diff --git a/libraries/libvapours/include/vapours/util/util_variadic.hpp b/libraries/libvapours/include/vapours/util/util_variadic.hpp new file mode 100644 index 000000000..91ed72e0d --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_variadic.hpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#define AMS_UTIL_VARIADIC_INVOKE_MACRO(__HANDLER__) \ + __HANDLER__(_01_) \ + __HANDLER__(_02_) \ + __HANDLER__(_03_) \ + __HANDLER__(_04_) \ + __HANDLER__(_05_) \ + __HANDLER__(_06_) \ + __HANDLER__(_07_) \ + __HANDLER__(_08_) \ + __HANDLER__(_09_) \ + __HANDLER__(_0A_) \ + __HANDLER__(_0B_) \ + __HANDLER__(_0C_) \ + __HANDLER__(_0D_) \ + __HANDLER__(_0E_) \ + __HANDLER__(_0F_) + +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_01_(_T_) typename _T_##_01_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_02_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_01_(_T_), typename _T_##_02_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_03_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_02_(_T_), typename _T_##_03_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_04_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_03_(_T_), typename _T_##_04_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_05_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_04_(_T_), typename _T_##_05_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_06_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_05_(_T_), typename _T_##_06_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_07_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_06_(_T_), typename _T_##_07_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_08_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_07_(_T_), typename _T_##_08_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_09_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_08_(_T_), typename _T_##_09_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0A_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_09_(_T_), typename _T_##_0A_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0B_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0A_(_T_), typename _T_##_0B_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0C_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0B_(_T_), typename _T_##_0C_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0D_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0C_(_T_), typename _T_##_0D_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0E_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0D_(_T_), typename _T_##_0E_ +#define AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0F_(_T_) AMS_UTIL_VARIADIC_TEMPLATE_PARAMETERS_0E_(_T_), typename _T_##_0F_ + +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_01_(_T_, _N_) _T_##_01_ &&_N_##_01_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_02_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_01_(_T_, _N_), _T_##_02_ &&_N_##_02_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_03_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_02_(_T_, _N_), _T_##_03_ &&_N_##_03_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_04_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_03_(_T_, _N_), _T_##_04_ &&_N_##_04_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_05_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_04_(_T_, _N_), _T_##_05_ &&_N_##_05_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_06_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_05_(_T_, _N_), _T_##_06_ &&_N_##_06_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_07_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_06_(_T_, _N_), _T_##_07_ &&_N_##_07_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_08_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_07_(_T_, _N_), _T_##_08_ &&_N_##_08_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_09_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_08_(_T_, _N_), _T_##_09_ &&_N_##_09_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0A_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_09_(_T_, _N_), _T_##_0A_ &&_N_##_0A_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0B_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0A_(_T_, _N_), _T_##_0B_ &&_N_##_0B_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0C_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0B_(_T_, _N_), _T_##_0C_ &&_N_##_0C_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0D_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0C_(_T_, _N_), _T_##_0D_ &&_N_##_0D_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0E_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0D_(_T_, _N_), _T_##_0E_ &&_N_##_0E_ +#define AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0F_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_ARGUMENTS_0E_(_T_, _N_), _T_##_0F_ &&_N_##_0F_ + +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_01_(_T_, _N_) ::std::forward<_T_##_01_>(_N_##_01_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_02_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_01_(_T_, _N_), ::std::forward<_T_##_02_>(_N_##_02_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_03_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_02_(_T_, _N_), ::std::forward<_T_##_03_>(_N_##_03_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_04_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_03_(_T_, _N_), ::std::forward<_T_##_04_>(_N_##_04_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_05_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_04_(_T_, _N_), ::std::forward<_T_##_05_>(_N_##_05_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_06_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_05_(_T_, _N_), ::std::forward<_T_##_06_>(_N_##_06_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_07_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_06_(_T_, _N_), ::std::forward<_T_##_07_>(_N_##_07_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_08_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_07_(_T_, _N_), ::std::forward<_T_##_08_>(_N_##_08_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_09_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_08_(_T_, _N_), ::std::forward<_T_##_09_>(_N_##_09_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0A_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_09_(_T_, _N_), ::std::forward<_T_##_0A_>(_N_##_0A_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0B_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0A_(_T_, _N_), ::std::forward<_T_##_0B_>(_N_##_0B_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0C_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0B_(_T_, _N_), ::std::forward<_T_##_0C_>(_N_##_0C_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0D_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0C_(_T_, _N_), ::std::forward<_T_##_0D_>(_N_##_0D_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0E_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0D_(_T_, _N_), ::std::forward<_T_##_0E_>(_N_##_0E_) +#define AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0F_(_T_, _N_) AMS_UTIL_VARIADIC_TEMPLATE_FORWARDS_0E_(_T_, _N_), ::std::forward<_T_##_0F_>(_N_##_0F_) diff --git a/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp b/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp new file mode 100644 index 000000000..d7a7ab2a5 --- /dev/null +++ b/libraries/libvapours/source/crypto/crypto_hmac_sha1_generator.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::crypto { + + void GenerateHmacSha1Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + HmacSha1Generator hmac; + + hmac.Initialize(key, key_size); + hmac.Update(data, data_size); + hmac.GetMac(dst, dst_size); + } + +} diff --git a/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp b/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp new file mode 100644 index 000000000..52b36e42f --- /dev/null +++ b/libraries/libvapours/source/crypto/crypto_hmac_sha256_generator.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::crypto { + + void GenerateHmacSha256Mac(void *dst, size_t dst_size, const void *data, size_t data_size, const void *key, size_t key_size) { + HmacSha256Generator hmac; + + hmac.Initialize(key, key_size); + hmac.Update(data, data_size); + hmac.GetMac(dst, dst_size); + } + +} diff --git a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp index f1c2cc8fc..96f89c68a 100644 --- a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp @@ -96,7 +96,7 @@ namespace ams::mitm { { u64 key_generation = 0; if (hos::GetVersion() >= hos::Version_5_0_0) { - R_ABORT_UNLESS(splGetConfig(SplConfigItem_NewKeyGeneration, &key_generation)); + R_ABORT_UNLESS(spl::GetConfig(std::addressof(key_generation), spl::ConfigItem::DeviceUniqueKeyGeneration)); } u8 bis_keys[4][2][0x10]; @@ -107,15 +107,15 @@ namespace ams::mitm { for (size_t partition = 0; partition < 4; partition++) { if (partition == 0) { for (size_t i = 0; i < 2; i++) { - R_ABORT_UNLESS(splFsGenerateSpecificAesKey(BisKeySources[partition][i], key_generation, i, bis_keys[partition][i])); + R_ABORT_UNLESS(spl::GenerateSpecificAesKey(bis_keys[partition][i], 0x10, BisKeySources[partition][i], 0x10, key_generation, i)); } } else { const u32 option = (partition == 3 && spl::IsRecoveryBoot()) ? 0x4 : 0x1; - u8 access_key[0x10]; - R_ABORT_UNLESS(splCryptoGenerateAesKek(BisKekSource, key_generation, option, access_key)); + spl::AccessKey access_key; + R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(access_key), BisKekSource, 0x10, key_generation, option)); for (size_t i = 0; i < 2; i++) { - R_ABORT_UNLESS(splCryptoGenerateAesKey(access_key, BisKeySources[partition][i], bis_keys[partition][i])); + R_ABORT_UNLESS(spl::GenerateAesKey(bis_keys[partition][i], 0x10, access_key, BisKeySources[partition][i], 0x10)); } } } diff --git a/stratosphere/ams_mitm/source/amsmitm_main.cpp b/stratosphere/ams_mitm/source/amsmitm_main.cpp index 9de2611db..88faca823 100644 --- a/stratosphere/ams_mitm/source/amsmitm_main.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_main.cpp @@ -81,7 +81,7 @@ void __appInit(void) { R_ABORT_UNLESS(fsInitialize()); R_ABORT_UNLESS(pmdmntInitialize()); R_ABORT_UNLESS(pminfoInitialize()); - R_ABORT_UNLESS(splFsInitialize()); + spl::InitializeForFs(); }); ams::CheckApiVersion(); @@ -89,7 +89,7 @@ void __appInit(void) { void __appExit(void) { /* Cleanup services. */ - splFsExit(); + spl::Finalize(); pminfoExit(); pmdmntExit(); fsExit(); diff --git a/stratosphere/boot/source/boot_boot_reason.cpp b/stratosphere/boot/source/boot_boot_reason.cpp index 6cb6cb863..0c255434f 100644 --- a/stratosphere/boot/source/boot_boot_reason.cpp +++ b/stratosphere/boot/source/boot_boot_reason.cpp @@ -22,19 +22,6 @@ namespace ams::boot { namespace { - /* Types. */ - struct BootReasonValue { - union { - struct { - u8 power_intr; - u8 rtc_intr; - u8 nv_erc; - u8 boot_reason; - }; - u32 value; - }; - }; - /* Globals. */ u32 g_boot_reason = 0; bool g_detected_boot_reason = false; @@ -90,12 +77,14 @@ namespace ams::boot { /* Set boot reason for SPL. */ if (hos::GetVersion() >= hos::Version_3_0_0) { - BootReasonValue boot_reason_value; - boot_reason_value.power_intr = power_intr; - boot_reason_value.rtc_intr = rtc_intr & ~rtc_intr_m; - boot_reason_value.nv_erc = nv_erc; + spl::BootReasonValue boot_reason_value = {}; + + boot_reason_value.power_intr = power_intr; + boot_reason_value.rtc_intr = rtc_intr & ~rtc_intr_m; + boot_reason_value.nv_erc = nv_erc; boot_reason_value.boot_reason = g_boot_reason; - R_ABORT_UNLESS(splSetBootReason(boot_reason_value.value)); + + R_ABORT_UNLESS(spl::SetBootReason(boot_reason_value)); } g_detected_boot_reason = true; diff --git a/stratosphere/boot/source/boot_display.cpp b/stratosphere/boot/source/boot_display.cpp index 9b1a2b346..8d6a78a83 100644 --- a/stratosphere/boot/source/boot_display.cpp +++ b/stratosphere/boot/source/boot_display.cpp @@ -66,7 +66,7 @@ namespace ams::boot { /* Globals. */ bool g_is_display_intialized = false; u32 *g_frame_buffer = nullptr; - bool g_is_mariko = false; + spl::SocType g_soc_type = spl::SocType_Erista; u32 g_lcd_vendor = 0; Handle g_dc_das_hnd = INVALID_HANDLE; u8 g_frame_buffer_storage[DeviceAddressSpaceAlignSize + FrameBufferSize]; @@ -95,10 +95,9 @@ namespace ams::boot { } inline void DoSocDependentRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes_erista, size_t num_writes_erista, const RegisterWrite *reg_writes_mariko, size_t num_writes_mariko) { - if (g_is_mariko) { - DoRegisterWrites(base_address, reg_writes_mariko, num_writes_mariko); - } else { - DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista); + switch (g_soc_type) { + case spl::SocType_Erista: DoRegisterWrites(base_address, reg_writes_erista, num_writes_erista); break; + case spl::SocType_Mariko: DoRegisterWrites(base_address, reg_writes_mariko, num_writes_mariko); break; } } @@ -188,7 +187,7 @@ namespace ams::boot { void InitializeDisplay() { /* Setup globals. */ InitializeRegisterBaseAddresses(); - g_is_mariko = spl::IsMariko(); + g_soc_type = spl::GetSocType(); InitializeFrameBuffer(); /* Turn on DSI/voltage rail. */ @@ -199,7 +198,7 @@ namespace ams::boot { i2c::driver::OpenSession(&i2c_session, I2cDevice_Max77620Pmic); - if (g_is_mariko) { + if (g_soc_type == spl::SocType_Mariko) { WriteI2cRegister(i2c_session, 0x18, 0x3A); WriteI2cRegister(i2c_session, 0x1F, 0x71); } @@ -242,7 +241,7 @@ namespace ams::boot { /* Configure display interface and display. */ reg::Write(g_mipi_cal_regs + 0x060, 0); - if (g_is_mariko) { + if (g_soc_type == spl::SocType_Mariko) { reg::Write(g_mipi_cal_regs + 0x058, 0); reg::Write(g_apb_misc_regs + 0xAC0, 0); } @@ -367,7 +366,7 @@ namespace ams::boot { DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11); DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal03); DO_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal04); - if (g_is_mariko) { + if (g_soc_type == spl::SocType_Mariko) { /* On Mariko the above configurations are executed twice, for some reason. */ DO_SOC_DEPENDENT_REGISTER_WRITES(g_mipi_cal_regs, DisplayConfigMipiCal02); DO_SOC_DEPENDENT_REGISTER_WRITES(g_dsi_regs, DisplayConfigDsi01Init11); diff --git a/stratosphere/boot/source/boot_main.cpp b/stratosphere/boot/source/boot_main.cpp index a6688f24d..50a7b7a2e 100644 --- a/stratosphere/boot/source/boot_main.cpp +++ b/stratosphere/boot/source/boot_main.cpp @@ -92,7 +92,7 @@ void __appInit(void) { /* Initialize services we need (TODO: NCM) */ sm::DoWithSession([&]() { R_ABORT_UNLESS(fsInitialize()); - R_ABORT_UNLESS(splInitialize()); + spl::Initialize(); R_ABORT_UNLESS(pmshellInitialize()); }); @@ -102,7 +102,7 @@ void __appInit(void) { void __appExit(void) { /* Cleanup services. */ pmshellExit(); - splExit(); + spl::Finalize(); fsExit(); } diff --git a/stratosphere/loader/source/ldr_main.cpp b/stratosphere/loader/source/ldr_main.cpp index c22308e6f..5d0af575e 100644 --- a/stratosphere/loader/source/ldr_main.cpp +++ b/stratosphere/loader/source/ldr_main.cpp @@ -75,7 +75,7 @@ void __appInit(void) { R_ABORT_UNLESS(fsInitialize()); lr::Initialize(); R_ABORT_UNLESS(fsldrInitialize()); - R_ABORT_UNLESS(splInitialize()); + spl::Initialize(); }); ams::CheckApiVersion(); @@ -83,7 +83,7 @@ void __appInit(void) { void __appExit(void) { /* Cleanup services. */ - splExit(); + spl::Finalize(); fsldrExit(); lr::Finalize(); fsExit(); @@ -121,9 +121,9 @@ int main(int argc, char **argv) /* Configure development. */ /* NOTE: Nintendo really does call the getter function three times instead of caching the value. */ - ldr::SetDevelopmentForAcidProductionCheck(spl::IsDevelopmentHardware()); - ldr::SetDevelopmentForAntiDowngradeCheck(spl::IsDevelopmentHardware()); - ldr::SetDevelopmentForAcidSignatureCheck(spl::IsDevelopmentHardware()); + ldr::SetDevelopmentForAcidProductionCheck(spl::IsDevelopment()); + ldr::SetDevelopmentForAntiDowngradeCheck(spl::IsDevelopment()); + ldr::SetDevelopmentForAcidSignatureCheck(spl::IsDevelopment()); /* Add services to manager. */ R_ABORT_UNLESS((g_server_manager.RegisterServer(ProcessManagerServiceName, ProcessManagerMaxSessions))); diff --git a/stratosphere/loader/source/ldr_meta.cpp b/stratosphere/loader/source/ldr_meta.cpp index bb8255b32..75c7ac666 100644 --- a/stratosphere/loader/source/ldr_meta.cpp +++ b/stratosphere/loader/source/ldr_meta.cpp @@ -101,13 +101,7 @@ namespace ams::ldr { } const u8 *GetAcidSignatureModulus(u32 key_generation) { - AMS_ASSERT(key_generation <= fssystem::AcidSignatureKeyGenerationMax); - const u32 used_keygen = (key_generation % (fssystem::AcidSignatureKeyGenerationMax + 1)); - if (IsDevelopmentForAcidSignatureCheck()) { - return fssystem::AcidSignatureKeyModulusDev[used_keygen]; - } else { - return fssystem::AcidSignatureKeyModulusProd[used_keygen]; - } + return fssystem::GetAcidSignatureKeyModulus(!IsDevelopmentForAcidSignatureCheck(), key_generation); } Result ValidateAcidSignature(Meta *meta) { @@ -122,8 +116,8 @@ namespace ams::ldr { const size_t sig_size = sizeof(meta->acid->signature); const u8 *mod = GetAcidSignatureModulus(meta->npdm->signature_key_generation); const size_t mod_size = fssystem::AcidSignatureKeyModulusSize; - const u8 *exp = fssystem::AcidSignatureKeyExponent; - const size_t exp_size = fssystem::AcidSignatureKeyExponentSize; + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; const u8 *msg = meta->acid->modulus; const size_t msg_size = meta->acid->size; const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size); diff --git a/stratosphere/loader/source/ldr_process_creation.cpp b/stratosphere/loader/source/ldr_process_creation.cpp index e715e3b3f..780368634 100644 --- a/stratosphere/loader/source/ldr_process_creation.cpp +++ b/stratosphere/loader/source/ldr_process_creation.cpp @@ -224,8 +224,8 @@ namespace ams::ldr { const size_t sig_size = sizeof(code_info.signature); const u8 *mod = static_cast(meta->modulus); const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize; - const u8 *exp = fssystem::AcidSignatureKeyExponent; - const size_t exp_size = fssystem::AcidSignatureKeyExponentSize; + const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent(); + const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize; const u8 *hsh = code_info.hash; const size_t hsh_size = sizeof(code_info.hash); const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size); diff --git a/stratosphere/ncm/source/ncm_main.cpp b/stratosphere/ncm/source/ncm_main.cpp index 8bb8760e2..8d16556c1 100644 --- a/stratosphere/ncm/source/ncm_main.cpp +++ b/stratosphere/ncm/source/ncm_main.cpp @@ -96,7 +96,7 @@ void __appInit(void) { sm::DoWithSession([&]() { R_ABORT_UNLESS(fsInitialize()); - R_ABORT_UNLESS(splInitialize()); + spl::Initialize(); }); ams::CheckApiVersion(); @@ -104,7 +104,7 @@ void __appInit(void) { void __appExit(void) { /* Cleanup services. */ - splExit(); + spl::Finalize(); fsExit(); } diff --git a/stratosphere/pm/source/pm_main.cpp b/stratosphere/pm/source/pm_main.cpp index 3efc9cabc..ae7078c32 100644 --- a/stratosphere/pm/source/pm_main.cpp +++ b/stratosphere/pm/source/pm_main.cpp @@ -143,7 +143,7 @@ void __appInit(void) { R_ABORT_UNLESS(sm::manager::EndInitialDefers()); R_ABORT_UNLESS(ldrPmInitialize()); - R_ABORT_UNLESS(splInitialize()); + spl::Initialize(); }); ams::CheckApiVersion(); @@ -151,7 +151,7 @@ void __appInit(void) { void __appExit(void) { /* Cleanup services. */ - splExit(); + spl::Finalize(); ldrPmExit(); smManagerExit(); fsprExit(); diff --git a/stratosphere/ro/source/ro_main.cpp b/stratosphere/ro/source/ro_main.cpp index aa0a1e912..2a0a8034c 100644 --- a/stratosphere/ro/source/ro_main.cpp +++ b/stratosphere/ro/source/ro_main.cpp @@ -64,7 +64,7 @@ void __appInit(void) { sm::DoWithSession([&]() { R_ABORT_UNLESS(setsysInitialize()); R_ABORT_UNLESS(fsInitialize()); - R_ABORT_UNLESS(splInitialize()); + spl::Initialize(); if (hos::GetVersion() < hos::Version_3_0_0) { R_ABORT_UNLESS(pminfoInitialize()); } @@ -80,6 +80,7 @@ void __appExit(void) { if (hos::GetVersion() < hos::Version_3_0_0) { pminfoExit(); } + setsysExit(); } @@ -114,9 +115,9 @@ int main(int argc, char **argv) /* Initialize Debug config. */ { - ON_SCOPE_EXIT { splExit(); }; + ON_SCOPE_EXIT { spl::Finalize(); }; - ro::SetDevelopmentHardware(spl::IsDevelopmentHardware()); + ro::SetDevelopmentHardware(spl::IsDevelopment()); ro::SetDevelopmentFunctionEnabled(spl::IsDevelopmentFunctionEnabled()); } diff --git a/stratosphere/spl/source/spl_api_impl.cpp b/stratosphere/spl/source/spl_api_impl.cpp index 49592e8b1..f4c420070 100644 --- a/stratosphere/spl/source/spl_api_impl.cpp +++ b/stratosphere/spl/source/spl_api_impl.cpp @@ -15,8 +15,8 @@ */ #include #include "spl_api_impl.hpp" - #include "spl_ctr_drbg.hpp" +#include "spl_key_slot_cache.hpp" namespace ams::spl::impl { @@ -35,21 +35,161 @@ namespace ams::spl::impl { constexpr size_t WorkBufferSizeMax = 0x800; - constexpr size_t MaxAesKeyslots = 6; - constexpr size_t MaxAesKeyslotsDeprecated = 4; + constexpr s32 MaxPhysicalAesKeyslots = 6; + constexpr s32 MaxPhysicalAesKeyslotsDeprecated = 4; - /* Max Keyslots helper. */ - inline size_t GetMaxKeyslots() { - return (hos::GetVersion() >= hos::Version_6_0_0) ? MaxAesKeyslots : MaxAesKeyslotsDeprecated; + constexpr s32 MaxVirtualAesKeyslots = 9; + + /* Keyslot management. */ + KeySlotCache g_keyslot_cache; + std::optional g_keyslot_cache_entry[MaxPhysicalAesKeyslots]; + + inline s32 GetMaxPhysicalKeyslots() { + return (hos::GetVersion() >= hos::Version_6_0_0) ? MaxPhysicalAesKeyslots : MaxPhysicalAesKeyslotsDeprecated; + } + + constexpr s32 VirtualKeySlotMin = 16; + constexpr s32 VirtualKeySlotMax = VirtualKeySlotMin + MaxVirtualAesKeyslots - 1; + + constexpr inline bool IsVirtualKeySlot(s32 keyslot) { + return VirtualKeySlotMin <= keyslot && keyslot <= VirtualKeySlotMax; + } + + inline bool IsPhysicalKeySlot(s32 keyslot) { + return keyslot < GetMaxPhysicalKeyslots(); + } + + constexpr inline s32 GetVirtualKeySlotIndex(s32 keyslot) { + AMS_ASSERT(IsVirtualKeySlot(keyslot)); + return keyslot - VirtualKeySlotMin; + } + + constexpr inline s32 MakeVirtualKeySlot(s32 index) { + const s32 virt_slot = index + VirtualKeySlotMin; + AMS_ASSERT(IsVirtualKeySlot(virt_slot)); + return virt_slot; + } + + void InitializeKeySlotCache() { + for (s32 i = 0; i < MaxPhysicalAesKeyslots; i++) { + g_keyslot_cache_entry[i].emplace(i); + g_keyslot_cache.AddEntry(std::addressof(g_keyslot_cache_entry[i].value())); + } + } + + enum class KeySlotContentType { + None = 0, + AesKey = 1, + TitleKey = 2, + }; + + struct KeySlotContents { + KeySlotContentType type; + union { + struct { + AccessKey access_key; + KeySource key_source; + } aes_key; + struct { + AccessKey access_key; + } title_key; + }; + }; + + const void *g_keyslot_owners[MaxVirtualAesKeyslots]; + KeySlotContents g_keyslot_contents[MaxVirtualAesKeyslots]; + KeySlotContents g_physical_keyslot_contents_for_backwards_compatibility[MaxPhysicalAesKeyslots]; + + void ClearPhysicalKeyslot(s32 keyslot) { + AMS_ASSERT(IsPhysicalKeySlot(keyslot)); + + AccessKey access_key = {}; + KeySource key_source = {}; + smc::LoadAesKey(keyslot, access_key, key_source); + } + + s32 GetPhysicalKeySlot(s32 keyslot, bool load) { + s32 phys_slot = -1; + KeySlotContents *contents = nullptr; + + if (hos::GetVersion() == hos::Version_1_0_0 && IsPhysicalKeySlot(keyslot)) { + /* On 1.0.0, we allow the use of physical keyslots. */ + phys_slot = keyslot; + contents = std::addressof(g_physical_keyslot_contents_for_backwards_compatibility[phys_slot]); + + /* If the physical slot is already loaded, we're good. */ + if (g_keyslot_cache.FindPhysical(phys_slot)) { + return phys_slot; + } + } else { + /* This should be a virtual keyslot. */ + AMS_ASSERT(IsVirtualKeySlot(keyslot)); + + /* Try to find a physical slot in the cache. */ + if (g_keyslot_cache.Find(std::addressof(phys_slot), keyslot)) { + return phys_slot; + } + + /* Allocate a physical slot. */ + phys_slot = g_keyslot_cache.Allocate(keyslot); + contents = std::addressof(g_keyslot_contents[GetVirtualKeySlotIndex(keyslot)]); + } + + /* Ensure the contents of the keyslot. */ + if (load) { + switch (contents->type) { + case KeySlotContentType::None: + ClearPhysicalKeyslot(phys_slot); + break; + case KeySlotContentType::AesKey: + R_ABORT_UNLESS(smc::ConvertResult(smc::LoadAesKey(phys_slot, contents->aes_key.access_key, contents->aes_key.key_source))); + break; + case KeySlotContentType::TitleKey: + R_ABORT_UNLESS(smc::ConvertResult(smc::LoadTitleKey(phys_slot, contents->title_key.access_key))); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + return phys_slot; + } + + Result LoadVirtualAesKey(s32 keyslot, const AccessKey &access_key, const KeySource &key_source) { + /* Ensure we can load into the slot. */ + const s32 phys_slot = GetPhysicalKeySlot(keyslot, false); + R_TRY(smc::ConvertResult(smc::LoadAesKey(phys_slot, access_key, key_source))); + + /* Update our contents. */ + const s32 index = GetVirtualKeySlotIndex(keyslot); + + g_keyslot_contents[index].type = KeySlotContentType::AesKey; + g_keyslot_contents[index].aes_key.access_key = access_key; + g_keyslot_contents[index].aes_key.key_source = key_source; + + return ResultSuccess(); + } + + Result LoadVirtualTitleKey(s32 keyslot, const AccessKey &access_key) { + /* Ensure we can load into the slot. */ + const s32 phys_slot = GetPhysicalKeySlot(keyslot, false); + R_TRY(smc::ConvertResult(smc::LoadTitleKey(phys_slot, access_key))); + + /* Update our contents. */ + const s32 index = GetVirtualKeySlotIndex(keyslot); + + g_keyslot_contents[index].type = KeySlotContentType::TitleKey; + g_keyslot_contents[index].title_key.access_key = access_key; + + return ResultSuccess(); } /* Type definitions. */ class ScopedAesKeyslot { private: - u32 slot; + s32 slot; bool has_slot; public: - ScopedAesKeyslot() : slot(0), has_slot(false) { + ScopedAesKeyslot() : slot(-1), has_slot(false) { /* ... */ } ~ScopedAesKeyslot() { @@ -58,7 +198,7 @@ namespace ams::spl::impl { } } - u32 GetKeyslot() const { + u32 GetKeySlot() const { return this->slot; } @@ -107,7 +247,6 @@ namespace ams::spl::impl { os::Mutex g_async_op_lock(false); - const void *g_keyslot_owners[MaxAesKeyslots]; BootReasonValue g_boot_reason; bool g_boot_reason_set; @@ -202,14 +341,21 @@ namespace ams::spl::impl { } /* Internal Keyslot utility. */ - Result ValidateAesKeyslot(u32 keyslot, const void *owner) { - R_UNLESS(keyslot < GetMaxKeyslots(), spl::ResultInvalidKeyslot()); - R_UNLESS((g_keyslot_owners[keyslot] == owner || hos::GetVersion() == hos::Version_1_0_0), spl::ResultInvalidKeyslot()); + Result ValidateAesKeyslot(s32 keyslot, const void *owner) { + /* Allow the use of physical keyslots on 1.0.0. */ + if (hos::GetVersion() == hos::Version_1_0_0) { + R_SUCCEED_IF(IsPhysicalKeySlot(keyslot)); + } + + R_UNLESS(IsVirtualKeySlot(keyslot), spl::ResultInvalidKeyslot()); + + const s32 index = GetVirtualKeySlotIndex(keyslot); + R_UNLESS(g_keyslot_owners[index] == owner, spl::ResultInvalidKeyslot()); return ResultSuccess(); } /* Helper to do a single AES block decryption. */ - smc::Result DecryptAesBlock(u32 keyslot, void *dst, const void *src) { + smc::Result DecryptAesBlock(s32 keyslot, void *dst, const void *src) { struct DecryptAesBlockLayout { SeCryptContext crypt_ctx; u8 in_block[AES_BLOCK_SIZE] __attribute__((aligned(AES_BLOCK_SIZE))); @@ -231,7 +377,7 @@ namespace ams::spl::impl { std::scoped_lock lk(g_async_op_lock); smc::AsyncOperationKey op_key; const IvCtr iv_ctr = {}; - const u32 mode = smc::GetCryptAesMode(smc::CipherMode::CbcDecrypt, keyslot); + const u32 mode = smc::GetCryptAesMode(smc::CipherMode::CbcDecrypt, GetPhysicalKeySlot(keyslot, true)); const u32 dst_ll_addr = g_se_mapped_work_buffer_addr + offsetof(DecryptAesBlockLayout, crypt_ctx.out); const u32 src_ll_addr = g_se_mapped_work_buffer_addr + offsetof(DecryptAesBlockLayout, crypt_ctx.in); @@ -363,6 +509,8 @@ namespace ams::spl::impl { InitializeSeEvents(); /* Initialize DAS for the SE. */ InitializeDeviceAddressSpace(); + /* Initialize the keyslot cache. */ + InitializeKeySlotCache(); } /* General. */ @@ -474,14 +622,12 @@ namespace ams::spl::impl { return smc::ConvertResult(smc::GenerateAesKek(out_access_key, key_source, generation, option)); } - Result LoadAesKey(u32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source) { + Result LoadAesKey(s32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source) { R_TRY(ValidateAesKeyslot(keyslot, owner)); - return smc::ConvertResult(smc::LoadAesKey(keyslot, access_key, key_source)); + return LoadVirtualAesKey(keyslot, access_key, key_source); } Result GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source) { - smc::Result smc_rc; - static constexpr KeySource s_generate_aes_key_source = { .data = {0x89, 0x61, 0x5E, 0xE0, 0x5C, 0x31, 0xB6, 0x80, 0x5F, 0xE5, 0x8F, 0x3D, 0xA2, 0x4F, 0x7A, 0xA8} }; @@ -489,12 +635,9 @@ namespace ams::spl::impl { ScopedAesKeyslot keyslot_holder; R_TRY(keyslot_holder.Allocate()); - smc_rc = smc::LoadAesKey(keyslot_holder.GetKeyslot(), access_key, s_generate_aes_key_source); - if (smc_rc == smc::Result::Success) { - smc_rc = DecryptAesBlock(keyslot_holder.GetKeyslot(), out_key, &key_source); - } + R_TRY(LoadVirtualAesKey(keyslot_holder.GetKeySlot(), access_key, s_generate_aes_key_source)); - return smc::ConvertResult(smc_rc); + return smc::ConvertResult(DecryptAesBlock(keyslot_holder.GetKeySlot(), out_key, &key_source)); } Result DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option) { @@ -508,7 +651,7 @@ namespace ams::spl::impl { return GenerateAesKey(out_key, access_key, key_source); } - Result CryptAesCtr(void *dst, size_t dst_size, u32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr) { + Result CryptAesCtr(void *dst, size_t dst_size, s32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr) { R_TRY(ValidateAesKeyslot(keyslot, owner)); /* Succeed immediately if there's nothing to crypt. */ @@ -555,7 +698,7 @@ namespace ams::spl::impl { { std::scoped_lock lk(g_async_op_lock); smc::AsyncOperationKey op_key; - const u32 mode = smc::GetCryptAesMode(smc::CipherMode::Ctr, keyslot); + const u32 mode = smc::GetCryptAesMode(smc::CipherMode::Ctr, GetPhysicalKeySlot(keyslot, true)); const u32 dst_ll_addr = g_se_mapped_work_buffer_addr + offsetof(SeCryptContext, out); const u32 src_ll_addr = g_se_mapped_work_buffer_addr + offsetof(SeCryptContext, in); @@ -573,26 +716,22 @@ namespace ams::spl::impl { return ResultSuccess(); } - Result ComputeCmac(Cmac *out_cmac, u32 keyslot, const void *owner, const void *data, size_t size) { + Result ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *owner, const void *data, size_t size) { R_TRY(ValidateAesKeyslot(keyslot, owner)); R_UNLESS(size <= WorkBufferSizeMax, spl::ResultInvalidSize()); std::memcpy(g_work_buffer, data, size); - return smc::ConvertResult(smc::ComputeCmac(out_cmac, keyslot, g_work_buffer, size)); + return smc::ConvertResult(smc::ComputeCmac(out_cmac, GetPhysicalKeySlot(keyslot, true), g_work_buffer, size)); } - Result AllocateAesKeyslot(u32 *out_keyslot, const void *owner) { - if (hos::GetVersion() <= hos::Version_1_0_0) { - /* On 1.0.0, keyslots were kind of a wild west. */ - *out_keyslot = 0; - return ResultSuccess(); - } - - for (size_t i = 0; i < GetMaxKeyslots(); i++) { - if (g_keyslot_owners[i] == 0) { - g_keyslot_owners[i] = owner; - *out_keyslot = static_cast(i); + Result AllocateAesKeyslot(s32 *out_keyslot, const void *owner) { + /* Find a virtual keyslot. */ + for (s32 i = 0; i < MaxVirtualAesKeyslots; i++) { + if (g_keyslot_owners[i] == nullptr) { + g_keyslot_owners[i] = owner; + g_keyslot_contents[i] = { .type = KeySlotContentType::None }; + *out_keyslot = MakeVirtualKeySlot(i); return ResultSuccess(); } } @@ -601,22 +740,24 @@ namespace ams::spl::impl { return spl::ResultOutOfKeyslots(); } - Result FreeAesKeyslot(u32 keyslot, const void *owner) { - if (hos::GetVersion() <= hos::Version_1_0_0) { - /* On 1.0.0, keyslots were kind of a wild west. */ - return ResultSuccess(); - } + Result FreeAesKeyslot(s32 keyslot, const void *owner) { + /* Only virtual keyslots can be freed. */ + R_UNLESS(IsVirtualKeySlot(keyslot), spl::ResultInvalidKeyslot()); + /* Ensure the keyslot is owned. */ R_TRY(ValidateAesKeyslot(keyslot, owner)); - /* Clear the keyslot. */ - { - AccessKey access_key = {}; - KeySource key_source = {}; - - smc::LoadAesKey(keyslot, access_key, key_source); + /* Clear the physical keyslot, if we're cached. */ + s32 phys_slot; + if (g_keyslot_cache.Release(std::addressof(phys_slot), keyslot)) { + ClearPhysicalKeyslot(phys_slot); } - g_keyslot_owners[keyslot] = nullptr; + + /* Clear the virtual keyslot. */ + const auto index = GetVirtualKeySlotIndex(keyslot); + g_keyslot_owners[index] = nullptr; + g_keyslot_contents[index].type = KeySlotContentType::None; + os::SignalSystemEvent(std::addressof(g_se_keyslot_available_event)); return ResultSuccess(); } @@ -702,7 +843,7 @@ namespace ams::spl::impl { return UnwrapEsRsaOaepWrappedKey(out_access_key, base, base_size, mod, mod_size, label_digest, label_digest_size, generation, smc::EsKeyType::ElicenseKey); } - Result LoadElicenseKey(u32 keyslot, const void *owner, const AccessKey &access_key) { + Result LoadElicenseKey(s32 keyslot, const void *owner, const AccessKey &access_key) { /* Right now, this is just literally the same function as LoadTitleKey in N's impl. */ return LoadTitleKey(keyslot, owner, access_key); } @@ -731,9 +872,9 @@ namespace ams::spl::impl { return smc::ConvertResult(smc::GenerateSpecificAesKey(out_key, key_source, generation, which)); } - Result LoadTitleKey(u32 keyslot, const void *owner, const AccessKey &access_key) { + Result LoadTitleKey(s32 keyslot, const void *owner, const AccessKey &access_key) { R_TRY(ValidateAesKeyslot(keyslot, owner)); - return smc::ConvertResult(smc::LoadTitleKey(keyslot, access_key)); + return LoadVirtualTitleKey(keyslot, access_key); } Result GetPackage2Hash(void *dst, const size_t size) { @@ -784,9 +925,9 @@ namespace ams::spl::impl { /* Helper. */ Result FreeAesKeyslots(const void *owner) { - for (size_t i = 0; i < GetMaxKeyslots(); i++) { - if (g_keyslot_owners[i] == owner) { - FreeAesKeyslot(i, owner); + for (s32 slot = VirtualKeySlotMin; slot <= VirtualKeySlotMax; ++slot) { + if (g_keyslot_owners[GetVirtualKeySlotIndex(slot)] == owner) { + FreeAesKeyslot(slot, owner); } } return ResultSuccess(); diff --git a/stratosphere/spl/source/spl_api_impl.hpp b/stratosphere/spl/source/spl_api_impl.hpp index 9422104ea..483f29e53 100644 --- a/stratosphere/spl/source/spl_api_impl.hpp +++ b/stratosphere/spl/source/spl_api_impl.hpp @@ -32,13 +32,13 @@ namespace ams::spl::impl { /* Crypto. */ Result GenerateAesKek(AccessKey *out_access_key, const KeySource &key_source, u32 generation, u32 option); - Result LoadAesKey(u32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source); + Result LoadAesKey(s32 keyslot, const void *owner, const AccessKey &access_key, const KeySource &key_source); Result GenerateAesKey(AesKey *out_key, const AccessKey &access_key, const KeySource &key_source); Result DecryptAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 option); - Result CryptAesCtr(void *dst, size_t dst_size, u32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr); - Result ComputeCmac(Cmac *out_cmac, u32 keyslot, const void *owner, const void *data, size_t size); - Result AllocateAesKeyslot(u32 *out_keyslot, const void *owner); - Result FreeAesKeyslot(u32 keyslot, const void *owner); + Result CryptAesCtr(void *dst, size_t dst_size, s32 keyslot, const void *owner, const void *src, size_t src_size, const IvCtr &iv_ctr); + Result ComputeCmac(Cmac *out_cmac, s32 keyslot, const void *owner, const void *data, size_t size); + Result AllocateAesKeyslot(s32 *out_keyslot, const void *owner); + Result FreeAesKeyslot(s32 keyslot, const void *owner); /* RSA. */ Result DecryptRsaPrivateKey(void *dst, size_t dst_size, const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); @@ -54,13 +54,13 @@ namespace ams::spl::impl { Result ImportDrmKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source); Result DrmExpMod(void *out, size_t out_size, const void *base, size_t base_size, const void *mod, size_t mod_size); Result UnwrapElicenseKey(AccessKey *out_access_key, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size, u32 generation); - Result LoadElicenseKey(u32 keyslot, const void *owner, const AccessKey &access_key); + Result LoadElicenseKey(s32 keyslot, const void *owner, const AccessKey &access_key); /* FS */ Result ImportLotusKey(const void *src, size_t src_size, const AccessKey &access_key, const KeySource &key_source, u32 option); Result DecryptLotusMessage(u32 *out_size, void *dst, size_t dst_size, const void *base, size_t base_size, const void *mod, size_t mod_size, const void *label_digest, size_t label_digest_size); Result GenerateSpecificAesKey(AesKey *out_key, const KeySource &key_source, u32 generation, u32 which); - Result LoadTitleKey(u32 keyslot, const void *owner, const AccessKey &access_key); + Result LoadTitleKey(s32 keyslot, const void *owner, const AccessKey &access_key); Result GetPackage2Hash(void *dst, const size_t size); /* Manu. */ diff --git a/stratosphere/spl/source/spl_crypto_service.cpp b/stratosphere/spl/source/spl_crypto_service.cpp index 4bd5bbe06..63b150a43 100644 --- a/stratosphere/spl/source/spl_crypto_service.cpp +++ b/stratosphere/spl/source/spl_crypto_service.cpp @@ -28,7 +28,7 @@ namespace ams::spl { return impl::GenerateAesKek(out_access_key.GetPointer(), key_source, generation, option); } - Result CryptoService::LoadAesKey(u32 keyslot, AccessKey access_key, KeySource key_source) { + Result CryptoService::LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source) { return impl::LoadAesKey(keyslot, this, access_key, key_source); } @@ -40,19 +40,19 @@ namespace ams::spl { return impl::DecryptAesKey(out_key.GetPointer(), key_source, generation, option); } - Result CryptoService::CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, u32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { + Result CryptoService::CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { return impl::CryptAesCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr); } - Result CryptoService::ComputeCmac(sf::Out out_cmac, u32 keyslot, const sf::InPointerBuffer &in_buf) { + Result CryptoService::ComputeCmac(sf::Out out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf) { return impl::ComputeCmac(out_cmac.GetPointer(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize()); } - Result CryptoService::AllocateAesKeyslot(sf::Out out_keyslot) { + Result CryptoService::AllocateAesKeyslot(sf::Out out_keyslot) { return impl::AllocateAesKeyslot(out_keyslot.GetPointer(), this); } - Result CryptoService::FreeAesKeyslot(u32 keyslot) { + Result CryptoService::FreeAesKeyslot(s32 keyslot) { return impl::FreeAesKeyslot(keyslot, this); } diff --git a/stratosphere/spl/source/spl_crypto_service.hpp b/stratosphere/spl/source/spl_crypto_service.hpp index 58841575c..8eec93dd8 100644 --- a/stratosphere/spl/source/spl_crypto_service.hpp +++ b/stratosphere/spl/source/spl_crypto_service.hpp @@ -25,13 +25,13 @@ namespace ams::spl { protected: /* Actual commands. */ virtual Result GenerateAesKek(sf::Out out_access_key, KeySource key_source, u32 generation, u32 option); - virtual Result LoadAesKey(u32 keyslot, AccessKey access_key, KeySource key_source); + virtual Result LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source); virtual Result GenerateAesKey(sf::Out out_key, AccessKey access_key, KeySource key_source); virtual Result DecryptAesKey(sf::Out out_key, KeySource key_source, u32 generation, u32 option); - virtual Result CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, u32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr); - virtual Result ComputeCmac(sf::Out out_cmac, u32 keyslot, const sf::InPointerBuffer &in_buf); - virtual Result AllocateAesKeyslot(sf::Out out_keyslot); - virtual Result FreeAesKeyslot(u32 keyslot); + virtual Result CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr); + virtual Result ComputeCmac(sf::Out out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf); + virtual Result AllocateAesKeyslot(sf::Out out_keyslot); + virtual Result FreeAesKeyslot(s32 keyslot); virtual void GetAesKeyslotAvailableEvent(sf::OutCopyHandle out_hnd); public: DEFINE_SERVICE_DISPATCH_TABLE { @@ -48,9 +48,9 @@ namespace ams::spl { MAKE_SERVICE_COMMAND_META(DecryptAesKey), MAKE_SERVICE_COMMAND_META(CryptAesCtr), MAKE_SERVICE_COMMAND_META(ComputeCmac), - MAKE_SERVICE_COMMAND_META(AllocateAesKeyslot, hos::Version_2_0_0), - MAKE_SERVICE_COMMAND_META(FreeAesKeyslot, hos::Version_2_0_0), - MAKE_SERVICE_COMMAND_META(GetAesKeyslotAvailableEvent, hos::Version_2_0_0), + MAKE_SERVICE_COMMAND_META(AllocateAesKeyslot /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), + MAKE_SERVICE_COMMAND_META(FreeAesKeyslot /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), + MAKE_SERVICE_COMMAND_META(GetAesKeyslotAvailableEvent /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), }; }; diff --git a/stratosphere/spl/source/spl_deprecated_service.cpp b/stratosphere/spl/source/spl_deprecated_service.cpp index 545c6a2b5..f6c4ec8c8 100644 --- a/stratosphere/spl/source/spl_deprecated_service.cpp +++ b/stratosphere/spl/source/spl_deprecated_service.cpp @@ -31,7 +31,7 @@ namespace ams::spl { return impl::GenerateAesKek(out_access_key.GetPointer(), key_source, generation, option); } - Result DeprecatedService::LoadAesKey(u32 keyslot, AccessKey access_key, KeySource key_source) { + Result DeprecatedService::LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source) { return impl::LoadAesKey(keyslot, this, access_key, key_source); } @@ -71,15 +71,15 @@ namespace ams::spl { return impl::DecryptAesKey(out_key.GetPointer(), key_source, generation, option); } - Result DeprecatedService::CryptAesCtrDeprecated(const sf::OutBuffer &out_buf, u32 keyslot, const sf::InBuffer &in_buf, IvCtr iv_ctr) { + Result DeprecatedService::CryptAesCtrDeprecated(const sf::OutBuffer &out_buf, s32 keyslot, const sf::InBuffer &in_buf, IvCtr iv_ctr) { return impl::CryptAesCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr); } - Result DeprecatedService::CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, u32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { + Result DeprecatedService::CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr) { return impl::CryptAesCtr(out_buf.GetPointer(), out_buf.GetSize(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize(), iv_ctr); } - Result DeprecatedService::ComputeCmac(sf::Out out_cmac, u32 keyslot, const sf::InPointerBuffer &in_buf) { + Result DeprecatedService::ComputeCmac(sf::Out out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf) { return impl::ComputeCmac(out_cmac.GetPointer(), keyslot, this, in_buf.GetPointer(), in_buf.GetSize()); } @@ -95,7 +95,7 @@ namespace ams::spl { return impl::UnwrapTitleKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation); } - Result DeprecatedService::LoadTitleKey(u32 keyslot, AccessKey access_key) { + Result DeprecatedService::LoadTitleKey(s32 keyslot, AccessKey access_key) { return impl::LoadTitleKey(keyslot, this, access_key); } @@ -107,11 +107,11 @@ namespace ams::spl { return impl::UnwrapCommonTitleKey(out_access_key.GetPointer(), key_source, generation); } - Result DeprecatedService::AllocateAesKeyslot(sf::Out out_keyslot) { + Result DeprecatedService::AllocateAesKeyslot(sf::Out out_keyslot) { return impl::AllocateAesKeyslot(out_keyslot.GetPointer(), this); } - Result DeprecatedService::FreeAesKeyslot(u32 keyslot) { + Result DeprecatedService::FreeAesKeyslot(s32 keyslot) { return impl::FreeAesKeyslot(keyslot, this); } diff --git a/stratosphere/spl/source/spl_deprecated_service.hpp b/stratosphere/spl/source/spl_deprecated_service.hpp index 6274ed336..76c86138b 100644 --- a/stratosphere/spl/source/spl_deprecated_service.hpp +++ b/stratosphere/spl/source/spl_deprecated_service.hpp @@ -63,7 +63,7 @@ namespace ams::spl { virtual Result GetConfig(sf::Out out, u32 which); virtual Result ExpMod(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &exp, const sf::InPointerBuffer &mod); virtual Result GenerateAesKek(sf::Out out_access_key, KeySource key_source, u32 generation, u32 option); - virtual Result LoadAesKey(u32 keyslot, AccessKey access_key, KeySource key_source); + virtual Result LoadAesKey(s32 keyslot, AccessKey access_key, KeySource key_source); virtual Result GenerateAesKey(sf::Out out_key, AccessKey access_key, KeySource key_source); virtual Result SetConfig(u32 which, u64 value); virtual Result GenerateRandomBytes(const sf::OutPointerBuffer &out); @@ -73,17 +73,17 @@ namespace ams::spl { virtual Result GenerateSpecificAesKey(sf::Out out_key, KeySource key_source, u32 generation, u32 which); virtual Result DecryptRsaPrivateKey(const sf::OutPointerBuffer &dst, const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option); virtual Result DecryptAesKey(sf::Out out_key, KeySource key_source, u32 generation, u32 option); - virtual Result CryptAesCtrDeprecated(const sf::OutBuffer &out_buf, u32 keyslot, const sf::InBuffer &in_buf, IvCtr iv_ctr); - virtual Result CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, u32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr); - virtual Result ComputeCmac(sf::Out out_cmac, u32 keyslot, const sf::InPointerBuffer &in_buf); + virtual Result CryptAesCtrDeprecated(const sf::OutBuffer &out_buf, s32 keyslot, const sf::InBuffer &in_buf, IvCtr iv_ctr); + virtual Result CryptAesCtr(const sf::OutNonSecureBuffer &out_buf, s32 keyslot, const sf::InNonSecureBuffer &in_buf, IvCtr iv_ctr); + virtual Result ComputeCmac(sf::Out out_cmac, s32 keyslot, const sf::InPointerBuffer &in_buf); virtual Result ImportEsKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source, u32 option); virtual Result UnwrapTitleKeyDeprecated(sf::Out out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest); virtual Result UnwrapTitleKey(sf::Out out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation); - virtual Result LoadTitleKey(u32 keyslot, AccessKey access_key); + virtual Result LoadTitleKey(s32 keyslot, AccessKey access_key); virtual Result UnwrapCommonTitleKeyDeprecated(sf::Out out_access_key, KeySource key_source); virtual Result UnwrapCommonTitleKey(sf::Out out_access_key, KeySource key_source, u32 generation); - virtual Result AllocateAesKeyslot(sf::Out out_keyslot); - virtual Result FreeAesKeyslot(u32 keyslot); + virtual Result AllocateAesKeyslot(sf::Out out_keyslot); + virtual Result FreeAesKeyslot(s32 keyslot); virtual void GetAesKeyslotAvailableEvent(sf::OutCopyHandle out_hnd); virtual Result SetBootReason(BootReasonValue boot_reason); virtual Result GetBootReason(sf::Out out); @@ -117,9 +117,9 @@ namespace ams::spl { MAKE_SERVICE_COMMAND_META(UnwrapCommonTitleKeyDeprecated, hos::Version_2_0_0, hos::Version_2_0_0), MAKE_SERVICE_COMMAND_META(UnwrapCommonTitleKey, hos::Version_3_0_0), - MAKE_SERVICE_COMMAND_META(AllocateAesKeyslot, hos::Version_2_0_0), - MAKE_SERVICE_COMMAND_META(FreeAesKeyslot, hos::Version_2_0_0), - MAKE_SERVICE_COMMAND_META(GetAesKeyslotAvailableEvent, hos::Version_2_0_0), + MAKE_SERVICE_COMMAND_META(AllocateAesKeyslot /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), + MAKE_SERVICE_COMMAND_META(FreeAesKeyslot /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), + MAKE_SERVICE_COMMAND_META(GetAesKeyslotAvailableEvent /* Atmosphere extension: This was added in hos::Version_2_0_0, but is allowed on older firmware by atmosphere. */), MAKE_SERVICE_COMMAND_META(SetBootReason, hos::Version_3_0_0), MAKE_SERVICE_COMMAND_META(GetBootReason, hos::Version_3_0_0), diff --git a/stratosphere/spl/source/spl_es_service.cpp b/stratosphere/spl/source/spl_es_service.cpp index 4c9e08952..9f1a9cffc 100644 --- a/stratosphere/spl/source/spl_es_service.cpp +++ b/stratosphere/spl/source/spl_es_service.cpp @@ -47,7 +47,7 @@ namespace ams::spl { return impl::UnwrapElicenseKey(out_access_key.GetPointer(), base.GetPointer(), base.GetSize(), mod.GetPointer(), mod.GetSize(), label_digest.GetPointer(), label_digest.GetSize(), generation); } - Result EsService::LoadElicenseKey(u32 keyslot, AccessKey access_key) { + Result EsService::LoadElicenseKey(s32 keyslot, AccessKey access_key) { return impl::LoadElicenseKey(keyslot, this, access_key); } diff --git a/stratosphere/spl/source/spl_es_service.hpp b/stratosphere/spl/source/spl_es_service.hpp index 82989d4ba..829cc4a86 100644 --- a/stratosphere/spl/source/spl_es_service.hpp +++ b/stratosphere/spl/source/spl_es_service.hpp @@ -31,7 +31,7 @@ namespace ams::spl { virtual Result ImportDrmKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source); virtual Result DrmExpMod(const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod); virtual Result UnwrapElicenseKey(sf::Out out_access_key, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest, u32 generation); - virtual Result LoadElicenseKey(u32 keyslot, AccessKey access_key); + virtual Result LoadElicenseKey(s32 keyslot, AccessKey access_key); public: DEFINE_SERVICE_DISPATCH_TABLE { MAKE_SERVICE_COMMAND_META(GetConfig), diff --git a/stratosphere/spl/source/spl_fs_service.cpp b/stratosphere/spl/source/spl_fs_service.cpp index 8d3c9ce8b..290e4b177 100644 --- a/stratosphere/spl/source/spl_fs_service.cpp +++ b/stratosphere/spl/source/spl_fs_service.cpp @@ -35,7 +35,7 @@ namespace ams::spl { return impl::GenerateSpecificAesKey(out_key.GetPointer(), key_source, generation, which); } - Result FsService::LoadTitleKey(u32 keyslot, AccessKey access_key) { + Result FsService::LoadTitleKey(s32 keyslot, AccessKey access_key) { return impl::LoadTitleKey(keyslot, this, access_key); } diff --git a/stratosphere/spl/source/spl_fs_service.hpp b/stratosphere/spl/source/spl_fs_service.hpp index 7b13303ee..b9a48781f 100644 --- a/stratosphere/spl/source/spl_fs_service.hpp +++ b/stratosphere/spl/source/spl_fs_service.hpp @@ -28,7 +28,7 @@ namespace ams::spl { virtual Result ImportLotusKey(const sf::InPointerBuffer &src, AccessKey access_key, KeySource key_source); virtual Result DecryptLotusMessage(sf::Out out_size, const sf::OutPointerBuffer &out, const sf::InPointerBuffer &base, const sf::InPointerBuffer &mod, const sf::InPointerBuffer &label_digest); virtual Result GenerateSpecificAesKey(sf::Out out_key, KeySource key_source, u32 generation, u32 which); - virtual Result LoadTitleKey(u32 keyslot, AccessKey access_key); + virtual Result LoadTitleKey(s32 keyslot, AccessKey access_key); virtual Result GetPackage2Hash(const sf::OutPointerBuffer &dst); public: DEFINE_SERVICE_DISPATCH_TABLE { diff --git a/stratosphere/spl/source/spl_key_slot_cache.hpp b/stratosphere/spl/source/spl_key_slot_cache.hpp new file mode 100644 index 000000000..18c093dc8 --- /dev/null +++ b/stratosphere/spl/source/spl_key_slot_cache.hpp @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::spl { + + class KeySlotCacheEntry : public util::IntrusiveListBaseNode { + NON_COPYABLE(KeySlotCacheEntry); + NON_MOVEABLE(KeySlotCacheEntry); + private: + friend class KeySlotCache; + public: + static constexpr size_t KeySize = crypto::AesDecryptor128::KeySize; + private: + const s32 slot_index; + s32 virtual_slot; + public: + explicit KeySlotCacheEntry(s32 idx) : slot_index(idx), virtual_slot(-1) { /* ... */ } + + bool Contains(s32 virtual_slot) const { + return virtual_slot == this->virtual_slot; + } + + s32 GetPhysicalKeySlotIndex() const { return this->slot_index; } + + s32 GetVirtualKeySlotIndex() const { return this->virtual_slot; } + + void SetVirtualSlot(s32 virtual_slot) { + this->virtual_slot = virtual_slot; + } + + void ClearVirtualSlot() { + this->virtual_slot = -1; + } + }; + + class KeySlotCache { + NON_COPYABLE(KeySlotCache); + NON_MOVEABLE(KeySlotCache); + private: + using KeySlotCacheEntryList = util::IntrusiveListBaseTraits::ListType; + private: + KeySlotCacheEntryList mru_list; + public: + constexpr KeySlotCache() : mru_list() { /* ... */ } + + s32 Allocate(s32 virtual_slot) { + return this->AllocateFromLru(virtual_slot); + } + + bool Find(s32 *out, s32 virtual_slot) { + for (auto it = this->mru_list.begin(); it != this->mru_list.end(); ++it) { + if (it->Contains(virtual_slot)) { + *out = it->GetPhysicalKeySlotIndex(); + + this->UpdateMru(it); + return true; + } + } + + return false; + } + + bool Release(s32 *out, s32 virtual_slot) { + for (auto it = this->mru_list.begin(); it != this->mru_list.end(); ++it) { + if (it->Contains(virtual_slot)) { + *out = it->GetPhysicalKeySlotIndex(); + it->ClearVirtualSlot(); + + this->UpdateLru(it); + return true; + } + } + + return false; + } + + bool FindPhysical(s32 physical_slot) { + for (auto it = this->mru_list.begin(); it != this->mru_list.end(); ++it) { + if (it->GetPhysicalKeySlotIndex() == physical_slot) { + this->UpdateMru(it); + + if (it->GetVirtualKeySlotIndex() == physical_slot) { + return true; + } else { + it->SetVirtualSlot(physical_slot); + return false; + } + } + } + AMS_ABORT(); + } + + void AddEntry(KeySlotCacheEntry *entry) { + this->mru_list.push_front(*entry); + } + private: + s32 AllocateFromLru(s32 virtual_slot) { + AMS_ASSERT(!this->mru_list.empty()); + + auto it = this->mru_list.rbegin(); + it->SetVirtualSlot(virtual_slot); + + auto *entry = std::addressof(*it); + this->mru_list.pop_back(); + this->mru_list.push_front(*entry); + + return entry->GetPhysicalKeySlotIndex(); + } + + void UpdateMru(KeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + this->mru_list.erase(it); + this->mru_list.push_front(*entry); + } + + void UpdateLru(KeySlotCacheEntryList::iterator it) { + auto *entry = std::addressof(*it); + this->mru_list.erase(it); + this->mru_list.push_back(*entry); + } + }; + +}