code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <array>
#include <sstream>

#include <boost/range/algorithm_ext/erase.hpp>

#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/scratch_buffer.h"
#include "core/guest_memory.h"
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/memory.h"

namespace Service {

SessionRequestHandler::SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_)
    : kernel{kernel_} {}

SessionRequestHandler::~SessionRequestHandler() = default;

SessionRequestManager::SessionRequestManager(Kernel::KernelCore& kernel_,
                                             ServerManager& server_manager_)
    : kernel{kernel_}, server_manager{server_manager_} {}

SessionRequestManager::~SessionRequestManager() = default;

bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& context) const {
    if (IsDomain() && context.HasDomainMessageHeader()) {
        const auto& message_header = context.GetDomainMessageHeader();
        const auto object_id = message_header.object_id;

        if (object_id > DomainHandlerCount()) {
            LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
            return false;
        }
        return !DomainHandler(object_id - 1).expired();
    } else {
        return session_handler != nullptr;
    }
}

Result SessionRequestManager::CompleteSyncRequest(Kernel::KServerSession* server_session,
                                                  HLERequestContext& context) {
    Result result = ResultSuccess;

    // If the session has been converted to a domain, handle the domain request
    if (this->HasSessionRequestHandler(context)) {
        if (IsDomain() && context.HasDomainMessageHeader()) {
            result = HandleDomainSyncRequest(server_session, context);
            // If there is no domain header, the regular session handler is used
        } else if (this->HasSessionHandler()) {
            // If this manager has an associated HLE handler, forward the request to it.
            result = this->SessionHandler().HandleSyncRequest(*server_session, context);
        }
    } else {
        ASSERT_MSG(false, "Session handler is invalid, stubbing response!");
        IPC::ResponseBuilder rb(context, 2);
        rb.Push(ResultSuccess);
    }

    if (convert_to_domain) {
        ASSERT_MSG(!IsDomain(), "ServerSession is already a domain instance.");
        this->ConvertToDomain();
        convert_to_domain = false;
    }

    return result;
}

Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* server_session,
                                                      HLERequestContext& context) {
    if (!context.HasDomainMessageHeader()) {
        return ResultSuccess;
    }

    // Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
    ASSERT(context.GetManager().get() == this);

    // If there is a DomainMessageHeader, then this is CommandType "Request"
    const auto& domain_message_header = context.GetDomainMessageHeader();
    const u32 object_id{domain_message_header.object_id};
    switch (domain_message_header.command) {
    case IPC::DomainMessageHeader::CommandType::SendMessage:
        if (object_id > this->DomainHandlerCount()) {
            LOG_CRITICAL(IPC,
                         "object_id {} is too big! This probably means a recent service call "
                         "needed to return a new interface!",
                         object_id);
            ASSERT(false);
            return ResultSuccess; // Ignore error if asserts are off
        }
        if (auto strong_ptr = this->DomainHandler(object_id - 1).lock()) {
            return strong_ptr->HandleSyncRequest(*server_session, context);
        } else {
            ASSERT(false);
            return ResultSuccess;
        }

    case IPC::DomainMessageHeader::CommandType::CloseVirtualHandle: {
        LOG_DEBUG(IPC, "CloseVirtualHandle, object_id=0x{:08X}", object_id);

        this->CloseDomainHandler(object_id - 1);

        IPC::ResponseBuilder rb{context, 2};
        rb.Push(ResultSuccess);
        return ResultSuccess;
    }
    }

    LOG_CRITICAL(IPC, "Unknown domain command={}", domain_message_header.command.Value());
    ASSERT(false);
    return ResultSuccess;
}

HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_,
                                     Kernel::KServerSession* server_session_,
                                     Kernel::KThread* thread_)
    : server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} {
    cmd_buf[0] = 0;
}

HLERequestContext::~HLERequestContext() = default;

void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
    IPC::RequestParser rp(src_cmdbuf);
    command_header = rp.PopRaw<IPC::CommandHeader>();

    if (command_header->IsCloseCommand()) {
        // Close does not populate the rest of the IPC header
        return;
    }

    // If handle descriptor is present, add size of it
    if (command_header->enable_handle_descriptor) {
        handle_descriptor_header = rp.PopRaw<IPC::HandleDescriptorHeader>();
        if (handle_descriptor_header->send_current_pid) {
            pid = thread->GetOwnerProcess()->GetProcessId();
            rp.Skip(2, false);
        }
        if (incoming) {
            // Populate the object lists with the data in the IPC request.
            incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy);
            incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move);

            for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
                incoming_copy_handles.push_back(rp.Pop<Handle>());
            }
            for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_move; ++handle) {
                incoming_move_handles.push_back(rp.Pop<Handle>());
            }
        } else {
            // For responses we just ignore the handles, they're empty and will be populated when
            // translating the response.
            rp.Skip(handle_descriptor_header->num_handles_to_copy, false);
            rp.Skip(handle_descriptor_header->num_handles_to_move, false);
        }
    }

    buffer_x_descriptors.reserve(command_header->num_buf_x_descriptors);
    buffer_a_descriptors.reserve(command_header->num_buf_a_descriptors);
    buffer_b_descriptors.reserve(command_header->num_buf_b_descriptors);
    buffer_w_descriptors.reserve(command_header->num_buf_w_descriptors);

    for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
        buffer_x_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
    }
    for (u32 i = 0; i < command_header->num_buf_a_descriptors; ++i) {
        buffer_a_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
    }
    for (u32 i = 0; i < command_header->num_buf_b_descriptors; ++i) {
        buffer_b_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
    }
    for (u32 i = 0; i < command_header->num_buf_w_descriptors; ++i) {
        buffer_w_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorABW>());
    }

    const auto buffer_c_offset = rp.GetCurrentOffset() + command_header->data_size;

    if (!command_header->IsTipc()) {
        // Padding to align to 16 bytes
        rp.AlignWithPadding();

        if (GetManager()->IsDomain() &&
            ((command_header->type == IPC::CommandType::Request ||
              command_header->type == IPC::CommandType::RequestWithContext) ||
             !incoming)) {
            // If this is an incoming message, only CommandType "Request" has a domain header
            // All outgoing domain messages have the domain header, if only incoming has it
            if (incoming || domain_message_header) {
                domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
            } else {
                if (GetManager()->IsDomain()) {
                    LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
                }
            }
        }

        data_payload_header = rp.PopRaw<IPC::DataPayloadHeader>();

        data_payload_offset = rp.GetCurrentOffset();

        if (domain_message_header &&
            domain_message_header->command ==
                IPC::DomainMessageHeader::CommandType::CloseVirtualHandle) {
            // CloseVirtualHandle command does not have SFC* or any data
            return;
        }

        if (incoming) {
            ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
        } else {
            ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
        }
    }

    rp.SetCurrentOffset(buffer_c_offset);

    // For Inline buffers, the response data is written directly to buffer_c_offset
    // and in this case we don't have any BufferDescriptorC on the request.
    if (command_header->buf_c_descriptor_flags >
        IPC::CommandHeader::BufferDescriptorCFlag::InlineDescriptor) {
        if (command_header->buf_c_descriptor_flags ==
            IPC::CommandHeader::BufferDescriptorCFlag::OneDescriptor) {
            buffer_c_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
        } else {
            u32 num_buf_c_descriptors =
                static_cast<u32>(command_header->buf_c_descriptor_flags.Value()) - 2;

            // This is used to detect possible underflows, in case something is broken
            // with the two ifs above and the flags value is == 0 || == 1.
            ASSERT(num_buf_c_descriptors < 14);

            for (u32 i = 0; i < num_buf_c_descriptors; ++i) {
                buffer_c_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorC>());
            }
        }
    }

    rp.SetCurrentOffset(data_payload_offset);

    command = rp.Pop<u32_le>();
    rp.Skip(1, false); // The command is actually an u64, but we don't use the high part.
}

Result HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf) {
    client_handle_table = &thread->GetOwnerProcess()->GetHandleTable();

    ParseCommandBuffer(src_cmdbuf, true);

    if (command_header->IsCloseCommand()) {
        // Close does not populate the rest of the IPC header
        return ResultSuccess;
    }

    std::copy_n(src_cmdbuf, IPC::COMMAND_BUFFER_LENGTH, cmd_buf.begin());

    return ResultSuccess;
}

Result HLERequestContext::WriteToOutgoingCommandBuffer() {
    auto current_offset = handles_offset;
    auto& owner_process = *thread->GetOwnerProcess();
    auto& handle_table = owner_process.GetHandleTable();

    for (auto& object : outgoing_copy_objects) {
        Handle handle{};
        if (object) {
            R_TRY(handle_table.Add(&handle, object));
        }
        cmd_buf[current_offset++] = handle;
    }
    for (auto& object : outgoing_move_objects) {
        Handle handle{};
        if (object) {
            R_TRY(handle_table.Add(&handle, object));

            // Close our reference to the object, as it is being moved to the caller.
            object->Close();
        }
        cmd_buf[current_offset++] = handle;
    }

    // Write the domain objects to the command buffer, these go after the raw untranslated data.
    // TODO(Subv): This completely ignores C buffers.

    if (GetManager()->IsDomain()) {
        current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
        for (auto& object : outgoing_domain_objects) {
            if (object) {
                GetManager()->AppendDomainHandler(std::move(object));
                cmd_buf[current_offset++] = static_cast<u32_le>(GetManager()->DomainHandlerCount());
            } else {
                cmd_buf[current_offset++] = 0;
            }
        }
    }

    // Copy the translated command buffer back into the thread's command buffer area.
    memory.WriteBlock(thread->GetTlsAddress(), cmd_buf.data(), write_size * sizeof(u32));

    return ResultSuccess;
}

