Migrate friends service to new IPC (#6174)

* Migrate friends service to new IPC

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

* Wrong length

* Format whitespace

* PR feedback

* Fill in structs from PR feedback

* Missed that one

* Somehow forgot to save that one

* Fill in enums from PR review

* Language enum, NotificationTime

* Format whitespace

* Fix the warning
This commit is contained in:
gdkchan 2024-01-29 18:45:40 -03:00 committed by GitHub
parent 20a392ad55
commit 4117c13377
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
148 changed files with 3026 additions and 832 deletions

View File

@ -330,7 +330,7 @@ namespace Ryujinx.HLE.HOS
HorizonFsClient fsClient = new(this); HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable(); 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) foreach (var service in services)
{ {

View File

@ -4,6 +4,7 @@ using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Sdk.Account;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -11,7 +12,7 @@ using System.Linq;
namespace Ryujinx.HLE.HOS.Services.Account.Acc namespace Ryujinx.HLE.HOS.Services.Account.Acc
{ {
public class AccountManager public class AccountManager : IEmulatorAccountManager
{ {
public static readonly UserId DefaultUserId = new("00000000000000010000000000000000"); public static readonly UserId DefaultUserId = new("00000000000000010000000000000000");
@ -106,6 +107,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
_accountSaveDataManager.Save(_profiles); _accountSaveDataManager.Save(_profiles);
} }
public void OpenUserOnlinePlay(Uid userId)
{
OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
}
public void OpenUserOnlinePlay(UserId userId) public void OpenUserOnlinePlay(UserId userId)
{ {
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
@ -127,6 +133,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
_accountSaveDataManager.Save(_profiles); _accountSaveDataManager.Save(_profiles);
} }
public void CloseUserOnlinePlay(Uid userId)
{
CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
}
public void CloseUserOnlinePlay(UserId userId) public void CloseUserOnlinePlay(UserId userId)
{ {
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile)) if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
namespace Ryujinx.Horizon.Friends
{
enum FriendsPortIndex
{
Admin,
User,
Viewer,
Manager,
System,
}
}

View File

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

View File

@ -1,4 +1,5 @@
using LibHac; using LibHac;
using Ryujinx.Horizon.Sdk.Account;
using Ryujinx.Horizon.Sdk.Fs; using Ryujinx.Horizon.Sdk.Fs;
namespace Ryujinx.Horizon namespace Ryujinx.Horizon
@ -10,13 +11,15 @@ namespace Ryujinx.Horizon
public HorizonClient BcatClient { get; } public HorizonClient BcatClient { get; }
public IFsClient FsClient { 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; IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true; ThrowOnInvalidCommandIds = true;
BcatClient = bcatClient; BcatClient = bcatClient;
FsClient = fsClient; FsClient = fsClient;
AccountManager = accountManager;
} }
} }
} }

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Horizon.Sdk.Account
{
public interface IEmulatorAccountManager
{
void OpenUserOnlinePlay(Uid userId);
void CloseUserOnlinePlay(Uid userId);
}
}

View File

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

View File

@ -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]);
}
}
}

View File

@ -6,16 +6,16 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Account namespace Ryujinx.Horizon.Sdk.Account
{ {
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
readonly record struct Uid public readonly record struct Uid
{ {
public readonly long High; public readonly ulong High;
public readonly long Low; public readonly ulong Low;
public bool IsNull => (Low | High) == 0; public bool IsNull => (Low | High) == 0;
public static Uid Null => new(0, 0); public static Uid Null => new(0, 0);
public Uid(long low, long high) public Uid(ulong low, ulong high)
{ {
Low = low; Low = low;
High = high; High = high;
@ -23,8 +23,8 @@ namespace Ryujinx.Horizon.Sdk.Account
public Uid(byte[] bytes) public Uid(byte[] bytes)
{ {
High = BitConverter.ToInt64(bytes, 0); High = BitConverter.ToUInt64(bytes, 0);
Low = BitConverter.ToInt64(bytes, 8); Low = BitConverter.ToUInt64(bytes, 8);
} }
public Uid(string hex) public Uid(string hex)
@ -34,8 +34,8 @@ namespace Ryujinx.Horizon.Sdk.Account
throw new ArgumentException("Invalid Hex value!", nameof(hex)); throw new ArgumentException("Invalid Hex value!", nameof(hex));
} }
Low = Convert.ToInt64(hex[16..], 16); Low = Convert.ToUInt64(hex[16..], 16);
High = Convert.ToInt64(hex[..16], 16); High = Convert.ToUInt64(hex[..16], 16);
} }
public void Write(BinaryWriter binaryWriter) public void Write(BinaryWriter binaryWriter)

View File

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

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct BlockedUserImpl
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct FriendCandidateImpl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x800)]
struct FriendDetailedInfoImpl
{
}
}

