From 4117c13377b51b83ff87b1d00393be1a5ab5bfff Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Mon, 29 Jan 2024 18:45:40 -0300
Subject: [PATCH] Migrate friends service to new IPC (#6174)

* Migrate friends service to new IPC

* Add a note that the pointer buffer size and domain counts are wrong

* Wrong length

* Format whitespace

* PR feedback

* Fill in structs from PR feedback

* Missed that one

* Somehow forgot to save that one

* Fill in enums from PR review

* Language enum, NotificationTime

* Format whitespace

* Fix the warning
---
 src/Ryujinx.HLE/HOS/Horizon.cs                |    2 +-
 .../Services/Account/Acc/AccountManager.cs    |   13 +-
 .../HOS/Services/Friend/IServiceCreator.cs    |   55 -
 .../HOS/Services/Friend/ResultCode.cs         |   14 -
 .../FriendService/Types/Friend.cs             |   29 -
 .../FriendService/Types/FriendFilter.cs       |   24 -
 .../FriendService/Types/UserPresence.cs       |   34 -
 .../IDaemonSuspendSessionService.cs           |   14 -
 .../Friend/ServiceCreator/IFriendService.cs   |  374 ------
 .../ServiceCreator/INotificationService.cs    |  178 ---
 .../NotificationEventHandler.cs               |   74 --
 .../Types/NotificationInfo.cs                 |   13 -
 .../Friends/FriendsIpcServer.cs               |   49 +
 src/Ryujinx.Horizon/Friends/FriendsMain.cs    |   17 +
 .../Friends/FriendsPortIndex.cs               |   11 +
 .../Friends/FriendsServerManager.cs           |   36 +
 src/Ryujinx.Horizon/HorizonOptions.cs         |    5 +-
 .../Sdk/Account/IEmulatorAccountManager.cs    |    8 +
 .../Sdk/Account/NetworkServiceAccountId.cs    |   20 +
 src/Ryujinx.Horizon/Sdk/Account/Nickname.cs   |   29 +
 src/Ryujinx.Horizon/Sdk/Account/Uid.cs        |   16 +-
 .../Sdk/Friends/ApplicationInfo.cs            |   12 +
 .../Sdk/Friends/Detail/BlockedUserImpl.cs     |    8 +
 .../Sdk/Friends/Detail/FriendCandidateImpl.cs |    8 +
 .../Friends/Detail/FriendDetailedInfoImpl.cs  |    9 +
 .../Sdk/Friends/Detail/FriendImpl.cs          |   19 +
 .../Detail/FriendInvitationForViewerImpl.cs   |    8 +
 .../Detail/FriendInvitationGroupImpl.cs       |    9 +
 .../Sdk/Friends/Detail/FriendRequestImpl.cs   |    8 +
 .../Sdk/Friends/Detail/FriendSettingImpl.cs   |    9 +
 .../Detail/Ipc/DaemonSuspendSessionService.cs |    7 +
 .../Sdk/Friends/Detail/Ipc/FriendService.cs   | 1015 +++++++++++++++++
 .../Ipc/FriendsServicePermissionLevel.cs}     |    9 +-
 .../Ipc/IDaemonSuspendSessionService.cs       |    9 +
 .../Sdk/Friends/Detail/Ipc/IFriendService.cs  |   97 ++
 .../Detail/Ipc/INotificationService.cs        |   12 +
 .../Sdk/Friends/Detail/Ipc/IServiceCreator.cs |   13 +
 .../Detail/Ipc/NotificationEventHandler.cs    |   58 +
 .../Detail/Ipc}/NotificationEventType.cs      |    2 +-
 .../Friends/Detail/Ipc/NotificationService.cs |  172 +++
 .../Detail/Ipc}/PresenceStatusFilter.cs       |    2 +-
 .../Sdk/Friends/Detail/Ipc/ServiceCreator.cs  |   51 +
 .../Friends/Detail/Ipc/SizedFriendFilter.cs   |   25 +
 .../Detail/Ipc/SizedNotificationInfo.cs       |   13 +
 .../Detail/NintendoNetworkIdFriendImpl.cs     |    8 +
 .../Sdk/Friends/Detail/PlayHistoryImpl.cs     |    8 +
 .../Sdk/Friends/Detail}/PresenceStatus.cs     |    2 +-
 .../Sdk/Friends/Detail/ProfileExtraImpl.cs    |    9 +
 .../Sdk/Friends/Detail/ProfileImpl.cs         |    8 +
 .../Friends/Detail/SnsAccountFriendImpl.cs    |    8 +
 .../Sdk/Friends/Detail/UserPresenceImpl.cs    |   29 +
 .../Friends/Detail/UserPresenceViewImpl.cs    |    9 +
 .../Sdk/Friends/Detail/UserSettingImpl.cs     |    9 +
 .../Sdk/Friends/ExternalApplicationCatalog.cs |    9 +
 .../Friends/ExternalApplicationCatalogId.cs   |    9 +
 .../FacedFriendRequestRegistrationKey.cs      |    9 +
 src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs |    9 +
 .../FriendInvitationGameModeDescription.cs    |    9 +
 .../Sdk/Friends/FriendInvitationGroupId.cs    |    9 +
 .../Sdk/Friends/FriendInvitationId.cs         |    8 +
 .../Sdk/Friends/FriendResult.cs               |   13 +
 .../Sdk/Friends/InAppScreenName.cs            |   26 +
 .../Sdk/Friends/MiiImageUrlParam.cs           |    9 +
 src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs    |    9 +
 .../Sdk/Friends/NintendoNetworkIdUserInfo.cs  |    9 +
 .../Friends}/PlayHistoryRegistrationKey.cs    |    8 +-
 .../Sdk/Friends/PlayHistoryStatistics.cs      |    9 +
 .../Sdk/Friends/Relationship.cs               |    9 +
 src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs  |    9 +
 .../Sdk/Friends/SnsAccountLinkage.cs          |    9 +
 .../Sdk/Friends/SnsAccountProfile.cs          |    9 +
 src/Ryujinx.Horizon/Sdk/Friends/Url.cs        |   30 +
 src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs |    9 +
 .../Sdk/Settings/BatteryLot.cs                |    9 +
 .../Settings/Factory/AccelerometerOffset.cs   |   12 +
 .../Settings/Factory/AccelerometerScale.cs    |   12 +
 .../Factory/AmiiboEcdsaCertificate.cs         |    9 +
 .../Factory/AmiiboEcqvBlsCertificate.cs       |    9 +
 .../Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs  |    9 +
 .../Factory/AmiiboEcqvBlsRootCertificate.cs   |    9 +
 .../Settings/Factory/AmiiboEcqvCertificate.cs |    9 +
 .../Sdk/Settings/Factory/AmiiboKey.cs         |    9 +
 .../Factory/AnalogStickFactoryCalibration.cs  |    9 +
 .../Factory/AnalogStickModelParameter.cs      |    9 +
 .../Sdk/Settings/Factory/BdAddress.cs         |    9 +
 .../Sdk/Settings/Factory/ConfigurationId1.cs  |    9 +
 .../ConsoleSixAxisSensorHorizontalOffset.cs   |   12 +
 .../Sdk/Settings/Factory/CountryCode.cs       |    8 +
 .../Factory/EccB233DeviceCertificate.cs       |    9 +
 .../Sdk/Settings/Factory/EccB233DeviceKey.cs  |    9 +
 .../Settings/Factory/GameCardCertificate.cs   |    9 +
 .../Sdk/Settings/Factory/GameCardKey.cs       |    9 +
 .../Sdk/Settings/Factory/GyroscopeOffset.cs   |   12 +
 .../Sdk/Settings/Factory/GyroscopeScale.cs    |   12 +
 .../Sdk/Settings/Factory/MacAddress.cs        |    9 +
 .../Factory/Rsa2048DeviceCertificate.cs       |    9 +
 .../Sdk/Settings/Factory/Rsa2048DeviceKey.cs  |    9 +
 .../Sdk/Settings/Factory/SerialNumber.cs      |    9 +
 .../Sdk/Settings/Factory/SpeakerParameter.cs  |   32 +
 .../Sdk/Settings/Factory/SslCertificate.cs    |    9 +
 .../Sdk/Settings/Factory/SslKey.cs            |    9 +
 src/Ryujinx.Horizon/Sdk/Settings/Language.cs  |   24 +
 .../Sdk/Settings/LanguageCode.cs              |   63 +
 .../Sdk/Settings/SettingsItemKey.cs           |    9 +
 .../Sdk/Settings/SettingsName.cs              |    9 +
 .../System/AccountNotificationSettings.cs     |   15 +
 .../System/AccountOnlineStorageSettings.cs    |    6 +
 .../Sdk/Settings/System/AccountSettings.cs    |    9 +
 .../Sdk/Settings/System/AllowedSslHost.cs     |    9 +
 .../System/AnalogStickUserCalibration.cs      |    9 +
 .../Sdk/Settings/System/AppletLaunchFlag.cs   |    9 +
 .../Sdk/Settings/System/AudioVolume.cs        |    9 +
 .../Sdk/Settings/System/BacklightSettings.cs  |   22 +
 .../Settings/System/BacklightSettingsEx.cs    |    9 +
 .../Sdk/Settings/System/BlePairingSettings.cs |    6 +
 .../System/BluetoothDevicesSettings.cs        |   29 +
 .../System/ButtonConfigRegisteredSettings.cs  |    9 +
 .../Settings/System/ButtonConfigSettings.cs   |    9 +
 .../ConsoleSixAxisSensorAccelerationBias.cs   |    9 +
 .../ConsoleSixAxisSensorAccelerationGain.cs   |    9 +
 ...ConsoleSixAxisSensorAngularAcceleration.cs |    9 +
 ...ConsoleSixAxisSensorAngularVelocityBias.cs |    9 +
 ...ConsoleSixAxisSensorAngularVelocityGain.cs |    9 +
 ...oleSixAxisSensorAngularVelocityTimeBias.cs |    9 +
 .../Settings/System/DataDeletionSettings.cs   |   18 +
 .../Sdk/Settings/System/DeviceNickName.cs     |   25 +
 .../Sdk/Settings/System/Edid.cs               |    9 +
 .../Sdk/Settings/System/EulaVersion.cs        |    6 +
 .../Sdk/Settings/System/FatalDirtyFlag.cs     |    9 +
 .../Sdk/Settings/System/FirmwareVersion.cs    |    9 +
 .../Settings/System/FirmwareVersionDigest.cs  |    9 +
 .../Sdk/Settings/System/HomeMenuScheme.cs     |   14 +
 .../Sdk/Settings/System/HostFsMountPoint.cs   |    9 +
 .../Settings/System/InitialLaunchSettings.cs  |   14 +
 .../Sdk/Settings/System/NetworkSettings.cs    |    6 +
 .../Settings/System/NotificationSettings.cs   |   38 +
 .../System/NxControllerLegacySettings.cs      |    9 +
 .../Settings/System/NxControllerSettings.cs   |    9 +
 .../Settings/System/PtmFuelGaugeParameter.cs  |   20 +
 .../System/RebootlessSystemUpdateVersion.cs   |    9 +
 .../Sdk/Settings/System/SerialNumber.cs       |    9 +
 .../System/ServiceDiscoveryControlSettings.cs |   10 +
 .../Sdk/Settings/System/SleepSettings.cs      |   40 +
 .../Sdk/Settings/System/TelemetryDirtyFlag.cs |    9 +
 .../Sdk/Settings/System/ThemeId.cs            |    9 +
 .../Sdk/Settings/System/ThemeSettings.cs      |    9 +
 .../Sdk/Settings/System/TvSettings.cs         |   59 +
 src/Ryujinx.Horizon/ServiceTable.cs           |    2 +
 148 files changed, 3026 insertions(+), 832 deletions(-)
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs
 delete mode 100644 src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs
 create mode 100644 src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
 create mode 100644 src/Ryujinx.Horizon/Friends/FriendsMain.cs
 create mode 100644 src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs
 create mode 100644 src/Ryujinx.Horizon/Friends/FriendsServerManager.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Account/Nickname.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs
 rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs} (64%)
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs
 rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc}/NotificationEventType.cs (64%)
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs
 rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types => Ryujinx.Horizon/Sdk/Friends/Detail/Ipc}/PresenceStatusFilter.cs (64%)
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs
 rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types => Ryujinx.Horizon/Sdk/Friends/Detail}/PresenceStatus.cs (58%)
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs
 rename src/{Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types => Ryujinx.Horizon/Sdk/Friends}/PlayHistoryRegistrationKey.cs (59%)
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/Url.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/Language.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs
 create mode 100644 src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs

diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 1a402240fb..cd1719580a 100644
--- a/src/Ryujinx.HLE/HOS/Horizon.cs
+++ b/src/Ryujinx.HLE/HOS/Horizon.cs
@@ -330,7 +330,7 @@ namespace Ryujinx.HLE.HOS
             HorizonFsClient fsClient = new(this);
 
             ServiceTable = new ServiceTable();
-            var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient));
+            var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager));
 
             foreach (var service in services)
             {
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
index 924ac3fb97..c724660eae 100644
--- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs
@@ -4,6 +4,7 @@ using LibHac.Fs;
 using LibHac.Fs.Shim;
 using Ryujinx.Common;
 using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Sdk.Account;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -11,7 +12,7 @@ using System.Linq;
 
 namespace Ryujinx.HLE.HOS.Services.Account.Acc
 {
-    public class AccountManager
+    public class AccountManager : IEmulatorAccountManager
     {
         public static readonly UserId DefaultUserId = new("00000000000000010000000000000000");
 
@@ -106,6 +107,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             _accountSaveDataManager.Save(_profiles);
         }
 
+        public void OpenUserOnlinePlay(Uid userId)
+        {
+            OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
+        }
+
         public void OpenUserOnlinePlay(UserId userId)
         {
             if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
@@ -127,6 +133,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
             _accountSaveDataManager.Save(_profiles);
         }
 
+        public void CloseUserOnlinePlay(Uid userId)
+        {
+            CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
+        }
+
         public void CloseUserOnlinePlay(UserId userId)
         {
             if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
deleted file mode 100644
index 3f15f3fc68..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator;
-
-namespace Ryujinx.HLE.HOS.Services.Friend
-{
-    [Service("friend:a", FriendServicePermissionLevel.Administrator)]
-    [Service("friend:m", FriendServicePermissionLevel.Manager)]
-    [Service("friend:s", FriendServicePermissionLevel.System)]
-    [Service("friend:u", FriendServicePermissionLevel.User)]
-    [Service("friend:v", FriendServicePermissionLevel.Viewer)]
-    class IServiceCreator : IpcService
-    {
-        private readonly FriendServicePermissionLevel _permissionLevel;
-
-        public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel)
-        {
-            _permissionLevel = permissionLevel;
-        }
-
-        [CommandCmif(0)]
-        // CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService>
-        public ResultCode CreateFriendService(ServiceCtx context)
-        {
-            MakeObject(context, new IFriendService(_permissionLevel));
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)] // 2.0.0+
-        // CreateNotificationService(nn::account::Uid userId) -> object<nn::friends::detail::ipc::INotificationService>
-        public ResultCode CreateNotificationService(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            MakeObject(context, new INotificationService(context, userId, _permissionLevel));
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(2)] // 4.0.0+
-        // CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService>
-        public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context)
-        {
-            MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel));
-
-            return ResultCode.Success;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs
deleted file mode 100644
index 9f612059c1..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Friend
-{
-    enum ResultCode
-    {
-        ModuleId = 121,
-        ErrorCodeShift = 9,
-
-        Success = 0,
-
-        InvalidArgument = (2 << ErrorCodeShift) | ModuleId,
-        InternetRequestDenied = (6 << ErrorCodeShift) | ModuleId,
-        NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId,
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs
deleted file mode 100644
index 28745c3f22..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
-{
-    [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)]
-    struct Friend
-    {
-        public UserId UserId;
-        public long NetworkUserId;
-
-        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)]
-        public string Nickname;
-
-        public UserPresence presence;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsFavourite;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsNew;
-
-        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)]
-        readonly char[] Unknown;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsValid;
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs
deleted file mode 100644
index 5f13f31369..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
-{
-    [StructLayout(LayoutKind.Sequential)]
-    struct FriendFilter
-    {
-        public PresenceStatusFilter PresenceStatus;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsFavoriteOnly;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsSameAppPresenceOnly;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsSameAppPlayedOnly;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool IsArbitraryAppPlayedOnly;
-
-        public long PresenceGroupId;
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs
deleted file mode 100644
index 80d1420596..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Ryujinx.Common.Memory;
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
-{
-    [StructLayout(LayoutKind.Sequential, Pack = 0x8)]
-    struct UserPresence
-    {
-        public UserId UserId;
-        public long LastTimeOnlineTimestamp;
-        public PresenceStatus Status;
-
-        [MarshalAs(UnmanagedType.I1)]
-        public bool SamePresenceGroupApplication;
-
-        public Array3<byte> Unknown;
-        private AppKeyValueStorageHolder _appKeyValueStorage;
-
-        public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
-
-        [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
-        private struct AppKeyValueStorageHolder
-        {
-            public const int Size = 0xC0;
-        }
-
-        public readonly override string ToString()
-        {
-            return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}";
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs
deleted file mode 100644
index 3b1601abb0..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
-{
-    class IDaemonSuspendSessionService : IpcService
-    {
-#pragma warning disable IDE0052 // Remove unread private member
-        private readonly FriendServicePermissionLevel _permissionLevel;
-#pragma warning restore IDE0052
-
-        public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
-        {
-            _permissionLevel = permissionLevel;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
deleted file mode 100644
index 54d23e88ce..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs
+++ /dev/null
@@ -1,374 +0,0 @@
-using LibHac.Ns;
-using Ryujinx.Common;
-using Ryujinx.Common.Logging;
-using Ryujinx.Common.Memory;
-using Ryujinx.Common.Utilities;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService;
-using Ryujinx.Horizon.Common;
-using System;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
-{
-    class IFriendService : IpcService
-    {
-#pragma warning disable IDE0052 // Remove unread private member
-        private readonly FriendServicePermissionLevel _permissionLevel;
-#pragma warning restore IDE0052
-        private KEvent _completionEvent;
-
-        public IFriendService(FriendServicePermissionLevel permissionLevel)
-        {
-            _permissionLevel = permissionLevel;
-        }
-
-        [CommandCmif(0)]
-        // GetCompletionEvent() -> handle<copy>
-        public ResultCode GetCompletionEvent(ServiceCtx context)
-        {
-            _completionEvent ??= new KEvent(context.Device.System.KernelContext);
-
-            if (context.Process.HandleTable.GenerateHandle(_completionEvent.ReadableEvent, out int completionEventHandle) != Result.Success)
-            {
-                throw new InvalidOperationException("Out of handles!");
-            }
-
-            _completionEvent.WritableEvent.Signal();
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)]
-        // nn::friends::Cancel()
-        public ResultCode Cancel(ServiceCtx context)
-        {
-            // TODO: Original service sets an internal field to 1 here. Determine usage.
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10100)]
-        // nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
-        // -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
-        public ResultCode GetFriendListIds(ServiceCtx context)
-        {
-            int offset = context.RequestData.ReadInt32();
-
-            // Padding
-            context.RequestData.ReadInt32();
-
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-            FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
-
-            // Pid placeholder
-            context.RequestData.ReadInt64();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
-            context.ResponseData.Write(0);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
-            {
-                UserId = userId.ToString(),
-                offset,
-                filter.PresenceStatus,
-                filter.IsFavoriteOnly,
-                filter.IsSameAppPresenceOnly,
-                filter.IsSameAppPlayedOnly,
-                filter.IsArbitraryAppPlayedOnly,
-                filter.PresenceGroupId,
-            });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10101)]
-        // nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
-        // -> int outCount, array<nn::friends::detail::FriendImpl, 0x6>
-        public ResultCode GetFriendList(ServiceCtx context)
-        {
-            int offset = context.RequestData.ReadInt32();
-
-            // Padding
-            context.RequestData.ReadInt32();
-
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-            FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
-
-            // Pid placeholder
-            context.RequestData.ReadInt64();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
-            context.ResponseData.Write(0);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
-            {
-                UserId = userId.ToString(),
-                offset,
-                filter.PresenceStatus,
-                filter.IsFavoriteOnly,
-                filter.IsSameAppPresenceOnly,
-                filter.IsSameAppPlayedOnly,
-                filter.IsArbitraryAppPlayedOnly,
-                filter.PresenceGroupId,
-            });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10120)] // 10.0.0+
-        // nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool
-        public ResultCode IsFriendListCacheAvailable(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise.
-            // NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check.
-            context.ResponseData.Write(true);
-
-            // TODO: Since we don't support friend features, it's fine to stub it for now.
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10121)] // 10.0.0+
-        // nn::friends::EnsureFriendListAvailable(nn::account::Uid userId)
-        public ResultCode EnsureFriendListAvailable(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id.
-            //       Since we don't support friend features, it's fine to stub it for now.
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10400)]
-        // nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>)
-        public ResultCode GetBlockedUserListIds(ServiceCtx context)
-        {
-            int offset = context.RequestData.ReadInt32();
-
-            // Padding
-            context.RequestData.ReadInt32();
-
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            // There are no friends blocked, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
-            context.ResponseData.Write(0);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { offset, UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10420)]
-        // nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool
-        public ResultCode CheckBlockedUserListAvailability(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            // Yes, it is available.
-            context.ResponseData.Write(true);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10600)]
-        // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
-        public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            context.Device.System.AccountManager.OpenUserOnlinePlay(userId);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10601)]
-        // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId)
-        public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            context.Device.System.AccountManager.CloseUserOnlinePlay(userId);
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10610)]
-        // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
-        public ResultCode UpdateUserPresence(ServiceCtx context)
-        {
-            UserId uuid = context.RequestData.ReadStruct<UserId>();
-
-            // Pid placeholder
-            context.RequestData.ReadInt64();
-
-            ulong position = context.Request.PtrBuff[0].Position;
-            ulong size = context.Request.PtrBuff[0].Size;
-
-            ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
-
-            if (uuid.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10700)]
-        // nn::friends::GetPlayHistoryRegistrationKey(b8 unknown, nn::account::Uid) -> buffer<nn::friends::PlayHistoryRegistrationKey, 0x1a>
-        public ResultCode GetPlayHistoryRegistrationKey(ServiceCtx context)
-        {
-            bool unknownBool = context.RequestData.ReadBoolean();
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x40UL);
-
-            ulong bufferPosition = context.Request.RecvListBuff[0].Position;
-
-            if (userId.IsNull)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance.
-
-            byte[] randomBytes = new byte[8];
-
-            Random.Shared.NextBytes(randomBytes);
-
-            // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance.
-            //       Then call nn::friends::detail::service::core::AccountStorageManager::GetInstance and store the instance.
-            //       Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid.
-            //       And store it in the savedata 8000000000000080 in the friends:/uid.bin file.
-
-            Array16<byte> randomGuid = new();
-
-            Guid.NewGuid().ToByteArray().AsSpan().CopyTo(randomGuid.AsSpan());
-
-            PlayHistoryRegistrationKey playHistoryRegistrationKey = new()
-            {
-                Type = 0x101,
-                KeyIndex = (byte)(randomBytes[0] & 7),
-                UserIdBool = 0, // TODO: Find it.
-                UnknownBool = (byte)(unknownBool ? 1 : 0), // TODO: Find it.
-                Reserved = new Array11<byte>(),
-                Uuid = randomGuid,
-            };
-
-            ReadOnlySpan<byte> playHistoryRegistrationKeyBuffer = SpanHelpers.AsByteSpan(ref playHistoryRegistrationKey);
-
-            /*
-
-            NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer).
-                  We currently don't support play history and online services so we can use a blank key for now.
-                  Code for reference:
-
-            byte[] hmacKey = new byte[0x20];
-
-            HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey);
-            byte[]     hmacHash   = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer);
-
-            */
-
-            context.Memory.Write(bufferPosition, playHistoryRegistrationKeyBuffer);
-            context.Memory.Write(bufferPosition + 0x20, new byte[0x20]); // HmacHash
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(10702)]
-        // nn::friends::AddPlayHistory(nn::account::Uid, u64, pid, buffer<nn::friends::PlayHistoryRegistrationKey, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>)
-        public ResultCode AddPlayHistory(ServiceCtx context)
-        {
-            UserId userId = context.RequestData.ReadStruct<UserId>();
-
-            // Pid placeholder
-            context.RequestData.ReadInt64();
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            ulong pid = context.Request.HandleDesc.PId;
-
-            ulong playHistoryRegistrationKeyPosition = context.Request.PtrBuff[0].Position;
-            ulong playHistoryRegistrationKeySize = context.Request.PtrBuff[0].Size;
-
-            ulong inAppScreenName1Position = context.Request.PtrBuff[1].Position;
-#pragma warning restore IDE0059
-            ulong inAppScreenName1Size = context.Request.PtrBuff[1].Size;
-
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            ulong inAppScreenName2Position = context.Request.PtrBuff[2].Position;
-#pragma warning restore IDE0059
-            ulong inAppScreenName2Size = context.Request.PtrBuff[2].Size;
-
-            if (userId.IsNull || inAppScreenName1Size > 0x48 || inAppScreenName2Size > 0x48)
-            {
-                return ResultCode.InvalidArgument;
-            }
-
-            // TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
-#pragma warning disable IDE0059 // Remove unnecessary value assignment
-            ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
-#pragma warning restore IDE0059
-
-            /*
-
-            NOTE: The service calls nn::friends::detail::service::core::PlayHistoryManager to store informations using the registration key computed in GetPlayHistoryRegistrationKey.
-                  Then calls nn::friends::detail::service::core::FriendListManager to update informations on the friend list.
-                  We currently don't support play history and online services so it's fine to do nothing.
-
-            */
-
-            Logger.Stub?.PrintStub(LogClass.ServiceFriend);
-
-            return ResultCode.Success;
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
deleted file mode 100644
index 8fc7a46091..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs
+++ /dev/null
@@ -1,178 +0,0 @@
-using Ryujinx.Common;
-using Ryujinx.HLE.HOS.Ipc;
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService;
-using Ryujinx.Horizon.Common;
-using System;
-using System.Collections.Generic;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
-{
-    class INotificationService : DisposableIpcService
-    {
-        private readonly UserId _userId;
-        private readonly FriendServicePermissionLevel _permissionLevel;
-
-        private readonly object _lock = new();
-
-        private readonly KEvent _notificationEvent;
-        private int _notificationEventHandle = 0;
-
-        private readonly LinkedList<NotificationInfo> _notifications;
-
-        private bool _hasNewFriendRequest;
-        private bool _hasFriendListUpdate;
-
-        public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel)
-        {
-            _userId = userId;
-            _permissionLevel = permissionLevel;
-            _notifications = new LinkedList<NotificationInfo>();
-            _notificationEvent = new KEvent(context.Device.System.KernelContext);
-
-            _hasNewFriendRequest = false;
-            _hasFriendListUpdate = false;
-
-            NotificationEventHandler.Instance.RegisterNotificationService(this);
-        }
-
-        [CommandCmif(0)] //2.0.0+
-        // nn::friends::detail::ipc::INotificationService::GetEvent() -> handle<copy>
-        public ResultCode GetEvent(ServiceCtx context)
-        {
-            if (_notificationEventHandle == 0)
-            {
-                if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != Result.Success)
-                {
-                    throw new InvalidOperationException("Out of handles!");
-                }
-            }
-
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle);
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(1)] //2.0.0+
-        // nn::friends::detail::ipc::INotificationService::Clear()
-        public ResultCode Clear(ServiceCtx context)
-        {
-            lock (_lock)
-            {
-                _hasNewFriendRequest = false;
-                _hasFriendListUpdate = false;
-
-                _notifications.Clear();
-            }
-
-            return ResultCode.Success;
-        }
-
-        [CommandCmif(2)] // 2.0.0+
-        // nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo
-        public ResultCode Pop(ServiceCtx context)
-        {
-            lock (_lock)
-            {
-                if (_notifications.Count >= 1)
-                {
-                    NotificationInfo notificationInfo = _notifications.First.Value;
-                    _notifications.RemoveFirst();
-
-                    if (notificationInfo.Type == NotificationEventType.FriendListUpdate)
-                    {
-                        _hasFriendListUpdate = false;
-                    }
-                    else if (notificationInfo.Type == NotificationEventType.NewFriendRequest)
-                    {
-                        _hasNewFriendRequest = false;
-                    }
-
-                    context.ResponseData.WriteStruct(notificationInfo);
-
-                    return ResultCode.Success;
-                }
-            }
-
-            return ResultCode.NotificationQueueEmpty;
-        }
-
-        public void SignalFriendListUpdate(UserId targetId)
-        {
-            lock (_lock)
-            {
-                if (_userId == targetId)
-                {
-                    if (!_hasFriendListUpdate)
-                    {
-                        NotificationInfo friendListNotification = new();
-
-                        if (_notifications.Count != 0)
-                        {
-                            friendListNotification = _notifications.First.Value;
-                            _notifications.RemoveFirst();
-                        }
-
-                        friendListNotification.Type = NotificationEventType.FriendListUpdate;
-                        _hasFriendListUpdate = true;
-
-                        if (_hasNewFriendRequest)
-                        {
-                            NotificationInfo newFriendRequestNotification = new();
-
-                            if (_notifications.Count != 0)
-                            {
-                                newFriendRequestNotification = _notifications.First.Value;
-                                _notifications.RemoveFirst();
-                            }
-
-                            newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest;
-                            _notifications.AddFirst(newFriendRequestNotification);
-                        }
-
-                        // We defer this to make sure we are on top of the queue.
-                        _notifications.AddFirst(friendListNotification);
-                    }
-
-                    _notificationEvent.ReadableEvent.Signal();
-                }
-            }
-        }
-
-        public void SignalNewFriendRequest(UserId targetId)
-        {
-            lock (_lock)
-            {
-                if ((_permissionLevel & FriendServicePermissionLevel.ViewerMask) != 0 && _userId == targetId)
-                {
-                    if (!_hasNewFriendRequest)
-                    {
-                        if (_notifications.Count == 100)
-                        {
-                            SignalFriendListUpdate(targetId);
-                        }
-
-                        NotificationInfo newFriendRequestNotification = new()
-                        {
-                            Type = NotificationEventType.NewFriendRequest,
-                        };
-
-                        _notifications.AddLast(newFriendRequestNotification);
-                        _hasNewFriendRequest = true;
-                    }
-
-                    _notificationEvent.ReadableEvent.Signal();
-                }
-            }
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            if (isDisposing)
-            {
-                NotificationEventHandler.Instance.UnregisterNotificationService(this);
-            }
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs
deleted file mode 100644
index 88627fd789..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs
+++ /dev/null
@@ -1,74 +0,0 @@
-using Ryujinx.HLE.HOS.Services.Account.Acc;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
-{
-    public sealed class NotificationEventHandler
-    {
-        private static NotificationEventHandler _instance;
-        private static readonly object _instanceLock = new();
-
-        private readonly INotificationService[] _registry;
-
-        public static NotificationEventHandler Instance
-        {
-            get
-            {
-                lock (_instanceLock)
-                {
-                    _instance ??= new NotificationEventHandler();
-
-                    return _instance;
-                }
-            }
-        }
-
-        NotificationEventHandler()
-        {
-            _registry = new INotificationService[0x20];
-        }
-
-        internal void RegisterNotificationService(INotificationService service)
-        {
-            // NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors.
-            for (int i = 0; i < _registry.Length; i++)
-            {
-                if (_registry[i] == null)
-                {
-                    _registry[i] = service;
-                    break;
-                }
-            }
-        }
-
-        internal void UnregisterNotificationService(INotificationService service)
-        {
-            // NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors.
-            for (int i = 0; i < _registry.Length; i++)
-            {
-                if (_registry[i] == service)
-                {
-                    _registry[i] = null;
-                    break;
-                }
-            }
-        }
-
-        // TODO: Use this when we will have enough things to go online.
-        public void SignalFriendListUpdate(UserId targetId)
-        {
-            for (int i = 0; i < _registry.Length; i++)
-            {
-                _registry[i]?.SignalFriendListUpdate(targetId);
-            }
-        }
-
-        // TODO: Use this when we will have enough things to go online.
-        public void SignalNewFriendRequest(UserId targetId)
-        {
-            for (int i = 0; i < _registry.Length; i++)
-            {
-                _registry[i]?.SignalNewFriendRequest(targetId);
-            }
-        }
-    }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs b/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs
deleted file mode 100644
index aa58433d87..0000000000
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Ryujinx.Common.Memory;
-using System.Runtime.InteropServices;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
-{
-    [StructLayout(LayoutKind.Sequential, Size = 0x10)]
-    struct NotificationInfo
-    {
-        public NotificationEventType Type;
-        private Array4<byte> _padding;
-        public long NetworkUserIdPlaceholder;
-    }
-}
diff --git a/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
new file mode 100644
index 0000000000..523c617a89
--- /dev/null
+++ b/src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
@@ -0,0 +1,49 @@
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+
+namespace Ryujinx.Horizon.Friends
+{
+    class FriendsIpcServer
+    {
+        private const int MaxSessionsCount = 8;
+        private const int TotalMaxSessionsCount = MaxSessionsCount * 5;
+
+        private const int PointerBufferSize = 0xA00;
+        private const int MaxDomains = 64;
+        private const int MaxDomainObjects = 16;
+        private const int MaxPortsCount = 5;
+
+        private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
+
+        private SmApi _sm;
+        private FriendsServerManager _serverManager;
+
+        public void Initialize()
+        {
+            HeapAllocator allocator = new();
+
+            _sm = new SmApi();
+            _sm.Initialize().AbortOnFailure();
+
+            _serverManager = new FriendsServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount);
+
+#pragma warning disable IDE0055 // Disable formatting
+            _serverManager.RegisterServer((int)FriendsPortIndex.Admin,   ServiceName.Encode("friend:a"), MaxSessionsCount);
+            _serverManager.RegisterServer((int)FriendsPortIndex.User,    ServiceName.Encode("friend:u"), MaxSessionsCount);
+            _serverManager.RegisterServer((int)FriendsPortIndex.Viewer,  ServiceName.Encode("friend:v"), MaxSessionsCount);
+            _serverManager.RegisterServer((int)FriendsPortIndex.Manager, ServiceName.Encode("friend:m"), MaxSessionsCount);
+            _serverManager.RegisterServer((int)FriendsPortIndex.System,  ServiceName.Encode("friend:s"), MaxSessionsCount);
+#pragma warning restore IDE0055
+        }
+
+        public void ServiceRequests()
+        {
+            _serverManager.ServiceRequests();
+        }
+
+        public void Shutdown()
+        {
+            _serverManager.Dispose();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Friends/FriendsMain.cs b/src/Ryujinx.Horizon/Friends/FriendsMain.cs
new file mode 100644
index 0000000000..0f119cf018
--- /dev/null
+++ b/src/Ryujinx.Horizon/Friends/FriendsMain.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Horizon.Friends
+{
+    class FriendsMain : IService
+    {
+        public static void Main(ServiceTable serviceTable)
+        {
+            FriendsIpcServer ipcServer = new();
+
+            ipcServer.Initialize();
+
+            serviceTable.SignalServiceReady();
+
+            ipcServer.ServiceRequests();
+            ipcServer.Shutdown();
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs
new file mode 100644
index 0000000000..f567db3020
--- /dev/null
+++ b/src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Horizon.Friends
+{
+    enum FriendsPortIndex
+    {
+        Admin,
+        User,
+        Viewer,
+        Manager,
+        System,
+    }
+}
diff --git a/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs
new file mode 100644
index 0000000000..5026206b79
--- /dev/null
+++ b/src/Ryujinx.Horizon/Friends/FriendsServerManager.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Friends.Detail.Ipc;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using Ryujinx.Horizon.Sdk.Sm;
+using System;
+
+namespace Ryujinx.Horizon.Friends
+{
+    class FriendsServerManager : ServerManager
+    {
+        private readonly IEmulatorAccountManager _accountManager;
+        private readonly NotificationEventHandler _notificationEventHandler;
+
+        public FriendsServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
+        {
+            _accountManager = HorizonStatic.Options.AccountManager;
+            _notificationEventHandler = new();
+        }
+
+        protected override Result OnNeedsToAccept(int portIndex, Server server)
+        {
+            return (FriendsPortIndex)portIndex switch
+            {
+#pragma warning disable IDE0055 // Disable formatting
+                FriendsPortIndex.Admin   => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Admin)),
+                FriendsPortIndex.User    => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.User)),
+                FriendsPortIndex.Viewer  => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Viewer)),
+                FriendsPortIndex.Manager => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Manager)),
+                FriendsPortIndex.System  => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.System)),
+                _                        => throw new ArgumentOutOfRangeException(nameof(portIndex)),
+#pragma warning restore IDE0055
+            };
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/HorizonOptions.cs b/src/Ryujinx.Horizon/HorizonOptions.cs
index e3c862da47..7946205692 100644
--- a/src/Ryujinx.Horizon/HorizonOptions.cs
+++ b/src/Ryujinx.Horizon/HorizonOptions.cs
@@ -1,4 +1,5 @@
 using LibHac;
