From b126ea48c63a3de8da8f3b817860c0323f2621ef Mon Sep 17 00:00:00 2001
From: Thomas Guillemard <thog@protonmail.com>
Date: Thu, 14 Feb 2019 01:44:39 +0100
Subject: [PATCH] Support HomeBrew Loader (#577)

* Make it possibles to load hb-loader and hb-menu

One issue remains with hb-menu homebrew icons because of SIMD issues
(libjpeg-turbo related) and netloader doesn't work.

* Implement GetApplicationControlData

* Fix shared fonts for NSO/NRO

* Add homebrew NRO romfs support

This readd the NRO support by parsing the ASET header

* Address comments about HomebrewRomFs

* override Dispose in homebrew romfs stream

* Use a struct for file timestamp

* Simplify positional increments in GetApplicationControlData

* Address comments

* improve readability of the memory permission check in SetProcessMemoryPermission

* Fix previous broken check

* Add address space checks in SetProcessMemoryPermission
---
 Ryujinx.HLE/FileSystem/FileSystemProvider.cs  |  30 +++
 Ryujinx.HLE/FileSystem/IFileSystemProvider.cs |   3 +
 Ryujinx.HLE/FileSystem/PFsProvider.cs         |   5 +
 Ryujinx.HLE/FileSystem/RomFsProvider.cs       |   5 +
 Ryujinx.HLE/HOS/HomebrewRomFsStream.cs        |  77 +++++++
 Ryujinx.HLE/HOS/Horizon.cs                    |  58 ++++-
 .../HOS/Kernel/SupervisorCall/SvcMemory.cs    | 126 +++++++++++
 .../HOS/Kernel/SupervisorCall/SvcTable.cs     |   5 +-
 .../HOS/Services/FspSrv/FileTimestamp.cs      |  11 +
 .../HOS/Services/FspSrv/IFileSystem.cs        |  32 ++-
 .../Ns/IApplicationManagerInterface.cs        | 209 +++++++++++++++++-
 .../Services/Ns/IServiceGetterInterface.cs    |   9 +-
 .../HOS/Services/Pm/IShellInterface.cs        |  34 +++
 Ryujinx.HLE/HOS/Services/ServiceFactory.cs    |   5 +
 Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs |  37 +++-
 Ryujinx.HLE/HOS/Services/Sm/SmErr.cs          |   1 +
 .../Executables/NxRelocatableObject.cs        |   3 +
 17 files changed, 633 insertions(+), 17 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs

diff --git a/Ryujinx.HLE/FileSystem/FileSystemProvider.cs b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs
index a806c9eebb..f5459eece0 100644
--- a/Ryujinx.HLE/FileSystem/FileSystemProvider.cs
+++ b/Ryujinx.HLE/FileSystem/FileSystemProvider.cs
@@ -1,5 +1,6 @@
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.FspSrv;
+using Ryujinx.HLE.Utilities;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -279,5 +280,34 @@ namespace Ryujinx.HLE.FileSystem
 
             throw new InvalidOperationException($"Path {path} is not a child directory of {_rootPath}");
         }
+
+        public FileTimestamp GetFileTimeStampRaw(string name)
+        {
+            CheckIfDescendentOfRootPath(name);
+
+            DateTime creationDateTime   = DateTime.UnixEpoch;
+            DateTime modifiedDateTime   = DateTime.UnixEpoch;
+            DateTime lastAccessDateTime = DateTime.UnixEpoch;
+
+            if (File.Exists(name))
+            {
+                creationDateTime   = File.GetCreationTime(name);
+                modifiedDateTime   = File.GetLastWriteTime(name);
+                lastAccessDateTime = File.GetLastAccessTime(name);
+            }
+            else if (Directory.Exists(name))
+            {
+                creationDateTime   = Directory.GetCreationTime(name);
+                modifiedDateTime   = Directory.GetLastWriteTime(name);
+                lastAccessDateTime = Directory.GetLastAccessTime(name);
+            }
+
+            return new FileTimestamp
+            {
+                CreationDateTime   = creationDateTime,
+                ModifiedDateTime   = modifiedDateTime,
+                LastAccessDateTime = lastAccessDateTime
+            };
+        }
     }
 }
