From 0ff00bd6d35390eb132eecd3ba307ba4bbfdd56d Mon Sep 17 00:00:00 2001
From: Thog <me@thog.eu>
Date: Fri, 15 May 2020 03:56:14 +0200
Subject: [PATCH] am: Implement common web applets (#1188)

* am: Implemnet common web applets

This implement parsing of input and output of web applets while making
those close directly.

TODO for the future: Use and hook a web browser.

* Address Ac_K's comments
---
 Ryujinx.HLE/HOS/Applets/AppletManager.cs      |   8 +-
 .../HOS/Applets/Browser/BootDisplayKind.cs    |  11 ++
 .../HOS/Applets/Browser/BrowserApplet.cs      | 105 ++++++++++++++
 .../HOS/Applets/Browser/BrowserArgument.cs    | 133 ++++++++++++++++++
 .../HOS/Applets/Browser/BrowserOutput.cs      |  47 +++++++
 .../HOS/Applets/Browser/BrowserOutputType.cs  |  14 ++
 .../HOS/Applets/Browser/DocumentKind.cs       |   9 ++
 .../HOS/Applets/Browser/LeftStickMode.cs      |   8 ++
 Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs   |  13 ++
 .../HOS/Applets/Browser/WebArgHeader.cs       |   9 ++
 Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs  |   9 ++
 .../HOS/Applets/Browser/WebArgTLVType.cs      |  62 ++++++++
 .../Applets/Browser/WebCommonReturnValue.cs   |  10 ++
 .../HOS/Applets/Browser/WebExitReason.cs      |  11 ++
 Ryujinx.HLE/HOS/Applets/CommonArguments.cs    |  16 +++
 15 files changed, 463 insertions(+), 2 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs
 create mode 100644 Ryujinx.HLE/HOS/Applets/CommonArguments.cs

diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
index 1cba9ec91..5d075882c 100644
--- a/Ryujinx.HLE/HOS/Applets/AppletManager.cs
+++ b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
@@ -1,4 +1,5 @@
-using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using Ryujinx.HLE.HOS.Applets.Browser;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
 using System;
 using System.Collections.Generic;
 
@@ -14,7 +15,10 @@ namespace Ryujinx.HLE.HOS.Applets
             {
                 { AppletId.PlayerSelect,     typeof(PlayerSelectApplet)     },
                 { AppletId.Controller,       typeof(ControllerApplet)       },
-                { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) }
+                { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) },
+                { AppletId.LibAppletWeb,     typeof(BrowserApplet)          },
+                { AppletId.LibAppletShop,    typeof(BrowserApplet)          },
+                { AppletId.LibAppletOff,     typeof(BrowserApplet)          }
             };
         }
 
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs b/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs
new file mode 100644
index 000000000..fe6e60400
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/BootDisplayKind.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    enum BootDisplayKind
+    {
+        White,
+        Offline,
+        Black,
+        Share,
+        Lobby
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs
new file mode 100644
index 000000000..f9693c340
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs
@@ -0,0 +1,105 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    internal class BrowserApplet : IApplet
+    {
+        public event EventHandler AppletStateChanged;
+
+        private AppletSession _normalSession;
+        private AppletSession _interactiveSession;
+
+        private CommonArguments _commonArguments;
+        private List<BrowserArgument> _arguments;
+        private ShimKind _shimKind;
+
+        public BrowserApplet(Horizon system) {}
+
+        public ResultCode GetResult()
+        {
+            return ResultCode.Success;
+        }
+
+        public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
+        {
+            _normalSession = normalSession;
+            _interactiveSession = interactiveSession;
+
+            _commonArguments = IApplet.ReadStruct<CommonArguments>(_normalSession.Pop());
+
+            Logger.PrintStub(LogClass.ServiceAm, $"WebApplet version: 0x{_commonArguments.AppletVersion:x8}");
+
+            ReadOnlySpan<byte> webArguments = _normalSession.Pop();
+
+            (_shimKind, _arguments) = BrowserArgument.ParseArguments(webArguments);
+
+            Logger.PrintStub(LogClass.ServiceAm, $"Web Arguments: {_arguments.Count}");
+
+            foreach (BrowserArgument argument in _arguments)
+            {
+                Logger.PrintStub(LogClass.ServiceAm, $"{argument.Type}: {argument.GetValue()}");
+            }
+
+            if ((_commonArguments.AppletVersion >= 0x80000 && _shimKind == ShimKind.Web) || (_commonArguments.AppletVersion >= 0x30000 && _shimKind == ShimKind.Share))
+            {
+                List<BrowserOutput> result = new List<BrowserOutput>();
+
+                result.Add(new BrowserOutput(BrowserOutputType.ExitReason, (uint)WebExitReason.ExitButton));
+
+                _normalSession.Push(BuildResponseNew(result));
+            }
+            else
+            {
+                WebCommonReturnValue result = new WebCommonReturnValue()
+                {
+                    ExitReason  = WebExitReason.ExitButton,
+                };
+
+                _normalSession.Push(BuildResponseOld(result));
+            }
+
+            AppletStateChanged?.Invoke(this, null);
+
+            return ResultCode.Success;
+        }
+
+        private byte[] BuildResponseOld(WebCommonReturnValue result)
+        {
+            using (MemoryStream stream = new MemoryStream())
+            using (BinaryWriter writer = new BinaryWriter(stream))
+            {
+                writer.WriteStruct(result);
+
+                return stream.ToArray();
+            }
+        }
+        private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
+        {
+            using (MemoryStream stream = new MemoryStream())
+            using (BinaryWriter writer = new BinaryWriter(stream))
+            {
+                writer.WriteStruct(new WebArgHeader
+                {
+                    Count    = (ushort)outputArguments.Count,
+                    ShimKind = _shimKind
+                });
+
+                foreach (BrowserOutput output in outputArguments)
+                {
+                    output.Write(writer);
+                }
+
+                writer.Write(new byte[0x2000 - writer.BaseStream.Position]);
+
+                return stream.ToArray();
+            }
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs b/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs
new file mode 100644
index 000000000..17fd40898
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/BrowserArgument.cs
@@ -0,0 +1,133 @@
+using Ryujinx.HLE.HOS.Services.Account.Acc;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    class BrowserArgument
+    {
+        public WebArgTLVType Type  { get; }
+        public byte[]        Value { get; }
+
+        public BrowserArgument(WebArgTLVType type, byte[] value)
+        {
+            Type  = type;
+            Value = value;
+        }
+
+        private static readonly Dictionary<WebArgTLVType, Type> _typeRegistry = new Dictionary<WebArgTLVType, Type>
+        {
+            { WebArgTLVType.InitialURL,                     typeof(string) },
+            { WebArgTLVType.CallbackUrl,                    typeof(string) },
+            { WebArgTLVType.CallbackableUrl,                typeof(string) },
+            { WebArgTLVType.ApplicationId,                  typeof(ulong) },
+            { WebArgTLVType.DocumentPath,                   typeof(string) },
+            { WebArgTLVType.DocumentKind,                   typeof(DocumentKind) },
+            { WebArgTLVType.SystemDataId,                   typeof(ulong) },
+            { WebArgTLVType.Whitelist,                      typeof(string) },
+            { WebArgTLVType.NewsFlag,                       typeof(bool) },
+            { WebArgTLVType.UserID,                         typeof(UserId) },
+            { WebArgTLVType.ScreenShotEnabled,              typeof(bool) },
+            { WebArgTLVType.EcClientCertEnabled,            typeof(bool) },
+            { WebArgTLVType.UnknownFlag0x14,                typeof(bool) },
+            { WebArgTLVType.UnknownFlag0x15,                typeof(bool) },
+            { WebArgTLVType.PlayReportEnabled,              typeof(bool) },
+            { WebArgTLVType.BootDisplayKind,                typeof(BootDisplayKind) },
+            { WebArgTLVType.FooterEnabled,                  typeof(bool) },
+            { WebArgTLVType.PointerEnabled,                 typeof(bool) },
+            { WebArgTLVType.LeftStickMode,                  typeof(LeftStickMode) },
+            { WebArgTLVType.KeyRepeatFrame1,                typeof(int) },
+            { WebArgTLVType.KeyRepeatFrame2,                typeof(int) },
+            { WebArgTLVType.BootAsMediaPlayerInverted,      typeof(bool) },
+            { WebArgTLVType.DisplayUrlKind,                 typeof(bool) },
+            { WebArgTLVType.BootAsMediaPlayer,              typeof(bool) },
+            { WebArgTLVType.ShopJumpEnabled,                typeof(bool) },
+            { WebArgTLVType.MediaAutoPlayEnabled,           typeof(bool) },
+            { WebArgTLVType.LobbyParameter,                 typeof(string) },
+            { WebArgTLVType.JsExtensionEnabled,             typeof(bool) },
+            { WebArgTLVType.AdditionalCommentText,          typeof(string) },
+            { WebArgTLVType.TouchEnabledOnContents,         typeof(bool) },
+            { WebArgTLVType.UserAgentAdditionalString,      typeof(string) },
+            { WebArgTLVType.MediaPlayerAutoCloseEnabled,    typeof(bool) },
+            { WebArgTLVType.PageCacheEnabled,               typeof(bool) },
+            { WebArgTLVType.WebAudioEnabled,                typeof(bool) },
+            { WebArgTLVType.PageFadeEnabled,                typeof(bool) },
+            { WebArgTLVType.BootLoadingIconEnabled,         typeof(bool) },
+            { WebArgTLVType.PageScrollIndicatorEnabled,     typeof(bool) },
+            { WebArgTLVType.MediaPlayerSpeedControlEnabled, typeof(bool) },
+            { WebArgTLVType.OverrideWebAudioVolume,         typeof(float) },
+            { WebArgTLVType.OverrideMediaAudioVolume,       typeof(float) },
+            { WebArgTLVType.MediaPlayerUiEnabled,           typeof(bool) },
+        };
+
+        public static (ShimKind, List<BrowserArgument>) ParseArguments(ReadOnlySpan<byte> data)
+        {
+            List<BrowserArgument> browserArguments = new List<BrowserArgument>();
+
+            WebArgHeader header = IApplet.ReadStruct<WebArgHeader>(data.Slice(0, 8));
+
+            ReadOnlySpan<byte> rawTLVs = data.Slice(8);
+
+            for (int i = 0; i < header.Count; i++)
+            {
+                WebArgTLV tlv = IApplet.ReadStruct<WebArgTLV>(rawTLVs);
+                ReadOnlySpan<byte> tlvData = rawTLVs.Slice(Unsafe.SizeOf<WebArgTLV>(), tlv.Size);
+
+                browserArguments.Add(new BrowserArgument((WebArgTLVType)tlv.Type, tlvData.ToArray()));
+
+                rawTLVs = rawTLVs.Slice(Unsafe.SizeOf<WebArgTLV>() + tlv.Size);
+            }
+
+            return (header.ShimKind, browserArguments);
+        }
+
+        public object GetValue()
+        {
+            if (_typeRegistry.TryGetValue(Type, out Type valueType))
+            {
+                if (valueType == typeof(string))
+                {
+                    return Encoding.UTF8.GetString(Value);
+                }
+                else if (valueType == typeof(bool))
+                {
+                    return Value[0] == 1;
+                }
+                else if (valueType == typeof(uint))
+                {
+                    return BitConverter.ToUInt32(Value);
+                }
+                else if (valueType == typeof(int))
+                {
+                    return BitConverter.ToInt32(Value);
+                }
+                else if (valueType == typeof(ulong))
+                {
+                    return BitConverter.ToUInt64(Value);
+                }
+                else if (valueType == typeof(long))
+                {
+                    return BitConverter.ToInt64(Value);
+                }
+                else if (valueType == typeof(float))
+                {
+                    return BitConverter.ToSingle(Value);
+                }
+                else if (valueType == typeof(UserId))
+                {
+                    return new UserId(Value);
+                }
+                else if (valueType.IsEnum)
+                {
+                    return Enum.ToObject(valueType, BitConverter.ToInt32(Value));
+                }
+
+                return $"{valueType.Name} parsing not implemented";
+            }
+
+            return $"Unknown value format (raw length: {Value.Length})";
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs b/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs
new file mode 100644
index 000000000..0b3682627
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutput.cs
@@ -0,0 +1,47 @@
+using Ryujinx.Common;
+using System;
+using System.IO;
+
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    class BrowserOutput
+    {
+        public BrowserOutputType Type { get; }
+        public byte[] Value { get; }
+
+        public BrowserOutput(BrowserOutputType type, byte[] value)
+        {
+            Type  = type;
+            Value = value;
+        }
+
+        public BrowserOutput(BrowserOutputType type, uint value)
+        {
+            Type  = type;
+            Value = BitConverter.GetBytes(value); 
+        }
+
+        public BrowserOutput(BrowserOutputType type, ulong value)
+        {
+            Type  = type;
+            Value = BitConverter.GetBytes(value);
+        }
+
+        public BrowserOutput(BrowserOutputType type, bool value)
+        {
+            Type  = type;
+            Value = BitConverter.GetBytes(value);
+        }
+
+        public void Write(BinaryWriter writer)
+        {
+            writer.WriteStruct(new WebArgTLV
+            {
+                Type = (ushort)Type,
+                Size = (ushort)Value.Length
+            });
+
+            writer.Write(Value);
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs b/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs
new file mode 100644
index 000000000..209ae8ae1
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/BrowserOutputType.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    enum BrowserOutputType : ushort
+    {
+        ExitReason                        = 0x1,
+        LastUrl                           = 0x2,
+        LastUrlSize                       = 0x3,
+        SharePostResult                   = 0x4,
+        PostServiceName                   = 0x5,
+        PostServiceNameSize               = 0x6,
+        PostId                            = 0x7,
+        MediaPlayerAutoClosedByCompletion = 0x8
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs b/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs
new file mode 100644
index 000000000..385bcdd00
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/DocumentKind.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    enum DocumentKind
+    {
+        OfflineHtmlPage = 1,
+        ApplicationLegalInformation,
+        SystemDataPage
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs b/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs
new file mode 100644
index 000000000..917549d2c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/LeftStickMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    enum LeftStickMode
+    {
+        Pointer = 0,
+        Cursor
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs b/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs
new file mode 100644
index 000000000..ca2ef32fe
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/ShimKind.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    public enum ShimKind : uint
+    {
+        Shop = 1,
+        Login,
+        Offline,
+        Share,
+        Web,
+        Wifi,
+        Lobby
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs b/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs
new file mode 100644
index 000000000..c5e19f6ca
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/WebArgHeader.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    public struct WebArgHeader
+    {
+        public ushort   Count;
+        public ushort   Padding;
+        public ShimKind ShimKind;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs b/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs
new file mode 100644
index 000000000..f6c1e5ae0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLV.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    public struct WebArgTLV
+    {
+        public ushort Type;
+        public ushort Size;
+        public uint   Padding;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs b/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs
new file mode 100644
index 000000000..bd3032072
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/WebArgTLVType.cs
@@ -0,0 +1,62 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    enum WebArgTLVType : ushort
+    {
+        InitialURL                       = 0x1,
+        CallbackUrl                      = 0x3,
+        CallbackableUrl                  = 0x4,
+        ApplicationId                    = 0x5,
+        DocumentPath                     = 0x6,
+        DocumentKind                     = 0x7,
+        SystemDataId                     = 0x8,
+        ShareStartPage                   = 0x9,
+        Whitelist                        = 0xA,
+        NewsFlag                         = 0xB,
+        UserID                           = 0xE,
+        AlbumEntry0                      = 0xF,
+        ScreenShotEnabled                = 0x10,
+        EcClientCertEnabled              = 0x11,
+        PlayReportEnabled                = 0x13,
+        UnknownFlag0x14                  = 0x14,
+        UnknownFlag0x15                  = 0x15,
+        BootDisplayKind                  = 0x17,
+        BackgroundKind                   = 0x18,
+        FooterEnabled                    = 0x19,
+        PointerEnabled                   = 0x1A,
+        LeftStickMode                    = 0x1B,
+        KeyRepeatFrame1                  = 0x1C,
+        KeyRepeatFrame2                  = 0x1D,
+        BootAsMediaPlayerInverted        = 0x1E,
+        DisplayUrlKind                   = 0x1F,
+        BootAsMediaPlayer                = 0x21,
+        ShopJumpEnabled                  = 0x22,
+        MediaAutoPlayEnabled             = 0x23,
+        LobbyParameter                   = 0x24,
+        ApplicationAlbumEntry            = 0x26,
+        JsExtensionEnabled               = 0x27,
+        AdditionalCommentText            = 0x28,
+        TouchEnabledOnContents           = 0x29,
+        UserAgentAdditionalString        = 0x2A,
+        AdditionalMediaData0             = 0x2B,
+        MediaPlayerAutoCloseEnabled      = 0x2C,
+        PageCacheEnabled                 = 0x2D,
+        WebAudioEnabled                  = 0x2E,
+        FooterFixedKind                  = 0x32,
+        PageFadeEnabled                  = 0x33,
+        MediaCreatorApplicationRatingAge = 0x34,
+        BootLoadingIconEnabled           = 0x35,
+        PageScrollIndicatorEnabled       = 0x36,
+        MediaPlayerSpeedControlEnabled   = 0x37,
+        AlbumEntry1                      = 0x38,
+        AlbumEntry2                      = 0x39,
+        AlbumEntry3                      = 0x3A,
+        AdditionalMediaData1             = 0x3B,
+        AdditionalMediaData2             = 0x3C,
+        AdditionalMediaData3             = 0x3D,
+        BootFooterButton                 = 0x3E,
+        OverrideWebAudioVolume           = 0x3F,
+        OverrideMediaAudioVolume         = 0x40,
+        BootMode                         = 0x41,
+        MediaPlayerUiEnabled             = 0x43
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs b/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs
new file mode 100644
index 000000000..aab7a86bc
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/WebCommonReturnValue.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    public unsafe struct WebCommonReturnValue
+    {
+        public WebExitReason ExitReason;
+        public uint          Padding;
+        public fixed byte    LastUrl[0x1000];
+        public ulong         LastUrlSize;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs b/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs
new file mode 100644
index 000000000..4e44d34ab
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/Browser/WebExitReason.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Applets.Browser
+{
+    public enum WebExitReason : uint
+    {
+        ExitButton,
+        BackButton,
+        Requested,
+        LastUrl,
+        ErrorDialog = 7
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/CommonArguments.cs b/Ryujinx.HLE/HOS/Applets/CommonArguments.cs
new file mode 100644
index 000000000..5da34db1f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/CommonArguments.cs
@@ -0,0 +1,16 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+    [StructLayout(LayoutKind.Sequential, Pack = 8)]
+    struct CommonArguments
+    {
+        public uint  Version;
+        public uint  StructureSize;
+        public uint  AppletVersion;
+        public uint  ThemeColor;
+        [MarshalAs(UnmanagedType.I1)]
+        public bool  PlayStartupSound;
+        public ulong SystemTicks;
+    }
+}