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 7b7d381450..5359cf3f6c 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"} { @@ -157,34 +206,47 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData2( } const auto icon_area_size = size - nacp_size; - size_t available_icon_bytes = 0; + std::vector final_icon_data; + if (control.second != nullptr) { - available_icon_bytes = control.second->GetSize(); + 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) { - if (control.second != nullptr) { - const size_t to_copy = std::min(available_icon_bytes, icon_area_size); - if (to_copy > 0) { - std::vector tmp(to_copy); - control.second->Read(tmp.data(), to_copy); - std::memcpy(out_buffer.data() + nacp_size, tmp.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 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); + + if (application_id == 0x0100152000022000) { + LOG_INFO(Service_NS, "Debug: AppID={:016X}, IconSize={}, TotalSize={}, Flag1={}", application_id, available_icon_bytes, total_available, flag1); + } + + *out_total_size = (static_cast(total_available) << 32) | static_cast(flag1); R_SUCCEED(); } Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( - OutBuffer out_buffer, Out out_total_size, - ApplicationControlSource application_control_source, u8 flag1, u8 flag2, u64 application_id) { + 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); @@ -215,26 +277,43 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData3( } const auto icon_area_size = size - nacp_size; - size_t available_icon_bytes = 0; + std::vector final_icon_data; + if (control.second != nullptr) { - available_icon_bytes = control.second->GetSize(); + 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) { - if (control.second != nullptr) { - const auto to_copy = static_cast((std::min)(available_icon_bytes, 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); - } - } 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); + 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_total_size = static_cast(actual_total_size) << 32; + + // 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 72b399e596..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 @@ -36,7 +36,9 @@ public: u64 application_id); Result GetApplicationControlData3( OutBuffer out_buffer, - Out out_total_size, + Out out_flags_a, + Out out_flags_b, + Out out_actual_size, ApplicationControlSource application_control_source, u8 flag1, u8 flag2,