// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2016 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include "common/assert.h" #include "common/common_types.h" #include "core/hle/ipc.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/k_session.h" #include "core/hle/result.h" #include "core/hle/service/hle_ipc.h" #include "core/hle/service/server_manager.h" namespace IPC { constexpr Result ResultSessionClosed{ErrorModule::HIPC, 301}; struct ResponseBuilder { /// Flags used for customizing the behavior of ResponseBuilder enum class Flags : u32 { None = 0, /// Uses move handles to move objects in the response, even when in a domain. This is /// required when PushMoveObjects is used. AlwaysMoveHandles = 1, }; inline explicit ResponseBuilder(Service::HLERequestContext& ctx, u32 normal_params_size_, u32 num_handles_to_copy_ = 0, u32 num_objects_to_move_ = 0, Flags flags = Flags::None) : cmdbuf(ctx.CommandBuffer()) , normal_params_size(normal_params_size_) , num_handles_to_copy(num_handles_to_copy_) , num_objects_to_move(num_objects_to_move_) { std::memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH); IPC::CommandHeader header{}; auto const mgr = ctx.GetManager().get(); // The entire size of the raw data section in u32 units, including the 16 bytes of mandatory // padding. u32 raw_data_size = ctx.write_size = ctx.IsTipc() ? normal_params_size - 1 : normal_params_size; u32 num_handles_to_move{}; u32 num_domain_objects{}; const bool always_move_handles = (u32(flags) & u32(Flags::AlwaysMoveHandles)) != 0; if (!mgr->IsDomain() || always_move_handles) { num_handles_to_move = num_objects_to_move; } else { num_domain_objects = num_objects_to_move; } if (mgr->IsDomain()) { raw_data_size += u32(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects); ctx.write_size += num_domain_objects; } if (ctx.IsTipc()) { header.type.Assign(ctx.GetCommandType()); } else { raw_data_size += u32(sizeof(IPC::DataPayloadHeader) / sizeof(u32) + 4 + normal_params_size); } header.data_size.Assign(raw_data_size); if (num_handles_to_copy || num_handles_to_move) { header.enable_handle_descriptor.Assign(1); } PushRaw(header); if (header.enable_handle_descriptor) { IPC::HandleDescriptorHeader handle_descriptor_header{}; handle_descriptor_header.num_handles_to_copy.Assign(num_handles_to_copy_); handle_descriptor_header.num_handles_to_move.Assign(num_handles_to_move); PushRaw(handle_descriptor_header); ctx.handles_offset = index; Skip(num_handles_to_copy + num_handles_to_move, true); } if (!ctx.IsTipc()) { AlignWithPadding(); if (mgr->IsDomain() && ctx.HasDomainMessageHeader()) { IPC::DomainMessageHeader domain_header{}; domain_header.num_objects = num_domain_objects; PushRaw(domain_header); } IPC::DataPayloadHeader data_payload_header{}; data_payload_header.magic = Common::MakeMagic('S', 'F', 'C', 'O'); PushRaw(data_payload_header); } data_payload_index = index; ctx.data_payload_offset = index; ctx.write_size += index; ctx.domain_offset = u32(index + raw_data_size / sizeof(u32)); } inline void Skip(u32 size_in_words, bool set_to_null) { if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); index += size_in_words; } /// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros. inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); } inline u32 GetCurrentOffset() const { return index; } inline void SetCurrentOffset(u32 offset) { index = offset; } template inline void PushIpcInterface(Service::HLERequestContext& ctx, std::shared_ptr iface) { auto manager = ctx.GetManager(); if (manager->IsDomain()) { ctx.AddDomainObject(std::move(iface)); } else { ASSERT(Kernel::GetCurrentProcess(ctx.kernel).GetResourceLimit()->Reserve(ctx.kernel, Kernel::LimitableResource::SessionCountMax, 1)); auto* session = Kernel::KSession::Create(ctx.kernel); session->Initialize(ctx.kernel, nullptr, 0); Kernel::KSession::Register(ctx.kernel, session); auto next_manager = std::make_shared(ctx.kernel, manager->GetServerManager()); next_manager->SetSessionHandler(iface); manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager); ctx.AddMoveObject(&session->GetClientSession()); } } template inline void PushIpcInterface(Service::HLERequestContext& ctx, Args&&... args) { PushIpcInterface(ctx, std::make_shared(std::forward(args)...)); } void PushImpl(s8 value); void PushImpl(s16 value); void PushImpl(s32 value); void PushImpl(s64 value); void PushImpl(u8 value); void PushImpl(u16 value); void PushImpl(u32 value); void PushImpl(u64 value); void PushImpl(float value); void PushImpl(double value); void PushImpl(bool value); void PushImpl(Result value); template inline void Push(T value) { return PushImpl(value); } template void Push(const First& first_value, const Other&... other_values); /// @brief Helper function for pushing strongly-typed enumeration values. /// @tparam Enum The enumeration type to be pushed /// @param value The value to push. /// @note The underlying size of the enumeration type is the size of the data that gets pushed. /// e.g. "enum class SomeEnum : u16" will push a u16-sized amount of data. template inline void PushEnum(Enum value) { static_assert(std::is_enum_v, "T must be an enum type within a PushEnum call."); static_assert(!std::is_convertible_v, "enum type in PushEnum must be a strongly typed enum."); Push(static_cast>(value)); } /// @brief Copies the content of the given trivially copyable class to the buffer as a normal param /// @note: The input class must be correctly packed/padded to fit hardware layout. template void PushRaw(const T& value); template void PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers); template void PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers); template void PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers); template void PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers); u32* cmdbuf; u32 index = 0; u32 normal_params_size{}; u32 num_handles_to_copy{}; u32 num_objects_to_move{}; ///< Domain objects or move handles, context dependent u32 data_payload_index{}; }; /// Push /// inline void ResponseBuilder::PushImpl(s32 value) { cmdbuf[index++] = value; } inline void ResponseBuilder::PushImpl(u32 value) { cmdbuf[index++] = value; } template void ResponseBuilder::PushRaw(const T& value) { static_assert(std::is_trivially_copyable_v, "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(cmdbuf + index, &value, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } inline void ResponseBuilder::PushImpl(Result value) { // Result codes are actually 64-bit in the IPC buffer, but only the high part is discarded. Push(value.raw); Push(0); } inline void ResponseBuilder::PushImpl(s8 value) { PushRaw(value); } inline void ResponseBuilder::PushImpl(s16 value) { PushRaw(value); } inline void ResponseBuilder::PushImpl(s64 value) { PushImpl(u32(value)); PushImpl(u32(value >> 32)); } inline void ResponseBuilder::PushImpl(u8 value) { PushRaw(value); } inline void ResponseBuilder::PushImpl(u16 value) { PushRaw(value); } inline void ResponseBuilder::PushImpl(u64 value) { PushImpl(u32(value)); PushImpl(u32(value >> 32)); } inline void ResponseBuilder::PushImpl(float value) { u32 integral; std::memcpy(&integral, &value, sizeof(u32)); PushImpl(integral); } inline void ResponseBuilder::PushImpl(double value) { u64 integral; std::memcpy(&integral, &value, sizeof(u64)); PushImpl(integral); } inline void ResponseBuilder::PushImpl(bool value) { PushImpl(static_cast(value)); } template void ResponseBuilder::Push(const First& first_value, const Other&... other_values) { Push(first_value); Push(other_values...); } template inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O*... pointers) { auto objects = {pointers...}; for (auto& object : objects) { ctx.AddCopyObject(object); } } template inline void ResponseBuilder::PushCopyObjects(Service::HLERequestContext& ctx, O&... pointers) { auto objects = {&pointers...}; for (auto& object : objects) { ctx.AddCopyObject(object); } } template inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O*... pointers) { auto objects = {pointers...}; for (auto& object : objects) { ctx.AddMoveObject(object); } } template inline void ResponseBuilder::PushMoveObjects(Service::HLERequestContext& ctx, O&... pointers) { auto objects = {&pointers...}; for (auto& object : objects) { ctx.AddMoveObject(object); } } struct RequestParser { inline explicit RequestParser(u32* command_buffer) : cmdbuf(command_buffer) {} inline explicit RequestParser(Service::HLERequestContext& ctx) : cmdbuf(ctx.CommandBuffer()) { // TIPC does not have data payload offset if (!ctx.IsTipc()) { ASSERT_MSG(ctx.GetDataPayloadOffset(), "context is incomplete"); Skip(ctx.GetDataPayloadOffset(), false); } // Skip the u64 command id, it's already stored in the context static constexpr u32 CommandIdSize = 2; Skip(CommandIdSize, false); } inline void Skip(u32 size_in_words, bool set_to_null) { if (set_to_null) std::memset(cmdbuf + index, 0, size_in_words * sizeof(u32)); index += size_in_words; } /// @brief Aligns the current position forward to a 16-byte boundary, padding with zeros. inline void AlignWithPadding() { if (index & 3) Skip(u32(4 - (index & 3)), true); } inline u32 GetCurrentOffset() const { return index; } inline void SetCurrentOffset(u32 offset) { index = offset; } template T Pop(); template void Pop(T& value); template void Pop(First& first_value, Other&... other_values); template T PopEnum() { static_assert(std::is_enum_v, "T must be an enum type within a PopEnum call."); static_assert(!std::is_convertible_v, "enum type in PopEnum must be a strongly typed enum."); return T(Pop>()); } /// @brief Reads the next normal parameters as a struct, by copying it /// @note: The output class must be correctly packed/padded to fit hardware layout. template void PopRaw(T& value); /// @brief Reads the next normal parameters as a struct, by copying it into a new value /// @note: The output class must be correctly packed/padded to fit hardware layout. template T PopRaw(); template [[nodiscard]] std::weak_ptr PopIpcInterface(Service::HLERequestContext& ctx) { ASSERT(ctx.GetManager()->IsDomain()); ASSERT(ctx.GetDomainMessageHeader().input_object_count > 0); return ctx.GetDomainHandler(Pop() - 1); } u32* cmdbuf; u32 index = 0; }; /// Pop /// template <> inline u32 RequestParser::Pop() { return cmdbuf[index++]; } template <> inline s32 RequestParser::Pop() { return s32(Pop()); } // Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects. #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif template void RequestParser::PopRaw(T& value) { static_assert(std::is_trivially_copyable_v, "It's undefined behavior to use memcpy with non-trivially copyable objects"); std::memcpy(&value, cmdbuf + index, sizeof(T)); index += (sizeof(T) + 3) / 4; // round up to word length } #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) #pragma GCC diagnostic pop #endif template T RequestParser::PopRaw() { T value; PopRaw(value); return value; } template <> inline u8 RequestParser::Pop() { return PopRaw(); } template <> inline u16 RequestParser::Pop() { return PopRaw(); } template <> inline u64 RequestParser::Pop() { const u64 lsw = Pop(); const u64 msw = Pop(); return msw << 32 | lsw; } template <> inline s8 RequestParser::Pop() { return static_cast(Pop()); } template <> inline s16 RequestParser::Pop() { return static_cast(Pop()); } template <> inline s64 RequestParser::Pop() { return static_cast(Pop()); } template <> inline float RequestParser::Pop() { const u32 value = Pop(); float real; std::memcpy(&real, &value, sizeof(real)); return real; } template <> inline double RequestParser::Pop() { const u64 value = Pop(); double real; std::memcpy(&real, &value, sizeof(real)); return real; } template <> inline bool RequestParser::Pop() { return Pop() != 0; } template <> inline Result RequestParser::Pop() { return Result{Pop()}; } template void RequestParser::Pop(T& value) { value = Pop(); } template void RequestParser::Pop(First& first_value, Other&... other_values) { first_value = Pop(); Pop(other_values...); } } // namespace IPC