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>
This commit is contained in:
Mary 2020-09-21 05:45:30 +02:00 committed by GitHub
parent 90ab28d1c6
commit 33f8284bc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 435 additions and 270 deletions

View file

@ -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;
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateData(_virtualFileSystem, titleId, 0, out updatePath);
if (!File.Exists(updatePath))
if (patchNca != null && controlNca != null)
{
version = "";
ApplicationControlProperty controlData = new ApplicationControlProperty();
return false;
}
using (FileStream file = new FileStream(updatePath, FileMode.Open, FileAccess.Read))
{
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();
nacpFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
version = controlData.DisplayVersion.ToString();
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}");
break;
}
}
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();
version = controlData.DisplayVersion.ToString();
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 = "";