Application data handling (#392)

Return checked application metadata and icons, handle early controller disconnects, and register limited StreamPlay interfaces for firmware 23 or newer.

NS and StreamPlay compatibility support adapted from Eden. Preserve its copyright and license notices.
This commit is contained in:
Bruno 2026-10-09 15:17:36 +02:00 committed by GitHub
parent 732d0c4cba
commit 4a0bc1497c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 527 additions and 9 deletions

View File

@ -69,7 +69,8 @@ NACP::NACP() = default;
NACP::NACP(VirtualFile file) NACP::NACP(VirtualFile file)
{ {
file->ReadObject(&raw); valid = file != nullptr && file->GetSize() == sizeof(raw) &&
file->ReadObject(&raw) == sizeof(raw);
version = Common::StringFromFixedZeroTerminatedBuffer(raw.version_string.data(), raw.version_string.size()); version = Common::StringFromFixedZeroTerminatedBuffer(raw.version_string.data(), raw.version_string.size());
} }
@ -80,6 +81,7 @@ NACP & NACP::operator=(const NACP & other)
if (this != &other) if (this != &other)
{ {
raw = other.raw; raw = other.raw;
valid = other.valid;
version = other.version; version = other.version;
developer = other.developer; developer = other.developer;
} }

View File

@ -113,6 +113,7 @@ public:
uint64_t GetDefaultNormalSaveSize() const; uint64_t GetDefaultNormalSaveSize() const;
uint64_t GetDefaultJournalSaveSize() const; uint64_t GetDefaultJournalSaveSize() const;
std::vector<u8> GetRawBytes() const; std::vector<u8> GetRawBytes() const;
bool IsValid() const { return valid; }
uint64_t GetDeviceSaveDataSize() const; uint64_t GetDeviceSaveDataSize() const;
//IFileSysNACP //IFileSysNACP
@ -130,6 +131,7 @@ private:
std::string version; std::string version;
mutable std::string developer; mutable std::string developer;
RawNACP raw{}; RawNACP raw{};
bool valid{};
}; };
} // namespace FileSys } // namespace FileSys

View File

