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:
parent
732d0c4cba
commit
4a0bc1497c
|
|
@ -69,7 +69,8 @@ NACP::NACP() = default;
|
|||
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +81,7 @@ NACP & NACP::operator=(const NACP & other)
|
|||
if (this != &other)
|
||||
{
|
||||
raw = other.raw;
|
||||
valid = other.valid;
|
||||
version = other.version;
|
||||
developer = other.developer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ public:
|
|||
uint64_t GetDefaultNormalSaveSize() const;
|
||||
uint64_t GetDefaultJournalSaveSize() const;
|
||||
std::vector<u8> GetRawBytes() const;
|
||||
bool IsValid() const { return valid; }
|
||||
uint64_t GetDeviceSaveDataSize() const;
|
||||
|
||||
//IFileSysNACP
|
||||
|
|
@ -130,6 +131,7 @@ private:
|
|||
std::string version;
|
||||
mutable std::string developer;
|
||||
RawNACP raw{};
|
||||
bool valid{};
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "system_loader.h"
|
||||
#include <nxemu-module-spec/application_control_data.h>
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
|
|
@ -818,6 +819,25 @@ IFileSysNACP * Systemloader::GetPMControlMetadata(uint64_t programID)
|
|||
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()
|
||||
{
|
||||
return *(impl->m_manualContentProvider.get());
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ public:
|
|||
bool InstallFirmwarePackage(const char * utf8_path) 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;
|
||||
LoaderResultStatus GetPMControlData(uint64_t program_id, uint8_t * buffer,
|
||||
uint32_t buffer_size, uint32_t & actual_size) override;
|
||||
|
||||
private:
|
||||
Systemloader() = delete;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -25,10 +25,10 @@
|
|||
|
||||
enum
|
||||
{
|
||||
MODULE_LOADER_SPECS_VERSION = 0x0130,
|
||||
MODULE_LOADER_SPECS_VERSION = 0x0131,
|
||||
MODULE_VIDEO_SPECS_VERSION = 0x0123,
|
||||
MODULE_CPU_SPECS_VERSION = 0x011d,
|
||||
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0124,
|
||||
MODULE_OPERATING_SYSTEM_SPECS_VERSION = 0x0126,
|
||||
};
|
||||
|
||||
enum MODULE_TYPE : uint16_t
|
||||
|
|
@ -356,4 +356,4 @@ Purpose: Called when emulation is saving settings
|
|||
Input: None
|
||||
Output: None
|
||||
*/
|
||||
EXPORT void CALL FlushSettings();
|
||||
EXPORT void CALL FlushSettings();
|
||||
|
|
|
|||
|
|
@ -369,6 +369,8 @@ nxinterface ISystemloader
|
|||
virtual bool InstallFirmwarePackage(const char * utf8_path) = 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 LoaderResultStatus GetPMControlData(uint64_t program_id, uint8_t * buffer,
|
||||
uint32_t buffer_size, uint32_t & actual_size) = 0;
|
||||
};
|
||||
|
||||
EXPORT ISystemloader * CALL CreateSystemLoader(ISystemModules & modules);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "core/core.h"
|
||||
#include "core/hle/service/ipc_helpers.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/service.h"
|
||||
|
||||
|
|
@ -144,6 +145,7 @@ void LoopProcess(Core::System& system) {
|
|||
|
||||
server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
|
||||
server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
|
||||
RegisterStreamPlayServices(*server_manager, system);
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -109,7 +109,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
{305, nullptr, "TerminateSystemApplet"},
|
||||
{306, nullptr, "LaunchOverlayApplet"},
|
||||
{307, nullptr, "TerminateOverlayApplet"},
|
||||
{400, D<&IApplicationManagerInterface::GetApplicationControlData>, "GetApplicationControlData"},
|
||||
{400, &IApplicationManagerInterface::GetApplicationControlDataRequest, "GetApplicationControlData"},
|
||||
{401, nullptr, "InvalidateAllApplicationControlCache"},
|
||||
{402, nullptr, "RequestDownloadApplicationControlData"},
|
||||
{403, nullptr, "GetMaxApplicationControlCacheCount"},
|
||||
|
|
@ -305,6 +305,10 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
|
||||
IApplicationManagerInterface::~IApplicationManagerInterface() = default;
|
||||
|
||||
void IApplicationManagerInterface::GetApplicationControlDataRequest(HLERequestContext& ctx) {
|
||||
IReadOnlyApplicationControlDataInterface(system).GetApplicationControlDataRequest(ctx);
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationControlData(
|
||||
OutBuffer<BufferAttr_HipcMapAlias> out_buffer, Out<u32> out_actual_size,
|
||||
ApplicationControlSource application_control_source, u64 application_id) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class IApplicationManagerInterface final : public ServiceFramework<IApplicationM
|
|||
public:
|
||||
explicit IApplicationManagerInterface(Core::System& system_);
|
||||
~IApplicationManagerInterface() override;
|
||||
void GetApplicationControlDataRequest(HLERequestContext& ctx);
|
||||
|
||||
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
Out<u32> out_actual_size,
|
||||
|
|
|
|||
|
|
@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#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/ipc_helpers.h"
|
||||
#include "core/hle/service/ns/language.h"
|
||||
#include "core/hle/service/ns/ns_results.h"
|
||||
#include "core/hle/service/set/settings_server.h"
|
||||
|
|
@ -11,16 +20,35 @@
|
|||
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_) :
|
||||
ServiceFramework{system_, "IReadOnlyApplicationControlDataInterface"}
|
||||
{
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData>, "GetApplicationControlData"},
|
||||
{0, &IReadOnlyApplicationControlDataInterface::GetApplicationControlDataRequest, "GetApplicationControlData"},
|
||||
{1, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDesiredLanguage>, "GetApplicationDesiredLanguage"},
|
||||
{2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"},
|
||||
{3, nullptr, "ConvertLanguageCodeToApplicationLanguage"},
|
||||
{4, nullptr, "SelectApplicationDesiredLanguage"},
|
||||
{19, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData3Request, "GetApplicationControlData3"},
|
||||
{23, &IReadOnlyApplicationControlDataInterface::GetApplicationControlData3Request, "GetApplicationControlData3"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
@ -29,11 +57,93 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
|||
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -15,6 +18,8 @@ class IReadOnlyApplicationControlDataInterface final
|
|||
public:
|
||||
explicit IReadOnlyApplicationControlDataInterface(Core::System& system_);
|
||||
~IReadOnlyApplicationControlDataInterface() override;
|
||||
void GetApplicationControlDataRequest(HLERequestContext& ctx);
|
||||
void GetApplicationControlData3Request(HLERequestContext& ctx);
|
||||
|
||||
public:
|
||||
Result GetApplicationControlData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
|
||||
|
|
@ -25,6 +30,13 @@ public:
|
|||
u32 supported_languages);
|
||||
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -426,6 +426,7 @@
|
|||
<ClCompile Include="core\hle\service\mnpp\mnpp_app.cpp" />
|
||||
<ClCompile Include="core\hle\service\ncm\ncm.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\npns\npns.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\query_service.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\service_getter_interface.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\ncm\ncm.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\npns\npns.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\query_service.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\service_getter_interface.h" />
|
||||
<ClInclude Include="core\hle\service\ns\system_update_control.h" />
|
||||
|
|
|
|||
|
|
@ -1191,6 +1191,9 @@
|
|||
<ClInclude Include="core\hle\service\ngc\ngc.h">
|
||||
<Filter>Header Files\core\hle\service\ngc</Filter>
|
||||
</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">
|
||||
<Filter>Header Files\core\hle\service\nim</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -1248,6 +1251,9 @@
|
|||
<ClInclude Include="core\hle\service\ns\read_only_application_control_data_interface.h">
|
||||
<Filter>Header Files\core\hle\service\ns</Filter>
|
||||
</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">
|
||||
<Filter>Header Files\core\hle\service\ns</Filter>
|
||||
</ClInclude>
|
||||
|
|
@ -2770,6 +2776,9 @@
|
|||
<ClCompile Include="core\hle\service\ngc\ngc.cpp">
|
||||
<Filter>Source Files\core\hle\service\ngc</Filter>
|
||||
</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">
|
||||
<Filter>Source Files\core\hle\service\nim</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -2830,6 +2839,9 @@
|
|||
<ClCompile Include="core\hle\service\ns\read_only_application_control_data_interface.cpp">
|
||||
<Filter>Source Files\core\hle\service\ns</Filter>
|
||||
</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">
|
||||
<Filter>Source Files\core\hle\service\ns</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
|||
|
|
@ -886,12 +886,24 @@ Result NPad::DisconnectNpad(u64 aruid, NpadIdType npad_id) {
|
|||
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);
|
||||
auto& controller = GetControllerFromNpadIdType(aruid, npad_id);
|
||||
auto& controller = controller_data[aruid_index][NpadIdTypeToIndex(npad_id)];
|
||||
|
||||
auto* shared_memory = controller.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;
|
||||
}
|
||||
// Don't reset shared_memory->assignment_mode this value is persistent
|
||||
|
|
|
|||
Loading…
Reference in New Issue