diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h index 621a7b4921..beb52b74dd 100644 --- a/src/core/hle/service/am/am_types.h +++ b/src/core/hle/service/am/am_types.h @@ -94,6 +94,7 @@ enum class AppletId : u32 { LoginShare = 0x18, WebAuth = 0x19, MyPage = 0x1A, + Lhub = 0x35 }; enum class AppletProgramId : u64 { diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp index 8246b3e88e..e7cbdf6361 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp @@ -236,10 +236,6 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr applet_, WebBrowser::~WebBrowser() = default; void WebBrowser::Initialize() { - if (Settings::values.disable_web_applet) { - return; - } - FrontendApplet::Initialize(); LOG_INFO(Service_AM, "Initializing Web Browser Applet."); @@ -264,6 +260,12 @@ void WebBrowser::Initialize() { LOG_DEBUG(Service_AM, "WebArgHeader: total_tlv_entries={}, shim_kind={}", web_arg_header.total_tlv_entries, web_arg_header.shim_kind); + if (Settings::values.disable_web_applet && + web_arg_header.shim_kind != ShimKind::Web && + web_arg_header.shim_kind != ShimKind::Lhub) { + return; + } + ExtractSharedFonts(system); switch (web_arg_header.shim_kind) { @@ -288,6 +290,9 @@ void WebBrowser::Initialize() { case ShimKind::Lobby: InitializeLobby(); break; + case ShimKind::Lhub: + InitializeLhub(); + break; default: ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); break; @@ -303,8 +308,19 @@ void WebBrowser::ExecuteInteractive() { } void WebBrowser::Execute() { + if (web_arg_header.shim_kind == ShimKind::Web) { + ExecuteWeb(); + return; + } + + if (web_arg_header.shim_kind == ShimKind::Lhub) { + ExecuteLhub(); + return; + } + if (Settings::values.disable_web_applet) { - LOG_WARNING(Service_AM, "(STUBBED) called, Web Browser Applet is disabled"); + LOG_WARNING(Service_AM, "(STUBBED) called, Web Browser Applet is disabled. shim_kind={}", + web_arg_header.shim_kind); WebBrowserExit(WebExitReason::EndButtonPressed); return; } @@ -331,6 +347,9 @@ void WebBrowser::Execute() { case ShimKind::Lobby: ExecuteLobby(); break; + case ShimKind::Lhub: + ExecuteLhub(); + break; default: ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); WebBrowserExit(WebExitReason::EndButtonPressed); @@ -351,17 +370,99 @@ void WebBrowser::ExtractOfflineRomFS() { } void WebBrowser::WebBrowserExit(WebExitReason exit_reason, std::string last_url) { - if ((web_arg_header.shim_kind == ShimKind::Share && + const bool use_tlv_output = + (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 + web_applet_version >= WebAppletVersion::Version524288) || + (web_arg_header.shim_kind == ShimKind::Lhub); + + // https://switchbrew.org/wiki/Internet_Browser#TLVs + if (use_tlv_output) { + LOG_DEBUG(Service_AM, "Using TLV output: exit_reason={}, last_url={}, last_url_size={}", + exit_reason, last_url, last_url.size()); + + // storage size for TLVs is 0x2000 bytes (as per switchbrew documentation) + constexpr size_t TLV_STORAGE_SIZE = 0x2000; + std::vector out_data(TLV_STORAGE_SIZE, 0); + + size_t current_offset = sizeof(WebArgHeader); + u16 tlv_count = 0; + + // align and matchng TLV struct alignment + auto align_offset = [](size_t offset) -> size_t { + return (offset + 7) & ~static_cast(7); + }; + + // 0x1 ShareExitReason + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::ShareExitReason; + tlv.arg_data_size = sizeof(u32); + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + const u32 exit_reason_value = static_cast(exit_reason); + std::memcpy(out_data.data() + current_offset, &exit_reason_value, sizeof(u32)); + current_offset += sizeof(u32); + + current_offset = align_offset(current_offset); + tlv_count++; + } + + // 0x2 LastUrl + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::LastURL; + const u16 url_data_size = static_cast(last_url.size() + 1); + tlv.arg_data_size = url_data_size; + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + // null terminator + std::memcpy(out_data.data() + current_offset, last_url.c_str(), last_url.size() + 1); + current_offset += url_data_size; + current_offset = align_offset(current_offset); + tlv_count++; + } + + // 0x3 LastUrlSize + { + WebArgOutputTLV tlv{}; + tlv.output_tlv_type = WebArgOutputTLVType::LastURLSize; + tlv.arg_data_size = sizeof(u64); + + std::memcpy(out_data.data() + current_offset, &tlv, sizeof(WebArgOutputTLV)); + current_offset += sizeof(WebArgOutputTLV); + + const u64 url_size = last_url.size(); + std::memcpy(out_data.data() + current_offset, &url_size, sizeof(u64)); + current_offset += sizeof(u64); + tlv_count++; + } + + WebArgHeader out_header{}; + out_header.total_tlv_entries = tlv_count; + out_header.shim_kind = web_arg_header.shim_kind; + std::memcpy(out_data.data(), &out_header, sizeof(WebArgHeader)); + + LOG_DEBUG(Service_AM, "TLV output: total_size={}, tlv_count={}, used_offset={}", + out_data.size(), tlv_count, current_offset); + + complete = true; + PushOutData(std::make_shared(system, std::move(out_data))); + Exit(); + return; } - WebCommonReturnValue web_common_return_value; + // for old browser, keep old return, use 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()); + std::memcpy(&web_common_return_value.last_url, last_url.data(), + std::min(last_url.size(), web_common_return_value.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={}", @@ -516,4 +617,13 @@ void WebBrowser::ExecuteLobby() { LOG_WARNING(Service_AM, "(STUBBED) called, Lobby Applet is not implemented"); WebBrowserExit(WebExitReason::EndButtonPressed); } + +void WebBrowser::InitializeLhub() {} + +void WebBrowser::ExecuteLhub() { + LOG_INFO(Service_AM, "(STUBBED) called, Lhub Applet is not implemented"); + WebBrowserExit(WebExitReason::EndButtonPressed); +} + + } // namespace Service::AM::Frontend diff --git a/src/core/hle/service/am/frontend/applet_web_browser.h b/src/core/hle/service/am/frontend/applet_web_browser.h index ba20b7a4cf..ae62389f13 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.h +++ b/src/core/hle/service/am/frontend/applet_web_browser.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -53,6 +56,7 @@ private: void InitializeWeb(); void InitializeWifi(); void InitializeLobby(); + void InitializeLhub(); // Executors for the various types of browser applets void ExecuteShop(); @@ -62,6 +66,7 @@ private: void ExecuteWeb(); void ExecuteWifi(); void ExecuteLobby(); + void ExecuteLhub(); const Core::Frontend::WebBrowserApplet& frontend; diff --git a/src/core/hle/service/am/frontend/applet_web_browser_types.h b/src/core/hle/service/am/frontend/applet_web_browser_types.h index 2f7c05c243..0892a6a716 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser_types.h +++ b/src/core/hle/service/am/frontend/applet_web_browser_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -30,6 +33,7 @@ enum class ShimKind : u32 { Web = 5, Wifi = 6, Lobby = 7, + Lhub = 8, }; enum class WebExitReason : u32 { diff --git a/src/core/hle/service/am/frontend/applets.cpp b/src/core/hle/service/am/frontend/applets.cpp index a25b7e3aa2..98a5e69db8 100644 --- a/src/core/hle/service/am/frontend/applets.cpp +++ b/src/core/hle/service/am/frontend/applets.cpp @@ -237,6 +237,7 @@ std::shared_ptr FrontendAppletHolder::GetApplet(std::shared_ptr< case AppletId::OfflineWeb: case AppletId::LoginShare: case AppletId::WebAuth: + case AppletId::Lhub: return std::make_shared(system, applet, mode, *frontend.web_browser); case AppletId::PhotoViewer: return std::make_shared(system, applet, mode, *frontend.photo_viewer); diff --git a/src/core/hle/service/bcat/news/builtin_news.cpp b/src/core/hle/service/bcat/news/builtin_news.cpp index e087a3271a..440a5d1f51 100644 --- a/src/core/hle/service/bcat/news/builtin_news.cpp +++ b/src/core/hle/service/bcat/news/builtin_news.cpp @@ -507,9 +507,9 @@ std::vector BuildMsgpack(std::string_view title, std::string_view body, w.WriteKey("browser"); w.WriteFixMap(2); w.WriteKey("url"); - w.WriteString(""); + w.WriteString(html_url); w.WriteKey("text"); - w.WriteString(""); + w.WriteString("Open GitHub"); // Body w.WriteKey("body"); diff --git a/src/yuzu/applets/qt_web_browser.cpp b/src/yuzu/applets/qt_web_browser.cpp index cab8ef190e..3d9d851ab6 100644 --- a/src/yuzu/applets/qt_web_browser.cpp +++ b/src/yuzu/applets/qt_web_browser.cpp @@ -4,6 +4,11 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +#include "common/logging/log.h" + #ifdef YUZU_USE_QT_WEB_ENGINE #include @@ -429,15 +434,17 @@ void QtWebBrowser::OpenLocalWebPage(const std::string& local_url, void QtWebBrowser::OpenExternalWebPage(const std::string& external_url, OpenWebPageCallback callback_) const { - callback = std::move(callback_); + LOG_INFO(Service_AM, "Opening external URL in host browser: {}", external_url); - const auto index = external_url.find('?'); + const QUrl url(QString::fromStdString(external_url)); + const bool success = QDesktopServices::openUrl(url); - if (index == std::string::npos) { - emit MainWindowOpenWebPage(external_url, "", false); + if (success) { + LOG_INFO(Service_AM, "Successfully opened URL in host browser"); + callback_(Service::AM::Frontend::WebExitReason::EndButtonPressed, external_url); } else { - emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index), - false); + LOG_ERROR(Service_AM, "Failed to open URL in host browser"); + callback_(Service::AM::Frontend::WebExitReason::WindowClosed, external_url); } }