@ -1,4 +1,5 @@
#include "system_loader.h" #include "system_loader.h"
#include <nxemu-module-spec/application_control_data.h>
#include "core/core.h" #include "core/core.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h" #include "core/file_sys/common_funcs.h"
@ -818,6 +819,25 @@ IFileSysNACP * Systemloader::GetPMControlMetadata(uint64_t programID)
return metadata.first.release(); return metadata.first.release();
} }
LoaderResultStatus Systemloader::GetPMControlData(uint64_t program_id, uint8_t * buffer,
uint32_t buffer_size, uint32_t & actual_size)
{
actual_size = 0;
const FileSys::PatchManager pm(program_id, GetFileSystemController(), GetContentProvider());
const auto metadata = pm.GetControlMetadata();
if (metadata.first == nullptr || !metadata.first->IsValid())
{
return LoaderResultStatus::ErrorNoControl;
}
static_assert(sizeof(FileSys::RawNACP) == ApplicationControlData::NacpSize);
const auto nacp = metadata.first->GetRawBytes();
const auto & icon = metadata.second;
return ApplicationControlData::Read(nacp, icon != nullptr ? icon->GetSize() : 0,
[&icon](uint8_t * destination, uint64_t size) {
return icon->Read(destination, static_cast<size_t>(size), 0);
}, buffer, buffer_size, actual_size);
}
IManualContentProvider & Systemloader::ManualContentProvider() IManualContentProvider & Systemloader::ManualContentProvider()
{ {
return *(impl->m_manualContentProvider.get()); return *(impl->m_manualContentProvider.get());

View File

@ -49,6 +49,8 @@ public:
bool InstallFirmwarePackage(const char * utf8_path) override; bool InstallFirmwarePackage(const char * utf8_path) override;
void SetDisabledAddons(uint64_t program_id, const char * const * names, uint32_t count) override; void SetDisabledAddons(uint64_t program_id, const char * const * names, uint32_t count) override;
bool IsAddonDisabled(uint64_t program_id, const char * name) const override; bool IsAddonDisabled(uint64_t program_id, const char * name) const override;
LoaderResultStatus GetPMControlData(uint64_t program_id, uint8_t * buffer,
uint32_t buffer_size, uint32_t & actual_size) override;
private: private:
Systemloader() = delete; Systemloader() = delete;

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <cstring>
#include <span>
#include "system_loader.h"
namespace ApplicationControlData {
inline constexpr uint32_t NacpSize = 0x4000;
inline constexpr uint32_t MaxIconSize = 0x20000;
inline constexpr uint32_t MaxSize = NacpSize + MaxIconSize;
// A null, zero-sized buffer queries the size. Other calls copy the complete payload.
// The reader must report the number of bytes actually read, never its requested size.
template <typename IconReader>
LoaderResultStatus Read(std::span<const uint8_t> nacp, uint64_t icon_size,
IconReader&& read_icon, uint8_t* buffer, uint32_t buffer_size,
uint32_t& actual_size) {
actual_size = 0;
if (nacp.size() != NacpSize) {
return LoaderResultStatus::ErrorNoControl;
}
if (icon_size > MaxIconSize) {
return LoaderResultStatus::ErrorNoIcon;
}
const auto size = NacpSize + static_cast<uint32_t>(icon_size);
if (buffer == nullptr && buffer_size == 0) {
actual_size = size;
return LoaderResultStatus::Success;
}
if (buffer == nullptr || buffer_size < size) {
return LoaderResultStatus::ErrorBufferTooSmall;
}
if (icon_size != 0 && read_icon(buffer + NacpSize, icon_size) != icon_size) {
return LoaderResultStatus::ErrorNoIcon;
}
std::memcpy(buffer, nacp.data(), NacpSize);
actual_size = size;
return LoaderResultStatus::Success;
}
} // namespace ApplicationControlData

View File

@ -25,10 +25,10 @@
enum enum
{ {
MODULE_LOADER_SPECS_VERSION = 0x0130, MODULE_LOADER_SPECS_VERSION = 0x0131,
MODULE_VIDEO_SPECS_VERSION = 0x0123, MODULE_VIDEO_SPECS_VERSION = 0x0123,
MODULE_CPU_SPECS_VERSION = 0x011d, MODULE_CPU_SPECS_VERSION = 0x011d,
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0124, MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0126,
}; };
enum MODULE_TYPE : uint16_t enum MODULE_TYPE : uint16_t

View File

@ -369,6 +369,8 @@ nxinterface ISystemloader
virtual bool InstallFirmwarePackage(const char * utf8_path) = 0; virtual bool InstallFirmwarePackage(const char * utf8_path) = 0;
virtual void SetDisabledAddons(uint64_t program_id, const char * const * names, uint32_t count) = 0; virtual void SetDisabledAddons(uint64_t program_id, const char * const * names, uint32_t count) = 0;
virtual bool IsAddonDisabled(uint64_t program_id, const char * name) const = 0; virtual bool IsAddonDisabled(uint64_t program_id, const char * name) const = 0;
virtual LoaderResultStatus GetPMControlData(uint64_t program_id, uint8_t * buffer,
uint32_t buffer_size, uint32_t & actual_size) = 0;
}; };
EXPORT ISystemloader * CALL CreateSystemLoader(ISystemModules & modules); EXPORT ISystemloader * CALL CreateSystemLoader(ISystemModules & modules);

View File

@ -5,6 +5,7 @@
#include "core/core.h" #include "core/core.h"
#include "core/hle/service/ipc_helpers.h" #include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ngc/ngc.h" #include "core/hle/service/ngc/ngc.h"
#include "core/hle/service/ngc/streamplay.h"
#include "core/hle/service/server_manager.h" #include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -144,6 +145,7 @@ void LoopProcess(Core::System& system) {
server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system)); server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system)); server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
RegisterStreamPlayServices(*server_manager, system);
ServerManager::RunServer(std::move(server_manager)); ServerManager::RunServer(std::move(server_manager));
} }

View File

