diff --git a/src/Ryujinx.Common/Memory/StructArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs index 94ce8d2f..762c7388 100644 --- a/src/Ryujinx.Common/Memory/StructArrayHelpers.cs +++ b/src/Ryujinx.Common/Memory/StructArrayHelpers.cs @@ -744,6 +744,17 @@ namespace Ryujinx.Common.Memory public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); } + public struct Array65 : IArray where T : unmanaged + { + T _e0; + Array64 _other; + public readonly int Length => 65; + public ref T this[int index] => ref AsSpan()[index]; + + [Pure] + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length); + } + public struct Array73 : IArray where T : unmanaged { T _e0; diff --git a/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs index 3b066662..79b7d681 100644 --- a/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs +++ b/src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs @@ -63,6 +63,18 @@ namespace Ryujinx.Common.Memory public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); } + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] + public struct ByteArray3000 : IArray + { + private const int Size = 3000; + + byte _element; + + public readonly int Length => Size; + public ref byte this[int index] => ref AsSpan()[index]; + public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size); + } + [StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)] public struct ByteArray4096 : IArray { diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs deleted file mode 100644 index 61131042..00000000 --- a/src/Ryujinx.HLE/HOS/Services/Arp/IReader.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Arp -{ - [Service("arp:r")] - class IReader : IpcService - { - public IReader(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs b/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs deleted file mode 100644 index 22d081b9..00000000 --- a/src/Ryujinx.HLE/HOS/Services/Arp/IWriter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Arp -{ - [Service("arp:w")] - class IWriter : IpcService - { - public IWriter(ServiceCtx context) { } - } -} diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs index cf8c1f78..9b026a1c 100644 --- a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs +++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs @@ -159,9 +159,7 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory } else { -#pragma warning disable CS0162 // Unreachable code return ResultCode.StereoVisionRestrictionConfigurableDisabled; -#pragma warning restore CS0162 } } diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs index 28f6fcff..3904d660 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/FileSystemExtensions.cs @@ -34,7 +34,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions return metaLoader; } - public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct nacpData, MetaLoader metaLoader, bool isHomebrew = false) + public static ProcessResult Load(this IFileSystem exeFs, Switch device, BlitStruct nacpData, MetaLoader metaLoader, byte programIndex, bool isHomebrew = false) { ulong programId = metaLoader.GetProgramId(); @@ -119,6 +119,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions true, programName, metaLoader.GetProgramId(), + programIndex, null, nsoExecutables); diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs index 798a9261..39139ecc 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/LocalFileSystemExtensions.cs @@ -26,7 +26,7 @@ namespace Ryujinx.HLE.Loaders.Processes ProcessLoaderHelper.EnsureSaveData(device, new ApplicationId(programId), nacpData); } - ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, 0); // Load RomFS. if (!string.IsNullOrEmpty(romFsPath)) diff --git a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs index e369f4b0..e70fcb6f 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/Extensions/NcaExtensions.cs @@ -61,7 +61,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions */ - ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader); + ProcessResult processResult = exeFs.Load(device, nacpData, metaLoader, (byte)nca.GetProgramIndex()); // Load RomFS. if (romFs == null) diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index efeb9f61..e5056c89 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -77,7 +77,7 @@ namespace Ryujinx.HLE.Loaders.Processes if (processResult.ProcessId == 0) { // This is not a normal NSP, it's actually a ExeFS as a NSP - processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), true); + processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), 0, true); } if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) @@ -198,7 +198,7 @@ namespace Ryujinx.HLE.Loaders.Processes } else { - programName = System.IO.Path.GetFileNameWithoutExtension(path); + programName = Path.GetFileNameWithoutExtension(path); executable = new NsoExecutable(new LocalStorage(path, FileAccess.Read), programName); } @@ -215,6 +215,7 @@ namespace Ryujinx.HLE.Loaders.Processes allowCodeMemoryForJit: true, programName, programId, + 0, null, executable); diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs index a6a1d87e..fe2aaac6 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoaderHelper.cs @@ -19,6 +19,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; using System; using System.Linq; using System.Runtime.InteropServices; @@ -229,6 +230,7 @@ namespace Ryujinx.HLE.Loaders.Processes bool allowCodeMemoryForJit, string name, ulong programId, + byte programIndex, byte[] arguments = null, params IExecutable[] executables) { @@ -421,7 +423,7 @@ namespace Ryujinx.HLE.Loaders.Processes // Once everything is loaded, we can load cheats. device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(programId, tamperInfo, device.TamperMachine); - return new ProcessResult( + ProcessResult processResult = new( metaLoader, applicationControlProperties, diskCacheEnabled, @@ -431,6 +433,25 @@ namespace Ryujinx.HLE.Loaders.Processes meta.MainThreadPriority, meta.MainThreadStackSize, device.System.State.DesiredTitleLanguage); + + // Register everything in arp service. + device.System.ServiceTable.ArpWriter.AcquireRegistrar(out IRegistrar registrar); + registrar.SetApplicationControlProperty(MemoryMarshal.Cast(applicationControlProperties.ByteSpan)[0]); + // TODO: Handle Version and StorageId when it will be needed. + registrar.SetApplicationLaunchProperty(new ApplicationLaunchProperty() + { + ApplicationId = new Horizon.Sdk.Ncm.ApplicationId(programId), + Version = 0x00, + Storage = Horizon.Sdk.Ncm.StorageId.BuiltInUser, + PatchStorage = Horizon.Sdk.Ncm.StorageId.None, + ApplicationKind = ApplicationKind.Application, + }); + + device.System.ServiceTable.ArpReader.GetApplicationInstanceId(out ulong applicationInstanceId, process.Pid); + device.System.ServiceTable.ArpWriter.AcquireApplicationProcessPropertyUpdater(out IUpdater updater, applicationInstanceId); + updater.SetApplicationProcessProperty(process.Pid, new ApplicationProcessProperty() { ProgramIndex = programIndex }); + + return processResult; } public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress) diff --git a/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs new file mode 100644 index 00000000..6080b475 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpIpcServer.cs @@ -0,0 +1,61 @@ +using Ryujinx.Horizon.Arp.Ipc; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; + +namespace Ryujinx.Horizon.Arp +{ + class ArpIpcServer + { + private const int ArpRMaxSessionsCount = 16; + private const int ArpWMaxSessionsCount = 8; + private const int MaxSessionsCount = ArpRMaxSessionsCount + ArpWMaxSessionsCount; + + private const int PointerBufferSize = 0x1000; + private const int MaxDomains = 24; + private const int MaxDomainObjects = 32; + private const int MaxPortsCount = 2; + + private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); + + private SmApi _sm; + private ServerManager _serverManager; + private ApplicationInstanceManager _applicationInstanceManager; + + public IReader Reader { get; private set; } + public IWriter Writer { get; private set; } + + public void Initialize() + { + HeapAllocator allocator = new(); + + _sm = new SmApi(); + _sm.Initialize().AbortOnFailure(); + + _serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _managerOptions, MaxSessionsCount); + + _applicationInstanceManager = new ApplicationInstanceManager(); + + Reader reader = new(_applicationInstanceManager); + Reader = reader; + + Writer writer = new(_applicationInstanceManager); + Writer = writer; + + _serverManager.RegisterObjectForServer(reader, ServiceName.Encode("arp:r"), ArpRMaxSessionsCount); + _serverManager.RegisterObjectForServer(writer, ServiceName.Encode("arp:w"), ArpWMaxSessionsCount); + } + + public void ServiceRequests() + { + _serverManager.ServiceRequests(); + } + + public void Shutdown() + { + _applicationInstanceManager.Dispose(); + _serverManager.Dispose(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/ArpMain.cs b/src/Ryujinx.Horizon/Arp/ArpMain.cs new file mode 100644 index 00000000..a28baa71 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/ArpMain.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.Horizon.Arp +{ + class ArpMain : IService + { + public static void Main(ServiceTable serviceTable) + { + ArpIpcServer arpIpcServer = new(); + + arpIpcServer.Initialize(); + + serviceTable.ArpReader = arpIpcServer.Reader; + serviceTable.ArpWriter = arpIpcServer.Writer; + + serviceTable.SignalServiceReady(); + + arpIpcServer.ServiceRequests(); + arpIpcServer.Shutdown(); + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs new file mode 100644 index 00000000..de99c2ad --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Reader.cs @@ -0,0 +1,135 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Reader : IReader, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Reader(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.HasValue) + { + applicationLaunchProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationLaunchProperty = _applicationInstanceManager.Entries[applicationInstanceId].LaunchProperty.Value; + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetApplicationControlProperty([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias, 0x4000)] out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.HasValue) + { + applicationControlProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationControlProperty = _applicationInstanceManager.Entries[applicationInstanceId].ControlProperty.Value; + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationProcessProperty, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null || !_applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.HasValue) + { + applicationProcessProperty = default; + + return ArpResult.InvalidInstanceId; + } + + applicationProcessProperty = _applicationInstanceManager.Entries[applicationInstanceId].ProcessProperty.Value; + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid) + { + applicationInstanceId = 0; + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + for (int i = 0; i < _applicationInstanceManager.Entries.Length; i++) + { + if (_applicationInstanceManager.Entries[i] != null && _applicationInstanceManager.Entries[i].Pid == pid) + { + applicationInstanceId = (ulong)i; + + return Result.Success; + } + } + + return ArpResult.InvalidPid; + } + + [CmifCommand(4)] + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier) + { + unregistrationNotifier = new UnregistrationNotifier(_applicationInstanceManager); + + return Result.Success; + } + + [CmifCommand(5)] + public Result ListApplicationInstanceId(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span applicationInstanceIdList) + { + count = 0; + + if (_applicationInstanceManager.Entries[0] != null) + { + applicationInstanceIdList[count++] = 0; + } + + if (_applicationInstanceManager.Entries[1] != null) + { + applicationInstanceIdList[count++] = 1; + } + + return Result.Success; + } + + [CmifCommand(6)] + public Result GetMicroApplicationInstanceId(out ulong microApplicationInstanceId, [ClientProcessId] ulong pid) + { + return GetApplicationInstanceId(out microApplicationInstanceId, pid); + } + + [CmifCommand(7)] + public Result GetApplicationCertificate([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x528)] out ApplicationCertificate applicationCertificate, ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] == null) + { + applicationCertificate = default; + + return ArpResult.InvalidInstanceId; + } + + applicationCertificate = _applicationInstanceManager.Entries[applicationInstanceId].Certificate.Value; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs new file mode 100644 index 00000000..841ab760 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Registrar.cs @@ -0,0 +1,52 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Registrar : IRegistrar, IServiceObject + { + private readonly ApplicationInstance _applicationInstance; + + public Registrar(ApplicationInstance applicationInstance) + { + _applicationInstance = applicationInstance; + } + + [CmifCommand(0)] + public Result Issue(out ulong applicationInstanceId) + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty) + { + if (_applicationInstance.LaunchProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.LaunchProperty = applicationLaunchProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result SetApplicationControlProperty([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize, 0x4000)] in ApplicationControlProperty applicationControlProperty) + { + if (_applicationInstance.ControlProperty != null) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstance.ControlProperty = applicationControlProperty; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs new file mode 100644 index 00000000..49f2b1cc --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/UnregistrationNotifier.cs @@ -0,0 +1,25 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class UnregistrationNotifier : IUnregistrationNotifier, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public UnregistrationNotifier(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result GetReadableHandle([CopyHandle] out int readableHandle) + { + readableHandle = _applicationInstanceManager.EventHandle; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs new file mode 100644 index 00000000..f7531d71 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Updater.cs @@ -0,0 +1,74 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Updater : IUpdater, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + private readonly ulong _applicationInstanceId; + private readonly bool _forCertificate; + + public Updater(ApplicationInstanceManager applicationInstanceManager, ulong applicationInstanceId, bool forCertificate) + { + _applicationInstanceManager = applicationInstanceManager; + _applicationInstanceId = applicationInstanceId; + _forCertificate = forCertificate; + } + + [CmifCommand(0)] + public Result Issue() + { + throw new NotImplementedException(); + } + + [CmifCommand(1)] + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty) + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + if (pid == 0) + { + return ArpResult.InvalidPid; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Pid = pid; + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = applicationProcessProperty; + + return Result.Success; + } + + [CmifCommand(2)] + public Result DeleteApplicationProcessProperty() + { + if (_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].ProcessProperty = new ApplicationProcessProperty(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result SetApplicationCertificate([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ApplicationCertificate applicationCertificate) + { + if (!_forCertificate) + { + return ArpResult.DataAlreadyBound; + } + + _applicationInstanceManager.Entries[_applicationInstanceId].Certificate = applicationCertificate; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs new file mode 100644 index 00000000..29c98b77 --- /dev/null +++ b/src/Ryujinx.Horizon/Arp/Ipc/Writer.cs @@ -0,0 +1,75 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Arp; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Arp.Ipc +{ + partial class Writer : IWriter, IServiceObject + { + private readonly ApplicationInstanceManager _applicationInstanceManager; + + public Writer(ApplicationInstanceManager applicationInstanceManager) + { + _applicationInstanceManager = applicationInstanceManager; + } + + [CmifCommand(0)] + public Result AcquireRegistrar(out IRegistrar registrar) + { + if (_applicationInstanceManager.Entries[0] != null) + { + if (_applicationInstanceManager.Entries[1] != null) + { + registrar = null; + + return ArpResult.NoFreeInstance; + } + else + { + _applicationInstanceManager.Entries[1] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[1]); + } + } + else + { + _applicationInstanceManager.Entries[0] = new ApplicationInstance(); + + registrar = new Registrar(_applicationInstanceManager.Entries[0]); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result UnregisterApplicationInstance(ulong applicationInstanceId) + { + if (_applicationInstanceManager.Entries[applicationInstanceId] != null) + { + _applicationInstanceManager.Entries[applicationInstanceId] = null; + } + + Os.SignalSystemEvent(ref _applicationInstanceManager.SystemEvent); + + return Result.Success; + } + + [CmifCommand(2)] + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, false); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId) + { + updater = new Updater(_applicationInstanceManager, applicationInstanceId, true); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs index c165f46f..4ed7dd48 100644 --- a/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs +++ b/src/Ryujinx.Horizon/Prepo/Ipc/PrepoService.cs @@ -5,6 +5,7 @@ using Ryujinx.Common.Utilities; using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Types; using Ryujinx.Horizon.Sdk.Account; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Prepo; using Ryujinx.Horizon.Sdk.Sf; using Ryujinx.Horizon.Sdk.Sf.Hipc; @@ -22,14 +23,16 @@ namespace Ryujinx.Horizon.Prepo.Ipc System, } + private readonly ArpApi _arp; private readonly PrepoServicePermissionLevel _permissionLevel; private ulong _systemSessionId; private bool _immediateTransmissionEnabled; private bool _userAgreementCheckEnabled = true; - public PrepoService(PrepoServicePermissionLevel permissionLevel) + public PrepoService(ArpApi arp, PrepoServicePermissionLevel permissionLevel) { + _arp = arp; _permissionLevel = permissionLevel; } @@ -165,7 +168,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc return PrepoResult.PermissionDenied; } - private static Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid, Uid userId, bool withUserId = false, ApplicationId applicationId = default) + private Result ProcessPlayReport(PlayReportKind playReportKind, ReadOnlySpan gameRoomBuffer, ReadOnlySpan reportBuffer, ulong pid, Uid userId, bool withUserId = false, ApplicationId applicationId = default) { if (withUserId) { @@ -199,8 +202,8 @@ namespace Ryujinx.Horizon.Prepo.Ipc builder.AppendLine("PlayReport log:"); builder.AppendLine($" Kind: {playReportKind}"); - // NOTE: The service calls arp:r using the pid to get the application id, if it fails PrepoResult.InvalidPid is returned. - // Reports are stored internally and an event is signaled to transmit them. + // NOTE: Reports are stored internally and an event is signaled to transmit them. + if (pid != 0) { builder.AppendLine($" Pid: {pid}"); @@ -210,6 +213,16 @@ namespace Ryujinx.Horizon.Prepo.Ipc builder.AppendLine($" ApplicationId: {applicationId}"); } + Result result = _arp.GetApplicationInstanceId(out ulong applicationInstanceId, pid); + if (result.IsFailure) + { + return PrepoResult.InvalidPid; + } + + _arp.GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, applicationInstanceId).AbortOnFailure(); + + builder.AppendLine($" ApplicationVersion: {applicationLaunchProperty.Version}"); + if (!userId.IsNull) { builder.AppendLine($" UserId: {userId}"); diff --git a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs index fd3f86ff..1902cde2 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoIpcServer.cs @@ -1,4 +1,5 @@ using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.Sdk.Sm; @@ -17,16 +18,19 @@ namespace Ryujinx.Horizon.Prepo private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false); private SmApi _sm; + private ArpApi _arp; private PrepoServerManager _serverManager; public void Initialize() { HeapAllocator allocator = new(); + _arp = new ArpApi(allocator); + _sm = new SmApi(); _sm.Initialize().AbortOnFailure(); - _serverManager = new PrepoServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); + _serverManager = new PrepoServerManager(allocator, _sm, _arp, MaxPortsCount, _managerOptions, TotalMaxSessionsCount); #pragma warning disable IDE0055 // Disable formatting _serverManager.RegisterServer((int)PrepoPortIndex.Admin, ServiceName.Encode("prepo:a"), MaxSessionsCount); // 1.0.0-5.1.0 @@ -45,6 +49,7 @@ namespace Ryujinx.Horizon.Prepo public void Shutdown() { + _arp.Dispose(); _serverManager.Dispose(); } } diff --git a/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs index 0c1c782f..8cac44c8 100644 --- a/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs +++ b/src/Ryujinx.Horizon/Prepo/PrepoServerManager.cs @@ -1,6 +1,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Prepo.Ipc; using Ryujinx.Horizon.Prepo.Types; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Horizon.Sdk.Sm; using System; @@ -9,8 +10,11 @@ namespace Ryujinx.Horizon.Prepo { class PrepoServerManager : ServerManager { - public PrepoServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) + private readonly ArpApi _arp; + + public PrepoServerManager(HeapAllocator allocator, SmApi sm, ArpApi arp, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions) { + _arp = arp; } protected override Result OnNeedsToAccept(int portIndex, Server server) @@ -18,12 +22,12 @@ namespace Ryujinx.Horizon.Prepo return (PrepoPortIndex)portIndex switch { #pragma warning disable IDE0055 // Disable formatting - PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)), - PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Admin)), - PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Manager)), - PrepoPortIndex.User => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.User)), - PrepoPortIndex.System => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.System)), - PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(PrepoServicePermissionLevel.Debug)), + PrepoPortIndex.Admin => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Admin2 => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Admin)), + PrepoPortIndex.Manager => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Manager)), + PrepoPortIndex.User => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.User)), + PrepoPortIndex.System => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.System)), + PrepoPortIndex.Debug => AcceptImpl(server, new PrepoService(_arp, PrepoServicePermissionLevel.Debug)), _ => throw new ArgumentOutOfRangeException(nameof(portIndex)), #pragma warning restore IDE0055 }; diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs new file mode 100644 index 00000000..d60d337d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationCertificate.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + [StructLayout(LayoutKind.Sequential, Size = 0x528)] + public struct ApplicationCertificate + { + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs new file mode 100644 index 00000000..586e6a98 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationKind.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Arp +{ + public enum ApplicationKind : byte + { + Application, + MicroApplication, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs new file mode 100644 index 00000000..00b81832 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationLaunchProperty.cs @@ -0,0 +1,14 @@ +using Ryujinx.Horizon.Sdk.Ncm; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationLaunchProperty + { + public ApplicationId ApplicationId; + public uint Version; + public StorageId Storage; + public StorageId PatchStorage; + public ApplicationKind ApplicationKind; + public byte Padding; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs new file mode 100644 index 00000000..13d222a1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ApplicationProcessProperty.cs @@ -0,0 +1,10 @@ +using Ryujinx.Common.Memory; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public struct ApplicationProcessProperty + { + public byte ProgramIndex; + public Array15 Unknown; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs new file mode 100644 index 00000000..b0acc006 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpApi.cs @@ -0,0 +1,130 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using Ryujinx.Horizon.Sdk.Sf.Cmif; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using Ryujinx.Horizon.Sdk.Sm; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + class ArpApi : IDisposable + { + private const string ArpRName = "arp:r"; + + private readonly HeapAllocator _allocator; + private int _sessionHandle; + + public ArpApi(HeapAllocator allocator) + { + _allocator = allocator; + } + + private void InitializeArpRService() + { + if (_sessionHandle == 0) + { + using var smApi = new SmApi(); + + smApi.Initialize(); + smApi.GetServiceHandle(out _sessionHandle, ServiceName.Encode(ArpRName)).AbortOnFailure(); + } + } + + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong applicationPid) + { + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationPid); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 3, sendPid: false, data); + if (result.IsFailure) + { + applicationInstanceId = 0; + + return result; + } + + SpanReader reader = new(response.Data); + + applicationInstanceId = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId) + { + applicationLaunchProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest(out CmifResponse response, _sessionHandle, 0, sendPid: false, data); + if (result.IsFailure) + { + return result; + } + + SpanReader reader = new(response.Data); + + applicationLaunchProperty = reader.Read(); + + return Result.Success; + } + + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId) + { + applicationControlProperty = default; + + Span data = stackalloc byte[8]; + SpanWriter writer = new(data); + + writer.Write(applicationInstanceId); + + ulong bufferSize = (ulong)Unsafe.SizeOf(); + ulong bufferAddress = _allocator.Allocate(bufferSize); + + InitializeArpRService(); + + Result result = ServiceUtil.SendRequest( + out CmifResponse response, + _sessionHandle, + 1, + sendPid: false, + data, + stackalloc[] { HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.FixedSize }, + stackalloc[] { new PointerAndSize(bufferAddress, bufferSize) }); + + if (result.IsFailure) + { + return result; + } + + applicationControlProperty = HorizonStatic.AddressSpace.Read(bufferAddress); + + _allocator.Free(bufferAddress, bufferSize); + + return Result.Success; + } + + public void Dispose() + { + if (_sessionHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_sessionHandle); + + _sessionHandle = 0; + } + + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs new file mode 100644 index 00000000..5de07871 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/ArpResult.cs @@ -0,0 +1,17 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + static class ArpResult + { + private const int ModuleId = 157; + + public static Result InvalidArgument => new(ModuleId, 30); + public static Result InvalidPid => new(ModuleId, 31); + public static Result InvalidPointer => new(ModuleId, 32); + public static Result DataAlreadyBound => new(ModuleId, 42); + public static Result AllocationFailed => new(ModuleId, 63); + public static Result NoFreeInstance => new(ModuleId, 101); + public static Result InvalidInstanceId => new(ModuleId, 102); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs new file mode 100644 index 00000000..5eb0ab18 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstance.cs @@ -0,0 +1,13 @@ +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstance + { + public ulong Pid { get; set; } + public ApplicationLaunchProperty? LaunchProperty { get; set; } + public ApplicationProcessProperty? ProcessProperty { get; set; } + public ApplicationControlProperty? ControlProperty { get; set; } + public ApplicationCertificate? Certificate { get; set; } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs new file mode 100644 index 00000000..18c993ce --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/Detail/ApplicationInstanceManager.cs @@ -0,0 +1,31 @@ +using Ryujinx.Horizon.Sdk.OsTypes; +using System; +using System.Threading; + +namespace Ryujinx.Horizon.Sdk.Arp.Detail +{ + class ApplicationInstanceManager : IDisposable + { + private int _disposalState; + + public SystemEventType SystemEvent; + public int EventHandle; + + public readonly ApplicationInstance[] Entries = new ApplicationInstance[2]; + + public ApplicationInstanceManager() + { + Os.CreateSystemEvent(out SystemEvent, EventClearMode.ManualClear, true).AbortOnFailure(); + + EventHandle = Os.GetReadableHandleOfSystemEvent(ref SystemEvent); + } + + public void Dispose() + { + if (EventHandle != 0 && Interlocked.Exchange(ref _disposalState, 1) == 0) + { + Os.DestroySystemEvent(ref SystemEvent); + } + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs new file mode 100644 index 00000000..ef78f7fd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IReader.cs @@ -0,0 +1,18 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; +using System; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IReader + { + public Result GetApplicationLaunchProperty(out ApplicationLaunchProperty applicationLaunchProperty, ulong applicationInstanceId); + public Result GetApplicationControlProperty(out ApplicationControlProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationProcessProperty(out ApplicationProcessProperty applicationControlProperty, ulong applicationInstanceId); + public Result GetApplicationInstanceId(out ulong applicationInstanceId, ulong pid); + public Result GetApplicationInstanceUnregistrationNotifier(out IUnregistrationNotifier unregistrationNotifier); + public Result ListApplicationInstanceId(out int count, Span applicationInstanceIdList); + public Result GetMicroApplicationInstanceId(out ulong MicroApplicationInstanceId, ulong pid); + public Result GetApplicationCertificate(out ApplicationCertificate applicationCertificate, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs new file mode 100644 index 00000000..467f3dbd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IRegistrar.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Ns; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IRegistrar + { + public Result Issue(out ulong applicationInstanceId); + public Result SetApplicationLaunchProperty(ApplicationLaunchProperty applicationLaunchProperty); + public Result SetApplicationControlProperty(in ApplicationControlProperty applicationControlProperty); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs new file mode 100644 index 00000000..24b9807d --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUnregistrationNotifier.cs @@ -0,0 +1,9 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUnregistrationNotifier + { + public Result GetReadableHandle(out int readableHandle); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs new file mode 100644 index 00000000..f9beeb69 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IUpdater.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IUpdater + { + public Result Issue(); + public Result SetApplicationProcessProperty(ulong pid, ApplicationProcessProperty applicationProcessProperty); + public Result DeleteApplicationProcessProperty(); + public Result SetApplicationCertificate(ApplicationCertificate applicationCertificate); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs new file mode 100644 index 00000000..b3e000e1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Arp/IWriter.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Arp +{ + public interface IWriter + { + public Result AcquireRegistrar(out IRegistrar registrar); + public Result UnregisterApplicationInstance(ulong applicationInstanceId); + public Result AcquireApplicationProcessPropertyUpdater(out IUpdater updater, ulong applicationInstanceId); + public Result AcquireApplicationCertificateUpdater(out IUpdater updater, ulong applicationInstanceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs index 4c5e76e6..24b7d9ca 100644 --- a/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs +++ b/src/Ryujinx.Horizon/Sdk/Ncm/ApplicationId.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.Ncm { - readonly struct ApplicationId + public readonly struct ApplicationId { public readonly ulong Id; diff --git a/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs new file mode 100644 index 00000000..e2fb3250 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ncm/StorageId.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.Horizon.Sdk.Ncm +{ + public enum StorageId : byte + { + None, + Host, + GameCard, + BuiltInSystem, + BuiltInUser, + SdCard, + Any, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs new file mode 100644 index 00000000..12c19168 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Ns/ApplicationControlProperty.cs @@ -0,0 +1,309 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Sdk.Arp.Detail; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Ns +{ + public struct ApplicationControlProperty + { + public Array16 Title; + public Array37 Isbn; + public StartupUserAccountValue StartupUserAccount; + public UserAccountSwitchLockValue UserAccountSwitchLock; + public AddOnContentRegistrationTypeValue AddOnContentRegistrationType; + public AttributeFlagValue AttributeFlag; + public uint SupportedLanguageFlag; + public ParentalControlFlagValue ParentalControlFlag; + public ScreenshotValue Screenshot; + public VideoCaptureValue VideoCapture; + public DataLossConfirmationValue DataLossConfirmation; + public PlayLogPolicyValue PlayLogPolicy; + public ulong PresenceGroupId; + public Array32 RatingAge; + public Array16 DisplayVersion; + public ulong AddOnContentBaseId; + public ulong SaveDataOwnerId; + public long UserAccountSaveDataSize; + public long UserAccountSaveDataJournalSize; + public long DeviceSaveDataSize; + public long DeviceSaveDataJournalSize; + public long BcatDeliveryCacheStorageSize; + public Array8 ApplicationErrorCodeCategory; + public Array8 LocalCommunicationId; + public LogoTypeValue LogoType; + public LogoHandlingValue LogoHandling; + public RuntimeAddOnContentInstallValue RuntimeAddOnContentInstall; + public RuntimeParameterDeliveryValue RuntimeParameterDelivery; + public Array2 Reserved30F4; + public CrashReportValue CrashReport; + public HdcpValue Hdcp; + public ulong SeedForPseudoDeviceId; + public Array65 BcatPassphrase; + public StartupUserAccountOptionFlagValue StartupUserAccountOption; + public Array6 ReservedForUserAccountSaveDataOperation; + public long UserAccountSaveDataSizeMax; + public long UserAccountSaveDataJournalSizeMax; + public long DeviceSaveDataSizeMax; + public long DeviceSaveDataJournalSizeMax; + public long TemporaryStorageSize; + public long CacheStorageSize; + public long CacheStorageJournalSize; + public long CacheStorageDataAndJournalSizeMax; + public ushort CacheStorageIndexMax; + public byte Reserved318A; + public byte RuntimeUpgrade; + public uint SupportingLimitedLicenses; + public Array16 PlayLogQueryableApplicationId; + public PlayLogQueryCapabilityValue PlayLogQueryCapability; + public RepairFlagValue RepairFlag; + public byte ProgramIndex; + public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag; + public Array4 Reserved3214; + public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration; + public ApplicationJitConfiguration JitConfiguration; + public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors; + public PlayReportPermissionValue PlayReportPermission; + public CrashScreenshotForProdValue CrashScreenshotForProd; + public CrashScreenshotForDevValue CrashScreenshotForDev; + public byte ContentsAvailabilityTransitionPolicy; + public Array4 Reserved3404; + public AccessibleLaunchRequiredVersionValue AccessibleLaunchRequiredVersion; + public ByteArray3000 Reserved3448; + + public readonly string IsbnString => Encoding.UTF8.GetString(Isbn.AsSpan()).TrimEnd('\0'); + public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0'); + public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0'); + public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0'); + + public struct ApplicationTitle + { + public ByteArray512 Name; + public Array256 Publisher; + + public readonly string NameString => Encoding.UTF8.GetString(Name.AsSpan()).TrimEnd('\0'); + public readonly string PublisherString => Encoding.UTF8.GetString(Publisher.AsSpan()).TrimEnd('\0'); + } + + public struct ApplicationNeighborDetectionClientConfiguration + { + public ApplicationNeighborDetectionGroupConfiguration SendGroupConfiguration; + public Array16 ReceivableGroupConfigurations; + } + + public struct ApplicationNeighborDetectionGroupConfiguration + { + public ulong GroupId; + public Array16 Key; + } + + public struct ApplicationJitConfiguration + { + public JitConfigurationFlag Flags; + public long MemorySize; + } + + public struct RequiredAddOnContentsSetBinaryDescriptor + { + public Array32 Descriptors; + } + + public struct AccessibleLaunchRequiredVersionValue + { + public Array8 ApplicationId; + } + + public enum Language + { + AmericanEnglish = 0, + BritishEnglish = 1, + Japanese = 2, + French = 3, + German = 4, + LatinAmericanSpanish = 5, + Spanish = 6, + Italian = 7, + Dutch = 8, + CanadianFrench = 9, + Portuguese = 10, + Russian = 11, + Korean = 12, + TraditionalChinese = 13, + SimplifiedChinese = 14, + BrazilianPortuguese = 15, + } + + public enum Organization + { + CERO = 0, + GRACGCRB = 1, + GSRMR = 2, + ESRB = 3, + ClassInd = 4, + USK = 5, + PEGI = 6, + PEGIPortugal = 7, + PEGIBBFC = 8, + Russian = 9, + ACB = 10, + OFLC = 11, + IARCGeneric = 12, + } + + public enum StartupUserAccountValue : byte + { + None = 0, + Required = 1, + RequiredWithNetworkServiceAccountAvailable = 2, + } + + public enum UserAccountSwitchLockValue : byte + { + Disable = 0, + Enable = 1, + } + + public enum AddOnContentRegistrationTypeValue : byte + { + AllOnLaunch = 0, + OnDemand = 1, + } + + [Flags] + public enum AttributeFlagValue + { + None = 0, + Demo = 1 << 0, + RetailInteractiveDisplay = 1 << 1, + } + + public enum ParentalControlFlagValue + { + None = 0, + FreeCommunication = 1, + } + + public enum ScreenshotValue : byte + { + Allow = 0, + Deny = 1, + } + + public enum VideoCaptureValue : byte + { + Disable = 0, + Manual = 1, + Enable = 2, + } + + public enum DataLossConfirmationValue : byte + { + None = 0, + Required = 1, + } + + public enum PlayLogPolicyValue : byte + { + Open = 0, + LogOnly = 1, + None = 2, + Closed = 3, + All = Open, + } + + public enum LogoTypeValue : byte + { + LicensedByNintendo = 0, + DistributedByNintendo = 1, + Nintendo = 2, + } + + public enum LogoHandlingValue : byte + { + Auto = 0, + Manual = 1, + } + + public enum RuntimeAddOnContentInstallValue : byte + { + Deny = 0, + AllowAppend = 1, + AllowAppendButDontDownloadWhenUsingNetwork = 2, + } + + public enum RuntimeParameterDeliveryValue : byte + { + Always = 0, + AlwaysIfUserStateMatched = 1, + OnRestart = 2, + } + + public enum CrashReportValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum HdcpValue : byte + { + None = 0, + Required = 1, + } + + [Flags] + public enum StartupUserAccountOptionFlagValue : byte + { + None = 0, + IsOptional = 1 << 0, + } + + public enum PlayLogQueryCapabilityValue : byte + { + None = 0, + WhiteList = 1, + All = 2, + } + + [Flags] + public enum RepairFlagValue : byte + { + None = 0, + SuppressGameCardAccess = 1 << 0, + } + + [Flags] + public enum RequiredNetworkServiceLicenseOnLaunchValue : byte + { + None = 0, + Common = 1 << 0, + } + + [Flags] + public enum JitConfigurationFlag : ulong + { + None = 0, + Enabled = 1 << 0, + } + + [Flags] + public enum PlayReportPermissionValue : byte + { + None = 0, + TargetMarketing = 1 << 0, + } + + public enum CrashScreenshotForProdValue : byte + { + Deny = 0, + Allow = 1, + } + + public enum CrashScreenshotForDevValue : byte + { + Deny = 0, + Allow = 1, + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs index ccd6c93a..5527c1e3 100644 --- a/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs +++ b/src/Ryujinx.Horizon/Sdk/ServiceUtil.cs @@ -35,5 +35,254 @@ namespace Ryujinx.Horizon.Sdk return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); } + + public static Result SendRequest( + out CmifResponse response, + int sessionHandle, + uint requestId, + bool sendPid, + scoped ReadOnlySpan data, + ReadOnlySpan bufferFlags, + ReadOnlySpan buffers) + { + ulong tlsAddress = HorizonStatic.ThreadContext.TlsAddress; + int tlsSize = Api.TlsMessageBufferSize; + + using (var tlsRegion = HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize)) + { + CmifRequestFormat format = new() + { + DataSize = data.Length, + RequestId = requestId, + SendPid = sendPid, + }; + + for (int index = 0; index < bufferFlags.Length; index++) + { + FormatProcessBuffer(ref format, bufferFlags[index]); + } + + CmifRequest request = CmifMessage.CreateRequest(tlsRegion.Memory.Span, format); + + for (int index = 0; index < buffers.Length; index++) + { + RequestProcessBuffer(ref request, buffers[index], bufferFlags[index]); + } + + data.CopyTo(request.Data); + } + + Result result = HorizonStatic.Syscall.SendSyncRequest(sessionHandle); + + if (result.IsFailure) + { + response = default; + + return result; + } + + return CmifMessage.ParseResponse(out response, HorizonStatic.AddressSpace.GetWritableRegion(tlsAddress, tlsSize).Memory.Span, false, 0); + } + + private static void FormatProcessBuffer(ref CmifRequestFormat format, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + if (isIn) + { + format.InAutoBuffersCount++; + } + + if (isOut) + { + format.OutAutoBuffersCount++; + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + format.InPointersCount++; + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + format.OutFixedPointersCount++; + } + else + { + format.OutPointersCount++; + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + if (isIn && isOut) + { + format.InOutBuffersCount++; + } + else if (isIn) + { + format.InBuffersCount++; + } + else + { + format.OutBuffersCount++; + } + } + } + + private static void RequestProcessBuffer(ref CmifRequest request, PointerAndSize buffer, HipcBufferFlags flags) + { + if (flags == 0) + { + return; + } + + bool isIn = flags.HasFlag(HipcBufferFlags.In); + bool isOut = flags.HasFlag(HipcBufferFlags.Out); + + if (flags.HasFlag(HipcBufferFlags.AutoSelect)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn) + { + RequestInAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + + if (isOut) + { + RequestOutAutoBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + else if (flags.HasFlag(HipcBufferFlags.Pointer)) + { + if (isIn) + { + RequestInPointer(ref request, buffer.Address, buffer.Size); + } + + if (isOut) + { + if (flags.HasFlag(HipcBufferFlags.FixedSize)) + { + RequestOutFixedPointer(ref request, buffer.Address, buffer.Size); + } + else + { + RequestOutPointer(ref request, buffer.Address, buffer.Size); + } + } + } + else if (flags.HasFlag(HipcBufferFlags.MapAlias)) + { + HipcBufferMode mode = HipcBufferMode.Normal; + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonSecure)) + { + mode = HipcBufferMode.NonSecure; + } + + if (flags.HasFlag(HipcBufferFlags.MapTransferAllowsNonDevice)) + { + mode = HipcBufferMode.NonDevice; + } + + if (isIn && isOut) + { + RequestInOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else if (isIn) + { + RequestInBuffer(ref request, buffer.Address, buffer.Size, mode); + } + else + { + RequestOutBuffer(ref request, buffer.Address, buffer.Size, mode); + } + } + } + + private static void RequestInAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestInPointer(ref request, bufferAddress, bufferSize); + RequestInBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestInPointer(ref request, 0UL, 0UL); + RequestInBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestOutAutoBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + if (request.ServerPointerSize != 0 && bufferSize <= (ulong)request.ServerPointerSize) + { + RequestOutPointer(ref request, bufferAddress, bufferSize); + RequestOutBuffer(ref request, 0UL, 0UL, mode); + } + else + { + RequestOutPointer(ref request, 0UL, 0UL); + RequestOutBuffer(ref request, bufferAddress, bufferSize, mode); + } + } + + private static void RequestInBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.SendBuffers[request.SendBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ReceiveBuffers[request.RecvBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInOutBuffer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize, HipcBufferMode mode) + { + request.Hipc.ExchangeBuffers[request.ExchBufferIndex++] = new HipcBufferDescriptor(bufferAddress, bufferSize, mode); + } + + private static void RequestInPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.SendStatics[request.SendStaticIndex++] = new HipcStaticDescriptor(bufferAddress, (ushort)bufferSize, request.CurrentInPointerId++); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutFixedPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + request.Hipc.ReceiveList[request.RecvListIndex++] = new HipcReceiveListEntry(bufferAddress, (ushort)bufferSize); + request.ServerPointerSize -= (int)bufferSize; + } + + private static void RequestOutPointer(ref CmifRequest request, ulong bufferAddress, ulong bufferSize) + { + RequestOutFixedPointer(ref request, bufferAddress, bufferSize); + request.OutPointerSizes[request.OutPointerSizeIndex++] = (ushort)bufferSize; + } } } diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs index d409be5b..62c15baa 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Cmif/CmifRequest.cs @@ -10,5 +10,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Cmif public Span OutPointerSizes; public Span Objects; public int ServerPointerSize; + public int CurrentInPointerId; + public int SendBufferIndex; + public int RecvBufferIndex; + public int ExchBufferIndex; + public int SendStaticIndex; + public int RecvListIndex; + public int OutPointerSizeIndex; } } diff --git a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs index 03ef6d3f..4e962894 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/Hipc/HipcBufferDescriptor.cs @@ -11,5 +11,12 @@ namespace Ryujinx.Horizon.Sdk.Sf.Hipc public ulong Address => _addressLow | (((ulong)_word2 << 4) & 0xf00000000UL) | (((ulong)_word2 << 34) & 0x7000000000UL); public ulong Size => _sizeLow | ((ulong)_word2 << 8) & 0xf00000000UL; public HipcBufferMode Mode => (HipcBufferMode)(_word2 & 3); + + public HipcBufferDescriptor(ulong address, ulong size, HipcBufferMode mode) + { + _sizeLow = (uint)size; + _addressLow = (uint)address; + _word2 = (uint)mode | ((uint)(address >> 34) & 0x1c) | ((uint)(size >> 32) << 24) | ((uint)(address >> 4) & 0xf0000000); + } } } diff --git a/src/Ryujinx.Horizon/ServiceTable.cs b/src/Ryujinx.Horizon/ServiceTable.cs index c79328a9..ee62ee84 100644 --- a/src/Ryujinx.Horizon/ServiceTable.cs +++ b/src/Ryujinx.Horizon/ServiceTable.cs @@ -1,3 +1,4 @@ +using Ryujinx.Horizon.Arp; using Ryujinx.Horizon.Bcat; using Ryujinx.Horizon.Hshl; using Ryujinx.Horizon.Ins; @@ -8,6 +9,7 @@ using Ryujinx.Horizon.Ngc; using Ryujinx.Horizon.Ovln; using Ryujinx.Horizon.Prepo; using Ryujinx.Horizon.Psc; +using Ryujinx.Horizon.Sdk.Arp; using Ryujinx.Horizon.Srepo; using Ryujinx.Horizon.Usb; using Ryujinx.Horizon.Wlan; @@ -23,6 +25,9 @@ namespace Ryujinx.Horizon private readonly ManualResetEvent _servicesReadyEvent = new(false); + public IReader ArpReader { get; internal set; } + public IWriter ArpWriter { get; internal set; } + public IEnumerable GetServices(HorizonOptions options) { List entries = new(); @@ -32,6 +37,7 @@ namespace Ryujinx.Horizon entries.Add(new ServiceEntry(T.Main, this, options)); } + RegisterService(); RegisterService(); RegisterService(); RegisterService();