From bd9d8fff467f9d74c72ee7b95f72e1fbc43fa770 Mon Sep 17 00:00:00 2001 From: ndeadly Date: Fri, 10 Mar 2023 18:06:38 +0100 Subject: [PATCH] Add system setting to mirror bluetooth pairing database to sd card (#1787) * ams_mitm: add ability to mirror bluetooth device pairing database to sd card via a system setting * ams_mitm: address requested stylistic changes * ams_mitm: make use of R_SUCCEED macro * ams_mitm: use settings::BluetoothDevicesSettings instead of libnx type * ams_mitm: fix logic error when truncating pairing database on read * Update .ini comment * ams_mitm: missing R_TRY around call to fs::FlushFile * stratosphere: remove union from BluetoothDevicesSettings type --------- Co-authored-by: ndeadly <24677491+ndeadly@users.noreply.github.com> --- config_templates/system_settings.ini | 4 + .../stratosphere/settings/settings_types.hpp | 25 ++++ .../source/set_mitm/setsys_mitm_service.cpp | 135 ++++++++++++++++++ .../source/set_mitm/setsys_mitm_service.hpp | 16 ++- .../ams_mitm/source/set_mitm/setsys_shim.c | 30 ++++ .../ams_mitm/source/set_mitm/setsys_shim.h | 20 +++ .../source/set_mitm/settings_sd_kvs.cpp | 5 + 7 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 stratosphere/ams_mitm/source/set_mitm/setsys_shim.c create mode 100644 stratosphere/ams_mitm/source/set_mitm/setsys_shim.h diff --git a/config_templates/system_settings.ini b/config_templates/system_settings.ini index e16fbcd2f..f9c7f61bb 100644 --- a/config_templates/system_settings.ini +++ b/config_templates/system_settings.ini @@ -67,6 +67,10 @@ ; Note that this setting is ignored (and treated as 1) when htc is enabled. ; 0 = Disabled, 1 = Enabled ; enable_log_manager = u8!0x0 +; Controls whether the bluetooth pairing database is redirected to the SD card (shared across sysmmc/all emummcs) +; NOTE: On <13.0.0, the database size was 10 instead of 20; booting pre-13.0.0 will truncate the database. +; 0 = Disabled, 1 = Enabled +; enable_external_bluetooth_db = u8!0x0 [hbloader] ; Controls the size of the homebrew heap when running as applet. ; If set to zero, all available applet memory is used as heap. diff --git a/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp b/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp index 8d6f3755a..a0996f21c 100644 --- a/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp +++ b/libraries/libstratosphere/include/stratosphere/settings/settings_types.hpp @@ -235,4 +235,29 @@ namespace ams::settings { return !(lhs <= rhs); } + struct BluetoothDevicesSettings : public sf::LargeData { + u8 address[0x6]; + char name[0x20]; + u8 class_of_device[0x3]; + u8 link_key[0x10]; + u8 link_key_present; + u16 version; + u32 trusted_services; + u16 vid; + u16 pid; + u8 sub_class; + u8 attribute_mask; + u16 descriptor_length; + u8 descriptor[0x80]; + u8 key_type; + u8 device_type; + u16 brr_size; + u8 brr[0x9]; + u8 reserved0; + char name2[0xF9]; + u8 reserved1[0x31]; + }; + + static_assert(sizeof(BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + } 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 eb504b3c9..fe07512ad 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.cpp @@ -16,6 +16,7 @@ #include #include "setsys_mitm_service.hpp" #include "settings_sd_kvs.hpp" +#include "setsys_shim.h" namespace ams::mitm::settings { @@ -23,6 +24,8 @@ namespace ams::mitm::settings { namespace { + constexpr const char ExternalBluetoothDatabasePath[] = "@Sdcard:/atmosphere/bluetooth_devices.db"; + constinit os::SdkMutex g_firmware_version_lock; constinit bool g_cached_firmware_version; constinit settings::FirmwareVersion g_firmware_version; @@ -87,6 +90,97 @@ namespace ams::mitm::settings { R_SUCCEED(); } + bool ExternalBluetoothDatabaseEnabled() { + u8 en = 0; + settings::fwdbg::GetSettingsItemValue(std::addressof(en), sizeof(en), "atmosphere", "enable_external_bluetooth_db"); + return en; + } + + bool HasExternalBluetoothDatabase() { + bool file_exists; + R_ABORT_UNLESS(fs::HasFile(std::addressof(file_exists), ExternalBluetoothDatabasePath)); + return file_exists; + } + + Result ReadExternalBluetoothDatabase(s32 *entries_read, settings::BluetoothDevicesSettings *db, size_t db_max_size) { + /* Open external database file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), ExternalBluetoothDatabasePath, fs::OpenMode_Read)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Read number of database entries stored in external database. */ + size_t total_entries; + R_TRY(fs::ReadFile(file, 0, std::addressof(total_entries), sizeof(total_entries))); + + u64 db_offset = sizeof(total_entries); + if (total_entries > db_max_size) { + /* Pairings are stored from least to most recent. Add offset to skip the older entries that won't fit. */ + db_offset += (total_entries - db_max_size) * sizeof(settings::BluetoothDevicesSettings); + + /* Cap number of database entries read to size of database on this firmware. */ + total_entries = db_max_size; + } + + /* Read database entries. */ + R_TRY(fs::ReadFile(file, db_offset, db, total_entries * sizeof(settings::BluetoothDevicesSettings))); + + /* Convert to old database format if running on a firmware below 13.0.0. */ + if (hos::GetVersion() < hos::Version_13_0_0) { + for (size_t i = 0; i < total_entries; ++i) { + /* Copy as many chars from currently used name field as we can fit in the original one. */ + util::SNPrintf(db[i].name, sizeof(db[i].name), "%s", db[i].name2); + + /* Clear the current name field. */ + std::memset(db[i].name2, 0, sizeof(db[i].name2)); + } + } + + *entries_read = total_entries; + + R_SUCCEED(); + } + + Result StoreExternalBluetoothDatabase(const settings::BluetoothDevicesSettings *db, size_t total_entries) { + /* Open external database file. */ + fs::FileHandle file; + R_TRY(fs::OpenFile(std::addressof(file), ExternalBluetoothDatabasePath, fs::OpenMode_Write)); + ON_SCOPE_EXIT { fs::CloseFile(file); }; + + /* Set file size to accomodate all database entries */ + R_TRY(fs::SetFileSize(file, sizeof(total_entries) + total_entries * sizeof(settings::BluetoothDevicesSettings))); + + /* Write number of database entries. */ + R_TRY(fs::WriteFile(file, 0, std::addressof(total_entries), sizeof(total_entries), fs::WriteOption::None)); + + /* Write database entries. */ + u64 db_offset = sizeof(total_entries); + if (hos::GetVersion() < hos::Version_13_0_0) { + /* Convert to new database format if running on a firmware below 13.0.0 */ + settings::BluetoothDevicesSettings tmp_entry; + for (size_t i = 0; i < total_entries; ++i) { + /* Take a copy of the current database entry. */ + std::memcpy(std::addressof(tmp_entry), std::addressof(db[i]), sizeof(settings::BluetoothDevicesSettings)); + + /* Copy the name field from the original location to the currently used one. */ + util::SNPrintf(tmp_entry.name2, sizeof(tmp_entry.name2), "%s", db[i].name); + + /* Clear the original name field. */ + std::memset(std::addressof(tmp_entry.name), 0, sizeof(tmp_entry.name)); + + /* Write the converted database entry. */ + R_TRY(fs::WriteFile(file, db_offset, &tmp_entry, sizeof(settings::BluetoothDevicesSettings), fs::WriteOption::None)); + + /* Increment offset to the next database entry. */ + db_offset += sizeof(settings::BluetoothDevicesSettings); + } + R_TRY(fs::FlushFile(file)); + } else { + R_TRY(fs::WriteFile(file, db_offset, db, total_entries * sizeof(settings::BluetoothDevicesSettings), fs::WriteOption::Flush)); + } + + R_SUCCEED(); + } + } Result SetSysMitmService::GetFirmwareVersion(sf::Out out) { @@ -102,6 +196,47 @@ namespace ams::mitm::settings { R_RETURN(GetFirmwareVersionImpl(out.GetPointer(), m_client_info)); } + Result SetSysMitmService::SetBluetoothDevicesSettings(const sf::InMapAliasArray &settings) { + /* Forward to session unless external database setting enabled. */ + R_UNLESS(ExternalBluetoothDatabaseEnabled(), sm::mitm::ResultShouldForwardToSession()); + + /* Create the external database if it doesn't exist. */ + if (!HasExternalBluetoothDatabase()) { + R_TRY(fs::CreateFile(ExternalBluetoothDatabasePath, 0)); + } + + /* Backup local database to sd card. */ + R_TRY(StoreExternalBluetoothDatabase(settings.GetPointer(), settings.GetSize())); + + /* Also allow the updated database to be stored to system save as usual. */ + static_assert(sizeof(settings::BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + R_TRY(setsysSetBluetoothDevicesSettingsFwd(m_forward_service.get(), reinterpret_cast(settings.GetPointer()), settings.GetSize())); + + R_SUCCEED(); + } + + Result SetSysMitmService::GetBluetoothDevicesSettings(sf::Out out_count, const sf::OutMapAliasArray &out) { + /* Forward to session unless external database setting enabled. */ + R_UNLESS(ExternalBluetoothDatabaseEnabled(), sm::mitm::ResultShouldForwardToSession()); + + if (!HasExternalBluetoothDatabase()) { + /* Forward to the real command to fetch database stored in system save. */ + static_assert(sizeof(settings::BluetoothDevicesSettings) == sizeof(::SetSysBluetoothDevicesSettings)); + R_TRY(setsysGetBluetoothDevicesSettingsFwd(m_forward_service.get(), out_count.GetPointer(), reinterpret_cast<::SetSysBluetoothDevicesSettings *>(out.GetPointer()), out.GetSize())); + + /* Create the external database file. */ + R_TRY(fs::CreateFile(ExternalBluetoothDatabasePath, 0)); + + /* Backup local database to sd card. */ + R_TRY(StoreExternalBluetoothDatabase(out.GetPointer(), out_count.GetValue())); + } else { + /* Read the external database from sd card. */ + R_TRY(ReadExternalBluetoothDatabase(out_count.GetPointer(), out.GetPointer(), out.GetSize())); + } + + R_SUCCEED(); + } + 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) 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 5779438bb..f61a0bf28 100644 --- a/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_mitm_service.hpp @@ -16,12 +16,14 @@ #pragma once #include -#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::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)) +#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, 11, Result, SetBluetoothDevicesSettings, (const sf::InMapAliasArray &settings), (settings)) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, GetBluetoothDevicesSettings, (sf::Out out_count, const sf::OutMapAliasArray &out), (out_count, out)) \ + 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, 0x0E82ED13) @@ -41,6 +43,8 @@ namespace ams::mitm::settings { public: Result GetFirmwareVersion(sf::Out out); Result GetFirmwareVersion2(sf::Out out); + Result SetBluetoothDevicesSettings(const sf::InMapAliasArray &settings); + Result GetBluetoothDevicesSettings(sf::Out out_count, const sf::OutMapAliasArray &out); 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); diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c new file mode 100644 index 000000000..ff0244f6b --- /dev/null +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c @@ -0,0 +1,30 @@ +/* + * 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 "setsys_shim.h" + +Result setsysSetBluetoothDevicesSettingsFwd(Service *s, const SetSysBluetoothDevicesSettings *settings, s32 count) { + return serviceDispatch(s, 11, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_In }, + .buffers = { { settings, count * sizeof(SetSysBluetoothDevicesSettings) } }, + ); +} + +Result setsysGetBluetoothDevicesSettingsFwd(Service *s, s32 *total_out, SetSysBluetoothDevicesSettings *settings, s32 count) { + return serviceDispatchOut(s, 12, *total_out, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { settings, count * sizeof(SetSysBluetoothDevicesSettings) } }, + ); +} diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h new file mode 100644 index 000000000..fab5e591d --- /dev/null +++ b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.h @@ -0,0 +1,20 @@ +/** + * @file setsys_shim.h + * @brief Settings Services (fs) IPC wrapper for setsys.mitm. + * @author ndeadly + * @copyright libnx Authors + */ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forwarding shims. */ +Result setsysSetBluetoothDevicesSettingsFwd(Service *s, const SetSysBluetoothDevicesSettings *settings, s32 count); +Result setsysGetBluetoothDevicesSettingsFwd(Service *s, s32 *total_out, SetSysBluetoothDevicesSettings *settings, s32 count); + +#ifdef __cplusplus +} +#endif \ No newline at end of file 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 1d0d9ce10..bdb25a8e0 100644 --- a/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp +++ b/stratosphere/ams_mitm/source/set_mitm/settings_sd_kvs.cpp @@ -393,6 +393,11 @@ namespace ams::settings::fwdbg { /* 0 = Disabled, 1 = Enabled */ R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_log_manager", "u8!0x0")); + /* Controls whether the bluetooth pairing database is redirected to the SD card (shared across sysmmc/all emummcs) */ + /* NOTE: On <13.0.0, the database size was 10 instead of 20; booting pre-13.0.0 will truncate the database. */ + /* 0 = Disabled, 1 = Enabled */ + R_ABORT_UNLESS(ParseSettingsItemValue("atmosphere", "enable_external_bluetooth_db", "u8!0x0")); + /* Hbloader custom settings. */ /* Controls the size of the homebrew heap when running as applet. */