@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Service names, scoped interfaces and command tables are adapted from Eden.
// Unsupported operations use error-only replies rather than reporting success.
#include "core/hle/service/ngc/streamplay.h"
#include <span>
#include <nxemu-module-spec/system_loader.h>
#include "core/core.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NGC {
namespace {
class StreamPlayShim final : public ServiceFramework<StreamPlayShim> {
public:
StreamPlayShim(Core::System& system_, bool system_scope)
: ServiceFramework{system_, system_scope ? "ISystemShimScopedObject" : "IUserShimScopedObject"} {
const std::span<const StreamPlayCommand> commands = system_scope
? std::span<const StreamPlayCommand>{SystemShimCommands}
: std::span<const StreamPlayCommand>{UserShimCommands};
for (const auto& command : commands) {
const FunctionInfo function{command.id, &StreamPlayShim::Unsupported, command.name};
RegisterHandlers(&function, 1);
}
}
private:
void Unsupported(HLERequestContext& ctx) {
LOG_WARNING(Service_NGC, "StreamPlay operation unavailable: {} command={}",
GetServiceName(), ctx.GetCommand());
// No save handles, writes, events or asynchronous objects exist yet. An
// error-only response must not imply a successful read or durable write.
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(Result{StreamPlayNotSupported});
}
};
class StreamPlayService final : public ServiceFramework<StreamPlayService> {
public:
StreamPlayService(Core::System& system_, bool system_scope_)
: ServiceFramework{system_, system_scope_ ? "stpl:sys" : "stpl:u"},
system_scope{system_scope_} {
static const FunctionInfo functions[]{
{0, &StreamPlayService::CreateShim, "CreateShimScopedObject"},
};
RegisterHandlers(functions);
}
private:
void CreateShim(HLERequestContext& ctx) {
if (!system_scope) {
IPC::RequestParser parser{ctx};
const auto mode = parser.Pop<u32>();
LOG_DEBUG(Service_NGC, "StreamPlay user shim requested, mode={:#x}", mode);
}
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
rb.PushIpcInterface<StreamPlayShim>(system, system_scope);
}
bool system_scope;
};
} // namespace
void RegisterStreamPlayServices(ServerManager& manager, Core::System& system) {
char version[32]{};
if (system.GetSystemloader().GetInstalledFirmwareDisplayVersion(version, sizeof(version)) == 0 ||
!IsStreamPlayFirmwareSupported(version)) {
return;
}
manager.RegisterNamedService("stpl:u", std::make_shared<StreamPlayService>(system, false), 4);
manager.RegisterNamedService("stpl:sys", std::make_shared<StreamPlayService>(system, true), 4);
}
} // namespace Service::NGC

View File

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <array>
#include <charconv>
#include <cstdint>
#include <string_view>
namespace Core { class System; }
namespace Service { class ServerManager; }
namespace Service::NGC {
// SF/CMIF NotSupported (module 10, description 1), not HIPC (module 11).
inline constexpr uint32_t StreamPlayNotSupported = (1u << 9) | 10u;
struct StreamPlayCommand {
uint32_t id;
const char* name;
};
inline constexpr std::array UserShimCommands{
StreamPlayCommand{450, "InitializeForSaveData"},
StreamPlayCommand{451, "FinalizeForSaveData"},
StreamPlayCommand{452, "OpenSaveData"},
StreamPlayCommand{453, "CloseSaveData"},
StreamPlayCommand{454, "ReadSaveSlot"},
StreamPlayCommand{455, "WriteSaveSlot"},
StreamPlayCommand{456, "FlushSaveSlot"},
StreamPlayCommand{457, "CommitSaveData"},
};
inline constexpr std::array SystemShimCommands{
StreamPlayCommand{106, "Cmd106"}, StreamPlayCommand{107, "Cmd107"},
StreamPlayCommand{108, "Cmd108"}, StreamPlayCommand{207, "Cmd207"},
StreamPlayCommand{208, "Cmd208"}, StreamPlayCommand{209, "Cmd209"},
StreamPlayCommand{210, "Cmd210"}, StreamPlayCommand{211, "Cmd211"},
StreamPlayCommand{212, "Cmd212"},
};
inline bool IsStreamPlayFirmwareSupported(std::string_view version) {
if (version.empty()) {
return false;
}
std::array<uint32_t, 3> components{};
const char* cursor = version.data();
const char* end = cursor + version.size();
for (size_t i = 0; i < components.size(); ++i) {
if (cursor == end || *cursor < '0' || *cursor > '9') {
return false;
}
const auto parsed = std::from_chars(cursor, end, components[i]);
if (parsed.ec != std::errc{}) {
return false;
}
cursor = parsed.ptr;
if (i + 1 != components.size()) {
if (cursor == end || *cursor++ != '.') {
return false;
}
}
}
return cursor == end && components[0] >= 23;
}
void RegisterStreamPlayServices(ServerManager& manager, Core::System& system);
} // namespace Service::NGC

View File