+using Ryujinx.Horizon.Sdk.Account;
 using Ryujinx.Horizon.Sdk.Fs;
 
 namespace Ryujinx.Horizon
@@ -10,13 +11,15 @@ namespace Ryujinx.Horizon
 
         public HorizonClient BcatClient { get; }
         public IFsClient FsClient { get; }
+        public IEmulatorAccountManager AccountManager { get; }
 
-        public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient)
+        public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager)
         {
             IgnoreMissingServices = ignoreMissingServices;
             ThrowOnInvalidCommandIds = true;
             BcatClient = bcatClient;
             FsClient = fsClient;
+            AccountManager = accountManager;
         }
     }
 }
diff --git a/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs
new file mode 100644
index 0000000000..af02cc8eb5
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Horizon.Sdk.Account
+{
+    public interface IEmulatorAccountManager
+    {
+        void OpenUserOnlinePlay(Uid userId);
+        void CloseUserOnlinePlay(Uid userId);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs
new file mode 100644
index 0000000000..2512975e32
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs
@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Account
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
+    readonly record struct NetworkServiceAccountId
+    {
+        public readonly ulong Id;
+
+        public NetworkServiceAccountId(ulong id)
+        {
+            Id = id;
+        }
+
+        public override readonly string ToString()
+        {
+            return Id.ToString("x16");
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs
new file mode 100644
index 0000000000..1f351ee3a4
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Account/Nickname.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Account
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x21, Pack = 0x1)]
+    readonly struct Nickname
+    {
+        public readonly Array33<byte> Name;
+
+        public Nickname(in Array33<byte> name)
+        {
+            Name = name;
+        }
+
+        public override string ToString()
+        {
+            int length = ((ReadOnlySpan<byte>)Name.AsSpan()).IndexOf((byte)0);
+            if (length < 0)
+            {
+                length = 33;
+            }
+
+            return Encoding.UTF8.GetString(Name.AsSpan()[..length]);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
index a76e6d2564..ada2c02ba0 100644
--- a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
+++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs
@@ -6,16 +6,16 @@ using System.Runtime.InteropServices;
 namespace Ryujinx.Horizon.Sdk.Account
 {
     [StructLayout(LayoutKind.Sequential)]
-    readonly record struct Uid
+    public readonly record struct Uid
     {
-        public readonly long High;
-        public readonly long Low;
+        public readonly ulong High;
+        public readonly ulong Low;
 
         public bool IsNull => (Low | High) == 0;
 
         public static Uid Null => new(0, 0);
 
-        public Uid(long low, long high)
+        public Uid(ulong low, ulong high)
         {
             Low = low;
             High = high;
@@ -23,8 +23,8 @@ namespace Ryujinx.Horizon.Sdk.Account
 
         public Uid(byte[] bytes)
         {
-            High = BitConverter.ToInt64(bytes, 0);
-            Low = BitConverter.ToInt64(bytes, 8);
+            High = BitConverter.ToUInt64(bytes, 0);
+            Low = BitConverter.ToUInt64(bytes, 8);
         }
 
         public Uid(string hex)
@@ -34,8 +34,8 @@ namespace Ryujinx.Horizon.Sdk.Account
                 throw new ArgumentException("Invalid Hex value!", nameof(hex));
             }
 
-            Low = Convert.ToInt64(hex[16..], 16);
-            High = Convert.ToInt64(hex[..16], 16);
+            Low = Convert.ToUInt64(hex[16..], 16);
+            High = Convert.ToUInt64(hex[..16], 16);
         }
 
         public void Write(BinaryWriter binaryWriter)
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs
new file mode 100644
index 0000000000..23bad3d132
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Horizon.Sdk.Ncm;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct ApplicationInfo
+    {
+        public ApplicationId ApplicationId;
+        public ulong PresenceGroupId;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs
new file mode 100644
index 0000000000..d5f8a0313e
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct BlockedUserImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs
new file mode 100644
index 0000000000..21e99c754a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct FriendCandidateImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs
new file mode 100644
index 0000000000..1b46dccd56
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x800)]
+    struct FriendDetailedInfoImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs
new file mode 100644
index 0000000000..d22ca4b90a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Sdk.Account;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x200, Pack = 0x8)]
+    struct FriendImpl
+    {
+        public Uid UserId;
+        public NetworkServiceAccountId NetworkUserId;
+        public Nickname Nickname;
+        public UserPresenceImpl Presence;
+        public bool IsFavourite;
+        public bool IsNew;
+        public Array6<byte> Unknown;
+        public bool IsValid;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs
new file mode 100644
index 0000000000..416ba36559
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct FriendInvitationForViewerImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs
new file mode 100644
index 0000000000..ef92383473
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x1400)]
+    struct FriendInvitationGroupImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs
new file mode 100644
index 0000000000..ba56716922
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct FriendRequestImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs
new file mode 100644
index 0000000000..f711d31fdd
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40)]
+    struct FriendSettingImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs
new file mode 100644
index 0000000000..aaf88ed039
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    partial class DaemonSuspendSessionService : IDaemonSuspendSessionService
+    {
+        // NOTE: This service has no commands.
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs
new file mode 100644
index 0000000000..1b4c8c3098
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs
@@ -0,0 +1,1015 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Settings;
+using Ryujinx.Horizon.Sdk.Sf;
+using Ryujinx.Horizon.Sdk.Sf.Hipc;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    partial class FriendService : IFriendService, IDisposable
+    {
+        private readonly IEmulatorAccountManager _accountManager;
+        private SystemEventType _completionEvent;
+
+        public FriendService(IEmulatorAccountManager accountManager, FriendsServicePermissionLevel permissionLevel)
+        {
+            _accountManager = accountManager;
+
+            Os.CreateSystemEvent(out _completionEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure();
+            Os.SignalSystemEvent(ref _completionEvent); // TODO: Figure out where we are supposed to signal this.
+        }
+
+        [CmifCommand(0)]
+        public Result GetCompletionEvent([CopyHandle] out int completionEventHandle)
+        {
+            completionEventHandle = Os.GetReadableHandleOfSystemEvent(ref _completionEvent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result Cancel()
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10100)]
+        public Result GetFriendListIds(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span<NetworkServiceAccountId> friendIds,
+            Uid userId,
+            int offset,
+            SizedFriendFilter filter,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid });
+
+            if (userId.IsNull)
+            {
+                return FriendResult.InvalidArgument;
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10101)]
+        public Result GetFriendList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<FriendImpl> friendList,
+            Uid userId,
+            int offset,
+            SizedFriendFilter filter,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset, filter, pidPlaceholder, pid });
+
+            if (userId.IsNull)
+            {
+                return FriendResult.InvalidArgument;
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10102)]
+        public Result UpdateFriendInfo(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<FriendImpl> info,
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<NetworkServiceAccountId> friendIds,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            string friendIdList = string.Join(", ", friendIds.ToArray());
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10110)]
+        public Result GetFriendProfileImage(
+            out int size,
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> profileImage)
+        {
+            size = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10120)]
+        public Result CheckFriendListAvailability(out bool listAvailable, Uid userId)
+        {
+            listAvailable = true;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10121)]
+        public Result EnsureFriendListAvailable(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10200)]
+        public Result SendFriendRequestForApplication(
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10211)]
+        public Result AddFacedFriendRequestForApplication(
+            Uid userId,
+            FacedFriendRequestRegistrationKey key,
+            Nickname nickname,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> arg3,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, key, nickname, arg4, arg5, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10400)]
+        public Result GetBlockedUserListIds(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer)] Span<NetworkServiceAccountId> blockedIds,
+            Uid userId,
+            int offset)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, offset });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10420)]
+        public Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId)
+        {
+            listAvailable = true;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10421)]
+        public Result EnsureBlockedUserListAvailable(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10500)]
+        public Result GetProfileList(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ProfileImpl> profileList,
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<NetworkServiceAccountId> friendIds)
+        {
+            string friendIdList = string.Join(", ", friendIds.ToArray());
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10600)]
+        public Result DeclareOpenOnlinePlaySession(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            if (userId.IsNull)
+            {
+                return FriendResult.InvalidArgument;
+            }
+
+            _accountManager.OpenUserOnlinePlay(userId);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10601)]
+        public Result DeclareCloseOnlinePlaySession(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            if (userId.IsNull)
+            {
+                return FriendResult.InvalidArgument;
+            }
+
+            _accountManager.CloseUserOnlinePlay(userId);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10610)]
+        public Result UpdateUserPresence(
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0xE0)] in UserPresenceImpl userPresence,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, userPresence, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10700)]
+        public Result GetPlayHistoryRegistrationKey(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey,
+            Uid userId,
+            bool arg2)
+        {
+            if (userId.IsNull)
+            {
+                registrationKey = default;
+
+                return FriendResult.InvalidArgument;
+            }
+
+            // NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance.
+
+            // NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance.
+            //       Then calls nn::friends::detail::service::core::AccountStorageManager::GetInstance and stores the instance.
+            //       Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid,
+            //       and stores it in the savedata 8000000000000080 in the friends:/uid.bin file.
+
+            /*
+
+            NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer).
+                  We currently don't support play history and online services so we can use a blank key for now.
+                  Code for reference:
+
+            byte[] hmacKey = new byte[0x20];
+
+            HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey);
+            byte[]     hmacHash   = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer);
+
+            */
+
+            Uid randomGuid = new();
+
+            Guid.NewGuid().TryWriteBytes(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref randomGuid, 1)));
+
+            registrationKey = new()
+            {
+                Type = 0x101,
+                KeyIndex = (byte)(Random.Shared.Next() & 7),
+                UserIdBool = 0, // TODO: Find it.
+                UnknownBool = (byte)(arg2 ? 1 : 0), // TODO: Find it.
+                Reserved = new(),
+                Uuid = randomGuid,
+                HmacHash = new(),
+            };
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10701)]
+        public Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out PlayHistoryRegistrationKey registrationKey,
+            NetworkServiceAccountId friendId,
+            bool arg2)
+        {
+            registrationKey = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { friendId, arg2 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(10702)]
+        public Result AddPlayHistory(
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x40)] in PlayHistoryRegistrationKey registrationKey,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg2,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg3,
+            ulong pidPlaceholder,
+            [ClientProcessId] ulong pid)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, arg2, arg3, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(11000)]
+        public Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2)
+        {
+            imageUrl = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { url, arg2 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20100)]
+        public Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, [ClientProcessId] ulong pid)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, filter, pidPlaceholder, pid });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20101)]
+        public Result GetNewlyFriendCount(out int count, Uid userId)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20102)]
+        public Result GetFriendDetailedInfo(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out FriendDetailedInfoImpl detailedInfo,
+            Uid userId,
+            NetworkServiceAccountId friendId)
+        {
+            detailedInfo = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20103)]
+        public Result SyncFriendList(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20104)]
+        public Result RequestSyncFriendList(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20110)]
+        public Result LoadFriendSetting(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x40)] out FriendSettingImpl friendSetting,
+            Uid userId,
+            NetworkServiceAccountId friendId)
+        {
+            friendSetting = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20200)]
+        public Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId)
+        {
+            count = 0;
+            count2 = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20201)]
+        public Result GetFriendRequestList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<FriendRequestImpl> requestList,
+            Uid userId,
+            int arg3,
+            int arg4)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3, arg4 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20300)]
+        public Result GetFriendCandidateList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<FriendCandidateImpl> candidateList,
+            Uid userId,
+            int arg3)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20301)]
+        public Result GetNintendoNetworkIdInfo(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x38)] out NintendoNetworkIdUserInfo networkIdInfo,
+            out int arg1,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<NintendoNetworkIdFriendImpl> friendInfo,
+            Uid userId,
+            int arg4)
+        {
+            networkIdInfo = default;
+            arg1 = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg4 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20302)]
+        public Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId)
+        {
+            accountLinkage = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20303)]
+        public Result GetSnsAccountProfile(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x380)] out SnsAccountProfile accountProfile,
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            int arg3)
+        {
+            accountProfile = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg3 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20304)]
+        public Result GetSnsAccountFriendList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<SnsAccountFriendImpl> friendList,
+            Uid userId,
+            int arg3)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20400)]
+        public Result GetBlockedUserList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<BlockedUserImpl> blockedUsers,
+            Uid userId,
+            int arg3)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20401)]
+        public Result SyncBlockedUserList(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20500)]
+        public Result GetProfileExtraList(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ProfileExtraImpl> extraList,
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<NetworkServiceAccountId> friendIds)
+        {
+            string friendIdList = string.Join(", ", friendIds.ToArray());
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20501)]
+        public Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId)
+        {
+            relationship = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20600)]
+        public Result GetUserPresenceView([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0xE0)] out UserPresenceViewImpl userPresenceView, Uid userId)
+        {
+            userPresenceView = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20700)]
+        public Result GetPlayHistoryList(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<PlayHistoryImpl> playHistoryList, Uid userId, int arg3)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg3 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20701)]
+        public Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId)
+        {
+            statistics = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20800)]
+        public Result LoadUserSetting([Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x800)] out UserSettingImpl userSetting, Uid userId)
+        {
+            userSetting = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20801)]
+        public Result SyncUserSetting(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(20900)]
+        public Result RequestListSummaryOverlayNotification()
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(21000)]
+        public Result GetExternalApplicationCatalog(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x4B8)] out ExternalApplicationCatalog catalog,
+            ExternalApplicationCatalogId catalogId,
+            LanguageCode language)
+        {
+            catalog = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { catalogId, language });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(22000)]
+        public Result GetReceivedFriendInvitationList(
+            out int count,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<FriendInvitationForViewerImpl> invitationList,
+            Uid userId)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(22001)]
+        public Result GetReceivedFriendInvitationDetailedInfo(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1400)] out FriendInvitationGroupImpl invicationGroup,
+            Uid userId,
+            FriendInvitationGroupId groupId)
+        {
+            invicationGroup = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, groupId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(22010)]
+        public Result GetReceivedFriendInvitationCountCache(out int count, Uid userId)
+        {
+            count = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30100)]
+        public Result DropFriendNewlyFlags(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30101)]
+        public Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30110)]
+        public Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30120)]
+        public Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, favoriteFlag });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30121)]
+        public Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, onlineNotificationFlag });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30200)]
+        public Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30201)]
+        public Result SendFriendRequestWithApplicationInfo(
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            int arg2,
+            ApplicationInfo applicationInfo,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4, arg5 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30202)]
+        public Result CancelFriendRequest(Uid userId, RequestId requestId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30203)]
+        public Result AcceptFriendRequest(Uid userId, RequestId requestId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30204)]
+        public Result RejectFriendRequest(Uid userId, RequestId requestId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30205)]
+        public Result ReadFriendRequest(Uid userId, RequestId requestId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, requestId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30210)]
+        public Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId)
+        {
+            registrationKey = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30211)]
+        public Result AddFacedFriendRequest(
+            Uid userId,
+            FacedFriendRequestRegistrationKey registrationKey,
+            Nickname nickname,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> arg3)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, registrationKey, nickname });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30212)]
+        public Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30213)]
+        public Result GetFacedFriendRequestProfileImage(
+            out int size,
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> profileImage)
+        {
+            size = 0;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30214)]
+        public Result GetFacedFriendRequestProfileImageFromPath(
+            out int size,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> path,
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> profileImage)
+        {
+            size = 0;
+
+            string pathString = Encoding.UTF8.GetString(path);
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { pathString });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30215)]
+        public Result SendFriendRequestWithExternalApplicationCatalogId(
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            int arg2,
+            ExternalApplicationCatalogId catalogId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg5)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, catalogId, arg4, arg5 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30216)]
+        public Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30217)]
+        public Result SendFriendRequestWithNintendoNetworkIdInfo(
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            int arg2,
+            MiiName arg3,
+            MiiImageUrlParam arg4,
+            MiiName arg5,
+            MiiImageUrlParam arg6)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, arg3, arg4, arg5, arg6 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30300)]
+        public Result GetSnsAccountLinkPageUrl([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x1000)] out WebPageUrl url, Uid userId, int arg2)
+        {
+            url = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg2 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30301)]
+        public Result UnlinkSnsAccount(Uid userId, int arg1)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, arg1 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30400)]
+        public Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30401)]
+        public Result BlockUserWithApplicationInfo(
+            Uid userId,
+            NetworkServiceAccountId friendId,
+            int arg2,
+            ApplicationInfo applicationInfo,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x48)] in InAppScreenName arg4)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId, arg2, applicationInfo, arg4 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30402)]
+        public Result UnblockUser(Uid userId, NetworkServiceAccountId friendId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30500)]
+        public Result GetProfileExtraFromFriendCode(
+            [Buffer(HipcBufferFlags.Out | HipcBufferFlags.Pointer, 0x400)] out ProfileExtraImpl profileExtra,
+            Uid userId,
+            FriendCode friendCode)
+        {
+            profileExtra = default;
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendCode });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30700)]
+        public Result DeletePlayHistory(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30810)]
+        public Result ChangePresencePermission(Uid userId, int permission)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30811)]
+        public Result ChangeFriendRequestReception(Uid userId, bool reception)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, reception });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30812)]
+        public Result ChangePlayLogPermission(Uid userId, int permission)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, permission });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30820)]
+        public Result IssueFriendCode(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30830)]
+        public Result ClearPlayLog(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30900)]
+        public Result SendFriendInvitation(
+            Uid userId,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<NetworkServiceAccountId> friendIds,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias, 0xC00)] in FriendInvitationGameModeDescription description,
+            ApplicationInfo applicationInfo,
+            [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> arg4,
+            bool arg5)
+        {
+            string friendIdList = string.Join(", ", friendIds.ToArray());
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, friendIdList, description, applicationInfo, arg5 });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30910)]
+        public Result ReadFriendInvitation(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<FriendInvitationId> invitationIds)
+        {
+            string invitationIdList = string.Join(", ", invitationIds.ToArray());
+
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId, invitationIdList });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(30911)]
+        public Result ReadAllFriendInvitations(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(40100)]
+        public Result DeleteFriendListCache(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(40400)]
+        public Result DeleteBlockedUserListCache(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        [CmifCommand(49900)]
+        public Result DeleteNetworkServiceAccountCache(Uid userId)
+        {
+            Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { userId });
+
+            return Result.Success;
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Os.DestroySystemEvent(ref _completionEvent);
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs
similarity index 64%
rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs
rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs
index 7902d9c535..f4bbe100f7 100644
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs
@@ -1,16 +1,13 @@
-using System;
-
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
 {
-    [Flags]
-    enum FriendServicePermissionLevel
+    enum FriendsServicePermissionLevel
     {
         UserMask = 1,
         ViewerMask = 2,
         ManagerMask = 4,
         SystemMask = 8,
 
-        Administrator = -1,
+        Admin = -1,
         User = UserMask,
         Viewer = UserMask | ViewerMask,
         Manager = UserMask | ViewerMask | ManagerMask,
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs
new file mode 100644
index 0000000000..2bb0434e86
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs
@@ -0,0 +1,9 @@
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    interface IDaemonSuspendSessionService : IServiceObject
+    {
+        // NOTE: This service has no commands.
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs
new file mode 100644
index 0000000000..c19d0b7885
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs
@@ -0,0 +1,97 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Settings;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    interface IFriendService : IServiceObject
+    {
+        Result GetCompletionEvent(out int completionEventHandle);
+        Result Cancel();
+        Result GetFriendListIds(out int count, Span<NetworkServiceAccountId> friendIds, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid);
+        Result GetFriendList(out int count, Span<FriendImpl> friendList, Uid userId, int offset, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid);
+        Result UpdateFriendInfo(Span<FriendImpl> info, Uid userId, ReadOnlySpan<NetworkServiceAccountId> friendIds, ulong pidPlaceholder, ulong pid);
+        Result GetFriendProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span<byte> profileImage);
+        Result CheckFriendListAvailability(out bool listAvailable, Uid userId);
+        Result EnsureFriendListAvailable(Uid userId);
+        Result SendFriendRequestForApplication(Uid userId, NetworkServiceAccountId friendId, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid);
+        Result AddFacedFriendRequestForApplication(Uid userId, FacedFriendRequestRegistrationKey key, Nickname nickname, ReadOnlySpan<byte> arg3, in InAppScreenName arg4, in InAppScreenName arg5, ulong pidPlaceholder, ulong pid);
+        Result GetBlockedUserListIds(out int count, Span<NetworkServiceAccountId> blockedIds, Uid userId, int offset);
+        Result CheckBlockedUserListAvailability(out bool listAvailable, Uid userId);
+        Result EnsureBlockedUserListAvailable(Uid userId);
+        Result GetProfileList(Span<ProfileImpl> profileList, Uid userId, ReadOnlySpan<NetworkServiceAccountId> friendIds);
+        Result DeclareOpenOnlinePlaySession(Uid userId);
+        Result DeclareCloseOnlinePlaySession(Uid userId);
+        Result UpdateUserPresence(Uid userId, in UserPresenceImpl userPresence, ulong pidPlaceholder, ulong pid);
+        Result GetPlayHistoryRegistrationKey(out PlayHistoryRegistrationKey registrationKey, Uid userId, bool arg2);
+        Result GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId(out PlayHistoryRegistrationKey registrationKey, NetworkServiceAccountId friendId, bool arg2);
+        Result AddPlayHistory(Uid userId, in PlayHistoryRegistrationKey registrationKey, in InAppScreenName arg2, in InAppScreenName arg3, ulong pidPlaceholder, ulong pid);
+        Result GetProfileImageUrl(out Url imageUrl, Url url, int arg2);
+        Result GetFriendCount(out int count, Uid userId, SizedFriendFilter filter, ulong pidPlaceholder, ulong pid);
+        Result GetNewlyFriendCount(out int count, Uid userId);
+        Result GetFriendDetailedInfo(out FriendDetailedInfoImpl detailedInfo, Uid userId, NetworkServiceAccountId friendId);
+        Result SyncFriendList(Uid userId);
+        Result RequestSyncFriendList(Uid userId);
+        Result LoadFriendSetting(out FriendSettingImpl friendSetting, Uid userId, NetworkServiceAccountId friendId);
+        Result GetReceivedFriendRequestCount(out int count, out int count2, Uid userId);
+        Result GetFriendRequestList(out int count, Span<FriendRequestImpl> requestList, Uid userId, int arg3, int arg4);
+        Result GetFriendCandidateList(out int count, Span<FriendCandidateImpl> candidateList, Uid userId, int arg3);
+        Result GetNintendoNetworkIdInfo(out NintendoNetworkIdUserInfo networkIdInfo, out int arg1, Span<NintendoNetworkIdFriendImpl> friendInfo, Uid userId, int arg4);
+        Result GetSnsAccountLinkage(out SnsAccountLinkage accountLinkage, Uid userId);
+        Result GetSnsAccountProfile(out SnsAccountProfile accountProfile, Uid userId, NetworkServiceAccountId friendId, int arg3);
+        Result GetSnsAccountFriendList(out int count, Span<SnsAccountFriendImpl> friendList, Uid userId, int arg3);
+        Result GetBlockedUserList(out int count, Span<BlockedUserImpl> blockedUsers, Uid userId, int arg3);
+        Result SyncBlockedUserList(Uid userId);
+        Result GetProfileExtraList(Span<ProfileExtraImpl> extraList, Uid userId, ReadOnlySpan<NetworkServiceAccountId> friendIds);
+        Result GetRelationship(out Relationship relationship, Uid userId, NetworkServiceAccountId friendId);
+        Result GetUserPresenceView(out UserPresenceViewImpl userPresenceView, Uid userId);
+        Result GetPlayHistoryList(out int count, Span<PlayHistoryImpl> playHistoryList, Uid userId, int arg3);
+        Result GetPlayHistoryStatistics(out PlayHistoryStatistics statistics, Uid userId);
+        Result LoadUserSetting(out UserSettingImpl userSetting, Uid userId);
+        Result SyncUserSetting(Uid userId);
+        Result RequestListSummaryOverlayNotification();
+        Result GetExternalApplicationCatalog(out ExternalApplicationCatalog catalog, ExternalApplicationCatalogId catalogId, LanguageCode language);
+        Result GetReceivedFriendInvitationList(out int count, Span<FriendInvitationForViewerImpl> invitationList, Uid userId);
+        Result GetReceivedFriendInvitationDetailedInfo(out FriendInvitationGroupImpl invicationGroup, Uid userId, FriendInvitationGroupId groupId);
+        Result GetReceivedFriendInvitationCountCache(out int count, Uid userId);
+        Result DropFriendNewlyFlags(Uid userId);
+        Result DeleteFriend(Uid userId, NetworkServiceAccountId friendId);
+        Result DropFriendNewlyFlag(Uid userId, NetworkServiceAccountId friendId);
+        Result ChangeFriendFavoriteFlag(Uid userId, NetworkServiceAccountId friendId, bool favoriteFlag);
+        Result ChangeFriendOnlineNotificationFlag(Uid userId, NetworkServiceAccountId friendId, bool onlineNotificationFlag);
+        Result SendFriendRequest(Uid userId, NetworkServiceAccountId friendId, int arg2);
+        Result SendFriendRequestWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4, in InAppScreenName arg5);
+        Result CancelFriendRequest(Uid userId, RequestId requestId);
+        Result AcceptFriendRequest(Uid userId, RequestId requestId);
+        Result RejectFriendRequest(Uid userId, RequestId requestId);
+        Result ReadFriendRequest(Uid userId, RequestId requestId);
+        Result GetFacedFriendRequestRegistrationKey(out FacedFriendRequestRegistrationKey registrationKey, Uid userId);
+        Result AddFacedFriendRequest(Uid userId, FacedFriendRequestRegistrationKey registrationKey, Nickname nickname, ReadOnlySpan<byte> arg3);
+        Result CancelFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId);
+        Result GetFacedFriendRequestProfileImage(out int size, Uid userId, NetworkServiceAccountId friendId, Span<byte> profileImage);
+        Result GetFacedFriendRequestProfileImageFromPath(out int size, ReadOnlySpan<byte> path, Span<byte> profileImage);
+        Result SendFriendRequestWithExternalApplicationCatalogId(Uid userId, NetworkServiceAccountId friendId, int arg2, ExternalApplicationCatalogId catalogId, in InAppScreenName arg4, in InAppScreenName arg5);
+        Result ResendFacedFriendRequest(Uid userId, NetworkServiceAccountId friendId);
+        Result SendFriendRequestWithNintendoNetworkIdInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, MiiName arg3, MiiImageUrlParam arg4, MiiName arg5, MiiImageUrlParam arg6);
+        Result GetSnsAccountLinkPageUrl(out WebPageUrl url, Uid userId, int arg2);
+        Result UnlinkSnsAccount(Uid userId, int arg1);
+        Result BlockUser(Uid userId, NetworkServiceAccountId friendId, int arg2);
+        Result BlockUserWithApplicationInfo(Uid userId, NetworkServiceAccountId friendId, int arg2, ApplicationInfo applicationInfo, in InAppScreenName arg4);
+        Result UnblockUser(Uid userId, NetworkServiceAccountId friendId);
+        Result GetProfileExtraFromFriendCode(out ProfileExtraImpl profileExtra, Uid userId, FriendCode friendCode);
+        Result DeletePlayHistory(Uid userId);
+        Result ChangePresencePermission(Uid userId, int permission);
+        Result ChangeFriendRequestReception(Uid userId, bool reception);
+        Result ChangePlayLogPermission(Uid userId, int permission);
+        Result IssueFriendCode(Uid userId);
+        Result ClearPlayLog(Uid userId);
+        Result SendFriendInvitation(Uid userId, ReadOnlySpan<NetworkServiceAccountId> friendIds, in FriendInvitationGameModeDescription description, ApplicationInfo applicationInfo, ReadOnlySpan<byte> arg4, bool arg5);
+        Result ReadFriendInvitation(Uid userId, ReadOnlySpan<FriendInvitationId> invitationIds);
+        Result ReadAllFriendInvitations(Uid userId);
+        Result DeleteFriendListCache(Uid userId);
+        Result DeleteBlockedUserListCache(Uid userId);
+        Result DeleteNetworkServiceAccountCache(Uid userId);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs
new file mode 100644
index 0000000000..a3a28e8ce0
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    interface INotificationService : IServiceObject
+    {
+        Result GetEvent(out int eventHandle);
+        Result Clear();
+        Result Pop(out SizedNotificationInfo sizedNotificationInfo);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs
new file mode 100644
index 0000000000..58e2569bb7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs
@@ -0,0 +1,13 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    interface IServiceCreator : IServiceObject
+    {
+        Result CreateFriendService(out IFriendService friendService);
+        Result CreateNotificationService(out INotificationService notificationService, Uid userId);
+        Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs
new file mode 100644
index 0000000000..61c692a6ef
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs
@@ -0,0 +1,58 @@
+using Ryujinx.Horizon.Sdk.Account;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    sealed class NotificationEventHandler
+    {
+        private readonly NotificationService[] _registry;
+
+        public NotificationEventHandler()
+        {
+            _registry = new NotificationService[0x20];
+        }
+
+        public void RegisterNotificationService(NotificationService service)
+        {
+            // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors.
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] == null)
+                {
+                    _registry[i] = service;
+                    break;
+                }
+            }
+        }
+
+        public void UnregisterNotificationService(NotificationService service)
+        {
+            // NOTE: When there's no enough space in the registry array, Nintendo doesn't return any errors.
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] == service)
+                {
+                    _registry[i] = null;
+                    break;
+                }
+            }
+        }
+
+        // TODO: Use this when we have enough things to go online.
+        public void SignalFriendListUpdate(Uid targetId)
+        {
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                _registry[i]?.SignalFriendListUpdate(targetId);
+            }
+        }
+
+        // TODO: Use this when we have enough things to go online.
+        public void SignalNewFriendRequest(Uid targetId)
+        {
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                _registry[i]?.SignalNewFriendRequest(targetId);
+            }
+        }
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs
similarity index 64%
rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs
rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs
index 363e03eafa..e46fc9b7a5 100644
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
 {
     enum NotificationEventType : uint
     {
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs
new file mode 100644
index 0000000000..534bf63ed6
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs
@@ -0,0 +1,172 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.OsTypes;
+using Ryujinx.Horizon.Sdk.Sf;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    partial class NotificationService : INotificationService, IDisposable
+    {
+        private readonly NotificationEventHandler _notificationEventHandler;
+        private readonly Uid _userId;
+        private readonly FriendsServicePermissionLevel _permissionLevel;
+
+        private readonly object _lock = new();
+
+        private SystemEventType _notificationEvent;
+
+        private readonly LinkedList<SizedNotificationInfo> _notifications;
+
+        private bool _hasNewFriendRequest;
+        private bool _hasFriendListUpdate;
+
+        public NotificationService(NotificationEventHandler notificationEventHandler, Uid userId, FriendsServicePermissionLevel permissionLevel)
+        {
+            _notificationEventHandler = notificationEventHandler;
+            _userId = userId;
+            _permissionLevel = permissionLevel;
+            _notifications = new LinkedList<SizedNotificationInfo>();
+            Os.CreateSystemEvent(out _notificationEvent, EventClearMode.AutoClear, interProcess: true).AbortOnFailure();
+
+            _hasNewFriendRequest = false;
+            _hasFriendListUpdate = false;
+
+            notificationEventHandler.RegisterNotificationService(this);
+        }
+
+        [CmifCommand(0)]
+        public Result GetEvent([CopyHandle] out int eventHandle)
+        {
+            eventHandle = Os.GetReadableHandleOfSystemEvent(ref _notificationEvent);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)]
+        public Result Clear()
+        {
+            lock (_lock)
+            {
+                _hasNewFriendRequest = false;
+                _hasFriendListUpdate = false;
+
+                _notifications.Clear();
+            }
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)]
+        public Result Pop(out SizedNotificationInfo sizedNotificationInfo)
+        {
+            lock (_lock)
+            {
+                if (_notifications.Count >= 1)
+                {
+                    sizedNotificationInfo = _notifications.First.Value;
+                    _notifications.RemoveFirst();
+
+                    if (sizedNotificationInfo.Type == NotificationEventType.FriendListUpdate)
+                    {
+                        _hasFriendListUpdate = false;
+                    }
+                    else if (sizedNotificationInfo.Type == NotificationEventType.NewFriendRequest)
+                    {
+                        _hasNewFriendRequest = false;
+                    }
+
+                    return Result.Success;
+                }
+            }
+
+            sizedNotificationInfo = default;
+
+            return FriendResult.NotificationQueueEmpty;
+        }
+
+        public void SignalFriendListUpdate(Uid targetId)
+        {
+            lock (_lock)
+            {
+                if (_userId == targetId)
+                {
+                    if (!_hasFriendListUpdate)
+                    {
+                        SizedNotificationInfo friendListNotification = new();
+
+                        if (_notifications.Count != 0)
+                        {
+                            friendListNotification = _notifications.First.Value;
+                            _notifications.RemoveFirst();
+                        }
+
+                        friendListNotification.Type = NotificationEventType.FriendListUpdate;
+                        _hasFriendListUpdate = true;
+
+                        if (_hasNewFriendRequest)
+                        {
+                            SizedNotificationInfo newFriendRequestNotification = new();
+
+                            if (_notifications.Count != 0)
+                            {
+                                newFriendRequestNotification = _notifications.First.Value;
+                                _notifications.RemoveFirst();
+                            }
+
+                            newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest;
+                            _notifications.AddFirst(newFriendRequestNotification);
+                        }
+
+                        // We defer this to make sure we are on top of the queue.
+                        _notifications.AddFirst(friendListNotification);
+                    }
+
+                    Os.SignalSystemEvent(ref _notificationEvent);
+                }
+            }
+        }
+
+        public void SignalNewFriendRequest(Uid targetId)
+        {
+            lock (_lock)
+            {
+                if (_permissionLevel.HasFlag(FriendsServicePermissionLevel.ViewerMask) && _userId == targetId)
+                {
+                    if (!_hasNewFriendRequest)
+                    {
+                        if (_notifications.Count == 100)
+                        {
+                            SignalFriendListUpdate(targetId);
+                        }
+
+                        SizedNotificationInfo newFriendRequestNotification = new()
+                        {
+                            Type = NotificationEventType.NewFriendRequest,
+                        };
+
+                        _notifications.AddLast(newFriendRequestNotification);
+                        _hasNewFriendRequest = true;
+                    }
+
+                    Os.SignalSystemEvent(ref _notificationEvent);
+                }
+            }
+        }
+
+        protected virtual void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _notificationEventHandler.UnregisterNotificationService(this);
+            }
+        }
+
+        public void Dispose()
+        {
+            Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs
similarity index 64%
rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs
rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs
index c9a54250f3..3ea1058727 100644
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
 {
     enum PresenceStatusFilter : uint
     {
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs
new file mode 100644
index 0000000000..1be804dfd7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs
@@ -0,0 +1,51 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Account;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    partial class ServiceCreator : IServiceCreator
+    {
+        private readonly IEmulatorAccountManager _accountManager;
+        private readonly NotificationEventHandler _notificationEventHandler;
+        private readonly FriendsServicePermissionLevel _permissionLevel;
+
+        public ServiceCreator(IEmulatorAccountManager accountManager, NotificationEventHandler notificationEventHandler, FriendsServicePermissionLevel permissionLevel)
+        {
+            _accountManager = accountManager;
+            _notificationEventHandler = notificationEventHandler;
+            _permissionLevel = permissionLevel;
+        }
+
+        [CmifCommand(0)]
+        public Result CreateFriendService(out IFriendService friendService)
+        {
+            friendService = new FriendService(_accountManager, _permissionLevel);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(1)] // 2.0.0+
+        public Result CreateNotificationService(out INotificationService notificationService, Uid userId)
+        {
+            if (userId.IsNull)
+            {
+                notificationService = null;
+
+                return FriendResult.InvalidArgument;
+            }
+
+            notificationService = new NotificationService(_notificationEventHandler, userId, _permissionLevel);
+
+            return Result.Success;
+        }
+
+        [CmifCommand(2)] // 4.0.0+
+        public Result CreateDaemonSuspendSessionService(out IDaemonSuspendSessionService daemonSuspendSessionService)
+        {
+            daemonSuspendSessionService = new DaemonSuspendSessionService();
+
+            return Result.Success;
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs
new file mode 100644
index 0000000000..d93a2ae296
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct SizedFriendFilter
+    {
+        public PresenceStatusFilter PresenceStatus;
+        public bool IsFavoriteOnly;
+        public bool IsSameAppPresenceOnly;
+        public bool IsSameAppPlayedOnly;
+        public bool IsArbitraryAppPlayedOnly;
+        public ulong PresenceGroupId;
+
+        public readonly override string ToString()
+        {
+            return $"{{ PresenceStatus: {PresenceStatus}, " +
+                $"IsFavoriteOnly: {IsFavoriteOnly}, " +
+                $"IsSameAppPresenceOnly: {IsSameAppPresenceOnly}, " +
+                $"IsSameAppPlayedOnly: {IsSameAppPlayedOnly}, " +
+                $"IsArbitraryAppPlayedOnly: {IsArbitraryAppPlayedOnly}, " +
+                $"PresenceGroupId: {PresenceGroupId} }}";
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs
new file mode 100644
index 0000000000..0da26a1ae0
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs
@@ -0,0 +1,13 @@
+using Ryujinx.Horizon.Sdk.Account;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct SizedNotificationInfo
+    {
+        public NotificationEventType Type;
+        public uint Padding;
+        public NetworkServiceAccountId NetworkUserIdPlaceholder;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs
new file mode 100644
index 0000000000..66d61e4c14
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct NintendoNetworkIdFriendImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs
new file mode 100644
index 0000000000..9f90f0c8f6
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct PlayHistoryImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs
similarity index 58%
rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs
rename to src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs
index 7930aff0b8..5ddbe14ea8 100644
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs
@@ -1,4 +1,4 @@
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
 {
     enum PresenceStatus : uint
     {
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs
new file mode 100644
index 0000000000..1548d725f4
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+    struct ProfileExtraImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs
new file mode 100644
index 0000000000..f779d93cf4
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct ProfileImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs
new file mode 100644
index 0000000000..dc6adf03a0
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    struct SnsAccountFriendImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs
new file mode 100644
index 0000000000..cf4520cf4e
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Sdk.Account;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xE0)]
+    struct UserPresenceImpl
+    {
+        public Uid UserId;
+        public long LastTimeOnlineTimestamp;
+        public PresenceStatus Status;
+        public bool SamePresenceGroupApplication;
+        public Array3<byte> Unknown;
+        public AppKeyValueStorageHolder AppKeyValueStorage;
+
+        [InlineArray(0xC0)]
+        public struct AppKeyValueStorageHolder
+        {
+            public byte Value;
+        }
+
+        public readonly override string ToString()
+        {
+            return $"{{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}";
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs
new file mode 100644
index 0000000000..04c0926009
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xE0)]
+    struct UserPresenceViewImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs
new file mode 100644
index 0000000000..9d057fb1e7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends.Detail
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x800)]
+    struct UserSettingImpl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs
new file mode 100644
index 0000000000..0d9c157d33
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x4B8)]
+    struct ExternalApplicationCatalog
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs
new file mode 100644
index 0000000000..7ed36cd9df
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct ExternalApplicationCatalogId
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs
new file mode 100644
index 0000000000..6b5812f646
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)]
+    struct FacedFriendRequestRegistrationKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs
new file mode 100644
index 0000000000..d78497a181
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)]
+    struct FriendCode
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs
new file mode 100644
index 0000000000..29b4a09743
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xC00)]
+    struct FriendInvitationGameModeDescription
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs
new file mode 100644
index 0000000000..ef53882b34
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
+    struct FriendInvitationGroupId
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs
new file mode 100644
index 0000000000..7be19d574f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    struct FriendInvitationId
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs
new file mode 100644
index 0000000000..5965d508d8
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs
@@ -0,0 +1,13 @@
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    static class FriendResult
+    {
+        private const int ModuleId = 121;
+
+        public static Result InvalidArgument => new(ModuleId, 2);
+        public static Result InternetRequestDenied => new(ModuleId, 6);
+        public static Result NotificationQueueEmpty => new(ModuleId, 15);
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs
new file mode 100644
index 0000000000..22574a5cca
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs
@@ -0,0 +1,26 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Sdk.Settings;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x48)]
+    struct InAppScreenName
+    {
+        public Array64<byte> Name;
+        public LanguageCode LanguageCode;
+
+        public override readonly string ToString()
+        {
+            int length = Name.AsSpan().IndexOf((byte)0);
+            if (length < 0)
+            {
+                length = 64;
+            }
+
+            return Encoding.UTF8.GetString(Name.AsSpan()[..length]);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs
new file mode 100644
index 0000000000..8790bb931c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x1)]
+    struct MiiImageUrlParam
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs
new file mode 100644
index 0000000000..e73c0d8336
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)]
+    struct MiiName
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs
new file mode 100644
index 0000000000..a2a9e046fb
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x38)]
+    struct NintendoNetworkIdUserInfo
+    {
+    }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs
similarity index 59%
rename from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs
rename to src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs
index 9687c5478d..bb672a795c 100644
--- a/src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs
+++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs
@@ -1,9 +1,10 @@
 using Ryujinx.Common.Memory;
