aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS
diff options
context:
space:
mode:
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");
+ }
+ }
+ }
+}