/*
* Copyright (c) 2018 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "ipc_out.hpp"
#include "ipc_buffers.hpp"
#include "ipc_special.hpp"
#include "ipc_domain_object.hpp"
#include "ipc_response_context.hpp"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
template
struct PopFront;
template
struct PopFront> {
using type = std::tuple;
};
template struct WhichType;
template
struct TypeList{};
template
constexpr auto Concatenate(TypeList, TypeList) {
return TypeList{};
}
template typename Condition, typename R>
constexpr auto FilterTypes(R result, TypeList<>) {
return result;
}
template typename Condition, typename R, typename T, typename... Ts>
constexpr auto FilterTypes(R result, TypeList) {
if constexpr (Condition{})
return FilterTypes(Concatenate(result, TypeList{}), TypeList{});
else
return FilterTypes(result, TypeList{});
}
template struct TypeListToTuple;
template
struct TypeListToTuple> {
using type = std::tuple;
};
template typename Condition, typename... Types>
using FilteredTypes = typename TypeListToTuple(TypeList<>{}, TypeList{}))>>::type;
enum class ArgType {
InData,
OutData,
InHandle,
OutHandle,
InSession,
OutSession,
PidDesc,
InBuffer,
OutBuffer,
InPointer,
OutPointerClientSize,
OutPointerServerSize,
};
template
constexpr ArgType GetArgType() {
if constexpr (std::is_base_of_v) {
return ArgType::OutData;
} else if constexpr (std::is_base_of_v) {
return ArgType::OutSession;
} else if constexpr (std::is_base_of_v) {
return ArgType::OutHandle;
} else if constexpr (std::is_base_of_v) {
return ArgType::InBuffer;
} else if constexpr (std::is_base_of_v) {
return ArgType::OutBuffer;
} else if constexpr (std::is_base_of_v) {
return ArgType::InPointer;
} else if constexpr (std::is_base_of_v) {
return ArgType::OutPointerClientSize;
} else if constexpr (std::is_base_of_v) {
return ArgType::OutPointerServerSize;
} else if constexpr (std::is_base_of_v) {
return ArgType::PidDesc;
} else if constexpr (std::is_base_of_v) {
return ArgType::InHandle;
} else if constexpr (std::is_trivial_v && !std::is_pointer_v) {
return ArgType::InData;
} else {
static_assert(std::is_pod_v && !std::is_pod_v, "Unhandled InSession!");
return ArgType::InSession;
}
}
template
struct ArgTypeFilter {
template
using type = std::conditional_t() == ArgT, std::true_type, std::false_type>;
};
template
struct IsArgTypeBuffer {
static constexpr bool value = ArgT == ArgType::InBuffer || ArgT == ArgType::OutBuffer || ArgT == ArgType::InPointer || ArgT == ArgType::OutPointerClientSize || ArgT == ArgType::OutPointerServerSize;
};
struct ArgTypeBufferFilter {
template
using type = std::conditional_t()>::value, std::true_type, std::false_type>;
};
template
struct IsArgTypeInData {
static constexpr bool value = ArgT == ArgType::InData || ArgT == ArgType::PidDesc;
};
struct ArgTypeInDataFilter {
template
using type = std::conditional_t()>::value, std::true_type, std::false_type>;
};
template
struct RawDataHelper {
static_assert(GetArgType() == ArgType::InData || GetArgType() == ArgType::PidDesc);
static constexpr size_t align = (GetArgType() == ArgType::InData) ? __alignof__(T) : __alignof__(u64);
static constexpr size_t size = (GetArgType() == ArgType::InData) ? sizeof(T) : sizeof(u64);
};
template
struct RawDataHelper> {
static_assert(GetArgType() == ArgType::InData);
static constexpr size_t align = __alignof(T);
static constexpr size_t size = sizeof(T);
};
template
struct RawDataComputer;
template
struct RawDataComputer> {
/* https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,2604 */
static constexpr void QuickSort(std::array &map, std::array &values, int left, int right) {
do {
int i = left;
int j = right;
int x = map[i + ((j - i) >> 1)];
do {
while (i < static_cast(sizeof...(Ts)) && values[x] > values[map[i]]) i++;
while (j >= 0 && values[x] < values[map[j]]) j--;
if (i > j) break;
if (i < j) {
const size_t temp = map[i];
map[i] = map[j];
map[j] = temp;
}
i++;
j--;
} while (i <= j);
if (j - left <= right - i) {
if (left < j) QuickSort(map, values, left, j);
left = i;
} else {
if (i < right) QuickSort(map, values, i, right);
right = j;
}
} while (left < right);
}
static constexpr void StableSort(std::array &map, std::array &values) {
/* First, quicksort a copy of the map. */
std::array map_unstable(map);
QuickSort(map_unstable, values, 0, sizeof...(Ts)-1);
/* Now, create stable sorted map from unstably quicksorted indices (via repeated insertion sort on element runs). */
for (size_t i = 0; i < sizeof...(Ts); i++) {
map[i] = map_unstable[i];
for (ssize_t j = i-1; j >= 0 && values[map[j]] == values[map[j+1]] && map[j] > map[j+1]; j--) {
const size_t temp = map[j];
map[j] = map[j+1];
map[j+1] = temp;
}
}
}
static constexpr std::array GetOffsets() {
std::array offsets = {};
offsets[0] = 0;
if constexpr (sizeof...(Ts) > 0) {
/* Get size, alignment for each type. */
std::array sizes = { RawDataHelper::size... };
std::array aligns = { RawDataHelper::align... };
/* We want to sort...by alignment. */
std::array map = {};
for (size_t i = 0; i < sizeof...(Ts); i++) { map[i] = i; }
StableSort(map, aligns);
/* Iterate over sorted types. */
size_t cur_offset = 0;
for (size_t i = 0; i < sizeof...(Ts); i++) {
const size_t align = aligns[map[i]];
if (cur_offset % align != 0) {
cur_offset += align - (cur_offset % align);
}
offsets[map[i]] = cur_offset;
cur_offset += sizes[map[i]];
}
offsets[sizeof...(Ts)] = cur_offset;
}
return offsets;
}
static constexpr std::array offsets = GetOffsets();
};
template
struct CommandMetaInfo;
template
struct CommandMetaInfo, _ReturnType> {
using Args = std::tuple<_Args...>;
using ReturnType = _ReturnType;
static constexpr bool ReturnsResult = std::is_same_v;
static constexpr bool ReturnsVoid = std::is_same_v;
using InDatas = FilteredTypes;
using OutDatas = FilteredTypes::type, _Args...>;
using InHandles = FilteredTypes::type, _Args...>;
using OutHandles = FilteredTypes::type, _Args...>;
using InSessions = FilteredTypes::type, _Args...>;
using OutSessions = FilteredTypes::type, _Args...>;
using PidDescs = FilteredTypes::type, _Args...>;
using InBuffers = FilteredTypes::type, _Args...>;
using OutBuffers = FilteredTypes::type, _Args...>;
using InPointers = FilteredTypes::type, _Args...>;
using ClientSizeOutPointers = FilteredTypes::type, _Args...>;
using ServerSizeOutPointers = FilteredTypes::type, _Args...>;
using Buffers = FilteredTypes;
static constexpr size_t NumInDatas = std::tuple_size_v;
static constexpr size_t NumOutDatas = std::tuple_size_v;
static constexpr size_t NumInHandles = std::tuple_size_v;
static constexpr size_t NumOutHandles = std::tuple_size_v;
static constexpr size_t NumInSessions = std::tuple_size_v;
static constexpr size_t NumOutSessions = std::tuple_size_v;
static constexpr size_t NumPidDescs = std::tuple_size_v;
static constexpr size_t NumInBuffers = std::tuple_size_v;
static constexpr size_t NumOutBuffers = std::tuple_size_v;
static constexpr size_t NumInPointers = std::tuple_size_v;
static constexpr size_t NumClientSizeOutPointers = std::tuple_size_v;
static constexpr size_t NumServerSizeOutPointers = std::tuple_size_v;
static constexpr size_t NumBuffers = std::tuple_size_v;
static_assert(NumInSessions == 0, "InSessions not yet supported!");
static_assert(NumPidDescs == 0 || NumPidDescs == 1, "Methods can only take in 0 or 1 PIDs!");
static_assert(NumBuffers <= 8, "Methods can only take in <= 8 Buffers!");
static_assert(NumInHandles <= 8, "Methods can take in <= 8 Handles!");
static_assert(NumOutHandles + NumOutSessions <= 8, "Methods can only return <= 8 Handles+Sessions!");
static constexpr std::array InDataOffsets = RawDataComputer::offsets;
static constexpr size_t InRawArgSize = InDataOffsets[NumInDatas];
static constexpr size_t InRawArgSizeWithOutPointers = ((InRawArgSize + NumClientSizeOutPointers * sizeof(u16)) + 3) & ~3;
static constexpr std::array OutDataOffsets = RawDataComputer::offsets;
static constexpr size_t OutRawArgSize = OutDataOffsets[NumOutDatas];
};
/* ================================================================================= */
/* Actual wrapping implementation goes here. */
/* Validator. */
struct Validator {
template
static constexpr bool ValidateCommandArgument(IpcResponseContext *ctx, size_t& a_index, size_t& b_index, size_t& x_index, size_t& h_index, size_t& cur_c_size_offset, size_t& total_c_size) {
constexpr ArgType argT = GetArgType();
if constexpr (argT == ArgType::InBuffer) {
return (ctx->request.Buffers[a_index] != nullptr || ctx->request.BufferSizes[a_index] == 0) && ctx->request.BufferDirections[a_index] == BufferDirection_Send && ctx->request.BufferTypes[a_index++] == T::expected_type;
} else if constexpr (argT == ArgType::OutBuffer) {
return (ctx->request.Buffers[b_index] != nullptr || ctx->request.BufferSizes[b_index] == 0) && ctx->request.BufferDirections[b_index] == BufferDirection_Recv && ctx->request.BufferTypes[b_index++] == T::expected_type;
} else if constexpr (argT == ArgType::InPointer) {
return ctx->request.Statics[x_index++] != nullptr;
} else if constexpr (argT == ArgType::InHandle) {
if constexpr (std::is_same_v) {
return !ctx->request.WasHandleCopied[h_index++];
} else if constexpr (std::is_same_v) {
return ctx->request.WasHandleCopied[h_index++];
}
} else {
if constexpr (argT == ArgType::OutPointerServerSize) {
total_c_size += T::num_elements * sizeof(T);
} else if constexpr (argT == ArgType::OutPointerServerSize) {
total_c_size += *((u16 *)((uintptr_t)(ctx->request.Raw) + 0x10 + cur_c_size_offset));
cur_c_size_offset += sizeof(u16);
}
return true;
}
}
template
struct ValidateCommandTuple;
template
struct ValidateCommandTuple> {
static constexpr bool IsValid(IpcResponseContext *ctx, size_t& a_index, size_t& b_index, size_t& x_index, size_t& h_index, size_t& cur_c_size_offset, size_t& total_c_size) {
return (ValidateCommandArgument(ctx, a_index, b_index, x_index, h_index, cur_c_size_offset, total_c_size) && ...);
}
};
template
static constexpr Result Validate(IpcResponseContext *ctx) {
if (ctx->request.RawSize < MetaInfo::InRawArgSizeWithOutPointers) {
return 0xF601;
}
if (ctx->request.NumBuffers != MetaInfo::NumInBuffers + MetaInfo::NumOutBuffers) {
return 0xF601;
}
if (ctx->request.NumStatics != MetaInfo::NumInPointers) {
return 0xF601;
}
if (ctx->request.NumStaticsOut != MetaInfo::NumClientSizeOutPointers + MetaInfo::NumServerSizeOutPointers) {
return 0xF601;
}
if (ctx->request.NumHandles != MetaInfo::NumInHandles) {
return 0xF601;
}
if ((ctx->request.HasPid && MetaInfo::NumPidDescs == 0) || (!ctx->request.HasPid && MetaInfo::NumPidDescs != 0)) {
return 0xF601;
}
if (((u32 *)ctx->request.Raw)[0] != SFCI_MAGIC) {
return 0xF601;
}
size_t a_index = 0, b_index = MetaInfo::NumInBuffers, x_index = 0, h_index = 0;
size_t cur_c_size_offset = MetaInfo::InRawArgSize + (0x10 - ((uintptr_t)ctx->request.Raw - (uintptr_t)ctx->request.RawWithoutPadding));
size_t total_c_size = 0;
if (!ValidateCommandTuple::IsValid(ctx, a_index, b_index, x_index, h_index, cur_c_size_offset, total_c_size)) {
return 0xF601;
}
if (total_c_size > ctx->pb_size) {
return 0xF601;
}
return 0;
}
};
/* ================================================================================= */
/* Decoder. */
template
struct Decoder {
template
static constexpr T DecodeCommandArgument(IpcResponseContext *ctx, size_t& a_index, size_t& b_index, size_t& x_index, size_t& c_index, size_t& in_h_index, size_t& out_h_index, size_t& out_obj_index, size_t& in_data_index, size_t& out_data_index, size_t& pb_offset, size_t& c_sz_offset) {
constexpr ArgType argT = GetArgType();
if constexpr (argT == ArgType::InBuffer) {
const T& value = T(ctx->request.Buffers[a_index], ctx->request.BufferSizes[a_index], ctx->request.BufferTypes[a_index]);
++a_index;
return value;
} else if constexpr (argT == ArgType::OutBuffer) {
const T& value = T(ctx->request.Buffers[b_index], ctx->request.BufferSizes[b_index], ctx->request.BufferTypes[b_index]);
++b_index;
return value;
} else if constexpr (argT == ArgType::InPointer) {
const T& value = T(ctx->request.Statics[x_index], ctx->request.StaticSizes[x_index]);
++x_index;
return value;
} else if constexpr (argT == ArgType::InHandle) {
return T(ctx->request.Handles[in_h_index++]);
} else if constexpr (argT == ArgType::OutHandle) {
return T(&ctx->out_handles[out_h_index++]);
} else if constexpr (argT == ArgType::PidDesc) {
uintptr_t ptr = ((uintptr_t)ctx->request.Raw + 0x10 + MetaInfo::InDataOffsets[in_data_index++]);
*(u64 *)ptr = ctx->request.Pid;
return T(ctx->request.Pid);
} else if constexpr (argT == ArgType::InData) {
uintptr_t ptr = ((uintptr_t)ctx->request.Raw + 0x10 + MetaInfo::InDataOffsets[in_data_index++]);
if constexpr (std::is_same_v) {
return *((u8 *)ptr) & 1;
} else {
return *((T *)ptr);
}
} else if constexpr (argT == ArgType::OutData) {
uintptr_t ptr = ((uintptr_t)ctx->out_data + MetaInfo::OutDataOffsets[out_data_index++]);
return T(reinterpret_cast::type *>(ptr));
} else if constexpr (argT == ArgType::OutPointerClientSize || argT == ArgType::OutPointerServerSize) {
u16 sz;
if constexpr(argT == ArgType::OutPointerServerSize) {
sz = T::element_size;
} else {
sz = *(const u16 *)((uintptr_t)ctx->request.Raw + 0x10 + c_sz_offset);
}
u8* buf = ctx->pb + pb_offset;
c_sz_offset += sizeof(u16);
pb_offset += sz;
ipcAddSendStatic(&ctx->reply, buf, sz, c_index++);
return T(buf, sz);
} else if constexpr (argT == ArgType::OutSession) {
if (IsDomainObject(ctx->obj_holder)) {
const T& value = T(ctx->out_objs[out_obj_index], ctx->obj_holder->GetServiceObject(), &ctx->out_object_ids[out_obj_index]);
out_obj_index++;
return value;
} else {
const T& value = T(ctx->out_objs[out_obj_index], nullptr, 0);
out_obj_index++;
return value;
}
}
}
template
struct DecodeTuple;
template
struct DecodeTuple> {
static constexpr std::tuple GetArgs(IpcResponseContext *ctx, size_t& a_index, size_t& b_index, size_t& x_index, size_t& c_index, size_t& in_h_index, size_t& out_h_index, size_t& out_obj_index, size_t& in_data_index, size_t& out_data_index, size_t& pb_offset, size_t& c_sz_offset) {
return std::tuple {
DecodeCommandArgument(ctx, a_index, b_index, x_index, c_index, in_h_index, out_h_index, out_obj_index, in_data_index, out_data_index, pb_offset, c_sz_offset)
...
};
}
};
static constexpr typename MetaInfo::Args Decode(IpcResponseContext *ctx) {
size_t a_index = 0, b_index = MetaInfo::NumInBuffers, x_index = 0, c_index = 0, in_h_index = 0, out_h_index = 0, out_obj_index = 0;
size_t in_data_index = 0x0, out_data_index = 0, pb_offset = 0;
size_t c_sz_offset = MetaInfo::InRawArgSize + (0x10 - ((uintptr_t)ctx->request.Raw - (uintptr_t)ctx->request.RawWithoutPadding));
return DecodeTuple::GetArgs(ctx, a_index, b_index, x_index, c_index, in_h_index, out_h_index, out_obj_index, in_data_index, out_data_index, pb_offset, c_sz_offset);
}
};
/* ================================================================================= */
template
static constexpr void EncodeArgument(IpcResponseContext *ctx, size_t&out_obj_index, T& arg) {
constexpr ArgType argT = GetArgType();
if constexpr (argT == ArgType::OutHandle) {
if constexpr (std::is_same_v::type>) {
ipcSendHandleMove(&ctx->reply, arg.GetValue().handle);
} else {
ipcSendHandleCopy(&ctx->reply, arg.GetValue().handle);
}
} else if constexpr (argT == ArgType::OutSession) {
if (IsDomainObject(ctx->obj_holder)) {
auto domain = ctx->obj_holder->GetServiceObject();
domain->SetObject(arg.GetObjectId(), std::move(arg.GetHolder()));
} else {
ctx->manager->AddSession(ctx->out_object_server_handles[out_obj_index++], std::move(arg.GetHolder()));
}
}
}
template
struct Encoder;
template
struct Encoder> {
static constexpr void EncodeFailure(IpcResponseContext *ctx, Result rc) {
memset(armGetTls(), 0, 0x100);
ipcInitialize(&ctx->reply);
struct {
u64 magic;
u64 result;
} *raw;
if (IsDomainObject(ctx->obj_holder)) {
raw = (decltype(raw))ipcPrepareHeaderForDomain(&ctx->reply, sizeof(*raw), 0);
auto resp_header = (DomainResponseHeader *)((uintptr_t)raw - sizeof(DomainResponseHeader));
*resp_header = {0};
} else {
raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw));
}
raw->magic = SFCO_MAGIC;
raw->result = rc;
}
static constexpr void EncodeSuccess(IpcResponseContext *ctx, Args... args) {
size_t out_obj_index = 0;
((EncodeArgument(ctx, out_obj_index, args)), ...);
const bool is_domain = IsDomainObject(ctx->obj_holder);
if (!is_domain) {
for (unsigned int i = 0; i < MetaInfo::NumOutSessions; i++) {
ipcSendHandleMove(&ctx->reply, ctx->out_handles[MetaInfo::NumOutHandles + i].handle);
}
}
struct {
u64 magic;
u64 result;
} *raw;
if (is_domain) {
raw = (decltype(raw))ipcPrepareHeaderForDomain(&ctx->reply, sizeof(*raw) + MetaInfo::OutRawArgSize, 0);
auto resp_header = (DomainResponseHeader *)((uintptr_t)raw - sizeof(DomainResponseHeader));
*resp_header = {0};
resp_header->NumObjectIds = MetaInfo::NumOutSessions;
} else {
raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw)+ MetaInfo::OutRawArgSize);
}
raw->magic = SFCO_MAGIC;
raw->result = 0;
memcpy((void *)((uintptr_t)raw + sizeof(*raw)), ctx->out_data, MetaInfo::OutRawArgSize);
if (is_domain) {
memcpy((void *)((uintptr_t)raw + sizeof(*raw) + MetaInfo::OutRawArgSize), ctx->out_object_ids, sizeof(*ctx->out_object_ids) * MetaInfo::NumOutSessions);
}
}
};
/* ================================================================================= */
template
constexpr Result WrapIpcCommandImpl(IpcResponseContext *ctx) {
using InArgs = typename PopFront>::type;
using OutArgs = typename boost::callable_traits::return_type_t;
using ClassType = typename boost::callable_traits::class_of_t;
using CommandMetaData = CommandMetaInfo;
static_assert(CommandMetaData::ReturnsResult || CommandMetaData::ReturnsVoid, "IpcCommandImpls must return Result or void");
ipcInitialize(&ctx->reply);
memset(ctx->out_data, 0, CommandMetaData::OutRawArgSize);
Result rc = Validator::Validate(ctx);
if (R_FAILED(rc)) {
return 0xAAEE;
}
ClassType *this_ptr = nullptr;
if (IsDomainObject(ctx->obj_holder)) {
this_ptr = ctx->obj_holder->GetServiceObject()->GetObject(ctx->request.InThisObjectId)->GetServiceObject();
} else {
this_ptr = ctx->obj_holder->GetServiceObject();
}
if (this_ptr == nullptr) {
return 0xBBEE;
}
std::shared_ptr out_objects[CommandMetaData::NumOutSessions];
/* Allocate out object IDs. */
size_t num_out_objects;
if (IsDomainObject(ctx->obj_holder)) {
for (num_out_objects = 0; num_out_objects < CommandMetaData::NumOutSessions; num_out_objects++) {
if (R_FAILED((rc = ctx->obj_holder->GetServiceObject()->ReserveObject(&ctx->out_object_ids[num_out_objects])))) {
break;
}
ctx->out_objs[num_out_objects] = &out_objects[num_out_objects];
}
} else {
for (num_out_objects = 0; num_out_objects < CommandMetaData::NumOutSessions; num_out_objects++) {
Handle server_h, client_h;
if (R_FAILED((rc = SessionManagerBase::CreateSessionHandles(&server_h, &client_h)))) {
break;
}
ctx->out_object_server_handles[num_out_objects] = server_h;
ctx->out_handles[CommandMetaData::NumOutHandles + num_out_objects].handle = client_h;
ctx->out_objs[num_out_objects] = &out_objects[num_out_objects];
}
}
ON_SCOPE_EXIT {
/* Clean up objects as necessary. */
if (IsDomainObject(ctx->obj_holder) && R_FAILED(rc)) {
for (unsigned int i = 0; i < num_out_objects; i++) {
ctx->obj_holder->GetServiceObject()->FreeObject(ctx->out_object_ids[i]);
}
} else {
for (unsigned int i = 0; i < num_out_objects; i++) {
svcCloseHandle(ctx->out_object_server_handles[i]);
svcCloseHandle(ctx->out_handles[CommandMetaData::NumOutHandles + i].handle);
}
}
for (unsigned int i = 0; i < num_out_objects; i++) {
ctx->out_objs[i] = nullptr;
}
};
if (R_SUCCEEDED(rc)) {
auto args = Decoder::Decode(ctx);
if constexpr (CommandMetaData::ReturnsResult) {
rc = std::apply( [=](auto&&... args) { return (this_ptr->*IpcCommandImpl)(args...); }, args);
} else {
std::apply( [=](auto&&... args) { (this_ptr->*IpcCommandImpl)(args...); }, args);
}
if (R_SUCCEEDED(rc)) {
std::apply(Encoder::EncodeSuccess, std::tuple_cat(std::make_tuple(ctx), args));
} else {
std::apply(Encoder::EncodeFailure, std::tuple_cat(std::make_tuple(ctx), std::make_tuple(rc)));
}
} else {
std::apply(Encoder::EncodeFailure, std::tuple_cat(std::make_tuple(ctx), std::make_tuple(rc)));
}
return rc;
}
template
inline static constexpr ServiceCommandMeta MakeServiceCommandMeta() {
return {
.fw_low = l,
.fw_high = h,
.cmd_id = c,
.handler = WrapIpcCommandImpl,
};
};
#pragma GCC diagnostic pop