+using Ryujinx.Horizon.Sdk.Account;
 using System.Runtime.InteropServices;
 
-namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
+namespace Ryujinx.Horizon.Sdk.Friends
 {
-    [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+    [StructLayout(LayoutKind.Sequential, Size = 0x40)]
     struct PlayHistoryRegistrationKey
     {
         public ushort Type;
@@ -11,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
         public byte UserIdBool;
         public byte UnknownBool;
         public Array11<byte> Reserved;
-        public Array16<byte> Uuid;
+        public Uid Uuid;
+        public Array32<byte> HmacHash;
     }
 }
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs
new file mode 100644
index 0000000000..ea3e3d9976
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct PlayHistoryStatistics
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs
new file mode 100644
index 0000000000..efba09a8f2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
+    struct Relationship
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs
new file mode 100644
index 0000000000..3236a1d7db
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
+    struct RequestId
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs
new file mode 100644
index 0000000000..b4660d9e72
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
+    struct SnsAccountLinkage
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs
new file mode 100644
index 0000000000..d872b3dac2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x380)]
+    struct SnsAccountProfile
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/Url.cs b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs
new file mode 100644
index 0000000000..833ee12306
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/Url.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xA0, Pack = 0x1)]
+    struct Url
+    {
+        public UrlStorage Path;
+
+        [InlineArray(0xA0)]
+        public struct UrlStorage
+        {
+            public byte Value;
+        }
+
+        public override readonly string ToString()
+        {
+            int length = ((ReadOnlySpan<byte>)Path).IndexOf((byte)0);
+            if (length < 0)
+            {
+                length = 33;
+            }
+
+            return Encoding.UTF8.GetString(((ReadOnlySpan<byte>)Path)[..length]);
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs
new file mode 100644
index 0000000000..85488af61c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Friends
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x1000)]
+    struct WebPageUrl
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs
new file mode 100644
index 0000000000..71185fcd0f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)]
+    struct BatteryLot
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs
new file mode 100644
index 0000000000..292a368f1e
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)]
+    struct AccelerometerOffset
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs
new file mode 100644
index 0000000000..ef9d17ef96
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)]
+    struct AccelerometerScale
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs
new file mode 100644
index 0000000000..7cbab2f090
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x74, Pack = 0x4)]
+    struct AmiiboEcdsaCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs
new file mode 100644
index 0000000000..8d16b51b10
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)]
+    struct AmiiboEcqvBlsCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs
new file mode 100644
index 0000000000..da6ca53bc4
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x48, Pack = 0x4)]
+    struct AmiiboEcqvBlsKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs
new file mode 100644
index 0000000000..e69e38a1a4
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x94, Pack = 0x4)]
+    struct AmiiboEcqvBlsRootCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs
new file mode 100644
index 0000000000..43742fbb7d
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)]
+    struct AmiiboEcqvCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs
new file mode 100644
index 0000000000..43ffccb003
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)]
+    struct AmiiboKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs
new file mode 100644
index 0000000000..3fe6f32235
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x9, Pack = 0x1)]
+    struct AnalogStickFactoryCalibration
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs
new file mode 100644
index 0000000000..a442032c7f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x12, Pack = 0x1)]
+    struct AnalogStickModelParameter
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs
new file mode 100644
index 0000000000..519d72e8f2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)]
+    struct BdAddress
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs
new file mode 100644
index 0000000000..40565805ff
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x1E, Pack = 0x1)]
+    struct ConfigurationId1
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs
new file mode 100644
index 0000000000..c5503edc5a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)]
+    struct ConsoleSixAxisSensorHorizontalOffset
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs
new file mode 100644
index 0000000000..daf2ba3b89
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs
@@ -0,0 +1,8 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    struct CountryCode
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs
new file mode 100644
index 0000000000..727408ed5f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x180)]
+    struct EccB233DeviceCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs
new file mode 100644
index 0000000000..a0481f4dcb
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)]
+    struct EccB233DeviceKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs
new file mode 100644
index 0000000000..ce3908afea
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x400)]
+    struct GameCardCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs
new file mode 100644
index 0000000000..81144ac48c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x138)]
+    struct GameCardKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs
new file mode 100644
index 0000000000..801d117cbb
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)]
+    struct GyroscopeOffset
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs
new file mode 100644
index 0000000000..7812281f89
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x2)]
+    struct GyroscopeScale
+    {
+        public ushort X;
+        public ushort Y;
+        public ushort Z;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs
new file mode 100644
index 0000000000..65e222ee58
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)]
+    struct MacAddress
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs
new file mode 100644
index 0000000000..57217059f3
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x240)]
+    struct Rsa2048DeviceCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs
new file mode 100644
index 0000000000..d2fd51cf77
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x248)]
+    struct Rsa2048DeviceKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs
new file mode 100644
index 0000000000..af664cdc59
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)]
+    struct SerialNumber
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs
new file mode 100644
index 0000000000..f147f66ff0
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x5A, Pack = 0x2)]
+    struct SpeakerParameter
+    {
+        public ushort Version;
+        public Array34<byte> Reserved;
+        public ushort SpeakerHpf2A1;
+        public ushort SpeakerHpf2A2;
+        public ushort SpeakerHpf2H0;
+        public ushort SpeakerEqInputVolume;
+        public ushort SpeakerEqOutputVolume;
+        public ushort SpeakerEqCtrl1;
+        public ushort SpeakerEqCtrl2;
+        public ushort SpeakerDrcAgcCtrl2;
+        public ushort SpeakerDrcAgcCtrl3;
+        public ushort SpeakerDrcAgcCtrl1;
+        public ushort SpeakerAnalogVolume;
+        public ushort HeadphoneAnalogVolume;
+        public ushort SpeakerDigitalVolumeMin;
+        public ushort SpeakerDigitalVolumeMax;
+        public ushort HeadphoneDigitalVolumeMin;
+        public ushort HeadphoneDigitalVolumeMax;
+        public ushort MicFixedGain;
+        public ushort MicVariableVolumeMin;
+        public ushort MicVariableVolumeMax;
+        public Array16<byte> Reserved2;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs
new file mode 100644
index 0000000000..5d82521641
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x804)]
+    struct SslCertificate
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs
new file mode 100644
index 0000000000..7d4b413696
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.Factory
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x138)]
+    struct SslKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/Language.cs b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs
new file mode 100644
index 0000000000..4ffc66fece
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/Language.cs
@@ -0,0 +1,24 @@
+namespace Ryujinx.Horizon.Sdk.Settings
+{
+    enum Language : uint
+    {
+        Japanese,
+        AmericanEnglish,
+        French,
+        German,
+        Italian,
+        Spanish,
+        Chinese,
+        Korean,
+        Dutch,
+        Portuguese,
+        Russian,
+        Taiwanese,
+        BritishEnglish,
+        CanadianFrench,
+        LatinAmericanSpanish,
+        SimplifiedChinese,
+        TraditionalChinese,
+        BrazilianPortuguese,
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs
new file mode 100644
index 0000000000..dc97126924
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs
@@ -0,0 +1,63 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Settings
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
+    struct LanguageCode
+    {
+        private static readonly string[] _languageCodes = new string[]
+        {
+            "ja",
+            "en-US",
+            "fr",
+            "de",
+            "it",
+            "es",
+            "zh-CN",
+            "ko",
+            "nl",
+            "pt",
+            "ru",
+            "zh-TW",
+            "en-GB",
+            "fr-CA",
+            "es-419",
+            "zh-Hans",
+            "zh-Hant",
+            "pt-BR"
+        };
+
+        public Array8<byte> Value;
+
+        public bool IsValid()
+        {
+            int length = Value.AsSpan().IndexOf((byte)0);
+            if (length < 0)
+            {
+                return false;
+            }
+
+            string str = Encoding.ASCII.GetString(Value.AsSpan()[..length]);
+
+            return _languageCodes.AsSpan().Contains(str);
+        }
+
+        public LanguageCode(Language language)
+        {
+            if ((uint)language >= _languageCodes.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(language));
+            }
+
+            Value = new LanguageCode(_languageCodes[(int)language]).Value;
+        }
+
+        public LanguageCode(string strCode)
+        {
+            Encoding.ASCII.GetBytes(strCode, Value.AsSpan());
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs
new file mode 100644
index 0000000000..6611841033
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x48)]
+    struct SettingsItemKey
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs
new file mode 100644
index 0000000000..6864b8cd64
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x48)]
+    struct SettingsName
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs
new file mode 100644
index 0000000000..a2cbad6a6a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Horizon.Sdk.Account;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct AccountNotificationSettings
+    {
+#pragma warning disable CS0649 // Field is never assigned to
+        public Uid UserId;
+        public uint Flags;
+        public byte FriendPresenceOverlayPermission;
+        public byte FriendInvitationOverlayPermission;
+        public ushort Reserved;
+#pragma warning restore CS0649
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs
new file mode 100644
index 0000000000..3ed77e52b9
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct AccountOnlineStorageSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs
new file mode 100644
index 0000000000..bd27ea0bfe
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x4, Pack = 0x4)]
+    struct AccountSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs
new file mode 100644
index 0000000000..cb90daf18b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x100)]
+    struct AllowedSslHost
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs
new file mode 100644
index 0000000000..36023da9cc
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
+    struct AnalogStickUserCalibration
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs
new file mode 100644
index 0000000000..00d6f4d06b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum AppletLaunchFlag : uint
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs
new file mode 100644
index 0000000000..d246bc2b96
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
+    struct AudioVolume
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs
new file mode 100644
index 0000000000..00de6869c7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs
@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x28, Pack = 0x4)]
+    struct BacklightSettings
+    {
+        // TODO: Determine field names.
+        public uint Unknown0x00;
+        public float Unknown0x04;
+        // 1st group
+        public float Unknown0x08;
+        public float Unknown0x0C;
+        public float Unknown0x10;
+        // 2nd group
+        public float Unknown0x14;
+        public float Unknown0x18;
+        public float Unknown0x1C;
+        public float Unknown0x20;
+        public float Unknown0x24;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs
new file mode 100644
index 0000000000..347afdfe3a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x2C, Pack = 0x4)]
+    struct BacklightSettingsEx
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs
new file mode 100644
index 0000000000..d9b01f9ffe
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct BlePairingSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs
new file mode 100644
index 0000000000..ec5c97c5a2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct BluetoothDevicesSettings
+    {
+#pragma warning disable CS0649 // Field is never assigned to
+        public Array6<byte> BdAddr;
+        public Array32<byte> DeviceName;
+        public Array3<byte> ClassOfDevice;
+        public Array16<byte> LinkKey;
+        public bool LinkKeyPresent;
+        public ushort Version;
+        public uint TrustedServices;
+        public ushort Vid;
+        public ushort Pid;
+        public byte SubClass;
+        public byte AttributeMask;
+        public ushort DescriptorLength;
+        public Array128<byte> Descriptor;
+        public byte KeyType;
+        public byte DeviceType;
+        public ushort BrrSize;
+        public Array9<byte> Brr;
+        public Array256<byte> Reserved;
+        public Array43<byte> Reserved2;
+#pragma warning restore CS0649
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs
new file mode 100644
index 0000000000..8bd4924e9a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x5C8)]
+    struct ButtonConfigRegisteredSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs
new file mode 100644
index 0000000000..2f06e32e15
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x5A8)]
+    struct ButtonConfigSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs
new file mode 100644
index 0000000000..c70d4ff28a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAccelerationBias
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs
new file mode 100644
index 0000000000..0803beb871
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAccelerationGain
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs
new file mode 100644
index 0000000000..831e44bd5b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAngularAcceleration
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs
new file mode 100644
index 0000000000..83d1faa8da
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAngularVelocityBias
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs
new file mode 100644
index 0000000000..68e0c614ae
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAngularVelocityGain
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs
new file mode 100644
index 0000000000..47f3d951c1
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)]
+    struct ConsoleSixAxisSensorAngularVelocityTimeBias
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs
new file mode 100644
index 0000000000..a10a265d1c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum DataDeletionFlag : uint
+    {
+        AutomaticDeletionFlag = 1 << 0,
+    }
+
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
+    struct DataDeletionSettings
+    {
+        public DataDeletionFlag Flags;
+        public uint UseCount;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs
new file mode 100644
index 0000000000..99c9f98178
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x80)]
+    struct DeviceNickName
+    {
+        public Array128<byte> Value;
+
+        public DeviceNickName(string value)
+        {
+            int bytesWritten = Encoding.ASCII.GetBytes(value, Value.AsSpan());
+            if (bytesWritten < 128)
+            {
+                Value[bytesWritten] = 0;
+            }
+            else
+            {
+                Value[127] = 0;
+            }
+        }
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs
new file mode 100644
index 0000000000..3ff5668549
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x200)]
+    struct Edid
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs
new file mode 100644
index 0000000000..65905b1ba7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct EulaVersion
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs
new file mode 100644
index 0000000000..6be941151f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct FatalDirtyFlag
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs
new file mode 100644
index 0000000000..39825e010f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x100)]
+    struct FirmwareVersion
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs
new file mode 100644
index 0000000000..0027d7ef1a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)]
+    struct FirmwareVersionDigest
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs
new file mode 100644
index 0000000000..cc7b317be7
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 0x1)]
+    struct HomeMenuScheme
+    {
+        public uint Main;
+        public uint Back;
+        public uint Sub;
+        public uint Bezel;
+        public uint Extra;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs
new file mode 100644
index 0000000000..1a66abac90
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x100)]
+    struct HostFsMountPoint
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs
new file mode 100644
index 0000000000..b3989de750
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x8)]
+    struct InitialLaunchSettings
+    {
+        public uint Flags;
+        public uint Reserved;
+        public ulong TimeStamp1;
+        public ulong TimeStamp2;
+        public ulong TimeStamp3;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs
new file mode 100644
index 0000000000..a0101b6266
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs
@@ -0,0 +1,6 @@
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    struct NetworkSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs
new file mode 100644
index 0000000000..2ce56c4df2
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum NotificationFlag : uint
+    {
+        RingtoneFlag = 1 << 0,
+        DownloadCompletionFlag = 1 << 1,
+        EnablesNews = 1 << 8,
+        IncomingLampFlag = 1 << 9,
+    }
+
+    enum NotificationVolume : uint
+    {
+        Mute,
+        Low,
+        High,
+    }
+
+    struct NotificationTime
+    {
+#pragma warning disable CS0649 // Field is never assigned to
+        public uint Hour;
+        public uint Minute;
+#pragma warning restore CS0649
+    }
+
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)]
+    struct NotificationSettings
+    {
+        public NotificationFlag Flag;
+        public NotificationVolume Volume;
+        public NotificationTime HeadTime;
+        public NotificationTime TailTime;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs
new file mode 100644
index 0000000000..845715df21
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x29)]
+    struct NxControllerLegacySettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs
new file mode 100644
index 0000000000..c8f81cecbc
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x42C)]
+    struct NxControllerSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs
new file mode 100644
index 0000000000..b843bcd64a
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs
@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)]
+    struct PtmFuelGaugeParameter
+    {
+        public ushort Rcomp0;
+        public ushort TempCo;
+        public ushort FullCap;
+        public ushort FullCapNom;
+        public ushort IavgEmpty;
+        public ushort QrTable00;
+        public ushort QrTable10;
+        public ushort QrTable20;
+        public ushort QrTable30;
+        public ushort Reserved;
+        public uint Cycles;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs
new file mode 100644
index 0000000000..b4e9b8b28b
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x4)]
+    struct RebootlessSystemUpdateVersion
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs
new file mode 100644
index 0000000000..22ddb85cec
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)]
+    struct SerialNumber
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs
new file mode 100644
index 0000000000..7c7b625a28
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum ServiceDiscoveryControlSettings : uint
+    {
+        IsChangeEnvironmentIdentifierDisabled = 1 << 0,
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs
new file mode 100644
index 0000000000..7493c677ca
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum SleepFlag : uint
+    {
+        SleepsWhilePlayingMedia = 1 << 0,
+        WakesAtPowerStateChange = 1 << 1,
+    }
+
+    enum HandheldSleepPlan : uint
+    {
+        At1Min,
+        At3Min,
+        At5Min,
+        At10Min,
+        At30Min,
+        Never,
+    }
+
+    enum ConsoleSleepPlan : uint
+    {
+        At1Hour,
+        At2Hour,
+        At3Hour,
+        At6Hour,
+        At12Hour,
+        Never,
+    }
+
+    [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 0x4)]
+    struct SleepSettings
+    {
+        public SleepFlag Flags;
+        public HandheldSleepPlan HandheldSleepPlan;
+        public ConsoleSleepPlan ConsoleSleepPlan;
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs
new file mode 100644
index 0000000000..46ec2d7676
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
+    struct TelemetryDirtyFlag
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs
new file mode 100644
index 0000000000..886ec87212
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x80, Pack = 0x8)]
+    struct ThemeId
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs
new file mode 100644
index 0000000000..ac36bcd80c
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs
@@ -0,0 +1,9 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
+    struct ThemeSettings
+    {
+    }
+}
diff --git a/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs
new file mode 100644
index 0000000000..5ee0b85d9f
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Horizon.Sdk.Settings.System
+{
+    [Flags]
+    enum TvFlag : uint
+    {
+        Allows4k = 1 << 0,
+        Allows3d = 1 << 1,
+        AllowsCec = 1 << 2,
+        PreventsScreenBurnIn = 1 << 3,
+    }
+
+    enum TvResolution : uint
+    {
+        Auto,
+        At1080p,
+        At720p,
+        At480p,
+    }
+
+    enum HdmiContentType : uint
+    {
+        None,
+        Graphics,
+        Cinema,
+        Photo,
+        Game,
+    }
+
+    enum RgbRange : uint
+    {
+        Auto,
+        Full,
+        Limited,
+    }
+
+    enum CmuMode : uint
+    {
+        None,
+        ColorInvert,
+        HighContrast,
+        GrayScale,
+    }
+
+    [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x4)]
+    struct TvSettings
+    {
+        public TvFlag Flags;
+        public TvResolution TvResolution;
+        public HdmiContentType HdmiContentType;
+        public RgbRange RgbRange;
+        public CmuMode CmuMode;
+        public float TvUnderscan;
+        public float TvGamma;
+        public float ContrastRatio;
+    }
+}
diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs
index ee62ee84d4..f3fe51940b 100644
--- a/src/Ryujinx.Horizon/ServiceTable.cs
+++ b/src/Ryujinx.Horizon/ServiceTable.cs
@@ -1,5 +1,6 @@
 using Ryujinx.Horizon.Arp;
 using Ryujinx.Horizon.Bcat;
+using Ryujinx.Horizon.Friends;
 using Ryujinx.Horizon.Hshl;
 using Ryujinx.Horizon.Ins;
 using Ryujinx.Horizon.Lbl;
@@ -39,6 +40,7 @@ namespace Ryujinx.Horizon
 
             RegisterService<ArpMain>();
             RegisterService<BcatMain>();
+            RegisterService<FriendsMain>();
             RegisterService<HshlMain>();
             RegisterService<InsMain>();
             RegisterService<LblMain>();