Compare commits

...

8 Commits

Author SHA1 Message Date
gdkchan
ccbbaddbcb Fix exception when trying to read output pointer buffer size (#6221)
* Fix exception when trying to read output pointer buffer size

* Better name
2024-01-29 21:19:39 -03:00
Ac_K
8bf102d2cd Cpu: Implement Vpadal and Vrintr instructions (#6185)
* Cpu: Implement Vpadal and Vrintr instructions

This PR superseed last instructions left in #2242.
Since I'm not a CPU guy I've just ported the code and nothing more.
Please be precise during review if there are some changes to be done.

It should fixes #1781

Co-Authored-By: Piyachet Kanda <piyachetk@gmail.com>

* Addresses gdkchan's feedback

* Addresses gdkchan's feedback 2

* Apply suggestions from code review

Co-authored-by: gdkchan <gab.dark.100@gmail.com>

* another fix

* Update InstEmitSimdHelper32.cs

* Correct fix

* Addresses gdkchan's feedback

* Update CpuTestSimdCvt32.cs

---------

Co-authored-by: Piyachet Kanda <piyachetk@gmail.com>
Co-authored-by: gdkchan <gab.dark.100@gmail.com>
2024-01-30 00:51:05 +01:00
Ac_K
2adf031830 deps: Update Avalonia.Svg (#6218)
Updates `Avalonia.Svg` from 11.0.0.10 to 11.0.0.13
Updates `Avalonia.Svg.Skia` from 11.0.0.10 to 11.0.0.13

And reflect update changes as dependabot can't do it.

Superseed #6209
2024-01-29 23:22:42 +01:00
Isaac Marovitz
bb4a28b525 Ava UI: Mod Manager Fixes (Again) (#6187)
* Fix typo + Fix deleting from old dir

* Avoid double enumeration

* Break when parentDir is found

* Fix deleting non subdirectory mods

* Typo
2024-01-29 23:14:19 +01:00
merry
a8fbcdae9f UI: Clarify Create Application Shortcut tooltip text (#6217) 2024-01-29 23:08:24 +01:00
TSRBerry
4e81ab4229 Avalonia: Fix dialog issues caused by 1.1.1105 (#6211)
* Set _contentDialogOverlayWindow to null

* Make CheckLaunchState async
2024-01-29 22:57:20 +01:00
gdkchan
4117c13377 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
2024-01-29 22:45:40 +01:00
TSRBerry
20a392ad55 Remove events that trigger from a forked repository (#6213)
[skip ci]
2024-01-29 20:10:29 +01:00
169 changed files with 3220 additions and 883 deletions

View File

@@ -9,10 +9,6 @@ on:
types: [created, edited]
issues:
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
pull_request_review:
types: [submitted, dismissed]
pull_request_review_comment:
types: [created, edited]
pull_request_target:
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]

View File

@@ -8,8 +8,8 @@
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />

View File

@@ -875,6 +875,7 @@ namespace ARMeilleure.Decoders
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
@@ -995,6 +996,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

@@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
}
}
public static void Vpadal(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
}
public static void Vpaddl(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;

View File

@@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
}
}
// VRINTR (floating-point).
public static void Vrintr_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
// VRINTZ (floating-point).
public static void Vrint_Z(ArmEmitterContext context)
{

View File

@@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
int elems = op.GetBytesCount() >> op.Size;
int pairs = elems >> 1;
Operand res = GetVecA32(op.Qd);
for (int index = 0; index < pairs; index++)
{
int pairIndex = index * 2;
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
if (op.Size == 2)
{
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
}
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
// Narrow
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)

View File

@@ -637,6 +637,7 @@ namespace ARMeilleure.Instructions
Vorn,
Vorr,
Vpadd,
Vpadal,
Vpaddl,
Vpmax,
Vpmin,
@@ -656,6 +657,7 @@ namespace ARMeilleure.Instructions
Vrintm,
Vrintn,
Vrintp,
Vrintr,
Vrintx,
Vrshr,
Vrshrn,

View File

@@ -72,6 +72,7 @@
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
"GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",

View File

@@ -9,6 +9,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -103,7 +104,7 @@ namespace Ryujinx.Ava.UI.Applet
if (!string.IsNullOrWhiteSpace(path))
{
SvgSource source = new();
SvgSource source = new(default(Uri));
source.Load(EmbeddedResources.GetStream(path));

View File

@@ -16,7 +16,7 @@
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<Separator />
<MenuItem
Click="OpenUserSaveDirectory_Click"

View File

@@ -388,6 +388,7 @@ namespace Ryujinx.Ava.UI.Helpers
{
_contentDialogOverlayWindow.Content = null;
_contentDialogOverlayWindow.Close();
_contentDialogOverlayWindow = null;
}
return result;

View File

@@ -17,14 +17,16 @@ namespace Ryujinx.Ava.UI.Models
}
}
public bool InSd { get; }
public string Path { get; }
public string Name { get; }
public ModModel(string path, string name, bool enabled)
public ModModel(string path, string name, bool enabled, bool inSd)
{
Path = path;
Name = name;
Enabled = enabled;
InSd = inSd;
}
}
}

View File

@@ -180,7 +180,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (!string.IsNullOrWhiteSpace(_controllerImage))
{
SvgSource source = new();
SvgSource source = new(default(Uri));
source.Load(EmbeddedResources.GetStream(_controllerImage));

View File

@@ -102,13 +102,14 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var path in modsBasePaths)
{
var inSd = path == ModLoader.GetSdModsBasePath();
var modCache = new ModLoader.ModCache();
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId);
foreach (var mod in modCache.RomfsDirs)
{
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
{
Mods.Add(modModel);
@@ -117,12 +118,12 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var mod in modCache.RomfsContainers)
{
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
}
foreach (var mod in modCache.ExefsDirs)
{
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
{
Mods.Add(modModel);
@@ -131,7 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels
foreach (var mod in modCache.ExefsContainers)
{
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
}
}
@@ -183,30 +184,43 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Delete(ModModel model)
{
var modsDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16"));
var parentDir = String.Empty;
var isSubdir = true;
var pathToDelete = model.Path;
var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath();
var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16"));
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir)
{
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
isSubdir = false;
}
if (isSubdir)
{
var parentDir = String.Empty;
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
{
parentDir = dir;
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
{
parentDir = dir;
break;
}
}
if (parentDir == String.Empty)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogModDeleteNoParentMessage,
model.Path));
});
return;
}
}
if (parentDir == String.Empty)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogModDeleteNoParentMessage,
parentDir));
});
return;
}
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{model.Path}\"");
Directory.Delete(parentDir, true);
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\"");
Directory.Delete(pathToDelete, true);
Mods.Remove(model);
OnPropertyChanged(nameof(ModCount));