@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Application-control reply flags and icon conversion are adapted from Eden.
#include "core/hle/service/ns/application_control_data.h"
#include <algorithm>
#include <memory>
#include <vector>
#include "yuzu_common/stb.h"
namespace Service::NS {
namespace {
constexpr int IconDimension = 174;
constexpr int MaxDecodedDimension = 1024;
struct JpegOutput {
std::vector<uint8_t> bytes;
bool overflow{};
};
void WriteJpeg(void* context, void* bytes, int count) {
auto& output = *static_cast<JpegOutput*>(context);
if (count < 0 || static_cast<size_t>(count) > ApplicationControlData::MaxIconSize - output.bytes.size()) {
output.overflow = true;
return;
}
if (!output.overflow && count != 0) {
const auto* begin = static_cast<const uint8_t*>(bytes);
output.bytes.insert(output.bytes.end(), begin, begin + count);
}
}
bool ResizeIcon(std::span<const uint8_t> input, std::vector<uint8_t>& output) {
// Restrict the decode before allocating pixels. Icons are small JPEG images.
constexpr uint8_t end_marker[]{0xff, 0xd9};
if (input.size() < 4 || input[0] != 0xff || input[1] != 0xd8 ||
std::search(input.begin() + 2, input.end(), std::begin(end_marker), std::end(end_marker)) == input.end()) {
return false;
}
int width{}, height{}, channels{};
const auto size = static_cast<int>(input.size());
if (!stbi_info_from_memory(input.data(), size, &width, &height, &channels) ||
width <= 0 || height <= 0 || width > MaxDecodedDimension || height > MaxDecodedDimension) {
return false;
}
std::unique_ptr<stbi_uc, decltype(&stbi_image_free)> pixels{
stbi_load_from_memory(input.data(), size, &width, &height, &channels, STBI_rgb),
stbi_image_free};
if (!pixels) {
return false;
}
if (width == IconDimension && height == IconDimension) {
output.assign(input.begin(), input.end());
return true;
}
std::vector<uint8_t> resized(IconDimension * IconDimension * STBI_rgb);
// RGB has no alpha channel. The final argument is a flags bitmask, not a filter enum.
if (!stbir_resize_uint8_srgb(pixels.get(), width, height, 0, resized.data(),
IconDimension, IconDimension, 0, STBI_rgb,
STBIR_ALPHA_CHANNEL_NONE, 0)) {
return false;
}
JpegOutput encoded;
if (!stbi_write_jpg_to_func(WriteJpeg, &encoded, IconDimension, IconDimension,
STBI_rgb, resized.data(), 90) || encoded.overflow || encoded.bytes.empty()) {
return false;
}
output = std::move(encoded.bytes);
return true;
}
} // namespace
LoaderResultStatus WriteApplicationControlData(std::span<const uint8_t> data,
std::span<uint8_t> output, bool modern, bool resize_icon, ControlDataResponse& response) {
response = {};
if (data.size() < ApplicationControlData::NacpSize || data.size() > ApplicationControlData::MaxSize) {
return LoaderResultStatus::ErrorNoControl;
}
auto icon = data.subspan(ApplicationControlData::NacpSize);
// The modern success flags follow Eden's existing compatibility contract. Do not
// return them when the icon is absent or its requested conversion failed.
if (modern && icon.empty()) {
return LoaderResultStatus::ErrorNoIcon;
}
std::vector<uint8_t> resized;
if (resize_icon) {
if (!ResizeIcon(icon, resized)) {
return LoaderResultStatus::ErrorNoIcon;
}
icon = resized;
}
const auto size = ApplicationControlData::NacpSize + icon.size();
if (output.size() < size) {
return LoaderResultStatus::ErrorBufferTooSmall;
}
// Validate everything before touching the guest's output. Never truncate JPEG data.
std::memcpy(output.data(), data.data(), ApplicationControlData::NacpSize);
if (!icon.empty()) {
std::memcpy(output.data() + ApplicationControlData::NacpSize, icon.data(), icon.size());
}
std::fill(output.begin() + size, output.end(), 0);
response.actual_size = static_cast<uint32_t>(size);
if (modern) {
response.flags_a = 0x10001;
response.flags_b = resize_icon ? 0x10001 : 0;
}
return LoaderResultStatus::Success;
}
} // namespace Service::NS

View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <span>
#include <nxemu-module-spec/application_control_data.h>
namespace Service::NS {
struct ControlDataResponse {
uint32_t flags_a{};
uint32_t flags_b{};
uint32_t actual_size{};
};
// Only the installed application's primary control-data entry is available.
constexpr bool IsControlDataRequestSupported(uint8_t source, uint8_t resize, uint8_t index) {
return source <= 2 && resize <= 1 && index == 0;
}
LoaderResultStatus WriteApplicationControlData(std::span<const uint8_t> data,
std::span<uint8_t> output, bool modern, bool resize_icon, ControlDataResponse& response);
} // namespace Service::NS

