From 28a6bb713c6c2228b0db0ae1e24da28e9c8c1988 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Thu, 25 Jun 2020 21:59:59 -0700 Subject: [PATCH] sysupdater: begin implementing api --- .../impl/ams_system_thread_definitions.hpp | 9 +- .../ncm/ncm_content_meta_utils.hpp | 5 +- .../fssystem_file_system_proxy_api.cpp | 12 +- .../source/ncm/ncm_content_meta_utils.cpp | 12 +- .../source/amsmitm_initialization.cpp | 1 + stratosphere/ams_mitm/source/amsmitm_main.cpp | 7 + .../ams_mitm/source/amsmitm_module.hpp | 1 - .../source/amsmitm_module_management.cpp | 3 + .../source/bpc_mitm/bpc_ams_service.hpp | 1 - .../source/sysupdater/sysupdater_fs_utils.cpp | 26 +++ .../source/sysupdater/sysupdater_fs_utils.hpp | 23 +++ .../source/sysupdater/sysupdater_module.cpp | 50 ++++++ .../source/sysupdater/sysupdater_module.hpp | 24 +++ .../source/sysupdater/sysupdater_service.cpp | 159 ++++++++++++++++++ .../source/sysupdater/sysupdater_service.hpp | 43 +++++ 15 files changed, 362 insertions(+), 14 deletions(-) create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp create mode 100644 stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp 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 82da25ee9..3db6c394c 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 @@ -57,10 +57,11 @@ namespace ams::impl { AMS_DEFINE_SYSTEM_THREAD(-1, boot, Main); /* Mitm. */ - AMS_DEFINE_SYSTEM_THREAD(-7, mitm, InitializeThread); - AMS_DEFINE_SYSTEM_THREAD(-1, mitm_sf, QueryServerProcessThread); - AMS_DEFINE_SYSTEM_THREAD(16, mitm_fs, RomFileSystemInitializeThread); - AMS_DEFINE_SYSTEM_THREAD(21, mitm, DebugThrowThread); + AMS_DEFINE_SYSTEM_THREAD(-7, mitm, InitializeThread); + AMS_DEFINE_SYSTEM_THREAD(-1, mitm_sf, QueryServerProcessThread); + AMS_DEFINE_SYSTEM_THREAD(16, mitm_fs, RomFileSystemInitializeThread); + AMS_DEFINE_SYSTEM_THREAD(21, mitm, DebugThrowThread); + AMS_DEFINE_SYSTEM_THREAD(21, mitm_sysupdater, IpcServer); /* boot2. */ AMS_DEFINE_SYSTEM_THREAD(20, boot2, Main); diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp index 72ffbfe61..572f744f7 100644 --- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp +++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_content_meta_utils.hpp @@ -23,8 +23,11 @@ namespace ams::ncm { - Result ReadContentMetaPath(AutoBuffer *out, const char *path); + using MountContentMetaFunction = Result (*)(const char *mount_name, const char *path); + Result ReadContentMetaPath(AutoBuffer *out, const char *path); Result ReadVariationContentMetaInfoList(s32 *out_count, std::unique_ptr *out_meta_infos, const Path &path, FirmwareVariationId firmware_variation_id); + void SetMountContentMetaFunction(MountContentMetaFunction func); + } diff --git a/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp b/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp index 31011c0c1..8bc1dd43d 100644 --- a/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp +++ b/libraries/libstratosphere/source/fssystem/fssystem_file_system_proxy_api.cpp @@ -24,12 +24,12 @@ namespace ams::fssystem { /* Official FS has a 4.5 MB exp heap, a 6 MB buffer pool, an 8 MB device buffer manager heap, and a 14 MB buffer manager heap. */ /* We don't need so much memory for ams.mitm (as we're servicing a much more limited context). */ - /* We'll give ourselves a 2.5 MB exp heap, a 2 MB buffer pool, a 2 MB device buffer manager heap, and a 2 MB buffer manager heap. */ - /* These numbers match signed-system-partition safe FS. */ - constexpr size_t ExpHeapSize = 2_MB + 512_KB; - constexpr size_t BufferPoolSize = 2_MB; - constexpr size_t DeviceBufferSize = 2_MB; - constexpr size_t BufferManagerHeapSize = 2_MB; + /* We'll give ourselves a 1.5 MB exp heap, a 1 MB buffer pool, a 1 MB device buffer manager heap, and a 1 MB buffer manager heap. */ + /* These numbers are 1 MB less than signed-system-partition safe FS in all pools. */ + constexpr size_t ExpHeapSize = 1_MB + 512_KB; + constexpr size_t BufferPoolSize = 1_MB; + constexpr size_t DeviceBufferSize = 1_MB; + constexpr size_t BufferManagerHeapSize = 1_MB; constexpr size_t MaxCacheCount = 1024; constexpr size_t BlockSize = 16_KB; diff --git a/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp b/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp index e4c77ad70..05b6c78f1 100644 --- a/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp +++ b/libraries/libstratosphere/source/ncm/ncm_content_meta_utils.cpp @@ -26,12 +26,18 @@ namespace ams::ncm { return impl::PathView(name).HasSuffix(".cnmt"); } + Result MountContentMetaByRemoteFileSystemProxy(const char *mount_name, const char *path) { + return fs::MountContent(mount_name, path, fs::ContentType_Meta); + } + + constinit MountContentMetaFunction g_mount_content_meta_func = MountContentMetaByRemoteFileSystemProxy; + } Result ReadContentMetaPath(AutoBuffer *out, const char *path) { /* Mount the content. */ auto mount_name = impl::CreateUniqueMountName(); - R_TRY(fs::MountContent(mount_name.str, path, fs::ContentType_Meta)); + R_TRY(g_mount_content_meta_func(mount_name.str, path)); ON_SCOPE_EXIT { fs::Unmount(mount_name.str); }; /* Open the root directory. */ @@ -156,4 +162,8 @@ namespace ams::ncm { return ResultSuccess(); } + void SetMountContentMetaFunction(MountContentMetaFunction func) { + g_mount_content_meta_func = func; + } + } diff --git a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp index 96f89c68a..62eeeac0e 100644 --- a/stratosphere/ams_mitm/source/amsmitm_initialization.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_initialization.cpp @@ -157,6 +157,7 @@ namespace ams::mitm { /* Connect to set:sys. */ sm::DoWithSession([]() { + R_ABORT_UNLESS(setInitialize()); R_ABORT_UNLESS(setsysInitialize()); }); diff --git a/stratosphere/ams_mitm/source/amsmitm_main.cpp b/stratosphere/ams_mitm/source/amsmitm_main.cpp index 88faca823..c9524af7a 100644 --- a/stratosphere/ams_mitm/source/amsmitm_main.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_main.cpp @@ -17,6 +17,7 @@ #include "amsmitm_initialization.hpp" #include "amsmitm_module_management.hpp" #include "bpc_mitm/bpc_ams_power_utils.hpp" +#include "sysupdater/sysupdater_fs_utils.hpp" extern "C" { extern u32 __start__; @@ -84,6 +85,12 @@ void __appInit(void) { spl::InitializeForFs(); }); + /* Initialize fssystem library. */ + fssystem::InitializeForFileSystemProxy(); + + /* Configure ncm to use fssystem library to mount content. */ + ncm::SetMountContentMetaFunction(mitm::sysupdater::MountContentMeta); + ams::CheckApiVersion(); } diff --git a/stratosphere/ams_mitm/source/amsmitm_module.hpp b/stratosphere/ams_mitm/source/amsmitm_module.hpp index 1c68f3200..1c5796f37 100644 --- a/stratosphere/ams_mitm/source/amsmitm_module.hpp +++ b/stratosphere/ams_mitm/source/amsmitm_module.hpp @@ -18,7 +18,6 @@ namespace ams::mitm { - /* TODO: C++20 Concepts will make this a lot less stupid. */ template concept IsModule = requires(T, void *arg) { { T::ThreadPriority } -> std::convertible_to; diff --git a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp index ffa792fb9..03a7d779d 100644 --- a/stratosphere/ams_mitm/source/amsmitm_module_management.cpp +++ b/stratosphere/ams_mitm/source/amsmitm_module_management.cpp @@ -23,6 +23,7 @@ #include "bpc_mitm/bpc_ams_module.hpp" #include "ns_mitm/nsmitm_module.hpp" #include "hid_mitm/hidmitm_module.hpp" +#include "sysupdater/sysupdater_module.hpp" namespace ams::mitm { @@ -35,6 +36,7 @@ namespace ams::mitm { ModuleId_BpcAms, ModuleId_NsMitm, ModuleId_HidMitm, + ModuleId_Sysupdater, ModuleId_Count, }; @@ -67,6 +69,7 @@ namespace ams::mitm { GetModuleDefinition(), GetModuleDefinition(), GetModuleDefinition(), + GetModuleDefinition(), }; } diff --git a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp index fb15dc540..7a00d6de5 100644 --- a/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp +++ b/stratosphere/ams_mitm/source/bpc_mitm/bpc_ams_service.hpp @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - #pragma once #include diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp new file mode 100644 index 000000000..4bcd746ea --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.cpp @@ -0,0 +1,26 @@ +/* + * 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 "sysupdater_fs_utils.hpp" + +namespace ams::mitm::sysupdater { + + Result MountContentMeta(const char *mount_name, const char *path) { + /* TODO: Implement */ + AMS_ABORT(); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp new file mode 100644 index 000000000..c63df8dbf --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_fs_utils.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::sysupdater { + + Result MountContentMeta(const char *mount_name, const char *path); + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp new file mode 100644 index 000000000..1eed1e633 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.cpp @@ -0,0 +1,50 @@ +/* + * 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 "../amsmitm_initialization.hpp" +#include "sysupdater_module.hpp" +#include "sysupdater_service.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + constexpr sm::ServiceName SystemUpdateServiceName = sm::ServiceName::Encode("ams:su"); + constexpr size_t SystemUpdateMaxSessions = 1; + + constexpr size_t MaxServers = 1; + constexpr size_t MaxSessions = SystemUpdateMaxSessions; + using ServerOptions = sf::hipc::DefaultServerManagerOptions; + sf::hipc::ServerManager g_server_manager; + + } + + void MitmModule::ThreadFunction(void *arg) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + + /* Connect to nim. */ + sm::DoWithSession([]() { nim::InitializeForNetworkInstallManager(); }); + ON_SCOPE_EXIT { nim::FinalizeForNetworkInstallManager(); }; + + /* Register ams:su. */ + R_ABORT_UNLESS((g_server_manager.RegisterServer(SystemUpdateServiceName, SystemUpdateMaxSessions))); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp new file mode 100644 index 000000000..caf8ac688 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_module.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "../amsmitm_module.hpp" + +namespace ams::mitm::sysupdater { + + DEFINE_MITM_MODULE_CLASS(0x8000, AMS_GET_SYSTEM_THREAD_PRIORITY(mitm_sysupdater, IpcServer)); + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp new file mode 100644 index 000000000..a301e51e5 --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.cpp @@ -0,0 +1,159 @@ +/* + * 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 "sysupdater_service.hpp" + +namespace ams::mitm::sysupdater { + + namespace { + + /* ExFat NCAs prior to 2.0.0 do not actually include the exfat driver, and don't boot. */ + constexpr inline u32 MinimumVersionForExFatDriver = 65536; + + Result ActivateSystemUpdateContentMetaDatabase() { + /* TODO: Don't use gamecard db. */ + return ncm::ActivateContentMetaDatabase(ncm::StorageId::GameCard); + } + + void InactivateSystemUpdateContentMetaDatabase() { + /* TODO: Don't use gamecard db. */ + ncm::InactivateContentMetaDatabase(ncm::StorageId::GameCard); + } + + Result OpenSystemUpdateContentMetaDatabase(ncm::ContentMetaDatabase *out) { + /* TODO: Don't use gamecard db. */ + return ncm::OpenContentMetaDatabase(out, ncm::StorageId::GameCard); + } + + Result GetContentInfoOfContentMeta(ncm::ContentInfo *out, ncm::ContentMetaDatabase &db, const ncm::ContentMetaKey &key) { + s32 ofs = 0; + while (true) { + /* List content infos. */ + s32 count; + ncm::ContentInfo info; + R_TRY(db.ListContentInfo(std::addressof(count), std::addressof(info), 1, key, ofs++)); + + /* No content infos left to list. */ + if (count == 0) { + break; + } + + /* Check if the info is for meta content. */ + if (info.GetType() == ncm::ContentType::Meta) { + *out = info; + return ResultSuccess(); + } + } + + /* Not found. */ + return ncm::ResultContentInfoNotFound(); + } + + Result ReadContentMetaPath(ncm::AutoBuffer *out, const char *package_root, const ncm::ContentInfo &content_info) { + /* Get the content id string for the info. */ + auto content_id_str = ncm::GetContentIdString(content_info.GetId()); + + /* Create a new path. */ + ncm::Path content_path; + std::snprintf(content_path.str, sizeof(content_path.str), "%s/%s.cnmt.nca", package_root, content_id_str.data); + + /* Read the content meta path. */ + return ncm::ReadContentMetaPath(out, content_path.str); + } + + bool IsExFatDriverSupported(const ncm::ContentMetaInfo &info) { + return info.version >= MinimumVersionForExFatDriver && ((info.attributes & ncm::ContentMetaAttribute_IncludesExFatDriver) != 0); + } + + } + + Result SystemUpdateService::GetUpdateInformation(sf::Out out, const ncm::Path &package_root) { + /* Create a new update information. */ + UpdateInformation update_info = {}; + + /* Parse the update. */ + { + /* Activate the package database. */ + R_TRY(ActivateSystemUpdateContentMetaDatabase()); + ON_SCOPE_EXIT { InactivateSystemUpdateContentMetaDatabase(); }; + + /* Open the package database. */ + ncm::ContentMetaDatabase package_db; + R_TRY(OpenSystemUpdateContentMetaDatabase(std::addressof(package_db))); + + /* Cleanup and build the content meta database. */ + ncm::ContentMetaDatabaseBuilder builder(std::addressof(package_db)); + R_TRY(builder.Cleanup()); + R_TRY(builder.BuildFromPackage(package_root.str)); + + /* Get the key for the SystemUpdate. */ + ncm::ContentMetaKey key; + auto list_count = package_db.ListContentMeta(std::addressof(key), 1, ncm::ContentMetaType::SystemUpdate); + R_UNLESS(list_count.written > 0, ncm::ResultSystemUpdateNotFoundInPackage()); + + /* Get the content info for the key. */ + ncm::ContentInfo content_info; + R_TRY(GetContentInfoOfContentMeta(std::addressof(content_info), package_db, key)); + + /* Read the content meta. */ + ncm::AutoBuffer content_meta_buffer; + R_TRY(ReadContentMetaPath(std::addressof(content_meta_buffer), package_root.str, content_info)); + + /* Create a reader. */ + const auto reader = ncm::PackagedContentMetaReader(content_meta_buffer.Get(), content_meta_buffer.GetSize()); + + /* Iterate over infos to find the system update info. */ + for (size_t i = 0; i < reader.GetContentMetaCount(); ++i) { + const auto &meta_info = *reader.GetContentMetaInfo(i); + + switch (meta_info.type) { + case ncm::ContentMetaType::SystemUpdate: + /* Set the version. */ + update_info.version = meta_info.version; + break; + case ncm::ContentMetaType::BootImagePackage: + /* Detect exFAT support. */ + update_info.exfat_supported |= IsExFatDriverSupported(meta_info); + break; + default: + break; + } + } + + /* Default to no firmware variations. */ + update_info.firmware_variation_count = 0; + + /* Parse firmware variations if relevant. */ + if (reader.GetExtendedDataSize() != 0) { + /* Get the actual firmware variation count. */ + ncm::SystemUpdateMetaExtendedDataReader extended_data_reader(reader.GetExtendedData(), reader.GetExtendedDataSize()); + update_info.firmware_variation_count = extended_data_reader.GetFirmwareVariationCount(); + + /* NOTE: Update this if Nintendo ever actually releases an update with this many variations? */ + R_UNLESS(update_info.firmware_variation_count <= FirmwareVariationCountMax, ncm::ResultInvalidFirmwareVariation()); + + for (size_t i = 0; i < update_info.firmware_variation_count; ++i) { + update_info.firmware_variation_ids[i] = *extended_data_reader.GetFirmwareVariationId(i); + } + } + } + + /* Set the parsed update info. */ + out.SetValue(update_info); + return ResultSuccess(); + } + +} diff --git a/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp new file mode 100644 index 000000000..8d2cb39be --- /dev/null +++ b/stratosphere/ams_mitm/source/sysupdater/sysupdater_service.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::mitm::sysupdater { + + constexpr inline size_t FirmwareVariationCountMax = 16; + + struct UpdateInformation { + u32 version; + bool exfat_supported; + u32 firmware_variation_count; + ncm::FirmwareVariationId firmware_variation_ids[FirmwareVariationCountMax]; + }; + + class SystemUpdateService final : public sf::IServiceObject { + private: + enum class CommandId { + GetUpdateInformation = 0, + }; + private: + Result GetUpdateInformation(sf::Out out, const ncm::Path &path); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(GetUpdateInformation), + }; + }; + +}