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
parent b2b736abc2
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 FriendServicePermissionLevel PermissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IDaemonSuspendSessionService()
public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
{
_commands = new Dictionary<int, ServiceProcessRequest>
{
// ...
};
PermissionLevel = permissionLevel;
}
}
}

View file

@ -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<int, ServiceProcessRequest> _commands;
private FriendServicePermissionLevel _permissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IFriendService()
public IFriendService(FriendServicePermissionLevel permissionLevel)
{
_commands = new Dictionary<int, ServiceProcessRequest>
{
{ 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<nn::account::NetworkServiceAccountId>
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<nn::account::NetworkServiceAccountId, 0xa>
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<UInt128>();
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
// 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<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)
{
UInt128 uuid = new UInt128(
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64());
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
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<UInt128>();
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<Unknown1, type: 0x19, size: 0xe0>
// nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
public long UpdateUserPresence(ServiceCtx context)
{
UInt128 uuid = new UInt128(
context.RequestData.ReadInt64(),
context.RequestData.ReadInt64());
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
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<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;
}

View file

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

View file

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

View file

@ -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<int, ServiceProcessRequest> _commands;
private FriendServicePermissionLevel _permissionLevel;
public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
public IServiceCreator()
public IServiceCreator(FriendServicePermissionLevel permissionLevel)
{
_commands = new Dictionary<int, ServiceProcessRequest>
{
@ -20,35 +23,37 @@ namespace Ryujinx.HLE.HOS.Services.Friend
{ 1, CreateNotificationService }, // 2.0.0+
{ 2, CreateDaemonSuspendSessionService }, // 4.0.0+
};
_permissionLevel = permissionLevel;
}
// 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;
}
// 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)
{
return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
}
MakeObject(context, new INotificationService(userId));
MakeObject(context, new INotificationService(userId, _permissionLevel));
return 0;
}
// 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;
}

View file

@ -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<UInt128>();
long saveId = context.RequestData.ReadInt64();
SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadByte();

View file

@ -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();

View file

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