aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx/Ui
diff options
context:
space:
mode:
authorMary <me@thog.eu>2020-09-21 05:45:30 +0200
committerGitHub <noreply@github.com>2020-09-21 13:45:30 +1000
commit33f8284bc0f80b18bdce6f8e9a363f5b70388788 (patch)
treec48fe40f33cf90dab0fc08a4b2e327a07cfaf2a5 /Ryujinx/Ui
parent90ab28d1c6ecdcaec2d8a3df905de3c0639eb869 (diff)
hle/ui: Basic multi programs support (#1560)
* hos/gui: Add a check of NCA program index in titleid This add a check to `ApplicationLoader` for the last 2 digits of the game TitleId who seems to be the NCA program index. We currently return the last index, instead of the lower one. Same check is added to ApplicationLibrary in the UI. I've cleaned up both file too. * hle: implement partial relaunch logic TODO: make the emulator auto relauch. * Handle auto relaunch * hle: Unify update usage system * hle: Implement support of multi programs in update system * Add some documentation * Address rip's comment Co-authored-by: Ac_K <Acoustik666@gmail.com>
Diffstat (limited to 'Ryujinx/Ui')
-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
5 files changed, 104 insertions, 150 deletions
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}");
}
}
}