@ -4,6 +4,12 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// 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 "common/settings.h"
# include "core/file_sys/control_metadata.h"
# include "core/file_sys/control_metadata.h"
# include "core/file_sys/patch_manager.h"
# include "core/file_sys/patch_manager.h"
@ -17,6 +23,49 @@
namespace Service : : NS {
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 (
IReadOnlyApplicationControlDataInterface : : IReadOnlyApplicationControlDataInterface (
Core : : System & system_ )
Core : : System & system_ )
: ServiceFramework { system_ , " IReadOnlyApplicationControlDataInterface " } {
: ServiceFramework { system_ , " IReadOnlyApplicationControlDataInterface " } {
@ -27,8 +76,8 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{ 2 , D < & IReadOnlyApplicationControlDataInterface : : ConvertApplicationLanguageToLanguageCode > , " ConvertApplicationLanguageToLanguageCode " } ,
{ 2 , D < & IReadOnlyApplicationControlDataInterface : : ConvertApplicationLanguageToLanguageCode > , " ConvertApplicationLanguageToLanguageCode " } ,
{ 3 , nullptr , " ConvertLanguageCodeToApplicationLanguage " } ,
{ 3 , nullptr , " ConvertLanguageCodeToApplicationLanguage " } ,
{ 4 , nullptr , " SelectApplicationDesiredLanguage " } ,
{ 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
// clang-format on
@ -125,11 +174,77 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan
R_SUCCEED ( ) ;
R_SUCCEED ( ) ;
}
}
Result IReadOnlyApplicationControlDataInterface : : GetApplicationControlDataWithoutIcon (
Result IReadOnlyApplicationControlDataInterface : : GetApplicationControlData2 (
OutBuffer < BufferAttr_HipcMapAlias > out_buffer , Out < u64 > out_total_size ,
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 ( ) ,
const FileSys : : PatchManager pm { application_id , system . GetFileSystemController ( ) ,
system . GetContentProvider ( ) } ;
system . GetContentProvider ( ) } ;
@ -158,22 +273,43 @@ Result IReadOnlyApplicationControlDataInterface::GetApplicationControlDataWithou
}
}
const auto icon_area_size = size - nacp_size ;
const auto icon_area_size = size - nacp_size ;
if ( icon_area_size > 0 ) {
std : : vector < u8 > final_icon_data ;
if ( control . second ! = nullptr ) {
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 ) ;
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 ) {
if ( to_copy < icon_area_size ) {
std : : memset ( out_buffer . data ( ) + nacp_size + to_copy , 0 , icon_area_size - to_copy ) ;
std : : memset ( out_buffer . data ( ) + nacp_size + to_copy , 0 , icon_area_size - to_copy ) ;
}
}
} else {
} else {
std : : memset ( out_buffer . data ( ) + nacp_size , 0 , icon_area_size ) ;
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 ) ;
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 ( ) ;
R_SUCCEED ( ) ;
}
}