#pragma once #include "framework/meta_tools.hpp" #include "platform/ipc.hpp" #include "os.hpp" #include "bit_field.h" #include namespace HLE { namespace OS { class Thread; struct Handle; using Result = uint32_t; } namespace IPC { using namespace Platform::IPC; /// Dummy structure used for IPC command tags that don't need any actual data struct EmptyValue { }; struct MappedBuffer { /*VAddr*/uint32_t addr; uint32_t size; uint32_t EndAddr() const { return addr + size; } }; struct StaticBuffer { uint32_t addr; uint32_t size; uint32_t id; }; template struct NormalParamToCommandTagHelper { using type = CommandTags::serialized_tag; }; template<> struct NormalParamToCommandTagHelper { using type = CommandTags::uint32_tag; }; template<> struct NormalParamToCommandTagHelper { using type = CommandTags::uint64_tag; }; template using NormalParamToCommandTag = typename NormalParamToCommandTagHelper::type; template auto DoDeserialize(F& f, std::index_sequence) { return Meta::CallWithSequentialEvaluation()(f(NormalParamToCommandTag>{})...))> { detail::GetDeserializer(), f(NormalParamToCommandTag>{})... }.GetResult(); } /// Utility class to parse IPC messages from TLS class TLSReader { OS::Thread& thread; // Skip header uint32_t offset = 0x84; OS::Handle ParseHandle(); template using HandleReturnType = std::conditional_t>; public: TLSReader(OS::Thread& thread); TLSReader(const TLSReader&) = delete; TLSReader(TLSReader&&) = default; OS::Result operator()(const IPC::CommandTags::result_tag& /* unused */); uint32_t operator()(const IPC::CommandTags::uint32_tag& /* unused */); uint64_t operator()(const IPC::CommandTags::uint64_tag& /* unused */); StaticBuffer operator()(const IPC::CommandTags::static_buffer_tag& /* unused */); StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag& /* unused */); StaticBuffer operator()(const IPC::CommandTags::pxi_buffer_tag& /* unused */); /*ProcessId*/uint32_t operator()(const IPC::CommandTags::process_id_tag& /* unused */); MappedBuffer operator()(const IPC::CommandTags::map_buffer_r_tag& /* unused */); MappedBuffer operator()(const IPC::CommandTags::map_buffer_w_tag& /* unused */); template T operator()(const IPC::CommandTags::serialized_tag& /* unused */) { using arg_list = typename Meta::function_traits())>::args; return DoDeserialize(*this, std::make_index_sequence::value>{}); } template HandleReturnType operator()(const IPC::CommandTags::handle_close_tag& /* unused */) { return (*this)(IPC::CommandTags::handle_tag { }); } template HandleReturnType operator()(const IPC::CommandTags::handle_tag& /* unused */) { offset += 4; // Move past descriptor if constexpr (Num == 1) { return ParseHandle(); } else { HandleReturnType ret; for (auto& handle : ret) { handle = ParseHandle(); } return ret; } } }; template inline void DoWrite(F& f, const Data& data, std::index_sequence) { (f(NormalParamToCommandTag>{}, std::get(data)), ...); } /// Utility class to construct IPC messages class TLSWriter { OS::Thread& thread; // Initial offset (response header omitted) uint32_t offset = 0x84; void WriteHandleDescriptor(const OS::Handle* data, size_t num, bool close); public: TLSWriter(OS::Thread& thread); TLSWriter(const TLSWriter& tls) = delete; TLSWriter(TLSWriter&& tls) = default; void operator()(IPC::CommandTags::result_tag, OS::Result data); void operator()(IPC::CommandTags::uint32_tag, uint32_t data); void operator()(IPC::CommandTags::uint64_tag, uint64_t data); void operator()(IPC::CommandTags::map_buffer_r_tag, const MappedBuffer& buffer); void operator()(IPC::CommandTags::map_buffer_w_tag, const MappedBuffer& buffer); void operator()(IPC::CommandTags::static_buffer_tag, const StaticBuffer& buffer); void operator()(IPC::CommandTags::process_id_tag, const EmptyValue&); void operator()(IPC::CommandTags::pxi_buffer_tag, const StaticBuffer& buffer); void operator()(IPC::CommandTags::pxi_buffer_tag, const StaticBuffer& buffer); template void operator()(IPC::CommandTags::handle_close_tag, OS::Handle data) { WriteHandleDescriptor(&data, 1, true); } template void operator()(IPC::CommandTags::handle_tag, OS::Handle data) { WriteHandleDescriptor(&data, 1, false); } template void operator()(IPC::CommandTags::handle_close_tag, const std::array& data) { WriteHandleDescriptor(data.data(), num, true); } template void operator()(IPC::CommandTags::handle_tag, const std::array& data) { WriteHandleDescriptor(data.data(), num, false); } template void operator()(IPC::CommandTags::serialized_tag, T& data) { auto parameters = detail::Serialize(data); constexpr auto tuple_size = std::tuple_size::value; DoWrite(*this, parameters, std::make_index_sequence{}); } }; namespace detail { template struct CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR { static_assert(NoError, "Given integers are not equal!"); }; } /** * Utility structure for asserting that the compile-time constants A and B * are equal. If they aren't, the type Error will be instantiated, which * is supposed to abort compilation due to a static_assert failure. * * The error message can be customized by passing an own Error template, which * is expected to have a static_assert fail if its instantiated with a "true" * argument. This is useful to give helpful error messages instead of cryptic * "5 != 3" messages. * * @todo Move this into a utility header */ template class Error = detail::CHECK_EQUALITY_VERBOSELY_DEFAULT_ERROR> struct CHECK_EQUALITY_VERBOSELY { // Force Error to be instantiated and error out if A!=B // If A==B, use sizeof to form the value "true". static constexpr bool value = sizeof(Error); }; /// Exception class thrown by SendIPCRequest on error struct IPCError { IPCError(uint32_t response_header, OS::Result result) noexcept : header(response_header), result(result) { } ~IPCError() noexcept = default; uint32_t header; OS::Result result; }; template auto TransformTupleSequentiallyHelper(F&& f, Tuple&& tuple, std::index_sequence) { return std::tuple>...> { f(std::get(tuple))... }; } template auto TransformTupleSequentially(F&& f, Tuple&& tuple) { return TransformTupleSequentiallyHelper(std::forward(f), std::forward(tuple), std::make_index_sequence::value>{}); } namespace detail { template struct IPCArgumentMatchError { static_assert(NoError, "Given number of function arguments doesn't match the expected number per the IPC message descriptor"); }; template struct IPCResponseChecker; template struct IPCResponseChecker> { void Check(Parameters...) { // Do nothing, just syntax-check this is a valid function call } }; template struct TagToTypeHelper; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template struct TagToTypeHelper> : boost::mp11::mp_identity {}; template struct TagToTypeHelper> : boost::mp11::mp_identity {}; template struct TagToTypeHelper> : boost::mp11::mp_identity> {}; template struct TagToTypeHelper> : boost::mp11::mp_identity> {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template struct TagToTypeHelper> : boost::mp11::mp_identity {}; template struct TagToTypeHelper> : boost::mp11::mp_identity {}; template<> struct TagToTypeHelper : boost::mp11::mp_identity {}; template using TagToType = typename TagToTypeHelper::type; template struct IPCMessageWriter; template struct IPCMessageWriter< Command, IsResponse, Thread, boost::mp11::mp_list> { Thread& thread; using TypeList = std::conditional_t; template auto operator()(Data... data) { if constexpr (IsResponse) { // Check if the returned value matches the command definition // TODO: Enable this check for requests, too using paramlist = boost::mp11::mp_transform; IPCResponseChecker{}.Check(data...); } thread.WriteTLS(0x80, (IsResponse ? Command::response_header : Command::request_header)); IPC::TLSWriter writer(thread); (writer(ArgTags { }, data), ...); } }; } // namespace detail template void WriteIPCReplyFromTuple(Thread& thread, std::tuple data) { std::apply(detail::IPCMessageWriter { thread }, data); } template struct IPCMessageResultsHelper; template struct IPCMessageResultsHelper, ExtraArgs...> : std::invoke_result< Func, std::invoke_result_t..., ExtraArgs...> { }; template using IPCMessageResults = IPCMessageResultsHelper; template< typename Func, typename... ExtraArgs, typename... ArgTags> auto DispatchIPCMessageHelper(IPC::TLSReader reader, Func&& handler, ExtraArgs&&... args, boost::mp11::mp_list) { // Read request data from TLS (via ArgTags) and invoke request handler // TODO: The following may perform implicit conversion of uint64_t // parameters to uint32_t (or vice versa), hence it actually provides // less compile-time safety that we would like it to, currently. if constexpr (std::is_invocable_v< Func, ExtraArgs..., std::invoke_result_t...>) { // Use CallWithSequentialEvaluation to get well-defined execution order // TODO: We should static assert for handlers returning void, but this somehow doesn't always compile currently // static_assert( !std::is_invocable_r_v...>, // "Handler must have non-void return type"); // std::move is needed to work around a clang bug with class template argument deduction return std::move(Meta::CallWithSequentialEvaluation { std::forward(handler), std::forward(args)..., reader(ArgTags{})... }).GetResult(); } else { // handler is not invocable with the given arguments: // Use a regular function call to get a good compiler diagnostic return std::forward(handler)( std::forward(args)..., reader(ArgTags{})...); } } /** * Read IPC request data from the thread's TLS and forward the decoded * arguments to the given handler. */ template auto DispatchIPCMessage(Func&& handler, Thread& thread, ExtraArgs&&... args) { if (thread.ReadTLS(0x80) != Command::request_header) throw std::runtime_error(fmt::format("Expected command header {:#x}, but got {:#x}", Command::request_header, thread.ReadTLS(0x80))); return DispatchIPCMessageHelper(IPC::TLSReader(thread), std::forward(handler), std::forward(args)..., typename Command::request_list { }); } /** * Read IPC request data from the thread's TLS, forward the decoded * arguments to the given handler, and write results back to TLS */ template void HandleIPCCommand(Func&& handler, Thread& thread, ExtraArgs&&... args) { // Decode request data from TLS and invoke handler auto output_data = DispatchIPCMessage(std::forward(handler), thread, std::forward(args)...); // Write response to TLS // TODO: Do not pass output_data per copy! WriteIPCReplyFromTuple(thread, output_data); } namespace detail { /** * Unwrap small tuples: * For single-element tuples, this returns the contained element. * For zero-element tuples, this returns void. * Otherwise, this just returns the unmodified tuple. */ template inline auto UnwrapTuple(std::tuple&& tuple) { return tuple; } template inline decltype(auto) UnwrapTuple(std::tuple& tuple) { return tuple; } template inline decltype(auto) UnwrapTuple(std::tuple&& tuple) { return std::get<0>(tuple); } template inline decltype(auto) UnwrapTuple(std::tuple& tuple) { return std::get<0>(tuple); } inline void UnwrapTuple(std::tuple<>&&) { } inline void UnwrapTuple(std::tuple<>&) { } } /** * Submit an IPC request from the given thread through the given session * handle. The request parameters are taken from the parameter pack, which is * expected to be compatible with the parameter list inferred from the Command * type. * @return A tuple of return values inferred from the given Command. This does * not include the return code. If only a single value is returned, the * tuple is unwrapped to the value itself. If no value is returned, * this function returns void. * @throws IPCError if sending the IPC request was unsuccessful, if the * response indicates a failure, and if the response header * is different from the one indicated by Command. * @todo Consider changing "t..." to use (universal) reference semantics * @todo Currently, things like the process_id tag require an EmptyValue * dummy argument, despite not actually needing any input data */ template auto SendIPCRequest(Thread& thread, OS::Handle session_handle, T... t) { // Write request data to TLS detail::IPCMessageWriter { thread } (t...); // Send IPC request and wait for response // TODO: The command header returned here shouldn't be read from TLS but // rather constructed on-the-fly! OS::Result result = std::get<0>(thread.CallSVC(&OS::OS::SVCSendSyncRequest, session_handle)); if (result != 0 /* RESULT_OK */) throw IPCError(thread.ReadTLS(0x080), result); // Read result code of the response auto reader = TLSReader(thread); result = reader(CommandTags::result_tag{}); if (result != 0) throw IPCError{thread.ReadTLS(0x80), result}; // TODO: Currently, we build the response header incorrectly and hence // cannot actually perform this important check //assert(thread.ReadTLS(0x80) == Command::response_header); // Read results, but before that drop the result code (if it doesn't // indicate success, we aborted before anyway) using ResponseTypeList = boost::mp11::mp_rename; // TODO: Filter out EmptyValue values! using FixedResponseTypeList = boost::mp11::mp_pop_front; auto result_data = TransformTupleSequentially(reader, FixedResponseTypeList{}); // Return data: Decay to "void" or an unwrapped type if the data tuple is // empty or a singleton, respectively return detail::UnwrapTuple(result_data); } } // namespace IPC } // namespace HLE