std::vector<u8> HLERequestContext::ReadBufferCopy(std::size_t buffer_index) const {
    const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
                           BufferDescriptorA()[buffer_index].Size()};
    if (is_buffer_a) {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorA().size() > buffer_index, { return {}; },
            "BufferDescriptorA invalid buffer_index {}", buffer_index);
        std::vector<u8> buffer(BufferDescriptorA()[buffer_index].Size());
        memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
        return buffer;
    } else {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorX().size() > buffer_index, { return {}; },
            "BufferDescriptorX invalid buffer_index {}", buffer_index);
        std::vector<u8> buffer(BufferDescriptorX()[buffer_index].Size());
        memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
        return buffer;
    }
}

std::span<const u8> HLERequestContext::ReadBufferA(std::size_t buffer_index) const {
    Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);

    ASSERT_OR_EXECUTE_MSG(
        BufferDescriptorA().size() > buffer_index, { return {}; },
        "BufferDescriptorA invalid buffer_index {}", buffer_index);
    return gm.Read(BufferDescriptorA()[buffer_index].Address(),
                   BufferDescriptorA()[buffer_index].Size(), &read_buffer_data_a[buffer_index]);
}

std::span<const u8> HLERequestContext::ReadBufferX(std::size_t buffer_index) const {
    Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);

    ASSERT_OR_EXECUTE_MSG(
        BufferDescriptorX().size() > buffer_index, { return {}; },
        "BufferDescriptorX invalid buffer_index {}", buffer_index);
    return gm.Read(BufferDescriptorX()[buffer_index].Address(),
                   BufferDescriptorX()[buffer_index].Size(), &read_buffer_data_x[buffer_index]);
}

std::span<const u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
    Core::Memory::CpuGuestMemory<u8, Core::Memory::GuestMemoryFlags::UnsafeRead> gm(memory, 0, 0);

    const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
                           BufferDescriptorA()[buffer_index].Size()};
    const bool is_buffer_x{BufferDescriptorX().size() > buffer_index &&
                           BufferDescriptorX()[buffer_index].Size()};

    if (is_buffer_a && is_buffer_x) {
        LOG_WARNING(Input, "Both buffer descriptors are available a.size={}, x.size={}",
                    BufferDescriptorA()[buffer_index].Size(),
                    BufferDescriptorX()[buffer_index].Size());
    }

    if (is_buffer_a) {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorA().size() > buffer_index, { return {}; },
            "BufferDescriptorA invalid buffer_index {}", buffer_index);
        return gm.Read(BufferDescriptorA()[buffer_index].Address(),
                       BufferDescriptorA()[buffer_index].Size(), &read_buffer_data_a[buffer_index]);
    } else {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorX().size() > buffer_index, { return {}; },
            "BufferDescriptorX invalid buffer_index {}", buffer_index);
        return gm.Read(BufferDescriptorX()[buffer_index].Address(),
                       BufferDescriptorX()[buffer_index].Size(), &read_buffer_data_x[buffer_index]);
    }
}

std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
                                           std::size_t buffer_index) const {
    if (size == 0) {
        LOG_WARNING(Core, "skip empty buffer write");
        return 0;
    }

    const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
                           BufferDescriptorB()[buffer_index].Size()};
    const std::size_t buffer_size{GetWriteBufferSize(buffer_index)};
    if (size > buffer_size) {
        LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
                     buffer_size);
        size = buffer_size; // TODO(bunnei): This needs to be HW tested
    }

    if (is_buffer_b) {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorB().size() > buffer_index &&
                BufferDescriptorB()[buffer_index].Size() >= size,
            { return 0; }, "BufferDescriptorB is invalid, index={}, size={}", buffer_index, size);
        WriteBufferB(buffer, size, buffer_index);
    } else {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorC().size() > buffer_index &&
                BufferDescriptorC()[buffer_index].Size() >= size,
            { return 0; }, "BufferDescriptorC is invalid, index={}, size={}", buffer_index, size);
        WriteBufferC(buffer, size, buffer_index);
    }

    return size;
}

std::size_t HLERequestContext::WriteBufferB(const void* buffer, std::size_t size,
                                            std::size_t buffer_index) const {
    if (buffer_index >= BufferDescriptorB().size() || size == 0) {
        return 0;
    }

    const auto buffer_size{BufferDescriptorB()[buffer_index].Size()};
    if (size > buffer_size) {
        LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
                     buffer_size);
        size = buffer_size; // TODO(bunnei): This needs to be HW tested
    }

    memory.WriteBlock(BufferDescriptorB()[buffer_index].Address(), buffer, size);
    return size;
}

std::size_t HLERequestContext::WriteBufferC(const void* buffer, std::size_t size,
                                            std::size_t buffer_index) const {
    if (buffer_index >= BufferDescriptorC().size() || size == 0) {
        return 0;
    }

    const auto buffer_size{BufferDescriptorC()[buffer_index].Size()};
    if (size > buffer_size) {
        LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size,
                     buffer_size);
        size = buffer_size; // TODO(bunnei): This needs to be HW tested
    }

    memory.WriteBlock(BufferDescriptorC()[buffer_index].Address(), buffer, size);
    return size;
}

std::size_t HLERequestContext::GetReadBufferSize(std::size_t buffer_index) const {
    const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
                           BufferDescriptorA()[buffer_index].Size()};
    if (is_buffer_a) {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorA().size() > buffer_index, { return 0; },
            "BufferDescriptorA invalid buffer_index {}", buffer_index);
        return BufferDescriptorA()[buffer_index].Size();
    } else {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorX().size() > buffer_index, { return 0; },
            "BufferDescriptorX invalid buffer_index {}", buffer_index);
        return BufferDescriptorX()[buffer_index].Size();
    }
}

std::size_t HLERequestContext::GetWriteBufferSize(std::size_t buffer_index) const {
    const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
                           BufferDescriptorB()[buffer_index].Size()};
    if (is_buffer_b) {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorB().size() > buffer_index, { return 0; },
            "BufferDescriptorB invalid buffer_index {}", buffer_index);
        return BufferDescriptorB()[buffer_index].Size();
    } else {
        ASSERT_OR_EXECUTE_MSG(
            BufferDescriptorC().size() > buffer_index, { return 0; },
            "BufferDescriptorC invalid buffer_index {}", buffer_index);
        return BufferDescriptorC()[buffer_index].Size();
    }
    return 0;
}

bool HLERequestContext::CanReadBuffer(std::size_t buffer_index) const {
    const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
                           BufferDescriptorA()[buffer_index].Size()};

    if (is_buffer_a) {
        return BufferDescriptorA().size() > buffer_index;
    } else {
        return BufferDescriptorX().size() > buffer_index;
    }
}

bool HLERequestContext::CanWriteBuffer(std::size_t buffer_index) const {
    const bool is_buffer_b{BufferDescriptorB().size() > buffer_index &&
                           BufferDescriptorB()[buffer_index].Size()};

    if (is_buffer_b) {
        return BufferDescriptorB().size() > buffer_index;
    } else {
        return BufferDescriptorC().size() > buffer_index;
    }
}

void HLERequestContext::AddMoveInterface(SessionRequestHandlerPtr s) {
    ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(
        Kernel::LimitableResource::SessionCountMax, 1));

    auto* session = Kernel::KSession::Create(kernel);
    session->Initialize(nullptr, 0);
    Kernel::KSession::Register(kernel, session);

    auto& server = manager.lock()->GetServerManager();
    auto next_manager = std::make_shared<Service::SessionRequestManager>(kernel, server);
    next_manager->SetSessionHandler(std::move(s));
    server.RegisterSession(&session->GetServerSession(), next_manager);

    AddMoveObject(&session->GetClientSession());
}

std::string HLERequestContext::Description() const {
    if (!command_header) {
        return "No command header available";
    }
    std::ostringstream s;
    s << "IPC::CommandHeader: Type:" << static_cast<u32>(command_header->type.Value());
    s << ", X(Pointer):" << command_header->num_buf_x_descriptors;
    if (command_header->num_buf_x_descriptors) {
        s << '[';
        for (u64 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
            s << "0x" << std::hex << BufferDescriptorX()[i].Size();
            if (i < command_header->num_buf_x_descriptors - 1)
                s << ", ";
        }
        s << ']';
    }
    s << ", A(Send):" << command_header->num_buf_a_descriptors;
    if (command_header->num_buf_a_descriptors) {
        s << '[';
        for (u64 i = 0; i < command_header->num_buf_a_descriptors; ++i) {
            s << "0x" << std::hex << BufferDescriptorA()[i].Size();
            if (i < command_header->num_buf_a_descriptors - 1)
                s << ", ";
        }
        s << ']';
    }
    s << ", B(Receive):" << command_header->num_buf_b_descriptors;
    if (command_header->num_buf_b_descriptors) {
        s << '[';
        for (u64 i = 0; i < command_header->num_buf_b_descriptors; ++i) {
            s << "0x" << std::hex << BufferDescriptorB()[i].Size();
            if (i < command_header->num_buf_b_descriptors - 1)
                s << ", ";
        }
        s << ']';
    }
    s << ", C(ReceiveList):" << BufferDescriptorC().size();
    if (!BufferDescriptorC().empty()) {
        s << '[';
        for (u64 i = 0; i < BufferDescriptorC().size(); ++i) {
            s << "0x" << std::hex << BufferDescriptorC()[i].Size();
            if (i < BufferDescriptorC().size() - 1)
                s << ", ";
        }
        s << ']';
    }
    s << ", data_size:" << command_header->data_size.Value();

    return s.str();
}

} // namespace Service


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <type_traits>
#include <vector>

