aboutsummaryrefslogtreecommitdiff
path: root/src/core/hle/service/am
diff options
context:
space:
mode:
authorMaufeat <sahyno1996@gmail.com>2026-01-17 01:48:15 +0100
committercrueter <crueter@eden-emu.dev>2026-01-17 01:48:15 +0100
commitae501e256ea462268dbae8109ad07ef457c4fd8e (patch)
treeb36691663a10be26462d68f562ad51b8c697ee7b /src/core/hle/service/am
parentb7417f68ce10145e5eb282110e7d50f46ec8047b (diff)
[bcat/news/web/am] Implement news applet, proper TLV return, external web browser URL and qlaunch app sorting (#3308)
This pulls eden releases changelog & text from our github releases. We don't store the msgpack file but rather generate them in-memory for the News Applet. Uses cache folder. Files generated are: - cache/news/github_releases.json - cache/news/eden_logo.jpg - cache/news/news_read Additional changes: - Proper TLV returning for online web applet, to open external URL - Add applet type `LHub` to properly close, as it also uses TLV return - qlaunch app sorting, adds another cached .json to track last launched app timestamps and sort them accordingly Co-authored-by: crueter <crueter@eden-emu.dev> Co-authored-by: DraVee <dravee@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3308 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: DraVee <dravee@eden-emu.dev> Reviewed-by: Lizzie <lizzie@eden-emu.dev> Co-authored-by: Maufeat <sahyno1996@gmail.com> Co-committed-by: Maufeat <sahyno1996@gmail.com>
Diffstat (limited to 'src/core/hle/service/am')
-rw-r--r--src/core/hle/service/am/am_types.h1
-rw-r--r--src/core/hle/service/am/frontend/applet_web_browser.cpp130
-rw-r--r--src/core/hle/service/am/frontend/applet_web_browser.h5
-rw-r--r--src/core/hle/service/am/frontend/applet_web_browser_types.h4
-rw-r--r--src/core/hle/service/am/frontend/applets.cpp4
-rw-r--r--src/core/hle/service/am/service/application_creator.cpp3
6 files changed, 136 insertions, 11 deletions
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> 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<u8> 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<size_t>(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<u32>(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<u16>(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<IStorage>(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..2ff2bcdbb6 100644
--- a/src/core/hle/service/am/frontend/applets.cpp
+++ b/src/core/hle/service/am/frontend/applets.cpp
@@ -237,13 +237,15 @@ std::shared_ptr<FrontendApplet> FrontendAppletHolder::GetApplet(std::shared_ptr<
case AppletId::OfflineWeb:
case AppletId::LoginShare:
case AppletId::WebAuth:
+ case AppletId::Lhub:
return std::make_shared<WebBrowser>(system, applet, mode, *frontend.web_browser);
case AppletId::PhotoViewer:
return std::make_shared<PhotoViewer>(system, applet, mode, *frontend.photo_viewer);
case AppletId::NetConnect:
return std::make_shared<NetConnect>(system, applet, mode, *frontend.net_connect);
default:
- LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X}. Falling back to stub applet", static_cast<u8>(id));
+ LOG_ERROR(Service_AM, "No backend implementation exists for applet_id={:02X} program_id={:016X}"
+ "Falling back to stub applet", static_cast<u8>(id), applet->program_id);
return std::make_shared<StubApplet>(system, applet, id, mode);
}
}
diff --git a/src/core/hle/service/am/service/application_creator.cpp b/src/core/hle/service/am/service/application_creator.cpp
index 2fc33a303c..d16fd7dd84 100644
--- a/src/core/hle/service/am/service/application_creator.cpp
+++ b/src/core/hle/service/am/service/application_creator.cpp
@@ -15,6 +15,7 @@
#include "core/hle/service/am/window_system.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/loader/loader.h"
+#include "core/launch_timestamp_cache.h"
namespace Service::AM {
@@ -72,6 +73,7 @@ IApplicationCreator::~IApplicationCreator() = default;
Result IApplicationCreator::CreateApplication(
Out<SharedPointer<IApplicationAccessor>> out_application_accessor, u64 application_id) {
LOG_INFO(Service_NS, "called, application_id={:016X}", application_id);
+ Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id);
R_RETURN(
CreateGuestApplication(out_application_accessor, system, m_window_system, application_id));
}
@@ -103,6 +105,7 @@ Result IApplicationCreator::CreateSystemApplication(
*out_application_accessor =
std::make_shared<IApplicationAccessor>(system, applet, m_window_system);
+ Core::LaunchTimestampCache::SaveLaunchTimestamp(application_id);
R_SUCCEED();
}