From a5750f437d58e63ed76235d171732ca25dd34be2 Mon Sep 17 00:00:00 2001
From: Morph <39850852+Morph1984@users.noreply.github.com>
Date: Sun, 8 Nov 2020 23:27:33 -0500
Subject: [PATCH] applets/web: Initial implementation of the web browser applet

---
 .../hle/service/am/applets/web_browser.cpp    | 227 +++++++++++++++++-
 src/core/hle/service/am/applets/web_browser.h |  25 ++
 src/core/hle/service/am/applets/web_types.h   | 178 ++++++++++++++
 3 files changed, 428 insertions(+), 2 deletions(-)
 create mode 100644 src/core/hle/service/am/applets/web_types.h

diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index 74ef037fe0..7f398254e8 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <optional>
+
 #include "common/assert.h"
 #include "common/logging/log.h"
+#include "common/string_util.h"
 #include "core/core.h"
 #include "core/frontend/applets/web_browser.h"
 #include "core/hle/result.h"
@@ -12,6 +15,76 @@
 
 namespace Service::AM::Applets {
 
+namespace {
+
+template <typename T>
+void ParseRawValue(T& value, const std::vector<u8>& data) {
+    static_assert(std::is_trivially_copyable_v<T>,
+                  "It's undefined behavior to use memcpy with non-trivially copyable objects");
+    std::memcpy(&value, data.data(), data.size());
+}
+
+template <typename T>
+T ParseRawValue(const std::vector<u8>& data) {
+    T value;
+    ParseRawValue(value, data);
+    return value;
+}
+
+std::string ParseStringValue(const std::vector<u8>& data) {
+    return Common::StringFromFixedZeroTerminatedBuffer(reinterpret_cast<const char*>(data.data()),
+                                                       data.size());
+}
+
+WebArgInputTLVMap ReadWebArgs(const std::vector<u8>& web_arg, WebArgHeader& web_arg_header) {
+    std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader));
+
+    if (web_arg.size() == sizeof(WebArgHeader)) {
+        return {};
+    }
+
+    WebArgInputTLVMap input_tlv_map;
+
+    u64 current_offset = sizeof(WebArgHeader);
+
+    for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) {
+        if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) {
+            return input_tlv_map;
+        }
+
+        WebArgInputTLV input_tlv;
+        std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV));
+
+        current_offset += sizeof(WebArgInputTLV);
+
+        if (web_arg.size() < current_offset + input_tlv.arg_data_size) {
+            return input_tlv_map;
+        }
+
+        std::vector<u8> data(input_tlv.arg_data_size);
+        std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size);
+
+        current_offset += input_tlv.arg_data_size;
+
+        input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data));
+    }
+
+    return input_tlv_map;
+}
+
+std::optional<std::vector<u8>> GetInputTLVData(const WebArgInputTLVMap& input_tlv_map,
+                                               WebArgInputTLVType input_tlv_type) {
+    const auto map_it = input_tlv_map.find(input_tlv_type);
+
+    if (map_it == input_tlv_map.end()) {
+        return std::nullopt;
+    }
+
+    return map_it->second;
+}
+
+} // namespace
+
 WebBrowser::WebBrowser(Core::System& system_, const Core::Frontend::WebBrowserApplet& frontend_)
     : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
 