View File

@@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.Windows
}
}
private void CheckLaunchState()
private async Task CheckLaunchState()
{
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
@@ -271,23 +271,11 @@ namespace Ryujinx.Ava.UI.Windows
if (LinuxHelper.PkExecPath is not null)
{
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountDialog();
}
});
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
}
else
{
Dispatcher.UIThread.Post(async () =>
{
if (OperatingSystem.IsLinux())
{
await ShowVmMaxMapCountWarning();
}
});
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
}
}
@@ -304,12 +292,12 @@ namespace Ryujinx.Ava.UI.Windows
{
ShowKeyErrorOnLoad = false;
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(this, false).ContinueWith(task =>
await Updater.BeginParse(this, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
@@ -404,7 +392,9 @@ namespace Ryujinx.Ava.UI.Windows
LoadApplications();
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
CheckLaunchState();
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
private void SetMainContent(Control content = null)

View File

@@ -330,7 +330,7 @@ namespace Ryujinx.HLE.HOS
HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient));
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager));
foreach (var service in services)
{

View File

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

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

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

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.HLE.HOS.Services.Friend.ServiceCreator
namespace Ryujinx.Horizon.Sdk.Friends.Detail.Ipc
{
[Flags]
enum FriendServicePermissionLevel
enum FriendsServicePermissionLevel
{
UserMask = 1,
ViewerMask = 2,
ManagerMask = 4,
SystemMask = 8,
Administrator = -1,
Admin = -1,
User = UserMask,
Viewer = UserMask | ViewerMask,
Manager = UserMask | ViewerMask | ManagerMask,

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
{

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
{

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
{

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.Horizon.Sdk.Account;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
namespace Ryujinx.Horizon.Sdk.Friends
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
[StructLayout(LayoutKind.Sequential, Size = 0x40)]
struct PlayHistoryRegistrationKey
{
public ushort Type;
@@ -11,6 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
public byte UserIdBool;
public byte UnknownBool;
public Array11<byte> Reserved;
public Array16<byte> Uuid;
public Uid Uuid;
public Array32<byte> HmacHash;
}
}

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

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