libstratosphere: refactor everything

This commit is contained in:
Michael Scire 2018-10-29 17:25:36 -07:00 committed by SciresM
parent 5c147e5188
commit 058f735031
41 changed files with 3184 additions and 1013 deletions

View file

@ -16,22 +16,17 @@
#pragma once
#include "stratosphere/ipc_templating.hpp"
#include "stratosphere/iwaitable.hpp"
#include "stratosphere/iserviceobject.hpp"
#include "stratosphere/iserver.hpp"
#include "stratosphere/ipcsession.hpp"
#include "stratosphere/servicesession.hpp"
#include "stratosphere/serviceserver.hpp"
#include "stratosphere/managedportserver.hpp"
#include "stratosphere/existingportserver.hpp"
#include "stratosphere/ievent.hpp"
#include "stratosphere/systemevent.hpp"
#include "stratosphere/hossynch.hpp"
#include "stratosphere/waitablemanager.hpp"
#include "stratosphere/multithreadedwaitablemanager.hpp"
#include "stratosphere/version_check.hpp"
#include "stratosphere/scope_guard.hpp"
#include "stratosphere/hossynch.hpp"
#include "stratosphere/iwaitable.hpp"
#include "stratosphere/event.hpp"
#include "stratosphere/waitable_manager.hpp"
#include "stratosphere/ipc.hpp"
#include "stratosphere/mitm.hpp"
#include "stratosphere/services.hpp"

View file

@ -1,79 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <algorithm>
#include <memory>
#include <type_traits>
#include "iserviceobject.hpp"
#define DOMAIN_ID_MAX 0x1000
class IServiceObject;
class DomainOwner {
private:
std::array<std::shared_ptr<IServiceObject>, DOMAIN_ID_MAX> domain_objects;
public:
/* Shared ptrs should auto delete here. */
virtual ~DomainOwner() = default;
std::shared_ptr<IServiceObject> get_domain_object(unsigned int i) {
if (i < DOMAIN_ID_MAX) {
return domain_objects[i];
}
return nullptr;
}
Result reserve_object(std::shared_ptr<IServiceObject> object, unsigned int *out_i) {
auto object_it = std::find(domain_objects.begin() + 4, domain_objects.end(), nullptr);
if (object_it == domain_objects.end()) {
return 0x1900B;
}
*out_i = std::distance(domain_objects.begin(), object_it);
*object_it = object;
object->set_owner(this);
return 0;
}
Result set_object(std::shared_ptr<IServiceObject> object, unsigned int i) {
if (domain_objects[i] == nullptr) {
domain_objects[i] = object;
object->set_owner(this);
return 0;
}
return 0x1900B;
}
unsigned int get_object_id(std::shared_ptr<IServiceObject> object) {
auto object_it = std::find(domain_objects.begin(), domain_objects.end(), object);
return std::distance(domain_objects.begin(), object_it);
}
void delete_object(unsigned int i) {
domain_objects[i].reset();
}
void delete_object(std::shared_ptr<IServiceObject> object) {
auto object_it = std::find(domain_objects.begin(), domain_objects.end(), object);
if (object_it != domain_objects.end()) {
object_it->reset();
}
}
};

View file

@ -0,0 +1,127 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <algorithm>
#include <vector>
#include "iwaitable.hpp"
class IEvent : public IWaitable {
public:
/* Information members. */
Handle r_h;
Handle w_h;
bool autoclear;
public:
IEvent(bool a = false) : r_h(INVALID_HANDLE), w_h(INVALID_HANDLE), autoclear(a) { }
IEvent(Handle r, bool a = false) : r_h(r), w_h(INVALID_HANDLE), autoclear(a) { }
IEvent(Handle r, Handle w, bool a = false) : r_h(r), w_h(w), autoclear(a) { }
~IEvent() {
if (r_h != INVALID_HANDLE) {
svcCloseHandle(r_h);
}
if (w_h != INVALID_HANDLE) {
svcCloseHandle(w_h);
}
}
/* Make it non-copyable */
IEvent() = delete;
IEvent(const IEvent &) = delete;
IEvent& operator=(const IEvent&) = delete;
bool IsAutoClear() {
return this->autoclear;
}
void Clear() {
std::scoped_lock<HosMutex> lock(this->sig_lock);
this->is_signaled = false;
if (this->r_h != INVALID_HANDLE) {
svcResetSignal(this->r_h);
}
}
void Signal() {
std::scoped_lock<HosMutex> lock(this->sig_lock);
if (this->w_h == INVALID_HANDLE && this->r_h != INVALID_HANDLE) {
/* We can't signal an event if we only have a read handle. */
std::abort();
}
if (this->w_h == INVALID_HANDLE && this->is_signaled) {
return;
}
this->is_signaled = true;
if (this->w_h != INVALID_HANDLE) {
svcSignalEvent(this->w_h);
} else {
this->NotifyManagerSignaled();
}
}
virtual Result HandleSignaled(u64 timeout) = 0;
/* IWaitable */
virtual Handle GetHandle() override {
return this->r_h;
}
};
template<class F>
class HosEvent : public IEvent {
private:
F callback;
public:
HosEvent(F f, bool a = false) : IEvent(a), callback(std::move(f)) { }
HosEvent(Handle r, F f, bool a = false) : IEvent(r, a), callback(std::move(f)) { }
HosEvent(Handle r, Handle w, F f, bool a = false) : IEvent(r, w, a), callback(std::move(f)) { }
virtual Result HandleSignaled(u64 timeout) override {
if (this->IsAutoClear()) {
this->Clear();
}
return this->callback(timeout);
}
};
template <class F>
static IEvent *CreateHosEvent(F f, bool autoclear = false) {
return new HosEvent<F>(INVALID_HANDLE, INVALID_HANDLE, std::move(f), autoclear);
}
template <class F>
static IEvent *CreateSystemEvent(F f, bool autoclear = false) {
Handle w_h, r_h;
if (R_FAILED(svcCreateEvent(&w_h, &r_h))) {
std::abort();
}
return new HosEvent<F>(r_h, w_h, std::move(f), autoclear);
}
template <bool a = false>
static IEvent *CreateWriteOnlySystemEvent() {
return CreateSystemEvent([](u64 timeout) { std::abort(); return 0; }, a);
}

View file

@ -0,0 +1,49 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
enum FirmwareVersion : u32 {
FirmwareVersion_Min = 0,
FirmwareVersion_100 = FirmwareVersion_Min,
FirmwareVersion_200 = 1,
FirmwareVersion_300 = 2,
FirmwareVersion_400 = 3,
FirmwareVersion_500 = 4,
FirmwareVersion_600 = 5,
FirmwareVersion_Current = FirmwareVersion_600,
FirmwareVersion_Max = 32,
};
static inline FirmwareVersion GetRuntimeFirmwareVersion() {
FirmwareVersion fw = FirmwareVersion_Min;
if (kernelAbove200()) {
fw = FirmwareVersion_200;
}
if (kernelAbove300()) {
fw = FirmwareVersion_300;
}
if (kernelAbove400()) {
fw = FirmwareVersion_400;
}
if (kernelAbove500()) {
fw = FirmwareVersion_500;
}
if (kernelAbove600()) {
fw = FirmwareVersion_600;
}
return fw;
}

View file