@@ -19,6 +92,55 @@ WebBrowser::~WebBrowser() = default;
 
 void WebBrowser::Initialize() {
     Applet::Initialize();
+
+    LOG_INFO(Service_AM, "Initializing Web Browser Applet.");
+
+    LOG_DEBUG(Service_AM,
+              "Initializing Applet with common_args: arg_version={}, lib_version={}, "
+              "play_startup_sound={}, size={}, system_tick={}, theme_color={}",
+              common_args.arguments_version, common_args.library_version,
+              common_args.play_startup_sound, common_args.size, common_args.system_tick,
+              common_args.theme_color);
+
+    web_applet_version = WebAppletVersion{common_args.library_version};
+
+    const auto web_arg_storage = broker.PopNormalDataToApplet();
+    ASSERT(web_arg_storage != nullptr);
+
+    const auto& web_arg = web_arg_storage->GetData();
+    ASSERT_OR_EXECUTE(web_arg.size() >= sizeof(WebArgHeader), { return; });
+
+    web_arg_input_tlv_map = ReadWebArgs(web_arg, web_arg_header);
+
+    LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}",
+              web_arg_header.total_tlv_entries, web_arg_header.shim_kind);
+
+    switch (web_arg_header.shim_kind) {
+    case ShimKind::Shop:
+        InitializeShop();
+        break;
+    case ShimKind::Login:
+        InitializeLogin();
+        break;
+    case ShimKind::Offline:
+        InitializeOffline();
+        break;
+    case ShimKind::Share:
+        InitializeShare();
+        break;
+    case ShimKind::Web:
+        InitializeWeb();
+        break;
+    case ShimKind::Wifi:
+        InitializeWifi();
+        break;
+    case ShimKind::Lobby:
+        InitializeLobby();
+        break;
+    default:
+        UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+        break;
+    }
 }
 
 bool WebBrowser::TransactionComplete() const {
@@ -30,9 +152,110 @@ ResultCode WebBrowser::GetStatus() const {
 }
 
 void WebBrowser::ExecuteInteractive() {
-    UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
+    UNIMPLEMENTED_MSG("WebSession is not implemented");
 }
 
-void WebBrowser::Execute() {}
+void WebBrowser::Execute() {
+    switch (web_arg_header.shim_kind) {
+    case ShimKind::Shop:
+        ExecuteShop();
+        break;
+    case ShimKind::Login:
+        ExecuteLogin();
+        break;
+    case ShimKind::Offline:
+        ExecuteOffline();
+        break;
+    case ShimKind::Share:
+        ExecuteShare();
+        break;
+    case ShimKind::Web:
+        ExecuteWeb();
+        break;
+    case ShimKind::Wifi:
+        ExecuteWifi();
+        break;
+    case ShimKind::Lobby:
+        ExecuteLobby();
+        break;
+    default:
+        UNREACHABLE_MSG("Invalid ShimKind={}", web_arg_header.shim_kind);
+        WebBrowserExit(WebExitReason::EndButtonPressed);
+        break;
+    }
+}
+
+void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) {
+    if ((web_arg_header.shim_kind == ShimKind::Share &&
+         web_applet_version >= WebAppletVersion::Version196608) ||
+        (web_arg_header.shim_kind == ShimKind::Web &&
+         web_applet_version >= WebAppletVersion::Version524288)) {
+        // TODO: Push Output TLVs instead of a WebCommonReturnValue
+    }
+
+    WebCommonReturnValue web_common_return_value;
+
+    web_common_return_value.exit_reason = exit_reason;
+    std::memcpy(&web_common_return_value.last_url, last_url.data(), last_url.size());
+    web_common_return_value.last_url_size = last_url.size();
+
+    LOG_DEBUG(Service_AM, "WebCommonReturnValue: exit_reason={}, last_url={}, last_url_size={}",
+              exit_reason, last_url, last_url.size());
+
+    complete = true;
+    std::vector<u8> out_data(sizeof(WebCommonReturnValue));
+    std::memcpy(out_data.data(), &web_common_return_value, out_data.size());
+    broker.PushNormalDataFromApplet(std::make_shared<IStorage>(system, std::move(out_data)));
+    broker.SignalStateChanged();
+}
+
+void WebBrowser::InitializeShop() {}
+
+void WebBrowser::InitializeLogin() {}
+
+void WebBrowser::InitializeOffline() {}
+
+void WebBrowser::InitializeShare() {}
+
+void WebBrowser::InitializeWeb() {}
+
+void WebBrowser::InitializeWifi() {}
+
+void WebBrowser::InitializeLobby() {}
+
+void WebBrowser::ExecuteShop() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Shop Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteLogin() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Login Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteOffline() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Offline Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteShare() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Share Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteWeb() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Web Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteWifi() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Wifi Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
+
+void WebBrowser::ExecuteLobby() {
+    LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented");
+    WebBrowserExit(WebExitReason::EndButtonPressed);
+}
 
 } // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 0584142e52..a1ed5fd1dc 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -8,6 +8,7 @@
 #include "common/common_types.h"
 #include "core/hle/result.h"
 #include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/am/applets/web_types.h"
 
 namespace Core {
 class System;
@@ -28,12 +29,36 @@ public:
     void ExecuteInteractive() override;
     void Execute() override;
 
+    void WebBrowserExit(WebExitReason exit_reason, std::string last_url = "");
+
 private:
+    // Initializers for the various types of browser applets
+    void InitializeShop();
+    void InitializeLogin();
+    void InitializeOffline();
+    void InitializeShare();
+    void InitializeWeb();
+    void InitializeWifi();
+    void InitializeLobby();
+
+    // Executors for the various types of browser applets
+    void ExecuteShop();
+    void ExecuteLogin();
+    void ExecuteOffline();
+    void ExecuteShare();
+    void ExecuteWeb();
+    void ExecuteWifi();
+    void ExecuteLobby();
+
     const Core::Frontend::WebBrowserApplet& frontend;
 
     bool complete{false};
     ResultCode status{RESULT_SUCCESS};
 
+    WebAppletVersion web_applet_version;
+    WebArgHeader web_arg_header;
+    WebArgInputTLVMap web_arg_input_tlv_map;
+
     Core::System& system;
 };
 
