[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>
This commit is contained in:
@@ -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());
|
||||
const auto requested = application_ids.size();
|
||||
LOG_WARNING(Service_NS, "called, size={}", requested);
|
||||
|
||||
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 = {};
|
||||
const auto fw_pair = FirmwareManager::GetFirmwareVersion(system);
|
||||
const bool is_fw20 = fw_pair.first.major >= 20;
|
||||
|
||||
out_application_views[i] = view;
|
||||
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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(ApplicationViewWithPromotionInfo) == 0x70,
|
||||
"ApplicationViewWithPromotionInfo has incorrect size.");
|
||||
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{};
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -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) {
|
||||
std::vector<u8> final_icon_data;
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
*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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user