Browse Source

[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>
pull/3172/head
Maufeat 1 week ago
committed by crueter
parent
commit
30d75d4e52
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  1. 52
      src/core/hle/service/ns/application_manager_interface.cpp
  2. 7
      src/core/hle/service/ns/application_manager_interface.h
  3. 84
      src/core/hle/service/ns/ns_types.h
  4. 172
      src/core/hle/service/ns/read_only_application_control_data_interface.cpp
  5. 13
      src/core/hle/service/ns/read_only_application_control_data_interface.h

52
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/content_management_interface.h"
#include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h"
#include "core/file_sys/patch_manager.h" #include "core/file_sys/patch_manager.h"
#include "frontend_common/firmware_manager.h"
namespace Service::NS { namespace Service::NS {
@ -429,13 +430,13 @@ Result IApplicationManagerInterface::IsAnyApplicationEntityInstalled(
} }
Result IApplicationManagerInterface::GetApplicationViewDeprecated( Result IApplicationManagerInterface::GetApplicationViewDeprecated(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) { InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = (std::min)(out_application_views.size(), application_ids.size()); const auto size = (std::min)(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size()); LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
ApplicationView view{};
ApplicationViewV19 view{};
view.application_id = application_ids[i]; view.application_id = application_ids[i];
view.version = 0x70000; view.version = 0x70000;
view.flags = 0x401f17; view.flags = 0x401f17;
@ -447,34 +448,53 @@ Result IApplicationManagerInterface::GetApplicationViewDeprecated(
} }
Result IApplicationManagerInterface::GetApplicationViewWithPromotionInfo( 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) { 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(); R_SUCCEED();
} }
Result IApplicationManagerInterface::GetApplicationView( Result IApplicationManagerInterface::GetApplicationView(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids) { InArray<u64, BufferAttr_HipcMapAlias> application_ids) {
const auto size = (std::min)(out_application_views.size(), application_ids.size()); const auto size = (std::min)(out_application_views.size(), application_ids.size());
LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size()); LOG_WARNING(Service_NS, "(STUBBED) called, size={}", application_ids.size());
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
ApplicationView view{};
ApplicationViewV20 view{};
view.application_id = application_ids[i]; view.application_id = application_ids[i];
view.version = 0x70000; view.version = 0x70000;
view.unk = 0;
view.flags = 0x401f17; view.flags = 0x401f17;
out_application_views[i] = view; out_application_views[i] = view;

7
src/core/hle/service/ns/application_manager_interface.h

@ -36,13 +36,14 @@ public:
Result IsGameCardApplicationRunning(Out<bool> out_is_running); Result IsGameCardApplicationRunning(Out<bool> out_is_running);
Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed); Result IsAnyApplicationEntityInstalled(Out<bool> out_is_any_application_entity_installed);
Result GetApplicationViewDeprecated( Result GetApplicationViewDeprecated(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
OutArray<ApplicationViewV19, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids); InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationViewWithPromotionInfo( Result GetApplicationViewWithPromotionInfo(
OutArray<ApplicationViewWithPromotionInfo, BufferAttr_HipcMapAlias> out_application_views,
OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_count,
InArray<u64, BufferAttr_HipcMapAlias> application_ids); InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationView( Result GetApplicationView(
OutArray<ApplicationView, BufferAttr_HipcMapAlias> out_application_views,
OutArray<ApplicationViewV20, BufferAttr_HipcMapAlias> out_application_views,
InArray<u64, BufferAttr_HipcMapAlias> application_ids); InArray<u64, BufferAttr_HipcMapAlias> application_ids);
Result GetApplicationRightsOnClient( Result GetApplicationRightsOnClient(
OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count, OutArray<ApplicationRightsOnClient, BufferAttr_HipcMapAlias> out_rights, Out<u32> out_count,

84
src/core/hle/service/ns/ns_types.h

@ -55,16 +55,6 @@ struct ApplicationDownloadState {
static_assert(sizeof(ApplicationDownloadState) == 0x20, static_assert(sizeof(ApplicationDownloadState) == 0x20,
"ApplicationDownloadState has incorrect size."); "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 { struct ApplicationRightsOnClient {
u64 application_id; u64 application_id;
Common::UUID uid; Common::UUID uid;
@ -88,14 +78,74 @@ struct PromotionInfo {
}; };
static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size."); 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 { struct ApplicationOccupiedSizeEntity {
FileSys::StorageId storage_id; FileSys::StorageId storage_id;

172
src/core/hle/service/ns/read_only_application_control_data_interface.cpp

@ -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) {
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(); R_SUCCEED();
} }

13
src/core/hle/service/ns/read_only_application_control_data_interface.h

@ -27,10 +27,21 @@ public:
u32 supported_languages); u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code, Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language); ApplicationLanguage application_language);
Result GetApplicationControlDataWithoutIcon(
Result GetApplicationControlData2(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u64> out_total_size, Out<u64> out_total_size,
ApplicationControlSource application_control_source, 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); u64 application_id);
}; };

Loading…
Cancel
Save