From 7ab3fccd4d13bf3ed07a7fa207cfee61b43c56f3 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Wed, 29 Apr 2020 21:58:19 -0700 Subject: [PATCH] Add BCAT delivery cache support (#1154) * Initial bcat delivery cache support * Use LibHac 0.11.0 * Add option to open the BCAT savedata directory --- Ryujinx.HLE/HOS/Horizon.cs | 21 +++++ Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs | 52 +++++++++++++ .../HOS/Services/Bcat/IServiceCreator.cs | 56 +++++++++----- .../IDeliveryCacheDirectoryService.cs | 63 +++++++++++++++ .../IDeliveryCacheFileService.cs | 76 +++++++++++++++++++ .../IDeliveryCacheStorageService.cs | 73 +++++++++++------- Ryujinx.HLE/Ryujinx.HLE.csproj | 2 +- Ryujinx/Ui/GameTableContextMenu.cs | 21 +++++ Ryujinx/Ui/GameTableContextMenu.glade | 9 +++ 9 files changed, 329 insertions(+), 44 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 79fef93d5e..2dfa275713 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -1,5 +1,6 @@ using LibHac; using LibHac.Account; +using LibHac.Bcat; using LibHac.Common; using LibHac.Fs; using LibHac.FsSystem; @@ -18,6 +19,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.Services.Arp; using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; @@ -144,6 +146,9 @@ namespace Ryujinx.HLE.HOS internal NvHostSyncpt HostSyncpoint { get; private set; } + internal LibHac.Horizon LibHacHorizonServer { get; private set; } + internal HorizonClient LibHacHorizonClient { get; private set; } + public Horizon(Switch device, ContentManager contentManager) { ControlData = new BlitStruct(1); @@ -280,6 +285,22 @@ namespace Ryujinx.HLE.HOS SurfaceFlinger = new SurfaceFlinger(device); ConfigurationState.Instance.System.EnableDockedMode.Event += OnDockedModeChange; + + InitLibHacHorizon(); + } + + private void InitLibHacHorizon() + { + LibHac.Horizon horizon = new LibHac.Horizon(null, Device.FileSystem.FsServer); + + horizon.CreateHorizonClient(out HorizonClient ryujinxClient).ThrowIfFailure(); + horizon.CreateHorizonClient(out HorizonClient bcatClient).ThrowIfFailure(); + + ryujinxClient.Sm.RegisterService(new LibHacIReader(this), "arp:r").ThrowIfFailure(); + new BcatServer(bcatClient); + + LibHacHorizonServer = horizon; + LibHacHorizonClient = ryujinxClient; } private void OnDockedModeChange(object sender, ReactiveEventArgs e) diff --git a/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs new file mode 100644 index 0000000000..77f02e8d23 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs @@ -0,0 +1,52 @@ +using LibHac; +using LibHac.Arp.Impl; +using LibHac.Ncm; +using LibHac.Ns; +using System; + +using ApplicationId = LibHac.ApplicationId; + +namespace Ryujinx.HLE.HOS.Services.Arp +{ + class LibHacIReader : IReader + { + private Horizon System { get; } + + public LibHacIReader(Horizon system) + { + System = system; + } + + public Result GetApplicationLaunchProperty(out LibHac.Arp.ApplicationLaunchProperty launchProperty, ulong processId) + { + launchProperty = new LibHac.Arp.ApplicationLaunchProperty(); + + launchProperty.BaseStorageId = StorageId.BuiltInUser; + launchProperty.ApplicationId = new ApplicationId(System.TitleId); + + return Result.Success; + } + + public Result GetApplicationLaunchPropertyWithApplicationId(out LibHac.Arp.ApplicationLaunchProperty launchProperty, + ApplicationId applicationId) + { + launchProperty = new LibHac.Arp.ApplicationLaunchProperty(); + + launchProperty.BaseStorageId = StorageId.BuiltInUser; + launchProperty.ApplicationId = applicationId; + + return Result.Success; + } + + public Result GetApplicationControlProperty(out ApplicationControlProperty controlProperty, ulong processId) + { + throw new NotImplementedException(); + } + + public Result GetApplicationControlPropertyWithApplicationId(out ApplicationControlProperty controlProperty, + ApplicationId applicationId) + { + throw new NotImplementedException(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs index ec34f5407e..ac1abc3521 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/IServiceCreator.cs @@ -1,18 +1,25 @@ +using LibHac; +using Ryujinx.Common; using Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator; using Ryujinx.HLE.HOS.Services.Arp; namespace Ryujinx.HLE.HOS.Services.Bcat { - [Service("bcat:a")] - [Service("bcat:m")] - [Service("bcat:u")] - [Service("bcat:s")] + [Service("bcat:a", "bcat:a")] + [Service("bcat:m", "bcat:m")] + [Service("bcat:u", "bcat:u")] + [Service("bcat:s", "bcat:s")] class IServiceCreator : IpcService { - public IServiceCreator(ServiceCtx context) { } + private LibHac.Bcat.Detail.Ipc.IServiceCreator _base; + + public IServiceCreator(ServiceCtx context, string serviceName) + { + context.Device.System.LibHacHorizonClient.Sm.GetService(out _base, serviceName).ThrowIfFailure(); + } [Command(0)] - // CreateBcatService(u64, pid) -> object + // CreateBcatService(pid) -> object public ResultCode CreateBcatService(ServiceCtx context) { // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. @@ -30,21 +37,36 @@ namespace Ryujinx.HLE.HOS.Services.Bcat } [Command(1)] - // CreateDeliveryCacheStorageService(u64, pid) -> object + // CreateDeliveryCacheStorageService(pid) -> object public ResultCode CreateDeliveryCacheStorageService(ServiceCtx context) { - // TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId. - // Add an instance of nn::bcat::detail::service::core::ApplicationStorageManager who load "bcat-dc-X:/" system save data, - // return ResultCode.NullSaveData if failed. - // Where X depend of the ApplicationLaunchProperty stored in an array (range 0-3). - // Add an instance of nn::bcat::detail::service::ServiceMemoryManager. + ulong pid = context.RequestData.ReadUInt64(); - MakeObject(context, new IDeliveryCacheStorageService(context, ApplicationLaunchProperty.GetByPid(context))); + Result rc = _base.CreateDeliveryCacheStorageService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, pid); - // NOTE: If the IDeliveryCacheStorageService is null this error is returned, Doesn't occur in our case. - // return ResultCode.NullObject; + if (rc.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheStorageService(context, serv)); + } - return ResultCode.Success; + return (ResultCode)rc.Value; + } + + [Command(2)] + // CreateDeliveryCacheStorageServiceWithApplicationId(nn::ApplicationId) -> object + public ResultCode CreateDeliveryCacheStorageServiceWithApplicationId(ServiceCtx context) + { + ApplicationId applicationId = context.RequestData.ReadStruct(); + + Result rc = _base.CreateDeliveryCacheStorageServiceWithApplicationId(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService serv, + applicationId); + + if (rc.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheStorageService(context, serv)); + } + + return (ResultCode)rc.Value; } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs new file mode 100644 index 0000000000..ce5c575e34 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheDirectoryService.cs @@ -0,0 +1,63 @@ +using LibHac; +using LibHac.Bcat; +using Ryujinx.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheDirectoryService : IpcService, IDisposable + { + private LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService _base; + + public IDeliveryCacheDirectoryService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService baseService) + { + _base = baseService; + } + + [Command(0)] + // Open(nn::bcat::DirectoryName) + public ResultCode Open(ServiceCtx context) + { + DirectoryName directoryName = context.RequestData.ReadStruct(); + + Result result = _base.Open(ref directoryName); + + return (ResultCode)result.Value; + } + + [Command(1)] + // Read() -> (u32, buffer) + public ResultCode Read(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + byte[] data = new byte[size]; + + Result result = _base.Read(out int entriesRead, MemoryMarshal.Cast(data)); + + context.Memory.WriteBytes(position, data); + + context.ResponseData.Write(entriesRead); + + return (ResultCode)result.Value; + } + + [Command(2)] + // GetCount() -> u32 + public ResultCode GetCount(ServiceCtx context) + { + Result result = _base.GetCount(out int count); + + context.ResponseData.Write(count); + + return (ResultCode)result.Value; + } + + public void Dispose() + { + _base?.Dispose(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs new file mode 100644 index 0000000000..a621283f9c --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheFileService.cs @@ -0,0 +1,76 @@ +using LibHac; +using LibHac.Bcat; +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator +{ + class IDeliveryCacheFileService : IpcService, IDisposable + { + private LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService _base; + + public IDeliveryCacheFileService(LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService baseService) + { + _base = baseService; + } + + [Command(0)] + // Open(nn::bcat::DirectoryName, nn::bcat::FileName) + public ResultCode Open(ServiceCtx context) + { + DirectoryName directoryName = context.RequestData.ReadStruct(); + FileName fileName = context.RequestData.ReadStruct(); + + Result result = _base.Open(ref directoryName, ref fileName); + + return (ResultCode)result.Value; + } + + [Command(1)] + // Read(u64) -> (u64, buffer) + public ResultCode Read(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long offset = context.RequestData.ReadInt64(); + + byte[] data = new byte[size]; + + Result result = _base.Read(out long bytesRead, offset, data); + + context.Memory.WriteBytes(position, data); + + context.ResponseData.Write(bytesRead); + + return (ResultCode)result.Value; + } + + [Command(2)] + // GetSize() -> u64 + public ResultCode GetSize(ServiceCtx context) + { + Result result = _base.GetSize(out long size); + + context.ResponseData.Write(size); + + return (ResultCode)result.Value; + } + + [Command(3)] + // GetDigest() -> nn::bcat::Digest + public ResultCode GetDigest(ServiceCtx context) + { + Result result = _base.GetDigest(out Digest digest); + + context.ResponseData.WriteStruct(digest); + + return (ResultCode)result.Value; + } + + public void Dispose() + { + _base?.Dispose(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs index cad4437052..344eb54e40 100644 --- a/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs +++ b/Ryujinx.HLE/HOS/Services/Bcat/ServiceCreator/IDeliveryCacheStorageService.cs @@ -1,47 +1,68 @@ -using Ryujinx.HLE.HOS.Services.Arp; +using LibHac; +using LibHac.Bcat; using System; -using System.Text; +using System.Runtime.InteropServices; namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator { - class IDeliveryCacheStorageService : IpcService + class IDeliveryCacheStorageService : IpcService, IDisposable { - private const int DeliveryCacheDirectoriesLimit = 100; - private const int DeliveryCacheDirectoryNameLength = 32; + private LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService _base; - private string[] _deliveryCacheDirectories = new string[0]; - - public IDeliveryCacheStorageService(ServiceCtx context, ApplicationLaunchProperty applicationLaunchProperty) + public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService baseService) { - // TODO: Read directories.meta file from the save data (loaded in IServiceCreator) in _deliveryCacheDirectories. + _base = baseService; + } + + [Command(0)] + // CreateFileService() -> object + public ResultCode CreateFileService(ServiceCtx context) + { + Result result = _base.CreateFileService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheFileService service); + + if (result.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheFileService(service)); + } + + return (ResultCode)result.Value; + } + + [Command(1)] + // CreateDirectoryService() -> object + public ResultCode CreateDirectoryService(ServiceCtx context) + { + Result result = _base.CreateDirectoryService(out LibHac.Bcat.Detail.Ipc.IDeliveryCacheDirectoryService service); + + if (result.IsSuccess()) + { + MakeObject(context, new IDeliveryCacheDirectoryService(service)); + } + + return (ResultCode)result.Value; } [Command(10)] // EnumerateDeliveryCacheDirectory() -> (u32, buffer) public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context) { - long outputPosition = context.Request.ReceiveBuff[0].Position; - long outputSize = context.Request.ReceiveBuff[0].Size; + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; - for (int index = 0; index < _deliveryCacheDirectories.Length; index++) - { - if (index == DeliveryCacheDirectoriesLimit - 1) - { - break; - } + byte[] data = new byte[size]; - byte[] directoryNameBuffer = Encoding.ASCII.GetBytes(_deliveryCacheDirectories[index]); + Result result = _base.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast(data)); - Array.Resize(ref directoryNameBuffer, DeliveryCacheDirectoryNameLength); + context.Memory.WriteBytes(position, data); - directoryNameBuffer[DeliveryCacheDirectoryNameLength - 1] = 0x00; - - context.Memory.WriteBytes(outputPosition + index * DeliveryCacheDirectoryNameLength, directoryNameBuffer); - } + context.ResponseData.Write(count); - context.ResponseData.Write(_deliveryCacheDirectories.Length); + return (ResultCode)result.Value; + } - return ResultCode.Success; + public void Dispose() + { + _base?.Dispose(); } } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index 8a7d4a0f07..d82fc40226 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -52,7 +52,7 @@ - + diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs index 5db34ecbbe..8bead1e3dd 100644 --- a/Ryujinx/Ui/GameTableContextMenu.cs +++ b/Ryujinx/Ui/GameTableContextMenu.cs @@ -40,6 +40,7 @@ namespace Ryujinx.Ui #pragma warning disable IDE0044 [GUI] MenuItem _openSaveUserDir; [GUI] MenuItem _openSaveDeviceDir; + [GUI] MenuItem _openSaveBcatDir; [GUI] MenuItem _manageTitleUpdates; [GUI] MenuItem _extractRomFs; [GUI] MenuItem _extractExeFs; @@ -61,6 +62,7 @@ namespace Ryujinx.Ui _openSaveUserDir.Activated += OpenSaveUserDir_Clicked; _openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked; + _openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked; _manageTitleUpdates.Activated += ManageTitleUpdates_Clicked; _extractRomFs.Activated += ExtractRomFs_Clicked; _extractExeFs.Activated += ExtractExeFs_Clicked; @@ -68,6 +70,7 @@ namespace Ryujinx.Ui _openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0; _openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0; + _openSaveBcatDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.BcatDeliveryCacheStorageSize > 0; string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower(); if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci") @@ -516,6 +519,24 @@ namespace Ryujinx.Ui OpenSaveDir(titleName, titleIdNumber, filter); } + private void OpenSaveBcatDir_Clicked(object sender, EventArgs args) + { + string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; + string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower(); + + if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber)) + { + GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID"); + + return; + } + + SaveDataFilter filter = new SaveDataFilter(); + filter.SetSaveDataType(SaveDataType.Bcat); + + OpenSaveDir(titleName, titleIdNumber, filter); + } + private void ManageTitleUpdates_Clicked(object sender, EventArgs args) { string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0]; diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade index 8f71ecf73b..e648b745bf 100644 --- a/Ryujinx/Ui/GameTableContextMenu.glade +++ b/Ryujinx/Ui/GameTableContextMenu.glade @@ -23,6 +23,15 @@ True + + + True + False + Open the folder where the BCAT save for the application is loaded + Open BCAT Save Directory + True + + True