/** * @file service.h * @brief Service wrapper object * @author fincs * @author SciresM * @copyright libnx Authors */ #pragma once #include "hipc.h" #include "cmif.h" /// Service object structure typedef struct Service { Handle session; u32 own_handle; u32 object_id; u16 pointer_buffer_size; } Service; enum { SfBufferAttr_In = BIT(0), SfBufferAttr_Out = BIT(1), SfBufferAttr_HipcMapAlias = BIT(2), SfBufferAttr_HipcPointer = BIT(3), SfBufferAttr_FixedSize = BIT(4), SfBufferAttr_HipcAutoSelect = BIT(5), SfBufferAttr_HipcMapTransferAllowsNonSecure = BIT(6), SfBufferAttr_HipcMapTransferAllowsNonDevice = BIT(7), }; typedef struct SfBufferAttrs { u32 attr0; u32 attr1; u32 attr2; u32 attr3; u32 attr4; u32 attr5; u32 attr6; u32 attr7; } SfBufferAttrs; typedef struct SfBuffer { const void* ptr; size_t size; } SfBuffer; typedef enum SfOutHandleAttr { SfOutHandleAttr_None = 0, SfOutHandleAttr_HipcCopy = 1, SfOutHandleAttr_HipcMove = 2, } SfOutHandleAttr; typedef struct SfOutHandleAttrs { SfOutHandleAttr attr0; SfOutHandleAttr attr1; SfOutHandleAttr attr2; SfOutHandleAttr attr3; SfOutHandleAttr attr4; SfOutHandleAttr attr5; SfOutHandleAttr attr6; SfOutHandleAttr attr7; } SfOutHandleAttrs; typedef struct SfDispatchParams { Handle target_session; u32 context; SfBufferAttrs buffer_attrs; SfBuffer buffers[8]; bool in_send_pid; u32 in_num_objects; const Service* in_objects[8]; u32 in_num_handles; Handle in_handles[8]; u32 out_num_objects; Service* out_objects; SfOutHandleAttrs out_handle_attrs; Handle* out_handles; } SfDispatchParams; /** * @brief Returns whether a service has been initialized. * @param[in] s Service object. * @return true if initialized. */ NX_CONSTEXPR bool serviceIsActive(Service* s) { return s->session != INVALID_HANDLE; } /** * @brief Returns whether a service is overriden in the homebrew environment. * @param[in] s Service object. * @return true if overriden. */ NX_CONSTEXPR bool serviceIsOverride(Service* s) { return serviceIsActive(s) && !s->own_handle && !s->object_id; } /** * @brief Returns whether a service is a domain. * @param[in] s Service object. * @return true if a domain. */ NX_CONSTEXPR bool serviceIsDomain(Service* s) { return serviceIsActive(s) && s->own_handle && s->object_id; } /** * @brief Returns whether a service is a domain subservice. * @param[in] s Service object. * @return true if a domain subservice. */ NX_CONSTEXPR bool serviceIsDomainSubservice(Service* s) { return serviceIsActive(s) && !s->own_handle && s->object_id; } /** * @brief For a domain/domain subservice, return the associated object ID. * @param[in] s Service object, necessarily a domain or domain subservice. * @return The object ID. */ NX_CONSTEXPR u32 serviceGetObjectId(Service* s) { return s->object_id; } /** * @brief Creates a service object from an IPC session handle. * @param[out] s Service object. * @param[in] h IPC session handle. */ NX_INLINE void serviceCreate(Service* s, Handle h) { s->session = h; s->own_handle = 1; s->object_id = 0; s->pointer_buffer_size = 0; cmifQueryPointerBufferSize(h, &s->pointer_buffer_size); } /** * @brief Creates a domain subservice object from a parent service. * @param[out] s Service object. * @param[in] parent Parent service, necessarily a domain or domain subservice. * @param[in] object_id Object ID for this subservice. */ NX_CONSTEXPR void serviceCreateDomainSubservice(Service* s, Service* parent, u32 object_id) { s->session = parent->session; s->own_handle = 0; s->object_id = object_id; s->pointer_buffer_size = parent->pointer_buffer_size; } /** * @brief Hints the compiler that a service will always contain a domain object. * @param[in] s Service object. */ NX_CONSTEXPR void serviceAssumeDomain(Service* s) { if (!s->object_id) __builtin_unreachable(); } /** * @brief Closes a service. * @param[in] s Service object. */ NX_INLINE void serviceClose(Service* s) { #if defined(NX_SERVICE_ASSUME_NON_DOMAIN) if (s->object_id) __builtin_unreachable(); #endif if (s->own_handle || s->object_id) { cmifMakeCloseRequest(armGetTls(), s->own_handle ? 0 : s->object_id); svcSendSyncRequest(s->session); if (s->own_handle) svcCloseHandle(s->session); } *s = (Service){}; } /** * @brief Clones a service. * @param[in] s Service object. * @param[out] out_s Output service object. */ NX_INLINE Result serviceClone(Service* s, Service* out_s) { #if defined(NX_SERVICE_ASSUME_NON_DOMAIN) if (s->object_id) __builtin_unreachable(); #endif out_s->session = 0; out_s->own_handle = 1; out_s->object_id = s->object_id; out_s->pointer_buffer_size = s->pointer_buffer_size; return cmifCloneCurrentObject(s->session, &out_s->session); } /** * @brief Clones a service with a session manager tag. * @param[in] s Service object. * @param[in] tag Session manager tag (unused in current official server code) * @param[out] out_s Output service object. */ NX_INLINE Result serviceCloneEx(Service* s, u32 tag, Service* out_s) { #if defined(NX_SERVICE_ASSUME_NON_DOMAIN) if (s->object_id) __builtin_unreachable(); #endif out_s->session = 0; out_s->own_handle = 1; out_s->object_id = s->object_id; out_s->pointer_buffer_size = s->pointer_buffer_size; return cmifCloneCurrentObjectEx(s->session, tag, &out_s->session); } /** * @brief Converts a regular service to a domain. * @param[in] s Service object. * @return Result code. */ NX_INLINE Result serviceConvertToDomain(Service* s) { if (!s->own_handle) { // For overridden services, create a clone first. Result rc = cmifCloneCurrentObjectEx(s->session, 0, &s->session); if (R_FAILED(rc)) return rc; s->own_handle = 1; } return cmifConvertCurrentObjectToDomain(s->session, &s->object_id); } NX_CONSTEXPR void _serviceRequestFormatProcessBuffer(CmifRequestFormat* fmt, u32 attr) { if (!attr) return; const bool is_in = (attr & SfBufferAttr_In) != 0; const bool is_out = (attr & SfBufferAttr_Out) != 0; if (attr & SfBufferAttr_HipcAutoSelect) { if (is_in) fmt->num_in_auto_buffers ++; if (is_out) fmt->num_out_auto_buffers ++; } else if (attr & SfBufferAttr_HipcPointer) { if (is_in) fmt->num_in_pointers ++; if (is_out) { if (attr & SfBufferAttr_FixedSize) fmt->num_out_fixed_pointers ++; else fmt->num_out_pointers ++; } } else if (attr & SfBufferAttr_HipcMapAlias) { if (is_in && is_out) fmt->num_inout_buffers ++; else if (is_in) fmt->num_in_buffers ++; else if (is_out) fmt->num_out_buffers ++; } } NX_CONSTEXPR void _serviceRequestProcessBuffer(CmifRequest* req, const SfBuffer* buf, u32 attr) { if (!attr) return; const bool is_in = (attr & SfBufferAttr_In); const bool is_out = (attr & SfBufferAttr_Out); if (attr & SfBufferAttr_HipcAutoSelect) { if (is_in) cmifRequestInAutoBuffer(req, buf->ptr, buf->size); if (is_out) cmifRequestOutAutoBuffer(req, (void*)buf->ptr, buf->size); } else if (attr & SfBufferAttr_HipcPointer) { if (is_in) cmifRequestInPointer(req, buf->ptr, buf->size); if (is_out) { if (attr & SfBufferAttr_FixedSize) cmifRequestOutFixedPointer(req, (void*)buf->ptr, buf->size); else cmifRequestOutPointer(req, (void*)buf->ptr, buf->size); } } else if (attr & SfBufferAttr_HipcMapAlias) { HipcBufferMode mode = HipcBufferMode_Normal; if (attr & SfBufferAttr_HipcMapTransferAllowsNonSecure) mode = HipcBufferMode_NonSecure; if (attr & SfBufferAttr_HipcMapTransferAllowsNonDevice) mode = HipcBufferMode_NonDevice; if (is_in && is_out) cmifRequestInOutBuffer(req, (void*)buf->ptr, buf->size, mode); else if (is_in) cmifRequestInBuffer(req, buf->ptr, buf->size, mode); else if (is_out) cmifRequestOutBuffer(req, (void*)buf->ptr, buf->size, mode); } } NX_INLINE void* serviceMakeRequest( Service* s, u32 request_id, u32 context, u32 data_size, bool send_pid, const SfBufferAttrs buffer_attrs, const SfBuffer* buffers, u32 num_objects, const Service* const* objects, u32 num_handles, const Handle* handles ) { #if defined(NX_SERVICE_ASSUME_NON_DOMAIN) if (s->object_id) __builtin_unreachable(); #endif CmifRequestFormat fmt = {}; fmt.object_id = s->object_id; fmt.request_id = request_id; fmt.context = context; fmt.data_size = data_size; fmt.server_pointer_size = s->pointer_buffer_size; fmt.num_objects = num_objects; fmt.num_handles = num_handles; fmt.send_pid = send_pid; _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr0); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr1); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr2); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr3); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr4); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr5); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr6); _serviceRequestFormatProcessBuffer(&fmt, buffer_attrs.attr7); CmifRequest req = cmifMakeRequest(armGetTls(), fmt); if (s->object_id) // TODO: Check behavior of input objects in non-domain sessions for (u32 i = 0; i < num_objects; i ++) cmifRequestObject(&req, objects[i]->object_id); for (u32 i = 0; i < num_handles; i ++) cmifRequestHandle(&req, handles[i]); _serviceRequestProcessBuffer(&req, &buffers[0], buffer_attrs.attr0); _serviceRequestProcessBuffer(&req, &buffers[1], buffer_attrs.attr1); _serviceRequestProcessBuffer(&req, &buffers[2], buffer_attrs.attr2); _serviceRequestProcessBuffer(&req, &buffers[3], buffer_attrs.attr3); _serviceRequestProcessBuffer(&req, &buffers[4], buffer_attrs.attr4); _serviceRequestProcessBuffer(&req, &buffers[5], buffer_attrs.attr5); _serviceRequestProcessBuffer(&req, &buffers[6], buffer_attrs.attr6); _serviceRequestProcessBuffer(&req, &buffers[7], buffer_attrs.attr7); return req.data; } NX_CONSTEXPR void _serviceResponseGetHandle(CmifResponse* res, SfOutHandleAttr type, Handle* out) { switch (type) { default: case SfOutHandleAttr_None: break; case SfOutHandleAttr_HipcCopy: *out = cmifResponseGetCopyHandle(res); break; case SfOutHandleAttr_HipcMove: *out = cmifResponseGetMoveHandle(res); break; } } NX_INLINE Result serviceParseResponse( Service* s, u32 out_size, void** out_data, u32 num_out_objects, Service* out_objects, const SfOutHandleAttrs out_handle_attrs, Handle* out_handles ) { #if defined(NX_SERVICE_ASSUME_NON_DOMAIN) if (s->object_id) __builtin_unreachable(); #endif CmifResponse res = {}; bool is_domain = s->object_id != 0; Result rc = cmifParseResponse(&res, armGetTls(), is_domain, out_size); if (R_FAILED(rc)) return rc; if (out_size) *out_data = res.data; for (u32 i = 0; i < num_out_objects; i ++) { if (is_domain) serviceCreateDomainSubservice(&out_objects[i], s, cmifResponseGetObject(&res)); else // Output objects are marshalled as move handles at the beginning of the list. serviceCreate(&out_objects[i], cmifResponseGetMoveHandle(&res)); } _serviceResponseGetHandle(&res, out_handle_attrs.attr0, &out_handles[0]); _serviceResponseGetHandle(&res, out_handle_attrs.attr1, &out_handles[1]); _serviceResponseGetHandle(&res, out_handle_attrs.attr2, &out_handles[2]); _serviceResponseGetHandle(&res, out_handle_attrs.attr3, &out_handles[3]); _serviceResponseGetHandle(&res, out_handle_attrs.attr4, &out_handles[4]); _serviceResponseGetHandle(&res, out_handle_attrs.attr5, &out_handles[5]); _serviceResponseGetHandle(&res, out_handle_attrs.attr6, &out_handles[6]); _serviceResponseGetHandle(&res, out_handle_attrs.attr7, &out_handles[7]); return 0; } NX_INLINE Result serviceDispatchImpl( Service* s, u32 request_id, const void* in_data, u32 in_data_size, void* out_data, u32 out_data_size, SfDispatchParams disp ) { // Make a copy of the service struct, so that the compiler can assume that it won't be modified by function calls. Service srv = *s; void* in = serviceMakeRequest(&srv, request_id, disp.context, in_data_size, disp.in_send_pid, disp.buffer_attrs, disp.buffers, disp.in_num_objects, disp.in_objects, disp.in_num_handles, disp.in_handles); if (in_data_size) __builtin_memcpy(in, in_data, in_data_size); Result rc = svcSendSyncRequest(disp.target_session == INVALID_HANDLE ? s->session : disp.target_session); if (R_SUCCEEDED(rc)) { void* out = NULL; rc = serviceParseResponse(&srv, out_data_size, &out, disp.out_num_objects, disp.out_objects, disp.out_handle_attrs, disp.out_handles); if (R_SUCCEEDED(rc) && out_data_size) __builtin_memcpy(out_data, out, out_data_size); } return rc; } #define serviceDispatch(_s,_rid,...) \ serviceDispatchImpl((_s),(_rid),NULL,0,NULL,0,(SfDispatchParams){ __VA_ARGS__ }) #define serviceDispatchIn(_s,_rid,_in,...) \ serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),NULL,0,(SfDispatchParams){ __VA_ARGS__ }) #define serviceDispatchOut(_s,_rid,_out,...) \ serviceDispatchImpl((_s),(_rid),NULL,0,&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ }) #define serviceDispatchInOut(_s,_rid,_in,_out,...) \ serviceDispatchImpl((_s),(_rid),&(_in),sizeof(_in),&(_out),sizeof(_out),(SfDispatchParams){ __VA_ARGS__ })