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;
|
||||||
using LibHac.Account;
|
using LibHac.Account;
|
||||||
|
using LibHac.Bcat;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
|
@ -18,6 +19,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
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.Mii;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv;
|
using Ryujinx.HLE.HOS.Services.Nv;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
||||||
|
@ -144,6 +146,9 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
internal NvHostSyncpt HostSyncpoint { get; private set; }
|
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)
|
public Horizon(Switch device, ContentManager contentManager)
|
||||||
{
|
{
|
||||||
ControlData = new BlitStruct<ApplicationControlProperty>(1);
|
ControlData = new BlitStruct<ApplicationControlProperty>(1);
|
||||||
|
@ -280,6 +285,22 @@ namespace Ryujinx.HLE.HOS
|
||||||
SurfaceFlinger = new SurfaceFlinger(device);
|
SurfaceFlinger = new SurfaceFlinger(device);
|
||||||
|
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Event += OnDockedModeChange;
|
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)
|
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.Bcat.ServiceCreator;
|
||||||
using Ryujinx.HLE.HOS.Services.Arp;
|
using Ryujinx.HLE.HOS.Services.Arp;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Bcat
|
namespace Ryujinx.HLE.HOS.Services.Bcat
|
||||||
{
|
{
|
||||||
[Service("bcat:a")]
|
[Service("bcat:a", "bcat:a")]
|
||||||
[Service("bcat:m")]
|
[Service("bcat:m", "bcat:m")]
|
||||||
[Service("bcat:u")]
|
[Service("bcat:u", "bcat:u")]
|
||||||
[Service("bcat:s")]
|
[Service("bcat:s", "bcat:s")]
|
||||||
class IServiceCreator : IpcService
|
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)]
|
[Command(0)]
|
||||||
// CreateBcatService(u64, pid) -> object<nn::bcat::detail::ipc::IBcatService>
|
// CreateBcatService(pid) -> object<nn::bcat::detail::ipc::IBcatService>
|
||||||
public ResultCode CreateBcatService(ServiceCtx context)
|
public ResultCode CreateBcatService(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId.
|
// TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId.
|
||||||
|
@ -30,21 +37,36 @@ namespace Ryujinx.HLE.HOS.Services.Bcat
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command(1)]
|
[Command(1)]
|
||||||
// CreateDeliveryCacheStorageService(u64, pid) -> object<nn::bcat::detail::ipc::IDeliveryCacheStorageService>
|
// CreateDeliveryCacheStorageService(pid) -> object<nn::bcat::detail::ipc::IDeliveryCacheStorageService>
|
||||||
public ResultCode CreateDeliveryCacheStorageService(ServiceCtx context)
|
public ResultCode CreateDeliveryCacheStorageService(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: Call arp:r GetApplicationLaunchProperty with the pid to get the TitleId.
|
ulong pid = context.RequestData.ReadUInt64();
|
||||||
// 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.
|
|
||||||
|
|
||||||
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.
|
if (rc.IsSuccess())
|
||||||
// return ResultCode.NullObject;
|
{
|
||||||
|
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;
|
||||||
using System.Text;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
namespace Ryujinx.HLE.HOS.Services.Bcat.ServiceCreator
|
||||||
{
|
{
|
||||||
class IDeliveryCacheStorageService : IpcService
|
class IDeliveryCacheStorageService : IpcService, IDisposable
|
||||||
{
|
{
|
||||||
private const int DeliveryCacheDirectoriesLimit = 100;
|
private LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService _base;
|
||||||
private const int DeliveryCacheDirectoryNameLength = 32;
|
|
||||||
|
|
||||||
private string[] _deliveryCacheDirectories = new string[0];
|
public IDeliveryCacheStorageService(ServiceCtx context, LibHac.Bcat.Detail.Ipc.IDeliveryCacheStorageService baseService)
|
||||||
|
|
||||||
public IDeliveryCacheStorageService(ServiceCtx context, ApplicationLaunchProperty applicationLaunchProperty)
|
|
||||||
{
|
{
|
||||||
// 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)]
|
[Command(10)]
|
||||||
// EnumerateDeliveryCacheDirectory() -> (u32, buffer<nn::bcat::DirectoryName, 6>)
|
// EnumerateDeliveryCacheDirectory() -> (u32, buffer<nn::bcat::DirectoryName, 6>)
|
||||||
public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context)
|
public ResultCode EnumerateDeliveryCacheDirectory(ServiceCtx context)
|
||||||
{
|
{
|
||||||
long outputPosition = context.Request.ReceiveBuff[0].Position;
|
long position = context.Request.ReceiveBuff[0].Position;
|
||||||
long outputSize = context.Request.ReceiveBuff[0].Size;
|
long size = context.Request.ReceiveBuff[0].Size;
|
||||||
|
|
||||||
for (int index = 0; index < _deliveryCacheDirectories.Length; index++)
|
byte[] data = new byte[size];
|
||||||
{
|
|
||||||
if (index == DeliveryCacheDirectoriesLimit - 1)
|
Result result = _base.EnumerateDeliveryCacheDirectory(out int count, MemoryMarshal.Cast<byte, DirectoryName>(data));
|
||||||
{
|
|
||||||
break;
|
context.Memory.WriteBytes(position, data);
|
||||||
|
|
||||||
|
context.ResponseData.Write(count);
|
||||||
|
|
||||||
|
return (ResultCode)result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] directoryNameBuffer = Encoding.ASCII.GetBytes(_deliveryCacheDirectories[index]);
|
public void Dispose()
|
||||||
|
{
|
||||||
Array.Resize(ref directoryNameBuffer, DeliveryCacheDirectoryNameLength);
|
_base?.Dispose();
|
||||||
|
|
||||||
directoryNameBuffer[DeliveryCacheDirectoryNameLength - 1] = 0x00;
|
|
||||||
|
|
||||||
context.Memory.WriteBytes(outputPosition + index * DeliveryCacheDirectoryNameLength, directoryNameBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ResponseData.Write(_deliveryCacheDirectories.Length);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Concentus" Version="1.1.7" />
|
<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" />
|
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ namespace Ryujinx.Ui
|
||||||
#pragma warning disable IDE0044
|
#pragma warning disable IDE0044
|
||||||
[GUI] MenuItem _openSaveUserDir;
|
[GUI] MenuItem _openSaveUserDir;
|
||||||
[GUI] MenuItem _openSaveDeviceDir;
|
[GUI] MenuItem _openSaveDeviceDir;
|
||||||
|
[GUI] MenuItem _openSaveBcatDir;
|
||||||
[GUI] MenuItem _manageTitleUpdates;
|
[GUI] MenuItem _manageTitleUpdates;
|
||||||
[GUI] MenuItem _extractRomFs;
|
[GUI] MenuItem _extractRomFs;
|
||||||
[GUI] MenuItem _extractExeFs;
|
[GUI] MenuItem _extractExeFs;
|
||||||
|
@ -61,6 +62,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
|
_openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
|
||||||
_openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
|
_openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
|
||||||
|
_openSaveBcatDir.Activated += OpenSaveBcatDir_Clicked;
|
||||||
_manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
|
_manageTitleUpdates.Activated += ManageTitleUpdates_Clicked;
|
||||||
_extractRomFs.Activated += ExtractRomFs_Clicked;
|
_extractRomFs.Activated += ExtractRomFs_Clicked;
|
||||||
_extractExeFs.Activated += ExtractExeFs_Clicked;
|
_extractExeFs.Activated += ExtractExeFs_Clicked;
|
||||||
|
@ -68,6 +70,7 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
_openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
_openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
|
||||||
_openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 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();
|
string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
|
||||||
if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci")
|
if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci")
|
||||||
|
@ -516,6 +519,24 @@ namespace Ryujinx.Ui
|
||||||
OpenSaveDir(titleName, titleIdNumber, filter);
|
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)
|
private void ManageTitleUpdates_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
|
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
|
||||||
|
|
|
@ -23,6 +23,15 @@
|
||||||
<property name="use_underline">True</property>
|
<property name="use_underline">True</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</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>
|
<child>
|
||||||
<object class="GtkSeparatorMenuItem">
|
<object class="GtkSeparatorMenuItem">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
Loading…
Reference in a new issue