Add BCAT delivery cache support (#1154)
* Initial bcat delivery cache support * Use LibHac 0.11.0 * Add option to open the BCAT savedata directory
This commit is contained in:
parent
23170da5a0
commit
7ab3fccd4d
9 changed files with 329 additions and 44 deletions
|
@ -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<ApplicationControlProperty>(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<bool> e)
|
||||
|
|
52
Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
Normal file
52
Ryujinx.HLE/HOS/Services/Arp/LibHacIReader.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<nn::bcat::detail::ipc::IBcatService>
|
||||
// CreateBcatService(pid) -> object<nn::bcat::detail::ipc::IBcatService>
|
||||
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<nn::bcat::detail::ipc::IDeliveryCacheStorageService>
|
||||
// CreateDeliveryCacheStorageService(pid) -> object<nn::bcat::detail::ipc::IDeliveryCacheStorageService>
|
||||
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<nn::bcat::detail::ipc::IDeliveryCacheStorageService>
|
||||
public ResultCode CreateDeliveryCacheStorageServiceWithApplicationId(ServiceCtx context)
|
||||
{
|
||||
ApplicationId applicationId = context.RequestData.ReadStruct<ApplicationId>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<DirectoryName>();
|
||||
|
||||
Result result = _base.Open(ref directoryName);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
[Command(1)]
|
||||
// Read() -> (u32, buffer<nn::bcat::DeliveryCacheDirectoryEntry, 6>)
|
||||
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<byte, DeliveryCacheDirectoryEntry>(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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<DirectoryName>();
|
||||
FileName fileName = context.RequestData.ReadStruct<FileName>();
|
||||
|
||||
Result result = _base.Open(ref directoryName, ref fileName);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
[Command(1)]
|
||||
// Read(u64) -> (u64, buffer<bytes, 6>)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<nn::bcat::detail::ipc::IDeliveryCacheFileService>
|
||||
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<nn::bcat::detail::ipc::IDeliveryCacheDirectoryService>
|
||||
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<nn::bcat::DirectoryName, 6>)
|
||||
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];
|
||||
|
||||
Result result = _base.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast<byte, DirectoryName>(data));
|
||||
|
||||
context.Memory.WriteBytes(position, data);
|
||||
|
||||
context.ResponseData.Write(count);
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
byte[] directoryNameBuffer = Encoding.ASCII.GetBytes(_deliveryCacheDirectories[index]);
|
||||
|
||||
Array.Resize(ref directoryNameBuffer, DeliveryCacheDirectoryNameLength);
|
||||
|
||||
directoryNameBuffer[DeliveryCacheDirectoryNameLength - 1] = 0x00;
|
||||
|
||||
context.Memory.WriteBytes(outputPosition + index * DeliveryCacheDirectoryNameLength, directoryNameBuffer);
|
||||
}
|
||||
|
||||
context.ResponseData.Write(_deliveryCacheDirectories.Length);
|
||||
|
||||
return ResultCode.Success;
|
||||
public void Dispose()
|
||||
{
|
||||
_base?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
||||
<PackageReference Include="LibHac" Version="0.10.0" />
|
||||
<PackageReference Include="LibHac" Version="0.11.0" />
|
||||
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -23,6 +23,15 @@
|
|||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuItem" id="_openSaveBcatDir">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Open the folder where the BCAT save for the application is loaded</property>
|
||||
<property name="label" translatable="yes">Open BCAT Save Directory</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorMenuItem">
|
||||
<property name="visible">True</property>
|
||||
|
|
Loading…
Reference in a new issue