diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp index c2914af1f..e0d1fae4e 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.cpp @@ -54,4 +54,31 @@ namespace ams::sprofile::srv { return fs::WriteFile(file, 0, src, size, fs::WriteOption::Flush); } + Result MoveFile(const char *src_path, const char *dst_path) { + /* Require that the source path is a file. */ + { + fs::DirectoryEntryType type; + R_ABORT_UNLESS(fs::GetEntryType(std::addressof(type), src_path)); + AMS_ABORT_UNLESS(type == fs::DirectoryEntryType_File); + } + + /* Delete the destination file. */ + R_TRY_CATCH(fs::DeleteFile(dst_path)) { + R_CATCH(fs::ResultPathNotFound) { /* It's okay if the dst path doesn't exist. */ } + } R_END_TRY_CATCH; + + /* Move the source file to the destination file. */ + R_TRY(fs::RenameFile(src_path, dst_path)); + + return ResultSuccess(); + } + + Result EnsureDirectory(const char *path) { + R_TRY_CATCH(fs::CreateDirectory(path)) { + R_CATCH(fs::ResultPathAlreadyExists) { /* It's okay if the directory already exists. */ } + } R_END_TRY_CATCH; + + return ResultSuccess(); + } + } diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp index 19bb8229c..8e420bf17 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_fs_utils.hpp @@ -20,6 +20,8 @@ namespace ams::sprofile::srv { Result ReadFile(const char *path, void *dst, size_t size, s64 offset); Result WriteFile(const char *path, const void *src, size_t size); - Result MoveFile(const char *dst_path, const char *src_path); + Result MoveFile(const char *src_path, const char *dst_path); + + Result EnsureDirectory(const char *path); } \ No newline at end of file diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.cpp new file mode 100644 index 000000000..dec66ad3d --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_controller_for_debug_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileControllerForDebugImpl::Reset() { + return m_manager->ResetSaveData(); + } + + Result ProfileControllerForDebugImpl::GetRaw(sf::Out out_type, sf::Out out_value, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetRaw(out_type.GetPointer(), out_value.GetPointer(), profile, key); + } + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp new file mode 100644 index 000000000..faa975f76 --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_controller_for_debug_impl.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sprofile_srv_i_profile_controller_for_debug.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileControllerForDebugImpl { + private: + ProfileManager *m_manager; + public: + ProfileControllerForDebugImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + public: + Result Reset(); + Result GetRaw(sf::Out out_type, sf::Out out_value, sprofile::Identifier profile, sprofile::Identifier key); + }; + static_assert(IsIProfileControllerForDebug); + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp new file mode 100644 index 000000000..fc1d3b07e --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sprofile_srv_types.hpp" + +namespace ams::sprofile::srv { + + class ProfileImporter { + private: + bool m_committed; + bool m_imported_metadata; + int m_importing_count; + util::optional m_metadata; + Identifier m_importing_profiles[50]; + util::BitFlagSet<50> m_imported_profiles; + Identifier m_revision_key; + public: + ProfileImporter(const util::optional &meta) : m_committed(false), m_imported_metadata(false), m_importing_count(0), m_metadata(util::nullopt), m_imported_profiles(), m_revision_key() { + if (meta.has_value()) { + m_metadata = *meta; + } + } + public: + bool HasProfile(Identifier id0, Identifier id1) { + /* Require that we have metadata. */ + if (m_metadata.has_value()) { + for (auto i = 0u; i < m_metadata->num_entries; ++i) { + const auto &entry = m_metadata->entries[i]; + if (entry.identifier_0 == id0 && entry.identifier_1 == id1) { + return true; + } + } + } + + /* We don't have the desired profile. */ + return false; + } + + bool CanImportMetadata() { + /* We can only import metadata if we haven't already imported metadata. */ + return !m_imported_metadata; + } + + void ImportMetadata(const ProfileMetadata &meta) { + /* Set that we've imported metadata. */ + m_imported_metadata = true; + + /* Import the service revision key. */ + m_revision_key = meta.revision_key; + + /* Import all profiles. */ + for (auto i = 0u; i < meta.num_entries; ++i) { + const auto &import_entry = meta.entries[i]; + if (!this->HasProfile(import_entry.identifier_0, import_entry.identifier_1)) { + m_importing_profiles[m_importing_count++] = import_entry.identifier_0; + } + } + } + + bool CanImportProfile(Identifier profile) { + /* Require that we imported metadata. */ + if (m_imported_metadata) { + /* Find the specified profile. */ + for (auto i = 0; i < m_importing_count; ++i) { + if (m_importing_profiles[i] == profile) { + /* Require the profile not already be imported. */ + return !m_imported_profiles[i]; + } + } + } + + /* We can't import the desired profile. */ + return false; + } + + void OnImportProfile(Identifier profile) { + /* Set the profile as imported. */ + for (auto i = 0; i < m_importing_count; ++i) { + if (m_importing_profiles[i] == profile) { + m_imported_profiles[i] = true; + break; + } + } + } + + bool CanCommit() { + /* We can't commit if we've already committed. */ + if (m_committed) { + return false; + } + + /* We need metadata in order to commit. */ + if (!m_imported_metadata) { + return false; + } + + /* We need to have imported everything we intended to import. */ + return m_imported_profiles.PopCount() == m_importing_count; + } + + int GetImportingCount() const { return m_importing_count; } + Identifier GetImportingProfile(int i) const { return m_importing_profiles[i]; } + + Identifier GetRevisionKey() const { return m_revision_key; } + }; + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp new file mode 100644 index 000000000..d13185e52 --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_importer_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileImporterImpl::ImportProfile(const sprofile::srv::ProfileDataForImportData &data) { + return m_manager->ImportProfile(data); + } + + Result ProfileImporterImpl::Commit() { + return m_manager->Commit(); + } + + Result ProfileImporterImpl::ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data) { + return m_manager->ImportMetadata(data); + } + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp new file mode 100644 index 000000000..b47ffb246 --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_importer_impl.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sprofile_srv_i_profile_importer.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileImporterImpl { + private: + ProfileManager *m_manager; + public: + ProfileImporterImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + public: + Result ImportProfile(const sprofile::srv::ProfileDataForImportData &data); + Result Commit(); + Result ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data); + }; + static_assert(IsIProfileImporter); + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp index 9ae304218..acde764ad 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.cpp @@ -21,6 +21,9 @@ namespace ams::sprofile::srv { namespace { + constexpr const char PrimaryDirectoryName[] = "primary"; + constexpr const char TemporaryDirectoryName[] = "temp"; + Result CreateSaveData(const ProfileManager::SaveDataInfo &save_data_info) { R_TRY_CATCH(fs::CreateSystemSaveData(save_data_info.id, save_data_info.size, save_data_info.journal_size, save_data_info.flags)) { R_CATCH(fs::ResultPathAlreadyExists) { /* Nintendo accepts already-existing savedata here. */ } @@ -28,11 +31,70 @@ namespace ams::sprofile::srv { return ResultSuccess(); } + void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + + void SafePrint(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const size_t len = util::TVSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + AMS_ABORT_UNLESS(len < dst_size); + } + + void CreateMetadataPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s/metadata", mount, dir); + } + + void CreatePrimaryMetadataPath(char *dst, size_t dst_size, const char *mount) { + CreateMetadataPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryMetadataPath(char *dst, size_t dst_size, const char *mount) { + CreateMetadataPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + + void CreateProfilePathImpl(char *dst, size_t dst_size, const char *mount, const char *dir, const Identifier &id) { + SafePrint(dst, dst_size, "%s:/%s/profiles/%02x%02x%02x%02x%02x%02x%02x", mount, dir, id.data[0], id.data[1], id.data[2], id.data[3], id.data[4], id.data[5], id.data[6]); + } + + void CreatePrimaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) { + CreateProfilePathImpl(dst, dst_size, mount, PrimaryDirectoryName, id); + } + + void CreateTemporaryProfilePath(char *dst, size_t dst_size, const char *mount, const Identifier &id) { + CreateProfilePathImpl(dst, dst_size, mount, TemporaryDirectoryName, id); + } + + void CreateDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s", mount, dir); + } + + void CreatePrimaryDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + + void CreateProfileDirectoryPathImpl(char *dst, size_t dst_size, const char *mount, const char *dir) { + SafePrint(dst, dst_size, "%s:/%s/profiles", mount, dir); + } + + void CreatePrimaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateProfileDirectoryPathImpl(dst, dst_size, mount, PrimaryDirectoryName); + } + + void CreateTemporaryProfileDirectoryPath(char *dst, size_t dst_size, const char *mount) { + CreateProfileDirectoryPathImpl(dst, dst_size, mount, TemporaryDirectoryName); + } + } ProfileManager::ProfileManager(const SaveDataInfo &save_data_info) - : m_save_data_info(save_data_info), m_save_file_mounted(false), - m_profile_metadata(util::nullopt), m_update_observer_manager() + : m_save_data_info(save_data_info), m_save_file_mounted(false), m_profile_importer(util::nullopt), + m_profile_metadata(util::nullopt), m_service_profile(util::nullopt), m_update_observer_manager() { /* ... */ } @@ -45,9 +107,6 @@ namespace ams::sprofile::srv { /* Ensure the savedata exists. */ if (R_SUCCEEDED(CreateSaveData(m_save_data_info))) { m_save_file_mounted = R_SUCCEEDED(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id)); - - /* TODO: Remove this after implementation, as it's for debugging. */ - R_ABORT_UNLESS(fs::MountSdCard("sprof-dbg")); } } @@ -67,7 +126,7 @@ namespace ams::sprofile::srv { /* Unload profile. */ m_profile_metadata = util::nullopt; - /* TODO m_service_profile = util::nullopt; */ + m_service_profile = util::nullopt; /* Create the save data. */ R_TRY(CreateSaveData(m_save_data_info)); @@ -79,6 +138,124 @@ namespace ams::sprofile::srv { return result; } + Result ProfileManager::OpenProfileImporter() { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_metadata_mutex); + std::scoped_lock lk2(m_profile_importer_mutex); + + /* Check that we don't already have an importer. */ + R_UNLESS(!m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Create importer. */ + m_profile_importer.emplace(m_profile_metadata); + return ResultSuccess(); + } + + Result ProfileManager::ImportProfile(const sprofile::srv::ProfileDataForImportData &data) { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_importer_mutex); + std::scoped_lock lk2(m_fs_mutex); + + /* Check that we have an importer. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Check that the metadata we're importing is valid. */ + R_UNLESS(data.data.version == ProfileDataVersion, sprofile::ResultInvalidDataVersion()); + + /* Succeed if we already have the profile. */ + R_SUCCEED_IF(m_profile_importer->HasProfile(data.identifier_0, data.identifier_1)); + + /* Check that we're importing the profile. */ + R_UNLESS(m_profile_importer->CanImportProfile(data.identifier_0), sprofile::ResultInvalidState()); + + /* Create temporary directories. */ + R_TRY(this->EnsureTemporaryDirectories()); + + /* Create profile. */ + char path[0x30]; + CreateTemporaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, data.identifier_0); + R_TRY(WriteFile(path, std::addressof(data.data), sizeof(data.data))); + + /* Set profile imported. */ + m_profile_importer->OnImportProfile(data.identifier_0); + return ResultSuccess(); + } + + Result ProfileManager::Commit() { + /* Acquire locks. */ + std::scoped_lock lk1(m_service_profile_mutex); + std::scoped_lock lk2(m_profile_metadata_mutex); + std::scoped_lock lk3(m_profile_importer_mutex); + std::scoped_lock lk4(m_general_mutex); + std::scoped_lock lk5(m_fs_mutex); + + /* Check that we have an importer. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + + /* Commit, and if we fail remount our save. */ + { + /* Setup guard in case we fail. */ + auto remount_guard = SCOPE_GUARD { + if (m_profile_importer.has_value()) { + /* Unmount save file. */ + fs::Unmount(m_save_data_info.mount_name); + m_save_file_mounted = false; + + /* Re-mount save file. */ + R_ABORT_UNLESS(fs::MountSystemSaveData(m_save_data_info.mount_name, m_save_data_info.id)); + m_save_file_mounted = true; + + /* Reset our importer. */ + m_profile_importer = util::nullopt; + } + }; + + /* Check that we can commit the importer. */ + R_UNLESS(m_profile_importer->CanCommit(), sprofile::ResultInvalidState()); + + /* Commit. */ + R_TRY(this->CommitImpl()); + + /* Commit the save file. */ + R_TRY(fs::CommitSaveData(m_save_data_info.mount_name)); + + /* We successfully committed. */ + remount_guard.Cancel(); + } + + /* NOTE: Here nintendo generates an "sprofile_update_profile" sreport with the new and old revision keys. */ + + /* Handle tasks for when we've committed (including notifying update observers). */ + this->OnCommitted(); + + return ResultSuccess(); + } + + Result ProfileManager::ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data) { + /* Acquire locks. */ + std::scoped_lock lk1(m_profile_importer_mutex); + std::scoped_lock lk2(m_fs_mutex); + + /* Check that we can import metadata. */ + R_UNLESS(m_profile_importer.has_value(), sprofile::ResultInvalidState()); + R_UNLESS(m_profile_importer->CanImportMetadata(), sprofile::ResultInvalidState()); + + /* Check that the metadata we're importing is valid. */ + R_UNLESS(data.metadata.version == ProfileMetadataVersion, sprofile::ResultInvalidMetadataVersion()); + + /* Create temporary directories. */ + R_TRY(this->EnsureTemporaryDirectories()); + + /* Create metadata. */ + char path[0x30]; + CreateTemporaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(WriteFile(path, std::addressof(data.metadata), sizeof(data.metadata))); + + /* Import the metadata. */ + m_profile_importer->ImportMetadata(data.metadata); + return ResultSuccess(); + } + Result ProfileManager::LoadPrimaryMetadata(ProfileMetadata *out) { /* Acquire locks. */ std::scoped_lock lk1(m_profile_metadata_mutex); @@ -92,7 +269,7 @@ namespace ams::sprofile::srv { /* Read profile metadata. */ char path[0x30]; - AMS_ABORT_UNLESS(util::TSNPrintf(path, sizeof(path), "%s:/%s/metadata", m_save_data_info.mount_name, "primary") < static_cast(sizeof(path))); + CreatePrimaryMetadataPath(path, sizeof(path), m_save_data_info.mount_name); R_TRY(ReadFile(path, std::addressof(*m_profile_metadata), sizeof(*m_profile_metadata), 0)); /* We read the metadata successfully. */ @@ -104,4 +281,196 @@ namespace ams::sprofile::srv { return ResultSuccess(); } + Result ProfileManager::LoadProfile(Identifier profile) { + /* Check if we already have the profile. */ + if (m_service_profile.has_value()) { + R_SUCCEED_IF(m_service_profile->name == profile); + } + + /* If we fail past this point, we want to have no profile. */ + auto prof_guard = SCOPE_GUARD { m_service_profile = util::nullopt; }; + + /* Create profile path. */ + char path[0x30]; + CreatePrimaryProfilePath(path, sizeof(path), m_save_data_info.mount_name, profile); + + /* Load the profile. */ + m_service_profile = {}; + R_TRY(ReadFile(path, std::addressof(m_service_profile->data), sizeof(m_service_profile->data), 0)); + + /* We succeeded. */ + prof_guard.Cancel(); + return ResultSuccess(); + } + + Result ProfileManager::GetDataEntry(ProfileDataEntry *out, Identifier profile, Identifier key) { + /* Acquire locks. */ + std::scoped_lock lk1(m_service_profile_mutex); + std::scoped_lock lk2(m_general_mutex); + + /* Load the desired profile. */ + R_TRY(this->LoadProfile(profile)); + + /* Find the specified key. */ + for (auto i = 0u; i < m_service_profile->data.num_entries; ++i) { + if (m_service_profile->data.entries[i].key == key) { + *out = m_service_profile->data.entries[i]; + return ResultSuccess(); + } + } + + return sprofile::ResultKeyNotFound(); + } + + Result ProfileManager::GetSigned64(s64 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_S64, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_s64; + return ResultSuccess(); + } + + Result ProfileManager::GetUnsigned64(u64 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_U64, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u64; + return ResultSuccess(); + } + + Result ProfileManager::GetSigned32(s32 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_S32, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_s32; + return ResultSuccess(); + } + + Result ProfileManager::GetUnsigned32(u32 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_U32, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u32; + return ResultSuccess(); + } + + Result ProfileManager::GetByte(u8 *out, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Check the type. */ + R_UNLESS(entry.type == ValueType_Byte, sprofile::ResultInvalidDataType()); + + /* Set the output value. */ + *out = entry.value_u8; + return ResultSuccess(); + } + + Result ProfileManager::GetRaw(u8 *out_type, u64 *out_value, Identifier profile, Identifier key) { + /* Get the data entry. */ + ProfileDataEntry entry; + R_TRY(this->GetDataEntry(std::addressof(entry), profile, key)); + + /* Set the output type and value. */ + *out_type = entry.type; + *out_value = entry.value_u64; + return ResultSuccess(); + } + + Result ProfileManager::CommitImpl() { + /* Ensure primary directories. */ + R_TRY(this->EnsurePrimaryDirectories()); + + /* Declare re-usable paths. */ + char tmp_path[0x30]; + char pri_path[0x30]; + + /* Move the metadata. */ + { + CreateTemporaryMetadataPath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name); + CreatePrimaryMetadataPath(pri_path, sizeof(pri_path), m_save_data_info.mount_name); + R_TRY(MoveFile(tmp_path, pri_path)); + } + + /* Move all profiles. */ + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + const auto id = m_profile_importer->GetImportingProfile(i); + + CreateTemporaryProfilePath(tmp_path, sizeof(tmp_path), m_save_data_info.mount_name, id); + CreatePrimaryProfilePath(pri_path, sizeof(pri_path), m_save_data_info.mount_name, id); + R_TRY(MoveFile(tmp_path, pri_path)); + } + + return ResultSuccess(); + } + + void ProfileManager::OnCommitted() { + /* If we need to, invalidate the loaded service profile. */ + if (m_service_profile.has_value()) { + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + if (m_service_profile->name == m_profile_importer->GetImportingProfile(i)) { + m_service_profile = util::nullopt; + break; + } + } + } + + /* TODO: Here, Nintendo sets the erpt ServiceProfileRevisionKey to the current revision key. */ + + /* Reset profile metadata. */ + m_profile_metadata = util::nullopt; + + /* Invoke any listeners. */ + for (auto i = 0; i < m_profile_importer->GetImportingCount(); ++i) { + m_update_observer_manager.OnUpdate(m_profile_importer->GetImportingProfile(i)); + } + } + + Result ProfileManager::EnsurePrimaryDirectories() { + /* Ensure the primary directories. */ + char path[0x30]; + + CreatePrimaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + CreatePrimaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + return ResultSuccess(); + } + + Result ProfileManager::EnsureTemporaryDirectories() { + /* Ensure the temporary directories. */ + char path[0x30]; + + CreateTemporaryDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + CreateTemporaryProfileDirectoryPath(path, sizeof(path), m_save_data_info.mount_name); + R_TRY(EnsureDirectory(path)); + + return ResultSuccess(); + } + } diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp index 1f2fe7dee..92b4ea75b 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_manager.hpp @@ -17,6 +17,7 @@ #include #include "sprofile_srv_types.hpp" #include "sprofile_srv_profile_update_observer_impl.hpp" +#include "sprofile_srv_profile_importer.hpp" namespace ams::sprofile::srv { @@ -35,11 +36,11 @@ namespace ams::sprofile::srv { os::SdkMutex m_fs_mutex{}; SaveDataInfo m_save_data_info; bool m_save_file_mounted; - /* TODO: util::optional m_profile_importer; */ + util::optional m_profile_importer; os::SdkMutex m_profile_importer_mutex{}; util::optional m_profile_metadata; os::SdkMutex m_profile_metadata_mutex{}; - /* TODO: util::optional m_service_profile; */ + util::optional m_service_profile; os::SdkMutex m_service_profile_mutex{}; ProfileUpdateObserverManager m_update_observer_manager; public: @@ -48,11 +49,33 @@ namespace ams::sprofile::srv { void InitializeSaveData(); Result ResetSaveData(); + Result OpenProfileImporter(); + Result ImportProfile(const sprofile::srv::ProfileDataForImportData &data); + Result Commit(); + Result ImportMetadata(const sprofile::srv::ProfileMetadataForImportMetadata &data); + Result LoadPrimaryMetadata(ProfileMetadata *out); + Result GetSigned64(s64 *out, Identifier profile, Identifier key); + Result GetUnsigned64(u64 *out, Identifier profile, Identifier key); + Result GetSigned32(s32 *out, Identifier profile, Identifier key); + Result GetUnsigned32(u32 *out, Identifier profile, Identifier key); + Result GetByte(u8 *out, Identifier profile, Identifier key); + + Result GetRaw(u8 *out_type, u64 *out_value, Identifier profile, Identifier key); + ProfileUpdateObserverManager &GetUpdateObserverManager() { return m_update_observer_manager; } private: - /* TODO */ + Result CommitImpl(); + + void OnCommitted(); + + Result GetDataEntry(ProfileDataEntry *out, Identifier profile, Identifier key); + + Result LoadProfile(Identifier profile); + + Result EnsurePrimaryDirectories(); + Result EnsureTemporaryDirectories(); }; } diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp new file mode 100644 index 000000000..c602535bc --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "sprofile_srv_profile_manager.hpp" +#include "sprofile_srv_profile_reader_impl.hpp" + +namespace ams::sprofile::srv { + + Result ProfileReaderImpl::GetSigned64(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetSigned64(out.GetPointer(), profile, key); + } + + Result ProfileReaderImpl::GetUnsigned64(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetUnsigned64(out.GetPointer(), profile, key); + } + + Result ProfileReaderImpl::GetSigned32(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetSigned32(out.GetPointer(), profile, key); + } + + Result ProfileReaderImpl::GetUnsigned32(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetUnsigned32(out.GetPointer(), profile, key); + } + + Result ProfileReaderImpl::GetByte(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key) { + return m_manager->GetByte(out.GetPointer(), profile, key); + } + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp new file mode 100644 index 000000000..884a1224f --- /dev/null +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_reader_impl.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sprofile_srv_i_profile_reader.hpp" + +namespace ams::sprofile::srv { + + class ProfileManager; + + class ProfileReaderImpl { + private: + ProfileManager *m_manager; + public: + ProfileReaderImpl(ProfileManager *manager) : m_manager(manager) { /* ... */ } + public: + Result GetSigned64(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetUnsigned64(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetSigned32(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetUnsigned32(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key); + Result GetByte(sf::Out out, sprofile::Identifier profile, sprofile::Identifier key); + }; + static_assert(IsIProfileReader); + +} diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp index e5b4a66ee..34d7cd8f5 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.cpp @@ -22,51 +22,11 @@ namespace ams::sprofile::srv { namespace { class AutoUnregisterObserver : public ProfileUpdateObserverImpl { - public: - static constexpr auto MaxProfiles = 4; private: - Identifier m_profiles[MaxProfiles]; - int m_profile_count; - os::SdkMutex m_mutex; ProfileUpdateObserverManager *m_manager; public: - AutoUnregisterObserver(ProfileUpdateObserverManager *manager) : m_profile_count(0), m_mutex(), m_manager(manager) { /* ... */ } + AutoUnregisterObserver(ProfileUpdateObserverManager *manager) : m_manager(manager) { /* ... */ } virtual ~AutoUnregisterObserver() { m_manager->CloseObserver(this); } - public: - Result Listen(Identifier profile) { - /* Lock ourselves. */ - std::scoped_lock lk(m_mutex); - - /* Check if we can listen. */ - R_UNLESS(m_profile_count < MaxProfiles, sprofile::ResultMaxListeners()); - - /* Check if we're already listening. */ - for (auto i = 0; i < m_profile_count; ++i) { - R_UNLESS(m_profiles[i] != profile, sprofile::ResultAlreadyListening()); - } - - /* Add the profile. */ - m_profiles[m_profile_count++] = profile; - return ResultSuccess(); - } - - Result Unlisten(Identifier profile) { - /* Check that we're listening. */ - for (auto i = 0; i < m_profile_count; ++i) { - if (m_profiles[i] == profile) { - m_profiles[i] = m_profiles[--m_profile_count]; - AMS_ABORT_UNLESS(m_profile_count >= 0); - return ResultSuccess(); - } - } - - return sprofile::ResultNotListening(); - } - - Result GetEventHandle(sf::OutCopyHandle out) { - out.SetValue(this->GetEvent().GetReadableHandle()); - return ResultSuccess(); - } }; static_assert(sprofile::IsIProfileUpdateObserver); diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp index 6a1810630..fd6f9a707 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_profile_update_observer_impl.hpp @@ -20,15 +20,65 @@ namespace ams::sprofile::srv { class ProfileUpdateObserverImpl { + public: + static constexpr auto MaxProfiles = 4; private: os::SystemEvent m_event; + Identifier m_profiles[MaxProfiles]; + int m_profile_count; + os::SdkMutex m_mutex; public: - ProfileUpdateObserverImpl() : m_event(os::EventClearMode_ManualClear, true) { /* ... */ } + ProfileUpdateObserverImpl() : m_event(os::EventClearMode_ManualClear, true), m_profile_count(0), m_mutex() { /* ... */ } virtual ~ProfileUpdateObserverImpl() { /* ... */ } public: os::SystemEvent &GetEvent() { return m_event; } const os::SystemEvent &GetEvent() const { return m_event; } + public: + Result Listen(Identifier profile) { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check if we can listen. */ + R_UNLESS(m_profile_count < MaxProfiles, sprofile::ResultMaxListeners()); + + /* Check if we're already listening. */ + for (auto i = 0; i < m_profile_count; ++i) { + R_UNLESS(m_profiles[i] != profile, sprofile::ResultAlreadyListening()); + } + + /* Add the profile. */ + m_profiles[m_profile_count++] = profile; + return ResultSuccess(); + } + + Result Unlisten(Identifier profile) { + /* Check that we're listening. */ + for (auto i = 0; i < m_profile_count; ++i) { + if (m_profiles[i] == profile) { + m_profiles[i] = m_profiles[--m_profile_count]; + AMS_ABORT_UNLESS(m_profile_count >= 0); + return ResultSuccess(); + } + } + + return sprofile::ResultNotListening(); + } + + Result GetEventHandle(sf::OutCopyHandle out) { + out.SetValue(m_event.GetReadableHandle()); + return ResultSuccess(); + } + public: + void OnUpdate(Identifier profile) { + for (auto i = 0; i < m_profile_count; ++i) { + if (m_profiles[i] == profile) { + m_event.Signal(); + break; + } + } + } }; + static_assert(sprofile::IsIProfileUpdateObserver); class ProfileUpdateObserverManager { public: @@ -42,6 +92,14 @@ namespace ams::sprofile::srv { public: Result OpenObserver(sf::Out> &out, MemoryResource *memory_resource); void CloseObserver(ProfileUpdateObserverImpl *observer); + + void OnUpdate(Identifier profile) { + std::scoped_lock lk(m_mutex); + + for (auto i = 0; i < m_observer_count; ++i) { + m_observers[i]->OnUpdate(profile); + } + } }; } diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp index 567cb7f2c..af9991dc4 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_bg_agent.cpp @@ -16,12 +16,22 @@ #include #include "sprofile_srv_profile_manager.hpp" #include "sprofile_srv_service_for_bg_agent.hpp" +#include "sprofile_srv_profile_importer_impl.hpp" #include "sprofile_srv_fs_utils.hpp" namespace ams::sprofile::srv { Result ServiceForBgAgent::OpenProfileImporter(sf::Out> out) { - AMS_ABORT("TODO: OpenProfileImporter"); + /* Allocate an object. */ + auto obj = sf::ObjectFactory::CreateSharedEmplaced(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Confirm that we can begin an import. */ + R_TRY(m_profile_manager->OpenProfileImporter()); + + /* Return the object. */ + *out = std::move(obj); + return ResultSuccess(); } Result ServiceForBgAgent::ReadMetadata(sf::Out out_count, const sf::OutArray &out, const sprofile::srv::ReadMetadataArgument &arg) { diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp index f0844e4f9..9313c3156 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_service_for_system_process.cpp @@ -16,12 +16,19 @@ #include #include "sprofile_srv_profile_manager.hpp" #include "sprofile_srv_service_for_system_process.hpp" +#include "sprofile_srv_profile_reader_impl.hpp" +#include "sprofile_srv_profile_controller_for_debug_impl.hpp" namespace ams::sprofile::srv { Result ServiceForSystemProcess::OpenProfileReader(sf::Out> out) { - /* TODO */ - AMS_ABORT("TODO: OpenProfileReader"); + /* Allocate an object. */ + auto obj = sf::ObjectFactory::CreateSharedEmplaced(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Return the object. */ + *out = std::move(obj); + return ResultSuccess(); } Result ServiceForSystemProcess::OpenProfileUpdateObserver(sf::Out> out) { @@ -29,8 +36,16 @@ namespace ams::sprofile::srv { } Result ServiceForSystemProcess::OpenProfileControllerForDebug(sf::Out> out) { - /* TODO */ - AMS_ABORT("TODO: OpenProfileControllerForDebug"); + /* Require debug mode in order to open a debug controller. */ + R_UNLESS(settings::fwdbg::IsDebugModeEnabled(), sprofile::ResultNotPermitted()); + + /* Allocate an object. */ + auto obj = sf::ObjectFactory::CreateSharedEmplaced(m_memory_resource, m_profile_manager); + R_UNLESS(obj != nullptr, sprofile::ResultAllocationFailed()); + + /* Return the object. */ + *out = std::move(obj); + return ResultSuccess(); } } diff --git a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp index 364739280..291c83bc5 100644 --- a/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp +++ b/libraries/libstratosphere/source/sprofile/srv/sprofile_srv_types.hpp @@ -78,6 +78,13 @@ namespace ams::sprofile::srv { static_assert(util::is_pod::value); static_assert(sizeof(ProfileData) == 0x4000); + struct ServiceProfile { + Identifier name; + ProfileData data; + }; + static_assert(util::is_pod::value); + static_assert(sizeof(ServiceProfile) == 0x4008); + struct ProfileDataForImportData : public sf::LargeData, public sf::PrefersMapAliasTransferMode { Identifier identifier_0; Identifier identifier_1; diff --git a/libraries/libvapours/include/vapours/results/sprofile_results.hpp b/libraries/libvapours/include/vapours/results/sprofile_results.hpp index fa8abd29d..03ba5ce70 100644 --- a/libraries/libvapours/include/vapours/results/sprofile_results.hpp +++ b/libraries/libvapours/include/vapours/results/sprofile_results.hpp @@ -24,13 +24,19 @@ namespace ams::sprofile { R_DEFINE_ERROR_RESULT(InvalidArgument, 100); R_DEFINE_ERROR_RESULT(InvalidState, 101); + R_DEFINE_ERROR_RESULT(NotPermitted, 303); + R_DEFINE_ERROR_RESULT(AllocationFailed, 401); + R_DEFINE_ERROR_RESULT(KeyNotFound, 600); + R_DEFINE_ERROR_RESULT(InvalidDataType, 601); + R_DEFINE_ERROR_RESULT(MaxListeners, 620); R_DEFINE_ERROR_RESULT(AlreadyListening, 621); R_DEFINE_ERROR_RESULT(NotListening, 622); R_DEFINE_ERROR_RESULT(MaxObservers, 623); R_DEFINE_ERROR_RESULT(InvalidMetadataVersion, 3210); + R_DEFINE_ERROR_RESULT(InvalidDataVersion, 3230); } diff --git a/libraries/libvapours/include/vapours/util/util_bitflagset.hpp b/libraries/libvapours/include/vapours/util/util_bitflagset.hpp index 4ccf07ccd..6c3447193 100644 --- a/libraries/libvapours/include/vapours/util/util_bitflagset.hpp +++ b/libraries/libvapours/include/vapours/util/util_bitflagset.hpp @@ -76,7 +76,7 @@ namespace ams::util { constexpr int PopCount(const Storage arr[]) { int count = 0; for (size_t i = 0; i < Count; i++) { - count += PopCount(arr[i]); + count += ::ams::util::PopCount(arr[i]); } return count; }