diff --git a/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
index 8e2cae6430..82cdebd905 100644
--- a/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
+++ b/Ryujinx.HLE/FileSystem/IFileSystemProvider.cs
@@ -1,5 +1,6 @@
 using Ryujinx.HLE.HOS;
 using Ryujinx.HLE.HOS.Services.FspSrv;
+using System;
 
 namespace Ryujinx.HLE.FileSystem
 {
@@ -36,5 +37,7 @@ namespace Ryujinx.HLE.FileSystem
         long GetFreeSpace(ServiceCtx context);
 
         long GetTotalSpace(ServiceCtx context);
+
+        FileTimestamp GetFileTimeStampRaw(string name);
     }
 }
diff --git a/Ryujinx.HLE/FileSystem/PFsProvider.cs b/Ryujinx.HLE/FileSystem/PFsProvider.cs
index fdddc9b089..69e7a9b83d 100644
--- a/Ryujinx.HLE/FileSystem/PFsProvider.cs
+++ b/Ryujinx.HLE/FileSystem/PFsProvider.cs
@@ -143,5 +143,10 @@ namespace Ryujinx.HLE.FileSystem
         {
             throw new NotSupportedException();
         }
+
+        public FileTimestamp GetFileTimeStampRaw(string name)
+        {
+            throw new NotImplementedException();
+        }
     }
 }
diff --git a/Ryujinx.HLE/FileSystem/RomFsProvider.cs b/Ryujinx.HLE/FileSystem/RomFsProvider.cs
index 86bf234859..f64d99c704 100644
--- a/Ryujinx.HLE/FileSystem/RomFsProvider.cs
+++ b/Ryujinx.HLE/FileSystem/RomFsProvider.cs
@@ -160,5 +160,10 @@ namespace Ryujinx.HLE.FileSystem
         {
             throw new NotSupportedException();
         }
+
+        public FileTimestamp GetFileTimeStampRaw(string name)
+        {
+            throw new NotImplementedException();
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs b/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
new file mode 100644
index 0000000000..f39faf4d4c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/HomebrewRomFsStream.cs
@@ -0,0 +1,77 @@
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS
+{
+    class HomebrewRomFsStream : Stream
+    {
+        private Stream _baseStream;
+        private long   _positionOffset;
+
+        public HomebrewRomFsStream(Stream baseStream, long positionOffset)
+        {
+            _baseStream     = baseStream;
+            _positionOffset = positionOffset;
+
+            _baseStream.Position = _positionOffset;
+        }
+
+        public override bool CanRead => _baseStream.CanRead;
+
+        public override bool CanSeek => _baseStream.CanSeek;
+
+        public override bool CanWrite => false;
+
+        public override long Length => _baseStream.Length - _positionOffset;
+
+        public override long Position
+        {
+            get
+            {
+                return _baseStream.Position - _positionOffset;
+            }
+            set
+            {
+                _baseStream.Position = value + _positionOffset;
+            }
+        }
+
+        public override void Flush()
+        {
+            _baseStream.Flush();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            return _baseStream.Read(buffer, offset, count);
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin == SeekOrigin.Begin)
+            {
+                offset += _positionOffset;
+            }
+
+           return _baseStream.Seek(offset, origin);
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _baseStream.Dispose();
+            }
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 8a419af343..b5ce555a16 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -102,6 +102,8 @@ namespace Ryujinx.HLE.HOS
 
         public Horizon(Switch device)
         {
+            ControlData = new Nacp();
+
             Device = device;
 
             State = new SystemStateMgr();
@@ -549,14 +551,58 @@ namespace Ryujinx.HLE.HOS
 
             bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
 
-            using (FileStream input = new FileStream(filePath, FileMode.Open))
-            {
-                IExecutable staticObject = isNro
-                    ? (IExecutable)new NxRelocatableObject(input)
-                    : new NxStaticObject(input);
+            FileStream input = new FileStream(filePath, FileMode.Open);
 
-                ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
+            IExecutable staticObject;
+
+            if (isNro)
+            {
+                NxRelocatableObject obj = new NxRelocatableObject(input);
+                staticObject = obj;
+
+                // homebrew NRO can actually have some data after the actual NRO
+                if (input.Length > obj.FileSize)
+                {
+                    input.Position = obj.FileSize;
+
+                    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 nacpOffset = reader.ReadUInt64();
+                            ulong nacpSize = reader.ReadUInt64();
+
+                            ulong romfsOffset = reader.ReadUInt64();
+                            ulong romfsSize = reader.ReadUInt64();
+
+                            if (romfsSize != 0)
+                            {
+                                Device.FileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
+                            }
+                        }
+                        else
+                        {
+                            Logger.PrintWarning(LogClass.Loader, $"Unsupported ASET header version found \"{asetVersion}\"");
+                        }
+                    }
+                }
             }
+            else
+            {
+                staticObject = new NxStaticObject(input);
+            }
+
+            ContentManager.LoadEntries();
+
+            ProgramLoader.LoadStaticObjects(this, metaData, new IExecutable[] { staticObject });
         }
 
         private Npdm GetDefaultNpdm()
diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs
index 388dcc217c..6f8180c507 100644
--- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs
+++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcMemory.cs
@@ -386,6 +386,132 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
             return _process.MemoryManager.UnmapPhysicalMemory(address, size);
         }
 