View File

@ -109,7 +109,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{305, nullptr, "TerminateSystemApplet"}, {305, nullptr, "TerminateSystemApplet"},
{306, nullptr, "LaunchOverlayApplet"}, {306, nullptr, "LaunchOverlayApplet"},
{307, nullptr, "TerminateOverlayApplet"}, {307, nullptr, "TerminateOverlayApplet"},
{400, D<&IApplicationManagerInterface::GetApplicationControlData>, "GetApplicationControlData"}, {400, &IApplicationManagerInterface::GetApplicationControlDataRequest, "GetApplicationControlData"},
{401, nullptr, "InvalidateAllApplicationControlCache"}, {401, nullptr, "InvalidateAllApplicationControlCache"},
{402, nullptr, "RequestDownloadApplicationControlData"}, {402, nullptr, "RequestDownloadApplicationControlData"},
{403, nullptr, "GetMaxApplicationControlCacheCount"}, {403, nullptr, "GetMaxApplicationControlCacheCount"},
@ -305,6 +305,10 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
IApplicationManagerInterface::~IApplicationManagerInterface() = default; IApplicationManagerInterface::~IApplicationManagerInterface() = default;
void IApplicationManagerInterface::GetApplicationControlDataRequest(HLERequestContext& ctx) {
IReadOnlyApplicationControlDataInterface(system).GetApplicationControlDataRequest(ctx);
}
Result IApplicationManagerInterface::GetApplicationControlData( Result IApplicationManagerInterface::GetApplicationControlData(
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size, OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
ApplicationControlSource application_control_source, u64 application_id) { ApplicationControlSource application_control_source, u64 application_id) {

View File

@ -15,6 +15,7 @@ class IApplicationManagerInterface final : public ServiceFramework<IApplicationM
public: public:
explicit IApplicationManagerInterface(Core::System& system_); explicit IApplicationManagerInterface(Core::System& system_);
~IApplicationManagerInterface() override; ~IApplicationManagerInterface() override;
void GetApplicationControlDataRequest(HLERequestContext& ctx);
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size, Out<u32> out_actual_size,

View File

@ -1,8 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// 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 "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h"
#include <vector>
#include <algorithm>
#include <cstddef>
#include "core/core.h"
#include "core/hle/service/ns/application_control_data.h"
#include "core/hle/service/cmif_serialization.h" #include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/language.h" #include "core/hle/service/ns/language.h"
#include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/ns_results.h"
#include "core/hle/service/set/settings_server.h" #include "core/hle/service/set/settings_server.h"
@ -11,16 +20,35 @@
namespace Service::NS namespace Service::NS
{ {
namespace {
Result ReadControlData(Core::System& system, u64 application_id, std::vector<u8>& data) {
data.resize(ApplicationControlData::MaxSize);
u32 actual_size{};
const auto status = system.GetSystemloader().GetPMControlData(application_id, data.data(),
static_cast<u32>(data.size()), actual_size);
if (status != LoaderResultStatus::Success || actual_size < ApplicationControlData::NacpSize ||
actual_size > data.size()) {
LOG_ERROR(Service_NS, "Control-data read failed: application_id={:016X}, status={}, size={:#x}",
application_id, static_cast<u32>(status), actual_size);
R_THROW(ResultUnknown);
}
data.resize(actual_size);
R_SUCCEED();
}
} // namespace
IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(Core::System & system_) : IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterface(Core::System & system_) :
ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"} ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"}
{ {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData>, "GetApplicationControlData"}, {0, &IReadOnlyApplicationControlDataInterface::GetApplicationControlDataRequest, "GetApplicationControlData"},
{1, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage>, "GetApplicationDesiredLanguage"}, {1, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage>, "GetApplicationDesiredLanguage"},
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
{4, nullptr, "SelectApplicationDesiredLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"},
{19, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData3Request, "GetApplicationControlData3"},
{23, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData3Request, "GetApplicationControlData3"},
}; };
// clang-format on // clang-format on
@ -29,11 +57,93 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default; IReadOnlyApplicationControlDataInterface::~IReadOnlyApplicationControlDataInterface() = default;
void IReadOnlyApplicationControlDataInterface::GetApplicationControlDataRequest(HLERequestContext& ctx) {
ReplyApplicationControlData(ctx, false);
}
void IReadOnlyApplicationControlDataInterface::GetApplicationControlData3Request(HLERequestContext& ctx) {
ReplyApplicationControlData(ctx, true);
}
void IReadOnlyApplicationControlDataInterface::ReplyApplicationControlData(HLERequestContext& ctx, bool modern) {
struct Input {
ApplicationControlSource source;
u8 resize_icon;
u8 index;
u8 padding[5];
u64 application_id;
};
static_assert(sizeof(Input) == 16 && offsetof(Input, application_id) == 8);
// The two flag bytes are padding in the legacy command and must be ignored there.
IPC::RequestParser parser{ctx};
const auto input = parser.PopRaw<Input>();
std::vector<u8> buffer(std::min(ctx.GetWriteBufferSize(), size_t{ApplicationControlData::MaxSize}));
ControlDataResponse response;
const auto output = OutBuffer<BufferAttr_HipcMapAlias>{std::span<u8>{buffer}};
const Result result = modern
? GetApplicationControlData3(output, &response.flags_a, &response.flags_b,
&response.actual_size, input.source, input.resize_icon, input.index, input.application_id)
: GetApplicationControlData(output, &response.actual_size, input.source, input.application_id);
if (result.IsFailure()) {
// The generic CMIF wrapper writes its temporary output buffers even on failure.
// Send an error-only reply here, without copying any bytes to guest memory.
IPC::ResponseBuilder reply{ctx, 2};
reply.Push(result);
return;
}
ctx.WriteBuffer(buffer);
IPC::ResponseBuilder reply{ctx, modern ? 5u : 3u};
reply.Push(result);
if (modern) {
reply.Push(response.flags_a);
reply.Push(response.flags_b);
}
reply.Push(response.actual_size);
}
Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size, ApplicationControlSource application_control_source, u64 application_id) Result IReadOnlyApplicationControlDataInterface::GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size, ApplicationControlSource application_control_source, u64 application_id)
{ {
LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}", application_control_source, application_id); LOG_INFO(Service_NS, "called with control_source={}, application_id={:016X}", application_control_source, application_id);
UNIMPLEMENTED(); *out_actual_size = 0;
if (!IsControlDataRequestSupported(static_cast<u8>(application_control_source), 0, 0)) {
R_THROW(ResultUnknown);
}
std::vector<u8> data;
R_TRY(ReadControlData(system, application_id, data));
ControlDataResponse response;
if (WriteApplicationControlData(data, out_buffer, false, false, response) != LoaderResultStatus::Success) {
R_THROW(ResultUnknown);
}
*out_actual_size = response.actual_size;
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 resize_icon, u8 control_data_index, u64 application_id)
{
*out_flags_a = 0;
*out_flags_b = 0;
*out_actual_size = 0;
LOG_INFO(Service_NS, "called with control_source={}, resize={}, index={}, application_id={:016X}",
application_control_source, resize_icon, control_data_index, application_id);
if (!IsControlDataRequestSupported(static_cast<u8>(application_control_source), resize_icon, control_data_index)) {
R_THROW(ResultUnknown);
}
std::vector<u8> data;
R_TRY(ReadControlData(system, application_id, data));
ControlDataResponse response;
const auto status = WriteApplicationControlData(data, out_buffer, true, resize_icon == 1, response);
if (status != LoaderResultStatus::Success) {
LOG_ERROR(Service_NS, "Control-data response failed: application_id={:016X}, status={}, buffer={:#x}",
application_id, static_cast<u32>(status), out_buffer.size());
R_THROW(ResultUnknown);
}
*out_flags_a = response.flags_a;
*out_flags_b = response.flags_b;
*out_actual_size = response.actual_size;
R_SUCCEED(); R_SUCCEED();
} }

