aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Ryujinx.HLE/HOS/Horizon.cs2
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountManager.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs55
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ResultCode.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/Friend.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/FriendFilter.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/UserPresence.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IDaemonSuspendSessionService.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/IFriendService.cs374
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/INotificationService.cs178
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/NotificationEventHandler.cs74
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationInfo.cs13
-rw-r--r--src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs49
-rw-r--r--src/Ryujinx.Horizon/Friends/FriendsMain.cs17
-rw-r--r--src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs11
-rw-r--r--src/Ryujinx.Horizon/Friends/FriendsServerManager.cs36
-rw-r--r--src/Ryujinx.Horizon/HorizonOptions.cs5
-rw-r--r--src/Ryujinx.Horizon/Sdk/Account/IEmulatorAccountManager.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs20
-rw-r--r--src/Ryujinx.Horizon/Sdk/Account/Nickname.cs29
-rw-r--r--src/Ryujinx.Horizon/Sdk/Account/Uid.cs16
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/BlockedUserImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendCandidateImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendDetailedInfoImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs19
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationForViewerImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendInvitationGroupImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendRequestImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendSettingImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/DaemonSuspendSessionService.cs7
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendService.cs1015
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/FriendsServicePermissionLevel.cs (renamed from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/FriendServicePermissionLevel.cs)9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IDaemonSuspendSessionService.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IFriendService.cs97
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/INotificationService.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/IServiceCreator.cs13
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventHandler.cs58
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationEventType.cs (renamed from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/NotificationService/Types/NotificationEventType.cs)2
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/NotificationService.cs172
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/PresenceStatusFilter.cs (renamed from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatusFilter.cs)2
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/ServiceCreator.cs51
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedFriendFilter.cs25
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/Ipc/SizedNotificationInfo.cs13
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/NintendoNetworkIdFriendImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/PlayHistoryImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/PresenceStatus.cs (renamed from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/FriendService/Types/PresenceStatus.cs)2
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileExtraImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/ProfileImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/SnsAccountFriendImpl.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceImpl.cs29
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/UserPresenceViewImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Detail/UserSettingImpl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalog.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/ExternalApplicationCatalogId.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FacedFriendRequestRegistrationKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FriendCode.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGameModeDescription.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationGroupId.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FriendInvitationId.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/FriendResult.cs13
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/InAppScreenName.cs26
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/MiiImageUrlParam.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/MiiName.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/NintendoNetworkIdUserInfo.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryRegistrationKey.cs (renamed from src/Ryujinx.HLE/HOS/Services/Friend/ServiceCreator/Types/PlayHistoryRegistrationKey.cs)8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/PlayHistoryStatistics.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Relationship.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/RequestId.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/SnsAccountLinkage.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/SnsAccountProfile.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/Url.cs30
-rw-r--r--src/Ryujinx.Horizon/Sdk/Friends/WebPageUrl.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/BatteryLot.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerOffset.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AccelerometerScale.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcdsaCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvBlsRootCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboEcqvCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AmiiboKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickFactoryCalibration.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/AnalogStickModelParameter.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/BdAddress.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/ConfigurationId1.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/ConsoleSixAxisSensorHorizontalOffset.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/CountryCode.cs8
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/EccB233DeviceKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/GameCardKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeOffset.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/GyroscopeScale.cs12
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/MacAddress.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/Rsa2048DeviceKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/SerialNumber.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/SpeakerParameter.cs32
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/SslCertificate.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Factory/SslKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/Language.cs24
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/LanguageCode.cs63
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/SettingsItemKey.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/SettingsName.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AccountNotificationSettings.cs15
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AccountOnlineStorageSettings.cs6
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AccountSettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AllowedSslHost.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AnalogStickUserCalibration.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AppletLaunchFlag.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/AudioVolume.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettings.cs22
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/BacklightSettingsEx.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/BlePairingSettings.cs6
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/BluetoothDevicesSettings.cs29
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigRegisteredSettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ButtonConfigSettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationBias.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAccelerationGain.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularAcceleration.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityBias.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityGain.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ConsoleSixAxisSensorAngularVelocityTimeBias.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/DataDeletionSettings.cs18
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/DeviceNickName.cs25
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/Edid.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/EulaVersion.cs6
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/FatalDirtyFlag.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersion.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/FirmwareVersionDigest.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/HomeMenuScheme.cs14
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/HostFsMountPoint.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/InitialLaunchSettings.cs14
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/NetworkSettings.cs6
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/NotificationSettings.cs38
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerLegacySettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/NxControllerSettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/PtmFuelGaugeParameter.cs20
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/RebootlessSystemUpdateVersion.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/SerialNumber.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ServiceDiscoveryControlSettings.cs10
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/SleepSettings.cs40
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/TelemetryDirtyFlag.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ThemeId.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/ThemeSettings.cs9
-rw-r--r--src/Ryujinx.Horizon/Sdk/Settings/System/TvSettings.cs59
-rw-r--r--src/Ryujinx.Horizon/ServiceTable.cs2
148 files changed, 3026 insertions, 832 deletions
diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs
index 1a402240..cd171958 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 924ac3fb..c724660e 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 3f15f3fc..00000000
--- 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 9f612059..00000000
--- 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 28745c3f..00000000
--- 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 5f13f313..00000000
--- 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 80d14205..00000000
--- 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 3b1601ab..00000000
--- 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 54d23e88..00000000
--- 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 8fc7a460..00000000
--- 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 88627fd7..00000000
--- 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 aa58433d..00000000
--- 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 00000000..523c617a
--- /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 00000000..0f119cf0
--- /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 00000000..f567db30
--- /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 00000000..5026206b
--- /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 e3c862da..79462056 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 00000000..af02cc8e
--- /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 00000000..2512975e
--- /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 00000000..1f351ee3
--- /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 a76e6d25..ada2c02b 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 00000000..23bad3d1
--- /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 00000000..d5f8a031
--- /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 00000000..21e99c75
--- /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 00000000..1b46dccd
--- /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 00000000..d22ca4b9
--- /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 00000000..416ba365
--- /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 00000000..ef923834
--- /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 00000000..ba567169
--- /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 00000000..f711d31f
--- /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 00000000..aaf88ed0
--- /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 00000000..1b4c8c30
--- /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
index 7902d9c5..f4bbe100 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 00000000..2bb0434e
--- /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 00000000..c19d0b78
--- /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 00000000..a3a28e8c
--- /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 00000000..58e2569b
--- /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 00000000..61c692a6
--- /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
index 363e03ea..e46fc9b7 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 00000000..534bf63e
--- /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
index c9a54250..3ea10587 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 00000000..1be804df
--- /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 00000000..d93a2ae2
--- /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 00000000..0da26a1a
--- /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 00000000..66d61e4c
--- /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 00000000..9f90f0c8
--- /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
index 7930aff0..5ddbe14e 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 00000000..1548d725
--- /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 00000000..f779d93c
--- /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 00000000..dc6adf03
--- /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 00000000..cf4520cf
--- /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 00000000..04c09260
--- /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 00000000..9d057fb1
--- /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 00000000..0d9c157d
--- /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 00000000..7ed36cd9
--- /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 00000000..6b5812f6
--- /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 00000000..d78497a1
--- /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 00000000..29b4a097
--- /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 00000000..ef53882b
--- /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 00000000..7be19d57
--- /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 00000000..5965d508
--- /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 00000000..22574a5c
--- /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 00000000..8790bb93
--- /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 00000000..e73c0d83
--- /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 00000000..a2a9e046
--- /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
index 9687c547..bb672a79 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 00000000..ea3e3d99
--- /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 00000000..efba09a8
--- /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 00000000..3236a1d7
--- /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 00000000..b4660d9e
--- /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 00000000..d872b3da
--- /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 00000000..833ee123
--- /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 00000000..85488af6
--- /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 00000000..71185fcd
--- /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 00000000..292a368f
--- /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 00000000..ef9d17ef
--- /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 00000000..7cbab2f0
--- /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 00000000..8d16b51b
--- /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 00000000..da6ca53b
--- /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 00000000..e69e38a1
--- /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 00000000..43742fbb
--- /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 00000000..43ffccb0
--- /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 00000000..3fe6f322
--- /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 00000000..a442032c
--- /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 00000000..519d72e8
--- /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 00000000..40565805
--- /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 00000000..c5503edc
--- /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 00000000..daf2ba3b
--- /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 00000000..727408ed
--- /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 00000000..a0481f4d
--- /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 00000000..ce3908af
--- /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 00000000..81144ac4
--- /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 00000000..801d117c
--- /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 00000000..7812281f
--- /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 00000000..65e222ee
--- /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 00000000..57217059
--- /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 00000000..d2fd51cf
--- /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 00000000..af664cdc
--- /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 00000000..f147f66f
--- /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 00000000..5d825216
--- /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 00000000..7d4b4136
--- /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 00000000..4ffc66fe
--- /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 00000000..dc971269
--- /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 00000000..66118410
--- /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 00000000..6864b8cd
--- /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 00000000..a2cbad6a
--- /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 00000000..3ed77e52
--- /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 00000000..bd27ea0b
--- /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 00000000..cb90daf1
--- /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 00000000..36023da9
--- /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 00000000..00d6f4d0
--- /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 00000000..d246bc2b
--- /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 00000000..00de6869
--- /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 00000000..347afdfe
--- /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 00000000..d9b01f9f
--- /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 00000000..ec5c97c5
--- /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 00000000..8bd4924e
--- /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 00000000..2f06e32e
--- /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 00000000..c70d4ff2
--- /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 00000000..0803beb8
--- /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 00000000..831e44bd
--- /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 00000000..83d1faa8
--- /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 00000000..68e0c614
--- /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 00000000..47f3d951
--- /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 00000000..a10a265d
--- /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 00000000..99c9f981
--- /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 00000000..3ff56685
--- /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 00000000..65905b1b
--- /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 00000000..6be94115
--- /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 00000000..39825e01
--- /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 00000000..0027d7ef
--- /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 00000000..cc7b317b
--- /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 00000000..1a66abac
--- /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 00000000..b3989de7
--- /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 00000000..a0101b62
--- /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 00000000..2ce56c4d
--- /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 00000000..845715df
--- /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 00000000..c8f81cec
--- /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 00000000..b843bcd6
--- /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 00000000..b4e9b8b2
--- /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 00000000..22ddb85c
--- /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 00000000..7c7b625a
--- /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 00000000..7493c677
--- /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 00000000..46ec2d76
--- /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 00000000..886ec872
--- /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 00000000..ac36bcd8
--- /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 00000000..5ee0b85d
--- /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 ee62ee84..f3fe5194 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>();