diff --git a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp index 01b429b8d..33f5ff094 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/setmitm_module.cpp @@ -37,6 +37,9 @@ namespace ams::mitm::settings { } void MitmModule::ThreadFunction(void *arg) { + /* Wait until initialization is complete. */ + mitm::WaitInitialized(); + /* Create mitm servers. */ R_ASSERT(g_server_manager.RegisterMitmServer(SetMitmServiceName)); R_ASSERT(g_server_manager.RegisterMitmServer(SetSysMitmServiceName)); diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp new file mode 100644 index 000000000..d425c23e0 --- /dev/null +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018-2019 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 "setsys_mitm_service.hpp" + +namespace ams::mitm::settings { + + using namespace ams::settings; + + namespace { + + os::Mutex g_firmware_version_lock; + bool g_cached_firmware_version; + settings::FirmwareVersion g_firmware_version; + settings::FirmwareVersion g_ams_firmware_version; + + void CacheFirmwareVersion() { + std::scoped_lock lk(g_firmware_version_lock); + + if (g_cached_firmware_version) { + return; + } + + /* Mount firmware version data archive. */ + R_ASSERT(romfsMountFromDataArchive(static_cast(ncm::ProgramId::ArchiveSystemVersion), NcmStorageId_BuiltInSystem, "sysver")); + { + ON_SCOPE_EXIT { romfsUnmount("sysver"); }; + + /* Firmware version file must exist. */ + FILE *fp = fopen("sysver:/file", "rb"); + AMS_ASSERT(fp != nullptr); + ON_SCOPE_EXIT { fclose(fp); }; + + /* Must be possible to read firmware version from file. */ + AMS_ASSERT(fread(&g_firmware_version, sizeof(g_firmware_version), 1, fp) == 1); + + g_ams_firmware_version = g_firmware_version; + } + + /* Modify the atmosphere firmware version to display a custom version string. */ + { + const auto api_info = exosphere::GetApiInfo(); + const char emummc_char = emummc::IsActive() ? 'E' : 'S'; + + /* GCC complains about the following snprintf possibly truncating, but this is not a problem and has been carefully accounted for. */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-truncation" + { + char display_version[sizeof(g_ams_firmware_version.display_version)]; + std::snprintf(display_version, sizeof(display_version), "%s|AMS %u.%u.%u|%c", g_ams_firmware_version.display_version, api_info.major_version, api_info.minor_version, api_info.micro_version, emummc_char); + std::memcpy(g_ams_firmware_version.display_version, display_version, sizeof(display_version)); + } + #pragma GCC diagnostic pop + } + + g_cached_firmware_version = true; + } + + Result GetFirmwareVersionImpl(settings::FirmwareVersion *out, const sm::MitmProcessInfo &client_info) { + /* Ensure that we have the firmware version cached. */ + CacheFirmwareVersion(); + + /* We want to give a special firmware version to the home menu title, and nothing else. */ + /* This means Qlaunch + Maintenance Menu, and nothing else. */ + if (client_info.program_id == ncm::ProgramId::AppletQlaunch || client_info.program_id == ncm::ProgramId::AppletMaintenanceMenu) { + *out = g_ams_firmware_version; + } else { + *out = g_firmware_version; + } + + return ResultSuccess(); + } + + } + + Result SetSysMitmService::GetFirmwareVersion(sf::Out out) { + R_TRY(GetFirmwareVersionImpl(out.GetPointer(), this->client_info)); + + /* GetFirmwareVersion sanitizes the revision fields. */ + out.GetPointer()->revision_major = 0; + out.GetPointer()->revision_minor = 0; + return ResultSuccess(); + } + + Result SetSysMitmService::GetFirmwareVersion2(sf::Out out) { + return GetFirmwareVersionImpl(out.GetPointer(), this->client_info); + } + + +} 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 33189ec62..3d9de15fd 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp @@ -21,20 +21,25 @@ namespace ams::mitm::settings { class SetSysMitmService : public sf::IMitmServiceObject { private: enum class CommandId { - /* TODO */ + GetFirmwareVersion = 3, + GetFirmwareVersion2 = 4, }; public: static bool ShouldMitm(const sm::MitmProcessInfo &client_info) { - /* TODO */ - return false; + /* We will mitm: + * - everything, because we want to intercept all settings requests. + */ + return true; } public: SF_MITM_SERVICE_OBJECT_CTOR(SetSysMitmService) { /* ... */ } protected: - /* TODO */ + Result GetFirmwareVersion(sf::Out out); + Result GetFirmwareVersion2(sf::Out out); public: DEFINE_SERVICE_DISPATCH_TABLE { - /* TODO */ + MAKE_SERVICE_COMMAND_META(GetFirmwareVersion), + MAKE_SERVICE_COMMAND_META(GetFirmwareVersion2), }; }; diff --git a/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp b/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp index 994a3a85a..155a4ce7f 100644 --- a/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/settings/settings_types.hpp @@ -172,4 +172,52 @@ namespace ams::settings { return 0 <= rc && rc < RegionCode_Count; } + /* This needs to be defined separately from libnx's so that it can inherit from sf::LargeData. */ + + struct FirmwareVersion : public sf::LargeData { + u8 major; + u8 minor; + u8 micro; + u8 padding1; + u8 revision_major; + u8 revision_minor; + u8 padding2; + u8 padding3; + char platform[0x20]; + char version_hash[0x40]; + char display_version[0x18]; + char display_title[0x80]; + + constexpr inline u32 GetVersion() const { + return (static_cast(major) << 16) | (static_cast(minor) << 8) | (static_cast(micro) << 0); + } + }; + + static_assert(std::is_pod::value); + static_assert(sizeof(FirmwareVersion) == sizeof(::SetSysFirmwareVersion)); + + constexpr inline bool operator==(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() == rhs.GetVersion(); + } + + constexpr inline bool operator!=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs == rhs); + } + + constexpr inline bool operator<(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() < rhs.GetVersion(); + } + + constexpr inline bool operator>=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs < rhs); + } + + constexpr inline bool operator<=(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return lhs.GetVersion() <= rhs.GetVersion(); + } + + constexpr inline bool operator>(const FirmwareVersion &lhs, const FirmwareVersion &rhs) { + return !(lhs <= rhs); + } + } diff --git a/stratosphere/libstratosphere/include/stratosphere/spl/smc/spl_smc.hpp b/stratosphere/libstratosphere/include/stratosphere/spl/smc/spl_smc.hpp index cef9c970f..2453b2d7f 100644 --- a/stratosphere/libstratosphere/include/stratosphere/spl/smc/spl_smc.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/spl/smc/spl_smc.hpp @@ -57,6 +57,7 @@ namespace ams::spl::smc { Result AtmosphereCopyFromIram(void *dram_dst, uintptr_t iram_src, size_t size); Result AtmosphereReadWriteRegister(uint64_t address, uint32_t mask, uint32_t value, uint32_t *out_value); Result AtmosphereWriteAddress(void *dst, const void *src, size_t size); + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id); /* Helpers. */ inline Result SetConfig(SplConfigItem which, const u64 value) { diff --git a/stratosphere/libstratosphere/source/ams/ams_emummc_api.cpp b/stratosphere/libstratosphere/source/ams/ams_emummc_api.cpp new file mode 100644 index 000000000..1eb304f9e --- /dev/null +++ b/stratosphere/libstratosphere/source/ams/ams_emummc_api.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018-2019 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::emummc { + + namespace { + + /* Convenience Definitions. */ + constexpr u32 StorageMagic = 0x30534645; /* EFS0 */ + constexpr size_t MaxDirLen = 0x7F; + + /* Types. */ + struct BaseConfig { + u32 magic; + u32 type; + u32 id; + u32 fs_version; + }; + + struct PartitionConfig { + u64 start_sector; + }; + + struct FileConfig { + char path[MaxDirLen + 1]; + }; + + struct ExosphereConfig { + BaseConfig base_cfg; + union { + PartitionConfig partition_cfg; + FileConfig file_cfg; + }; + char emu_dir_path[MaxDirLen + 1]; + }; + + enum Storage : u32 { + Storage_Emmc, + Storage_Sd, + Storage_SdFile, + + Storage_Count, + }; + + /* Globals. */ + os::Mutex g_lock; + ExosphereConfig g_exo_config; + bool g_is_emummc; + bool g_has_cached; + + /* Helpers. */ + void CacheValues() { + std::scoped_lock lk(g_lock); + + if (g_has_cached) { + return; + } + + /* Retrieve and cache values. */ + { + + typename std::aligned_storage<2 * (MaxDirLen + 1), 0x1000>::type path_storage; + + struct { + char file_path[MaxDirLen + 1]; + char nintendo_path[MaxDirLen + 1]; + } *paths = reinterpret_cast(&path_storage); + + /* Retrieve paths from secure monitor. */ + AMS_ASSERT(spl::smc::AtmosphereGetEmummcConfig(&g_exo_config, paths, 0) == spl::smc::Result::Success); + + const Storage storage = static_cast(g_exo_config.base_cfg.type); + g_is_emummc = g_exo_config.base_cfg.magic == StorageMagic && storage != Storage_Emmc; + + /* Format paths. Ignore string format warnings. */ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wformat-truncation" + { + if (storage == Storage_SdFile) { + std::snprintf(g_exo_config.file_cfg.path, sizeof(g_exo_config.file_cfg.path), "/%s", paths->file_path); + } + + std::snprintf(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), "/%s", paths->nintendo_path); + + /* If we're emummc, implement default nintendo redirection path. */ + if (g_is_emummc && std::strcmp(g_exo_config.emu_dir_path, "/") == 0) { + std::snprintf(g_exo_config.emu_dir_path, sizeof(g_exo_config.emu_dir_path), "/emummc/Nintendo_%04x", g_exo_config.base_cfg.id); + } + } + #pragma GCC diagnostic pop + } + + g_has_cached = true; + } + + } + + /* Get whether emummc is active. */ + bool IsActive() { + CacheValues(); + return g_is_emummc; + } + + /* Get Nintendo redirection path. */ + const char *GetNintendoDirPath() { + CacheValues(); + if (!g_is_emummc) { + return nullptr; + } + return g_exo_config.emu_dir_path; + } + + /* Get Emummc folderpath, NULL if not file-based. */ + const char *GetFilePath() { + CacheValues(); + if (!g_is_emummc || g_exo_config.base_cfg.type != Storage_SdFile) { + return nullptr; + } + return g_exo_config.file_cfg.path; + } + +} diff --git a/stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp b/stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp index 50aac6f07..03668251c 100644 --- a/stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp +++ b/stratosphere/libstratosphere/source/spl/smc/spl_smc.cpp @@ -361,4 +361,18 @@ namespace ams::spl::smc { return static_cast(args.X[0]); } + Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) { + const u64 paths = reinterpret_cast(out_paths); + AMS_ASSERT(util::IsAligned(paths, 0x1000)); + + SecmonArgs args = {}; + args.X[0] = static_cast(FunctionId::AtmosphereGetEmummcConfig); + args.X[1] = storage_id; + args.X[2] = paths; + svcCallSecureMonitor(&args); + + std::memcpy(out_config, &args.X[1], sizeof(args) - sizeof(args.X[0])); + return static_cast(args.X[0]); + } + }