View File

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

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct FriendInvitationForViewerImpl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x1400)]
struct FriendInvitationGroupImpl
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct FriendRequestImpl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x40)]
struct FriendSettingImpl
{
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
{
partial class DaemonSuspendSessionService : IDaemonSuspendSessionService
{
// NOTE: This service has no commands.
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,13 @@
using System; namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
{ {
[Flags] enum FriendsServicePermissionLevel
enum FriendServicePermissionLevel
{ {
UserMask = 1, UserMask = 1,
ViewerMask = 2, ViewerMask = 2,
ManagerMask = 4, ManagerMask = 4,
SystemMask = 8, SystemMask = 8,
Administrator = -1, Admin = -1,
User = UserMask, User = UserMask,
Viewer = UserMask | ViewerMask, Viewer = UserMask | ViewerMask,
Manager = UserMask | ViewerMask | ManagerMask, Manager = UserMask | ViewerMask | ManagerMask,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
{ {
enum NotificationEventType : uint enum NotificationEventType : uint
{ {

View File

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

View File

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
{ {
enum PresenceStatusFilter : uint enum PresenceStatusFilter : uint
{ {

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct NintendoNetworkIdFriendImpl
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct PlayHistoryImpl
{
}
}

View File

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService namespace Ryujinx.Horizon.Sdk.Friends.Detail
{ {
enum PresenceStatus : uint enum PresenceStatus : uint
{ {

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x400)]
struct ProfileExtraImpl
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct ProfileImpl
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
struct SnsAccountFriendImpl
{
}
}

View File

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

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0xE0)]
struct UserPresenceViewImpl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x800)]
struct UserSettingImpl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x4B8)]
struct ExternalApplicationCatalog
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
struct ExternalApplicationCatalogId
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x40, Pack = 0x1)]
struct FacedFriendRequestRegistrationKey
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)]
struct FriendCode
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0xC00)]
struct FriendInvitationGameModeDescription
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
struct FriendInvitationGroupId
{
}
}

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
struct FriendInvitationId
{
}
}

View File

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

View File

@ -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]);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x1)]
struct MiiImageUrlParam
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 0x1)]
struct MiiName
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x38)]
struct NintendoNetworkIdUserInfo
{
}
}

View File

@ -1,9 +1,10 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Horizon.Sdk.Account;
using System.Runtime.InteropServices; 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 struct PlayHistoryRegistrationKey
{ {
public ushort Type; public ushort Type;
@ -11,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
public byte UserIdBool; public byte UserIdBool;
public byte UnknownBool; public byte UnknownBool;
public Array11<byte> Reserved; public Array11<byte> Reserved;
public Array16<byte> Uuid; public Uid Uuid;
public Array32<byte> HmacHash;
} }
} }

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
struct PlayHistoryStatistics
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
struct Relationship
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
struct RequestId
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
struct SnsAccountLinkage
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x380)]
struct SnsAccountProfile
{
}
}

View File

@ -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]);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x1000)]
struct WebPageUrl
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings
{
[StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)]
struct BatteryLot
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x74, Pack = 0x4)]
struct AmiiboEcdsaCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x24, Pack = 0x4)]
struct AmiiboEcqvBlsCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x48, Pack = 0x4)]
struct AmiiboEcqvBlsKey
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x94, Pack = 0x4)]
struct AmiiboEcqvBlsRootCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x4)]
struct AmiiboEcqvCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)]
struct AmiiboKey
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x9, Pack = 0x1)]
struct AnalogStickFactoryCalibration
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x12, Pack = 0x1)]
struct AnalogStickModelParameter
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)]
struct BdAddress
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x1E, Pack = 0x1)]
struct ConfigurationId1
{
}
}

View File

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

View File

@ -0,0 +1,8 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
struct CountryCode
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x180)]
struct EccB233DeviceCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x58, Pack = 0x4)]
struct EccB233DeviceKey
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x400)]
struct GameCardCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x138)]
struct GameCardKey
{
}
}

View File

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

View File

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

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x6, Pack = 0x1)]
struct MacAddress
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x240)]
struct Rsa2048DeviceCertificate
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x248)]
struct Rsa2048DeviceKey
{
}
}

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x18, Pack = 0x1)]
struct SerialNumber
{
}
}

View File

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

View File

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Settings.Factory
{
[StructLayout(LayoutKind.Sequential, Size = 0x804)]
struct SslCertificate
{
}
}

Some files were not shown because too many files have changed in this diff Show More