#include "common/assert.h"
#include "common/common_types.h"
#include "common/concepts.h"
#include "common/swap.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/svc_common.h"

union Result;

namespace Core::Memory {
class Memory;
}

namespace IPC {
class ResponseBuilder;
}

namespace Service {
class ServiceFrameworkBase;
class ServerManager;
} // namespace Service

namespace Kernel {
class KAutoObject;
class KernelCore;
class KHandleTable;
class KProcess;
class KServerSession;
template <typename T>
class KScopedAutoObject;
class KThread;
} // namespace Kernel

namespace Service {

using Handle = Kernel::Handle;

class HLERequestContext;

/**
 * Interface implemented by HLE Session handlers.
 * This can be provided to a ServerSession in order to hook into several relevant events
 * (such as a new connection or a SyncRequest) so they can be implemented in the emulator.
 */
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
    SessionRequestHandler(Kernel::KernelCore& kernel_, const char* service_name_);
    virtual ~SessionRequestHandler();

    /**
     * Handles a sync request from the emulated application.
     * @param server_session The ServerSession that was triggered for this sync request,
     * it should be used to differentiate which client (As in ClientSession) we're answering to.
     * TODO(Subv): Use a wrapper structure to hold all the information relevant to
     * this request (ServerSession, Originator thread, Translated command buffer, etc).
     * @returns Result the result code of the translate operation.
     */
    virtual Result HandleSyncRequest(Kernel::KServerSession& session,
                                     HLERequestContext& context) = 0;

protected:
    Kernel::KernelCore& kernel;
};

using SessionRequestHandlerWeakPtr = std::weak_ptr<SessionRequestHandler>;
using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>;
using SessionRequestHandlerFactory = std::function<SessionRequestHandlerPtr()>;

/**
 * Manages the underlying HLE requests for a session, and whether (or not) the session should be
 * treated as a domain. This is managed separately from server sessions, as this state is shared
 * when objects are cloned.
 */
class SessionRequestManager final {
public:
    explicit SessionRequestManager(Kernel::KernelCore& kernel,
                                   Service::ServerManager& server_manager);
    ~SessionRequestManager();

    bool IsDomain() const {
        return is_domain;
    }

    void ConvertToDomain() {
        domain_handlers = {session_handler};
        is_domain = true;
    }

    void ConvertToDomainOnRequestEnd() {
        convert_to_domain = true;
    }

    std::size_t DomainHandlerCount() const {
        return domain_handlers.size();
    }

    bool HasSessionHandler() const {
        return session_handler != nullptr;
    }

    SessionRequestHandler& SessionHandler() {
        return *session_handler;
    }

    const SessionRequestHandler& SessionHandler() const {
        return *session_handler;
    }

    void CloseDomainHandler(std::size_t index) {
        if (index < DomainHandlerCount()) {
            domain_handlers[index] = nullptr;
        } else {
            ASSERT_MSG(false, "Unexpected handler index {}", index);
        }
    }

    SessionRequestHandlerWeakPtr DomainHandler(std::size_t index) const {
        ASSERT_MSG(index < DomainHandlerCount(), "Unexpected handler index {}", index);
        return domain_handlers.at(index);
    }

    void AppendDomainHandler(SessionRequestHandlerPtr&& handler) {
        domain_handlers.emplace_back(std::move(handler));
    }

    void SetSessionHandler(SessionRequestHandlerPtr&& handler) {
        session_handler = std::move(handler);
    }

    bool HasSessionRequestHandler(const HLERequestContext& context) const;

    Result HandleDomainSyncRequest(Kernel::KServerSession* server_session,
                                   HLERequestContext& context);
    Result CompleteSyncRequest(Kernel::KServerSession* server_session, HLERequestContext& context);

    Service::ServerManager& GetServerManager() {
        return server_manager;
    }

    // TODO: remove this when sm: is implemented with the proper IUserInterface
    // abstraction, creating a new C++ handler object for each session:

    bool GetIsInitializedForSm() const {
        return is_initialized_for_sm;
    }

    void SetIsInitializedForSm() {
        is_initialized_for_sm = true;
    }

private:
    bool convert_to_domain{};
    bool is_domain{};
    bool is_initialized_for_sm{};
    SessionRequestHandlerPtr session_handler;
    std::vector<SessionRequestHandlerPtr> domain_handlers;

private:
    Kernel::KernelCore& kernel;
    Service::ServerManager& server_manager;
};

/**
 * Class containing information about an in-flight IPC request being handled by an HLE service
 * implementation.
 */
class HLERequestContext {
public:
    explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory,
                               Kernel::KServerSession* session, Kernel::KThread* thread);
    ~HLERequestContext();

    /// Returns a pointer to the IPC command buffer for this request.
    [[nodiscard]] u32* CommandBuffer() {
        return cmd_buf.data();
    }

    /**
     * Returns the session through which this request was made. This can be used as a map key to
     * access per-client data on services.
     */
    [[nodiscard]] Kernel::KServerSession* Session() {
        return server_session;
    }

    /// Populates this context with data from the requesting process/thread.
    Result PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf);

    /// Writes data from this context back to the requesting process/thread.
    Result WriteToOutgoingCommandBuffer();

    [[nodiscard]] u32_le GetHipcCommand() const {
        return command;
    }

    [[nodiscard]] u32_le GetTipcCommand() const {
        return static_cast<u32_le>(command_header->type.Value()) -
               static_cast<u32_le>(IPC::CommandType::TIPC_CommandRegion);
    }

    [[nodiscard]] u32_le GetCommand() const {
        return command_header->IsTipc() ? GetTipcCommand() : GetHipcCommand();
    }

    [[nodiscard]] bool IsTipc() const {
        return command_header->IsTipc();
    }

    [[nodiscard]] IPC::CommandType GetCommandType() const {
        return command_header->type;
    }

    [[nodiscard]] u64 GetPID() const {
        return pid;
    }

    [[nodiscard]] u32 GetDataPayloadOffset() const {
        return data_payload_offset;
    }

    [[nodiscard]] const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const {
        return buffer_x_descriptors;
    }

    [[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const {
        return buffer_a_descriptors;
    }

    [[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const {
        return buffer_b_descriptors;
    }

    [[nodiscard]] const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
        return buffer_c_descriptors;
    }

    [[nodiscard]] const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
        return domain_message_header.value();
    }

    [[nodiscard]] bool HasDomainMessageHeader() const {
        return domain_message_header.has_value();
    }

    /// Helper function to get a span of a buffer using the buffer descriptor A
    [[nodiscard]] std::span<const u8> ReadBufferA(std::size_t buffer_index = 0) const;

    /// Helper function to get a span of a buffer using the buffer descriptor X
    [[nodiscard]] std::span<const u8> ReadBufferX(std::size_t buffer_index = 0) const;

    /// Helper function to get a span of a buffer using the appropriate buffer descriptor
    [[nodiscard]] std::span<const u8> ReadBuffer(std::size_t buffer_index = 0) const;

    /// Helper function to read a copy of a buffer using the appropriate buffer descriptor
    [[nodiscard]] std::vector<u8> ReadBufferCopy(std::size_t buffer_index = 0) const;

    /// Helper function to write a buffer using the appropriate buffer descriptor
    std::size_t WriteBuffer(const void* buffer, std::size_t size,
                            std::size_t buffer_index = 0) const;

    /// Helper function to write buffer B
    std::size_t WriteBufferB(const void* buffer, std::size_t size,
                             std::size_t buffer_index = 0) const;

    /// Helper function to write buffer C
    std::size_t WriteBufferC(const void* buffer, std::size_t size,
                             std::size_t buffer_index = 0) const;

    /* Helper function to write a buffer using the appropriate buffer descriptor
     *
     * @tparam T an arbitrary container that satisfies the
     *         ContiguousContainer concept in the C++ standard library or a trivially copyable type.
     *
     * @param data         The container/data to write into a buffer.
     * @param buffer_index The buffer in particular to write to.
     */
    template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
    std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const {
        if constexpr (Common::IsContiguousContainer<T>) {
            using ContiguousType = typename T::value_type;
            static_assert(std::is_trivially_copyable_v<ContiguousType>,
                          "Container to WriteBuffer must contain trivially copyable objects");
            return WriteBuffer(std::data(data), std::size(data) * sizeof(ContiguousType),
                               buffer_index);
        } else {
            static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
            return WriteBuffer(&data, sizeof(T), buffer_index);
        }
    }

    /// Helper function to get the size of the input buffer
    [[nodiscard]] std::size_t GetReadBufferSize(std::size_t buffer_index = 0) const;

    /// Helper function to get the size of the output buffer
    [[nodiscard]] std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const;

    /// Helper function to derive the number of elements able to be contained in the read buffer
    template <typename T>
    [[nodiscard]] std::size_t GetReadBufferNumElements(std::size_t buffer_index = 0) const {
        return GetReadBufferSize(buffer_index) / sizeof(T);
    }

    /// Helper function to derive the number of elements able to be contained in the write buffer
    template <typename T>
    [[nodiscard]] std::size_t GetWriteBufferNumElements(std::size_t buffer_index = 0) const {
        return GetWriteBufferSize(buffer_index) / sizeof(T);
    }

    /// Helper function to test whether the input buffer at buffer_index can be read
    [[nodiscard]] bool CanReadBuffer(std::size_t buffer_index = 0) const;

    /// Helper function to test whether the output buffer at buffer_index can be written
    [[nodiscard]] bool CanWriteBuffer(std::size_t buffer_index = 0) const;

    [[nodiscard]] Handle GetCopyHandle(std::size_t index) const {
        return incoming_copy_handles.at(index);
    }

    [[nodiscard]] Handle GetMoveHandle(std::size_t index) const {
        return incoming_move_handles.at(index);
    }

    void AddMoveObject(Kernel::KAutoObject* object) {
        outgoing_move_objects.emplace_back(object);
    }

    void AddMoveInterface(SessionRequestHandlerPtr s);

    void AddCopyObject(Kernel::KAutoObject* object) {
        outgoing_copy_objects.emplace_back(object);
    }

    void AddDomainObject(SessionRequestHandlerPtr object) {
        outgoing_domain_objects.emplace_back(std::move(object));
    }

    template <typename T>
    std::shared_ptr<T> GetDomainHandler(std::size_t index) const {
        return std::static_pointer_cast<T>(GetManager()->DomainHandler(index).lock());
    }

    void SetSessionRequestManager(std::weak_ptr<SessionRequestManager> manager_) {
        manager = manager_;
    }

    [[nodiscard]] std::string Description() const;

    [[nodiscard]] Kernel::KThread& GetThread() {
        return *thread;
    }

    [[nodiscard]] Core::Memory::Memory& GetMemory() const {
        return memory;
    }

    template <typename T>
    Kernel::KScopedAutoObject<T> GetObjectFromHandle(u32 handle) {
        auto obj = client_handle_table->GetObjectForIpc(handle, thread);
        if (obj.IsNotNull()) {
            return obj->DynamicCast<T*>();
        }
        return nullptr;
    }

    [[nodiscard]] std::shared_ptr<SessionRequestManager> GetManager() const {
        return manager.lock();
    }

    bool GetIsDeferred() const {
        return is_deferred;
    }

    void SetIsDeferred(bool is_deferred_ = true) {
        is_deferred = is_deferred_;
    }

private:
    friend class IPC::ResponseBuilder;

    void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);

    std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
    Kernel::KServerSession* server_session{};
    Kernel::KHandleTable* client_handle_table{};
    Kernel::KThread* thread{};

    std::vector<Handle> incoming_move_handles;
    std::vector<Handle> incoming_copy_handles;

    std::vector<Kernel::KAutoObject*> outgoing_move_objects;
    std::vector<Kernel::KAutoObject*> outgoing_copy_objects;
    std::vector<SessionRequestHandlerPtr> outgoing_domain_objects;

    std::optional<IPC::CommandHeader> command_header;
    std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
    std::optional<IPC::DataPayloadHeader> data_payload_header;
    std::optional<IPC::DomainMessageHeader> domain_message_header;
    std::vector<IPC::BufferDescriptorX> buffer_x_descriptors;
    std::vector<IPC::BufferDescriptorABW> buffer_a_descriptors;
    std::vector<IPC::BufferDescriptorABW> buffer_b_descriptors;
    std::vector<IPC::BufferDescriptorABW> buffer_w_descriptors;
    std::vector<IPC::BufferDescriptorC> buffer_c_descriptors;

    u32_le command{};
    u64 pid{};
    u32 write_size{};
    u32 data_payload_offset{};
    u32 handles_offset{};
    u32 domain_offset{};

    std::weak_ptr<SessionRequestManager> manager{};
    bool is_deferred{false};

    Kernel::KernelCore& kernel;
    Core::Memory::Memory& memory;

    mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
    mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
};

} // namespace Service

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>
#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};