View File

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// 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
@ -15,6 +18,8 @@ class IReadOnlyApplicationControlDataInterface final
public: public:
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_); explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
~IReadOnlyApplicationControlDataInterface() override; ~IReadOnlyApplicationControlDataInterface() override;
void GetApplicationControlDataRequest(HLERequestContext& ctx);
void GetApplicationControlData3Request(HLERequestContext& ctx);
public: public:
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
@ -25,6 +30,13 @@ 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 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 resize_icon, u8 control_data_index, u64 application_id);
private:
void ReplyApplicationControlData(HLERequestContext& ctx, bool modern);
}; };
} // namespace Service::NS } // namespace Service::NS

View File

@ -426,6 +426,7 @@
<ClCompile Include="core\hle\service\mnpp\mnpp_app.cpp" /> <ClCompile Include="core\hle\service\mnpp\mnpp_app.cpp" />
<ClCompile Include="core\hle\service\ncm\ncm.cpp" /> <ClCompile Include="core\hle\service\ncm\ncm.cpp" />
<ClCompile Include="core\hle\service\ngc\ngc.cpp" /> <ClCompile Include="core\hle\service\ngc\ngc.cpp" />
<ClCompile Include="core\hle\service\ngc\streamplay.cpp" />
<ClCompile Include="core\hle\service\nim\nim.cpp" /> <ClCompile Include="core\hle\service\nim\nim.cpp" />
<ClCompile Include="core\hle\service\npns\npns.cpp" /> <ClCompile Include="core\hle\service\npns\npns.cpp" />
<ClCompile Include="core\hle\service\ns\account_proxy_interface.cpp" /> <ClCompile Include="core\hle\service\ns\account_proxy_interface.cpp" />
@ -443,6 +444,7 @@
<ClCompile Include="core\hle\service\ns\platform_service_manager.cpp" /> <ClCompile Include="core\hle\service\ns\platform_service_manager.cpp" />
<ClCompile Include="core\hle\service\ns\query_service.cpp" /> <ClCompile Include="core\hle\service\ns\query_service.cpp" />
<ClCompile Include="core\hle\service\ns\read_only_application_control_data_interface.cpp" /> <ClCompile Include="core\hle\service\ns\read_only_application_control_data_interface.cpp" />
<ClCompile Include="core\hle\service\ns\application_control_data.cpp" />
<ClCompile Include="core\hle\service\ns\read_only_application_record_interface.cpp" /> <ClCompile Include="core\hle\service\ns\read_only_application_record_interface.cpp" />
<ClCompile Include="core\hle\service\ns\service_getter_interface.cpp" /> <ClCompile Include="core\hle\service\ns\service_getter_interface.cpp" />
<ClCompile Include="core\hle\service\ns\system_update_control.cpp" /> <ClCompile Include="core\hle\service\ns\system_update_control.cpp" />
@ -932,6 +934,7 @@
<ClInclude Include="core\hle\service\mnpp\mnpp_app.h" /> <ClInclude Include="core\hle\service\mnpp\mnpp_app.h" />
<ClInclude Include="core\hle\service\ncm\ncm.h" /> <ClInclude Include="core\hle\service\ncm\ncm.h" />
<ClInclude Include="core\hle\service\ngc\ngc.h" /> <ClInclude Include="core\hle\service\ngc\ngc.h" />
<ClInclude Include="core\hle\service\ngc\streamplay.h" />
<ClInclude Include="core\hle\service\nim\nim.h" /> <ClInclude Include="core\hle\service\nim\nim.h" />
<ClInclude Include="core\hle\service\npns\npns.h" /> <ClInclude Include="core\hle\service\npns\npns.h" />
<ClInclude Include="core\hle\service\ns\account_proxy_interface.h" /> <ClInclude Include="core\hle\service\ns\account_proxy_interface.h" />
@ -951,6 +954,7 @@
<ClInclude Include="core\hle\service\ns\platform_service_manager.h" /> <ClInclude Include="core\hle\service\ns\platform_service_manager.h" />
<ClInclude Include="core\hle\service\ns\query_service.h" /> <ClInclude Include="core\hle\service\ns\query_service.h" />
<ClInclude Include="core\hle\service\ns\read_only_application_control_data_interface.h" /> <ClInclude Include="core\hle\service\ns\read_only_application_control_data_interface.h" />
<ClInclude Include="core\hle\service\ns\application_control_data.h" />
<ClInclude Include="core\hle\service\ns\read_only_application_record_interface.h" /> <ClInclude Include="core\hle\service\ns\read_only_application_record_interface.h" />
<ClInclude Include="core\hle\service\ns\service_getter_interface.h" /> <ClInclude Include="core\hle\service\ns\service_getter_interface.h" />
<ClInclude Include="core\hle\service\ns\system_update_control.h" /> <ClInclude Include="core\hle\service\ns\system_update_control.h" />