diff --git a/src/core/hle/service/am/applets/web_types.h b/src/core/hle/service/am/applets/web_types.h
new file mode 100644
index 0000000000..419c2bf791
--- /dev/null
+++ b/src/core/hle/service/am/applets/web_types.h
@@ -0,0 +1,178 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <unordered_map>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Service::AM::Applets {
+
+enum class WebAppletVersion : u32_le {
+    Version0 = 0x0,          // Only used by WifiWebAuthApplet
+    Version131072 = 0x20000, // 1.0.0 - 2.3.0
+    Version196608 = 0x30000, // 3.0.0 - 4.1.0
+    Version327680 = 0x50000, // 5.0.0 - 5.1.0
+    Version393216 = 0x60000, // 6.0.0 - 7.0.1
+    Version524288 = 0x80000, // 8.0.0+
+};
+
+enum class ShimKind : u32 {
+    Shop = 1,
+    Login = 2,
+    Offline = 3,
+    Share = 4,
+    Web = 5,
+    Wifi = 6,
+    Lobby = 7,
+};
+
+enum class WebExitReason : u32 {
+    EndButtonPressed = 0,
+    BackButtonPressed = 1,
+    ExitRequested = 2,
+    CallbackURL = 3,
+    WindowClosed = 4,
+    ErrorDialog = 7,
+};
+
+enum class WebArgInputTLVType : u16 {
+    InitialURL = 0x1,
+    CallbackURL = 0x3,
+    CallbackableURL = 0x4,
+    ApplicationID = 0x5,
+    DocumentPath = 0x6,
+    DocumentKind = 0x7,
+    SystemDataID = 0x8,
+    ShareStartPage = 0x9,
+    Whitelist = 0xA,
+    News = 0xB,
+    UserID = 0xE,
+    AlbumEntry0 = 0xF,
+    ScreenShotEnabled = 0x10,
+    EcClientCertEnabled = 0x11,
+    PlayReportEnabled = 0x13,
+    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,
+    YouTubeVideoWhitelist = 0x31,
+    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,
+    WebSessionEnabled = 0x42,
+    MediaPlayerOfflineEnabled = 0x43,
+};
+
+enum class WebArgOutputTLVType : u16 {
+    ShareExitReason = 0x1,
+    LastURL = 0x2,
+    LastURLSize = 0x3,
+    SharePostResult = 0x4,
+    PostServiceName = 0x5,
+    PostServiceNameSize = 0x6,
+    PostID = 0x7,
+    PostIDSize = 0x8,
+    MediaPlayerAutoClosedByCompletion = 0x9,
+};
+
+enum class DocumentKind : u32 {
+    OfflineHtmlPage = 1,
+    ApplicationLegalInformation = 2,
+    SystemDataPage = 3,
+};
+
+enum class ShareStartPage : u32 {
+    Default,
+    Settings,
+};
+
+enum class BootDisplayKind : u32 {
+    Default,
+    White,
+    Black,
+};
+
+enum class BackgroundKind : u32 {
+    Default,
+};
+
+enum class LeftStickMode : u32 {
+    Pointer,
+    Cursor,
+};
+
+enum class WebSessionBootMode : u32 {
+    AllForeground,
+    AllForegroundInitiallyHidden,
+};
+
+struct WebArgHeader {
+    u16 total_tlv_entries{};
+    INSERT_PADDING_BYTES(2);
+    ShimKind shim_kind{};
+};
+static_assert(sizeof(WebArgHeader) == 0x8, "WebArgHeader has incorrect size.");
+
+struct WebArgInputTLV {
+    WebArgInputTLVType input_tlv_type{};
+    u16 arg_data_size{};
+    INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(WebArgInputTLV) == 0x8, "WebArgInputTLV has incorrect size.");
+
+struct WebArgOutputTLV {
+    WebArgOutputTLVType output_tlv_type{};
+    u16 arg_data_size{};
+    INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(WebArgOutputTLV) == 0x8, "WebArgOutputTLV has incorrect size.");
+
+struct WebCommonReturnValue {
+    WebExitReason exit_reason{};
+    INSERT_PADDING_WORDS(1);
+    std::array<char, 0x1000> last_url{};
+    u64 last_url_size{};
+};
+static_assert(sizeof(WebCommonReturnValue) == 0x1010, "WebCommonReturnValue has incorrect size.");
+
+using WebArgInputTLVMap = std::unordered_map<WebArgInputTLVType, std::vector<u8>>;
+
+} // namespace Service::AM::Applets