Refactor the friend namespace and UInt128

This commit also:
- Fix GetFriendsList arguments ordering.
- Add GetFriendListIds.
- Expose the permission level of the port instance.
- InvalidUUID => InvalidArgument
This commit is contained in:
Thog 2019-06-27 10:26:04 +02:00
commit 83282af8e0
No known key found for this signature in database
GPG key ID: 0CD291558FAFDBC6
9 changed files with 196 additions and 57 deletions

View file

@ -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
}
}

View file

@ -7,14 +7,18 @@ namespace Ryujinx.HLE.HOS.Services.Friend
{ {
private Dictionary<int, ServiceProcessRequest> _commands; private Dictionary<int, ServiceProcessRequest> _commands;
private FriendServicePermissionLevel PermissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands; public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IDaemonSuspendSessionService() public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
{ {
_commands = new Dictionary<int, ServiceProcessRequest> _commands = new Dictionary<int, ServiceProcessRequest>
{ {
// ... // ...
}; };
PermissionLevel = permissionLevel;
} }
} }
} }

View file

@ -1,8 +1,12 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using static Ryujinx.HLE.HOS.ErrorCode;
namespace Ryujinx.HLE.HOS.Services.Friend namespace Ryujinx.HLE.HOS.Services.Friend
{ {
@ -10,64 +14,108 @@ namespace Ryujinx.HLE.HOS.Services.Friend
{ {
private Dictionary<int, ServiceProcessRequest> _commands; private Dictionary<int, ServiceProcessRequest> _commands;
private FriendServicePermissionLevel _permissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands; public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IFriendService() public IFriendService(FriendServicePermissionLevel permissionLevel)
{ {
_commands = new Dictionary<int, ServiceProcessRequest> _commands = new Dictionary<int, ServiceProcessRequest>
{ {
{ 10100, GetFriendListIds },
{ 10101, GetFriendList }, { 10101, GetFriendList },
{ 10600, DeclareOpenOnlinePlaySession }, { 10600, DeclareOpenOnlinePlaySession },
{ 10601, DeclareCloseOnlinePlaySession }, { 10601, DeclareCloseOnlinePlaySession },
{ 10610, UpdateUserPresence } { 10610, UpdateUserPresence }
}; };
_permissionLevel = permissionLevel;
} }
// nn::friends::GetFriendListGetFriendListIds(nn::account::Uid, int Unknown0, nn::friends::detail::ipc::SizedFriendFilter, ulong Unknown1) -> int CounterIds, array<nn::account::NetworkServiceAccountId> // nn::friends::GetFriendListIds(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
public long GetFriendList(ServiceCtx context) public long GetFriendListIds(ServiceCtx context)
{ {
UInt128 uuid = new UInt128( int offset = context.RequestData.ReadInt32();
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64());
int unknown0 = context.RequestData.ReadInt32(); // Padding
context.RequestData.ReadInt32();
FriendFilter filter = new FriendFilter UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
// Pid placeholder
context.RequestData.ReadInt64();
if (uuid.IsNull)
{ {
PresenceStatus = (PresenceStatusFilter)context.RequestData.ReadInt32(), return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
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();
// There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty. // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
context.ResponseData.Write(0); context.ResponseData.Write(0);
Logger.PrintStub(LogClass.ServiceFriend, new { Logger.PrintStub(LogClass.ServiceFriend, new
{
UserId = uuid.ToString(), UserId = uuid.ToString(),
unknown0, offset,
filter.PresenceStatus, filter.PresenceStatus,
filter.IsFavoriteOnly, filter.IsFavoriteOnly,
filter.IsSameAppPresenceOnly, filter.IsSameAppPresenceOnly,
filter.IsSameAppPlayedOnly, filter.IsSameAppPlayedOnly,
filter.IsArbitraryAppPlayedOnly, filter.IsArbitraryAppPlayedOnly,
filter.PresenceGroupId, filter.PresenceGroupId,
unknown1
}); });
return 0; 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<nn::account::NetworkServiceAccountId, 0x6>
public long GetFriendList(ServiceCtx context)
{
int offset = context.RequestData.ReadInt32();
// Padding
context.RequestData.ReadInt32();
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
// 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) public long DeclareOpenOnlinePlaySession(ServiceCtx context)
{ {
UInt128 uuid = new UInt128( UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64()); if (uuid.IsNull)
{
return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
}
if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
{ {
@ -79,12 +127,15 @@ namespace Ryujinx.HLE.HOS.Services.Friend
return 0; return 0;
} }
// DeclareCloseOnlinePlaySession(nn::account::Uid) // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid)
public long DeclareCloseOnlinePlaySession(ServiceCtx context) public long DeclareCloseOnlinePlaySession(ServiceCtx context)
{ {
UInt128 uuid = new UInt128( UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64()); if (uuid.IsNull)
{
return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
}
if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile)) if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
{ {
@ -96,21 +147,31 @@ namespace Ryujinx.HLE.HOS.Services.Friend
return 0; return 0;
} }
// UpdateUserPresence(nn::account::Uid, ulong Unknown0) -> buffer<Unknown1, type: 0x19, size: 0xe0> // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
public long UpdateUserPresence(ServiceCtx context) public long UpdateUserPresence(ServiceCtx context)
{ {
UInt128 uuid = new UInt128( UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64());
long unknown0 = context.RequestData.ReadInt64(); // Pid placeholder
context.RequestData.ReadInt64();
long position = context.Request.PtrBuff[0].Position; long position = context.Request.PtrBuff[0].Position;
long size = context.Request.PtrBuff[0].Size; 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<UserPresence>();
using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent)))
{
UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray<UserPresence>(elementCount);
Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray });
}
return 0; return 0;
} }

View file

@ -1,6 +1,9 @@
using Ryujinx.HLE.Utilities;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Friend namespace Ryujinx.HLE.HOS.Services.Friend
{ {
enum PresenceStatusFilter enum PresenceStatusFilter : uint
{ {
None, None,
Online, Online,
@ -8,13 +11,49 @@ namespace Ryujinx.HLE.HOS.Services.Friend
OnlineOrOnlinePlay OnlineOrOnlinePlay
} }
enum PresenceStatus : uint
{
Offline,
Online,
OnlinePlay,
}
[StructLayout(LayoutKind.Sequential)]
struct FriendFilter struct FriendFilter
{ {
public PresenceStatusFilter PresenceStatus; public PresenceStatusFilter PresenceStatus;
[MarshalAs(UnmanagedType.I1)]
public bool IsFavoriteOnly; public bool IsFavoriteOnly;
[MarshalAs(UnmanagedType.I1)]
public bool IsSameAppPresenceOnly; public bool IsSameAppPresenceOnly;
[MarshalAs(UnmanagedType.I1)]
public bool IsSameAppPlayedOnly; public bool IsSameAppPlayedOnly;
[MarshalAs(UnmanagedType.I1)]
public bool IsArbitraryAppPlayedOnly; public bool IsArbitraryAppPlayedOnly;
public long PresenceGroupId; 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} }}";
}
}
} }

View file

@ -14,11 +14,13 @@ namespace Ryujinx.HLE.HOS.Services.Friend
private KEvent _notificationEvent; private KEvent _notificationEvent;
private int _notificationEventHandle = 0; private int _notificationEventHandle = 0;
private FriendServicePermissionLevel _permissionLevel;
private Dictionary<int, ServiceProcessRequest> _commands; private Dictionary<int, ServiceProcessRequest> _commands;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands; public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public INotificationService(UInt128 userId) public INotificationService(UInt128 userId, FriendServicePermissionLevel permissionLevel)
{ {
_commands = new Dictionary<int, ServiceProcessRequest> _commands = new Dictionary<int, ServiceProcessRequest>
{ {
@ -28,6 +30,8 @@ namespace Ryujinx.HLE.HOS.Services.Friend
}; };
_userId = userId; _userId = userId;
_permissionLevel = permissionLevel;
} }
public long GetEvent(ServiceCtx context) public long GetEvent(ServiceCtx context)

View file

@ -1,3 +1,4 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.Utilities; using Ryujinx.HLE.Utilities;
using System.Collections.Generic; using System.Collections.Generic;
@ -10,9 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Friend
{ {
private Dictionary<int, ServiceProcessRequest> _commands; private Dictionary<int, ServiceProcessRequest> _commands;
private FriendServicePermissionLevel _permissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands; public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IServiceCreator() public IServiceCreator(FriendServicePermissionLevel permissionLevel)
{ {
_commands = new Dictionary<int, ServiceProcessRequest> _commands = new Dictionary<int, ServiceProcessRequest>
{ {
@ -20,35 +23,37 @@ namespace Ryujinx.HLE.HOS.Services.Friend
{ 1, CreateNotificationService }, // 2.0.0+ { 1, CreateNotificationService }, // 2.0.0+
{ 2, CreateDaemonSuspendSessionService }, // 4.0.0+ { 2, CreateDaemonSuspendSessionService }, // 4.0.0+
}; };
_permissionLevel = permissionLevel;
} }
// CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService> // CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService>
public static long CreateFriendService(ServiceCtx context) public long CreateFriendService(ServiceCtx context)
{ {
MakeObject(context, new IFriendService()); MakeObject(context, new IFriendService(_permissionLevel));
return 0; return 0;
} }
// CreateNotificationService(nn::account::Uid) -> object<nn::friends::detail::ipc::INotificationService> // CreateNotificationService(nn::account::Uid) -> object<nn::friends::detail::ipc::INotificationService>
public static long CreateNotificationService(ServiceCtx context) public long CreateNotificationService(ServiceCtx context)
{ {
UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10)); UInt128 userId = context.RequestData.ReadStruct<UInt128>();
if (userId.IsNull) if (userId.IsNull)
{ {
return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument); return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
} }
MakeObject(context, new INotificationService(userId)); MakeObject(context, new INotificationService(userId, _permissionLevel));
return 0; return 0;
} }
// CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService> // CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService>
public static long CreateDaemonSuspendSessionService(ServiceCtx context) public long CreateDaemonSuspendSessionService(ServiceCtx context)
{ {
MakeObject(context, new IDaemonSuspendSessionService()); MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel));
return 0; return 0;
} }

View file

@ -1,6 +1,7 @@
using LibHac; using LibHac;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.NcaUtils; using LibHac.Fs.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
@ -234,9 +235,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
long titleId = context.RequestData.ReadInt64(); long titleId = context.RequestData.ReadInt64();
UInt128 userId = new UInt128( UInt128 userId = context.RequestData.ReadStruct<UInt128>();
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64());
long saveId = context.RequestData.ReadInt64(); long saveId = context.RequestData.ReadInt64();
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte(); SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte();

View file

@ -100,10 +100,19 @@ namespace Ryujinx.HLE.HOS.Services
return new IeTicketService(); return new IeTicketService();
case "friend:a": case "friend:a":
return new Friend.IServiceCreator(); return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Admin);
case "friend:u": 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": case "fsp-srv":
return new IFileSystemProxy(); return new IFileSystemProxy();

View file

@ -1,13 +1,15 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Utilities namespace Ryujinx.HLE.Utilities
{ {
[StructLayout(LayoutKind.Sequential)]
public struct UInt128 public struct UInt128
{ {
public long High { get; private set; }
public long Low { get; private set; } public long Low { get; private set; }
public long High { get; private set; }
public bool IsNull => (Low | High) == 0; public bool IsNull => (Low | High) == 0;