class RequestHelperBase {
protected:
    Service::HLERequestContext* context = nullptr;
    u32* cmdbuf;
    u32 index = 0;

public:
    explicit RequestHelperBase(u32* command_buffer) : cmdbuf(command_buffer) {}

    explicit RequestHelperBase(Service::HLERequestContext& ctx)
        : context(&ctx), cmdbuf(ctx.CommandBuffer()) {}

    void Skip(u32 size_in_words, bool set_to_null) {
        if (set_to_null) {
            memset(cmdbuf + index, 0, size_in_words * sizeof(u32));
        }
        index += size_in_words;
    }

    /**
     * Aligns the current position forward to a 16-byte boundary, padding with zeros.
     */
    void AlignWithPadding() {
        if (index & 3) {
            Skip(static_cast<u32>(4 - (index & 3)), true);
        }
    }

    u32 GetCurrentOffset() const {
        return index;
    }

    void SetCurrentOffset(u32 offset) {
        index = offset;
    }
};

class ResponseBuilder : public RequestHelperBase {
public:
    /// 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,
    };

    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)
        : RequestHelperBase(ctx), normal_params_size(normal_params_size_),
          num_handles_to_copy(num_handles_to_copy_),
          num_objects_to_move(num_objects_to_move_), kernel{ctx.kernel} {

        memset(cmdbuf, 0, sizeof(u32) * IPC::COMMAND_BUFFER_LENGTH);

        IPC::CommandHeader header{};

        // 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{
            (static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0};
        if (!ctx.GetManager()->IsDomain() || always_move_handles) {
            num_handles_to_move = num_objects_to_move;
        } else {
            num_domain_objects = num_objects_to_move;
        }

        if (ctx.GetManager()->IsDomain()) {
            raw_data_size +=
                static_cast<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 += static_cast<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 (ctx.GetManager()->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 = static_cast<u32>(index + raw_data_size / sizeof(u32));
    }

    template <class T>
    void PushIpcInterface(std::shared_ptr<T> iface) {
        auto manager{context->GetManager()};

        if (manager->IsDomain()) {
            context->AddDomainObject(std::move(iface));
        } else {
            ASSERT(Kernel::GetCurrentProcess(kernel).GetResourceLimit()->Reserve(
                Kernel::LimitableResource::SessionCountMax, 1));

            auto* session = Kernel::KSession::Create(kernel);
            session->Initialize(nullptr, 0);
            Kernel::KSession::Register(kernel, session);

            auto next_manager = std::make_shared<Service::SessionRequestManager>(
                kernel, manager->GetServerManager());
            next_manager->SetSessionHandler(iface);
            manager->GetServerManager().RegisterSession(&session->GetServerSession(), next_manager);

            context->AddMoveObject(&session->GetClientSession());
        }
    }

    template <class T, class... Args>
    void PushIpcInterface(Args&&... args) {
        PushIpcInterface<T>(std::make_shared<T>(std::forward<Args>(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 <typename T>
    void Push(T value) {
        return PushImpl(value);
    }

    template <typename First, typename... Other>
    void Push(const First& first_value, const Other&... other_values);

    /**
     * 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 <typename Enum>
    void PushEnum(Enum value) {
        static_assert(std::is_enum_v<Enum>, "T must be an enum type within a PushEnum call.");
        static_assert(!std::is_convertible_v<Enum, int>,
                      "enum type in PushEnum must be a strongly typed enum.");
        Push(static_cast<std::underlying_type_t<Enum>>(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 <typename T>
    void PushRaw(const T& value);

    template <typename... O>
    void PushMoveObjects(O*... pointers);

    template <typename... O>
    void PushMoveObjects(O&... pointers);

    template <typename... O>
    void PushCopyObjects(O*... pointers);

    template <typename... O>
    void PushCopyObjects(O&... pointers);

private:
    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{};
    Kernel::KernelCore& kernel;
};

/// Push ///

inline void ResponseBuilder::PushImpl(s32 value) {
    cmdbuf[index++] = value;
}

inline void ResponseBuilder::PushImpl(u32 value) {
    cmdbuf[index++] = value;
}

template <typename T>
void ResponseBuilder::PushRaw(const T& value) {
    static_assert(std::is_trivially_copyable_v<T>,
                  "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<u32>(0);
}

inline void ResponseBuilder::PushImpl(s8 value) {
    PushRaw(value);
}

inline void ResponseBuilder::PushImpl(s16 value) {
    PushRaw(value);
}

inline void ResponseBuilder::PushImpl(s64 value) {
    PushImpl(static_cast<u32>(value));
    PushImpl(static_cast<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(static_cast<u32>(value));
    PushImpl(static_cast<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<u8>(value));
}

template <typename First, typename... Other>
void ResponseBuilder::Push(const First& first_value, const Other&... other_values) {
    Push(first_value);
    Push(other_values...);
}

template <typename... O>
inline void ResponseBuilder::PushCopyObjects(O*... pointers) {
    auto objects = {pointers...};
    for (auto& object : objects) {
        context->AddCopyObject(object);
    }
}

template <typename... O>
inline void ResponseBuilder::PushCopyObjects(O&... pointers) {
    auto objects = {&pointers...};
    for (auto& object : objects) {
        context->AddCopyObject(object);
    }
}

template <typename... O>
inline void ResponseBuilder::PushMoveObjects(O*... pointers) {
    auto objects = {pointers...};
    for (auto& object : objects) {
        context->AddMoveObject(object);
    }
}

template <typename... O>
inline void ResponseBuilder::PushMoveObjects(O&... pointers) {
    auto objects = {&pointers...};
    for (auto& object : objects) {
        context->AddMoveObject(object);
    }
}

class RequestParser : public RequestHelperBase {
public:
    explicit RequestParser(u32* command_buffer) : RequestHelperBase(command_buffer) {}

    explicit RequestParser(Service::HLERequestContext& ctx) : RequestHelperBase(ctx) {
        // 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);
    }

    template <typename T>
    T Pop();

    template <typename T>
    void Pop(T& value);

    template <typename First, typename... Other>
    void Pop(First& first_value, Other&... other_values);

    template <typename T>
    T PopEnum() {
        static_assert(std::is_enum_v<T>, "T must be an enum type within a PopEnum call.");
        static_assert(!std::is_convertible_v<T, int>,
                      "enum type in PopEnum must be a strongly typed enum.");
        return static_cast<T>(Pop<std::underlying_type_t<T>>());
    }

    /**
     * @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 <typename T>
    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 <typename T>
    T PopRaw();

    template <class T>
    std::weak_ptr<T> PopIpcInterface() {
        ASSERT(context->GetManager()->IsDomain());
        ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
        return context->GetDomainHandler<T>(Pop<u32>() - 1);
    }
};

/// Pop ///

template <>
inline u32 RequestParser::Pop() {
    return cmdbuf[index++];
}

template <>
inline s32 RequestParser::Pop() {
    return static_cast<s32>(Pop<u32>());
}

// 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 <typename T>
void RequestParser::PopRaw(T& value) {
    static_assert(std::is_trivially_copyable_v<T>,
                  "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 <typename T>
T RequestParser::PopRaw() {
    T value;
    PopRaw(value);
    return value;
}

template <>
inline u8 RequestParser::Pop() {
    return PopRaw<u8>();
}

template <>
inline u16 RequestParser::Pop() {
    return PopRaw<u16>();
}

template <>
inline u64 RequestParser::Pop() {
    const u64 lsw = Pop<u32>();
    const u64 msw = Pop<u32>();
    return msw << 32 | lsw;
}

template <>
inline s8 RequestParser::Pop() {
    return static_cast<s8>(Pop<u8>());
}

template <>
inline s16 RequestParser::Pop() {
    return static_cast<s16>(Pop<u16>());
}

template <>
inline s64 RequestParser::Pop() {
    return static_cast<s64>(Pop<u64>());
}

template <>
inline float RequestParser::Pop() {
    const u32 value = Pop<u32>();
    float real;
    std::memcpy(&real, &value, sizeof(real));
    return real;
}

template <>
inline double RequestParser::Pop() {
    const u64 value = Pop<u64>();
    double real;
    std::memcpy(&real, &value, sizeof(real));
    return real;
}

template <>
inline bool RequestParser::Pop() {
    return Pop<u8>() != 0;
}

template <>
inline Result RequestParser::Pop() {
    return Result{Pop<u32>()};
}

template <typename T>
void RequestParser::Pop(T& value) {
    value = Pop<T>();
}

template <typename First, typename... Other>
void RequestParser::Pop(First& first_value, Other&... other_values) {
    first_value = Pop<First>();
    Pop(other_values...);
}

} // namespace IPC


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/arm/debug.h"
#include "core/arm/symbols.h"
#include "core/core.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/result.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/jit/jit.h"
#include "core/hle/service/jit/jit_code_memory.h"
#include "core/hle/service/jit/jit_context.h"
#include "core/hle/service/server_manager.h"
#include "core/memory.h"

namespace Service::JIT {

struct CodeRange {
    u64 offset;
    u64 size;
};

using Struct32 = std::array<u64, 4>;
static_assert(sizeof(Struct32) == 32, "Struct32 has wrong size");

class IJitEnvironment final : public ServiceFramework<IJitEnvironment> {
public:
    explicit IJitEnvironment(Core::System& system_,
                             Kernel::KScopedAutoObject<Kernel::KProcess> process_,
                             CodeMemory&& user_rx_, CodeMemory&& user_ro_)
        : ServiceFramework{system_, "IJitEnvironment"}, process{std::move(process_)},
          user_rx{std::move(user_rx_)}, user_ro{std::move(user_ro_)},
          context{system_.ApplicationMemory()} {

        // clang-format off
        static const FunctionInfo functions[] = {
            {0, C<&IJitEnvironment::GenerateCode>, "GenerateCode"},
            {1, C<&IJitEnvironment::Control>, "Control"},
            {1000, C<&IJitEnvironment::LoadPlugin>, "LoadPlugin"},
            {1001, C<&IJitEnvironment::GetCodeAddress>, "GetCodeAddress"},
        };
        // clang-format on

        RegisterHandlers(functions);

        // Identity map user code range into sysmodule context
        configuration.user_rx_memory.size = user_rx.GetSize();
        configuration.user_rx_memory.offset = user_rx.GetAddress();
        configuration.user_ro_memory.size = user_ro.GetSize();
        configuration.user_ro_memory.offset = user_ro.GetAddress();

        configuration.sys_rx_memory = configuration.user_rx_memory;
        configuration.sys_ro_memory = configuration.user_ro_memory;
    }

    Result GenerateCode(Out<s32> out_return_value, Out<CodeRange> out_range0,
                        Out<CodeRange> out_range1, OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
                        u32 data_size, u64 command, CodeRange range0, CodeRange range1,
                        Struct32 data, InBuffer<BufferAttr_HipcMapAlias> buffer) {
        // Function call prototype:
        // void GenerateCode(s32* ret, CodeRange* c0_out, CodeRange* c1_out, JITConfiguration* cfg,
        //                   u64 cmd, u8* input_buf, size_t input_size, CodeRange* c0_in,
        //                   CodeRange* c1_in, Struct32* data, size_t data_size, u8* output_buf,
        //                   size_t output_size);
        //
        // The command argument is used to control the behavior of the plugin during code
        // generation. The configuration allows the plugin to access the output code ranges, and the
        // other arguments are used to transfer state between the game and the plugin.

        const VAddr ret_ptr{context.AddHeap(0u)};
        const VAddr c0_in_ptr{context.AddHeap(range0)};
        const VAddr c1_in_ptr{context.AddHeap(range1)};
        const VAddr c0_out_ptr{context.AddHeap(ClearSize(range0))};
        const VAddr c1_out_ptr{context.AddHeap(ClearSize(range1))};

        const VAddr input_ptr{context.AddHeap(buffer.data(), buffer.size())};
        const VAddr output_ptr{context.AddHeap(out_buffer.data(), out_buffer.size())};
        const VAddr data_ptr{context.AddHeap(data)};
        const VAddr configuration_ptr{context.AddHeap(configuration)};

        // The callback does not directly return a value, it only writes to the output pointer
        context.CallFunction(callbacks.GenerateCode, ret_ptr, c0_out_ptr, c1_out_ptr,
                             configuration_ptr, command, input_ptr, buffer.size(), c0_in_ptr,
                             c1_in_ptr, data_ptr, data_size, output_ptr, out_buffer.size());

        *out_return_value = context.GetHeap<s32>(ret_ptr);
        *out_range0 = context.GetHeap<CodeRange>(c0_out_ptr);
        *out_range1 = context.GetHeap<CodeRange>(c1_out_ptr);
        context.GetHeap(output_ptr, out_buffer.data(), out_buffer.size());

        if (*out_return_value != 0) {
            LOG_WARNING(Service_JIT, "plugin GenerateCode callback failed");
            R_THROW(ResultUnknown);
        }

        R_SUCCEED();
    }

    Result Control(Out<s32> out_return_value, InBuffer<BufferAttr_HipcMapAlias> in_data,
                   OutBuffer<BufferAttr_HipcMapAlias> out_data, u64 command) {
        // Function call prototype:
        // u64 Control(s32* ret, JITConfiguration* cfg, u64 cmd, u8* input_buf, size_t input_size,
        //             u8* output_buf, size_t output_size);
        //
        // This function is used to set up the state of the plugin before code generation, generally
        // passing objects like pointers to VM state from the game. It is usually called once.

        const VAddr ret_ptr{context.AddHeap(0u)};
        const VAddr configuration_ptr{context.AddHeap(configuration)};
        const VAddr input_ptr{context.AddHeap(in_data.data(), in_data.size())};
        const VAddr output_ptr{context.AddHeap(out_data.data(), out_data.size())};

        const u64 wrapper_value{context.CallFunction(callbacks.Control, ret_ptr, configuration_ptr,
                                                     command, input_ptr, in_data.size(), output_ptr,
                                                     out_data.size())};

        *out_return_value = context.GetHeap<s32>(ret_ptr);
        context.GetHeap(output_ptr, out_data.data(), out_data.size());

        if (wrapper_value == 0 && *out_return_value == 0) {
            R_SUCCEED();
        }

        LOG_WARNING(Service_JIT, "plugin Control callback failed");
        R_THROW(ResultUnknown);
    }

    Result LoadPlugin(u64 tmem_size, InCopyHandle<Kernel::KTransferMemory> tmem,
                      InBuffer<BufferAttr_HipcMapAlias> nrr,
                      InBuffer<BufferAttr_HipcMapAlias> nro) {
        if (!tmem) {
            LOG_ERROR(Service_JIT, "Invalid transfer memory handle!");
            R_THROW(ResultUnknown);
        }

        // Set up the configuration with the required TransferMemory address
        configuration.transfer_memory.offset = GetInteger(tmem->GetSourceAddress());
        configuration.transfer_memory.size = tmem_size;

        // Gather up all the callbacks from the loaded plugin
        auto symbols{Core::Symbols::GetSymbols(nro, true)};
        const auto GetSymbol{[&](const std::string& name) { return symbols[name].first; }};

        callbacks.rtld_fini = GetSymbol("_fini");
        callbacks.rtld_init = GetSymbol("_init");
        callbacks.Control = GetSymbol("nnjitpluginControl");
        callbacks.ResolveBasicSymbols = GetSymbol("nnjitpluginResolveBasicSymbols");
        callbacks.SetupDiagnostics = GetSymbol("nnjitpluginSetupDiagnostics");
        callbacks.Configure = GetSymbol("nnjitpluginConfigure");
        callbacks.GenerateCode = GetSymbol("nnjitpluginGenerateCode");
        callbacks.GetVersion = GetSymbol("nnjitpluginGetVersion");
        callbacks.OnPrepared = GetSymbol("nnjitpluginOnPrepared");
        callbacks.Keeper = GetSymbol("nnjitpluginKeeper");

        if (callbacks.GetVersion == 0 || callbacks.Configure == 0 || callbacks.GenerateCode == 0 ||
            callbacks.OnPrepared == 0) {
            LOG_ERROR(Service_JIT, "plugin does not implement all necessary functionality");
            R_THROW(ResultUnknown);
        }

        if (!context.LoadNRO(nro)) {
            LOG_ERROR(Service_JIT, "failed to load plugin");
            R_THROW(ResultUnknown);
        }

        context.MapProcessMemory(configuration.sys_ro_memory.offset,
                                 configuration.sys_ro_memory.size);
        context.MapProcessMemory(configuration.sys_rx_memory.offset,
                                 configuration.sys_rx_memory.size);
        context.MapProcessMemory(configuration.transfer_memory.offset,
                                 configuration.transfer_memory.size);

        // Run ELF constructors, if needed
        if (callbacks.rtld_init != 0) {
            context.CallFunction(callbacks.rtld_init);
        }

        // Function prototype:
        // u64 GetVersion();
        const auto version{context.CallFunction(callbacks.GetVersion)};
        if (version != 1) {
            LOG_ERROR(Service_JIT, "unknown plugin version {}", version);
            R_THROW(ResultUnknown);
        }

        // Function prototype:
        // void ResolveBasicSymbols(void (*resolver)(const char* name));
        const auto resolve{context.GetHelper("_resolve")};
        if (callbacks.ResolveBasicSymbols != 0) {
            context.CallFunction(callbacks.ResolveBasicSymbols, resolve);
        }

        // Function prototype:
        // void SetupDiagnostics(u32 enabled, void (**resolver)(const char* name));
        const auto resolve_ptr{context.AddHeap(resolve)};
        if (callbacks.SetupDiagnostics != 0) {
            context.CallFunction(callbacks.SetupDiagnostics, 0u, resolve_ptr);
        }

        // Function prototype:
        // void Configure(u32* memory_flags);
        context.CallFunction(callbacks.Configure, 0ull);

        // Function prototype:
        // void OnPrepared(JITConfiguration* cfg);
        const auto configuration_ptr{context.AddHeap(configuration)};
        context.CallFunction(callbacks.OnPrepared, configuration_ptr);

        R_SUCCEED();
    }

    Result GetCodeAddress(Out<u64> rx_offset, Out<u64> ro_offset) {
        LOG_DEBUG(Service_JIT, "called");

        *rx_offset = configuration.user_rx_memory.offset;
        *ro_offset = configuration.user_ro_memory.offset;

        R_SUCCEED();
    }

private:
    struct GuestCallbacks {
        VAddr rtld_fini;
        VAddr rtld_init;
        VAddr Control;
        VAddr ResolveBasicSymbols;
        VAddr SetupDiagnostics;
        VAddr Configure;
        VAddr GenerateCode;
        VAddr GetVersion;
        VAddr Keeper;
        VAddr OnPrepared;
    };

    struct JITConfiguration {
        CodeRange user_rx_memory;
        CodeRange user_ro_memory;
        CodeRange transfer_memory;
        CodeRange sys_rx_memory;
        CodeRange sys_ro_memory;
    };

    static CodeRange ClearSize(CodeRange in) {
        in.size = 0;
        return in;
    }

    Kernel::KScopedAutoObject<Kernel::KProcess> process;
    CodeMemory user_rx;
    CodeMemory user_ro;
    GuestCallbacks callbacks;
    JITConfiguration configuration;
    JITContext context;
};

class JITU final : public ServiceFramework<JITU> {
public:
    explicit JITU(Core::System& system_) : ServiceFramework{system_, "jit:u"} {
        // clang-format off
        static const FunctionInfo functions[] = {
            {0, C<&JITU::CreateJitEnvironment>, "CreateJitEnvironment"},
        };
        // clang-format on

        RegisterHandlers(functions);
    }

private:
    Result CreateJitEnvironment(Out<SharedPointer<IJitEnvironment>> out_jit_environment,
                                u64 rx_size, u64 ro_size, InCopyHandle<Kernel::KProcess> process,
                                InCopyHandle<Kernel::KCodeMemory> rx_mem,
                                InCopyHandle<Kernel::KCodeMemory> ro_mem) {
        if (!process) {
            LOG_ERROR(Service_JIT, "process is null");
            R_THROW(ResultUnknown);
        }
        if (!rx_mem) {
            LOG_ERROR(Service_JIT, "rx_mem is null");
            R_THROW(ResultUnknown);
        }
        if (!ro_mem) {
            LOG_ERROR(Service_JIT, "ro_mem is null");
            R_THROW(ResultUnknown);
        }

        CodeMemory rx, ro;

        R_TRY(rx.Initialize(*process, *rx_mem, rx_size, Kernel::Svc::MemoryPermission::ReadExecute,
                            generate_random));
        R_TRY(ro.Initialize(*process, *ro_mem, ro_size, Kernel::Svc::MemoryPermission::Read,
                            generate_random));

        *out_jit_environment =
            std::make_shared<IJitEnvironment>(system, process.Get(), std::move(rx), std::move(ro));
        R_SUCCEED();
    }

private:
    std::mt19937_64 generate_random{};
};

void LoopProcess(Core::System& system) {
    auto server_manager = std::make_unique<ServerManager>(system);

    server_manager->RegisterNamedService("jit:u", std::make_shared<JITU>(system));
    ServerManager::RunServer(std::move(server_manager));
}

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

namespace Core {
class System;
}

namespace Service::JIT {

void LoopProcess(Core::System& system);

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/jit/jit_code_memory.h"

namespace Service::JIT {

Result CodeMemory::Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory,
                              size_t size, Kernel::Svc::MemoryPermission perm,
                              std::mt19937_64& generate_random) {
    auto& page_table = process.GetPageTable();
    const u64 alias_code_start =
        GetInteger(page_table.GetAliasCodeRegionStart()) / Kernel::PageSize;
    const u64 alias_code_size = page_table.GetAliasCodeRegionSize() / Kernel::PageSize;

    // NOTE: This will retry indefinitely until mapping the code memory succeeds.
    while (true) {
        // Generate a new trial address.
        const u64 mapped_address =
            (alias_code_start + (generate_random() % alias_code_size)) * Kernel::PageSize;

        // Try to map the address
        R_TRY_CATCH(code_memory.MapToOwner(mapped_address, size, perm)) {
            R_CATCH(Kernel::ResultInvalidMemoryRegion) {
                // If we could not map here, retry.
                continue;
            }
        }
        R_END_TRY_CATCH;

        // Set members.
        m_code_memory = std::addressof(code_memory);
        m_size = size;
        m_address = mapped_address;
        m_perm = perm;

        // Open a new reference to the code memory.
        m_code_memory->Open();

        // We succeeded.
        R_SUCCEED();
    }
}

void CodeMemory::Finalize() {
    if (m_code_memory) {
        R_ASSERT(m_code_memory->UnmapFromOwner(m_address, m_size));
        m_code_memory->Close();
    }

    m_code_memory = nullptr;
}

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <random>

#include "core/hle/kernel/k_code_memory.h"

namespace Service::JIT {

class CodeMemory {
public:
    YUZU_NON_COPYABLE(CodeMemory);

    explicit CodeMemory() = default;

    CodeMemory(CodeMemory&& rhs) {
        std::swap(m_code_memory, rhs.m_code_memory);
        std::swap(m_size, rhs.m_size);
        std::swap(m_address, rhs.m_address);
        std::swap(m_perm, rhs.m_perm);
    }

    ~CodeMemory() {
        this->Finalize();
    }

public:
    Result Initialize(Kernel::KProcess& process, Kernel::KCodeMemory& code_memory, size_t size,
                      Kernel::Svc::MemoryPermission perm, std::mt19937_64& generate_random);
    void Finalize();

    size_t GetSize() const {
        return m_size;
    }

    u64 GetAddress() const {
        return m_address;
    }

private:
    Kernel::KCodeMemory* m_code_memory{};
    size_t m_size{};
    u64 m_address{};
    Kernel::Svc::MemoryPermission m_perm{};
};

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <array>
#include <map>
#include <span>
#include <boost/icl/interval_set.hpp>
#include <dynarmic/interface/A64/a64.h>
#include <dynarmic/interface/A64/config.h>

#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/div_ceil.h"
#include "common/elf.h"
#include "common/logging/log.h"
#include "core/hle/service/jit/jit_context.h"
#include "core/memory.h"

using namespace Common::ELF;

namespace Service::JIT {

constexpr std::array<u8, 8> SVC0_ARM64 = {
    0x01, 0x00, 0x00, 0xd4, // svc  #0
    0xc0, 0x03, 0x5f, 0xd6, // ret
};

constexpr std::array HELPER_FUNCTIONS{
    "_stop", "_resolve", "_panic", "memcpy", "memmove", "memset",
};

constexpr size_t STACK_ALIGN = 16;

class JITContextImpl;

using IntervalSet = boost::icl::interval_set<VAddr>::type;
using IntervalType = boost::icl::interval_set<VAddr>::interval_type;

class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
    explicit DynarmicCallbacks64(Core::Memory::Memory& memory_, std::vector<u8>& local_memory_,
                                 IntervalSet& mapped_ranges_, JITContextImpl& parent_)
        : memory{memory_}, local_memory{local_memory_},
          mapped_ranges{mapped_ranges_}, parent{parent_} {}

    u8 MemoryRead8(u64 vaddr) override {
        return ReadMemory<u8>(vaddr);
    }
    u16 MemoryRead16(u64 vaddr) override {
        return ReadMemory<u16>(vaddr);
    }
    u32 MemoryRead32(u64 vaddr) override {
        return ReadMemory<u32>(vaddr);
    }
    u64 MemoryRead64(u64 vaddr) override {
        return ReadMemory<u64>(vaddr);
    }
    u128 MemoryRead128(u64 vaddr) override {
        return ReadMemory<u128>(vaddr);
    }
    std::string MemoryReadCString(u64 vaddr) {
        std::string result;
        u8 next;

        while ((next = MemoryRead8(vaddr++)) != 0) {
            result += next;
        }

        return result;
    }

    void MemoryWrite8(u64 vaddr, u8 value) override {
        WriteMemory<u8>(vaddr, value);
    }
    void MemoryWrite16(u64 vaddr, u16 value) override {
        WriteMemory<u16>(vaddr, value);
    }
    void MemoryWrite32(u64 vaddr, u32 value) override {
        WriteMemory<u32>(vaddr, value);
    }
    void MemoryWrite64(u64 vaddr, u64 value) override {
        WriteMemory<u64>(vaddr, value);
    }
    void MemoryWrite128(u64 vaddr, u128 value) override {
        WriteMemory<u128>(vaddr, value);
    }

    bool MemoryWriteExclusive8(u64 vaddr, u8 value, u8) override {
        return WriteMemory<u8>(vaddr, value);
    }
    bool MemoryWriteExclusive16(u64 vaddr, u16 value, u16) override {
        return WriteMemory<u16>(vaddr, value);
    }
    bool MemoryWriteExclusive32(u64 vaddr, u32 value, u32) override {
        return WriteMemory<u32>(vaddr, value);
    }
    bool MemoryWriteExclusive64(u64 vaddr, u64 value, u64) override {
        return WriteMemory<u64>(vaddr, value);
    }
    bool MemoryWriteExclusive128(u64 vaddr, u128 value, u128) override {
        return WriteMemory<u128>(vaddr, value);
    }

    void CallSVC(u32 swi) override;
    void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override;
    void InterpreterFallback(u64 pc, size_t num_instructions) override;

    void AddTicks(u64 ticks) override {}
    u64 GetTicksRemaining() override {
        return std::numeric_limits<u32>::max();
    }
    u64 GetCNTPCT() override {
        return 0;
    }

    template <class T>
    T ReadMemory(u64 vaddr) {
        T ret{};
        if (boost::icl::contains(mapped_ranges, vaddr)) {
            memory.ReadBlock(vaddr, &ret, sizeof(T));
        } else if (vaddr + sizeof(T) > local_memory.size()) {
            LOG_CRITICAL(Service_JIT, "plugin: unmapped read @ 0x{:016x}", vaddr);
        } else {
            std::memcpy(&ret, local_memory.data() + vaddr, sizeof(T));
        }
        return ret;
    }

    template <class T>
    bool WriteMemory(u64 vaddr, const T value) {
        if (boost::icl::contains(mapped_ranges, vaddr)) {
            memory.WriteBlock(vaddr, &value, sizeof(T));
        } else if (vaddr + sizeof(T) > local_memory.size()) {
            LOG_CRITICAL(Service_JIT, "plugin: unmapped write @ 0x{:016x}", vaddr);
        } else {
            std::memcpy(local_memory.data() + vaddr, &value, sizeof(T));
        }
        return true;
    }

private:
    Core::Memory::Memory& memory;
    std::vector<u8>& local_memory;
    IntervalSet& mapped_ranges;
    JITContextImpl& parent;
};

class JITContextImpl {
public:
    explicit JITContextImpl(Core::Memory::Memory& memory_) : memory{memory_} {
        callbacks =
            std::make_unique<DynarmicCallbacks64>(memory, local_memory, mapped_ranges, *this);
        user_config.callbacks = callbacks.get();
        jit = std::make_unique<Dynarmic::A64::Jit>(user_config);
    }

    bool LoadNRO(std::span<const u8> data) {
        local_memory.clear();

        relocbase = local_memory.size();
        local_memory.insert(local_memory.end(), data.begin(), data.end());

        if (FixupRelocations()) {
            InsertHelperFunctions();
            InsertStack();
            return true;
        }

        return false;
    }

    bool FixupRelocations() {
        // The loaded NRO file has ELF relocations that must be processed before it can run.
        // Normally this would be processed by RTLD, but in HLE context, we don't have
        // the linker available, so we have to do it ourselves.

        const VAddr mod_offset{callbacks->MemoryRead32(4)};
        if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0')) {
            return false;
        }

        // For more info about dynamic entries, see the ELF ABI specification:
        // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
        // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
        VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
        VAddr rela_dyn = 0, relr_dyn = 0;
        size_t num_rela = 0, num_relr = 0;
        while (true) {
            const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
            dynamic_offset += sizeof(Elf64_Dyn);

            if (!dyn.d_tag) {
                break;
            }
            if (dyn.d_tag == ElfDtRela) {
                rela_dyn = dyn.d_un.d_ptr;
            }
            if (dyn.d_tag == ElfDtRelasz) {
                num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
            }
            if (dyn.d_tag == ElfDtRelr) {
                relr_dyn = dyn.d_un.d_ptr;
            }
            if (dyn.d_tag == ElfDtRelrsz) {
                num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
            }
        }

        for (size_t i = 0; i < num_rela; i++) {
            const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))};
            if (Elf64RelType(rela.r_info) != ElfAArch64Relative) {
                continue;
            }
            const VAddr contents{callbacks->MemoryRead64(rela.r_offset)};
            callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
        }

        VAddr relr_where = 0;
        for (size_t i = 0; i < num_relr; i++) {
            const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
            const auto incr{[&](VAddr where) {
                callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
            }};

            if ((relr & 1) == 0) {
                // where pointer
                relr_where = relocbase + relr;
                incr(relr_where);
                relr_where += sizeof(Elf64_Addr);
            } else {
                // bitmap
                for (int bit = 1; bit < 64; bit++) {
                    if ((relr & (1ULL << bit)) != 0) {
                        incr(relr_where + i * sizeof(Elf64_Addr));
                    }
                }
                relr_where += 63 * sizeof(Elf64_Addr);
            }
        }

        return true;
    }

    void InsertHelperFunctions() {
        for (const auto& name : HELPER_FUNCTIONS) {
            helpers[name] = local_memory.size();
            local_memory.insert(local_memory.end(), SVC0_ARM64.begin(), SVC0_ARM64.end());
        }
    }

    void InsertStack() {
        // Allocate enough space to avoid any reasonable risk of
        // overflowing the stack during plugin execution
        const u64 pad_amount{Common::AlignUp(local_memory.size(), STACK_ALIGN) -
                             local_memory.size()};
        local_memory.insert(local_memory.end(), 0x10000 + pad_amount, 0);
        top_of_stack = local_memory.size();
        heap_pointer = top_of_stack;
    }

    void MapProcessMemory(VAddr dest_address, std::size_t size) {
        mapped_ranges.add(IntervalType{dest_address, dest_address + size});
    }

    void PushArgument(const void* data, size_t size) {
        const size_t num_words = Common::DivCeil(size, sizeof(u64));
        const size_t current_pos = argument_stack.size();
        argument_stack.insert(argument_stack.end(), num_words, 0);
        std::memcpy(argument_stack.data() + current_pos, data, size);
    }

    void SetupArguments() {
        // The first 8 integer registers are used for the first 8 integer
        // arguments. Floating-point arguments are not handled at this time.
        //
        // If a function takes more than 8 arguments, then stack space is reserved
        // for the remaining arguments, and the remaining arguments are inserted in
        // ascending memory order, each argument aligned to an 8-byte boundary. The
        // stack pointer must remain aligned to 16 bytes.
        //
        // For more info, see the AArch64 ABI PCS:
        // https://github.com/ARM-software/abi-aa/blob/main/aapcs64/aapcs64.rst

        for (size_t i = 0; i < 8 && i < argument_stack.size(); i++) {
            jit->SetRegister(i, argument_stack[i]);
        }

        if (argument_stack.size() > 8) {
            const VAddr new_sp = Common::AlignDown(
                top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN);
            for (size_t i = 8; i < argument_stack.size(); i++) {
                callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]);
            }
            jit->SetSP(new_sp);
        }

        // Reset the call state for the next invocation
        argument_stack.clear();
        heap_pointer = top_of_stack;
    }

    u64 CallFunction(VAddr func) {
        jit->SetRegister(30, helpers["_stop"]);
        jit->SetSP(top_of_stack);
        SetupArguments();

        jit->SetPC(func);
        jit->Run();
        return jit->GetRegister(0);
    }

    VAddr GetHelper(const std::string& name) {
        return helpers[name];
    }

    VAddr AddHeap(const void* data, size_t size) {
        // Require all heap data types to have the same alignment as the
        // stack pointer, for compatibility
        const size_t num_bytes{Common::AlignUp(size, STACK_ALIGN)};

        // Make additional memory space if required
        if (heap_pointer + num_bytes > local_memory.size()) {
            local_memory.insert(local_memory.end(),
                                (heap_pointer + num_bytes) - local_memory.size(), 0);
        }

        const VAddr location{heap_pointer};
        std::memcpy(local_memory.data() + location, data, size);
        heap_pointer += num_bytes;
        return location;
    }

    void GetHeap(VAddr location, void* data, size_t size) {
        std::memcpy(data, local_memory.data() + location, size);
    }

    std::unique_ptr<DynarmicCallbacks64> callbacks;
    std::vector<u8> local_memory;
    std::vector<u64> argument_stack;
    IntervalSet mapped_ranges;
    Dynarmic::A64::UserConfig user_config;
    std::unique_ptr<Dynarmic::A64::Jit> jit;
    std::map<std::string, VAddr, std::less<>> helpers;
    Core::Memory::Memory& memory;
    VAddr top_of_stack;
    VAddr heap_pointer;
    VAddr relocbase;
};

void DynarmicCallbacks64::CallSVC(u32 swi) {
    // Service calls are used to implement helper functionality.
    //
    // The most important of these is the _stop helper, which transfers control
    // from the plugin back to HLE context to return a value. However, a few more
    // are also implemented to reduce the need for direct ARM implementations of
    // basic functionality, like memory operations.
    //
    // When we receive a helper request, the swi number will be zero, and the call
    // will have originated from an address we know is a helper function. Otherwise,
    // the plugin may be trying to issue a service call, which we shouldn't handle.

    if (swi != 0) {
        LOG_CRITICAL(Service_JIT, "plugin issued unknown service call {}", swi);
        parent.jit->HaltExecution();
        return;
    }

    u64 pc{parent.jit->GetPC() - 4};
    auto& helpers{parent.helpers};

    if (pc == helpers["memcpy"] || pc == helpers["memmove"]) {
        const VAddr dest{parent.jit->GetRegister(0)};
        const VAddr src{parent.jit->GetRegister(1)};
        const size_t n{parent.jit->GetRegister(2)};

        if (dest < src) {
            for (size_t i = 0; i < n; i++) {
                MemoryWrite8(dest + i, MemoryRead8(src + i));
            }
        } else {
            for (size_t i = n; i > 0; i--) {
                MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1));
            }
        }
    } else if (pc == helpers["memset"]) {
        const VAddr dest{parent.jit->GetRegister(0)};
        const u64 c{parent.jit->GetRegister(1)};
        const size_t n{parent.jit->GetRegister(2)};

        for (size_t i = 0; i < n; i++) {
            MemoryWrite8(dest + i, static_cast<u8>(c));
        }
    } else if (pc == helpers["_resolve"]) {
        // X0 contains a char* for a symbol to resolve
        const auto name{MemoryReadCString(parent.jit->GetRegister(0))};
        const auto helper{helpers[name]};

        if (helper != 0) {
            parent.jit->SetRegister(0, helper);
        } else {
            LOG_WARNING(Service_JIT, "plugin requested unknown function {}", name);
            parent.jit->SetRegister(0, helpers["_panic"]);
        }
    } else if (pc == helpers["_stop"]) {
        parent.jit->HaltExecution();
    } else if (pc == helpers["_panic"]) {
        LOG_CRITICAL(Service_JIT, "plugin panicked!");
        parent.jit->HaltExecution();
    } else {
        LOG_CRITICAL(Service_JIT, "plugin issued syscall at unknown address 0x{:x}", pc);
        parent.jit->HaltExecution();
    }
}

void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) {
    LOG_CRITICAL(Service_JIT, "Illegal operation PC @ {:08x}", pc);
    parent.jit->HaltExecution();
}

void DynarmicCallbacks64::InterpreterFallback(u64 pc, size_t num_instructions) {
    LOG_CRITICAL(Service_JIT, "Unimplemented instruction PC @ {:08x}", pc);
    parent.jit->HaltExecution();
}

JITContext::JITContext(Core::Memory::Memory& memory)
    : impl{std::make_unique<JITContextImpl>(memory)} {}

JITContext::~JITContext() {}

bool JITContext::LoadNRO(std::span<const u8> data) {
    return impl->LoadNRO(data);
}

void JITContext::MapProcessMemory(VAddr dest_address, std::size_t size) {
    impl->MapProcessMemory(dest_address, size);
}

u64 JITContext::CallFunction(VAddr func) {
    return impl->CallFunction(func);
}

void JITContext::PushArgument(const void* data, size_t size) {
    impl->PushArgument(data, size);
}

VAddr JITContext::GetHelper(const std::string& name) {
    return impl->GetHelper(name);
}

VAddr JITContext::AddHeap(const void* data, size_t size) {
    return impl->AddHeap(data, size);
}

void JITContext::GetHeap(VAddr location, void* data, size_t size) {
    impl->GetHeap(location, data, size);
}

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <span>
#include <string>

#include "common/common_types.h"

namespace Core::Memory {
class Memory;
}

namespace Service::JIT {

class JITContextImpl;

class JITContext {
public:
    explicit JITContext(Core::Memory::Memory& memory);
    ~JITContext();

    [[nodiscard]] bool LoadNRO(std::span<const u8> data);
    void MapProcessMemory(VAddr dest_address, std::size_t size);

    template <typename T, typename... Ts>
    u64 CallFunction(VAddr func, T argument, Ts... rest) {
        static_assert(std::is_trivially_copyable_v<T>);
        static_assert(!std::is_floating_point_v<T>);
        PushArgument(&argument, sizeof(argument));

        if constexpr (sizeof...(rest) > 0) {
            return CallFunction(func, rest...);
        } else {
            return CallFunction(func);
        }
    }

    u64 CallFunction(VAddr func);
    VAddr GetHelper(const std::string& name);

    template <typename T>
    VAddr AddHeap(T argument) {
        return AddHeap(&argument, sizeof(argument));
    }
    VAddr AddHeap(const void* data, size_t size);

    template <typename T>
    T GetHeap(VAddr location) {
        static_assert(std::is_trivially_copyable_v<T>);
        T result;
        GetHeap(location, &result, sizeof(result));
        return result;
    }
    void GetHeap(VAddr location, void* data, size_t size);

private:
    std::unique_ptr<JITContextImpl> impl;

    void PushArgument(const void* data, size_t size);
};

} // namespace Service::JIT


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/service/kernel_helpers.h"

namespace Service::KernelHelpers {

ServiceContext::ServiceContext(Core::System& system_, std::string name_)
    : kernel(system_.Kernel()) {
    if (process = Kernel::GetCurrentProcessPointer(kernel); process != nullptr) {
        return;
    }

    // Create the process.
    process = Kernel::KProcess::Create(kernel);
    ASSERT(R_SUCCEEDED(process->Initialize(Kernel::Svc::CreateProcessParameter{},
                                           kernel.GetSystemResourceLimit(), false)));

    // Register the process.
    Kernel::KProcess::Register(kernel, process);
    process_created = true;
}

ServiceContext::~ServiceContext() {
    if (process_created) {
        process->Close();
        process = nullptr;
    }
}

Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
    // Reserve a new event from the process resource limit
    Kernel::KScopedResourceReservation event_reservation(process,
                                                         Kernel::LimitableResource::EventCountMax);
    if (!event_reservation.Succeeded()) {
        LOG_CRITICAL(Service, "Resource limit reached!");
        return {};
    }

    // Create a new event.
    auto* event = Kernel::KEvent::Create(kernel);
    if (!event) {
        LOG_CRITICAL(Service, "Unable to create event!");
        return {};
    }

    // Initialize the event.
    event->Initialize(process);

    // Commit the thread reservation.
    event_reservation.Commit();

    // Register the event.
    Kernel::KEvent::Register(kernel, event);

    return event;
}

void ServiceContext::CloseEvent(Kernel::KEvent* event) {
    if (!event) {
        return;
    }
    event->GetReadableEvent().Close();
    event->Close();
}

} // namespace Service::KernelHelpers


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <string>

namespace Core {
class System;
}

namespace Kernel {
class KernelCore;
class KEvent;
class KProcess;
} // namespace Kernel

namespace Service::KernelHelpers {

class ServiceContext {
public:
    ServiceContext(Core::System& system_, std::string name_);
    ~ServiceContext();

    Kernel::KEvent* CreateEvent(std::string&& name);

    void CloseEvent(Kernel::KEvent* event);

private:
    Kernel::KernelCore& kernel;
    Kernel::KProcess* process{};
    bool process_created{false};
};

} // namespace Service::KernelHelpers