From 7fe5035e88427fa61f8f6920ec350b0830f98b4c Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 16 Feb 2025 17:43:58 -0600 Subject: [PATCH] feat: add support for offline html docs inside the browser applet --- src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs | 25 ++- .../ILibraryRealAppletSelfAccessor.cs | 8 + .../SystemAppletProxy/IWindowController.cs | 7 +- .../FileSystemProxy/FileSystemProxyHelper.cs | 172 ++++++++++++++++++ .../HOS/Services/Fs/IFileSystemProxy.cs | 81 ++++++++- .../HOS/Services/Ns/IDocumentInterface.cs | 8 +- .../Ns/IReadOnlyApplicationRecordInterface.cs | 13 +- 7 files changed, 289 insertions(+), 25 deletions(-) diff --git a/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs index 10d3cc8e4..46c174fce 100644 --- a/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs +++ b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs @@ -522,24 +522,21 @@ namespace Ryujinx.HLE.HOS.Applets internal RealApplet GetApplicationApplet() { - RealApplet applet = null; - lock (_lock) + if (_application != null) { - foreach (var (_, value) in _applets) + return _application; + } + else + { + if (_homeMenu != null) { - if (value.IsApplication) - { - applet = value; - break; - } + return _homeMenu; + } + else + { + return _overlayDisp; } } - - if (applet == null) - { - return _foregroundRequestedApplet; - } - return applet; } public void RemoveProcess(ulong processHandlePid) diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs index 213bee597..13243bdc7 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs @@ -218,6 +218,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib return ResultCode.Success; } + [CommandCmif(51)] + // ReportVisibleErrorWithErrorContext(nn::err::ErrorCode) + public ResultCode ReportVisibleErrorWithErrorContext(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + return ResultCode.Success; + } + [CommandCmif(60)] // GetMainAppletApplicationDesiredLanguage() -> nn::os::Language public ResultCode GetMainAppletApplicationDesiredLanguage(ServiceCtx context) diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs index 59332ae96..102498332 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -26,8 +26,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys // GetAppletResourceUserIdOfCallerApplet() -> nn::applet::AppletResourceUserId public ResultCode GetAppletResourceUserIdOfCallerApplet(ServiceCtx context) { - ulong appletResourceUserId = _pid; - appletResourceUserId = context.Device.System.WindowSystem.GetByAruId(_pid).CallerApplet.ProcessHandle.TitleId; + ulong appletResourceUserId = 0x0100000000001000; + if (context.Device.System.WindowSystem.GetApplicationApplet() != null) + { + appletResourceUserId = context.Device.System.WindowSystem.GetApplicationApplet().ProcessHandle.TitleId; + } context.ResponseData.Write(appletResourceUserId); Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId }); return ResultCode.Success; diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs index ff8c2f609..e5e73d1df 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/FileSystemProxyHelper.cs @@ -5,15 +5,21 @@ using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; using LibHac.FsSystem; +using LibHac.Ncm; using LibHac.Spl; using LibHac.Tools.Es; using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.Loaders.Processes; +using Ryujinx.HLE.Loaders.Processes.Extensions; using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using ApplicationId = LibHac.ApplicationId; +using ContentType = LibHac.Fs.ContentType; using Path = System.IO.Path; namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy @@ -72,6 +78,172 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy return ResultCode.Success; } + + public static ResultCode OpenXciHtml(ServiceCtx context, ulong applicationId, Switch device, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Xci xci = new(context.Device.System.KeySet, ncaStorage); + + if (!xci.HasPartition(XciPartitionType.Secure)) + { + return ResultCode.PartitionNotFound; + } + + var partitionFileSystem = xci.OpenPartition(XciPartitionType.Secure); + + Nca nca = null; + + try + { + Dictionary applications = partitionFileSystem.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel); + + if (applicationId == 0) + { + foreach ((ulong _, ContentMetaData content) in applications) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + break; + } + } + else if (applications.TryGetValue(applicationId, out ContentMetaData content)) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + } + + ProcessLoaderHelper.RegisterProgramMapInfo(device, partitionFileSystem).ThrowIfFailure(); + } + catch (Exception ex) + { + return ResultCode.InvalidInput; + } + + LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using SharedRef sharedFs = new(fileSystem); + + using SharedRef adapter = FileSystemInterfaceAdapter.CreateShared(ref sharedFs.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + return ResultCode.Success; + + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + public static ResultCode OpenNspHtml(ServiceCtx context, string nspPath, ulong applicationId, Switch device, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + LocalStorage storage = new(nspPath, FileAccess.Read, FileMode.Open); + PartitionFileSystem pfs = new(); + using SharedRef nsp = new(pfs); + pfs.Initialize(storage).ThrowIfFailure(); + + ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet); + Nca nca = null; + + try + { + Dictionary applications = nsp.Get.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel); + + if (applicationId == 0) + { + foreach ((ulong _, ContentMetaData content) in applications) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + break; + } + } + else if (applications.TryGetValue(applicationId, out ContentMetaData content)) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + } + + ProcessLoaderHelper.RegisterProgramMapInfo(device, nsp.Get).ThrowIfFailure(); + } + catch (Exception ex) + { + return ResultCode.InvalidInput; + } + + LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using SharedRef sharedFs = new(fileSystem); + + using SharedRef adapter = FileSystemInterfaceAdapter.CreateShared(ref sharedFs.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + return ResultCode.Success; + + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + + public static ResultCode OpenNcaHtml(ServiceCtx context, string nspPath, ulong applicationId, Switch device, LibHac.Fs.IStorage ncaStorage, out IFileSystem openedFileSystem) + { + openedFileSystem = null; + + try + { + Nca ncaApp = new(context.Device.System.KeySet, ncaStorage); + + if (!ncaApp.SectionExists(NcaSectionType.Data)) + { + return ResultCode.PartitionNotFound; + } + + LibHac.Fs.Fsa.IFileSystem fileSystemB = ncaApp.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + + Nca nca = null; + + try + { + Dictionary applications = fileSystemB.GetContentData(ContentMetaType.Application, device.FileSystem, device.System.FsIntegrityCheckLevel); + + if (applicationId == 0) + { + foreach ((ulong _, ContentMetaData content) in applications) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + break; + } + } + else if (applications.TryGetValue(applicationId, out ContentMetaData content)) + { + nca = content.GetNcaByType(device.FileSystem.KeySet, LibHac.Ncm.ContentType.HtmlDocument, device.Configuration.UserChannelPersistence.Index); + } + + ProcessLoaderHelper.RegisterProgramMapInfo(device, fileSystemB).ThrowIfFailure(); + } + catch (Exception ex) + { + return ResultCode.InvalidInput; + } + + LibHac.Fs.Fsa.IFileSystem fileSystem = nca.OpenFileSystem(NcaSectionType.Data, context.Device.System.FsIntegrityCheckLevel); + using SharedRef sharedFs = new(fileSystem); + + using SharedRef adapter = FileSystemInterfaceAdapter.CreateShared(ref sharedFs.Ref, true); + + openedFileSystem = new IFileSystem(ref adapter.Ref); + return ResultCode.Success; + + } + catch (HorizonResultException ex) + { + return (ResultCode)ex.ResultValue.Value; + } + } + public static ResultCode OpenFileSystemFromInternalFile(ServiceCtx context, string fullPath, out IFileSystem openedFileSystem) { diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index 52a9f5e6c..e1cd012df 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -8,6 +8,7 @@ using LibHac.FsSystem; using LibHac.Ncm; using LibHac.Sf; using LibHac.Spl; +using LibHac.Tools.Fs; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; @@ -15,6 +16,7 @@ using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; using Ryujinx.HLE.HOS.Services.Ns.Types; using Ryujinx.HLE.HOS.Services.Pcv; +using Ryujinx.HLE.Loaders.Processes; using Ryujinx.Memory; using System; using System.IO; @@ -52,8 +54,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs // OpenFileSystemWithPatch(nn::fssrv::sf::FileSystemType filesystem_type, nn::ApplicationId tid) -> object contentFs public ResultCode OpenFileSystemWithPatch(ServiceCtx context) { + // TODO: Find out why the title id read from here is incorrect. FileSystemType fileSystemType = (FileSystemType)context.RequestData.ReadInt32(); - ulong titleId = context.RequestData.ReadUInt64(); + ulong titleId = 0x0100000000001000; + if (context.Device.System.WindowSystem.GetApplicationApplet() != null) + { + titleId = context.Device.System.WindowSystem.GetApplicationApplet().ProcessHandle.TitleId; + } + Logger.Info?.Print(LogClass.ServiceAm, $" { titleId} {fileSystemType} "); string switchPath = string.Empty; Logger.Stub?.PrintStub(LogClass.ServiceFs); foreach (RyuApplicationData ryuApplicationData in context.Device.Configuration.Titles) @@ -70,14 +78,75 @@ namespace Ryujinx.HLE.HOS.Services.Fs return ResultCode.PathDoesNotExist; } string fullPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(switchPath); - ResultCode result = FileSystemProxyHelper.OpenFileSystemFromInternalFile(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); - - if (result == ResultCode.Success) + if (fullPath == null) { - MakeObject(context, fileSystem); + fullPath = switchPath; } + FileStream fileStream = new(fullPath, FileMode.Open, FileAccess.Read); + string extension = System.IO.Path.GetExtension(fullPath); - return result; + if (fileSystemType == FileSystemType.ContentManual) + { + if (extension == ".xci") + { + ResultCode result = FileSystemProxyHelper.OpenXciHtml(context, titleId, context.Device,fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else if (extension == ".nsp") + { + ResultCode result = FileSystemProxyHelper.OpenNspHtml(context, fullPath, titleId, context.Device,fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else + { + ResultCode result = FileSystemProxyHelper.OpenNcaHtml(context, fullPath, titleId, context.Device,fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + } + else + { + if (extension == ".nca") + { + ResultCode result = FileSystemProxyHelper.OpenNcaFs(context, fullPath, fileStream.AsStorage(), out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + else if (extension == ".nsp") + { + ResultCode result = FileSystemProxyHelper.OpenNsp(context, fullPath, out FileSystemProxy.IFileSystem fileSystem); + + if (result == ResultCode.Success) + { + MakeObject(context, fileSystem); + } + + return result; + } + } + return ResultCode.InvalidInput; } [CommandCmif(8)] diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IDocumentInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IDocumentInterface.cs index 202f894f9..700556419 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ns/IDocumentInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IDocumentInterface.cs @@ -16,7 +16,13 @@ namespace Ryujinx.HLE.HOS.Services.Ns // GetRunningApplicationProgramId() -> u64 public ResultCode GetRunningApplicationProgramId(ServiceCtx context) { - context.ResponseData.Write(context.Device.Processes.ActiveApplication.ProgramId); + ulong appletResourceUserId = 0x0100000000001000; + if (context.Device.System.WindowSystem.GetApplicationApplet() != null) + { + appletResourceUserId = context.Device.System.WindowSystem.GetApplicationApplet().ProcessHandle.TitleId; + } + + context.ResponseData.Write(appletResourceUserId); return ResultCode.Success; } } diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationRecordInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationRecordInterface.cs index 1989846d9..915349965 100644 --- a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationRecordInterface.cs +++ b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationRecordInterface.cs @@ -1,7 +1,16 @@ -namespace Ryujinx.HLE.HOS.Services.Ns +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Ns { class IReadOnlyApplicationRecordInterface : IpcService { - + [CommandCmif(2)] // 10.0.0+ + // IsDataCorruptedResult() -> bool + public ResultCode IsDataCorrupted(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceNs); + context.ResponseData.Write(false); + return ResultCode.Success; + } } }