View File

@ -1191,6 +1191,9 @@
<ClInclude Include="core\hle\service\ngc\ngc.h"> <ClInclude Include="core\hle\service\ngc\ngc.h">
<Filter>Header Files\core\hle\service\ngc</Filter> <Filter>Header Files\core\hle\service\ngc</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="core\hle\service\ngc\streamplay.h">
<Filter>Header Files\core\hle\service\ngc</Filter>
</ClInclude>
<ClInclude Include="core\hle\service\nim\nim.h"> <ClInclude Include="core\hle\service\nim\nim.h">
<Filter>Header Files\core\hle\service\nim</Filter> <Filter>Header Files\core\hle\service\nim</Filter>
</ClInclude> </ClInclude>
@ -1248,6 +1251,9 @@
<ClInclude Include="core\hle\service\ns\read_only_application_control_data_interface.h"> <ClInclude Include="core\hle\service\ns\read_only_application_control_data_interface.h">
<Filter>Header Files\core\hle\service\ns</Filter> <Filter>Header Files\core\hle\service\ns</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="core\hle\service\ns\application_control_data.h">
<Filter>Header Files\core\hle\service\ns</Filter>
</ClInclude>
<ClInclude Include="core\hle\service\ns\read_only_application_record_interface.h"> <ClInclude Include="core\hle\service\ns\read_only_application_record_interface.h">
<Filter>Header Files\core\hle\service\ns</Filter> <Filter>Header Files\core\hle\service\ns</Filter>
</ClInclude> </ClInclude>
@ -2770,6 +2776,9 @@
<ClCompile Include="core\hle\service\ngc\ngc.cpp"> <ClCompile Include="core\hle\service\ngc\ngc.cpp">
<Filter>Source Files\core\hle\service\ngc</Filter> <Filter>Source Files\core\hle\service\ngc</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="core\hle\service\ngc\streamplay.cpp">
<Filter>Source Files\core\hle\service\ngc</Filter>
</ClCompile>
<ClCompile Include="core\hle\service\nim\nim.cpp"> <ClCompile Include="core\hle\service\nim\nim.cpp">
<Filter>Source Files\core\hle\service\nim</Filter> <Filter>Source Files\core\hle\service\nim</Filter>
</ClCompile> </ClCompile>
@ -2830,6 +2839,9 @@
<ClCompile Include="core\hle\service\ns\read_only_application_control_data_interface.cpp"> <ClCompile Include="core\hle\service\ns\read_only_application_control_data_interface.cpp">
<Filter>Source Files\core\hle\service\ns</Filter> <Filter>Source Files\core\hle\service\ns</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="core\hle\service\ns\application_control_data.cpp">
<Filter>Source Files\core\hle\service\ns</Filter>
</ClCompile>
<ClCompile Include="core\hle\service\ns\application_manager_interface.cpp"> <ClCompile Include="core\hle\service\ns\application_manager_interface.cpp">
<Filter>Source Files\core\hle\service\ns</Filter> <Filter>Source Files\core\hle\service\ns</Filter>
</ClCompile> </ClCompile>

