aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.HLE/HOS/ApplicationLoader.cs275
-rw-r--r--Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs67
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs9
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs9
-rw-r--r--Ryujinx.HLE/HOS/UserChannelPersistence.cs56
-rw-r--r--Ryujinx.HLE/IHostUiHandler.cs9
-rw-r--r--Ryujinx.HLE/Switch.cs11
-rw-r--r--Ryujinx/Ui/ApplicationLibrary.cs103
-rw-r--r--Ryujinx/Ui/GameTableContextMenu.cs35
-rw-r--r--Ryujinx/Ui/GtkHostUiHandler.cs7
-rw-r--r--Ryujinx/Ui/MainWindow.cs26
-rw-r--r--Ryujinx/Ui/TitleUpdateWindow.cs83
13 files changed, 432 insertions, 267 deletions
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index 16c9664d..46b62178 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -15,6 +15,7 @@ using Ryujinx.HLE.Loaders.Executables;
using Ryujinx.HLE.Loaders.Npdm;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -28,32 +29,33 @@ namespace Ryujinx.HLE.HOS
public class ApplicationLoader
{
- private readonly Switch _device;
- private readonly ContentManager _contentManager;
- private readonly VirtualFileSystem _fileSystem;
-
- public BlitStruct<ApplicationControlProperty> ControlData { get; set; }
+ // Binaries from exefs are loaded into mem in this order. Do not change.
+ private static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk*", "sdk" };
- public string TitleName { get; private set; }
- public string DisplayVersion { get; private set; }
+ private readonly Switch _device;
+ private readonly ContentManager _contentManager;
+ private readonly VirtualFileSystem _fileSystem;
- public ulong TitleId { get; private set; }
- public string TitleIdText => TitleId.ToString("x16");
+ private string _titleName;
+ private string _displayVersion;
+ private BlitStruct<ApplicationControlProperty> _controlData;
- public bool TitleIs64Bit { get; private set; }
+ public BlitStruct<ApplicationControlProperty> ControlData => _controlData;
+ public string TitleName => _titleName;
+ public string DisplayVersion => _displayVersion;
- public bool EnablePtc => _device.System.EnablePtc;
+ public ulong TitleId { get; private set; }
+ public bool TitleIs64Bit { get; private set; }
- // Binaries from exefs are loaded into mem in this order. Do not change.
- private static readonly string[] ExeFsPrefixes = { "rtld", "main", "subsdk*", "sdk" };
+ public string TitleIdText => TitleId.ToString("x16");
public ApplicationLoader(Switch device, VirtualFileSystem fileSystem, ContentManager contentManager)
{
- _device = device;
+ _device = device;
_contentManager = contentManager;
- _fileSystem = fileSystem;
+ _fileSystem = fileSystem;
- ControlData = new BlitStruct<ApplicationControlProperty>(1);
+ _controlData = new BlitStruct<ApplicationControlProperty>(1);
// Clear Mods cache
_fileSystem.ModLoader.Clear();
@@ -80,19 +82,26 @@ namespace Ryujinx.HLE.HOS
LoadExeFs(codeFs, metaData);
}
- private (Nca main, Nca patch, Nca control) GetGameData(PartitionFileSystem pfs)
+ public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
{
- Nca mainNca = null;
- Nca patchNca = null;
+ Nca mainNca = null;
+ Nca patchNca = null;
Nca controlNca = null;
- _fileSystem.ImportTickets(pfs);
+ fileSystem.ImportTickets(pfs);
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
{
pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
- Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
+ Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
if (nca.Header.ContentType == NcaContentType.Program)
{
@@ -116,11 +125,77 @@ namespace Ryujinx.HLE.HOS
return (mainNca, patchNca, controlNca);
}
+ public static (Nca patch, Nca control) GetGameUpdateDataFromPartition(VirtualFileSystem fileSystem, PartitionFileSystem pfs, string titleId, int programIndex)
+ {
+ Nca patchNca = null;
+ Nca controlNca = null;
+
+ fileSystem.ImportTickets(pfs);
+
+ foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
+ {
+ pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+
+ Nca nca = new Nca(fileSystem.KeySet, ncaFile.AsStorage());
+
+ int ncaProgramIndex = (int)(nca.Header.TitleId & 0xF);
+
+ if (ncaProgramIndex != programIndex)
+ {
+ continue;
+ }
+
+ if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
+ {
+ break;
+ }
+
+ if (nca.Header.ContentType == NcaContentType.Program)
+ {
+ patchNca = nca;
+ }
+ else if (nca.Header.ContentType == NcaContentType.Control)
+ {
+ controlNca = nca;
+ }
+ }
+
+ return (patchNca, controlNca);
+ }
+
+ public static (Nca patch, Nca control) GetGameUpdateData(VirtualFileSystem fileSystem, string titleId, int programIndex, out string updatePath)
+ {
+ updatePath = null;
+
+ if (ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdBase))
+ {
+ // Clear the program index part.
+ titleIdBase &= 0xFFFFFFFFFFFFFFF0;
+
+ // Load update informations if existing.
+ string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, titleIdBase.ToString("x16"), "updates.json");
+
+ if (File.Exists(titleUpdateMetadataPath))
+ {
+ updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
+
+ if (File.Exists(updatePath))
+ {
+ FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
+ PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
+
+ return GetGameUpdateDataFromPartition(fileSystem, nsp, titleIdBase.ToString("x16"), programIndex);
+ }
+ }
+ }
+
+ return (null, null);
+ }
+
public void LoadXci(string xciFile)
{
FileStream file = new FileStream(xciFile, FileMode.Open, FileAccess.Read);
-
- Xci xci = new Xci(_fileSystem.KeySet, file.AsStorage());
+ Xci xci = new Xci(_fileSystem.KeySet, file.AsStorage());
if (!xci.HasPartition(XciPartitionType.Secure))
{
@@ -131,13 +206,13 @@ namespace Ryujinx.HLE.HOS
PartitionFileSystem securePartition = xci.OpenPartition(XciPartitionType.Secure);
- Nca mainNca = null;
- Nca patchNca = null;
- Nca controlNca = null;
+ Nca mainNca;
+ Nca patchNca;
+ Nca controlNca;
try
{
- (mainNca, patchNca, controlNca) = GetGameData(securePartition);
+ (mainNca, patchNca, controlNca) = GetGameData(_fileSystem, securePartition, _device.UserChannelPersistence.Index);
}
catch (Exception e)
{
@@ -154,7 +229,6 @@ namespace Ryujinx.HLE.HOS
}
_contentManager.LoadEntries(_device);
-
_contentManager.ClearAocData();
_contentManager.AddAocData(securePartition, xciFile, mainNca.Header.TitleId);
@@ -163,17 +237,16 @@ namespace Ryujinx.HLE.HOS
public void LoadNsp(string nspFile)
{
- FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
-
- PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
+ FileStream file = new FileStream(nspFile, FileMode.Open, FileAccess.Read);
+ PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
- Nca mainNca = null;
- Nca patchNca = null;
- Nca controlNca = null;
+ Nca mainNca;
+ Nca patchNca;
+ Nca controlNca;
try
{
- (mainNca, patchNca, controlNca) = GetGameData(nsp);
+ (mainNca, patchNca, controlNca) = GetGameData(_fileSystem, nsp, _device.UserChannelPersistence.Index);
}
catch (Exception e)
{
@@ -206,8 +279,7 @@ namespace Ryujinx.HLE.HOS
public void LoadNca(string ncaFile)
{
FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
-
- Nca nca = new Nca(_fileSystem.KeySet, file.AsStorage(false));
+ Nca nca = new Nca(_fileSystem.KeySet, file.AsStorage(false));
LoadNca(nca, null, null);
}
@@ -221,46 +293,24 @@ namespace Ryujinx.HLE.HOS
return;
}
- IStorage dataStorage = null;
- IFileSystem codeFs = null;
+ IStorage dataStorage = null;
+ IFileSystem codeFs = null;
- // Load Update
- string titleUpdateMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
+ (Nca updatePatchNca, Nca updateControlNca) = GetGameUpdateData(_fileSystem, mainNca.Header.TitleId.ToString("x16"), _device.UserChannelPersistence.Index, out _);
- if (File.Exists(titleUpdateMetadataPath))
+ if (updatePatchNca != null)
{
- string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
-
- if (File.Exists(updatePath))
- {
- FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
- PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
-
- _fileSystem.ImportTickets(nsp);
-
- foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
- {
- nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- Nca nca = new Nca(_fileSystem.KeySet, ncaFile.AsStorage());
-
- if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
- {
- break;
- }
+ patchNca = updatePatchNca;
+ }
- if (nca.Header.ContentType == NcaContentType.Program)
- {
- patchNca = nca;
- }
- else if (nca.Header.ContentType == NcaContentType.Control)
- {
- controlNca = nca;
- }
- }
- }
+ if (updateControlNca != null)
+ {
+ controlNca = updateControlNca;
}
+ // Load program 0 control NCA as we are going to need it for display version.
+ (_, Nca updateProgram0ControlNca) = GetGameUpdateData(_fileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
+
// Load Aoc
string titleAocMetadataPath = Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "dlc.json");
@@ -315,13 +365,23 @@ namespace Ryujinx.HLE.HOS
if (controlNca != null)
{
- ReadControlData(controlNca);
+ ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref _displayVersion);
}
else
{
ControlData.ByteSpan.Clear();
}
+ // NOTE: Nintendo doesn't guarantee that the display version will be updated on sub programs when updating a multi program application.
+ // BODY: As such, to avoid PTC cache confusion, we only trust the the program 0 display version when launching a sub program.
+ if (updateProgram0ControlNca != null && _device.UserChannelPersistence.Index != 0)
+ {
+ string dummyTitleName = "";
+ BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
+
+ ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref _displayVersion);
+ }
+
if (dataStorage == null)
{
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
@@ -329,6 +389,7 @@ namespace Ryujinx.HLE.HOS
else
{
IStorage newStorage = _fileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
+
_fileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read));
}
@@ -346,6 +407,7 @@ namespace Ryujinx.HLE.HOS
private Npdm ReadNpdm(IFileSystem fs)
{
Result result = fs.OpenFile(out IFile npdmFile, "/main.npdm".ToU8Span(), OpenMode.Read);
+
Npdm metaData;
if (ResultFs.PathNotFound.Includes(result))
@@ -359,39 +421,36 @@ namespace Ryujinx.HLE.HOS
metaData = new Npdm(npdmFile.AsStream());
}
- TitleId = metaData.Aci0.TitleId;
+ TitleId = metaData.Aci0.TitleId;
TitleIs64Bit = metaData.Is64Bit;
return metaData;
}
- private void ReadControlData(Nca controlNca)
+ private static void ReadControlData(Switch device, Nca controlNca, ref BlitStruct<ApplicationControlProperty> controlData, ref string titleName, ref string displayVersion)
{
- IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, _device.System.FsIntegrityCheckLevel);
-
- Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
+ IFileSystem controlFs = controlNca.OpenFileSystem(NcaSectionType.Data, device.System.FsIntegrityCheckLevel);
+ Result result = controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read);
if (result.IsSuccess())
{
- result = controlFile.Read(out long bytesRead, 0, ControlData.ByteSpan, ReadOption.None);
+ result = controlFile.Read(out long bytesRead, 0, controlData.ByteSpan, ReadOption.None);
- if (result.IsSuccess() && bytesRead == ControlData.ByteSpan.Length)
+ if (result.IsSuccess() && bytesRead == controlData.ByteSpan.Length)
{
- TitleName = ControlData.Value
- .Titles[(int)_device.System.State.DesiredTitleLanguage].Name.ToString();
+ titleName = controlData.Value.Titles[(int)device.System.State.DesiredTitleLanguage].Name.ToString();
- if (string.IsNullOrWhiteSpace(TitleName))
+ if (string.IsNullOrWhiteSpace(titleName))
{
- TitleName = ControlData.Value.Titles.ToArray()
- .FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
+ titleName = controlData.Value.Titles.ToArray().FirstOrDefault(x => x.Name[0] != 0).Name.ToString();
}
- DisplayVersion = ControlData.Value.DisplayVersion.ToString();
+ displayVersion = controlData.Value.DisplayVersion.ToString();
}
}
else
{
- ControlData.ByteSpan.Clear();
+ controlData.ByteSpan.Clear();
}
}
@@ -428,18 +487,18 @@ namespace Ryujinx.HLE.HOS
// ExeFs file replacements
bool modified = _fileSystem.ModLoader.ApplyExefsMods(TitleId, nsos);
- var programs = nsos.ToArray();
+ NsoExecutable[] programs = nsos.ToArray();
modified |= _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
_contentManager.LoadEntries(_device);
- if (EnablePtc && modified)
+ if (_device.System.EnablePtc && modified)
{
Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
}
- Ptc.Initialize(TitleIdText, DisplayVersion, EnablePtc && !modified);
+ Ptc.Initialize(TitleIdText, DisplayVersion, _device.System.EnablePtc && !modified);
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs);
}
@@ -447,15 +506,15 @@ namespace Ryujinx.HLE.HOS
public void LoadProgram(string filePath)
{
Npdm metaData = GetDefaultNpdm();
-
- bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
+ bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
IExecutable executable;
if (isNro)
{
- FileStream input = new FileStream(filePath, FileMode.Open);
- NroExecutable obj = new NroExecutable(input.AsStorage());
+ FileStream input = new FileStream(filePath, FileMode.Open);
+ NroExecutable obj = new NroExecutable(input.AsStorage());
+
executable = obj;
// homebrew NRO can actually have some data after the actual NRO
@@ -466,20 +525,19 @@ namespace Ryujinx.HLE.HOS
BinaryReader reader = new BinaryReader(input);
uint asetMagic = reader.ReadUInt32();
-
if (asetMagic == 0x54455341)
{
uint asetVersion = reader.ReadUInt32();
if (asetVersion == 0)
{
ulong iconOffset = reader.ReadUInt64();
- ulong iconSize = reader.ReadUInt64();
+ ulong iconSize = reader.ReadUInt64();
ulong nacpOffset = reader.ReadUInt64();
- ulong nacpSize = reader.ReadUInt64();
+ ulong nacpSize = reader.ReadUInt64();
ulong romfsOffset = reader.ReadUInt64();
- ulong romfsSize = reader.ReadUInt64();
+ ulong romfsSize = reader.ReadUInt64();
if (romfsSize != 0)
{
@@ -533,8 +591,8 @@ namespace Ryujinx.HLE.HOS
_contentManager.LoadEntries(_device);
- TitleName = metaData.TitleName;
- TitleId = metaData.Aci0.TitleId;
+ _titleName = metaData.TitleName;
+ TitleId = metaData.Aci0.TitleId;
TitleIs64Bit = metaData.Is64Bit;
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: executable);
@@ -572,25 +630,24 @@ namespace Ryujinx.HLE.HOS
"No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
}
- FileSystemClient fs = _fileSystem.FsClient;
-
- Result rc = fs.EnsureApplicationCacheStorage(out _, applicationId, ref control);
+ FileSystemClient fileSystem = _fileSystem.FsClient;
+ Result resultCode = fileSystem.EnsureApplicationCacheStorage(out _, applicationId, ref control);
- if (rc.IsFailure())
+ if (resultCode.IsFailure())
{
- Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {rc.ToStringWithName()}");
+ Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationCacheStorage. Result code {resultCode.ToStringWithName()}");
- return rc;
+ return resultCode;
}
- rc = EnsureApplicationSaveData(fs, out _, applicationId, ref control, ref user);
+ resultCode = EnsureApplicationSaveData(fileSystem, out _, applicationId, ref control, ref user);
- if (rc.IsFailure())
+ if (resultCode.IsFailure())
{
- Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {rc.ToStringWithName()}");
+ Logger.Error?.Print(LogClass.Application, $"Error calling EnsureApplicationSaveData. Result code {resultCode.ToStringWithName()}");
}
- return rc;
+ return resultCode;
}
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs
index 54565cb1..7e2b5d4f 100644
--- a/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs
+++ b/Ryujinx.HLE/HOS/Services/Account/Acc/IManagerForApplication.cs
@@ -126,5 +126,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return ResultCode.Success;
}
+
+ [Command(160)] // 5.0.0+
+ // StoreOpenContext()
+ public ResultCode StoreOpenContext(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAcc);
+
+ return ResultCode.Success;
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
index a310a094..82d7c7fa 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs
@@ -5,11 +5,13 @@ using LibHac.Fs;
using LibHac.Ns;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService;
using Ryujinx.HLE.HOS.SystemState;
using System;
@@ -35,11 +37,27 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
}
[Command(1)]
- // PopLaunchParameter(u32) -> object<nn::am::service::IStorage>
+ // PopLaunchParameter(LaunchParameterKind kind) -> object<nn::am::service::IStorage>
public ResultCode PopLaunchParameter(ServiceCtx context)
{
- // Only the first 0x18 bytes of the Data seems to be actually used.
- MakeObject(context, new AppletAE.IStorage(StorageHelper.MakeLaunchParams(context.Device.System.State.Account.LastOpenedUser)));
+ LaunchParameterKind kind = (LaunchParameterKind)context.RequestData.ReadUInt32();
+
+ byte[] storageData;
+
+ switch (kind)
+ {
+ case LaunchParameterKind.UserChannel:
+ storageData = context.Device.UserChannelPersistence.Pop();
+ break;
+ case LaunchParameterKind.PreselectedUser:
+ // Only the first 0x18 bytes of the Data seems to be actually used.
+ storageData = StorageHelper.MakeLaunchParams(context.Device.System.State.Account.LastOpenedUser);
+ break;
+ default:
+ throw new NotImplementedException($"Unknown LaunchParameterKind {kind}");
+ }
+
+ MakeObject(context, new AppletAE.IStorage(storageData));
return ResultCode.Success;
}
@@ -376,14 +394,49 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true);
}
+ [Command(120)] // 5.0.0+
+ // ExecuteProgram(ProgramSpecifyKind kind, u64 value)
+ public ResultCode ExecuteProgram(ServiceCtx context)
+ {
+ ProgramSpecifyKind kind = (ProgramSpecifyKind)context.RequestData.ReadUInt32();
+
+ // padding
+ context.RequestData.ReadUInt32();
+
+ ulong value = context.RequestData.ReadUInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value });
+
+ context.Device.UiHandler.ExecuteProgram(context.Device, kind, value);
+
+ return ResultCode.Success;
+ }
+
+ [Command(121)] // 5.0.0+
+ // ClearUserChannel()
+ public ResultCode ClearUserChannel(ServiceCtx context)
+ {
+ context.Device.UserChannelPersistence.Clear();
+
+ return ResultCode.Success;
+ }
+
+ [Command(122)] // 5.0.0+
+ // UnpopToUserChannel(object<nn::am::service::IStorage> input_storage)
+ public ResultCode UnpopToUserChannel(ServiceCtx context)
+ {
+ AppletAE.IStorage data = GetObject<AppletAE.IStorage>(context, 0);
+
+ context.Device.UserChannelPersistence.Push(data.Data);
+
+ return ResultCode.Success;
+ }
+
[Command(123)] // 5.0.0+
// GetPreviousProgramIndex() -> s32 program_index
public ResultCode GetPreviousProgramIndex(ServiceCtx context)
{
- // TODO: The output PreviousProgramIndex is -1 when there was no previous title.
- // When multi-process will be supported, return the last program index.
-
- int previousProgramIndex = -1;
+ int previousProgramIndex = context.Device.UserChannelPersistence.PreviousIndex;
context.ResponseData.Write(previousProgramIndex);
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs
new file mode 100644
index 00000000..40432074
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types
+{
+ public enum LaunchParameterKind : uint
+ {
+ UserChannel = 1,
+ PreselectedUser,
+ Unknown
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs
new file mode 100644
index 00000000..efc284a5
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types
+{
+ public enum ProgramSpecifyKind : uint
+ {
+ ExecuteProgram,
+ SubApplicationProgram,
+ RestartProgram
+ }
+}
diff --git a/Ryujinx.HLE/HOS/UserChannelPersistence.cs b/Ryujinx.HLE/HOS/UserChannelPersistence.cs
new file mode 100644
index 00000000..62f28fee
--- /dev/null
+++ b/Ryujinx.HLE/HOS/UserChannelPersistence.cs
@@ -0,0 +1,56 @@
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS
+{
+ public class UserChannelPersistence
+ {
+ private Stack<byte[]> _userChannelStorages;
+ public int PreviousIndex { get; private set; }
+ public int Index { get; private set; }
+ public ProgramSpecifyKind Kind { get; private set; }
+
+ public UserChannelPersistence()
+ {
+ _userChannelStorages = new Stack<byte[]>();
+ Kind = ProgramSpecifyKind.ExecuteProgram;
+ PreviousIndex = -1;
+ Index = 0;
+ }
+
+ public void Clear()
+ {
+ _userChannelStorages.Clear();
+ }
+
+ public void Push(byte[] data)
+ {
+ _userChannelStorages.Push(data);
+ }
+
+ public byte[] Pop()
+ {
+ return _userChannelStorages.Pop();
+ }
+
+ public bool IsEmpty => _userChannelStorages.Count == 0;
+
+ public void ExecuteProgram(ProgramSpecifyKind kind, ulong value)
+ {
+ Kind = kind;
+ PreviousIndex = Index;
+
+ switch (kind)
+ {
+ case ProgramSpecifyKind.ExecuteProgram:
+ Index = (int)value;
+ break;
+ case ProgramSpecifyKind.RestartProgram:
+ break;
+ default:
+ throw new NotImplementedException($"{kind} not implemented");
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/IHostUiHandler.cs b/Ryujinx.HLE/IHostUiHandler.cs
index bd64da87..8e9bfc77 100644
--- a/Ryujinx.HLE/IHostUiHandler.cs
+++ b/Ryujinx.HLE/IHostUiHandler.cs
@@ -1,4 +1,5 @@
using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
namespace Ryujinx.HLE
{
@@ -22,5 +23,13 @@ namespace Ryujinx.HLE
/// </summary>
/// <returns>True when OK is pressed, False otherwise.</returns>
bool DisplayMessageDialog(ControllerAppletUiArgs args);
+
+ /// <summary>
+ /// Tell the UI that we need to transisition to another program.
+ /// </summary>
+ /// <param name="device">The device instance.</param>
+ /// <param name="kind">The program kind.</param>
+ /// <param name="value">The value associated to the <paramref name="kind"/>.</param>
+ void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs
index 5401f1cc..f689d2e0 100644
--- a/Ryujinx.HLE/Switch.cs
+++ b/Ryujinx.HLE/Switch.cs
@@ -35,13 +35,15 @@ namespace Ryujinx.HLE
public PerformanceStatistics Statistics { get; private set; }
+ public UserChannelPersistence UserChannelPersistence { get; }
+
public Hid Hid { get; private set; }
public IHostUiHandler UiHandler { get; set; }
public bool EnableDeviceVsync { get; set; } = true;
- public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, IRenderer renderer, IAalOutput audioOut)
+ public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, UserChannelPersistence userChannelPersistence, IRenderer renderer, IAalOutput audioOut)
{
if (renderer == null)
{
@@ -53,6 +55,13 @@ namespace Ryujinx.HLE
throw new ArgumentNullException(nameof(audioOut));
}
+ if (userChannelPersistence == null)
+ {
+ throw new ArgumentNullException(nameof(userChannelPersistence));
+ }
+
+ UserChannelPersistence = userChannelPersistence;
+
AudioOut = audioOut;
Memory = new MemoryBlock(1UL << 32);
diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs
index 09062c86..fbf14e0e 100644
--- a/Ryujinx/Ui/ApplicationLibrary.cs
+++ b/Ryujinx/Ui/ApplicationLibrary.cs
@@ -5,10 +5,11 @@ using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ns;
-using Ryujinx.Common.Logging;
using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
using Ryujinx.HLE.Loaders.Npdm;
using System;
using System.Collections.Generic;
@@ -39,10 +40,12 @@ namespace Ryujinx.Ui
public static IEnumerable<string> GetFilesInDirectory(string directory)
{
Stack<string> stack = new Stack<string>();
+
stack.Push(directory);
+
while (stack.Count > 0)
{
- string dir = stack.Pop();
+ string dir = stack.Pop();
string[] content = { };
try
@@ -57,7 +60,9 @@ namespace Ryujinx.Ui
if (content.Length > 0)
{
foreach (string file in content)
+ {
yield return file;
+ }
}
try
@@ -72,7 +77,9 @@ namespace Ryujinx.Ui
if (content.Length > 0)
{
foreach (string subdir in content)
+ {
stack.Push(subdir);
+ }
}
}
}
@@ -94,6 +101,7 @@ namespace Ryujinx.Ui
// Builds the applications list with paths to found applications
List<string> applications = new List<string>();
+
foreach (string appDir in appDirs)
{
@@ -128,6 +136,7 @@ namespace Ryujinx.Ui
string developer = "Unknown";
string version = "0";
byte[] applicationIcon = null;
+
BlitStruct<ApplicationControlProperty> controlHolder = new BlitStruct<ApplicationControlProperty>(1);
try
@@ -448,23 +457,7 @@ namespace Ryujinx.Ui
private static void GetControlFsAndTitleId(PartitionFileSystem pfs, out IFileSystem controlFs, out string titleId)
{
- Nca controlNca = null;
-
- // Add keys to key set if needed
- _virtualFileSystem.ImportTickets(pfs);
-
- // Find the Control NCA and store it in variable called controlNca
- foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
- {
- pfs.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
-
- if (nca.Header.ContentType == NcaContentType.Control)
- {
- controlNca = nca;
- }
- }
+ (_, _, Nca controlNca) = ApplicationLoader.GetGameData(_virtualFileSystem, pfs, 0);
// Return the ControlFS
controlFs = controlNca?.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None);
@@ -574,6 +567,7 @@ namespace Ryujinx.Ui
if (!((U8Span)controlTitle.Name).IsEmpty())
{
titleName = controlTitle.Name.ToString();
+
break;
}
}
@@ -586,6 +580,7 @@ namespace Ryujinx.Ui
if (!((U8Span)controlTitle.Publisher).IsEmpty())
{
publisher = controlTitle.Publisher.ToString();
+
break;
}
}
@@ -611,68 +606,34 @@ namespace Ryujinx.Ui
private static bool IsUpdateApplied(string titleId, out string version)
{
- string jsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json");
+ string updatePath = "(unknown)";
- if (File.Exists(jsonPath))
+ try
{
- string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(jsonPath).Selected;
-
- if (!File.Exists(updatePath))
- {
- version = "";
-
- return false;
- }
+ (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
- using (FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read))
+ if (patchNca != null && controlNca != null)
{
- PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
-
- _virtualFileSystem.ImportTickets(nsp);
-
- foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
- {
- nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- try
- {
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
-
- if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != titleId)
- {
- break;
- }
-
- if (nca.Header.ContentType == NcaContentType.Control)
- {
- ApplicationControlProperty controlData = new ApplicationControlProperty();
-
- nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(out IFile nacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ ApplicationControlProperty controlData = new ApplicationControlProperty();
- nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
+ controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(out IFile nacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- version = controlData.DisplayVersion.ToString();
+ nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
- return true;
- }
- }
- catch (InvalidDataException)
- {
- Logger.Warning?.Print(LogClass.Application,
- $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
-
- break;
- }
- catch (MissingKeyException exception)
- {
- Logger.Warning?.Print(LogClass.Application,
- $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
+ version = controlData.DisplayVersion.ToString();
- break;
- }
- }
+ return true;
}
}
+ catch (InvalidDataException)
+ {
+ Logger.Warning?.Print(LogClass.Application,
+ $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
+ }
+ catch (MissingKeyException exception)
+ {
+ Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {updatePath}");
+ }
version = "";
diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs
index a654b385..61e6a80c 100644
--- a/Ryujinx/Ui/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/GameTableContextMenu.cs
@@ -13,6 +13,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
using System;
using System.Buffers;
using System.Collections.Generic;
@@ -246,7 +247,7 @@ namespace Ryujinx.Ui
return workingPath;
}
- private void ExtractSection(NcaSectionType ncaSectionType)
+ private void ExtractSection(NcaSectionType ncaSectionType, int programIndex = 0)
{
FileChooserDialog fileChooser = new FileChooserDialog("Choose the folder to extract into", null, FileChooserAction.SelectFolder, "Cancel", ResponseType.Cancel, "Extract", ResponseType.Accept);
fileChooser.SetPosition(WindowPosition.Center);
@@ -340,36 +341,12 @@ namespace Ryujinx.Ui
return;
}
- string titleUpdateMetadataPath = System.IO.Path.Combine(AppDataManager.GamesDirPath, mainNca.Header.TitleId.ToString("x16"), "updates.json");
- if (File.Exists(titleUpdateMetadataPath))
- {
- string updatePath = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(titleUpdateMetadataPath).Selected;
-
- if (File.Exists(updatePath))
- {
- FileStream updateFile = new FileStream(updatePath, FileMode.Open, FileAccess.Read);
- PartitionFileSystem nsp = new PartitionFileSystem(updateFile.AsStorage());
-
- _virtualFileSystem.ImportTickets(nsp);
-
- foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
- {
- nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
-
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
-
- if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" != mainNca.Header.TitleId.ToString("x16"))
- {
- break;
- }
+ (Nca updatePatchNca, _) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), programIndex, out _);
- if (nca.Header.ContentType == NcaContentType.Program)
- {
- patchNca = nca;
- }
- }
- }
+ if (updatePatchNca != null)
+ {
+ patchNca = updatePatchNca;
}
int index = Nca.GetSectionIndexFromType(ncaSectionType, mainNca.Header.ContentType);
diff --git a/Ryujinx/Ui/GtkHostUiHandler.cs b/Ryujinx/Ui/GtkHostUiHandler.cs
index 90830056..fd193dd7 100644
--- a/Ryujinx/Ui/GtkHostUiHandler.cs
+++ b/Ryujinx/Ui/GtkHostUiHandler.cs
@@ -2,6 +2,7 @@ using Gtk;
using Ryujinx.Common.Logging;
using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets;
+using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using System;
using System.Threading;
@@ -121,5 +122,11 @@ namespace Ryujinx.Ui
return error || okPressed;
}
+
+ public void ExecuteProgram(HLE.Switch device, ProgramSpecifyKind kind, ulong value)
+ {
+ device.UserChannelPersistence.ExecuteProgram(kind, value);
+ MainWindow.GlWidget?.Exit();
+ }
}
}
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 4e689513..6ce06985 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -11,6 +11,7 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
+using Ryujinx.HLE.HOS;
using Ryujinx.Ui.Diagnostic;
using System;
using System.Diagnostics;
@@ -27,6 +28,7 @@ namespace Ryujinx.Ui
{
private static VirtualFileSystem _virtualFileSystem;
private static ContentManager _contentManager;
+ private static UserChannelPersistence _userChannelPersistence;
private static WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
private static HLE.Switch _emulationContext;
@@ -34,12 +36,15 @@ namespace Ryujinx.Ui
private static GlRenderer _glWidget;
private static GtkHostUiHandler _uiHandler;
+ public static GlRenderer GlWidget => _glWidget;
+
private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false);
private static ListStore _tableStore;
private static bool _updatingGameTable;
private static bool _gameLoaded;
+ private static string _gamePath;
private static bool _ending;
#pragma warning disable CS0169, CS0649, IDE0044
@@ -110,6 +115,7 @@ namespace Ryujinx.Ui
}
_virtualFileSystem = VirtualFileSystem.CreateInstance();
+ _userChannelPersistence = new UserChannelPersistence();
_contentManager = new ContentManager(_virtualFileSystem);
if (migrationNeeded)
@@ -181,6 +187,7 @@ namespace Ryujinx.Ui
_statusBar.Hide();
_uiHandler = new GtkHostUiHandler(this);
+ _gamePath = null;
}
private void MainWindow_WindowStateEvent(object o, WindowStateEventArgs args)
@@ -278,7 +285,7 @@ namespace Ryujinx.Ui
{
_virtualFileSystem.Reload();
- HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, InitializeRenderer(), InitializeAudioEngine())
+ HLE.Switch instance = new HLE.Switch(_virtualFileSystem, _contentManager, _userChannelPersistence, InitializeRenderer(), InitializeAudioEngine())
{
UiHandler = _uiHandler
};
@@ -485,6 +492,7 @@ namespace Ryujinx.Ui
}
_emulationContext = device;
+ _gamePath = path;
_deviceExitStatus.Reset();
@@ -589,6 +597,7 @@ namespace Ryujinx.Ui
UpdateGameTable();
Task.Run(RefreshFirmwareLabel);
+ Task.Run(HandleRelaunch);
_stopEmulation.Sensitive = false;
_firmwareInstallFile.Sensitive = true;
@@ -962,6 +971,21 @@ namespace Ryujinx.Ui
}));
}
+ private void HandleRelaunch()
+ {
+ // If the previous index isn't -1, that mean we are relaunching.
+ if (_userChannelPersistence.PreviousIndex != -1)
+ {
+ LoadApplication(_gamePath);
+ }
+ else
+ {
+ // otherwise, clear state.
+ _userChannelPersistence = new UserChannelPersistence();
+ _gamePath = null;
+ }
+ }
+
private void HandleInstallerDialog(FileChooserDialog fileChooser)
{
if (fileChooser.Run() == (int)ResponseType.Accept)
diff --git a/Ryujinx/Ui/TitleUpdateWindow.cs b/Ryujinx/Ui/TitleUpdateWindow.cs
index d332b547..c3345271 100644
--- a/Ryujinx/Ui/TitleUpdateWindow.cs
+++ b/Ryujinx/Ui/TitleUpdateWindow.cs
@@ -9,6 +9,7 @@ using LibHac.Ns;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.HLE.HOS;
using System;
using System.Collections.Generic;
using System.IO;
@@ -83,63 +84,47 @@ namespace Ryujinx.Ui
{
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage());
- _virtualFileSystem.ImportTickets(nsp);
-
- foreach (DirectoryEntryEx fileEntry in nsp.EnumerateEntries("/", "*.nca"))
+ try
{
- nsp.OpenFile(out IFile ncaFile, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, nsp, _titleId, 0);
- try
+ if (controlNca != null && patchNca != null)
{
- Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.AsStorage());
-
- if ($"{nca.Header.TitleId.ToString("x16")[..^3]}000" == _titleId)
- {
- if (nca.Header.ContentType == NcaContentType.Control)
- {
- ApplicationControlProperty controlData = new ApplicationControlProperty();
-
- nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(out IFile nacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
- nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
-
- RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
- radioButton.JoinGroup(_noUpdateRadioButton);
-
- _availableUpdatesBox.Add(radioButton);
- _radioButtonToPathDictionary.Add(radioButton, path);
-
- radioButton.Show();
- radioButton.Active = true;
- }
- }
- else
- {
- GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
-
- break;
- }
+ ApplicationControlProperty controlData = new ApplicationControlProperty();
+
+ controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(out IFile nacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
+
+ RadioButton radioButton = new RadioButton($"Version {controlData.DisplayVersion.ToString()} - {path}");
+ radioButton.JoinGroup(_noUpdateRadioButton);
+
+ _availableUpdatesBox.Add(radioButton);
+ _radioButtonToPathDictionary.Add(radioButton, path);
+
+ radioButton.Show();
+ radioButton.Active = true;
}
- catch (InvalidDataException exception)
+ else
{
- Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {path}");
-
- if (showErrorDialog)
- {
- GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
- }
-
- break;
+ GtkDialog.CreateErrorDialog("The specified file does not contain an update for the selected title!");
}
- catch (MissingKeyException exception)
- {
- Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}");
+ }
+ catch (InvalidDataException exception)
+ {
+ Logger.Error?.Print(LogClass.Application, $"{exception.Message}. Errored File: {path}");
- if (showErrorDialog)
- {
- GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", $"Your key set is missing a key with the name: {exception.Name}");
- }
+ if (showErrorDialog)
+ {
+ GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", "The NCA header content type check has failed. This is usually because the header key is incorrect or missing.");
+ }
+ }
+ catch (MissingKeyException exception)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}");
- break;
+ if (showErrorDialog)
+ {
+ GtkDialog.CreateInfoDialog("Ryujinx - Error", "Add Update Failed!", $"Your key set is missing a key with the name: {exception.Name}");
}
}
}