aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMaufeat <sahyno1996@gmail.com>2025-12-13 20:08:44 +0100
committercrueter <crueter@eden-emu.dev>2025-12-13 20:08:44 +0100
commit30d75d4e52c1029bf1f19cda8da20f6cc19d01cf (patch)
tree148868ab6b95e67fe82fd9667b83f26a19a6e210 /src
parent06b83a58a6d60063cd5fad934aa7f3a75f0ce040 (diff)
[hle] add GetApplicationControlData2 and GetApplicationControlData3 to make on FW20.0.0 - FW21.1.0 icons work on qlaunch (#3153)
This change makes firmware 20+ show icons in qlaunch and adds a way for ApplicationViewWithPromotion to handle below 20 and upwards struct Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3153 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: crueter <crueter@eden-emu.dev> Co-authored-by: Maufeat <sahyno1996@gmail.com> Co-committed-by: Maufeat <sahyno1996@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/core/hle/service/ns/application_manager_interface.cpp52
-rw-r--r--src/core/hle/service/ns/application_manager_interface.h7
-rw-r--r--src/core/hle/service/ns/ns_types.h84
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.cpp172
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.h13
5 files changed, 273 insertions, 55 deletions
diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp
index 81f42f0937..6f05116853 100644
--- a/src/core/hle/service/ns/application_manager_interface.cpp
+++ b/src/core/hle/service/ns/application_manager_interface.cpp
@@ -13,6 +13,7 @@
#include "core/hle/service/ns/content_management_interface.h"
#include "core/hle/service/ns/read_only_application_control_data_interface.h"
#include "core/file_sys/patch_manager.h"
+#include "frontend_common/firmware_manager.h"
namespace Service::NS {
@@ -429,13 +430,13 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
}
Result IApplicationManagerInterface::GetApplicationViewDeprecated(
- OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = (std::min)(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
for (size_t i = 0; i < size; i++) {
- ApplicationView view{};
+ ApplicationViewV19 view{};
view.application_id = application_ids[i];
view.version = 0x70000;
view.flags = 0x401f17;
@@ -447,34 +448,53 @@ Result IApplicationManagerInterface::GetApplicationViewDeprecated(
}
Result IApplicationManagerInterface::GetApplicationViewWithPromotionInfo(
- OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u32> out_count,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
- const auto size = (std::min)(out_application_views.size(), application_ids.size());
- LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
-
- for (size_t i = 0; i < size; i++) {
- ApplicationViewWithPromotionInfo view{};
- view.view.application_id = application_ids[i];
- view.view.version = 0x70000;
- view.view.flags = 0x401f17;
- view.promotion = {};
-
- out_application_views[i] = view;
+ const auto requested = application_ids.size();
+ LOG_WARNING(Service_NS, "called, size={}", requested);
+
+ const auto fw_pair = FirmwareManager::GetFirmwareVersion(system);
+ const bool is_fw20 = fw_pair.first.major >= 20;
+
+ const size_t per_entry_size = is_fw20 ? (sizeof(ApplicationViewV20) + sizeof(PromotionInfo))
+ : (sizeof(ApplicationViewV19) + sizeof(PromotionInfo));
+ const size_t capacity_entries = out_buffer.size() / per_entry_size;
+ const size_t to_write_entries = (std::min)(requested, capacity_entries);
+
+ u8* dst = out_buffer.data();
+ for (size_t i = 0; i < to_write_entries; ++i) {
+ ApplicationViewWithPromotionData data{};
+ data.view.application_id = application_ids[i];
+ data.view.version = 0x70000;
+ data.view.unk = 0;
+ data.view.flags = 0x401f17;
+ data.view.download_state = {};
+ data.view.download_progress = {};
+ data.promotion = {};
+
+ const size_t written = WriteApplicationViewWithPromotion(dst, out_buffer.size() - (dst - out_buffer.data()), data, is_fw20);
+ if (written == 0) {
+ break;
+ }
+ dst += written;
}
+ *out_count = static_cast<u32>(dst - out_buffer.data()) / static_cast<u32>(per_entry_size);
R_SUCCEED();
}
Result IApplicationManagerInterface::GetApplicationView(
- OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = (std::min)(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
for (size_t i = 0; i < size; i++) {
- ApplicationView view{};
+ ApplicationViewV20 view{};
view.application_id = application_ids[i];
view.version = 0x70000;
+ view.unk = 0;
view.flags = 0x401f17;
out_application_views[i] = view;
diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h
index 9c36939257..915581172e 100644
--- a/src/core/hle/service/ns/application_manager_interface.h
+++ b/src/core/hle/service/ns/application_manager_interface.h
@@ -36,13 +36,14 @@ public:
Result IsGameCardApplicationRunning(Out<bool> out_is_running);
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
Result GetApplicationViewDeprecated(
- OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationViewWithPromotionInfo(
- OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u32> out_count,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationView(
- OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
+ OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationRightsOnClient(
OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,
diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h
index f7c712f0f6..31981a6821 100644
--- a/src/core/hle/service/ns/ns_types.h
+++ b/src/core/hle/service/ns/ns_types.h
@@ -55,16 +55,6 @@ struct ApplicationDownloadState {
static_assert(sizeof(ApplicationDownloadState) == 0x20,
"ApplicationDownloadState has incorrect size.");
-/// ApplicationView
-struct ApplicationView {
- u64 application_id; ///< ApplicationId.
- u32 version; ///< Application Version(?)
- u32 flags; ///< Flags.
- ApplicationDownloadState download_state; ///< \ref ApplicationDownloadState
- ApplicationDownloadState download_progress; ///< \ref ApplicationDownloadState
-};
-static_assert(sizeof(ApplicationView) == 0x50, "ApplicationView has incorrect size.");
-
struct ApplicationRightsOnClient {
u64 application_id;
Common::UUID uid;
@@ -88,14 +78,74 @@ struct PromotionInfo {
};
static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size.");
-// TODO(Maufeat): NsApplicationViewWithPromotionInfo is on SDK20+ 0x78 bytes
-/// NsApplicationViewWithPromotionInfo
-struct ApplicationViewWithPromotionInfo {
- ApplicationView view; ///< \ref NsApplicationView
- PromotionInfo promotion; ///< \ref NsPromotionInfo
+struct ApplicationViewV19 {
+ u64 application_id;
+ u32 version;
+ u32 flags;
+ ApplicationDownloadState download_state;
+ ApplicationDownloadState download_progress;
+};
+static_assert(sizeof(ApplicationViewV19) == 0x50, "ApplicationViewV19 has incorrect size.");
+
+struct ApplicationViewV20 {
+ u64 application_id;
+ u32 version;
+ u32 unk;
+ u32 flags;
+ ApplicationDownloadState download_state;
+ ApplicationDownloadState download_progress;
+};
+static_assert(sizeof(ApplicationViewV20) == 0x58, "ApplicationViewV20 has incorrect size.");
+
+struct ApplicationViewData {
+ u64 application_id{};
+ u32 version{};
+ u32 unk{};
+ u32 flags{};
+ ApplicationDownloadState download_state{};
+ ApplicationDownloadState download_progress{};
};
-static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70,
- "ApplicationViewWithPromotionInfo has incorrect size.");
+
+inline size_t WriteApplicationView(void* dst, size_t dst_size, const ApplicationViewData& data,
+ bool is_fw20) {
+ if (is_fw20) {
+ if (dst_size < sizeof(ApplicationViewV20)) return 0;
+ auto* out = reinterpret_cast<ApplicationViewV20*>(dst);
+ out->application_id = data.application_id;
+ out->version = data.version;
+ out->unk = data.unk;
+ out->flags = data.flags;
+ out->download_state = data.download_state;
+ out->download_progress = data.download_progress;
+ return sizeof(ApplicationViewV20);
+ } else {
+ if (dst_size < sizeof(ApplicationViewV19)) return 0;
+ auto* out = reinterpret_cast<ApplicationViewV19*>(dst);
+ out->application_id = data.application_id;
+ out->version = data.version;
+ out->flags = data.flags;
+ out->download_state = data.download_state;
+ out->download_progress = data.download_progress;
+ return sizeof(ApplicationViewV19);
+ }
+}
+
+struct ApplicationViewWithPromotionData {
+ ApplicationViewData view;
+ PromotionInfo promotion;
+};
+
+inline size_t WriteApplicationViewWithPromotion(void* dst, size_t dst_size,
+ const ApplicationViewWithPromotionData& data,
+ bool sdk20_plus) {
+ const size_t view_written = WriteApplicationView(dst, dst_size, data.view, sdk20_plus);
+ if (view_written == 0) return 0;
+ const size_t remaining = dst_size - view_written;
+ if (remaining < sizeof(PromotionInfo)) return 0;
+ auto* promo_dst = reinterpret_cast<u8*>(dst) + view_written;
+ std::memcpy(promo_dst, &data.promotion, sizeof(PromotionInfo));
+ return view_written + sizeof(PromotionInfo);
+}
struct ApplicationOccupiedSizeEntity {
FileSys::StorageId storage_id;
diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
index b79369faad..0115b83fd0 100644
--- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
+++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
@@ -4,6 +4,12 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+#include <vector>
+#include <stb_image.h>
+#include <stb_image_resize.h>
+#include <stb_image_write.h>
+
#include "common/settings.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -17,6 +23,49 @@
namespace Service::NS {
+namespace {
+
+void JPGToMemory(void* context, void* data, int size) {
+ auto* buffer = static_cast<std::vector<u8>*>(context);
+ const auto* char_data = static_cast<const u8*>(data);
+ buffer->insert(buffer->end(), char_data, char_data + size);
+}
+
+void SanitizeJPEGImageSize(std::vector<u8>& image) {
+ constexpr std::size_t max_jpeg_image_size = 0x20000;
+ constexpr int profile_dimensions = 174; // for grid view thingy
+ int original_width, original_height, color_channels;
+
+ auto* plain_image =
+ stbi_load_from_memory(image.data(), static_cast<int>(image.size()), &original_width,
+ &original_height, &color_channels, STBI_rgb);
+
+ if (plain_image == nullptr) {
+ LOG_ERROR(Service_NS, "Failed to load JPEG for sanitization.");
+ return;
+ }
+
+ if (original_width != profile_dimensions || original_height != profile_dimensions) {
+ std::vector<u8> out_image(profile_dimensions * profile_dimensions * STBI_rgb);
+ stbir_resize_uint8_srgb(plain_image, original_width, original_height, 0, out_image.data(),
+ profile_dimensions, profile_dimensions, 0, STBI_rgb, 0,
+ STBIR_FILTER_BOX);
+ image.clear();
+ if (!stbi_write_jpg_to_func(JPGToMemory, &image, profile_dimensions, profile_dimensions,
+ STBI_rgb, out_image.data(), 90)) {
+ LOG_ERROR(Service_NS, "Failed to resize the user provided image.");
+ }
+ }
+
+ stbi_image_free(plain_image);
+
+ if (image.size() > max_jpeg_image_size) {
+ image.resize(max_jpeg_image_size);
+ }
+}
+
+} // namespace
+
IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(
Core::System& system_)
: ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} {
@@ -27,8 +76,8 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
{4, nullptr, "SelectApplicationDesiredLanguage"},
- {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithoutIcon"},
- {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon>, "GetApplicationControlDataWithoutIcon"},
+ {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData2>, "GetApplicationControlDataWithoutIcon"},
+ {19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlDataWithoutIcon3"},
};
// clang-format on
@@ -125,11 +174,77 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
R_SUCCEED();
}
-Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithoutIcon(
+Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u64> out_total_size,
- ApplicationControlSource application_control_source, u64 application_id) {
- LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}",
- application_control_source, application_id);
+ ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
+ LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
+ application_control_source, flag1, flag2, application_id);
+
+ const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
+ system.GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto size = out_buffer.size();
+
+ const auto nacp_size = sizeof(FileSys::RawNACP);
+
+ if (size < nacp_size) {
+ LOG_ERROR(Service_NS, "output buffer is too small! (actual={:016X}, expected_min={:08X})",
+ size, nacp_size);
+ R_THROW(ResultUnknown);
+ }
+
+ if (control.first != nullptr) {
+ const auto bytes = control.first->GetRawBytes();
+ const auto copy_len = (std::min)(static_cast<size_t>(bytes.size()), static_cast<size_t>(nacp_size));
+ std::memcpy(out_buffer.data(), bytes.data(), copy_len);
+ if (copy_len < nacp_size) {
+ std::memset(out_buffer.data() + copy_len, 0, nacp_size - copy_len);
+ }
+ } else {
+ LOG_WARNING(Service_NS, "missing NACP data for application_id={:016X}", application_id);
+ std::memset(out_buffer.data(), 0, nacp_size);
+ }
+
+ const auto icon_area_size = size - nacp_size;
+ std::vector<u8> final_icon_data;
+
+ if (control.second != nullptr) {
+ size_t full_size = control.second->GetSize();
+ if (full_size > 0) {
+ final_icon_data.resize(full_size);
+ control.second->Read(final_icon_data.data(), full_size);
+
+ if (flag1 == 1) {
+ SanitizeJPEGImageSize(final_icon_data);
+ }
+ }
+ }
+
+ size_t available_icon_bytes = final_icon_data.size();
+
+ if (icon_area_size > 0) {
+ const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
+
+ if (to_copy > 0) {
+ std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
+ }
+
+ if (to_copy < icon_area_size) {
+ std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
+ }
+ }
+
+ const u32 total_available = static_cast<u32>(nacp_size + available_icon_bytes);
+
+ *out_total_size = (static_cast<u64>(total_available) << 32) | static_cast<u64>(flag1);
+ R_SUCCEED();
+}
+
+Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3(
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_flags_a, Out<u32> out_flags_b,
+ Out<u32> out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) {
+ LOG_INFO(Service_NS, "called with control_source={}, flags=({:02X},{:02X}), application_id={:016X}",
+ application_control_source, flag1, flag2, application_id);
const FileSys::PatchManager pm{application_id, system.GetFileSystemController(),
system.GetContentProvider()};
@@ -158,22 +273,43 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithou
}
const auto icon_area_size = size - nacp_size;
- if (icon_area_size > 0) {
- if (control.second != nullptr) {
- const auto icon_size = control.second->GetSize();
- const auto to_copy = static_cast<size_t>((std::min)(icon_size, icon_area_size));
- control.second->Read(out_buffer.data() + nacp_size, to_copy);
- if (to_copy < icon_area_size) {
- std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
+ std::vector<u8> final_icon_data;
+
+ if (control.second != nullptr) {
+ size_t full_size = control.second->GetSize();
+ if (full_size > 0) {
+ final_icon_data.resize(full_size);
+ control.second->Read(final_icon_data.data(), full_size);
+
+ if (flag1 == 1) {
+ SanitizeJPEGImageSize(final_icon_data);
}
- } else {
- std::memset(out_buffer.data() + nacp_size, 0, icon_area_size);
- LOG_WARNING(Service_NS, "missing icon data for application_id={:016X}, zero-filling icon area",
- application_id);
}
}
- *out_total_size = static_cast<u64>(size);
+ size_t available_icon_bytes = final_icon_data.size();
+
+ if (icon_area_size > 0) {
+ const size_t to_copy = (std::min)(available_icon_bytes, icon_area_size);
+ if (to_copy > 0) {
+ std::memcpy(out_buffer.data() + nacp_size, final_icon_data.data(), to_copy);
+ }
+ if (to_copy < icon_area_size) {
+ std::memset(out_buffer.data() + nacp_size + to_copy, 0, icon_area_size - to_copy);
+ }
+ } else {
+ std::memset(out_buffer.data() + nacp_size, 0, icon_area_size);
+ }
+
+ const u32 actual_total_size = static_cast<u32>(nacp_size + available_icon_bytes);
+
+ // Out 1: always 0x10001 (likely presents flags: Bit0=Icon, Bit16=NACP)
+ // Out 2: reflects flag1 application (0 if flag1=0, 0x10001 if flag1=1)
+ // Out 3: The actual size of data
+ *out_flags_a = 0x10001;
+ *out_flags_b = (flag1 == 1) ? 0x10001 : 0;
+ *out_actual_size = actual_total_size;
+
R_SUCCEED();
}
diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h
index 99366c5792..4da45736b8 100644
--- a/src/core/hle/service/ns/read_only_application_control_data_interface.h
+++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h
@@ -27,10 +27,21 @@ public:
u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language);
- Result GetApplicationControlDataWithoutIcon(
+ Result GetApplicationControlData2(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u64> out_total_size,
ApplicationControlSource application_control_source,
+ u8 flag1,
+ u8 flag2,
+ u64 application_id);
+ Result GetApplicationControlData3(
+ OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u32> out_flags_a,
+ Out<u32> out_flags_b,
+ Out<u32> out_actual_size,
+ ApplicationControlSource application_control_source,
+ u8 flag1,
+ u8 flag2,
u64 application_id);
};