+        public KernelResult MapProcessCodeMemory64(int handle, ulong dst, ulong src, ulong size)
+        {
+            return MapProcessCodeMemory(handle, dst, src, size);
+        }
+
+        public KernelResult MapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
+        {
+            if (!PageAligned(dst) || !PageAligned(src))
+            {
+                return KernelResult.InvalidAddress;
+            }
+
+            if (!PageAligned(size) || size == 0)
+            {
+                return KernelResult.InvalidSize;
+            }
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+            if (targetProcess == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
+                targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
+                targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
+                targetProcess.MemoryManager.InsideHeapRegion(dst, size))
+            {
+                return KernelResult.InvalidMemRange;
+            }
+
+            if (size + dst <= dst || size + src <= src)
+            {
+                return KernelResult.InvalidMemState;
+            }
+
+            return targetProcess.MemoryManager.MapProcessCodeMemory(dst, src, size);
+        }
+
+        public KernelResult UnmapProcessCodeMemory64(int handle, ulong dst, ulong src, ulong size)
+        {
+            return UnmapProcessCodeMemory(handle, dst, src, size);
+        }
+
+        public KernelResult UnmapProcessCodeMemory(int handle, ulong dst, ulong src, ulong size)
+        {
+            if (!PageAligned(dst) || !PageAligned(src))
+            {
+                return KernelResult.InvalidAddress;
+            }
+
+            if (!PageAligned(size) || size == 0)
+            {
+                return KernelResult.InvalidSize;
+            }
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+            if (targetProcess == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            if (targetProcess.MemoryManager.OutsideAddrSpace(dst, size) ||
+                targetProcess.MemoryManager.OutsideAddrSpace(src, size) ||
+                targetProcess.MemoryManager.InsideAliasRegion(dst, size) ||
+                targetProcess.MemoryManager.InsideHeapRegion(dst, size))
+            {
+                return KernelResult.InvalidMemRange;
+            }
+
+            if (size + dst <= dst || size + src <= src)
+            {
+                return KernelResult.InvalidMemState;
+            }
+
+            return targetProcess.MemoryManager.UnmapProcessCodeMemory(dst, src, size);
+        }
+
+        public KernelResult SetProcessMemoryPermission64(int handle, ulong src, ulong size, MemoryPermission permission)
+        {
+            return SetProcessMemoryPermission(handle, src, size, permission);
+        }
+
+        public KernelResult SetProcessMemoryPermission(int handle, ulong src, ulong size, MemoryPermission permission)
+        {
+            if (!PageAligned(src))
+            {
+                return KernelResult.InvalidAddress;
+            }
+
+            if (!PageAligned(size) || size == 0)
+            {
+                return KernelResult.InvalidSize;
+            }
+
+            if (permission != MemoryPermission.None &&
+                permission != MemoryPermission.Read &&
+                permission != MemoryPermission.ReadAndWrite &&
+                permission != MemoryPermission.ReadAndExecute)
+            {
+                return KernelResult.InvalidPermission;
+            }
+
+            KProcess currentProcess = _system.Scheduler.GetCurrentProcess();
+
+            KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
+
+            if (targetProcess == null)
+            {
+                return KernelResult.InvalidHandle;
+            }
+
+            if (targetProcess.MemoryManager.OutsideAddrSpace(src, size))
+            {
+                return KernelResult.InvalidMemState;
+            }
+
+            return targetProcess.MemoryManager.SetProcessMemoryPermission(src, size, permission);
+        }
+
         private static bool PageAligned(ulong position)
         {
             return (position & (KMemoryManager.PageSize - 1)) == 0;
diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs
index cbcb712f75..dd98e8a06e 100644
--- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs
+++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/SvcTable.cs
@@ -71,7 +71,10 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 { 0x6f, nameof(SvcHandler.GetSystemInfo64)                 },
                 { 0x70, nameof(SvcHandler.CreatePort64)                    },
                 { 0x71, nameof(SvcHandler.ManageNamedPort64)               },
-                { 0x72, nameof(SvcHandler.ConnectToPort64)                 }
+                { 0x72, nameof(SvcHandler.ConnectToPort64)                 },
+                { 0x73, nameof(SvcHandler.SetProcessMemoryPermission64)    },
+                { 0x77, nameof(SvcHandler.MapProcessCodeMemory64)          },
+                { 0x78, nameof(SvcHandler.UnmapProcessCodeMemory64)        }
             };
 
             _svcTable64 = new Action<SvcHandler, CpuThreadState>[0x80];
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs b/Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs
new file mode 100644
index 0000000000..879fb78cde
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/FileTimestamp.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.FspSrv
+{
+    struct FileTimestamp
+    {
+        public DateTime CreationDateTime;
+        public DateTime ModifiedDateTime;
+        public DateTime LastAccessDateTime;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
index 9e29446017..bcb9dbaf81 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystem.cs
@@ -38,8 +38,8 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
                 { 10, Commit                     },
                 { 11, GetFreeSpaceSize           },
                 { 12, GetTotalSpaceSize          },
-                { 13, CleanDirectoryRecursively  }
-                //{ 14, GetFileTimeStampRaw        }
+                { 13, CleanDirectoryRecursively  },
+                { 14, GetFileTimeStampRaw        }
             };
 
             _openPaths = new HashSet<string>();
@@ -368,6 +368,34 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
             return 0;
         }
 
+        // GetFileTimeStampRaw(buffer<bytes<0x301>, 0x19, 0x301> path) -> bytes<0x20> timestamp
+        public long GetFileTimeStampRaw(ServiceCtx context)
+        {
+            string name = ReadUtf8String(context);
+
+            string path = _provider.GetFullPath(name);
+
+            if (_provider.FileExists(path) || _provider.DirectoryExists(path))
+            {
+                FileTimestamp timestamp = _provider.GetFileTimeStampRaw(path);
+
+                context.ResponseData.Write(new DateTimeOffset(timestamp.CreationDateTime).ToUnixTimeSeconds());
+                context.ResponseData.Write(new DateTimeOffset(timestamp.ModifiedDateTime).ToUnixTimeSeconds());
+                context.ResponseData.Write(new DateTimeOffset(timestamp.LastAccessDateTime).ToUnixTimeSeconds());
+
+                byte[] data = new byte[8];
+
+                // is valid?
+                data[0] = 1;
+
+                context.ResponseData.Write(data);
+
+                return 0;
+            }
+
+            return MakeError(ErrorModule.Fs, FsErr.PathDoesNotExist);
+        }
+
         private bool IsPathAlreadyInUse(string path)
         {
             lock (_openPaths)
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
index 72d7787f88..88fdb79221 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
@@ -1,5 +1,9 @@
-using Ryujinx.HLE.HOS.Ipc;
+using LibHac;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using System;
 using System.Collections.Generic;
+using System.Text;
 
 namespace Ryujinx.HLE.HOS.Services.Ns
 {
@@ -9,14 +13,211 @@ namespace Ryujinx.HLE.HOS.Services.Ns
 
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
-        private bool _isInitialized;
-
         public IApplicationManagerInterface()
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                
+                { 400, GetApplicationControlData }
             };
         }
+
+        public long GetApplicationControlData(ServiceCtx context)
+        {
+            long position = context.Request.ReceiveBuff[0].Position;
+
+            Nacp nacp = context.Device.System.ControlData;
+
+            for (int i = 0; i < 0x10; i++)
+            {
+                NacpDescription description = nacp.Descriptions[i];
+
+                byte[] titleData     = new byte[0x200];
+                byte[] developerData = new byte[0x100];
+
+                if (description !=null && description.Title != null)
+                {
+                    byte[] titleDescriptionData = Encoding.ASCII.GetBytes(description.Title);
+                    Buffer.BlockCopy(titleDescriptionData, 0, titleData, 0, titleDescriptionData.Length);
+
+                }
+
+                if (description != null && description.Developer != null)
+                {
+                    byte[] developerDescriptionData = Encoding.ASCII.GetBytes(description.Developer);
+                    Buffer.BlockCopy(developerDescriptionData, 0, developerData, 0, developerDescriptionData.Length);
+                }
+
+                context.Memory.WriteBytes(position, titleData);
+                context.Memory.WriteBytes(position + 0x200, developerData);
+
+                position += i * 0x300;
+            }
+
+            byte[] isbn = new byte[0x25];
+
+            if (nacp.Isbn != null)
+            {
+                byte[] isbnData = Encoding.ASCII.GetBytes(nacp.Isbn);
+                Buffer.BlockCopy(isbnData, 0, isbn, 0, isbnData.Length);
+            }
+
+            context.Memory.WriteBytes(position, isbn);
+            position += isbn.Length;
+
+            context.Memory.WriteByte(position++, nacp.StartupUserAccount);
+            context.Memory.WriteByte(position++, nacp.TouchScreenUsageMode);
+            context.Memory.WriteByte(position++, nacp.AocRegistrationType);
+
+            context.Memory.WriteInt32(position, nacp.AttributeFlag);
+            position += 4;
+
+            context.Memory.WriteUInt32(position, nacp.SupportedLanguageFlag);
+            position += 4;
+
+            context.Memory.WriteUInt32(position, nacp.ParentalControlFlag);
+            position += 4;
+
+            context.Memory.WriteByte(position++, nacp.Screenshot);
+            context.Memory.WriteByte(position++, nacp.VideoCapture);
+            context.Memory.WriteByte(position++, nacp.DataLossConfirmation);
+            context.Memory.WriteByte(position++, nacp.PlayLogPolicy);
+
+            context.Memory.WriteUInt64(position, nacp.PresenceGroupId);
+            position += 8;
+
+            for (int i = 0; i < nacp.RatingAge.Length; i++)
+            {
+                context.Memory.WriteSByte(position++, nacp.RatingAge[i]);
+            }
+
+            byte[] displayVersion = new byte[0x10];
+
+            if (nacp.DisplayVersion != null)
+            {
+                byte[] displayVersionData = Encoding.ASCII.GetBytes(nacp.DisplayVersion);
+                Buffer.BlockCopy(displayVersionData, 0, displayVersion, 0, displayVersionData.Length);
+            }
+
+            context.Memory.WriteBytes(position, displayVersion);
+            position += displayVersion.Length;
+
+            context.Memory.WriteUInt64(position, nacp.AddOnContentBaseId);
+            position += 8;
+
+            context.Memory.WriteUInt64(position, nacp.SaveDataOwnerId);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.UserAccountSaveDataSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.UserAccountSaveDataJournalSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.DeviceSaveDataSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.DeviceSaveDataJournalSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.BcatDeliveryCacheStorageSize);
+            position += 8;
+
+            byte[] applicationErrorCodeCategory = new byte[0x8];
+
+            if (nacp.ApplicationErrorCodeCategory != null)
+            {
+                byte[] applicationErrorCodeCategoryData = Encoding.ASCII.GetBytes(nacp.ApplicationErrorCodeCategory);
+                Buffer.BlockCopy(applicationErrorCodeCategoryData, 0, applicationErrorCodeCategoryData, 0, applicationErrorCodeCategoryData.Length);
+            }
+
+            context.Memory.WriteBytes(position, applicationErrorCodeCategory);
+            position += applicationErrorCodeCategory.Length;
+
+            for (int i = 0; i < nacp.LocalCommunicationId.Length; i++)
+            {
+                context.Memory.WriteUInt64(position, nacp.LocalCommunicationId[i]);
+                position += 8;
+            }
+
+            context.Memory.WriteByte(position++, nacp.LogoType);
+            context.Memory.WriteByte(position++, nacp.LogoHandling);
+            context.Memory.WriteByte(position++, nacp.RuntimeAddOnContentInstall);
+
+            byte[] reserved000 = new byte[0x3];
+            context.Memory.WriteBytes(position, reserved000);
+            position += reserved000.Length;
+
+            context.Memory.WriteByte(position++, nacp.CrashReport);
+            context.Memory.WriteByte(position++, nacp.Hdcp);
+            context.Memory.WriteUInt64(position, nacp.SeedForPseudoDeviceId);
+            position += 8;
+
+            byte[] bcatPassphrase = new byte[65];
+            if (nacp.BcatPassphrase != null)
+            {
+                byte[] bcatPassphraseData = Encoding.ASCII.GetBytes(nacp.BcatPassphrase);
+                Buffer.BlockCopy(bcatPassphraseData, 0, bcatPassphrase, 0, bcatPassphraseData.Length);
+            }
+
+            context.Memory.WriteBytes(position, bcatPassphrase);
+            position += bcatPassphrase.Length;
+
+            context.Memory.WriteByte(position++, nacp.Reserved01);
+
+            byte[] reserved02 = new byte[0x6];
+            context.Memory.WriteBytes(position, reserved02);
+            position += reserved02.Length;
+
+            context.Memory.WriteInt64(position, nacp.UserAccountSaveDataSizeMax);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.UserAccountSaveDataJournalSizeMax);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.DeviceSaveDataSizeMax);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.DeviceSaveDataJournalSizeMax);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.TemporaryStorageSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.CacheStorageSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.CacheStorageJournalSize);
+            position += 8;
+
+            context.Memory.WriteInt64(position, nacp.CacheStorageDataAndJournalSizeMax);
+            position += 8;
+
+            context.Memory.WriteInt16(position, nacp.CacheStorageIndex);
+            position += 2;
+
+            byte[] reserved03 = new byte[0x6];
+            context.Memory.WriteBytes(position, reserved03);
+            position += reserved03.Length;
+
+
+            for (int i = 0; i < 16; i++)
+            {
+                ulong value = 0;
+
+                if (nacp.PlayLogQueryableApplicationId.Count > i)
+                {
+                    value = nacp.PlayLogQueryableApplicationId[i];
+                }
+
+                context.Memory.WriteUInt64(position, value);
+                position += 8;
+            }
+
+            context.Memory.WriteByte(position++, nacp.PlayLogQueryCapability);
+            context.Memory.WriteByte(position++, nacp.RepairFlag);
+            context.Memory.WriteByte(position++, nacp.ProgramIndex);
+
+            return 0;
+        }
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
index 12f7b69b12..89dde1f9bb 100644
--- a/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
@@ -13,8 +13,15 @@ namespace Ryujinx.HLE.HOS.Services.Ns
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                //...
+                { 7996, GetApplicationManagerInterface }
             };
         }
