From 303c6eb5f94394f364ee1608725ba72baa1fadf9 Mon Sep 17 00:00:00 2001 From: Adubbz Date: Sun, 10 Oct 2021 03:16:40 +1100 Subject: [PATCH] settings: implement KeyValueStore (#1659) * settings: implement KeyValueStore and dependencies * settings: update KeyValueStore for recent refactoring * settings: address feedback --- .../program/source/smc/secmon_smc_info.cpp | 4 +- .../program/source/smc/secmon_smc_info.hpp | 2 +- .../libexosphere/include/exosphere/fuse.hpp | 30 +- .../libexosphere/source/fuse/fuse_api.cpp | 22 +- .../impl/ams_system_thread_definitions.hpp | 1 + .../ncm/ncm_system_content_meta_id.hpp | 8 + .../settings/settings_fwdbg_types.hpp | 15 - .../stratosphere/settings/settings_types.hpp | 15 + .../include/stratosphere/spl/spl_api.hpp | 6 + .../include/stratosphere/spl/spl_types.hpp | 7 +- .../impl/settings_key_value_store.cpp | 1641 +++++++++++++++++ .../impl/settings_key_value_store.hpp | 67 + .../source/settings/impl/settings_spl.cpp | 100 + .../source/settings/impl/settings_spl.hpp | 36 + .../settings/impl/settings_static_object.hpp | 52 + .../settings/impl/settings_system_data.cpp | 170 ++ .../settings/impl/settings_system_data.hpp | 41 + .../impl/settings_system_save_data.cpp | 503 +++++ .../impl/settings_system_save_data.hpp | 51 + .../vapours/results/settings_results.hpp | 33 +- .../source/set_mitm/setsys_mitm_service.cpp | 4 +- .../source/set_mitm/setsys_mitm_service.hpp | 8 +- .../source/set_mitm/settings_sd_kvs.cpp | 32 +- 23 files changed, 2767 insertions(+), 81 deletions(-) create mode 100644 libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_spl.cpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_spl.hpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_static_object.hpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_system_data.cpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_system_data.hpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp create mode 100644 libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp diff --git a/exosphere/program/source/smc/secmon_smc_info.cpp b/exosphere/program/source/smc/secmon_smc_info.cpp index 704dd6f08..9b889b2a9 100644 --- a/exosphere/program/source/smc/secmon_smc_info.cpp +++ b/exosphere/program/source/smc/secmon_smc_info.cpp @@ -213,8 +213,8 @@ namespace ams::secmon::smc { case ConfigItem::IsChargerHiZModeEnabled: args.r[1] = IsChargerHiZModeEnabled(); break; - case ConfigItem::QuestState: - args.r[1] = fuse::GetQuestState(); + case ConfigItem::RetailInteractiveDisplayState: + args.r[1] = fuse::GetRetailInteractiveDisplayState(); break; case ConfigItem::RegulatorType: args.r[1] = fuse::GetRegulator(); diff --git a/exosphere/program/source/smc/secmon_smc_info.hpp b/exosphere/program/source/smc/secmon_smc_info.hpp index b4811fb29..721083878 100644 --- a/exosphere/program/source/smc/secmon_smc_info.hpp +++ b/exosphere/program/source/smc/secmon_smc_info.hpp @@ -34,7 +34,7 @@ namespace ams::secmon::smc { IsDevelopmentFunctionEnabled = 11, KernelConfiguration = 12, IsChargerHiZModeEnabled = 13, - QuestState = 14, + RetailInteractiveDisplayState = 14, RegulatorType = 15, DeviceUniqueKeyGeneration = 16, Package2Hash = 17, diff --git a/libraries/libexosphere/include/exosphere/fuse.hpp b/libraries/libexosphere/include/exosphere/fuse.hpp index 705e36220..ede7485e1 100644 --- a/libraries/libexosphere/include/exosphere/fuse.hpp +++ b/libraries/libexosphere/include/exosphere/fuse.hpp @@ -81,9 +81,9 @@ namespace ams::fuse { DramId_Count, }; - enum QuestState { - QuestState_Disabled = 0, - QuestState_Enabled = 1, + enum RetailInteractiveDisplayState { + RetailInteractiveDisplayState_Disabled = 0, + RetailInteractiveDisplayState_Enabled = 1, }; void SetRegisterAddress(uintptr_t address); @@ -102,19 +102,19 @@ namespace ams::fuse { bool GetSecureBootKey(void *dst); - void GetEcid(br::BootEcid *out); - HardwareType GetHardwareType(); - HardwareState GetHardwareState(); - u64 GetDeviceId(); - PatchVersion GetPatchVersion(); - QuestState GetQuestState(); - pmic::Regulator GetRegulator(); - int GetDeviceUniqueKeyGeneration(); + void GetEcid(br::BootEcid *out); + HardwareType GetHardwareType(); + HardwareState GetHardwareState(); + u64 GetDeviceId(); + PatchVersion GetPatchVersion(); + RetailInteractiveDisplayState GetRetailInteractiveDisplayState(); + pmic::Regulator GetRegulator(); + int GetDeviceUniqueKeyGeneration(); - SocType GetSocType(); - int GetExpectedFuseVersion(TargetFirmware target_fw); - int GetFuseVersion(); - bool HasRcmVulnerabilityPatch(); + SocType GetSocType(); + int GetExpectedFuseVersion(TargetFirmware target_fw); + int GetFuseVersion(); + bool HasRcmVulnerabilityPatch(); bool IsOdmProductionMode(); void ConfigureFuseBypass(); diff --git a/libraries/libexosphere/source/fuse/fuse_api.cpp b/libraries/libexosphere/source/fuse/fuse_api.cpp index 68179c9c0..e8bf09afa 100644 --- a/libraries/libexosphere/source/fuse/fuse_api.cpp +++ b/libraries/libexosphere/source/fuse/fuse_api.cpp @@ -37,15 +37,15 @@ namespace ams::fuse { }; struct OdmWord4 { - using HardwareState1 = util::BitPack32::Field<0, 2, int>; - using HardwareType1 = util::BitPack32::Field; - using DramId = util::BitPack32::Field; - using HardwareType2 = util::BitPack32::Field; - using HardwareState2 = util::BitPack32::Field; - using QuestState = util::BitPack32::Field; - using FormatVersion = util::BitPack32::Field; - using Reserved = util::BitPack32::Field; - using HardwareType3 = util::BitPack32::Field; + using HardwareState1 = util::BitPack32::Field<0, 2, int>; + using HardwareType1 = util::BitPack32::Field; + using DramId = util::BitPack32::Field; + using HardwareType2 = util::BitPack32::Field; + using HardwareState2 = util::BitPack32::Field; + using RetailInteractiveDisplayState = util::BitPack32::Field; + using FormatVersion = util::BitPack32::Field; + using Reserved = util::BitPack32::Field; + using HardwareType3 = util::BitPack32::Field; }; struct OdmWord28 { @@ -343,8 +343,8 @@ namespace ams::fuse { return static_cast(static_cast(GetSocType() << 12) | patch_version); } - QuestState GetQuestState() { - return static_cast(util::BitPack32{GetCommonOdmWord(4)}.Get()); + RetailInteractiveDisplayState GetRetailInteractiveDisplayState() { + return static_cast(util::BitPack32{GetCommonOdmWord(4)}.Get()); } pmic::Regulator GetRegulator() { diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 7559e3abc..c183b21ef 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -114,6 +114,7 @@ namespace ams::impl { /* settings. */ AMS_DEFINE_SYSTEM_THREAD(21, settings, Main); AMS_DEFINE_SYSTEM_THREAD(21, settings, IpcServer); + AMS_DEFINE_SYSTEM_THREAD(21, settings, LazyWriter); /* erpt. */ AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main); diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp index c90b4d103..d6974c7b0 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_system_content_meta_id.hpp @@ -267,6 +267,10 @@ namespace ams::ncm { static const SystemDataId RebootlessSystemUpdateVersion; static const SystemDataId ContentActionTable; + static const SystemDataId PlatformConfigCalcio; + + static const SystemDataId PlatformConfigAula; + static const SystemDataId End; }; @@ -312,6 +316,10 @@ namespace ams::ncm { inline constexpr const SystemDataId SystemDataId::RebootlessSystemUpdateVersion = { 0x0100000000000826ul }; inline constexpr const SystemDataId SystemDataId::ContentActionTable = { 0x0100000000000827ul }; + inline constexpr const SystemDataId SystemDataId::PlatformConfigCalcio = { 0x0100000000000829ul }; + + inline constexpr const SystemDataId SystemDataId::PlatformConfigAula = { 0x0100000000000831ul }; + inline constexpr const SystemDataId SystemDataId::End = { 0x0100000000000FFFul }; inline constexpr bool IsSystemDataId(const DataId &data_id) { diff --git a/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp b/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp index 92bdb9b22..63e183e78 100644 --- a/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/settings/settings_fwdbg_types.hpp @@ -20,19 +20,4 @@ namespace ams::settings::fwdbg { - constexpr size_t SettingsNameLengthMax = 0x40; - constexpr size_t SettingsItemKeyLengthMax = 0x40; - - struct SettingsName : sf::LargeData { - char value[util::AlignUp(SettingsNameLengthMax + 1, alignof(u64))]; - }; - - static_assert(util::is_pod::value && sizeof(SettingsName) > SettingsNameLengthMax); - - struct SettingsItemKey : sf::LargeData { - char value[util::AlignUp(SettingsItemKeyLengthMax + 1, alignof(u64))]; - }; - - static_assert(util::is_pod::value && sizeof(SettingsItemKey) > SettingsItemKeyLengthMax); - } diff --git a/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp b/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp index 133272d87..247063ab4 100644 --- a/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp @@ -19,6 +19,21 @@ namespace ams::settings { + constexpr size_t SettingsNameLengthMax = 0x40; + constexpr size_t SettingsItemKeyLengthMax = 0x40; + + struct SettingsName : public sf::LargeData { + char value[util::AlignUp(SettingsNameLengthMax + 1, alignof(u64))]; + }; + + static_assert(util::is_pod::value && sizeof(SettingsName) > SettingsNameLengthMax); + + struct SettingsItemKey : public sf::LargeData { + char value[util::AlignUp(SettingsItemKeyLengthMax + 1, alignof(u64))]; + }; + + static_assert(util::is_pod::value && sizeof(SettingsItemKey) > SettingsItemKeyLengthMax); + enum Language { Language_Japanese, Language_AmericanEnglish, diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp index ecc15fc98..345d771bd 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_api.hpp @@ -60,6 +60,12 @@ namespace ams::spl { return static_cast(v); } + inline RetailInteractiveDisplayState GetRetailInteractiveDisplayState() { + u64 v; + R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::RetailInteractiveDisplayState)); + return static_cast(v); + } + inline u64 GetDeviceIdLow() { u64 v; R_ABORT_UNLESS(::ams::spl::GetConfig(std::addressof(v), ::ams::spl::ConfigItem::DeviceId)); diff --git a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp index 2ee7829a9..2fbb1435b 100644 --- a/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/spl/spl_types.hpp @@ -138,6 +138,11 @@ namespace ams::spl { MemoryArrangement_Count, }; + enum RetailInteractiveDisplayState { + RetailInteractiveDisplayState_Disabled = 0, + RetailInteractiveDisplayState_Enabled = 1, + }; + struct BootReasonValue { union { struct { @@ -217,7 +222,7 @@ namespace ams::spl { IsDevelopmentFunctionEnabled = 11, KernelConfiguration = 12, IsChargerHiZModeEnabled = 13, - QuestState = 14, + RetailInteractiveDisplayState = 14, RegulatorType = 15, DeviceUniqueKeyGeneration = 16, Package2Hash = 17, diff --git a/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp b/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp new file mode 100644 index 000000000..742a17485 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_key_value_store.cpp @@ -0,0 +1,1641 @@ +/* + * Copyright (c) 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 "settings_key_value_store.hpp" +#include "settings_spl.hpp" +#include "settings_system_data.hpp" + +namespace ams::settings::impl { + + namespace { + constexpr fs::SystemSaveDataId SystemSaveDataId = 0x8000000000000051; + constexpr u32 SystemSaveDataFlags = fs::SaveDataFlags_KeepAfterRefurbishment | fs::SaveDataFlags_KeepAfterResettingSystemSaveData; + constexpr s64 SystemSaveDataSize = 544_KB; + constexpr s64 SystemSaveDataJournalSize = 544_KB; + + constexpr inline const char FwdbgSystemDataMountName[] = "FwdbgSettingsD"; + constexpr inline const char PfCfgSystemDataMountName[] = "PfcfgSettingsD"; + constexpr inline const char SystemSaveDataMountName[] = "FwdbgSettingsS"; + + constexpr inline const char SettingsNameSeparator = '!'; + + /* Type forward declarations. */ + class MapKey; + struct MapValue; + template + class Allocator; + using Map = std::map, Allocator>>; + + /* Function forward declarations. */ + void FreeMapValueToHeap(const MapValue &value); + void *AllocateFromHeap(size_t size); + void FreeToHeap(void *block, size_t size); + lmem::HeapHandle &GetHeapHandle(); + Result GetKeyValueStoreMap(Map **out); + Result GetKeyValueStoreMap(Map **out, bool force_load); + Result GetKeyValueStoreMapForciblyForDebug(Map **out); + Result LoadKeyValueStoreMap(Map *out); + Result LoadKeyValueStoreMap(Map *out, SplHardwareType hardware_type); + template + Result LoadKeyValueStoreMapCurrent(Map *out, T &data); + template + Result LoadKeyValueStoreMapDefault(Map *out, T &data); + template + Result LoadKeyValueStoreMapEntries(Map *out, T &data, F load); + template + Result LoadKeyValueStoreMapEntry(Map *out, T &data, s64 &offset, F load); + Result LoadKeyValueStoreMapForDebug(Map *out, SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data); + template + Result ReadData(T &data, s64 &offset, void *buffer, size_t size); + template + Result ReadDataToHeap(T &data, s64 &offset, void **buffer, size_t size); + template + Result ReadAllBytes(T *data, u64 *out_count, char * const out_buffer, size_t out_buffer_size); + template + Result SaveKeyValueStoreMapCurrent(T &data, const Map &map); + + struct SystemDataTag { + struct Fwdbg{}; + struct PfCfg{}; + }; + + class MapKey { + public: + static constexpr size_t MaxKeySize = sizeof(SettingsName) + sizeof(SettingsItemKey); + private: + char *m_chars; + size_t m_count; + public: + MapKey(const char * const chars) : m_chars(nullptr), m_count(0) { + AMS_ASSERT(chars != nullptr); + this->Assign(chars, util::Strnlen(chars, MaxKeySize)); + } + + MapKey(const char * const chars, s32 count) : m_chars(nullptr), m_count(0) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + this->Assign(chars, count); + } + + MapKey(MapKey &&other) : m_chars(nullptr), m_count(0) { + std::swap(m_chars, other.m_chars); + std::swap(m_count, other.m_count); + } + + MapKey(const MapKey &other) : m_chars(nullptr), m_count(0) { + this->Assign(other.GetString(), other.GetCount()); + } + + ~MapKey() { + this->Reset(); + } + + MapKey &Append(char c) { + const char chars[2] = { c, 0 }; + return this->Append(chars, 1); + } + + MapKey &Append(const char * const chars, s32 count) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + + /* Allocate the new key. */ + const size_t new_count = m_count + count; + char *new_heap = static_cast(AllocateFromHeap(new_count)); + + /* Copy the existing string to the new heap. */ + std::memcpy(new_heap, this->GetString(), this->GetCount()); + + /* Copy the string to append to the new heap. */ + std::memcpy(new_heap + this->GetCount(), chars, count); + + /* Null-terminate the new string. */ + new_heap[new_count - 1] = '\x00'; + + /* Reset and update the key. */ + this->Reset(); + m_count = new_count; + m_chars = new_heap; + return *this; + } + + MapKey &Assign(const char * const chars, s32 count) { + AMS_ASSERT(chars != nullptr); + AMS_ASSERT(count >= 0); + + /* Reset the key. */ + this->Reset(); + + /* Update the count and allocate the buffer. */ + m_count = count + 1; + m_chars = static_cast(AllocateFromHeap(m_count)); + + /* Copy the characters to the buffer. */ + std::memcpy(m_chars, chars, count); + m_chars[count] = '\x00'; + return *this; + } + + size_t Find(const MapKey &other) const { + return std::search(this->GetString(), this->GetString() + this->GetCount(), other.GetString(), other.GetString() + other.GetCount()) - this->GetString(); + } + + s32 GetCount() const { + return static_cast(m_count) - 1; + } + + const char *GetString() const { + return m_chars; + } + private: + void Reset() { + if (m_chars != nullptr) { + FreeToHeap(m_chars, m_count); + m_chars = nullptr; + m_count = 0; + } + } + }; + + inline bool operator<(const MapKey &lhs, const MapKey &rhs) { + return std::strncmp(lhs.GetString(), rhs.GetString(), std::max(lhs.GetCount(), rhs.GetCount())) < 0; + } + + MapKey MakeMapKey(const SettingsName &name, const SettingsItemKey &item_key) { + /* Create a map key. */ + MapKey key(name.value, util::Strnlen(name.value, util::size(name.value))); + + /* Append the settings name separator followed by the item key. */ + key.Append(SettingsNameSeparator); + key.Append(item_key.value, util::Strnlen(item_key.value, util::size(item_key.value))); + + /* Output the map key. */ + return key; + } + + struct MapValue { + public: + u8 type; + size_t current_value_size; + size_t default_value_size; + void *current_value; + void *default_value; + }; + static_assert(sizeof(MapValue) == 0x28); + + template + class Allocator { + public: + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + public: + Allocator() noexcept = default; + ~Allocator() noexcept = default; + + Allocator(const Allocator &) noexcept = default; + Allocator(Allocator &&) noexcept = default; + + T *allocate(size_t n) noexcept { + return static_cast(AllocateFromHeap(sizeof(T) * n)); + } + + void deallocate(T *p, size_t n) noexcept { + FreeToHeap(p, sizeof(T) * n); + } + private: + Allocator &operator=(const Allocator &) noexcept = default; + Allocator &operator=(Allocator &&) noexcept = default; + }; + + template + constexpr inline bool operator==(const Allocator &, const Allocator &) { + return true; + } + + constexpr inline size_t MapKeyBufferSize = MapKey::MaxKeySize * 2; + constexpr inline size_t MapEntryBufferSize = 0x40 + sizeof(Map::value_type); + + constexpr inline size_t HeapMemorySize = 512_KB; + + constinit os::SdkMutex g_key_value_store_mutex; + + void ClearKeyValueStoreMap(Map &map) { + /* Free all values to the heap. */ + for (const auto &kv_pair : map) { + FreeMapValueToHeap(kv_pair.second); + } + + /* Clear the map. */ + map.clear(); + } + + bool CompareValue(const void *lhs, size_t lhs_size, const void *rhs, size_t rhs_size) { + /* Check if both buffers are the same. */ + if (lhs == rhs) { + return true; + } + + /* If the value sizes don't match, return false. */ + if (lhs_size != rhs_size) { + return false; + } + + /* If the value sizes are 0, they are considered to match. */ + if (lhs_size == 0) { + return true; + } + + /* Compare the two values if they are non-null. */ + return lhs != nullptr && rhs != nullptr && std::memcmp(lhs, rhs, lhs_size) == 0; + } + + void FreeMapValueToHeap(const MapValue &map_value) { + /* Free the current value. */ + if (map_value.current_value != nullptr && map_value.current_value != map_value.default_value) { + FreeToHeap(map_value.current_value, map_value.current_value_size); + } + + /* Free the default value. */ + if (map_value.default_value != nullptr) { + FreeToHeap(map_value.default_value, map_value.default_value_size); + } + } + + void FreeToHeap(void *block, size_t size) { + AMS_UNUSED(size); + lmem::FreeToExpHeap(GetHeapHandle(), block); + } + + void *AllocateFromHeap(size_t size) { + return lmem::AllocateFromExpHeap(GetHeapHandle(), size); + } + + size_t GetHeapAllocatableSize() { + return lmem::GetExpHeapAllocatableSize(GetHeapHandle(), sizeof(void *)); + } + + lmem::HeapHandle &GetHeapHandle() { + static constinit bool s_is_initialized = false; + static constinit lmem::HeapHandle s_heap_handle; + static constinit u8 s_heap_memory[HeapMemorySize]; + + if (!s_is_initialized) { + s_heap_handle = lmem::CreateExpHeap(s_heap_memory, sizeof(s_heap_memory), lmem::CreateOption_None); + s_is_initialized = true; + } + + return s_heap_handle; + } + + Result GetKeyValueStoreMap(Map **out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Get the map. */ + return GetKeyValueStoreMap(out, false); + } + + Result GetKeyValueStoreMap(Map **out, bool force_load) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Declare static instance variables. */ + static constinit util::TypedStorage s_storage = {}; + static constinit bool s_is_initialized = false; + static constinit bool s_is_loaded = false; + + /* Get pointer to the map. */ + Map *map = util::GetPointer(s_storage); + + /* Construct the map, if we haven't already. */ + if (AMS_UNLIKELY(!s_is_initialized)) { + /* Construct the instance. */ + util::ConstructAt(s_storage); + + /* Note that we constructed. */ + s_is_initialized = true; + } + + /* Load the map, if we haven't already. */ + if (AMS_UNLIKELY(!s_is_loaded)) { + /* Attempt to load the map, allowing for failure if acceptable. */ + const auto result = LoadKeyValueStoreMap(map); + + if (!force_load) { + R_TRY(result); + } + + /* Note that the map is loaded. */ + s_is_loaded = true; + } + + /* Set the output pointer. */ + *out = map; + return ResultSuccess(); + } + + Result GetKeyValueStoreMapForciblyForDebug(Map **out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Get the map. */ + return GetKeyValueStoreMap(out, true); + } + + Result GetMapValueOfKeyValueStoreItemForDebug(MapValue *out, const KeyValueStoreItemForDebug &item) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Create the map value. */ + MapValue map_value = { + .type = item.type, + .current_value_size = item.current_value_size, + .default_value_size = item.default_value_size, + .current_value = nullptr, + .default_value = nullptr, + }; + + /* Ensure we free any buffers we allocate, if we fail. */ + ON_SCOPE_EXIT { FreeMapValueToHeap(map_value); }; + + /* If the default value size is > 0, copy it to the map value. */ + if (map_value.default_value_size > 0) { + /* Allocate the default value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= map_value.default_value_size, ResultSettingsItemValueAllocationFailed()); + map_value.default_value = AllocateFromHeap(map_value.default_value_size); + AMS_ASSERT(map_value.default_value != nullptr); + + /* Copy the default value from the item. */ + std::memcpy(map_value.default_value, item.default_value, map_value.default_value_size); + } + + /* If the current value and the default values are identical, set the map value to the default value. */ + if (CompareValue(item.current_value, item.current_value_size, item.default_value, item.default_value_size)) { + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + } else if (map_value.current_value_size > 0) { + /* Allocate the current value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= map_value.current_value_size, ResultSettingsItemValueAllocationFailed()); + map_value.current_value = AllocateFromHeap(map_value.current_value_size); + AMS_ASSERT(map_value.current_value != nullptr); + + /* Copy the current value from the item. */ + std::memcpy(map_value.current_value, item.current_value, map_value.current_value_size); + } + + /* Set the output map value. */ + *out = map_value; + + /* Ensure we don't free the value buffers we returned. */ + map_value.current_value = nullptr; + map_value.default_value = nullptr; + return ResultSuccess(); + } + + template + const char *GetSystemDataMountName(); + + template<> + const char *GetSystemDataMountName() { + return FwdbgSystemDataMountName; + } + + template<> + const char *GetSystemDataMountName() { + return PfCfgSystemDataMountName; + } + + template + Result GetSystemData(SystemData **out_data, ncm::SystemDataId id) { + /* Check pre-conditions. */ + AMS_ASSERT(out_data != nullptr); + + /* Declare static instance variables. */ + static constinit util::TypedStorage s_storage = {}; + static constinit bool s_initialized = false; + static constinit bool s_mounted = false; + + /* Get pointer to the system data. */ + SystemData *data = util::GetPointer(s_storage); + + /* Construct the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_initialized)) { + /* Construct the instance. */ + util::ConstructAt(s_storage); + + /* Setup system data. */ + data->SetSystemDataId(id); + data->SetMountName(GetSystemDataMountName()); + + /* Note that we constructed. */ + s_initialized = true; + } + + /* Mount the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_mounted)) { + /* Mount the system data. */ + R_TRY(data->Mount()); + + /* Note that we mounted. */ + s_mounted = true; + } + + /* Set the output pointer. */ + *out_data = data; + return ResultSuccess(); + } + + Result GetSystemSaveData(SystemSaveData **out_data, bool create_save) { + /* Check pre-conditions. */ + AMS_ASSERT(out_data != nullptr); + + /* Declare static instance variables. */ + static constinit util::TypedStorage s_storage = {}; + static constinit bool s_initialized = false; + static constinit bool s_mounted = false; + + /* Get pointer to the system data. */ + SystemSaveData *data = util::GetPointer(s_storage); + + /* Construct the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_initialized)) { + /* Construct the instance. */ + util::ConstructAt(s_storage); + + /* Setup system data. */ + data->SetSystemSaveDataId(SystemSaveDataId); + data->SetTotalSize(SystemSaveDataSize); + data->SetJournalSize(SystemSaveDataJournalSize); + data->SetFlags(SystemSaveDataFlags); + data->SetMountName(SystemSaveDataMountName); + + /* Note that we constructed. */ + s_initialized = true; + } + + /* Mount the system data, if we haven't already. */ + if (AMS_UNLIKELY(!s_mounted)) { + /* Mount the system data. */ + R_TRY(data->Mount(create_save)); + + /* Note that we mounted. */ + s_mounted = true; + } + + /* Set the output pointer. */ + *out_data = data; + return ResultSuccess(); + } + + Result LoadKeyValueStoreMap(Map *out) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Clear the key value store map. */ + ClearKeyValueStoreMap(*out); + + /* Get the firmware debug system data. */ + SystemData *system_data = nullptr; + R_TRY(GetSystemData(std::addressof(system_data), ncm::SystemDataId::FirmwareDebugSettings)); + AMS_ASSERT(system_data != nullptr); + + /* Load the default keys/values for the firmware debug system data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *system_data)); + + /* Load the keys/values based on the hardware type. */ + R_TRY(LoadKeyValueStoreMap(out, GetSplHardwareType())); + + if (IsSplDevelopment()) { + /* Get the system save data. */ + SystemSaveData *system_save_data = nullptr; + R_SUCCEED_IF(R_FAILED(GetSystemSaveData(std::addressof(system_save_data), false))); + AMS_ASSERT(system_save_data != nullptr); + + /* Attempt to load the current keys/values from the system save data. */ + if (const auto result = LoadKeyValueStoreMapCurrent(out, *system_save_data); R_FAILED(result)) { + /* Reset all values to their defaults. */ + for (auto &kv_pair : *out) { + MapValue &map_value = kv_pair.second; + + /* Free the current value. */ + if (map_value.current_value != nullptr && map_value.current_value != map_value.default_value) { + FreeToHeap(map_value.current_value, map_value.current_value_size); + } + + /* Reset the current value to the default value. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + } + + /* Log failure to load system save data. TODO: Make this a warning. */ + AMS_LOG("[firmware debug settings] Warning: Failed to load the system save data. (%08x, %d%03d-%04d)\n", result.GetInnerValue(), 2, result.GetModule(), result.GetDescription()); + } + } + + return ResultSuccess(); + } + + Result LoadKeyValueStoreMap(Map *out, SplHardwareType hardware_type) { + SystemData *data = nullptr; + + /* Get the platform configuration system data for the hardware type. */ + switch (hardware_type) { + case SplHardwareType_None: + return ResultSuccess(); + case SplHardwareType_Icosa: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigIcosa)); + break; + case SplHardwareType_IcosaMariko: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigIcosaMariko)); + break; + case SplHardwareType_Copper: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigCopper)); + break; + case SplHardwareType_Hoag: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigHoag)); + break; + case SplHardwareType_Calcio: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigCalcio)); + break; + case SplHardwareType_Aula: + R_TRY(GetSystemData(std::addressof(data), ncm::SystemDataId::PlatformConfigAula)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure data is not null. */ + AMS_ASSERT(data != nullptr); + + /* Load the key value store map. */ + return LoadKeyValueStoreMapDefault(out, *data); + } + + template + Result LoadKeyValueStoreMapCurrent(Map *out, T &data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Open the data for reading. */ + R_TRY(data.OpenToRead()); + ON_SCOPE_EXIT { data.Close(); }; + + /* Load the map entries. */ + R_TRY(LoadKeyValueStoreMapEntries(out, data, [](Map &map, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) -> Result { + AMS_UNUSED(type); + /* Find the key in the map. */ + if (auto it = map.find(key); it != map.end()) { + MapValue &map_value = it->second; + size_t current_value_size = value_size; + void *current_value_buffer = nullptr; + + if (current_value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= current_value_size, ResultSettingsItemValueAllocationFailed()); + + /* Allocate the value buffer. */ + current_value_buffer = AllocateFromHeap(current_value_size); + AMS_ASSERT(current_value_buffer != nullptr); + + /* Copy the value to the current value buffer. */ + std::memcpy(current_value_buffer, value_buffer, current_value_size); + } + + /* Replace the current value buffer with a new one. */ + std::swap(map_value.current_value_size, current_value_size); + std::swap(map_value.current_value, current_value_buffer); + + /* Free the old buffer if it is no longer in use. */ + if (current_value_buffer != nullptr && current_value_buffer != map_value.default_value) { + FreeToHeap(current_value_buffer, current_value_size); + } + } + + return ResultSuccess(); + })); + + return ResultSuccess(); + } + + template + Result LoadKeyValueStoreMapDefault(Map *out, T &data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + + /* Open the data for reading. */ + R_TRY(data.OpenToRead()); + ON_SCOPE_EXIT { data.Close(); }; + + /* Load the map entries. */ + R_TRY(LoadKeyValueStoreMapEntries(out, data, [](Map &map, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) -> Result { + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Copy the map key. */ + MapKey default_key = key; + void *default_value_buffer = nullptr; + + ON_SCOPE_EXIT { + /* Free the value buffer if allocated. */ + if (default_value_buffer != nullptr) { + FreeToHeap(default_value_buffer, value_size); + } + }; + + if (value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, ResultSettingsItemValueAllocationFailed()); + + /* Allocate the value buffer. */ + default_value_buffer = AllocateFromHeap(value_size); + AMS_ASSERT(default_value_buffer != nullptr); + + /* Copy the value to the new value buffer. */ + std::memcpy(default_value_buffer, value_buffer, value_size); + } + + /* Create the map value. */ + MapValue default_value { + .type = type, + .current_value_size = value_size, + .default_value_size = value_size, + .current_value = default_value_buffer, + .default_value = default_value_buffer, + }; + + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= MapEntryBufferSize, ResultSettingsItemValueAllocationFailed()); + + /* Insert the value into the map. */ + map[std::move(default_key)] = default_value; + return ResultSuccess(); + })); + + return ResultSuccess(); + } + + template + Result LoadKeyValueStoreMapEntries(Map *out, T &data, F load) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(load != nullptr); + + /* Read the number of entries. */ + s64 offset = 0; + u32 total_size = 0; + R_TRY(ReadData(data, offset, std::addressof(total_size), sizeof(total_size))); + + /* Iterate through all entries. NOTE: The offset is updated within LoadKeyValueStoreMapEntry. */ + while (offset < total_size) { + R_TRY(LoadKeyValueStoreMapEntry(out, data, offset, load)); + } + + return ResultSuccess(); + } + + template + Result LoadKeyValueStoreMapEntry(Map *out, T &data, s64 &offset, F load) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(load != nullptr); + + /* Read the size of the key. */ + u32 key_size = 0; + R_TRY(ReadData(data, offset, std::addressof(key_size), sizeof(key_size))); + AMS_ASSERT(key_size > 1); + + /* Ensure there is sufficient memory for this key. */ + R_UNLESS(GetHeapAllocatableSize() >= key_size, ResultSettingsItemKeyAllocationFailed()); + + /* Read the key. */ + void *key_buffer = nullptr; + R_TRY(ReadDataToHeap(data, offset, std::addressof(key_buffer), key_size)); + AMS_ASSERT(key_buffer != nullptr); + ON_SCOPE_EXIT { FreeToHeap(key_buffer, key_size); }; + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + const MapKey key(static_cast(key_buffer), key_size - 1); + + /* Read the type from the data. */ + u8 type = 0; + R_TRY(ReadData(data, offset, std::addressof(type), sizeof(type))); + + /* Read the size of the value. */ + u32 value_size = 0; + R_TRY(ReadData(data, offset, std::addressof(value_size), sizeof(value_size))); + + void *value_buffer = nullptr; + ON_SCOPE_EXIT { + if (value_buffer != nullptr) { + FreeToHeap(value_buffer, value_size); + } + }; + + if (value_size > 0) { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, ResultSettingsItemValueAllocationFailed()); + + /* Read the value to the buffer. */ + R_TRY(ReadDataToHeap(data, offset, std::addressof(value_buffer), value_size)); + } + + /* Load the value. */ + return load(*out, key, type, value_buffer, value_size); + } + + Result LoadKeyValueStoreMapForDebug(Map *out, SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_save_data, SystemSaveData *pfcfg_system_save_data) { + /* Check pre-conditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(system_save_data != nullptr); + AMS_ASSERT(fwdbg_system_save_data != nullptr); + AMS_ASSERT(pfcfg_system_save_data != nullptr); + + /* Clear the map. */ + ClearKeyValueStoreMap(*out); + + /* Load the default keys/values for the firmware debug system save data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *fwdbg_system_save_data)); + + /* Load the default keys/values for the platform configuration system save data. */ + R_TRY(LoadKeyValueStoreMapDefault(out, *pfcfg_system_save_data)); + + /* Load the current values for the system save data. */ + R_TRY(LoadKeyValueStoreMapCurrent(out, *system_save_data)); + return ResultSuccess(); + } + + template + Result ReadData(T &data, s64 &offset, void *buffer, size_t size) { + AMS_ASSERT(buffer != nullptr); + + /* Read the data. */ + R_TRY(data.Read(offset, buffer, size)); + + /* Increment the offset. */ + offset += static_cast(size); + return ResultSuccess(); + } + + template + Result ReadDataToHeap(T &data, s64 &offset, void **buffer, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(buffer != nullptr); + AMS_ASSERT(size > 0); + + /* Allocate a buffer from the heap. */ + *buffer = AllocateFromHeap(size); + AMS_ASSERT(*buffer != nullptr); + + /* Ensure we free the buffer if we fail. */ + auto alloc_guard = SCOPE_GUARD { FreeToHeap(*buffer, size); *buffer = nullptr; }; + + /* Read data to the buffer. */ + R_TRY(ReadData(data, offset, *buffer, size)); + + /* We succeeded. */ + alloc_guard.Cancel(); + return ResultSuccess(); + } + + template + Result ReadAllBytes(T *data, u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(data != nullptr); + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Open data for reading. */ + R_TRY(data->OpenToRead()); + ON_SCOPE_EXIT { data->Close(); }; + + /* Read the data size. */ + u32 size = 0; + R_TRY(data->Read(0, std::addressof(size), sizeof(size))); + + /* Ensure the data size does not exceed the buffer size. */ + size = std::min(size, static_cast(out_buffer_size)); + + /* Read the data. */ + R_TRY(data->Read(0, out_buffer, size)); + + /* Set the count. */ + *out_count = size; + return ResultSuccess(); + } + + Result ReadSystemDataFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + /* Attempt to get the firmware debug system data. */ + SystemData *system_data = nullptr; + if (R_SUCCEEDED(GetSystemData(std::addressof(system_data), ncm::SystemDataId::FirmwareDebugSettings))) { + AMS_ASSERT(system_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + return ResultSuccess(); + } + + Result ReadSystemDataPlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + ncm::SystemDataId system_data_id; + + switch (GetSplHardwareType()) { + case SplHardwareType_None: + *out_count = 0; + return ResultSuccess(); + case SplHardwareType_Icosa: + system_data_id = ncm::SystemDataId::PlatformConfigIcosa; + break; + case SplHardwareType_IcosaMariko: + system_data_id = ncm::SystemDataId::PlatformConfigIcosaMariko; + break; + case SplHardwareType_Copper: + system_data_id = ncm::SystemDataId::PlatformConfigCopper; + break; + case SplHardwareType_Hoag: + system_data_id = ncm::SystemDataId::PlatformConfigHoag; + break; + case SplHardwareType_Calcio: + system_data_id = ncm::SystemDataId::PlatformConfigCalcio; + break; + case SplHardwareType_Aula: + system_data_id = ncm::SystemDataId::PlatformConfigAula; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Attempt to get the platform configuration system data. */ + SystemData *system_data = nullptr; + if (R_SUCCEEDED(GetSystemData(std::addressof(system_data), system_data_id))) { + AMS_ASSERT(system_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + return ResultSuccess(); + } + + Result ReadSystemSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer); + + /* Attempt to get the system save data. */ + SystemSaveData *system_save_data = nullptr; + if (R_SUCCEEDED(GetSystemSaveData(std::addressof(system_save_data), false))) { + AMS_ASSERT(system_save_data != nullptr); + + /* Read the data. */ + R_TRY(ReadAllBytes(system_save_data, out_count, out_buffer, out_buffer_size)); + } else { + /* Set the output count to 0. */ + *out_count = 0; + } + + return ResultSuccess(); + } + + Result SaveKeyValueStoreMap(const Map &map) { + /* Get the system save data. */ + SystemSaveData *system_save_data = nullptr; + R_TRY(GetSystemSaveData(std::addressof(system_save_data), false)); + AMS_ASSERT(system_save_data != nullptr); + + /* Save the current values of the key value store map. */ + return SaveKeyValueStoreMapCurrent(*system_save_data, map); + } + + template + Result SaveKeyValueStoreMap(T &data, const Map &map, F test) { + /* Check preconditions. */ + AMS_ASSERT(test != nullptr); + + /* Create the save data if necessary. */ + R_TRY_CATCH(data.Create(HeapMemorySize)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* It's okay if the save data already exists. */ } + } R_END_TRY_CATCH; + + { + /* Open the save data for writing. */ + R_TRY(data.OpenToWrite()); + ON_SCOPE_EXIT { + /* Flush and close the save data. NOTE: Nintendo only does this if SetFileSize succeeds. */ + R_ABORT_UNLESS(data.Flush()); + data.Close(); + }; + + /* Set the file size of the save data. */ + R_TRY(data.SetFileSize(HeapMemorySize)); + + /* Write the data size, which includes itself. */ + u32 data_size = sizeof(data_size); + R_TRY(data.Write(0, std::addressof(data_size), sizeof(data_size))); + + /* Set the current offset to after the data size. */ + s64 current_offset = sizeof(data_size); + + /* Iterate through map entries. */ + for (const auto &kv_pair : map) { + /* Declare variables for test. */ + u8 type = 0; + const void *value_buffer = nullptr; + u32 value_size = 0; + + /* Test if the map value varies from the default. */ + if (test(std::addressof(type), std::addressof(value_buffer), std::addressof(value_size), kv_pair.second)) { + R_TRY(SaveKeyValueStoreMapEntry(data, current_offset, kv_pair.first, type, value_buffer, value_size)); + } + } + + /* Write the updated save data size. */ + data_size = static_cast(current_offset); + R_TRY(data.Write(0, std::addressof(data_size), sizeof(data_size))); + } + + /* Commit the save data. */ + return data.Commit(false); + } + + template + Result SaveKeyValueStoreMapCurrent(T &data, const Map &map) { + /* Save the current values in the map to the data. */ + return SaveKeyValueStoreMap(data, map, [](u8 *out_type, const void **out_value_buffer, u32 *out_value_size, const MapValue &map_value) -> bool { + /* Check preconditions. */ + AMS_ASSERT(out_type != nullptr); + AMS_ASSERT(out_value_buffer != nullptr); + AMS_ASSERT(out_value_size != nullptr); + + /* Check if the current value matches the default value. */ + if (CompareValue(map_value.current_value, map_value.current_value_size, map_value.default_value, map_value.default_value_size)) { + return false; + } + + /* Output the map value type, current value and current value size. */ + *out_type = map_value.type; + *out_value_buffer = map_value.current_value; + *out_value_size = map_value.current_value_size; + return true; + }); + } + + template + Result SaveKeyValueStoreMapDefault(T &data, const Map &map) { + /* Save the default values in the map to the data. */ + return SaveKeyValueStoreMap(data, map, [](u8 *out_type, const void **out_value_buffer, u32 *out_value_size, const MapValue &map_value) -> bool { + /* Check preconditions. */ + AMS_ASSERT(out_type != nullptr); + AMS_ASSERT(out_value_buffer != nullptr); + AMS_ASSERT(out_value_size != nullptr); + + /* Output the map value type, default value and default value size. */ + *out_type = map_value.type; + *out_value_buffer = map_value.default_value; + *out_value_size = map_value.default_value_size; + return true; + }); + } + + Result SaveKeyValueStoreMapDefaultForDebug(SystemSaveData &data, const Map &map) { + return SaveKeyValueStoreMapDefault(data, map); + } + + template + Result SaveKeyValueStoreMapEntry(T &data, s64 &offset, const MapKey &key, u8 type, const void *value_buffer, u32 value_size) { + /* Write the key size and increment the offset. */ + const u32 key_size = key.GetCount() + 1; + R_TRY(data.Write(offset, std::addressof(key_size), sizeof(key_size))); + offset += static_cast(sizeof(key_size)); + + /* Write the key string and increment the offset. */ + R_TRY(data.Write(offset, key.GetString(), key_size)); + offset += static_cast(key_size); + + /* Write the type and increment the offset. */ + R_TRY(data.Write(offset, std::addressof(type), sizeof(type))); + offset += static_cast(sizeof(type)); + + /* Write the value size and increment the offset. */ + R_TRY(data.Write(offset, std::addressof(value_size), sizeof(value_size))); + offset += static_cast(sizeof(value_size)); + + /* If the value is larger than 0, write it to the data. */ + if (value_size > 0) { + /* Check preconditions. */ + AMS_ASSERT(value_buffer != nullptr); + + R_TRY(data.Write(offset, value_buffer, value_size)); + offset += static_cast(value_size); + } + + return ResultSuccess(); + } + + } + + Result KeyValueStore::CreateKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Create a map key from the key value store's name. */ + MapKey map_key_header(m_name.value); + + /* Append the settings name separator. */ + map_key_header.Append(SettingsNameSeparator); + + /* Define the item map key. */ + const MapKey *item_map_key = nullptr; + + /* Find an item map key with the name as a prefix. */ + for (const auto &kv_pair : *map) { + const MapKey &map_key = kv_pair.first; + + /* Check if the name map key is smaller than the current map key, and the current map key contains the name map key. */ + if (map_key_header < map_key && map_key.Find(map_key_header)) { + item_map_key = std::addressof(map_key); + break; + } + } + + /* Ensure we have located an item map key. */ + R_UNLESS(item_map_key != nullptr, ResultSettingsItemNotFound()); + + /* Ensure there is sufficient memory for the item map key. */ + const size_t item_map_key_size = item_map_key->GetCount() + 1; + R_UNLESS(GetHeapAllocatableSize() >= item_map_key_size, ResultSettingsItemKeyIteratorAllocationFailed()); + + /* Allocate the key buffer. */ + char *buffer = static_cast(AllocateFromHeap(item_map_key_size)); + AMS_ASSERT(buffer != nullptr); + + /* Copy the item map key's string to the buffer. */ + std::memcpy(buffer, item_map_key->GetString(), item_map_key_size); + + /* Output the iterator. */ + *out = { + .header_size = static_cast(map_key_header.GetCount()), + .entire_size = item_map_key_size, + .map_key = buffer, + }; + return ResultSuccess(); + } + + Result KeyValueStore::GetValue(u64 *out_count, char *out_buffer, size_t out_buffer_size, const SettingsItemKey &item_key) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::const_iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + const MapValue &map_value = it->second; + + /* Calculate the current value size. */ + const size_t current_value_size = std::min(map_value.current_value_size, out_buffer_size); + + /* If the current value size is > 0, copy to the output buffer. */ + if (current_value_size > 0) { + AMS_ASSERT(map_value.current_value != nullptr); + std::memcpy(out_buffer, map_value.current_value, current_value_size); + } + + /* Set the output count. */ + *out_count = current_value_size; + return ResultSuccess(); + } + + Result KeyValueStore::GetValueSize(u64 *out_value_size, const SettingsItemKey &item_key) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::const_iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), ResultSettingsItemNotFound()); + + /* Output the value size. */ + *out_value_size = it->second.current_value_size; + return ResultSuccess(); + } + + Result KeyValueStore::ResetValue(const SettingsItemKey &item_key) { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + MapValue &map_value = it->second; + + /* Succeed if the map value has already been reset. */ + R_SUCCEED_IF(map_value.current_value == map_value.default_value); + + /* Store the previous value and its size. */ + size_t prev_value_size = map_value.current_value_size; + void *prev_value = map_value.current_value; + + /* Reset the current value to default. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + + /* Attempt to save the key value store map. */ + if (const auto result = SaveKeyValueStoreMap(*map); R_FAILED(result)) { + /* Revert to the previous value. */ + map_value.current_value_size = prev_value_size; + map_value.current_value = prev_value; + + /* Attempt to save the map again. Nintendo does not check the result of this. */ + SaveKeyValueStoreMap(*map); + return result; + } + + /* If present, free the previous value. */ + if (prev_value != nullptr && prev_value != map_value.default_value) { + FreeToHeap(prev_value, prev_value_size); + } + + return ResultSuccess(); + } + + Result KeyValueStore::SetValue(const SettingsItemKey &item_key, const void *buffer, size_t buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Find the key in the map. */ + const Map::iterator it = map->find(MakeMapKey(m_name, item_key)); + R_UNLESS(it != map->end(), ResultSettingsItemNotFound()); + + /* Get the map value from the iterator. */ + MapValue &map_value = it->second; + + /* Succeed if the map value is already set to the new value. */ + R_SUCCEED_IF(CompareValue(map_value.current_value, map_value.current_value_size, buffer, buffer_size)); + + /* Define the value buffer and size variables. */ + size_t value_size = buffer_size; + void *value_buffer = nullptr; + + /* Set the value buffer to the default value if the new value is the same. */ + if (CompareValue(map_value.default_value, map_value.default_value_size, buffer, buffer_size)) { + value_buffer = map_value.default_value; + } else if (buffer_size > 0) { + /* Allocate the new value if there is sufficient memory available. */ + R_UNLESS(GetHeapAllocatableSize() >= value_size, ResultSettingsItemValueAllocationFailed()); + value_buffer = AllocateFromHeap(value_size); + AMS_ASSERT(value_buffer != nullptr); + + /* Copy the value to the value buffer. */ + std::memcpy(value_buffer, buffer, value_size); + } + + /* Swap the current value with the new value. */ + std::swap(map_value.current_value_size, value_size); + std::swap(map_value.current_value, value_buffer); + + /* Attempt to save the key value store map. */ + const auto result = SaveKeyValueStoreMap(*map); + + /* If we failed, revert to the previous value. */ + if (R_FAILED(result)) { + std::swap(map_value.current_value_size, value_size); + std::swap(map_value.current_value, value_buffer); + } + + /* Free the now unused value. */ + if (value_buffer != nullptr && value_buffer != map_value.default_value) { + FreeToHeap(value_buffer, value_size); + } + + /* If we failed, attempt to save the map again. Note that Nintendo does not check the result of this. */ + if (R_FAILED(result)) { + SaveKeyValueStoreMap(*map); + return result; + } + + return ResultSuccess(); + } + + Result AddKeyValueStoreItemForDebug(const KeyValueStoreItemForDebug * const items, size_t items_count) { + /* Check preconditions. */ + AMS_ASSERT(items != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMapForciblyForDebug(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Iterate through each item. */ + for (size_t i = 0; i < items_count; i++) { + const KeyValueStoreItemForDebug &item = items[i]; + + /* Create a map value for our scope. */ + MapValue map_value = {}; + ON_SCOPE_EXIT { FreeMapValueToHeap(map_value); }; + + /* Get the map value for the item. */ + R_TRY(GetMapValueOfKeyValueStoreItemForDebug(std::addressof(map_value), item)); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Create the map key. */ + MapKey map_key(item.key); + + /* Replace the existing value in the map if it already exists. */ + if (const Map::iterator it = map->find(map_key); it != map->end()) { + /* Free the existing map value. */ + FreeMapValueToHeap(it->second); + + /* Replace the existing map value. */ + it->second = map_value; + } else { + /* Ensure there is sufficient memory for the value. */ + R_UNLESS(GetHeapAllocatableSize() >= MapEntryBufferSize, ResultSettingsItemValueAllocationFailed()); + + /* Assign the map value to the map key in the map. */ + (*map)[std::move(map_key)] = map_value; + } + + /* Ensure we don't free the value buffers we added. */ + map_value.current_value = nullptr; + map_value.default_value = nullptr; + } + + return ResultSuccess(); + } + + Result AdvanceKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out->header_size > 0); + AMS_ASSERT(out->header_size < out->entire_size); + AMS_ASSERT(out->map_key != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Ensure there is sufficient memory for two keys. */ + R_UNLESS(GetHeapAllocatableSize() >= MapKeyBufferSize, ResultSettingsItemKeyAllocationFailed()); + + /* Locate the iterator's current key. */ + Map::const_iterator it = map->find(MapKey(out->map_key, static_cast(out->entire_size) - 1)); + R_UNLESS(it != map->end(), ResultNotFoundSettingsItemKeyIterator()); + + /* Increment the iterator, ensuring we aren't at the end of the map. */ + R_UNLESS((++it) != map->end(), ResultStopIteration()); + + /* Get the map key. */ + const MapKey &map_key = it->first; + + /* Ensure the advanced iterator retains the required name. */ + R_UNLESS(std::strncmp(map_key.GetString(), out->map_key, out->header_size) == 0, ResultStopIteration()); + + /* Ensure there is sufficient memory for the map key. */ + const size_t map_key_size = map_key.GetCount() + 1; + R_UNLESS(GetHeapAllocatableSize() >= map_key_size, ResultSettingsItemKeyIteratorAllocationFailed()); + + /* Free the iterator's old map key. */ + FreeToHeap(out->map_key, out->entire_size); + + /* Allocate the new map key. */ + char *buffer = static_cast(AllocateFromHeap(map_key_size)); + AMS_ASSERT(buffer != nullptr); + + /* Copy the new map key to the buffer. */ + std::memcpy(buffer, map_key.GetString(), map_key_size); + + /* Set the output map key. */ + out->entire_size = map_key_size; + out->map_key = buffer; + + return ResultSuccess(); + } + + Result DestroyKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + AMS_ASSERT(out->header_size > 0); + AMS_ASSERT(out->header_size < out->entire_size); + AMS_ASSERT(out->map_key != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Free the key to the heap. */ + FreeToHeap(out->map_key, out->entire_size); + + /* Reset the name and key. */ + out->header_size = 0; + out->entire_size = 0; + out->map_key = nullptr; + return ResultSuccess(); + } + + Result GetKeyValueStoreItemCountForDebug(u64 *out_count) { + /* Check preconditions. */ + AMS_ASSERT(out != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Output the item count. */ + *out_count = map->size(); + return ResultSuccess(); + } + + Result GetKeyValueStoreItemForDebug(u64 *out_count, KeyValueStoreItemForDebug * const out_items, size_t out_items_count) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_items != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Define the count variable. */ + size_t count = 0; + + /* Iterate through each value in the map and output kvs items. */ + for (const auto &kv_pair : *map) { + /* Get the current map value. */ + const MapValue &map_value = kv_pair.second; + + /* Break if the count exceeds the output items count. */ + if (count >= out_items_count) { + break; + } + + /* Get the current item. */ + KeyValueStoreItemForDebug &item = out_items[count++]; + + /* Copy the map key and value to the item. */ + item.key = kv_pair.first.GetString(); + item.type = map_value.type; + item.current_value_size = map_value.current_value_size; + item.default_value_size = map_value.default_value_size; + item.current_value = map_value.current_value; + item.default_value = map_value.default_value; + } + + /* Set the output count. */ + *out_count = count; + return ResultSuccess(); + } + + Result GetKeyValueStoreKeyIteratorKey(u64 *out_count, char *out_buffer, size_t out_buffer_size, const KeyValueStoreKeyIterator &iterator) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + AMS_ASSERT(iterator.header_size > 0); + AMS_ASSERT(iterator.header_size < iterator.entire_size); + AMS_ASSERT(iterator.map_key != nullptr); + + /* Copy the key from the iterator to the output buffer. */ + const size_t key_size = std::min(out_buffer_size, std::min(iterator.entire_size - iterator.header_size, SettingsItemKeyLengthMax + 1)); + std::strncpy(out_buffer, iterator.map_key + iterator.header_size, key_size); + + /* Set the end of the key to null. */ + if (key_size > 0) { + out_buffer[key_size - 1] = '\x00'; + } + + /* Output the key size. */ + *out_count = key_size; + return ResultSuccess(); + } + + Result GetKeyValueStoreKeyIteratorKeySize(u64 *out_count, const KeyValueStoreKeyIterator &iterator) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(iterator.header_size > 0); + AMS_ASSERT(iterator.header_size < iterator.entire_size); + AMS_ASSERT(iterator.map_key != nullptr); + + /* Output the key size. */ + *out_count = std::min(iterator.entire_size - iterator.header_size, SettingsItemKeyLengthMax + 1); + return ResultSuccess(); + } + + Result ReadKeyValueStoreFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the firmware debug system data. */ + return ReadSystemDataFirmwareDebug(out_count, out_buffer, out_buffer_size); + } + + Result ReadKeyValueStorePlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the platform configuration system data. */ + return ReadSystemDataPlatformConfiguration(out_count, out_buffer, out_buffer_size); + } + + Result ReadKeyValueStoreSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size) { + /* Check preconditions. */ + AMS_ASSERT(out_count != nullptr); + AMS_ASSERT(out_buffer != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Read the system save data. */ + return ReadSystemSaveData(out_count, out_buffer, out_buffer_size); + } + + Result ReloadKeyValueStoreForDebug(SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data) { + /* Check preconditions. */ + AMS_ASSERT(system_save_data != nullptr); + AMS_ASSERT(fwdbg_system_data != nullptr); + AMS_ASSERT(pfcfg_system_data != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMapForciblyForDebug(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Load the key value store map. */ + return LoadKeyValueStoreMapForDebug(map, system_save_data, fwdbg_system_data, pfcfg_system_data); + } + + Result ReloadKeyValueStoreForDebug() { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Load the key value store map. */ + return LoadKeyValueStoreMap(map); + } + + Result ResetKeyValueStoreSaveData() { + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Reset all values in the map. */ + for (auto &kv_pair : *map) { + /* Get the map value. */ + MapValue &map_value = kv_pair.second; + + /* If the current value isn't the default value, reset it. */ + if (map_value.current_value != map_value.default_value) { + /* Store the previous value and size. */ + size_t prev_value_size = map_value.current_value_size; + void *prev_value = map_value.current_value; + + /* Reset the current value to the default value. */ + map_value.current_value_size = map_value.default_value_size; + map_value.current_value = map_value.default_value; + + /* Free the current value if present. */ + if (prev_value != nullptr) { + FreeToHeap(prev_value, prev_value_size); + } + } + } + + /* Save the key value store map. */ + return SaveKeyValueStoreMap(*map); + } + + Result SaveKeyValueStoreAllForDebug(SystemSaveData *data) { + /* Check preconditions. */ + AMS_ASSERT(data != nullptr); + + /* Acquire exclusive access to global state. */ + std::scoped_lock lk(g_key_value_store_mutex); + + /* Get the key value store map. */ + Map *map = nullptr; + R_TRY(GetKeyValueStoreMap(std::addressof(map))); + AMS_ASSERT(map != nullptr); + + /* Save the key value store map default values. */ + R_TRY(SaveKeyValueStoreMapDefaultForDebug(*data, *map)); + + /* Save the key value store map. */ + return SaveKeyValueStoreMap(*map); + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp b/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp new file mode 100644 index 000000000..92c4507ad --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_key_value_store.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 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 "settings_system_save_data.hpp" + +namespace ams::settings::impl { + + struct KeyValueStoreItemForDebug { + const char *key; + u8 type; + size_t current_value_size; + size_t default_value_size; + void *current_value; + void *default_value; + }; + static_assert(sizeof(KeyValueStoreItemForDebug) == 0x30); + + struct KeyValueStoreKeyIterator { + size_t header_size; + size_t entire_size; + char *map_key; + }; + static_assert(sizeof(KeyValueStoreKeyIterator) == 0x18); + + class KeyValueStore { + private: + const SettingsName &m_name; + public: + explicit KeyValueStore(const SettingsName &name) : m_name(name) { /* ... */ } + + Result CreateKeyIterator(KeyValueStoreKeyIterator *out); + Result GetValue(u64 *out_count, char *out_buffer, size_t out_buffer_size, const SettingsItemKey &item_key); + Result GetValueSize(u64 *out_value_size, const SettingsItemKey &item_key); + Result ResetValue(const SettingsItemKey &item_key); + Result SetValue(const SettingsItemKey &item_key, const void *buffer, size_t buffer_size); + }; + + Result AddKeyValueStoreItemForDebug(const KeyValueStoreItemForDebug * const items, size_t items_count); + Result AdvanceKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out); + Result DestroyKeyValueStoreKeyIterator(KeyValueStoreKeyIterator *out); + Result GetKeyValueStoreItemCountForDebug(u64 *out_count); + Result GetKeyValueStoreItemForDebug(u64 *out_count, KeyValueStoreItemForDebug * const out_items, size_t out_items_count); + Result GetKeyValueStoreKeyIteratorKey(u64 *out_count, char *out_buffer, size_t out_buffer_size, const KeyValueStoreKeyIterator &iterator); + Result GetKeyValueStoreKeyIteratorKeySize(u64 *out_count, const KeyValueStoreKeyIterator &iterator); + Result ReadKeyValueStoreFirmwareDebug(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReadKeyValueStorePlatformConfiguration(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReadKeyValueStoreSaveData(u64 *out_count, char * const out_buffer, size_t out_buffer_size); + Result ReloadKeyValueStoreForDebug(SystemSaveData *system_save_data, SystemSaveData *fwdbg_system_data, SystemSaveData *pfcfg_system_data); + Result ReloadKeyValueStoreForDebug(); + Result ResetKeyValueStoreSaveData(); + Result SaveKeyValueStoreAllForDebug(SystemSaveData *data); + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_spl.cpp b/libraries/libstratosphere/source/settings/impl/settings_spl.cpp new file mode 100644 index 000000000..ef711dfe8 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_spl.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 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 "settings_spl.hpp" + +namespace ams::settings::impl { + + namespace { + + struct SplConfig { + bool is_development; + SplHardwareType hardware_type; + bool is_quest; + u64 device_id_low; + }; + + constexpr SplHardwareType ConvertToSplHardwareType(spl::HardwareType type) { + switch (type) { + case spl::HardwareType::Icosa: + return SplHardwareType_Icosa; + case spl::HardwareType::Copper: + return SplHardwareType_Copper; + case spl::HardwareType::Hoag: + return SplHardwareType_Hoag; + case spl::HardwareType::Iowa: + return SplHardwareType_IcosaMariko; + case spl::HardwareType::Calcio: + return SplHardwareType_Calcio; + case spl::HardwareType::Aula: + return SplHardwareType_Aula; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr bool IsSplRetailInteractiveDisplayStateEnabled(spl::RetailInteractiveDisplayState quest_state) { + switch (quest_state) { + case spl::RetailInteractiveDisplayState_Disabled: + return false; + case spl::RetailInteractiveDisplayState_Enabled: + return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + SplConfig GetSplConfig() { + static constinit bool s_is_initialized = false; + static constinit SplConfig s_config; + + if (!s_is_initialized) { + /* Initialize spl. */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + /* Create the config. */ + s_config = { + .is_development = spl::IsDevelopment(), + .hardware_type = ConvertToSplHardwareType(spl::GetHardwareType()), + .is_quest = IsSplRetailInteractiveDisplayStateEnabled(spl::GetRetailInteractiveDisplayState()), + .device_id_low = spl::GetDeviceIdLow(), + }; + + /* Mark as initialized. */ + s_is_initialized = true; + } + + return s_config; + } + + } + + bool IsSplDevelopment() { + return GetSplConfig().is_development; + } + + SplHardwareType GetSplHardwareType() { + return GetSplConfig().hardware_type; + } + + bool IsSplRetailInteractiveDisplayStateEnabled() { + return GetSplConfig().is_quest; + } + + u64 GetSplDeviceIdLow() { + return GetSplConfig().device_id_low; + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_spl.hpp b/libraries/libstratosphere/source/settings/impl/settings_spl.hpp new file mode 100644 index 000000000..83f658375 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_spl.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 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::settings::impl { + + enum SplHardwareType : u8 { + SplHardwareType_None = 0x0, + SplHardwareType_Icosa = 0x1, + SplHardwareType_IcosaMariko = 0x2, + SplHardwareType_Copper = 0x3, + SplHardwareType_Hoag = 0x4, + SplHardwareType_Calcio = 0x5, + SplHardwareType_Aula = 0x6, + }; + + bool IsSplDevelopment(); + SplHardwareType GetSplHardwareType(); + bool IsSplRetailInteractiveDisplayStateEnabled(); + u64 GetSplDeviceIdLow(); + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp b/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp new file mode 100644 index 000000000..b13b2b994 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_static_object.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 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::settings::impl { + + template + class StaticObject final { + NON_COPYABLE(StaticObject); + NON_MOVEABLE(StaticObject); + private: + StaticObject(); + public: + static T &Get() { + /* Declare static instance variables. */ + static constinit util::TypedStorage s_storage = {}; + static constinit bool s_initialized = false; + static constinit os::SdkMutex s_mutex; + + /* If we haven't already done so, construct the instance. */ + if (AMS_UNLIKELY(!s_initialized)) { + std::scoped_lock lk(s_mutex); + + /* Check that we didn't concurrently construct the instance. */ + if (AMS_LIKELY(!s_initialized)) { + /* Construct the instance. */ + util::ConstructAt(s_storage); + + /* Note that we constructed. */ + s_initialized = true; + } + } + + /* Return the constructed instance. */ + return util::GetReference(s_storage); + } + }; + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp b/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp new file mode 100644 index 000000000..3ec4a39ed --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_system_data.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 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 "settings_static_object.hpp" +#include "settings_system_data.hpp" + +namespace ams::settings::impl { + + namespace { + + constexpr inline const char MountNameSeparator[] = ":"; + constexpr inline const char DirectoryNameSeparator[] = "/"; + constexpr inline const char SystemDataFileName[] = "file"; + + class LazyFileAccessor final { + NON_COPYABLE(LazyFileAccessor); + NON_MOVEABLE(LazyFileAccessor); + private: + bool m_is_cached; + fs::FileHandle m_file; + s64 m_offset; + size_t m_size; + u8 m_buffer[16_KB]; + public: + LazyFileAccessor() : m_is_cached(false), m_file(), m_offset(), m_size(), m_buffer() { + /* ... */ + } + + Result Open(const char *path, int mode); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + private: + bool GetCache(s64 offset, void *dst, size_t size); + }; + + Result LazyFileAccessor::Open(const char *path, int mode) { + /* Open our file. */ + return fs::OpenFile(std::addressof(m_file), path, mode); + } + + void LazyFileAccessor::Close() { + /* Close our file. */ + fs::CloseFile(m_file); + + /* Reset our state. */ + m_is_cached = false; + m_file = {}; + m_size = 0; + m_offset = 0; + } + + bool LazyFileAccessor::GetCache(s64 offset, void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + /* Check that we're cached. */ + if (!m_is_cached) { + return false; + } + + /* Check that offset is within cache. */ + if (offset < m_offset) { + return false; + } + + /* Check that the read remains within range. */ + const size_t offset_in_cache = offset - m_offset; + if (m_size < offset_in_cache + size) { + return false; + } + + /* Copy the cached data. */ + std::memcpy(dst, m_buffer + offset_in_cache, size); + return true; + } + + Result LazyFileAccessor::Read(s64 offset, void *dst, size_t size) { + /* Check pre-conditions. */ + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + /* Try to read from cache. */ + R_SUCCEED_IF(this->GetCache(offset, dst, size)); + + /* If the read is too big for the cache, read the data directly. */ + if (size > sizeof(m_buffer)) { + return fs::ReadFile(m_file, offset, dst, size); + } + + /* Get the file size. */ + s64 file_size; + R_TRY(fs::GetFileSize(std::addressof(file_size), m_file)); + + /* If the file is too small, read the data directly. */ + if (file_size < offset + static_cast(size)) { + return fs::ReadFile(m_file, offset, dst, size); + } + + /* Determine the read size. */ + const size_t read_size = std::min(file_size - offset, sizeof(m_buffer)); + + /* Read into the cache. */ + if (read_size > 0) { + R_TRY(fs::ReadFile(m_file, offset, m_buffer, read_size)); + } + + /* Update cache statement. */ + m_offset = offset; + m_size = read_size; + m_is_cached = true; + + /* Get the data from the cache. */ + const bool succeeded = this->GetCache(offset, dst, size); + AMS_ASSERT(succeeded); + AMS_UNUSED(succeeded); + + return ResultSuccess(); + } + + LazyFileAccessor &GetLazyFileAccessor() { + return StaticObject::Get(); + } + + } + + void SystemData::SetSystemDataId(ncm::SystemDataId id) { + m_system_data_id = id; + } + + void SystemData::SetMountName(const char *name) { + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + + int pos = 0; + pos += util::Strlcpy(m_file_path + pos, name, sizeof(m_mount_name)); + pos += util::Strlcpy(m_file_path + pos, MountNameSeparator, sizeof(MountNameSeparator)); + pos += util::Strlcpy(m_file_path + pos, DirectoryNameSeparator, sizeof(DirectoryNameSeparator)); + pos += util::Strlcpy(m_file_path + pos, SystemDataFileName, sizeof(SystemDataFileName)); + } + + Result SystemData::Mount() { + return fs::MountSystemData(m_mount_name, m_system_data_id); + } + + Result SystemData::OpenToRead() { + return GetLazyFileAccessor().Open(m_file_path, fs::OpenMode_Read); + } + + void SystemData::Close() { + return GetLazyFileAccessor().Close(); + } + + Result SystemData::Read(s64 offset, void *dst, size_t size) { + return GetLazyFileAccessor().Read(offset, dst, size); + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp b/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp new file mode 100644 index 000000000..2cc37cae5 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_system_data.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 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::settings::impl { + + class SystemData { + NON_COPYABLE(SystemData); + NON_MOVEABLE(SystemData); + private: + static constexpr size_t FileNameLengthMax = 31; + private: + ncm::SystemDataId m_system_data_id; + char m_mount_name[fs::MountNameLengthMax + 1]; + char m_file_path[fs::MountNameLengthMax + 1 + 1 + FileNameLengthMax + 1]; + public: + SystemData() : m_system_data_id(), m_mount_name(), m_file_path() { /* ... */ } + + void SetSystemDataId(ncm::SystemDataId id); + void SetMountName(const char *name); + Result Mount(); + Result OpenToRead(); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + }; + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp b/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp new file mode 100644 index 000000000..ffa929596 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_system_save_data.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (c) 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 "settings_static_object.hpp" +#include "settings_system_save_data.hpp" + +namespace ams::settings::impl { + + namespace { + + constexpr s64 LazyWriterDelayMilliSeconds = 500; + + constexpr inline const char *MountNameSeparator = ":"; + constexpr inline const char *DirectoryNameSeparator = "/"; + constexpr inline const char *SystemSaveDataFileName = "file"; + + class LazyFileAccessor final { + NON_COPYABLE(LazyFileAccessor); + NON_MOVEABLE(LazyFileAccessor); + private: + static constexpr size_t FileNameLengthMax = 31; + private: + bool m_is_activated; + bool m_is_busy; + bool m_is_cached; + bool m_is_modified; + bool m_is_file_size_changed; + char m_mount_name[fs::MountNameLengthMax + 1]; + char m_file_path[fs::MountNameLengthMax + 1 + 1 + FileNameLengthMax + 1]; + int m_open_mode; + s64 m_file_size; + s64 m_offset; + size_t m_size; + u8 m_buffer[512_KB]; + os::Mutex m_mutex; + os::TimerEvent m_timer_event; + os::ThreadType m_thread; + alignas(os::ThreadStackAlignment) u8 m_thread_stack[4_KB]; + public: + LazyFileAccessor() : m_is_activated(false), m_is_busy(false), m_is_cached(false), m_is_modified(false), m_is_file_size_changed(false), m_mount_name{}, m_file_path{}, m_open_mode(0), m_file_size(0), m_offset(0), m_size(0), m_mutex(false), m_timer_event(os::EventClearMode_AutoClear), m_thread{} { + std::memset(m_buffer, 0, sizeof(m_buffer)); + std::memset(m_thread_stack, 0, sizeof(m_buffer)); + } + + Result Activate(); + Result Commit(const char *name, bool synchronous); + Result Create(const char *name, s64 size); + Result Open(const char *name, int mode); + void Close(); + Result Read(s64 offset, void *dst, size_t size); + Result Write(s64 offset, const void *src, size_t size); + Result SetFileSize(s64 size); + private: + static void ThreadFunc(void *arg); + + template + static int CreateFilePath(char (&path)[N], const char *name); + + static bool AreEqual(const void *lhs, const void *rhs, size_t size); + + void SetMountName(const char *name); + bool CompareMountName(const char *name) const; + void InvokeWriteBackLoop(); + Result CommitSynchronously(); + }; + + LazyFileAccessor &GetLazyFileAccessor() { + return StaticObject::Get(); + } + + Result LazyFileAccessor::Activate() { + std::scoped_lock lk(m_mutex); + + if (!m_is_activated) { + /* Create and start the lazy writer thread. */ + R_TRY(os::CreateThread(std::addressof(m_thread), LazyFileAccessor::ThreadFunc, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(settings, LazyWriter))); + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(settings, LazyWriter)); + os::StartThread(std::addressof(m_thread)); + m_is_activated = true; + } + + return ResultSuccess(); + } + + Result LazyFileAccessor::Commit(const char *name, bool synchronous) { + AMS_ASSERT(name != nullptr); + AMS_UNUSED(name); + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + AMS_ASSERT(this->CompareMountName(name)); + + if (synchronous) { + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + } else { + /* Start the timer to write. */ + m_timer_event.StartOneShot(TimeSpan::FromMilliSeconds(LazyWriterDelayMilliSeconds)); + } + + return ResultSuccess(); + } + + Result LazyFileAccessor::Create(const char *name, s64 size) { + AMS_ASSERT(name != nullptr); + AMS_ASSERT(size >= 0); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + + if (m_is_cached) { + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + + /* Reset the current state. */ + m_is_cached = false; + this->SetMountName(""); + m_open_mode = 0; + m_file_size = 0; + + /* Clear the buffer. */ + std::memset(m_buffer, 0, sizeof(m_buffer)); + } + + /* Create the save file. */ + this->CreateFilePath(m_file_path, name); + R_TRY(fs::CreateFile(m_file_path, size)); + + /* Initialize the accessor. */ + m_is_cached = true; + m_is_modified = true; + + this->SetMountName(name); + + m_open_mode = fs::OpenMode_Write; + m_file_size = size; + m_offset = 0; + m_size = size; + + /* Start the timer to write. */ + m_timer_event.StartOneShot(TimeSpan::FromMilliSeconds(LazyWriterDelayMilliSeconds)); + return ResultSuccess(); + } + + Result LazyFileAccessor::Open(const char *name, int mode) { + AMS_ASSERT(name != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(!m_is_busy); + + bool caches = true; + + if (m_is_cached) { + /* Check if the current mount matches the requested mount. */ + if (this->CompareMountName(name)) { + /* Check if the mode matches, or the existing mode is read-only. */ + if (m_open_mode == mode || m_open_mode == fs::OpenMode_Read) { + m_is_busy = true; + m_open_mode = mode; + return ResultSuccess(); + } + caches = false; + } + + /* Stop the timer and commit synchronously. */ + m_timer_event.Stop(); + R_TRY(this->CommitSynchronously()); + } + + if (caches) { + /* Create the save file if needed. */ + this->CreateFilePath(m_file_path, name); + + /* Open the save file. */ + fs::FileHandle file = {}; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Get the save size. */ + s64 file_size = 0; + R_TRY(fs::GetFileSize(std::addressof(file_size), file)); + AMS_ASSERT(0 <= file_size && file_size <= static_cast(sizeof(m_buffer))); + R_UNLESS(file_size <= static_cast(sizeof(m_buffer)), ResultTooLargeSystemSaveData()); + + /* Read the save file. */ + R_TRY(fs::ReadFile(file, 0, m_buffer, static_cast(file_size))); + + m_is_cached = true; + this->SetMountName(name); + m_file_size = file_size; + } + + m_is_busy = true; + m_open_mode = mode; + return ResultSuccess(); + } + + void LazyFileAccessor::Close() { + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + + m_is_busy = false; + } + + Result LazyFileAccessor::Read(s64 offset, void *dst, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(dst != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + AMS_ASSERT(m_is_cached); + AMS_ASSERT((m_open_mode & fs::OpenMode_Read) != 0); + AMS_ASSERT(offset + static_cast(size) <= m_file_size); + + std::memcpy(dst, m_buffer + offset, size); + + return ResultSuccess(); + } + + Result LazyFileAccessor::Write(s64 offset, const void *src, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(src != nullptr); + + std::scoped_lock lk(m_mutex); + + AMS_ASSERT(m_is_activated); + AMS_ASSERT(m_is_busy); + AMS_ASSERT(m_is_cached); + AMS_ASSERT((m_open_mode & ::ams::fs::OpenMode_Write) != 0); + + s64 end = offset + static_cast(size); + AMS_ASSERT(end <= static_cast(m_size)); + + /* Succeed if there's nothing to write. */ + R_SUCCEED_IF(this->AreEqual(m_buffer + offset, src, size)); + + /* Copy to dst. */ + std::memcpy(m_buffer + offset, src, size); + + /* Update offset and size. */ + if (m_is_modified) { + end = std::max(end, m_offset + static_cast(m_size)); + m_offset = std::min(m_offset, offset); + m_size = static_cast(end - m_offset); + } else { + m_is_modified = true; + m_offset = offset; + m_size = size; + } + + return ResultSuccess(); + } + + Result LazyFileAccessor::SetFileSize(s64 size) { + std::scoped_lock lk(m_mutex); + + const s64 prev_file_size = m_file_size; + + /* If the existing file size exceeds the new file size, reset the state or truncate. */ + if (m_file_size >= size) { + if (m_is_modified) { + /* If the current offset exceeds the new file size, reset the state. */ + if (m_offset >= size) { + m_is_modified = false; + m_offset = 0; + m_size = 0; + } else if (m_offset + static_cast(m_size) > size) { + /* Truncate the buffer for the new file size. */ + m_size = size - m_offset; + } + } + } else { + /* If unmodified, mark as modified and move the offset to the current end of the file. */ + if (!m_is_modified) { + m_is_modified = true; + m_offset = m_file_size; + } + + /* Zero-initialize the expanded file segment. */ + std::memset(m_buffer + m_file_size, 0, size - m_file_size); + + /* Update the current buffer size. */ + m_size = size - m_offset; + } + + /* Update state. */ + m_is_file_size_changed = prev_file_size != size; + m_file_size = size; + return ResultSuccess(); + } + + void LazyFileAccessor::ThreadFunc(void *arg) { + AMS_ASSERT(arg != nullptr); + reinterpret_cast(arg)->InvokeWriteBackLoop(); + } + + template + int LazyFileAccessor::CreateFilePath(char (&path)[N], const char *name) { + AMS_ASSERT(name != nullptr); + + const auto len = static_cast(N); + + s32 pos = util::Strlcpy(path, name, len); + pos += util::Strlcpy(path + pos, MountNameSeparator, len - pos); + pos += util::Strlcpy(path + pos, DirectoryNameSeparator, len - pos); + pos += util::Strlcpy(path + pos, SystemSaveDataFileName, len - pos); + return pos; + } + + bool LazyFileAccessor::AreEqual(const void *lhs, const void *rhs, size_t size) { + AMS_ASSERT(lhs != nullptr); + AMS_ASSERT(rhs != nullptr); + + auto lhs8 = static_cast(lhs); + auto rhs8 = static_cast(rhs); + for (size_t i = 0; i < size; i++) { + if (*(lhs8++) != *(rhs8++)) { + return false; + } + } + + return true; + } + + void LazyFileAccessor::SetMountName(const char *name) { + AMS_ASSERT(name != nullptr); + + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + } + + bool LazyFileAccessor::CompareMountName(const char *name) const { + return util::Strncmp(m_mount_name, name, sizeof(m_mount_name)) == 0; + } + + void LazyFileAccessor::InvokeWriteBackLoop() { + while (true) { + m_timer_event.Wait(); + + std::scoped_lock lk(m_mutex); + if (!m_is_busy) { + R_ABORT_UNLESS(this->CommitSynchronously()); + } + } + } + + Result LazyFileAccessor::CommitSynchronously() { + /* If we have data cached/modified, we need to commit. */ + if (m_is_cached && (m_is_file_size_changed || m_is_modified)) { + /* Get the save file path. */ + this->CreateFilePath(m_file_path, m_mount_name); + + { + /* Open the save file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), m_file_path, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* If we need to, update the file size. */ + if (m_is_file_size_changed) { + R_TRY(fs::SetFileSize(file, m_file_size)); + m_is_file_size_changed = false; + } + + /* If we need to, write the file data. */ + if (m_is_modified) { + /* Write to the file. */ + R_TRY(fs::WriteFile(file, m_offset, m_buffer + m_offset, m_size, fs::WriteOption::None)); + R_TRY(fs::FlushFile(file)); + + /* Reset state. */ + m_is_modified = false; + m_offset = 0; + m_size = 0; + } + } + + /* Commit the savedata. */ + R_TRY(fs::CommitSaveData(m_mount_name)); + } + + return ResultSuccess(); + } + + } + + void SystemSaveData::SetSystemSaveDataId(u64 id) { + m_system_save_data_id = id; + } + + void SystemSaveData::SetTotalSize(s64 size) { + m_total_size = size; + } + + void SystemSaveData::SetJournalSize(s64 size) { + m_journal_size = size; + } + + void SystemSaveData::SetFlags(u32 flags) { + m_flags = flags; + } + + void SystemSaveData::SetMountName(const char *name) { + AMS_ASSERT(name != nullptr); + + util::Strlcpy(m_mount_name, name, sizeof(m_mount_name)); + } + + Result SystemSaveData::Mount(bool create_save) { + /* Activate the file accessor. */ + R_TRY(GetLazyFileAccessor().Activate()); + + /* Don't allow automatic save data creation. */ + fs::DisableAutoSaveDataCreation(); + + /* Attempt to get the flags of existing save data. */ + u32 cur_flags = 0; + const auto flags_result = fs::GetSaveDataFlags(std::addressof(cur_flags), m_save_data_space_id, m_system_save_data_id); + + /* If the save data exists, ensure flags are correct. */ + if (R_SUCCEEDED(flags_result)) { + if (cur_flags != m_flags) { + R_TRY(fs::SetSaveDataFlags(m_system_save_data_id, m_save_data_space_id, m_flags)); + } + } else { + /* Unless we can create save data, return the error we got when retrieving flags. */ + R_UNLESS(create_save, flags_result); + + /* Create the save data. */ + R_TRY(fs::CreateSystemSaveData(m_save_data_space_id, m_system_save_data_id, ncm::SystemProgramId::Settings.value, m_total_size, m_journal_size, m_flags)); + } + + /* Mount the save data. */ + return fs::MountSystemSaveData(m_mount_name, m_save_data_space_id, m_system_save_data_id); + } + + Result SystemSaveData::Commit(bool synchronous) { + return GetLazyFileAccessor().Commit(m_mount_name, synchronous); + } + + Result SystemSaveData::Create(s64 size) { + return GetLazyFileAccessor().Create(m_mount_name, size); + } + + Result SystemSaveData::OpenToRead() { + return GetLazyFileAccessor().Open(m_mount_name, fs::OpenMode_Read); + } + + Result SystemSaveData::OpenToWrite() { + return GetLazyFileAccessor().Open(m_mount_name, fs::OpenMode_Write); + } + + void SystemSaveData::Close() { + GetLazyFileAccessor().Close(); + } + + Result SystemSaveData::Read(s64 offset, void *buf, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buf != nullptr); + + return GetLazyFileAccessor().Read(offset, buf, size); + } + + Result SystemSaveData::Write(s64 offset, const void *buf, size_t size) { + AMS_ASSERT(offset >= 0); + AMS_ASSERT(buf != nullptr); + + return GetLazyFileAccessor().Write(offset, buf, size); + } + + Result SystemSaveData::Flush() { + /* N doesn't do anything here. */ + return ResultSuccess(); + } + + Result SystemSaveData::SetFileSize(s64 size) { + return GetLazyFileAccessor().SetFileSize(size); + } + +} diff --git a/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp b/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp new file mode 100644 index 000000000..7fc85d883 --- /dev/null +++ b/libraries/libstratosphere/source/settings/impl/settings_system_save_data.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 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::settings::impl { + + class SystemSaveData { + NON_COPYABLE(SystemSaveData); + private: + fs::SystemSaveDataId m_system_save_data_id; + fs::SaveDataSpaceId m_save_data_space_id; + s64 m_total_size; + s64 m_journal_size; + u32 m_flags; + char m_mount_name[fs::MountNameLengthMax + 1]; + public: + SystemSaveData() : m_system_save_data_id(0), m_save_data_space_id(fs::SaveDataSpaceId::System), m_total_size(0), m_journal_size(0), m_flags(0) { /* ... */ } + + void SetSystemSaveDataId(u64 id); + void SetTotalSize(s64 size); + void SetJournalSize(s64 size); + void SetFlags(u32 flags); + void SetMountName(const char *name); + + Result Mount(bool create_save); + Result Commit(bool synchronous); + Result Create(s64 size); + Result OpenToRead(); + Result OpenToWrite(); + void Close(); + Result Read(s64 offset, void *buf, size_t size); + Result Write(s64 offset, const void *buf, size_t size); + Result Flush(); + Result SetFileSize(s64 size); + }; + +} diff --git a/libraries/libvapours/include/vapours/results/settings_results.hpp b/libraries/libvapours/include/vapours/results/settings_results.hpp index 7ce291a3a..0155b00f3 100644 --- a/libraries/libvapours/include/vapours/results/settings_results.hpp +++ b/libraries/libvapours/include/vapours/results/settings_results.hpp @@ -22,27 +22,32 @@ namespace ams::settings { R_DEFINE_NAMESPACE_RESULT_MODULE(105); R_DEFINE_ERROR_RESULT(SettingsItemNotFound, 11); + R_DEFINE_ERROR_RESULT(StopIteration, 21); R_DEFINE_ERROR_RANGE(InternalError, 100, 149); - R_DEFINE_ERROR_RESULT(SettingsItemKeyAllocationFailed, 101); - R_DEFINE_ERROR_RESULT(SettingsItemValueAllocationFailed, 102); + R_DEFINE_ERROR_RESULT(SettingsItemKeyAllocationFailed, 101); + R_DEFINE_ERROR_RESULT(SettingsItemValueAllocationFailed, 102); + R_DEFINE_ERROR_RESULT(SettingsItemKeyIteratorAllocationFailed, 111); + R_DEFINE_ERROR_RESULT(TooLargeSystemSaveData, 141); R_DEFINE_ERROR_RANGE(InvalidArgument, 200, 399); - R_DEFINE_ERROR_RESULT(SettingsNameNull, 201); - R_DEFINE_ERROR_RESULT(SettingsItemKeyNull, 202); - R_DEFINE_ERROR_RESULT(SettingsItemValueNull, 203); - R_DEFINE_ERROR_RESULT(SettingsItemKeyBufferNull, 204); - R_DEFINE_ERROR_RESULT(SettingsItemValueBufferNull, 205); + R_DEFINE_ERROR_RESULT(NullSettingsName, 201); + R_DEFINE_ERROR_RESULT(NullSettingsItemKey, 202); + R_DEFINE_ERROR_RESULT(NullSettingsItemValue, 203); + R_DEFINE_ERROR_RESULT(NullSettingsItemKeyBuffer, 204); + R_DEFINE_ERROR_RESULT(NullSettingsItemValueBuffer, 205); - R_DEFINE_ERROR_RESULT(SettingsNameEmpty, 221); - R_DEFINE_ERROR_RESULT(SettingsItemKeyEmpty, 222); + R_DEFINE_ERROR_RESULT(EmptySettingsName, 221); + R_DEFINE_ERROR_RESULT(EmptySettingsItemKey, 222); - R_DEFINE_ERROR_RESULT(SettingsNameTooLong, 241); - R_DEFINE_ERROR_RESULT(SettingsItemKeyTooLong, 242); + R_DEFINE_ERROR_RESULT(TooLongSettingsName, 241); + R_DEFINE_ERROR_RESULT(TooLongSettingsItemKey, 242); - R_DEFINE_ERROR_RESULT(SettingsNameInvalidFormat, 261); - R_DEFINE_ERROR_RESULT(SettingsItemKeyInvalidFormat, 262); - R_DEFINE_ERROR_RESULT(SettingsItemValueInvalidFormat, 263); + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsName, 261); + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsItemKey, 262); + R_DEFINE_ERROR_RESULT(InvalidFormatSettingsItemValue, 263); + + R_DEFINE_ERROR_RESULT(NotFoundSettingsItemKeyIterator, 281); R_DEFINE_ERROR_RANGE(CalibrationDataError, 580, 599); R_DEFINE_ERROR_RESULT(CalibrationDataFileSystemCorrupted, 581); diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp index d96bff739..7885286a2 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp @@ -102,7 +102,7 @@ namespace ams::mitm::settings { return GetFirmwareVersionImpl(out.GetPointer(), this->client_info); } - Result SetSysMitmService::GetSettingsItemValueSize(sf::Out out_size, const settings::fwdbg::SettingsName &name, const settings::fwdbg::SettingsItemKey &key) { + Result SetSysMitmService::GetSettingsItemValueSize(sf::Out out_size, const settings::SettingsName &name, const settings::SettingsItemKey &key) { R_TRY_CATCH(settings::fwdbg::GetSdCardKeyValueStoreSettingsItemValueSize(out_size.GetPointer(), name.value, key.value)) { R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged) R_CONVERT_ALL(sm::mitm::ResultShouldForwardToSession()); @@ -111,7 +111,7 @@ namespace ams::mitm::settings { return ResultSuccess(); } - Result SetSysMitmService::GetSettingsItemValue(sf::Out out_size, const sf::OutBuffer &out, const settings::fwdbg::SettingsName &name, const settings::fwdbg::SettingsItemKey &key) { + Result SetSysMitmService::GetSettingsItemValue(sf::Out out_size, const sf::OutBuffer &out, const settings::SettingsName &name, const settings::SettingsItemKey &key) { R_TRY_CATCH(settings::fwdbg::GetSdCardKeyValueStoreSettingsItemValue(out_size.GetPointer(), out.GetPointer(), out.GetSize(), name.value, key.value)) { R_CATCH_RETHROW(sf::impl::ResultRequestContextChanged) R_CONVERT_ALL(sm::mitm::ResultShouldForwardToSession()); diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp index d6db473c0..b9e6ededd 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp @@ -19,8 +19,8 @@ #define AMS_SETTINGS_SYSTEM_MITM_INTERFACE_INFO(C, H) \ AMS_SF_METHOD_INFO(C, H, 3, Result, GetFirmwareVersion, (sf::Out out), (out)) \ AMS_SF_METHOD_INFO(C, H, 4, Result, GetFirmwareVersion2, (sf::Out out), (out)) \ - AMS_SF_METHOD_INFO(C, H, 37, Result, GetSettingsItemValueSize, (sf::Out out_size, const ams::settings::fwdbg::SettingsName &name, const ams::settings::fwdbg::SettingsItemKey &key), (out_size, name, key)) \ - AMS_SF_METHOD_INFO(C, H, 38, Result, GetSettingsItemValue, (sf::Out out_size, const sf::OutBuffer &out, const ams::settings::fwdbg::SettingsName &name, const ams::settings::fwdbg::SettingsItemKey &key), (out_size, out, name, key)) \ + AMS_SF_METHOD_INFO(C, H, 37, Result, GetSettingsItemValueSize, (sf::Out out_size, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key), (out_size, name, key)) \ + AMS_SF_METHOD_INFO(C, H, 38, Result, GetSettingsItemValue, (sf::Out out_size, const sf::OutBuffer &out, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key), (out_size, out, name, key)) \ AMS_SF_METHOD_INFO(C, H, 62, Result, GetDebugModeFlag, (sf::Out out), (out)) AMS_SF_DEFINE_MITM_INTERFACE(ams::mitm::settings, ISetSysMitmInterface, AMS_SETTINGS_SYSTEM_MITM_INTERFACE_INFO) @@ -41,8 +41,8 @@ namespace ams::mitm::settings { public: Result GetFirmwareVersion(sf::Out out); Result GetFirmwareVersion2(sf::Out out); - Result GetSettingsItemValueSize(sf::Out out_size, const ams::settings::fwdbg::SettingsName &name, const ams::settings::fwdbg::SettingsItemKey &key); - Result GetSettingsItemValue(sf::Out out_size, const sf::OutBuffer &out, const ams::settings::fwdbg::SettingsName &name, const ams::settings::fwdbg::SettingsItemKey &key); + Result GetSettingsItemValueSize(sf::Out out_size, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key); + Result GetSettingsItemValue(sf::Out out_size, const sf::OutBuffer &out, const ams::settings::SettingsName &name, const ams::settings::SettingsItemKey &key); Result GetDebugModeFlag(sf::Out out); }; static_assert(IsISetSysMitmInterface); diff --git a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp index 18f1be696..0ecee3086 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -122,20 +122,20 @@ namespace ams::settings::fwdbg { } Result ValidateSettingsName(const char *name) { - R_UNLESS(name != nullptr, ResultSettingsNameNull()); + R_UNLESS(name != nullptr, ResultNullSettingsName()); const size_t len = strnlen(name, SettingsNameLengthMax + 1); - R_UNLESS(len > 0, ResultSettingsNameEmpty()); - R_UNLESS(len <= SettingsNameLengthMax, ResultSettingsNameTooLong()); - R_UNLESS(IsValidSettingsFormat(name, len), ResultSettingsNameInvalidFormat()); + R_UNLESS(len > 0, ResultEmptySettingsName()); + R_UNLESS(len <= SettingsNameLengthMax, ResultTooLongSettingsName()); + R_UNLESS(IsValidSettingsFormat(name, len), ResultInvalidFormatSettingsName()); return ResultSuccess(); } Result ValidateSettingsItemKey(const char *key) { - R_UNLESS(key != nullptr, ResultSettingsNameNull()); + R_UNLESS(key != nullptr, ResultNullSettingsName()); const size_t len = strnlen(key, SettingsItemKeyLengthMax + 1); - R_UNLESS(len > 0, ResultSettingsItemKeyEmpty()); - R_UNLESS(len <= SettingsNameLengthMax, ResultSettingsItemKeyTooLong()); - R_UNLESS(IsValidSettingsFormat(key, len), ResultSettingsItemKeyInvalidFormat()); + R_UNLESS(len > 0, ResultEmptySettingsItemKey()); + R_UNLESS(len <= SettingsNameLengthMax, ResultTooLongSettingsItemKey()); + R_UNLESS(IsValidSettingsFormat(key, len), ResultInvalidFormatSettingsItemKey()); return ResultSuccess(); } @@ -208,7 +208,7 @@ namespace ams::settings::fwdbg { const char *value_str = delimiter + 1; const char *type = val_tup; - R_UNLESS(delimiter != nullptr, ResultSettingsItemValueInvalidFormat()); + R_UNLESS(delimiter != nullptr, ResultInvalidFormatSettingsItemValue()); while (std::isspace(static_cast(*type)) && type != delimiter) { type++; @@ -216,8 +216,8 @@ namespace ams::settings::fwdbg { const size_t type_len = delimiter - type; const size_t value_len = strlen(value_str); - R_UNLESS(type_len > 0, ResultSettingsItemValueInvalidFormat()); - R_UNLESS(value_len > 0, ResultSettingsItemValueInvalidFormat()); + R_UNLESS(type_len > 0, ResultInvalidFormatSettingsItemValue()); + R_UNLESS(value_len > 0, ResultInvalidFormatSettingsItemValue()); /* Create new value. */ SdKeyValueStoreEntry new_value = {}; @@ -232,9 +232,9 @@ namespace ams::settings::fwdbg { std::memcpy(new_value.value, value_str, size); new_value.value_size = size; } else if (strncasecmp(type, "hex", type_len) == 0 || strncasecmp(type, "bytes", type_len) == 0) { - R_UNLESS(value_len > 0, ResultSettingsItemValueInvalidFormat()); - R_UNLESS(value_len % 2 == 0, ResultSettingsItemValueInvalidFormat()); - R_UNLESS(IsHexadecimal(value_str), ResultSettingsItemValueInvalidFormat()); + R_UNLESS(value_len > 0, ResultInvalidFormatSettingsItemValue()); + R_UNLESS(value_len % 2 == 0, ResultInvalidFormatSettingsItemValue()); + R_UNLESS(IsHexadecimal(value_str), ResultInvalidFormatSettingsItemValue()); const size_t size = value_len / 2; R_TRY(AllocateValue(&new_value.value, size)); @@ -253,7 +253,7 @@ namespace ams::settings::fwdbg { } else if (strncasecmp(type, "u64", type_len) == 0) { R_TRY((ParseSettingsItemIntegralValue(new_value, value_str))); } else { - return ResultSettingsItemValueInvalidFormat(); + return ResultInvalidFormatSettingsItemValue(); } /* Insert the entry. */ @@ -429,7 +429,7 @@ namespace ams::settings::fwdbg { } Result GetSdCardKeyValueStoreSettingsItemValue(size_t *out_size, void *dst, size_t dst_size, const char *name, const char *key) { - R_UNLESS(dst != nullptr, ResultSettingsItemValueBufferNull()); + R_UNLESS(dst != nullptr, ResultNullSettingsItemValueBuffer()); SdKeyValueStoreEntry *entry = nullptr; R_TRY(GetEntry(&entry, name, key));