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 out_application_views, + OutArray out_application_views, InArray 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 out_application_views, + OutBuffer out_buffer, + Out out_count, InArray 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(dst - out_buffer.data()) / static_cast(per_entry_size); R_SUCCEED(); } Result IApplicationManagerInterface::GetApplicationView( - OutArray out_application_views, + OutArray out_application_views, InArray 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 out_is_running); Result IsAnyApplicationEntityInstalled(Out out_is_any_application_entity_installed); Result GetApplicationViewDeprecated( - OutArray out_application_views, + OutArray out_application_views, InArray application_ids); Result GetApplicationViewWithPromotionInfo( - OutArray out_application_views, + OutBuffer out_buffer, + Out out_count, InArray application_ids); Result GetApplicationView( - OutArray out_application_views, + OutArray out_application_views, InArray application_ids); Result GetApplicationRightsOnClient( OutArray out_rights, Out 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(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(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(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 +#include +#include +#include +#include + #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*>(context); + const auto* char_data = static_cast(data); + buffer->insert(buffer->end(), char_data, char_data + size); +} + +void SanitizeJPEGImageSize(std::vector& 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(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 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 out_buffer, Out 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(bytes.size()), static_cast(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 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(nacp_size + available_icon_bytes); + + *out_total_size = (static_cast(total_available) << 32) | static_cast(flag1); + R_SUCCEED(); +} + +Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( + OutBuffer out_buffer, Out out_flags_a, Out out_flags_b, + Out 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((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 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(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(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 out_language_code, ApplicationLanguage application_language); - Result GetApplicationControlDataWithoutIcon( + Result GetApplicationControlData2( OutBuffer out_buffer, Out out_total_size, ApplicationControlSource application_control_source, + u8 flag1, + u8 flag2, + u64 application_id); + Result GetApplicationControlData3( + OutBuffer out_buffer, + Out out_flags_a, + Out out_flags_b, + Out out_actual_size, + ApplicationControlSource application_control_source, + u8 flag1, + u8 flag2, u64 application_id); };