+
+        public long GetApplicationManagerInterface(ServiceCtx context)
+        {
+            MakeObject(context, new IApplicationManagerInterface());
+
+            return 0;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs b/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs
new file mode 100644
index 0000000000..8880b3348e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Pm/IShellInterface.cs
@@ -0,0 +1,34 @@
+using Ryujinx.HLE.HOS.Ipc;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Pm
+{
+    class IShellInterface : IpcService
+    {
+        private Dictionary<int, ServiceProcessRequest> _commands;
+
+        public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
+
+        public IShellInterface()
+        {
+            _commands = new Dictionary<int, ServiceProcessRequest>
+            {
+                { 6, GetApplicationPid }
+            };
+        }
+
+        // GetApplicationPid() -> u64
+        public long GetApplicationPid(ServiceCtx context)
+        {
+            // FIXME: This is wrong but needed to make hb loader works
+            // TODO: Change this when we will have a way to process via a PM like interface.
+            long pid = context.Process.Pid;
+
+            context.ResponseData.Write(pid);
+
+            return 0;
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
index 3853d82e6f..83a217a5d3 100644
--- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
+++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
@@ -17,6 +17,7 @@ using Ryujinx.HLE.HOS.Services.Ns;
 using Ryujinx.HLE.HOS.Services.Nv;
 using Ryujinx.HLE.HOS.Services.Pctl;
 using Ryujinx.HLE.HOS.Services.Pl;
+using Ryujinx.HLE.HOS.Services.Pm;
 using Ryujinx.HLE.HOS.Services.Prepo;
 using Ryujinx.HLE.HOS.Services.Psm;
 using Ryujinx.HLE.HOS.Services.Set;
@@ -131,6 +132,7 @@ namespace Ryujinx.HLE.HOS.Services
                 case "ns:am":
                     return new IApplicationManagerInterface();
 
+                case "ns:am2":
                 case "ns:ec":
                     return new IServiceGetterInterface();
 
@@ -161,6 +163,9 @@ namespace Ryujinx.HLE.HOS.Services
                 case "pl:u":
                     return new ISharedFontManager();
 
+                case "pm:shell":
+                    return new IShellInterface();
+
                 case "prepo:a":
                     return new IPrepoService();
 
diff --git a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
index 6940bfc8df..914862f1c9 100644
--- a/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
+++ b/Ryujinx.HLE/HOS/Services/Sm/IUserInterface.cs
@@ -23,9 +23,10 @@ namespace Ryujinx.HLE.HOS.Services.Sm
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                { 0, Initialize      },
-                { 1, GetService      },
-                { 2, RegisterService }
+                { 0, Initialize        },
+                { 1, GetService        },
+                { 2, RegisterService   },
+                { 3, UnregisterService }
             };
 
             _registeredServices = new ConcurrentDictionary<string, KPort>();
@@ -128,6 +129,36 @@ namespace Ryujinx.HLE.HOS.Services.Sm
             return 0;
         }
 
+        public long UnregisterService(ServiceCtx context)
+        {
+            if (!_isInitialized)
+            {
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotInitialized);
+            }
+
+            long namePosition = context.RequestData.BaseStream.Position;
+
+            string name = ReadName(context);
+
+            context.RequestData.BaseStream.Seek(namePosition + 8, SeekOrigin.Begin);
+
+            bool isLight = (context.RequestData.ReadInt32() & 1) != 0;
+
+            int maxSessions = context.RequestData.ReadInt32();
+
+            if (name == string.Empty)
+            {
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.InvalidName);
+            }
+
+            if (!_registeredServices.TryRemove(name, out _))
+            {
+                return ErrorCode.MakeError(ErrorModule.Sm, SmErr.NotRegistered);
+            }
+
+            return 0;
+        }
+
         private static string ReadName(ServiceCtx context)
         {
             string name = string.Empty;
diff --git a/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs b/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs
index 5b5a66dc2f..7dd3a20573 100644
--- a/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs
+++ b/Ryujinx.HLE/HOS/Services/Sm/SmErr.cs
@@ -5,5 +5,6 @@ namespace Ryujinx.HLE.HOS.Services.Sm
         public const int NotInitialized    = 2;
         public const int AlreadyRegistered = 4;
         public const int InvalidName       = 6;
+        public const int NotRegistered     = 7;
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs
index eb3ca94a98..e68fe2670e 100644
--- a/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs
+++ b/Ryujinx.HLE/Loaders/Executables/NxRelocatableObject.cs
@@ -13,6 +13,7 @@ namespace Ryujinx.HLE.Loaders.Executables
         public int RoOffset   { get; private set; }
         public int DataOffset { get; private set; }
         public int BssSize    { get; private set; }
+        public int FileSize   { get; private set; }
 
         public int BssOffset => DataOffset + Data.Length;
 
@@ -59,6 +60,8 @@ namespace Ryujinx.HLE.Loaders.Executables
             Text = Read(textOffset, textSize);
             Ro   = Read(roOffset,   roSize);
             Data = Read(dataOffset, dataSize);
+
+            FileSize = fileSize;
         }
     }
 }
\ No newline at end of file