@ -16,6 +16,7 @@
#pragma once
#include <switch.h>
#include <mutex>
class HosMutex {
private:
@ -40,6 +41,18 @@ class HosMutex {
return mutexTryLock(GetMutex());
}
void Lock() {
lock();
}
void Unlock() {
unlock();
}
bool TryLock() {
return try_lock();
}
friend class HosCondVar;
};
@ -65,6 +78,18 @@ class HosRecursiveMutex {
bool try_lock() {
return rmutexTryLock(GetMutex());
}
void Lock() {
lock();
}
void Unlock() {
unlock();
}
bool TryLock() {
return try_lock();
}
};
class HosCondVar {

View file

@ -1,68 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <algorithm>
#include <vector>
#include "iwaitable.hpp"
typedef Result (*EventCallback)(void *arg, Handle *handles, size_t num_handles, u64 timeout);
class IEvent : public IWaitable {
protected:
std::vector<Handle> handles;
EventCallback callback;
void *arg;
public:
IEvent(Handle wait_h, void *a, EventCallback callback) {
if (wait_h) {
this->handles.push_back(wait_h);
}
this->arg = a;
this->callback = callback;
}
~IEvent() {
std::for_each(handles.begin(), handles.end(), svcCloseHandle);
}
virtual Result signal_event() = 0;
/* IWaitable */
virtual Handle get_handle() {
if (handles.size() > 0) {
return this->handles[0];
}
return 0;
}
virtual void handle_deferred() {
/* TODO: Panic, because we can never defer an event. */
}
virtual Result handle_signaled(u64 timeout) {
return this->callback(this->arg, this->handles.data(), this->handles.size(), timeout);
}
static Result PanicCallback(void *arg, Handle *handles, size_t num_handles, u64 timeout) {
/* TODO: Panic. */
return 0xCAFE;
}
};

View file

@ -0,0 +1,22 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc/ipc_service_object.hpp"
#include "ipc/ipc_serialization.hpp"
#include "ipc/ipc_service_session.hpp"

View file

@ -0,0 +1,117 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <type_traits>
enum class IpcBufferType {
InBuffer,
OutBuffer,
InPointer,
OutPointer,
};
/* Base for In/Out Buffers. */
struct IpcBufferBase {};
struct InOutBufferBase : public IpcBufferBase {};
/* Represents an A descriptor. */
struct InBufferBase : public InOutBufferBase {};
template <typename T, BufferType e_t = BufferType_Normal>
struct InBuffer : public InBufferBase {
T *buffer;
size_t num_elements;
BufferType type;
static const BufferType expected_type = e_t;
/* Convenience. */
T& operator[](size_t i) const {
return buffer[i];
}
InBuffer(void *b, size_t n, BufferType t) : buffer((T *)b), num_elements(n/sizeof(T)), type(t) { }
};
/* Represents a B descriptor. */
struct OutBufferBase : public InOutBufferBase {};
template <typename T, BufferType e_t = BufferType_Normal>
struct OutBuffer : OutBufferBase {
T *buffer;
size_t num_elements;
BufferType type;
static const BufferType expected_type = e_t;
/* Convenience. */
T& operator[](size_t i) const {
return buffer[i];
}
OutBuffer(void *b, size_t n, BufferType t) : buffer((T *)b), num_elements(n/sizeof(T)), type(t) { }
};
/* Represents an X descriptor. */
struct InPointerBase : public IpcBufferBase {};
template <typename T>
struct InPointer : public InPointerBase {
T *pointer;
size_t num_elements;
/* Convenience. */
T& operator[](size_t i) const {
return pointer[i];
}
InPointer(void *p, size_t n) : pointer((T *)p), num_elements(n/sizeof(T)) { }
};
/* Represents a C descriptor. */
struct OutPointerWithServerSizeBase : public IpcBufferBase {};
template <typename T, size_t N>
struct OutPointerWithServerSize : public OutPointerWithServerSizeBase {
T *pointer;
static const size_t num_elements = N;
static const size_t element_size = sizeof(T);
/* Convenience. */
T& operator[](size_t i) const {
return pointer[i];
}
OutPointerWithServerSize(void *p) : pointer((T *)p) { }
OutPointerWithServerSize(void *p, size_t n) : pointer((T *)p) { }
};
struct OutPointerWithClientSizeBase : public IpcBufferBase {};
/* Represents a C descriptor with size in raw data. */
template <typename T>
struct OutPointerWithClientSize : public OutPointerWithClientSizeBase {
T *pointer;
size_t num_elements;
/* Convenience. */
T& operator[](size_t i) const {
return pointer[i];
}
OutPointerWithClientSize(void *p, size_t n) : pointer((T *)p), num_elements(n/sizeof(T)) { }
};

View file

@ -0,0 +1,128 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <algorithm>
#include <memory>
#include <type_traits>
#include "ipc_service_object.hpp"
class IDomainObject;
class DomainManager {
public:
virtual std::shared_ptr<IDomainObject> AllocateDomain() = 0;
virtual void FreeDomain(IDomainObject *domain) = 0;
virtual Result ReserveObject(IDomainObject *domain, u32 *out_object_id) = 0;
virtual Result ReserveSpecificObject(IDomainObject *domain, u32 object_id) = 0;
virtual void SetObject(IDomainObject *domain, u32 object_id, ServiceObjectHolder&& holder) = 0;
virtual ServiceObjectHolder *GetObject(IDomainObject *domain, u32 object_id) = 0;
virtual Result FreeObject(IDomainObject *domain, u32 object_id) = 0;
virtual Result ForceFreeObject(u32 object_id) = 0;
};
class IDomainObject : public IServiceObject {
private:
DomainManager *manager;
public:
IDomainObject(DomainManager *m) : manager(m) {}
virtual ~IDomainObject() override {
this->manager->FreeDomain(this);
}
DomainManager *GetManager() {
return this->manager;
}
ServiceObjectHolder *GetObject(u32 object_id) {
return this->manager->GetObject(this, object_id);
}
Result ReserveObject(u32 *out_object_id) {
return this->manager->ReserveObject(this, out_object_id);
}
Result ReserveSpecificObject(u32 object_id) {
return this->manager->ReserveSpecificObject(this, object_id);
}
void SetObject(u32 object_id, ServiceObjectHolder&& holder) {
this->manager->SetObject(this, object_id, std::move(holder));
}
Result FreeObject(u32 object_id) {
return this->manager->FreeObject(this, object_id);
}
Result ForceFreeObject(u32 object_id) {
return this->manager->ForceFreeObject(object_id);
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
/* IDomainObject has no callable functions. */
};
};
static constexpr bool IsDomainObject(ServiceObjectHolder &holder) {
return holder.GetServiceId() == ServiceObjectId<IDomainObject>();
}
static constexpr bool IsDomainObject(ServiceObjectHolder *holder) {
return holder->GetServiceId() == ServiceObjectId<IDomainObject>();
}
/* Out for service impl. */
template <typename ServiceImpl>
class Out<std::shared_ptr<ServiceImpl>> : public OutSessionTag {
static_assert(std::is_base_of_v<IServiceObject, ServiceImpl>, "OutSessions must be shared_ptr<IServiceObject>!");
template<typename, typename>
friend class Out;
private:
std::shared_ptr<ServiceImpl> *srv;
IDomainObject *domain = nullptr;
u32 *object_id = nullptr;
public:
Out<std::shared_ptr<ServiceImpl>>(std::shared_ptr<IServiceObject> *s, IDomainObject *dm, u32 *o) : srv(reinterpret_cast<std::shared_ptr<ServiceImpl> *>(s)), domain(dm), object_id(o) { }
ServiceObjectHolder GetHolder() {
std::shared_ptr<ServiceImpl> clone = *srv;
return ServiceObjectHolder(std::move(clone));
}
bool IsDomain() {
return domain != nullptr;
}
u32 GetObjectId() {
return *object_id;
}
void ChangeObjectId(u32 o) {
domain->ForceFreeObject(*object_id);
domain->ReserveSpecificObject(o);
*object_id = o;
}
void SetValue(std::shared_ptr<ServiceImpl> &&s) {
*this->srv = std::move(s);
}
};

View file

@ -0,0 +1,75 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <type_traits>
/* Declare false allowed struct. */
template <typename>
struct AllowedOut : std::false_type {};
struct OutDataTag{};
struct OutHandleTag{};
struct OutSessionTag{};
/* Define out struct, so that we can get errors on enable_if */
template <typename T, typename Allowed = void>
class Out {
static_assert(std::is_pod<T>::value && !std::is_pod<T>::value, "Invalid IPC Out Type!");
};
template <typename T>
class Out<T, typename std::enable_if<std::is_trivial<T>::value || AllowedOut<T>::value>::type> : public OutDataTag {
private:
T *obj;
public:
Out(T *o) : obj(o) { }
void SetValue(const T& t) {
*obj = t;
}
const T& GetValue() {
return *obj;
}
T *GetPointer() {
return obj;
}
/* Convenience operators. */
T& operator*() {
return *obj;
}
T* operator->() {
return obj;
}
};
template <typename T>
class Out<T*> {
static_assert(std::is_pod<T>::value && !std::is_pod<T>::value, "Invalid IPC Out Type (Raw Pointer)!");
};
template <typename T>
struct OutHelper;
template <typename T>
struct OutHelper<Out<T>> {
using type = T;
};

View file

@ -16,18 +16,29 @@
#pragma once
#include <switch.h>
#include "iserver.hpp"
template <typename T>
class ManagedPortServer : public IServer<T> {
public:
ManagedPortServer(const char *service_name, unsigned int max_s, bool s_d = false) : IServer<T>(service_name, max_s, s_d) {
if (R_FAILED(svcManageNamedPort(&this->port_handle, service_name, this->max_sessions))) {
/* TODO: panic */
}
}
#include "ipc_service_object.hpp"
#include "ipc_domain_object.hpp"
ISession<T> *get_new_session(Handle session_h) override {
return new ServiceSession<T>(this, session_h, 0);
}
#include "ipc_special.hpp"
#include "ipc_session_manager_base.hpp"
struct IpcResponseContext {
/* Request/Reply data. */
IpcParsedCommand request;
IpcCommand reply;
u8 out_data[0x100];
std::shared_ptr<IServiceObject> *out_objs[8];
Handle out_object_server_handles[8];
IpcHandle out_handles[8];
u32 out_object_ids[8];
IpcCommandType cmd_type;
u64 cmd_id;
Result rc;
/* Context. */
SessionManagerBase *manager;
ServiceObjectHolder *obj_holder;
unsigned char *pb;
size_t pb_size;
};

View file

@ -0,0 +1,629 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <cstdlib>
#include <cstring>
#include <tuple>
#include "../../boost/callable_traits.hpp"
#include <type_traits>
#include <memory>
#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<typename Tuple>
struct PopFront;
template<typename Head, typename... Tail>
struct PopFront<std::tuple<Head, Tail...>> {
using type = std::tuple<Tail...>;
};
template <typename ...> struct WhichType;
template <typename...>
struct TypeList{};
template <typename... T1s, typename... T2s>
constexpr auto Concatenate(TypeList<T1s...>, TypeList<T2s...>) {
return TypeList<T1s..., T2s...>{};
}
template <template <typename> typename Condition, typename R>
constexpr auto FilterTypes(R result, TypeList<>) {
return result;
}
template <template <typename> typename Condition, typename R, typename T, typename... Ts>
constexpr auto FilterTypes(R result, TypeList<T, Ts...>) {
if constexpr (Condition<T>{})
return FilterTypes<Condition>(Concatenate(result, TypeList<T>{}), TypeList<Ts...>{});
else
return FilterTypes<Condition>(result, TypeList<Ts...>{});
}
template<typename Types> struct TypeListToTuple;
template<typename... Types>
struct TypeListToTuple<TypeList<Types...>> {
using type = std::tuple<Types...>;
};
template <template <typename> typename Condition, typename... Types>
using FilteredTypes = typename TypeListToTuple<std::decay_t<decltype(FilterTypes<Condition>(TypeList<>{}, TypeList<Types...>{}))>>::type;
enum class ArgType {
InData,
OutData,
InHandle,
OutHandle,
InSession,
OutSession,
PidDesc,
InBuffer,
OutBuffer,
InPointer,
OutPointerClientSize,
OutPointerServerSize,
};
template<typename X>
constexpr ArgType GetArgType() {
if constexpr (std::is_base_of_v<OutDataTag, X>) {
return ArgType::OutData;
} else if constexpr (std::is_base_of_v<OutSessionTag, X>) {
return ArgType::OutSession;
} else if constexpr (std::is_base_of_v<OutHandleTag, X>) {
return ArgType::OutHandle;
} else if constexpr (std::is_base_of_v<InBufferBase, X>) {
return ArgType::InBuffer;
} else if constexpr (std::is_base_of_v<OutBufferBase, X>) {
return ArgType::OutBuffer;
} else if constexpr (std::is_base_of_v<InPointerBase, X>) {
return ArgType::InPointer;
} else if constexpr (std::is_base_of_v<OutPointerWithClientSizeBase, X>) {
return ArgType::OutPointerClientSize;
} else if constexpr (std::is_base_of_v<OutPointerWithServerSizeBase, X>) {
return ArgType::OutPointerServerSize;
} else if constexpr (std::is_base_of_v<PidDescriptorTag, X>) {
return ArgType::PidDesc;
} else if constexpr (std::is_base_of_v<IpcHandleTag, X>) {
return ArgType::InHandle;
} else if constexpr (std::is_trivial_v<X> && !std::is_pointer_v<X>) {
return ArgType::InData;
} else {
static_assert(std::is_pod_v<X> && !std::is_pod_v<X>, "Unhandled InSession!");
return ArgType::InSession;
}
}
template<ArgType ArgT>
struct ArgTypeFilter {
template<typename X>
using type = std::conditional_t<GetArgType<X>() == ArgT, std::true_type, std::false_type>;
};
template<ArgType ArgT>
struct IsArgTypeBuffer {
static constexpr bool value = ArgT == ArgType::InBuffer || ArgT == ArgType::OutBuffer || ArgT == ArgType::InPointer || ArgT == ArgType::OutPointerClientSize || ArgT == ArgType::OutPointerServerSize;
};
struct ArgTypeBufferFilter {
template<typename X>
using type = std::conditional_t<IsArgTypeBuffer<GetArgType<X>()>::value, std::true_type, std::false_type>;
};
template<ArgType ArgT>
struct IsArgTypeInData {
static constexpr bool value = ArgT == ArgType::InData || ArgT == ArgType::PidDesc;
};
struct ArgTypeInDataFilter {
template<typename X>
using type = std::conditional_t<IsArgTypeInData<GetArgType<X>()>::value, std::true_type, std::false_type>;
};
template<typename T>
struct RawDataHelper {
static_assert(GetArgType<T>() == ArgType::InData || GetArgType<T>() == ArgType::PidDesc);
static constexpr size_t align = (GetArgType<T>() == ArgType::InData) ? __alignof__(T) : __alignof__(u64);
static constexpr size_t size = (GetArgType<T>() == ArgType::InData) ? sizeof(T) : sizeof(u64);
};
template<typename T>
struct RawDataHelper<Out<T>> {
static_assert(GetArgType<T>() == ArgType::InData);
static constexpr size_t align = __alignof(T);
static constexpr size_t size = sizeof(T);
};
template<typename T>
struct RawSizeElementAdder {
static constexpr size_t GetUpdateElementSize(size_t &size) {
constexpr size_t t_align = RawDataHelper<T>::align;
constexpr size_t t_size = RawDataHelper<T>::size;
if (size % t_align == 0) {
size += t_align - (size % t_align);
}
size += t_size;
return size;
}
};
template<typename Ts>
struct GetRawDataSize;
template<typename... Ts>
struct GetRawDataSize<std::tuple<Ts...>> {
static constexpr size_t Size() {
if constexpr (sizeof...(Ts) == 0) {
return 0;
} else {
size_t s = 0;
size_t ends[] = { RawSizeElementAdder<Ts>::GetUpdateElementSize(s)... };
return (ends[sizeof...(Ts)-1] + 3) & ~3;
}
}
};
template <typename _Args, typename _ReturnType>
struct CommandMetaInfo;
template<typename... _Args, typename _ReturnType>
struct CommandMetaInfo<std::tuple<_Args...>, _ReturnType> {
using Args = std::tuple<_Args...>;
using ReturnType = _ReturnType;
static constexpr bool ReturnsResult = std::is_same_v<ReturnType, Result>;
static constexpr bool ReturnsVoid = std::is_same_v<ReturnType, void>;
using InDatas = FilteredTypes<ArgTypeInDataFilter::type, _Args...>;
using OutDatas = FilteredTypes<ArgTypeFilter<ArgType::OutData>::type, _Args...>;
using InHandles = FilteredTypes<ArgTypeFilter<ArgType::InHandle>::type, _Args...>;
using OutHandles = FilteredTypes<ArgTypeFilter<ArgType::OutHandle>::type, _Args...>;
using InSessions = FilteredTypes<ArgTypeFilter<ArgType::InSession>::type, _Args...>;
using OutSessions = FilteredTypes<ArgTypeFilter<ArgType::OutSession>::type, _Args...>;
using PidDescs = FilteredTypes<ArgTypeFilter<ArgType::PidDesc>::type, _Args...>;
using InBuffers = FilteredTypes<ArgTypeFilter<ArgType::InBuffer>::type, _Args...>;
using OutBuffers = FilteredTypes<ArgTypeFilter<ArgType::OutBuffer>::type, _Args...>;
using InPointers = FilteredTypes<ArgTypeFilter<ArgType::InPointer>::type, _Args...>;
using ClientSizeOutPointers = FilteredTypes<ArgTypeFilter<ArgType::OutPointerClientSize>::type, _Args...>;
using ServerSizeOutPointers = FilteredTypes<ArgTypeFilter<ArgType::OutPointerServerSize>::type, _Args...>;
using Buffers = FilteredTypes<ArgTypeBufferFilter::type, _Args...>;
static constexpr size_t NumInDatas = std::tuple_size_v<InDatas>;
static constexpr size_t NumOutDatas = std::tuple_size_v<OutDatas>;
static constexpr size_t NumInHandles = std::tuple_size_v<InHandles>;
static constexpr size_t NumOutHandles = std::tuple_size_v<OutHandles>;
static constexpr size_t NumInSessions = std::tuple_size_v<InSessions>;
static constexpr size_t NumOutSessions = std::tuple_size_v<OutSessions>;
static constexpr size_t NumPidDescs = std::tuple_size_v<PidDescs>;
static constexpr size_t NumInBuffers = std::tuple_size_v<InBuffers>;
static constexpr size_t NumOutBuffers = std::tuple_size_v<OutBuffers>;
static constexpr size_t NumInPointers = std::tuple_size_v<InPointers>;
static constexpr size_t NumClientSizeOutPointers = std::tuple_size_v<ClientSizeOutPointers>;
static constexpr size_t NumServerSizeOutPointers = std::tuple_size_v<ServerSizeOutPointers>;
static constexpr size_t NumBuffers = std::tuple_size_v<Buffers>;
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 size_t InRawArgSize = GetRawDataSize<InDatas>::Size();
static constexpr size_t InRawArgSizeWithOutPointers = ((InRawArgSize + NumClientSizeOutPointers * sizeof(u16)) + 3) & ~3;
static constexpr size_t OutRawArgSize = GetRawDataSize<OutDatas>::Size();
};
/* ================================================================================= */
/* Actual wrapping implementation goes here. */
/* Validator. */
struct Validator {
template <typename T>
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<T>();
if constexpr (argT == ArgType::InBuffer) {
return ctx->request.Buffers[a_index] != nullptr && 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.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<T, MovedHandle>) {
return !ctx->request.WasHandleCopied[h_index++];
} else if constexpr (std::is_same_v<T, CopiedHandle>) {
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 <typename Ts>
struct ValidateCommandTuple;
template <typename ...Ts>
struct ValidateCommandTuple<std::tuple<Ts...>> {
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<Ts>(ctx, a_index, b_index, x_index, h_index, cur_c_size_offset, total_c_size) && ...);
}
};
template<typename MetaInfo>
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<typename MetaInfo::Args>::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. */
struct Decoder {
template<typename T>
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_rd_offset, size_t& out_rd_offset, size_t& pb_offset, size_t& c_sz_offset) {
constexpr ArgType argT = GetArgType<T>();
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) {
constexpr size_t t_align = RawDataHelper<T>::align;
constexpr size_t t_size = RawDataHelper<T>::size;
if (in_rd_offset % t_align) {
in_rd_offset += (t_align - (in_rd_offset % t_align));
}
uintptr_t ptr = ((uintptr_t)ctx->request.Raw + 0x10 + in_rd_offset);
in_rd_offset += t_size;
*(u64 *)ptr = ctx->request.Pid;
return T(ctx->request.Pid);
} else if constexpr (argT == ArgType::InData) {
constexpr size_t t_align = RawDataHelper<T>::align;
constexpr size_t t_size = RawDataHelper<T>::size;
if (in_rd_offset % t_align) {
in_rd_offset += (t_align - (in_rd_offset % t_align));
}
uintptr_t ptr = ((uintptr_t)ctx->request.Raw + 0x10 + in_rd_offset);
in_rd_offset += t_size;
if constexpr (std::is_same_v<bool, T>) {
return *((u8 *)ptr) & 1;
} else {
return *((T *)ptr);
}
} else if constexpr (argT == ArgType::OutData) {
constexpr size_t t_align = RawDataHelper<T>::align;
constexpr size_t t_size = RawDataHelper<T>::size;
if (out_rd_offset % t_align) {
out_rd_offset += (t_align - (out_rd_offset % t_align));
}
uintptr_t ptr = ((uintptr_t)ctx->out_data + out_rd_offset);
out_rd_offset += t_size;
return T(reinterpret_cast<typename OutHelper<T>::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<IDomainObject>(), &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 <typename Ts>
struct DecodeTuple;
template <typename ...Ts>
struct DecodeTuple<std::tuple<Ts...>> {
static constexpr std::tuple<Ts...> 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_rd_offset, size_t& out_rd_offset, size_t& pb_offset, size_t& c_sz_offset) {
return std::tuple<Ts... > {
DecodeCommandArgument<Ts>(ctx, a_index, b_index, x_index, c_index, in_h_index, out_h_index, out_obj_index, in_rd_offset, out_rd_offset, pb_offset, c_sz_offset)
...
};
}
};
template<typename MetaInfo>
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_rd_offset = 0x0, out_rd_offset = 0, pb_offset = 0;
size_t c_sz_offset = MetaInfo::InRawArgSize + (0x10 - ((uintptr_t)ctx->request.Raw - (uintptr_t)ctx->request.RawWithoutPadding));
return DecodeTuple<typename MetaInfo::Args>::GetArgs(ctx, a_index, b_index, x_index, c_index, in_h_index, out_h_index, out_obj_index, in_rd_offset, out_rd_offset, pb_offset, c_sz_offset);
}
};
/* ================================================================================= */
template<typename MetaInfo, typename T>
static constexpr void EncodeArgument(IpcResponseContext *ctx, size_t&out_obj_index, T& arg) {
constexpr ArgType argT = GetArgType<T>();
if constexpr (argT == ArgType::OutHandle) {
if constexpr (std::is_same_v<MovedHandle, typename OutHelper<T>::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<IDomainObject>();
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<typename MetaInfo, typename ArgsTuple>
struct Encoder;
template <typename MetaInfo, typename ...Args>
struct Encoder<MetaInfo, std::tuple<Args...>> {
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<MetaInfo, Args>(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<auto IpcCommandImpl>
constexpr Result WrapIpcCommandImpl(IpcResponseContext *ctx) {
using InArgs = typename PopFront<typename boost::callable_traits::args_t<decltype(IpcCommandImpl)>>::type;
using OutArgs = typename boost::callable_traits::return_type_t<decltype(IpcCommandImpl)>;
using ClassType = typename boost::callable_traits::class_of_t<decltype(IpcCommandImpl)>;
using CommandMetaData = CommandMetaInfo<InArgs, OutArgs>;
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<CommandMetaData>(ctx);
if (R_FAILED(rc)) {
return 0xAAEE;
}
ClassType *this_ptr = nullptr;
if (IsDomainObject(ctx->obj_holder)) {
this_ptr = ctx->obj_holder->GetServiceObject<IDomainObject>()->GetObject(ctx->request.InThisObjectId)->GetServiceObject<ClassType>();
} else {
this_ptr = ctx->obj_holder->GetServiceObject<ClassType>();
}
if (this_ptr == nullptr) {
return 0xBBEE;
}
std::shared_ptr<IServiceObject> 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<IDomainObject>()->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<IDomainObject>()->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<CommandMetaData>(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<CommandMetaData, decltype(args)>::EncodeSuccess, std::tuple_cat(std::make_tuple(ctx), args));
} else {
std::apply(Encoder<CommandMetaData, decltype(args)>::EncodeFailure, std::tuple_cat(std::make_tuple(ctx), std::make_tuple(rc)));
}
} else {
std::apply(Encoder<CommandMetaData, typename CommandMetaData::Args>::EncodeFailure, std::tuple_cat(std::make_tuple(ctx), std::make_tuple(rc)));
}
return rc;
}
template <u32 c, auto CommandImpl, FirmwareVersion l = FirmwareVersion_Min, FirmwareVersion h = FirmwareVersion_Max>
inline static constexpr ServiceCommandMeta MakeServiceCommandMeta() {
return {
.fw_low = l,
.fw_high = h,
.cmd_id = c,
.handler = WrapIpcCommandImpl<CommandImpl>,
};
};
#pragma GCC diagnostic pop

View file

@ -0,0 +1,129 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <memory>
#include <type_traits>
#include "ipc_out.hpp"
#include "../firmware_version.hpp"
class IpcResponseContext;
struct ServiceCommandMeta {
FirmwareVersion fw_low = FirmwareVersion_Max;
FirmwareVersion fw_high = FirmwareVersion_Max;
u32 cmd_id = 0;
Result (*handler)(IpcResponseContext *) = nullptr;
};
class IServiceObject {
public:
virtual ~IServiceObject() { }
};
#define SERVICE_DISPATCH_TABLE_NAME s_DispatchTable
#define DEFINE_SERVICE_DISPATCH_TABLE static constexpr ServiceCommandMeta SERVICE_DISPATCH_TABLE_NAME[]
template <typename T>
static constexpr size_t DispatchTableEntryCount() {
static_assert(std::is_base_of<IServiceObject, T>::value, "DispatchTable owners must derive from IServiceObject");
return sizeof(T::SERVICE_DISPATCH_TABLE_NAME)/sizeof(ServiceCommandMeta);
}
template <typename T>
static constexpr const ServiceCommandMeta* DispatchTable() {
static_assert(std::is_base_of<IServiceObject, T>::value, "DispatchTable owners must derive from IServiceObject");
return reinterpret_cast<const ServiceCommandMeta*>(&T::SERVICE_DISPATCH_TABLE_NAME);
}
template <typename T>
static constexpr uintptr_t ServiceObjectId() {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
return reinterpret_cast<uintptr_t>(&T::SERVICE_DISPATCH_TABLE_NAME);
}
class ServiceObjectHolder {
private:
std::shared_ptr<IServiceObject> srv;
const ServiceCommandMeta *dispatch_table;
size_t entry_count;
/* Private copy constructor. */
ServiceObjectHolder(const ServiceObjectHolder& other) : srv(other.srv), dispatch_table(other.dispatch_table), entry_count(other.entry_count) { }
ServiceObjectHolder& operator=(const ServiceObjectHolder& other);
public:
/* Templated constructor ensures correct type id at runtime. */
template <typename ServiceImpl>
explicit ServiceObjectHolder(std::shared_ptr<ServiceImpl>&& s) : srv(std::move(s)), dispatch_table(DispatchTable<ServiceImpl>()), entry_count(DispatchTableEntryCount<ServiceImpl>()) { }
template <typename ServiceImpl>
ServiceImpl *GetServiceObject() {
if (GetServiceId() == ServiceObjectId<ServiceImpl>()) {
return static_cast<ServiceImpl *>(this->srv.get());
}
return nullptr;
}
template<typename ServiceImpl>
ServiceImpl *GetServiceObjectUnsafe() {
return static_cast<ServiceImpl *>(this->srv.get());
}
const ServiceCommandMeta *GetDispatchTable() {
return this->dispatch_table;
}
size_t GetDispatchTableEntryCount() {
return this->entry_count;
}
constexpr uintptr_t GetServiceId() {
return reinterpret_cast<uintptr_t>(this->dispatch_table);
}
/* Default constructor, move constructor, move assignment operator. */
ServiceObjectHolder() : srv(nullptr), dispatch_table(nullptr) { }
ServiceObjectHolder(ServiceObjectHolder&& other) : srv(std::move(other.srv)), dispatch_table(std::move(other.dispatch_table)), entry_count(std::move(other.entry_count)) { }
ServiceObjectHolder& operator=(ServiceObjectHolder&& other) {
this->srv = other.srv;
this->dispatch_table = other.dispatch_table;
this->entry_count = other.entry_count;
other.Reset();
return *this;
}
explicit operator bool() {
return this->srv != nullptr;
}
bool operator!() {
return this->srv == nullptr;
}
void Reset() {
this->srv.reset();
this->dispatch_table = nullptr;
this->entry_count = 0;
}
ServiceObjectHolder Clone() {
return ServiceObjectHolder(*this);
}
};

View file

@ -0,0 +1,358 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "../iwaitable.hpp"
#include "ipc_service_object.hpp"
#include "ipc_serialization.hpp"
enum HipcControlCommand : u32 {
HipcControlCommand_ConvertCurrentObjectToDomain = 0,
HipcControlCommand_CopyFromCurrentDomain = 1,
HipcControlCommand_CloneCurrentObject = 2,
HipcControlCommand_QueryPointerBufferSize = 3,
HipcControlCommand_CloneCurrentObjectEx = 4
};
#define RESULT_DEFER_SESSION (0x6580A)
class ServiceSession : public IWaitable
{
protected:
Handle session_handle;
std::vector<unsigned char> pointer_buffer;
ServiceObjectHolder obj_holder;
ServiceObjectHolder control_holder = ServiceObjectHolder(std::make_shared<IHipcControlService>(this));
u8 backup_tls[0x100];
ServiceSession(Handle s_h) : session_handle(s_h) { }
public:
template<typename T>
ServiceSession(Handle s_h, size_t pbs) : session_handle(s_h), pointer_buffer(pbs), obj_holder(std::make_shared<T>()) { }
ServiceSession(Handle s_h, size_t pbs, ServiceObjectHolder &&h) : session_handle(s_h), pointer_buffer(pbs), obj_holder(std::move(h)) { }
virtual ~ServiceSession() override {
svcCloseHandle(this->session_handle);
}
SessionManagerBase *GetSessionManager() {
return static_cast<SessionManagerBase *>(this->GetManager());
}
DomainManager *GetDomainManager() {
return static_cast<DomainManager *>(this->GetSessionManager());
}
Result Receive() {
int handle_index;
/* Prepare pointer buffer... */
IpcCommand c;
ipcInitialize(&c);
if (this->pointer_buffer.size() > 0) {
ipcAddRecvStatic(&c, this->pointer_buffer.data(), this->pointer_buffer.size(), 0);
ipcPrepareHeader(&c, 0);
/* Fix libnx bug in serverside C descriptor handling. */
((u32 *)armGetTls())[1] &= 0xFFFFC3FF;
((u32 *)armGetTls())[1] |= (2) << 10;
} else {
ipcPrepareHeader(&c, 0);
}
/* Receive. */
Result rc = svcReplyAndReceive(&handle_index, &this->session_handle, 1, 0, U64_MAX);
if (R_SUCCEEDED(rc)) {
std::memcpy(this->backup_tls, armGetTls(), sizeof(this->backup_tls));
}
return rc;
}
Result Reply() {
int handle_index;
return svcReplyAndReceive(&handle_index, &this->session_handle, 0, this->session_handle, 0);
}
/* For preparing basic replies. */
Result PrepareBasicResponse(IpcResponseContext *ctx, Result rc) {
ipcInitialize(&ctx->reply);
struct {
u64 magic;
u64 result;
} *raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw));
raw->magic = SFCO_MAGIC;
raw->result = rc;
return raw->result;
}
Result PrepareBasicDomainResponse(IpcResponseContext *ctx, Result rc) {
ipcInitialize(&ctx->reply);
struct {
DomainResponseHeader hdr;
u64 magic;
u64 result;
} *raw = (decltype(raw))ipcPrepareHeader(&ctx->reply, sizeof(*raw));
raw->hdr = (DomainResponseHeader){0};
raw->magic = SFCO_MAGIC;
raw->result = rc;
return raw->result;
}
/* For making a new response context. */
void InitializeResponseContext(IpcResponseContext *ctx) {
std::memset(ctx, 0, sizeof(*ctx));
ctx->manager = this->GetSessionManager();
ctx->obj_holder = &this->obj_holder;
ctx->pb = this->pointer_buffer.data();
ctx->pb_size = this->pointer_buffer.size();
}
/* IWaitable */
virtual Handle GetHandle() {
return this->session_handle;
}
virtual Result GetResponse(IpcResponseContext *ctx) {
Result rc = 0xF601;
FirmwareVersion fw = GetRuntimeFirmwareVersion();
const ServiceCommandMeta *dispatch_table = ctx->obj_holder->GetDispatchTable();
size_t entry_count = ctx->obj_holder->GetDispatchTableEntryCount();
if (IsDomainObject(ctx->obj_holder)) {
switch (ctx->request.InMessageType) {
case DomainMessageType_Invalid:
return 0xF601;
case DomainMessageType_Close:
return PrepareBasicDomainResponse(ctx, ctx->obj_holder->GetServiceObject<IDomainObject>()->FreeObject(ctx->request.InThisObjectId));
case DomainMessageType_SendMessage:
{
auto sub_obj = ctx->obj_holder->GetServiceObject<IDomainObject>()->GetObject(ctx->request.InThisObjectId);
if (sub_obj == nullptr) {
return PrepareBasicDomainResponse(ctx, 0x3D80B);
}
dispatch_table = sub_obj->GetDispatchTable();
entry_count = sub_obj->GetDispatchTableEntryCount();
}
}
}
for (size_t i = 0; i < entry_count; i++) {
if (ctx->cmd_id == dispatch_table[i].cmd_id && dispatch_table[i].fw_low <= fw && fw <= dispatch_table[i].fw_high) {
rc = dispatch_table[i].handler(ctx);
break;
}
}
return rc;
}
virtual Result HandleReceived() {
IpcResponseContext ctx;
this->InitializeResponseContext(&ctx);
ctx.cmd_type = (IpcCommandType)(*(u16 *)(armGetTls()));
ctx.rc = 0;
/* Parse based on command type. */
switch (ctx.cmd_type) {
case IpcCommandType_Invalid:
case IpcCommandType_LegacyRequest:
case IpcCommandType_LegacyControl:
return 0xF601;
case IpcCommandType_Close:
{
/* Clean up gracefully. */
PrepareBasicResponse(&ctx, 0);
this->Reply();
}
return 0xF601;
case IpcCommandType_Control:
case IpcCommandType_ControlWithContext:
ctx.rc = ipcParse(&ctx.request);
ctx.obj_holder = &this->control_holder;
break;
case IpcCommandType_Request:
case IpcCommandType_RequestWithContext:
if (IsDomainObject(&this->obj_holder)) {
ctx.rc = ipcParseDomainRequest(&ctx.request);
} else {
ctx.rc = ipcParse(&ctx.request);
}
break;
default:
return 0xF601;
}
if (R_SUCCEEDED(ctx.rc)) {
ctx.cmd_id = ((u32 *)ctx.request.Raw)[2];
this->PreProcessRequest(&ctx);
ctx.rc = this->GetResponse(&ctx);
}
if (ctx.rc == RESULT_DEFER_SESSION) {
/* Session defer. */
this->SetDeferred(true);
} else if (ctx.rc == 0xF601) {
/* Session close, nothing to do. */
} else {
if (R_SUCCEEDED(ctx.rc)) {
this->PostProcessResponse(&ctx);
}
ctx.rc = this->Reply();
if (ctx.rc == 0xEA01) {
ctx.rc = 0x0;
}
this->CleanupResponse(&ctx);
}
return ctx.rc;
}
virtual void HandleDeferred() override {
memcpy(armGetTls(), this->backup_tls, sizeof(this->backup_tls));
Result rc = this->HandleReceived();
if (rc != RESULT_DEFER_SESSION) {
this->SetDeferred(false);
if (rc == 0xF601) {
svcCloseHandle(this->GetHandle());
}
}
}
virtual Result HandleSignaled(u64 timeout) {
Result rc;
if (R_SUCCEEDED(rc = this->Receive())) {
rc = this->HandleReceived();
}
return rc;
}
virtual void PreProcessRequest(IpcResponseContext *ctx) {
/* ... */
(void)(ctx);
}
virtual void PostProcessResponse(IpcResponseContext *ctx) {
/* ... */
(void)(ctx);
}
virtual void CleanupResponse(IpcResponseContext *ctx) {
std::memset(this->backup_tls, 0, sizeof(this->backup_tls));
}
public:
class IHipcControlService : public IServiceObject {
private:
ServiceSession *session;
public:
explicit IHipcControlService(ServiceSession *s) : session(s) {
}
virtual ~IHipcControlService() override { }
Result ConvertCurrentObjectToDomain(Out<u32> object_id) {
/* Allocate new domain. */
auto new_domain = this->session->GetDomainManager()->AllocateDomain();
if (new_domain == nullptr) {
return 0x1900B;
}
/* Reserve an object in the domain for our session. */
u32 reserved_id;
Result rc = new_domain->ReserveObject(&reserved_id);
if (R_FAILED(rc)) {
return rc;
}
new_domain->SetObject(reserved_id, std::move(this->session->obj_holder));
this->session->obj_holder = std::move(ServiceObjectHolder(std::move(new_domain)));
/* Return the object id. */
object_id.SetValue(reserved_id);
return 0;
}
Result CopyFromCurrentDomain(Out<MovedHandle> out_h, u32 id) {
auto domain = this->session->obj_holder.GetServiceObject<IDomainObject>();
if (domain == nullptr) {
return 0x3D60B;
}
auto object = domain->GetObject(id);
if (object == nullptr) {
return 0x3D80B;
}
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
this->session->GetSessionManager()->AddSession(server_h, std::move(object->Clone()));
out_h.SetValue(client_h);
return 0;
}
void CloneCurrentObject(Out<MovedHandle> out_h) {
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
this->session->GetSessionManager()->AddSession(server_h, std::move(this->session->obj_holder.Clone()));
out_h.SetValue(client_h);
}
void QueryPointerBufferSize(Out<u16> size) {
size.SetValue(this->session->pointer_buffer.size());
}
void CloneCurrentObjectEx(Out<MovedHandle> out_h, u32 which) {
/* TODO: Figure out what this u32 controls. */
return CloneCurrentObject(out_h);
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<HipcControlCommand_ConvertCurrentObjectToDomain, &ServiceSession::IHipcControlService::ConvertCurrentObjectToDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CopyFromCurrentDomain, &ServiceSession::IHipcControlService::CopyFromCurrentDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObject, &ServiceSession::IHipcControlService::CloneCurrentObject>(),
MakeServiceCommandMeta<HipcControlCommand_QueryPointerBufferSize, &ServiceSession::IHipcControlService::QueryPointerBufferSize>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObjectEx, &ServiceSession::IHipcControlService::CloneCurrentObjectEx>(),
};
};
};

View file

@ -15,19 +15,20 @@
*/
#pragma once
#include <switch.h>
#include "iserver.hpp"
#include <atomic>
template <typename T>
class ServiceServer : public IServer<T> {
#include "../waitable_manager_base.hpp"
#include "ipc_service_object.hpp"
class SessionManagerBase : public WaitableManagerBase, public DomainManager {
public:
ServiceServer(const char *service_name, unsigned int max_s, bool s_d = false) : IServer<T>(service_name, max_s, s_d) {
if (R_FAILED(smRegisterService(&this->port_handle, service_name, false, this->max_sessions))) {
/* TODO: Panic. */
}
}
SessionManagerBase() = default;
virtual ~SessionManagerBase() = default;
ISession<T> *get_new_session(Handle session_h) override {
return new ServiceSession<T>(this, session_h, 0);
virtual void AddSession(Handle server_h, ServiceObjectHolder &&service) = 0;
static Result CreateSessionHandles(Handle *server_h, Handle *client_h) {
return svcCreateSession(server_h, client_h, 0, 0);
}
};

View file

@ -0,0 +1,144 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <type_traits>
#include "ipc_out.hpp"
/* Represents an input PID. */
struct PidDescriptorTag{};
struct PidDescriptor : public PidDescriptorTag {
u64 pid;
void operator=(u64 &p) {
pid = p;
}
PidDescriptor(u64 p) : pid(p) { }
};
struct IpcHandleTag{};
struct IpcHandle : public IpcHandleTag {
Handle handle;
};
/* Represents a moved handle. */
struct MovedHandle : public IpcHandle {
void operator=(const Handle &h) {
this->handle = h;
}
void operator=(const IpcHandle &o) {
this->handle = o.handle;
}
MovedHandle(Handle h) {
this->handle = h;
}
};
/* Represents a copied handle. */
struct CopiedHandle : public IpcHandle {
void operator=(const Handle &h) {
handle = h;
}
void operator=(const IpcHandle &o) {
this->handle = o.handle;
}
CopiedHandle(Handle h) {
this->handle = h;
}
};
template <>
class Out<MovedHandle> : public OutHandleTag {
private:
MovedHandle *obj;
public:
Out(IpcHandle *o) : obj(static_cast<MovedHandle *>(o)) { }
void SetValue(const Handle& h) {
*obj = h;
}
void SetValue(const MovedHandle& o) {
*obj = o;
}
const MovedHandle& GetValue() {
return *obj;
}
MovedHandle* GetPointer() {
return obj;
}
Handle* GetHandlePointer() {
return &obj->handle;
}
/* Convenience operators. */
MovedHandle& operator*() {
return *obj;
}
MovedHandle* operator->() {
return obj;
}
};
template <>
class Out<CopiedHandle> : public OutHandleTag {
private:
CopiedHandle *obj;
public:
Out(IpcHandle *o) : obj(static_cast<CopiedHandle *>(o)) { }
void SetValue(const Handle& h) {
*obj = h;
}
void SetValue(const CopiedHandle& o) {
*obj = o;
}
const CopiedHandle& GetValue() {
return *obj;
}
CopiedHandle* GetPointer() {
return obj;
}
Handle* GetHandlePointer() {
return &obj->handle;
}
/* Convenience operators. */
CopiedHandle& operator*() {
return *obj;
}
CopiedHandle* operator->() {
return obj;
}
};

View file

@ -337,7 +337,7 @@ struct Validator<std::tuple<Args...>> {
}
if (((u32 *)r.Raw)[0] != SFCI_MAGIC) {
//return 0xF601;
return 0xF601;
}
size_t a_index = 0, b_index = num_inbuffers_in_arguments<Args ...>::value, x_index = 0, c_index = 0, h_index = 0;

View file

@ -1,47 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <type_traits>
#include "ipc_templating.hpp"
#include "iserviceobject.hpp"
#include "iwaitable.hpp"
#include "isession.hpp"
template <typename T>
class IPCSession final : public ISession<T> {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
public:
IPCSession<T>(size_t pbs = 0x400) : ISession<T>(NULL, 0, 0, 0) {
Result rc;
if (R_FAILED((rc = svcCreateSession(&this->server_handle, &this->client_handle, 0, 0)))) {
fatalSimple(rc);
}
this->service_object = std::make_shared<T>();
this->pointer_buffer.resize(pbs);
}
IPCSession<T>(std::shared_ptr<T> so, size_t pbs = 0x400) : ISession<T>(NULL, 0, 0, so, 0) {
Result rc;
if (R_FAILED((rc = svcCreateSession(&this->server_handle, &this->client_handle, 0, 0)))) {
fatalSimple(rc);
}
this->pointer_buffer.resize(pbs);
}
};

View file

@ -1,40 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
template <typename T>
class ISession;
class DomainOwner;
class IServiceObject {
private:
DomainOwner *owner = NULL;
public:
virtual ~IServiceObject() { }
virtual IServiceObject *clone() = 0;
bool is_domain() { return this->owner != NULL; }
DomainOwner *get_owner() { return this->owner; }
void set_owner(DomainOwner *owner) { this->owner = owner; }
virtual Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) = 0;
virtual Result handle_deferred() = 0;
};
#include "domainowner.hpp"

View file

@ -1,294 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <type_traits>
#include "ipc_templating.hpp"
#include "iserviceobject.hpp"
#include "iwaitable.hpp"
#include "iserver.hpp"
#include "domainowner.hpp"
enum IpcControlCommand {
IpcCtrl_Cmd_ConvertCurrentObjectToDomain = 0,
IpcCtrl_Cmd_CopyFromCurrentDomain = 1,
IpcCtrl_Cmd_CloneCurrentObject = 2,
IpcCtrl_Cmd_QueryPointerBufferSize = 3,
IpcCtrl_Cmd_CloneCurrentObjectEx = 4
};
#define RESULT_DEFER_SESSION (0x6580A)
template <typename T>
class IServer;
class IServiceObject;
template <typename T>
class ISession : public IWaitable {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
protected:
std::shared_ptr<T> service_object;
IServer<T> *server;
Handle server_handle;
Handle client_handle;
std::vector<char> pointer_buffer;
bool is_domain = false;
std::shared_ptr<DomainOwner> domain;
std::shared_ptr<IServiceObject> active_object;
public:
ISession<T>(IServer<T> *s, Handle s_h, Handle c_h, size_t pbs = 0x400) : server(s), server_handle(s_h), client_handle(c_h), pointer_buffer(pbs) {
this->service_object = std::make_shared<T>();
}
ISession<T>(IServer<T> *s, Handle s_h, Handle c_h, std::shared_ptr<T> so, size_t pbs = 0x400) : service_object(so), server(s), server_handle(s_h), client_handle(c_h), pointer_buffer(pbs) {
}
~ISession() override {
if (server_handle) {
svcCloseHandle(server_handle);
}
if (client_handle) {
svcCloseHandle(client_handle);
}
}
void close_handles() {
if (server_handle) {
svcCloseHandle(server_handle);
server_handle = 0;
}
if (client_handle) {
svcCloseHandle(client_handle);
client_handle = 0;
}
}
std::shared_ptr<T> get_service_object() { return this->service_object; }
Handle get_server_handle() { return this->server_handle; }
Handle get_client_handle() { return this->client_handle; }
DomainOwner *get_owner() { return this->is_domain ? this->domain.get() : NULL; }
/* IWaitable */
Handle get_handle() override {
return this->server_handle;
}
void handle_deferred() override {
Result rc = this->service_object->handle_deferred();
int handle_index;
if (rc != RESULT_DEFER_SESSION) {
this->set_deferred(false);
if (rc == 0xF601) {
svcCloseHandle(this->get_handle());
} else {
rc = svcReplyAndReceive(&handle_index, &this->server_handle, 0, this->server_handle, 0);
}
}
}
virtual Result handle_message(IpcParsedCommand &r) {
Result retval = 0xF601;
IpcCommand c;
ipcInitialize(&c);
if (r.IsDomainRequest && this->active_object == NULL) {
return 0xF601;
}
if (r.IsDomainRequest && r.InMessageType == DomainMessageType_Close) {
this->domain->delete_object(this->active_object);
this->active_object = NULL;
struct {
u64 magic;
u64 result;
} *raw = (decltype(raw))ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCO_MAGIC;
raw->result = 0x0;
return 0x0;
}
u64 cmd_id = ((u32 *)r.Raw)[2];
switch (r.CommandType) {
case IpcCommandType_Close:
/* TODO: This should close the session and clean up its resources. */
retval = 0xF601;
break;
case IpcCommandType_LegacyControl:
/* TODO: What does this allow one to do? */
retval = 0xF601;
break;
case IpcCommandType_LegacyRequest:
/* TODO: What does this allow one to do? */
retval = 0xF601;
break;
case IpcCommandType_Request:
case IpcCommandType_RequestWithContext:
retval = this->active_object->dispatch(r, c, cmd_id, (u8 *)pointer_buffer.data(), pointer_buffer.size());
break;
case IpcCommandType_Control:
case IpcCommandType_ControlWithContext:
retval = this->dispatch_control_command(r, c, cmd_id);
break;
case IpcCommandType_Invalid:
default:
retval = 0xF601;
break;
}
return retval;
}
virtual void postprocess(IpcParsedCommand &r, u64 cmd_id) {
/* ... */
(void)(r);
(void)(cmd_id);
}
virtual void cleanup() {
/* ... */
}
Result handle_signaled(u64 timeout) override {
Result rc;
int handle_index;
/* Prepare pointer buffer... */
IpcCommand c_for_reply;
ipcInitialize(&c_for_reply);
ipcAddRecvStatic(&c_for_reply, this->pointer_buffer.data(), this->pointer_buffer.size(), 0);
ipcPrepareHeader(&c_for_reply, 0);
/* Fix libnx bug in serverside C descriptor handling. */
((u32 *)armGetTls())[1] &= 0xFFFFC3FF;
((u32 *)armGetTls())[1] |= (2) << 10;
if (R_SUCCEEDED(rc = svcReplyAndReceive(&handle_index, &this->server_handle, 1, 0, U64_MAX))) {
if (handle_index != 0) {
/* TODO: Panic? */
}
IpcParsedCommand r;
u64 cmd_id = 0;
Result retval = ipcParse(&r);
if (R_SUCCEEDED(retval)) {
if (this->is_domain && (r.CommandType == IpcCommandType_Request || r.CommandType == IpcCommandType_RequestWithContext)) {
retval = ipcParseDomainRequest(&r);
if (!r.IsDomainRequest || r.InThisObjectId >= DOMAIN_ID_MAX) {
retval = 0xF601;
} else {
this->active_object = this->domain->get_domain_object(r.InThisObjectId);
}
} else {
this->active_object = this->service_object;
}
}
if (R_SUCCEEDED(retval)) {
cmd_id = ((u32 *)r.Raw)[2];
}
if (R_SUCCEEDED(retval)) {
retval = this->handle_message(r);
}
if (retval == RESULT_DEFER_SESSION) {
/* Session defer. */
this->active_object.reset();
this->set_deferred(true);
rc = retval;
} else if (retval == 0xF601) {
/* Session close. */
this->active_object.reset();
rc = retval;
} else {
if (R_SUCCEEDED(retval)) {
this->postprocess(r, cmd_id);
}
this->active_object.reset();
rc = svcReplyAndReceive(&handle_index, &this->server_handle, 0, this->server_handle, 0);
if (rc == 0xEA01) {
rc = 0x0;
}
this->cleanup();
}
}
return rc;
}
Result dispatch_control_command(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id) {
Result rc = 0xF601;
/* TODO: Implement. */
switch ((IpcControlCommand)cmd_id) {
case IpcCtrl_Cmd_ConvertCurrentObjectToDomain:
rc = WrapIpcCommandImpl<&ISession::ConvertCurrentObjectToDomain>(this, r, out_c, (u8 *)this->pointer_buffer.data(), pointer_buffer.size());
break;
case IpcCtrl_Cmd_CopyFromCurrentDomain:
rc = WrapIpcCommandImpl<&ISession::CopyFromCurrentDomain>(this, r, out_c, (u8 *)this->pointer_buffer.data(), pointer_buffer.size());
break;
case IpcCtrl_Cmd_CloneCurrentObject:
rc = WrapIpcCommandImpl<&ISession::CloneCurrentObject>(this, r, out_c, (u8 *)this->pointer_buffer.data(), pointer_buffer.size());
break;
case IpcCtrl_Cmd_QueryPointerBufferSize:
rc = WrapIpcCommandImpl<&ISession::QueryPointerBufferSize>(this, r, out_c, (u8 *)this->pointer_buffer.data(), pointer_buffer.size());
break;
case IpcCtrl_Cmd_CloneCurrentObjectEx:
rc = WrapIpcCommandImpl<&ISession::CloneCurrentObjectEx>(this, r, out_c, (u8 *)this->pointer_buffer.data(), pointer_buffer.size());
break;
default:
break;
}
return rc;
}
/* Control commands. */
std::tuple<Result> ConvertCurrentObjectToDomain() {
/* TODO */
return {0xF601};
}
std::tuple<Result> CopyFromCurrentDomain() {
/* TODO */
return {0xF601};
}
std::tuple<Result> CloneCurrentObject() {
/* TODO */
return {0xF601};
}
std::tuple<Result, u32> QueryPointerBufferSize() {
return {0x0, (u32)this->pointer_buffer.size()};
}
std::tuple<Result> CloneCurrentObjectEx() {
/* TODO */
return {0xF601};
}
};

View file

@ -18,47 +18,61 @@
#include <switch.h>
#include "waitablemanagerbase.hpp"
class WaitableManager;
#include "waitable_manager_base.hpp"
#include "hossynch.hpp"
class IWaitable {
private:
u64 wait_priority = 0;
bool is_deferred = false;
WaitableManagerBase *manager;
WaitableManagerBase *manager = nullptr;
protected:
HosMutex sig_lock;
bool is_signaled = false;
public:
virtual ~IWaitable() { }
virtual ~IWaitable() = default;
virtual void handle_deferred() = 0;
virtual Handle get_handle() = 0;
virtual Result handle_signaled(u64 timeout) = 0;
WaitableManager *get_manager() {
return (WaitableManager *)this->manager;
virtual void HandleDeferred() {
/* ... */
}
void set_manager(WaitableManagerBase *m) {
bool IsSignaled() {
std::scoped_lock<HosMutex> lock(this->sig_lock);
return this->is_signaled;
}
virtual Handle GetHandle() = 0;
virtual Result HandleSignaled(u64 timeout) = 0;
WaitableManagerBase *GetManager() {
return this->manager;
}
void SetManager(WaitableManagerBase *m) {
this->manager = m;
}
void update_priority() {
void UpdatePriority() {
if (manager) {
this->wait_priority = this->manager->get_priority();
this->wait_priority = this->manager->GetNextPriority();
}
}
bool get_deferred() {
bool IsDeferred() {
return this->is_deferred;
}
void set_deferred(bool d) {
void SetDeferred(bool d) {
this->is_deferred = d;
}
static bool compare(IWaitable *a, IWaitable *b) {
return (a->wait_priority < b->wait_priority) && !a->is_deferred;
static bool Compare(IWaitable *a, IWaitable *b) {
return (a->wait_priority < b->wait_priority) && !a->IsDeferred() && (a->GetHandle() != INVALID_HANDLE);
}
void NotifyManagerSignaled() {
if (this->manager) {
this->manager->NotifySignaled(this);
}
}
};
#include "waitablemanager.hpp"

View file

@ -0,0 +1,26 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "mitm/sm_mitm.h"
#include "ipc.hpp"
#include "mitm/imitmserviceobject.hpp"
#include "mitm/mitm_query_service.hpp"
#include "mitm/mitm_session.hpp"
#include "mitm/mitm_server.hpp"

View file

@ -16,16 +16,20 @@
#pragma once
#include <switch.h>
#include "iserver.hpp"
#include <atomic>
template <typename T>
class ExistingPortServer : public IServer<T> {
#include <stratosphere.hpp>
class IMitmServiceObject : public IServiceObject {
protected:
std::shared_ptr<Service> forward_service;
u64 process_id = 0;
u64 title_id = 0;
public:
ExistingPortServer(Handle port_h, unsigned int max_s, bool s_d = false) : IServer<T>(NULL, max_s, s_d) {
this->port_handle = port_h;
}
IMitmServiceObject(std::shared_ptr<Service> s) : forward_service(s) {}
ISession<T> *get_new_session(Handle session_h) override {
return new ServiceSession<T>(this, session_h, 0);
}
static bool ShouldMitm(u64 pid, u64 tid);
protected:
virtual ~IMitmServiceObject() = default;
};

View file

@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
enum MitmQueryServiceCommand {
MQS_Cmd_ShouldMitm = 65000,
MQS_Cmd_AssociatePidTid = 65001
};
namespace MitmQueryUtils {
Result GetAssociatedTidForPid(u64 pid, u64 *tid);
void AssociatePidToTid(u64 pid, u64 tid);
}
template <typename T>
class MitmQueryService : public IServiceObject {
protected:
void ShouldMitm(Out<bool> should_mitm, u64 pid) {
should_mitm.SetValue(false);
u64 tid = 0;
if (R_SUCCEEDED(MitmQueryUtils::GetAssociatedTidForPid(pid, &tid))) {
should_mitm.SetValue(T::ShouldMitm(pid, tid));
}
}
void AssociatePidToTid(u64 pid, u64 tid) {
MitmQueryUtils::AssociatePidToTid(pid, tid);
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<MQS_Cmd_ShouldMitm, &MitmQueryService::ShouldMitm>(),
MakeServiceCommandMeta<MQS_Cmd_AssociatePidTid, &MitmQueryService::AssociatePidToTid>(),
};
};

View file

@ -0,0 +1,100 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "mitm_query_service.hpp"
#include "sm_mitm.h"
#include "mitm_session.hpp"
template <typename T>
class MitmServer : public IWaitable {
static_assert(std::is_base_of<IMitmServiceObject, T>::value, "MitM Service Objects must derive from IMitmServiceObject");
private:
Handle port_handle;
unsigned int max_sessions;
char mitm_name[9];
public:
MitmServer(Handle *out_query_h, const char *service_name, unsigned int max_s) : port_handle(0), max_sessions(max_s) {
Handle tmp_hnd;
Result rc;
if (R_SUCCEEDED((rc = smGetServiceOriginal(&tmp_hnd, smEncodeName(service_name))))) {
svcCloseHandle(tmp_hnd);
} else {
fatalSimple(rc);
}
strncpy(mitm_name, service_name, 8);
mitm_name[8] = '\x00';
if (R_FAILED((rc = smMitMInstall(&this->port_handle, out_query_h, mitm_name)))) {
fatalSimple(rc);
}
}
virtual ~MitmServer() override {
if (this->port_handle) {
if (R_FAILED(smMitMUninstall(this->mitm_name))) {
std::abort();
}
svcCloseHandle(port_handle);
}
}
SessionManagerBase *GetSessionManager() {
return static_cast<SessionManagerBase *>(this->GetManager());
}
/* IWaitable */
virtual Handle GetHandle() override {
return this->port_handle;
}
virtual Result HandleSignaled(u64 timeout) override {
/* If this server's port was signaled, accept a new session. */
Handle session_h;
Result rc = svcAcceptSession(&session_h, this->port_handle);
if (R_FAILED(rc)) {
return rc;
}
/* Create a forward service for this instance. */
std::shared_ptr<Service>forward_service(new Service(), [](Service *s) {
/* Custom deleter to ensure service is open as long as necessary. */
serviceClose(s);
delete s;
});
if (R_FAILED(smMitMGetService(forward_service.get(), mitm_name))) {
/* TODO: Panic. */
}
this->GetSessionManager()->AddWaitable(new MitmSession(session_h, forward_service, std::make_shared<T>(forward_service)));
return 0;
}
};
template<typename T>
static void AddMitmServerToManager(SessionManagerBase *manager, const char *srv_name, unsigned int max_sessions) {
Handle query_h;
auto *srv = new MitmServer<T>(&query_h, srv_name, max_sessions);
manager->AddWaitable(srv);
manager->AddSession(query_h, std::move(ServiceObjectHolder(std::move(std::make_shared<MitmQueryService<T>>()))));
}

View file

@ -0,0 +1,313 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "imitmserviceobject.hpp"
#include "mitm_query_service.hpp"
class MitmSession final : public ServiceSession {
private:
/* This will be for the actual session. */
std::shared_ptr<Service> forward_service;
/* Store a handler for the service. */
void (*service_post_process_handler)(IMitmServiceObject *, IpcResponseContext *);
/* For cleanup usage. */
u32 num_fwd_copy_hnds;
Handle fwd_copy_hnds[8];
public:
template<typename T>
MitmSession(Handle s_h, std::shared_ptr<Service> fs, std::shared_ptr<T> srv) : ServiceSession(s_h) {
this->forward_service = std::move(fs);
this->obj_holder = std::move(ServiceObjectHolder(std::move(srv)));
this->service_post_process_handler = T::PostProcess;
size_t pbs;
if (R_FAILED(ipcQueryPointerBufferSize(forward_service->handle, &pbs))) {
std::abort();
}
this->pointer_buffer.resize(pbs);
this->control_holder.Reset();
this->control_holder = ServiceObjectHolder(std::make_shared<IMitmHipcControlService>(this));
}
MitmSession(Handle s_h, std::shared_ptr<Service> fs, ServiceObjectHolder &&h, void (*pph)(IMitmServiceObject *, IpcResponseContext *)) : ServiceSession(s_h) {
this->session_handle = s_h;
this->forward_service = std::move(fs);
this->obj_holder = std::move(h);
this->service_post_process_handler = pph;
size_t pbs;
if (R_FAILED(ipcQueryPointerBufferSize(forward_service->handle, &pbs))) {
std::abort();
}
this->pointer_buffer.resize(pbs);
this->control_holder.Reset();
this->control_holder = ServiceObjectHolder(std::make_shared<IMitmHipcControlService>(this));
}
virtual void PreProcessRequest(IpcResponseContext *ctx) override {
u32 *cmdbuf = (u32 *)armGetTls();
u32 *backup_cmdbuf = (u32 *)this->backup_tls;
if (ctx->request.HasPid) {
/* [ctrl 0] [ctrl 1] [handle desc 0] [pid low] [pid high] */
cmdbuf[4] = 0xFFFE0000UL | (cmdbuf[4] & 0xFFFFUL);
backup_cmdbuf[4] = cmdbuf[4];
}
}
Result ForwardRequest(IpcResponseContext *ctx) {
IpcParsedCommand r;
Result rc = serviceIpcDispatch(this->forward_service.get());
if (R_SUCCEEDED(rc)) {
if (ctx->request.IsDomainRequest) {
/* We never work with out object ids, so this should be fine. */
ipcParseDomainResponse(&r, 0);
} else {
ipcParse(&r);
}
struct {
u64 magic;
u64 result;
} *resp = (decltype(resp))r.Raw;
rc = resp->result;
for (unsigned int i = 0; i < r.NumHandles; i++) {
if (r.WasHandleCopied[i]) {
this->fwd_copy_hnds[num_fwd_copy_hnds++] = r.Handles[i];
}
}
}
return rc;
}
virtual Result GetResponse(IpcResponseContext *ctx) {
Result rc = 0xF601;
FirmwareVersion fw = GetRuntimeFirmwareVersion();
const ServiceCommandMeta *dispatch_table = ctx->obj_holder->GetDispatchTable();
size_t entry_count = ctx->obj_holder->GetDispatchTableEntryCount();
if (IsDomainObject(ctx->obj_holder)) {
switch (ctx->request.InMessageType) {
case DomainMessageType_Invalid:
return 0xF601;
case DomainMessageType_Close:
rc = ForwardRequest(ctx);
if (R_SUCCEEDED(rc)) {
ctx->obj_holder->GetServiceObject<IDomainObject>()->FreeObject(ctx->request.InThisObjectId);
}
if (R_SUCCEEDED(rc) && ctx->request.InThisObjectId == serviceGetObjectId(this->forward_service.get())) {
/* If we're not longer MitMing anything, we don't need a mitm session. */
this->Reply();
this->GetSessionManager()->AddSession(this->session_handle, std::move(this->obj_holder));
this->session_handle = 0;
return 0xF601;
}
return rc;
case DomainMessageType_SendMessage:
{
auto sub_obj = ctx->obj_holder->GetServiceObject<IDomainObject>()->GetObject(ctx->request.InThisObjectId);
if (sub_obj == nullptr) {
return ForwardRequest(ctx);
}
dispatch_table = sub_obj->GetDispatchTable();
entry_count = sub_obj->GetDispatchTableEntryCount();
}
}
}
bool found_entry = false;
for (size_t i = 0; i < entry_count; i++) {
if (ctx->cmd_id == dispatch_table[i].cmd_id && dispatch_table[i].fw_low <= fw && fw <= dispatch_table[i].fw_high) {
rc = dispatch_table[i].handler(ctx);
found_entry = true;
break;
}
}
if (!found_entry) {
memcpy(armGetTls(), this->backup_tls, sizeof(this->backup_tls));
rc = ForwardRequest(ctx);
}
return rc;
}
virtual void PostProcessResponse(IpcResponseContext *ctx) override {
if ((ctx->cmd_type == IpcCommandType_Request || ctx->cmd_type == IpcCommandType_RequestWithContext) && R_SUCCEEDED(ctx->rc)) {
if (!IsDomainObject(ctx->obj_holder) || ctx->request.InThisObjectId == serviceGetObjectId(this->forward_service.get())) {
IMitmServiceObject *obj;
if (!IsDomainObject(ctx->obj_holder)) {
obj = ctx->obj_holder->GetServiceObjectUnsafe<IMitmServiceObject>();
} else {
obj = ctx->obj_holder->GetServiceObject<IDomainObject>()->GetObject(ctx->request.InThisObjectId)->GetServiceObjectUnsafe<IMitmServiceObject>();
}
this->service_post_process_handler(obj, ctx);
}
}
}
virtual void CleanupResponse(IpcResponseContext *ctx) override {
/* Cleanup tls backup. */
std::memset(this->backup_tls, 0, sizeof(this->backup_tls));
/* Clean up copy handles. */
for (unsigned int i = 0; i < ctx->request.NumHandles; i++) {
if (ctx->request.WasHandleCopied[i]) {
svcCloseHandle(ctx->request.Handles[i]);
}
}
for (unsigned int i = 0; i < this->num_fwd_copy_hnds; i++) {
svcCloseHandle(this->fwd_copy_hnds[i]);
}
this->num_fwd_copy_hnds = 0;
}
public:
class IMitmHipcControlService : public IServiceObject {
private:
MitmSession *session;
public:
explicit IMitmHipcControlService(MitmSession *s) : session(s) {
}
virtual ~IMitmHipcControlService() override { }
public:
Result ConvertCurrentObjectToDomain(Out<u32> object_id) {
if (IsDomainObject(this->session->obj_holder)) {
return 0xF601;
}
Result rc = serviceConvertToDomain(this->session->forward_service.get());
if (R_FAILED(rc)) {
return rc;
}
u32 expected_id = serviceGetObjectId(this->session->forward_service.get());
/* Allocate new domain. */
auto new_domain = this->session->GetDomainManager()->AllocateDomain();
if (new_domain == nullptr) {
/* If our domains mismatch, we're in trouble. */
return 0xF601;
}
/* Reserve the expected object in the domain for our session. */
if (R_FAILED(new_domain->ReserveSpecificObject(expected_id))) {
return 0xF601;
}
new_domain->SetObject(expected_id, std::move(this->session->obj_holder));
this->session->obj_holder = std::move(ServiceObjectHolder(std::move(new_domain)));
/* Return the object id. */
object_id.SetValue(expected_id);
return 0;
}
Result CopyFromCurrentDomain(Out<MovedHandle> out_h, u32 id) {
auto domain = this->session->obj_holder.GetServiceObject<IDomainObject>();
if (domain == nullptr) {
return 0x3D60B;
}
auto object = domain->GetObject(id);
if (object == nullptr) {
/* Forward onwards. */
u32 *buf = (u32 *)armGetTls();
buf[0] = IpcCommandType_Control;
buf[1] = 0xA;
buf[4] = SFCI_MAGIC;
buf[5] = 0;
buf[6] = 1;
buf[7] = 0;
buf[8] = id;
buf[9] = 0;
Result rc = ipcDispatch(this->session->forward_service->handle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *raw = (decltype(raw))r.Raw;
rc = raw->result;
if (R_SUCCEEDED(rc)) {
out_h.SetValue(r.Handles[0]);
this->session->fwd_copy_hnds[this->session->num_fwd_copy_hnds++] = r.Handles[0];
}
}
return rc;
}
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
out_h.SetValue(client_h);
if (id == serviceGetObjectId(this->session->forward_service.get())) {
this->session->GetSessionManager()->AddWaitable(new MitmSession(server_h, this->session->forward_service, std::move(object->Clone()), this->session->service_post_process_handler));
} else {
this->session->GetSessionManager()->AddSession(server_h, std::move(object->Clone()));
}
return 0;
}
void CloneCurrentObject(Out<MovedHandle> out_h) {
Handle server_h, client_h;
if (R_FAILED(SessionManagerBase::CreateSessionHandles(&server_h, &client_h))) {
/* N aborts here. Should we error code? */
std::abort();
}
this->session->GetSessionManager()->AddWaitable(new MitmSession(server_h, this->session->forward_service, std::move(this->session->obj_holder.Clone()), this->session->service_post_process_handler));
out_h.SetValue(client_h);
}
void QueryPointerBufferSize(Out<u16> size) {
size.SetValue(this->session->pointer_buffer.size());
}
void CloneCurrentObjectEx(Out<MovedHandle> out_h, u32 which) {
/* TODO: Figure out what this u32 controls. */
return CloneCurrentObject(out_h);
}
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MakeServiceCommandMeta<HipcControlCommand_ConvertCurrentObjectToDomain, &MitmSession::IMitmHipcControlService::ConvertCurrentObjectToDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CopyFromCurrentDomain, &MitmSession::IMitmHipcControlService::CopyFromCurrentDomain>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObject, &MitmSession::IMitmHipcControlService::CloneCurrentObject>(),
MakeServiceCommandMeta<HipcControlCommand_QueryPointerBufferSize, &MitmSession::IMitmHipcControlService::QueryPointerBufferSize>(),
MakeServiceCommandMeta<HipcControlCommand_CloneCurrentObjectEx, &MitmSession::IMitmHipcControlService::CloneCurrentObjectEx>(),
};
};
};

View file

@ -0,0 +1,24 @@
/**
* @file sm_mitm.h
* @brief Service manager (sm) IPC wrapper for Atmosphere extensions.
* @author SciresM
* @copyright libnx Authors
*/
#pragma once
#include <switch.h>
#ifdef __cplusplus
extern "C" {
#endif
Result smMitMInitialize(void);
void smMitMExit(void);
Result smMitMGetService(Service* service_out, const char *name);
Result smMitMInstall(Handle *handle_out, Handle *query_out, const char *name);
Result smMitMUninstall(const char *name);
Result smMitMIsRegistered(const char *name);
#ifdef __cplusplus
}
#endif

View file

@ -1,58 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <vector>
#include "waitablemanager.hpp"
#include "systemevent.hpp"
class MultiThreadedWaitableManager : public WaitableManager {
protected:
u32 num_threads;
Thread *threads;
HosMutex get_waitable_lock;
SystemEvent *new_waitable_event;
public:
MultiThreadedWaitableManager(u32 n, u64 t, u32 ss = 0x8000) : WaitableManager(t), num_threads(n-1) {
u32 prio;
u32 cpuid = svcGetCurrentProcessorNumber();
Result rc;
threads = new Thread[num_threads];
if (R_FAILED((rc = svcGetThreadPriority(&prio, CUR_THREAD_HANDLE)))) {
fatalSimple(rc);
}
for (unsigned int i = 0; i < num_threads; i++) {
threads[i] = {0};
threadCreate(&threads[i], &MultiThreadedWaitableManager::thread_func, this, ss, prio, cpuid);
}
new_waitable_event = new SystemEvent(this, &MultiThreadedWaitableManager::add_waitable_callback);
this->waitables.push_back(new_waitable_event);
}
~MultiThreadedWaitableManager() override {
/* TODO: Exit the threads? */
}
IWaitable *get_waitable();
void add_waitable(IWaitable *waitable) override;
void process() override;
void process_until_timeout() override;
static Result add_waitable_callback(void *this_ptr, Handle *handles, size_t num_handles, u64 timeout);
static void thread_func(void *this_ptr);
};

View file

@ -0,0 +1,61 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/* Scope guard logic lovingly taken from Andrei Alexandrescu's "Systemic Error Handling in C++" */
#pragma once
#include <utility>
template<class F>
class ScopeGuard {
private:
F f;
bool active;
public:
ScopeGuard(F f) : f(std::move(f)), active(true) { }
~ScopeGuard() { if (active) { f(); } }
void Cancel() { active = false; }
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard &) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& rhs) : f(std::move(rhs.f)), active(rhs.active) {
rhs.Cancel();
}
};
template<class F>
ScopeGuard<F> MakeScopeGuard(F f) {
return ScopeGuard<F>(std::move(f));
}
enum class ScopeGuardOnExit {};
template <typename F>
ScopeGuard<F> operator+(ScopeGuardOnExit, F&& f) {
return ScopeGuard<F>(std::forward<F>(f));
}
#define CONCATENATE_IMPL(S1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __LINE__)
#endif
#define ON_SCOPE_EXIT auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_) = ScopeGuardOnExit() + [&]()

View file

@ -16,28 +16,19 @@
#pragma once
#include <switch.h>
#include <algorithm>
#include <type_traits>
#include "iserviceobject.hpp"
#include "iwaitable.hpp"
#include "isession.hpp"
#include "ipc.hpp"
template <typename T>
class ISession;
template <typename T>
template<typename T>
class IServer : public IWaitable {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
protected:
Handle port_handle;
unsigned int max_sessions;
bool supports_domains;
public:
IServer(const char *service_name, unsigned int max_s, bool s_d = false) : max_sessions(max_s), supports_domains(s_d) {
}
IServer(unsigned int max_s) : port_handle(0), max_sessions(max_s) { }
virtual ~IServer() {
if (port_handle) {
@ -45,19 +36,16 @@ class IServer : public IWaitable {
}
}
virtual ISession<T> *get_new_session(Handle session_h) = 0;
SessionManagerBase *GetSessionManager() {
return static_cast<SessionManagerBase *>(this->GetManager());
}
/* IWaitable */
virtual Handle get_handle() {
virtual Handle GetHandle() override {
return this->port_handle;
}
virtual void handle_deferred() {
/* TODO: Panic, because we can never defer a server. */
}
virtual Result handle_signaled(u64 timeout) {
virtual Result HandleSignaled(u64 timeout) override {
/* If this server's port was signaled, accept a new session. */
Handle session_h;
Result rc = svcAcceptSession(&session_h, this->port_handle);
@ -65,7 +53,35 @@ class IServer : public IWaitable {
return rc;
}
this->get_manager()->add_waitable(this->get_new_session(session_h));
this->GetSessionManager()->AddSession(session_h, std::move(ServiceObjectHolder(std::move(std::make_shared<T>()))));
return 0;
}
};
template <typename T>
class ServiceServer : public IServer<T> {
public:
ServiceServer(const char *service_name, unsigned int max_s) : IServer<T>(max_s) {
if (R_FAILED(smRegisterService(&this->port_handle, service_name, false, this->max_sessions))) {
/* TODO: Panic. */
}
}
};
template <typename T>
class ExistingPortServer : public IServer<T> {
public:
ExistingPortServer(Handle port_h, unsigned int max_s) : IServer<T>(max_s) {
this->port_handle = port_h;
}
};
template <typename T>
class ManagedPortServer : public IServer<T> {
public:
ManagedPortServer(const char *service_name, unsigned int max_s) : IServer<T>(max_s) {
if (R_FAILED(svcManageNamedPort(&this->port_handle, service_name, this->max_sessions))) {
/* TODO: panic */
}
}
};

View file

@ -0,0 +1,21 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc.hpp"
#include "services/smm_ams.h"

View file

@ -16,21 +16,16 @@
#pragma once
#include <switch.h>
#include <type_traits>
#include "ipc_templating.hpp"
#include "iserviceobject.hpp"
#include "iwaitable.hpp"
#include "iserver.hpp"
#include "isession.hpp"
#ifdef __cplusplus
extern "C" {
#endif
template <typename T>
class ServiceSession final : public ISession<T> {
static_assert(std::is_base_of<IServiceObject, T>::value, "Service Objects must derive from IServiceObject");
Result smManagerAmsInitialize(void);
void smManagerAmsExit(void);
public:
ServiceSession<T>(IServer<T> *s, Handle s_h, Handle c_h, size_t pbs = 0x400) : ISession<T>(s, s_h, c_h, pbs) {
/* ... */
}
Result smManagerAmsEndInitialDefers(void);
};
#ifdef __cplusplus
}
#endif

View file

@ -1,42 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include "iwaitable.hpp"
#include "ievent.hpp"
#define SYSTEMEVENT_INDEX_WAITHANDLE 0
#define SYSTEMEVENT_INDEX_SGNLHANDLE 1
class SystemEvent final : public IEvent {
public:
SystemEvent(void *a, EventCallback callback) : IEvent(0, a, callback) {
Handle wait_h;
Handle sig_h;
if (R_FAILED(svcCreateEvent(&sig_h, &wait_h))) {
/* TODO: Panic. */
}
this->handles.push_back(wait_h);
this->handles.push_back(sig_h);
}
Result signal_event() override {
return svcSignalEvent(this->handles[SYSTEMEVENT_INDEX_SGNLHANDLE]);
}
};

View file

@ -0,0 +1,330 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <memory>
#include "../meta_tools.hpp"
#include "waitable_manager_base.hpp"
#include "event.hpp"
#include "ipc.hpp"
#include "servers.hpp"
#include "scope_guard.hpp"
static inline Handle GetCurrentThreadHandle() {
/* TODO: Wait for libnx to add a way for me to call getThreadVars() */
return *((Handle *)((uintptr_t)armGetTls() + 0x1E4));
}
struct DefaultManagerOptions {
static constexpr size_t PointerBufferSize = 0;
static constexpr size_t MaxDomains = 0;
static constexpr size_t MaxDomainObjects = 0;
};
struct DomainEntry {
ServiceObjectHolder obj_holder;
IDomainObject *owner = nullptr;
};
template<typename ManagerOptions = DefaultManagerOptions>
class WaitableManager : public SessionManagerBase {
private:
/* Domain Manager */
HosMutex domain_lock;
std::array<std::weak_ptr<IDomainObject>, ManagerOptions::MaxDomains> domains;
std::array<bool, ManagerOptions::MaxDomains> is_domain_allocated;
std::array<DomainEntry, ManagerOptions::MaxDomainObjects> domain_objects;
/* Waitable Manager */
std::vector<IWaitable *> to_add_waitables;
std::vector<IWaitable *> waitables;
u32 num_threads;
Thread *threads;
HosMutex process_lock;
HosMutex signal_lock;
HosMutex add_lock;
IEvent *new_waitable_event = nullptr;
IWaitable *next_signaled = nullptr;
Handle cur_thread_handle = INVALID_HANDLE;
public:
WaitableManager(u32 n, u32 ss = 0x8000) : num_threads(n-1) {
u32 prio;
u32 cpuid = svcGetCurrentProcessorNumber();
Result rc;
threads = new Thread[num_threads];
if (num_threads) {
if (R_FAILED((rc = svcGetThreadPriority(&prio, CUR_THREAD_HANDLE)))) {
fatalSimple(rc);
}
for (unsigned int i = 0; i < num_threads; i++) {
threads[i] = {0};
threadCreate(&threads[i], &WaitableManager::ProcessLoop, this, ss, prio, cpuid);
}
}
new_waitable_event = CreateSystemEvent([](u64 timeout) { return 0; }, true);
}
~WaitableManager() override {
/* This should call the destructor for every waitable. */
std::for_each(waitables.begin(), waitables.end(), std::default_delete<IWaitable>{});
/* TODO: Exit the threads? */
}
virtual void AddWaitable(IWaitable *w) override {
std::scoped_lock lk{this->add_lock};
this->to_add_waitables.push_back(w);
w->SetManager(this);
this->new_waitable_event->Signal();
}
virtual void NotifySignaled(IWaitable *w) override {
std::scoped_lock lk{this->signal_lock};
if (this->next_signaled == nullptr && w != this->new_waitable_event) {
this->next_signaled = w;
}
svcCancelSynchronization(this->cur_thread_handle);
}
virtual void Process() override {
/* Add initial set of waitables. */
AddWaitablesInternal();
Result rc;
for (unsigned int i = 0; i < num_threads; i++) {
if (R_FAILED((rc = threadStart(&threads[i])))) {
fatalSimple(rc);
}
}
ProcessLoop(this);
}
private:
static void ProcessLoop(void *t) {
WaitableManager *this_ptr = (WaitableManager *)t;
while (true) {
IWaitable *w = this_ptr->GetWaitable();
if (w) {
Result rc = w->HandleSignaled(0);
if (rc == 0xF601) {
/* Close! */
delete w;
} else {
this_ptr->AddWaitable(w);
}
}
}
}
IWaitable *GetWaitable() {
std::scoped_lock lk{this->process_lock};
this->next_signaled = nullptr;
IWaitable *result = nullptr;
if (this->new_waitable_event->IsSignaled()) {
AddWaitablesInternal();
}
this->cur_thread_handle = GetCurrentThreadHandle();
ON_SCOPE_EXIT {
this->cur_thread_handle = INVALID_HANDLE;
};
/* First, see if anything's already signaled. */
for (auto &w : this->waitables) {
if (w->IsSignaled()) {
result = w;
}
}
/* It's possible somebody signaled us while we were iterating. */
{
std::scoped_lock lk{this->signal_lock};
if (this->next_signaled != nullptr) result = this->next_signaled;
}
if (result == nullptr) {
std::vector<Handle> handles;
int handle_index = 0;
Result rc;
while (result == nullptr) {
/* Sort waitables by priority. */
std::sort(this->waitables.begin(), this->waitables.end(), IWaitable::Compare);
/* Copy out handles. */
handles.resize(this->waitables.size() + 1);
handles[0] = this->new_waitable_event->GetHandle();
unsigned int num_handles = 1;
for (unsigned int i = 0; i < this->waitables.size(); i++) {
Handle h = this->waitables[i]->GetHandle();
if (h != INVALID_HANDLE) {
handles[num_handles++] = h;
}
}
/* Do deferred callback for each waitable. This has to happen before we wait on anything else. */
for (auto & waitable : this->waitables) {
if (waitable->IsDeferred()) {
waitable->HandleDeferred();
}
}
/* Wait forever. */
rc = svcWaitSynchronization(&handle_index, handles.data(), num_handles, U64_MAX);
IWaitable *w = this->waitables[handle_index - 1];
if (R_SUCCEEDED(rc)) {
if (handle_index == 0) {
AddWaitablesInternal();
continue;
}
std::for_each(waitables.begin(), waitables.begin() + handle_index - 1, std::mem_fn(&IWaitable::UpdatePriority));
result = w;
} else if (rc == 0xEA01) {
/* Timeout: Just update priorities. */
std::for_each(waitables.begin(), waitables.end(), std::mem_fn(&IWaitable::UpdatePriority));
} else if (rc == 0xEC01) {
/* svcCancelSynchronization was called. */
std::scoped_lock lk{this->signal_lock};
if (this->next_signaled != nullptr) result = this->next_signaled;
} else if (rc != 0xF601 && rc != 0xE401) {
std::abort();
} else {
if (handle_index == 0) {
std::abort();
}
this->waitables.erase(this->waitables.begin() + handle_index - 1);
std::for_each(waitables.begin(), waitables.begin() + handle_index - 1, std::mem_fn(&IWaitable::UpdatePriority));
delete w;
}
}
}
this->waitables.erase(std::remove_if(this->waitables.begin(), this->waitables.end(), [&](IWaitable *w) { return w == result; }), this->waitables.end());
return result;
}
void AddWaitablesInternal() {
{
std::scoped_lock lk{this->add_lock};
this->waitables.insert(this->waitables.end(), this->to_add_waitables.begin(), this->to_add_waitables.end());
this->to_add_waitables.clear();
this->new_waitable_event->Clear();
}
}
/* Session Manager */
public:
virtual void AddSession(Handle server_h, ServiceObjectHolder &&service) override {
this->AddWaitable(new ServiceSession(server_h, ManagerOptions::PointerBufferSize, std::move(service)));
}
/* Domain Manager */
public:
virtual std::shared_ptr<IDomainObject> AllocateDomain() override {
std::scoped_lock lk{this->domain_lock};
for (size_t i = 0; i < ManagerOptions::MaxDomains; i++) {
if (!this->is_domain_allocated[i]) {
auto new_domain = std::make_shared<IDomainObject>(this);
this->domains[i] = new_domain;
this->is_domain_allocated[i] = true;
return new_domain;
}
}
return nullptr;
}
void FreeDomain(IDomainObject *domain) override {
std::scoped_lock lk{this->domain_lock};
for (size_t i = 0; i < ManagerOptions::MaxDomainObjects; i++) {
FreeObject(domain, i+1);
}
for (size_t i = 0; i < ManagerOptions::MaxDomains; i++) {
auto observe = this->domains[i].lock();
if (observe.get() == domain) {
this->is_domain_allocated[i] = false;
break;
}
}
}
virtual Result ReserveObject(IDomainObject *domain, u32 *out_object_id) override {
std::scoped_lock lk{this->domain_lock};
for (size_t i = 0; i < ManagerOptions::MaxDomainObjects; i++) {
if (this->domain_objects[i].owner == nullptr) {
this->domain_objects[i].owner = domain;
*out_object_id = i+1;
return 0;
}
}
return 0x25A0A;
}
virtual Result ReserveSpecificObject(IDomainObject *domain, u32 object_id) override {
std::scoped_lock lk{this->domain_lock};
if (this->domain_objects[object_id-1].owner == nullptr) {
this->domain_objects[object_id-1].owner = domain;
return 0;
}
return 0x25A0A;
}
virtual void SetObject(IDomainObject *domain, u32 object_id, ServiceObjectHolder&& holder) override {
std::scoped_lock lk{this->domain_lock};
if (this->domain_objects[object_id-1].owner == domain) {
this->domain_objects[object_id-1].obj_holder = std::move(holder);
}
}
virtual ServiceObjectHolder *GetObject(IDomainObject *domain, u32 object_id) override {
std::scoped_lock lk{this->domain_lock};
if (this->domain_objects[object_id-1].owner == domain) {
return &this->domain_objects[object_id-1].obj_holder;
}
return nullptr;
}
virtual Result FreeObject(IDomainObject *domain, u32 object_id) override {
std::scoped_lock lk{this->domain_lock};
if (this->domain_objects[object_id-1].owner == domain) {
this->domain_objects[object_id-1].obj_holder.Reset();
this->domain_objects[object_id-1].owner = nullptr;
return 0x0;
}
return 0x3D80B;
}
virtual Result ForceFreeObject(u32 object_id) override {
std::scoped_lock lk{this->domain_lock};
if (this->domain_objects[object_id-1].owner != nullptr) {
this->domain_objects[object_id-1].obj_holder.Reset();
this->domain_objects[object_id-1].owner = nullptr;
return 0x0;
}
return 0x3D80B;
}
};

View file

@ -15,9 +15,9 @@
*/
#pragma once
#include <switch.h>
#include <atomic>
#include <vector>
class IWaitable;
class WaitableManagerBase {
std::atomic<u64> cur_priority = 0;
@ -25,7 +25,13 @@ class WaitableManagerBase {
WaitableManagerBase() = default;
virtual ~WaitableManagerBase() = default;
u64 get_priority() {
u64 GetNextPriority() {
return std::atomic_fetch_add(&cur_priority, (u64)1);
}
virtual void AddWaitable(IWaitable *w) = 0;
virtual void NotifySignaled(IWaitable *w) = 0;
virtual void Process() = 0;
};

View file

@ -1,48 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <algorithm>
#include <memory>
#include <vector>
#include "waitablemanagerbase.hpp"
#include "iwaitable.hpp"
#include "hossynch.hpp"
class IWaitable;
class WaitableManager : public WaitableManagerBase {
protected:
std::vector<IWaitable *> to_add_waitables;
std::vector<IWaitable *> waitables;
u64 timeout = 0;
HosMutex lock;
std::atomic_bool has_new_items = false;
private:
void process_internal(bool break_on_timeout);
public:
WaitableManager(u64 t) : timeout(t) { }
~WaitableManager() override {
/* This should call the destructor for every waitable. */
std::for_each(waitables.begin(), waitables.end(), std::default_delete<IWaitable>{});
}
virtual void add_waitable(IWaitable *waitable);
virtual void process();
virtual void process_until_timeout();
};

View file

@ -0,0 +1,42 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <mutex>
#include <switch.h>
#include <stratosphere.hpp>
static std::vector<u64> g_known_pids;
static std::vector<u64> g_known_tids;
static HosMutex g_pid_tid_mutex;
Result MitmQueryUtils::GetAssociatedTidForPid(u64 pid, u64 *tid) {
Result rc = 0xCAFE;
std::scoped_lock lk{g_pid_tid_mutex};
for (unsigned int i = 0; i < g_known_pids.size(); i++) {
if (g_known_pids[i] == pid) {
*tid = g_known_tids[i];
rc = 0x0;
break;
}
}
return rc;
}
void MitmQueryUtils::AssociatePidToTid(u64 pid, u64 tid) {
std::scoped_lock lk{g_pid_tid_mutex};
g_known_pids.push_back(pid);
g_known_tids.push_back(tid);
}

View file

@ -1,119 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <algorithm>
#include <functional>
#include <mutex>
#include <stratosphere/multithreadedwaitablemanager.hpp>
void MultiThreadedWaitableManager::process() {
Result rc;
for (unsigned int i = 0; i < num_threads; i++) {
if (R_FAILED((rc = threadStart(&threads[i])))) {
fatalSimple(rc);
}
}
MultiThreadedWaitableManager::thread_func(this);
}
void MultiThreadedWaitableManager::process_until_timeout() {
/* TODO: Panic. */
}
void MultiThreadedWaitableManager::add_waitable(IWaitable *waitable) {
std::scoped_lock lk{this->lock};
this->to_add_waitables.push_back(waitable);
waitable->set_manager(this);
this->new_waitable_event->signal_event();
}
IWaitable *MultiThreadedWaitableManager::get_waitable() {
std::vector<Handle> handles;
int handle_index = 0;
Result rc;
std::scoped_lock lk{this->get_waitable_lock};
while (1) {
/* Sort waitables by priority. */
std::sort(this->waitables.begin(), this->waitables.end(), IWaitable::compare);
/* Copy out handles. */
handles.resize(this->waitables.size());
std::transform(this->waitables.begin(), this->waitables.end(), handles.begin(), [](IWaitable *w) { return w->get_handle(); });
rc = svcWaitSynchronization(&handle_index, handles.data(), this->waitables.size(), this->timeout);
IWaitable *w = this->waitables[handle_index];
if (R_SUCCEEDED(rc)) {
std::for_each(waitables.begin(), waitables.begin() + handle_index, std::mem_fn(&IWaitable::update_priority));
this->waitables.erase(this->waitables.begin() + handle_index);
} else if (rc == 0xEA01) {
/* Timeout. */
std::for_each(waitables.begin(), waitables.end(), std::mem_fn(&IWaitable::update_priority));
} else if (rc != 0xF601 && rc != 0xE401) {
/* TODO: Panic. When can this happen? */
} else {
std::for_each(waitables.begin(), waitables.begin() + handle_index, std::mem_fn(&IWaitable::update_priority));
this->waitables.erase(this->waitables.begin() + handle_index);
delete w;
}
/* Do deferred callback for each waitable. */
for (auto & waitable : this->waitables) {
if (waitable->get_deferred()) {
waitable->handle_deferred();
}
}
/* Return waitable. */
if (R_SUCCEEDED(rc)) {
if (w == this->new_waitable_event) {
w->handle_signaled(0);
this->waitables.push_back(w);
} else {
return w;
}
}
}
}
Result MultiThreadedWaitableManager::add_waitable_callback(void *arg, Handle *handles, size_t num_handles, u64 timeout) {
MultiThreadedWaitableManager *this_ptr = (MultiThreadedWaitableManager *)arg;
svcClearEvent(handles[0]);
std::scoped_lock lk{this_ptr->lock};
this_ptr->waitables.insert(this_ptr->waitables.end(), this_ptr->to_add_waitables.begin(), this_ptr->to_add_waitables.end());
this_ptr->to_add_waitables.clear();
return 0;
}
void MultiThreadedWaitableManager::thread_func(void *t) {
MultiThreadedWaitableManager *this_ptr = (MultiThreadedWaitableManager *)t;
while (1) {
IWaitable *w = this_ptr->get_waitable();
if (w) {
Result rc = w->handle_signaled(0);
if (rc == 0xF601) {
/* Close! */
delete w;
} else {
this_ptr->add_waitable(w);
}
}
}
}

View file

@ -0,0 +1,190 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <switch/arm/atomics.h>
#include <stratosphere/mitm/sm_mitm.h>
static Handle g_smMitmHandle = INVALID_HANDLE;
static u64 g_refCnt;
Result smMitMInitialize(void) {
atomicIncrement64(&g_refCnt);
if (g_smMitmHandle != INVALID_HANDLE)
return 0;
Result rc = svcConnectToNamedPort(&g_smMitmHandle, "sm:");
if (R_SUCCEEDED(rc)) {
IpcCommand c;
ipcInitialize(&c);
ipcSendPid(&c);
struct {
u64 magic;
u64 cmd_id;
u64 zero;
u64 reserved[2];
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 0;
raw->zero = 0;
rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
}
if (R_FAILED(rc))
smExit();
return rc;
}
void smMitMExit(void) {
if (atomicDecrement64(&g_refCnt) == 0) {
svcCloseHandle(g_smMitmHandle);
g_smMitmHandle = INVALID_HANDLE;
}
}
Result smMitMGetService(Service* service_out, const char *name_str)
{
u64 name = smEncodeName(name_str);
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
u64 reserved[2];
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 1;
raw->service_name = name;
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
service_out->type = ServiceType_Normal;
service_out->handle = r.Handles[0];
}
}
return rc;
}
Result smMitMInstall(Handle *handle_out, Handle *query_out, const char *name) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65000;
raw->service_name = smEncodeName(name);
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
if (R_SUCCEEDED(rc)) {
*handle_out = r.Handles[0];
*query_out = r.Handles[1];
}
}
return rc;
}
Result smMitMUninstall(const char *name) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
u64 service_name;
u64 reserved;
} *raw;
raw = ipcPrepareHeader(&c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65001;
raw->service_name = smEncodeName(name);
Result rc = ipcDispatch(g_smMitmHandle);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
ipcParse(&r);
struct {
u64 magic;
u64 result;
} *resp = r.Raw;
rc = resp->result;
}
return rc;
}

View file

@ -0,0 +1,69 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <switch/arm/atomics.h>
#include <stratosphere/services/smm_ams.h>
static Service g_smManagerAmsSrv;
static u64 g_smManagerAmsRefcnt;
Result smManagerAmsInitialize(void) {
atomicIncrement64(&g_smManagerAmsRefcnt);
if (serviceIsActive(&g_smManagerAmsSrv))
return 0;
return smGetService(&g_smManagerAmsSrv, "sm:m");
}
void smManagerAmsExit(void) {
if (atomicDecrement64(&g_smManagerAmsRefcnt) == 0)
serviceClose(&g_smManagerAmsSrv);
}
Result smManagerAmsEndInitialDefers(void) {
IpcCommand c;
ipcInitialize(&c);
struct {
u64 magic;
u64 cmd_id;
} *raw;
raw = serviceIpcPrepareHeader(&g_smManagerAmsSrv, &c, sizeof(*raw));
raw->magic = SFCI_MAGIC;
raw->cmd_id = 65000;
Result rc = serviceIpcDispatch(&g_smManagerAmsSrv);
if (R_SUCCEEDED(rc)) {
IpcParsedCommand r;
struct {
u64 magic;
u64 result;
} *resp;
serviceIpcParse(&g_smManagerAmsSrv, &r, sizeof(*resp));
resp = r.Raw;
rc = resp->result;
}
return rc;
}

View file

@ -1,105 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <algorithm>
#include <functional>
#include <mutex>
#include <stratosphere/waitablemanager.hpp>
void WaitableManager::add_waitable(IWaitable *waitable) {
std::scoped_lock lk{this->lock};
this->to_add_waitables.push_back(waitable);
waitable->set_manager(this);
this->has_new_items = true;
}
void WaitableManager::process_internal(bool break_on_timeout) {
std::vector<Handle> handles;
int handle_index = 0;
Result rc;
while (1) {
/* Add new items, if relevant. */
if (this->has_new_items) {
std::scoped_lock lk{this->lock};
this->waitables.insert(this->waitables.end(), this->to_add_waitables.begin(), this->to_add_waitables.end());
this->to_add_waitables.clear();
this->has_new_items = false;
}
/* Sort waitables by priority. */
std::sort(this->waitables.begin(), this->waitables.end(), IWaitable::compare);
/* Copy out handles. */
handles.resize(this->waitables.size());
std::transform(this->waitables.begin(), this->waitables.end(), handles.begin(), [](IWaitable *w) { return w->get_handle(); });
rc = svcWaitSynchronization(&handle_index, handles.data(), this->waitables.size(), this->timeout);
if (R_SUCCEEDED(rc)) {
/* Handle a signaled waitable. */
/* TODO: What timeout should be passed here? */
rc = this->waitables[handle_index]->handle_signaled(0);
std::for_each(waitables.begin(), waitables.begin() + handle_index, std::mem_fn(&IWaitable::update_priority));
} else if (rc == 0xEA01) {
/* Timeout. */
std::for_each(waitables.begin(), waitables.end(), std::mem_fn(&IWaitable::update_priority));
if (break_on_timeout) {
return;
}
} else if (rc != 0xF601) {
/* TODO: Panic. When can this happen? */
}
if (rc == 0xF601) {
/* handles[handle_index] was closed! */
/* Close the handle. */
svcCloseHandle(handles[handle_index]);
IWaitable *to_delete = this->waitables[handle_index];
/* If relevant, remove from waitables. */
this->waitables.erase(this->waitables.begin() + handle_index);
/* Delete it. */
delete to_delete;
std::for_each(waitables.begin(), waitables.begin() + handle_index, std::mem_fn(&IWaitable::update_priority));
}
/* Do deferred callback for each waitable. */
for (auto & waitable : this->waitables) {
if (waitable->get_deferred()) {
waitable->handle_deferred();
}
}
}
}
void WaitableManager::process() {
WaitableManager::process_internal(false);
}
void WaitableManager::process_until_timeout() {
WaitableManager::process_internal(true);
}