View File

@ -886,12 +886,24 @@ Result NPad::DisconnectNpad(u64 aruid, NpadIdType npad_id) {
return ResultInvalidNpadId; return ResultInvalidNpadId;
} }
// Applications can disconnect unused pads before creating their HID resource.
// Do not fall back to another application's controller data in this case.
const auto aruid_index = applet_resource_holder.applet_resource->GetIndexFromAruid(aruid);
if (aruid_index >= AruidIndexMax) {
return ResultSuccess;
}
const auto* data = applet_resource_holder.applet_resource->GetAruidDataByIndex(aruid_index);
if (data == nullptr || !data->flag.is_assigned) {
return ResultSuccess;
}
LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id); LOG_DEBUG(Service_HID, "Npad disconnected {}", npad_id);
auto& controller = GetControllerFromNpadIdType(aruid, npad_id); auto& controller = controller_data[aruid_index][NpadIdTypeToIndex(npad_id)];
auto* shared_memory = controller.shared_memory; auto* shared_memory = controller.shared_memory;
if (!shared_memory) { if (!shared_memory) {
LOG_WARNING(Service_HID, "shared_memory is null for npad_id={}", npad_id); LOG_DEBUG(Service_HID, "Npad resource is not active for aruid={:#x}, npad_id={}", aruid,
npad_id);
return ResultSuccess; return ResultSuccess;
} }
// Don't reset shared_memory->assignment_mode this value is persistent // Don't reset shared_memory->assignment_mode this value is persistent