diff --git a/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs b/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs new file mode 100644 index 0000000000..f2a73c59c8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Friend +{ + [Flags] + enum FriendServicePermissionLevel + { + Admin = -1, + User = 1, + Overlay = 3, + Manager = 7, + System = 9 + } +} diff --git a/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs b/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs index b1f23dd55a..f9de1db1e6 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs @@ -7,14 +7,18 @@ namespace Ryujinx.HLE.HOS.Services.Friend { private Dictionary _commands; + private FriendServicePermissionLevel PermissionLevel; + public override IReadOnlyDictionary Commands => _commands; - public IDaemonSuspendSessionService() + public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel) { _commands = new Dictionary { // ... }; + + PermissionLevel = permissionLevel; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs index 35f40818ab..5eb495a07a 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs @@ -1,8 +1,12 @@ +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Utilities; using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using static Ryujinx.HLE.HOS.ErrorCode; namespace Ryujinx.HLE.HOS.Services.Friend { @@ -10,64 +14,108 @@ namespace Ryujinx.HLE.HOS.Services.Friend { private Dictionary _commands; + private FriendServicePermissionLevel _permissionLevel; + public override IReadOnlyDictionary Commands => _commands; - public IFriendService() + public IFriendService(FriendServicePermissionLevel permissionLevel) { _commands = new Dictionary { + { 10100, GetFriendListIds }, { 10101, GetFriendList }, { 10600, DeclareOpenOnlinePlaySession }, { 10601, DeclareCloseOnlinePlaySession }, { 10610, UpdateUserPresence } }; + + _permissionLevel = permissionLevel; } - // nn::friends::GetFriendListGetFriendListIds(nn::account::Uid, int Unknown0, nn::friends::detail::ipc::SizedFriendFilter, ulong Unknown1) -> int CounterIds, array - public long GetFriendList(ServiceCtx context) + // nn::friends::GetFriendListIds(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) -> int outCount, array + public long GetFriendListIds(ServiceCtx context) { - UInt128 uuid = new UInt128( - context.RequestData.ReadInt64(), - context.RequestData.ReadInt64()); + int offset = context.RequestData.ReadInt32(); - int unknown0 = context.RequestData.ReadInt32(); + // Padding + context.RequestData.ReadInt32(); - FriendFilter filter = new FriendFilter + UInt128 uuid = context.RequestData.ReadStruct(); + + FriendFilter filter = context.RequestData.ReadStruct(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (uuid.IsNull) { - PresenceStatus = (PresenceStatusFilter)context.RequestData.ReadInt32(), - IsFavoriteOnly = context.RequestData.ReadBoolean(), - IsSameAppPresenceOnly = context.RequestData.ReadBoolean(), - IsSameAppPlayedOnly = context.RequestData.ReadBoolean(), - IsArbitraryAppPlayedOnly = context.RequestData.ReadBoolean(), - PresenceGroupId = context.RequestData.ReadInt64() - }; - - long unknown1 = context.RequestData.ReadInt64(); + return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); + } // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. context.ResponseData.Write(0); - Logger.PrintStub(LogClass.ServiceFriend, new { + Logger.PrintStub(LogClass.ServiceFriend, new + { UserId = uuid.ToString(), - unknown0, + offset, filter.PresenceStatus, filter.IsFavoriteOnly, filter.IsSameAppPresenceOnly, filter.IsSameAppPlayedOnly, filter.IsArbitraryAppPlayedOnly, filter.PresenceGroupId, - unknown1 }); return 0; } - // DeclareOpenOnlinePlaySession(nn::account::Uid) + // nn::friends::GetFriendList(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) -> int outCount, array + public long GetFriendList(ServiceCtx context) + { + int offset = context.RequestData.ReadInt32(); + + // Padding + context.RequestData.ReadInt32(); + + UInt128 uuid = context.RequestData.ReadStruct(); + + FriendFilter filter = context.RequestData.ReadStruct(); + + // Pid placeholder + context.RequestData.ReadInt64(); + + if (uuid.IsNull) + { + return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); + } + + // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. + context.ResponseData.Write(0); + + Logger.PrintStub(LogClass.ServiceFriend, new { + UserId = uuid.ToString(), + offset, + filter.PresenceStatus, + filter.IsFavoriteOnly, + filter.IsSameAppPresenceOnly, + filter.IsSameAppPlayedOnly, + filter.IsArbitraryAppPlayedOnly, + filter.PresenceGroupId, + }); + + return 0; + } + + // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid) public long DeclareOpenOnlinePlaySession(ServiceCtx context) { - UInt128 uuid = new UInt128( - context.RequestData.ReadInt64(), - context.RequestData.ReadInt64()); + UInt128 uuid = context.RequestData.ReadStruct(); + + if (uuid.IsNull) + { + return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); + } if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) { @@ -79,12 +127,15 @@ namespace Ryujinx.HLE.HOS.Services.Friend return 0; } - // DeclareCloseOnlinePlaySession(nn::account::Uid) + // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid) public long DeclareCloseOnlinePlaySession(ServiceCtx context) { - UInt128 uuid = new UInt128( - context.RequestData.ReadInt64(), - context.RequestData.ReadInt64()); + UInt128 uuid = context.RequestData.ReadStruct(); + + if (uuid.IsNull) + { + return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); + } if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) { @@ -96,21 +147,31 @@ namespace Ryujinx.HLE.HOS.Services.Friend return 0; } - // UpdateUserPresence(nn::account::Uid, ulong Unknown0) -> buffer + // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer) public long UpdateUserPresence(ServiceCtx context) { - UInt128 uuid = new UInt128( - context.RequestData.ReadInt64(), - context.RequestData.ReadInt64()); + UInt128 uuid = context.RequestData.ReadStruct(); - long unknown0 = context.RequestData.ReadInt64(); + // Pid placeholder + context.RequestData.ReadInt64(); long position = context.Request.PtrBuff[0].Position; long size = context.Request.PtrBuff[0].Size; - // TODO: Write the buffer content. + byte[] bufferContent = context.Memory.ReadBytes(position, size); - Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), unknown0 }); + if (uuid.IsNull) + { + return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); + } + + int elementCount = bufferContent.Length / Marshal.SizeOf(); + + using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent))) + { + UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray(elementCount); + Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray }); + } return 0; } diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs index 31459f7d68..98ca26166d 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs @@ -1,6 +1,9 @@ +using Ryujinx.HLE.Utilities; +using System.Runtime.InteropServices; + namespace Ryujinx.HLE.HOS.Services.Friend { - enum PresenceStatusFilter + enum PresenceStatusFilter : uint { None, Online, @@ -8,13 +11,49 @@ namespace Ryujinx.HLE.HOS.Services.Friend OnlineOrOnlinePlay } + enum PresenceStatus : uint + { + Offline, + Online, + OnlinePlay, + } + + [StructLayout(LayoutKind.Sequential)] struct FriendFilter { public PresenceStatusFilter PresenceStatus; - public bool IsFavoriteOnly; - public bool IsSameAppPresenceOnly; - public bool IsSameAppPlayedOnly; - public bool IsArbitraryAppPlayedOnly; - public long PresenceGroupId; + + [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; + } + + [StructLayout(LayoutKind.Sequential)] + struct UserPresence + { + public UInt128 UserId; + public long LastTimeOnlineTimestamp; + public PresenceStatus Status; + + uint padding; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)] + public char[] AppKeyValueStorage; + + + public override string ToString() + { + return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}"; + } } } diff --git a/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs b/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs index 8b684e6b8b..b18f3bf1ad 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs @@ -14,11 +14,13 @@ namespace Ryujinx.HLE.HOS.Services.Friend private KEvent _notificationEvent; private int _notificationEventHandle = 0; + private FriendServicePermissionLevel _permissionLevel; + private Dictionary _commands; public override IReadOnlyDictionary Commands => _commands; - public INotificationService(UInt128 userId) + public INotificationService(UInt128 userId, FriendServicePermissionLevel permissionLevel) { _commands = new Dictionary { @@ -28,6 +30,8 @@ namespace Ryujinx.HLE.HOS.Services.Friend }; _userId = userId; + + _permissionLevel = permissionLevel; } public long GetEvent(ServiceCtx context) diff --git a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs index 81281dc2d2..07e44d934e 100644 --- a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs +++ b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.Utilities; using System.Collections.Generic; @@ -10,9 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Friend { private Dictionary _commands; + private FriendServicePermissionLevel _permissionLevel; + public override IReadOnlyDictionary Commands => _commands; - public IServiceCreator() + public IServiceCreator(FriendServicePermissionLevel permissionLevel) { _commands = new Dictionary { @@ -20,35 +23,37 @@ namespace Ryujinx.HLE.HOS.Services.Friend { 1, CreateNotificationService }, // 2.0.0+ { 2, CreateDaemonSuspendSessionService }, // 4.0.0+ }; + + _permissionLevel = permissionLevel; } // CreateFriendService() -> object - public static long CreateFriendService(ServiceCtx context) + public long CreateFriendService(ServiceCtx context) { - MakeObject(context, new IFriendService()); + MakeObject(context, new IFriendService(_permissionLevel)); return 0; } // CreateNotificationService(nn::account::Uid) -> object - public static long CreateNotificationService(ServiceCtx context) + public long CreateNotificationService(ServiceCtx context) { - UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); + UInt128 userId = context.RequestData.ReadStruct(); if (userId.IsNull) { return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); } - MakeObject(context, new INotificationService(userId)); + MakeObject(context, new INotificationService(userId, _permissionLevel)); return 0; } // CreateDaemonSuspendSessionService() -> object - public static long CreateDaemonSuspendSessionService(ServiceCtx context) + public long CreateDaemonSuspendSessionService(ServiceCtx context) { - MakeObject(context, new IDaemonSuspendSessionService()); + MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel)); return 0; } diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs index 7207aaf020..a1afe9beb1 100644 --- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs +++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs @@ -1,6 +1,7 @@ using LibHac; using LibHac.Fs; using LibHac.Fs.NcaUtils; +using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Ipc; @@ -234,9 +235,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv long titleId = context.RequestData.ReadInt64(); - UInt128 userId = new UInt128( - context.RequestData.ReadInt64(), - context.RequestData.ReadInt64()); + UInt128 userId = context.RequestData.ReadStruct(); long saveId = context.RequestData.ReadInt64(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte(); diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs index 7cd943e026..3edb5619ee 100644 --- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs +++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs @@ -100,10 +100,19 @@ namespace Ryujinx.HLE.HOS.Services return new IeTicketService(); case "friend:a": - return new Friend.IServiceCreator(); + return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Admin); case "friend:u": - return new Friend.IServiceCreator(); + return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.User); + + case "friend:v": + return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Overlay); + + case "friend:m": + return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Manager); + + case "friend:s": + return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.System); case "fsp-srv": return new IFileSystemProxy(); diff --git a/Ryujinx.HLE/Utilities/UInt128.cs b/Ryujinx.HLE/Utilities/UInt128.cs index 8f5fc28fc6..deb2b7ad57 100644 --- a/Ryujinx.HLE/Utilities/UInt128.cs +++ b/Ryujinx.HLE/Utilities/UInt128.cs @@ -1,13 +1,15 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.Utilities { + [StructLayout(LayoutKind.Sequential)] public struct UInt128 { - public long High { get; private set; } public long Low { get; private set; } + public long High { get; private set; } public bool IsNull => (Low | High) == 0;