aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS
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.HLE/HOS
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.HLE/HOS')
-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
6 files changed, 309 insertions, 116 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");
+ }
+ }
+ }
+}