aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/core.cpp10
-rw-r--r--src/core/hle/function_wrappers.h38
-rw-r--r--src/core/hle/ipc.h3
-rw-r--r--src/core/hle/ipc_helpers.h149
-rw-r--r--src/core/hle/kernel/client_session.cpp29
-rw-r--r--src/core/hle/kernel/client_session.h4
-rw-r--r--src/core/hle/kernel/errors.h5
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp111
-rw-r--r--src/core/hle/kernel/hle_ipc.h93
-rw-r--r--src/core/hle/kernel/memory.cpp6
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/kernel/server_port.cpp12
-rw-r--r--src/core/hle/kernel/server_port.h11
-rw-r--r--src/core/hle/kernel/server_session.cpp24
-rw-r--r--src/core/hle/kernel/server_session.h16
-rw-r--r--src/core/hle/kernel/shared_memory.cpp2
-rw-r--r--src/core/hle/kernel/thread.cpp4
-rw-r--r--src/core/hle/kernel/timer.cpp2
-rw-r--r--src/core/hle/result.h7
-rw-r--r--src/core/hle/romfs.cpp102
-rw-r--r--src/core/hle/romfs.h22
-rw-r--r--src/core/hle/service/apt/apt.cpp168
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.cpp6
-rw-r--r--src/core/hle/service/boss/boss_p.cpp3
-rw-r--r--src/core/hle/service/cam/cam.cpp6
-rw-r--r--src/core/hle/service/cecd/cecd.cpp8
-rw-r--r--src/core/hle/service/cfg/cfg.cpp4
-rw-r--r--src/core/hle/service/csnd_snd.cpp4
-rw-r--r--src/core/hle/service/dsp_dsp.cpp2
-rw-r--r--src/core/hle/service/frd/frd.cpp43
-rw-r--r--src/core/hle/service/frd/frd.h13
-rw-r--r--src/core/hle/service/frd/frd_u.cpp2
-rw-r--r--src/core/hle/service/fs/archive.cpp4
-rw-r--r--src/core/hle/service/fs/fs_user.cpp6
-rw-r--r--src/core/hle/service/gsp_gpu.cpp15
-rw-r--r--src/core/hle/service/hid/hid.cpp12
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp4
-rw-r--r--src/core/hle/service/ir/ir_user.cpp6
-rw-r--r--src/core/hle/service/mic_u.cpp2
-rw-r--r--src/core/hle/service/nfc/nfc.cpp4
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp127
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h7
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h3
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp278
-rw-r--r--src/core/hle/service/nwm/uds_data.h78
-rw-r--r--src/core/hle/service/ptm/ptm.cpp2
-rw-r--r--src/core/hle/service/service.cpp106
-rw-r--r--src/core/hle/service/service.h150
-rw-r--r--src/core/hle/service/sm/sm.cpp14
-rw-r--r--src/core/hle/service/sm/sm.h10
-rw-r--r--src/core/hle/service/sm/srv.cpp164
-rw-r--r--src/core/hle/service/sm/srv.h26
-rw-r--r--src/core/hle/service/y2r_u.cpp6
-rw-r--r--src/core/hle/svc.cpp156
-rw-r--r--src/core/hw/aes/key.h2
-rw-r--r--src/core/hw/gpu.cpp41
-rw-r--r--src/core/hw/gpu.h2
-rw-r--r--src/core/loader/ncch.cpp6
-rw-r--r--src/core/memory.cpp153
-rw-r--r--src/core/memory.h38
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp86
63 files changed, 2084 insertions, 347 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6e602b0c5..360f407f3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SRCS
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
hle/kernel/wait_object.cpp
+ hle/romfs.cpp
hle/service/ac/ac.cpp
hle/service/ac/ac_i.cpp
hle/service/ac/ac_u.cpp
@@ -144,6 +145,7 @@ set(SRCS
hle/service/nwm/nwm_tst.cpp
hle/service/nwm/nwm_uds.cpp
hle/service/nwm/uds_beacon.cpp
+ hle/service/nwm/uds_data.cpp
hle/service/pm_app.cpp
hle/service/ptm/ptm.cpp
hle/service/ptm/ptm_gets.cpp
@@ -257,6 +259,7 @@ set(HEADERS
hle/kernel/vm_manager.h
hle/kernel/wait_object.h
hle/result.h
+ hle/romfs.h
hle/service/ac/ac.h
hle/service/ac/ac_i.h
hle/service/ac/ac_u.h
@@ -341,6 +344,7 @@ set(HEADERS
hle/service/nwm/nwm_tst.h
hle/service/nwm/nwm_uds.h
hle/service/nwm/uds_beacon.h
+ hle/service/nwm/uds_data.h
hle/service/pm_app.h
hle/service/ptm/ptm.h
hle/service/ptm/ptm_gets.h
@@ -385,4 +389,7 @@ set(HEADERS
create_directory_groups(${SRCS} ${HEADERS})
add_library(core STATIC ${SRCS} ${HEADERS})
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
+if (ENABLE_WEB_SERVICE)
+ target_link_libraries(core PUBLIC json-headers web_service)
+endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 5429bcb26..d08f18623 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -168,6 +168,16 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
}
void System::Shutdown() {
+ // Log last frame performance stats
+ auto perf_results = GetAndResetPerfStats();
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
+ perf_results.emulation_speed * 100.0);
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
+ perf_results.game_fps);
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
+ perf_results.frametime * 1000.0);
+
+ // Shutdown emulation session
GDBStub::Shutdown();
AudioCore::Shutdown();
VideoCore::Shutdown();
diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h
index 18b6e7017..5e6002f4e 100644
--- a/src/core/hle/function_wrappers.h
+++ b/src/core/hle/function_wrappers.h
@@ -16,9 +16,6 @@ namespace HLE {
#define PARAM(n) Core::CPU().GetReg(n)
-/// An invalid result code that is meant to be overwritten when a thread resumes from waiting
-static const ResultCode RESULT_INVALID(0xDEADC0DE);
-
/**
* HLE a function return from the current ARM11 userland process
* @param res Result to return
@@ -68,10 +65,18 @@ void Wrap() {
(PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0)))
.raw;
- if (retval != RESULT_INVALID.raw) {
- Core::CPU().SetReg(1, (u32)param_1);
- FuncReturn(retval);
- }
+ Core::CPU().SetReg(1, (u32)param_1);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(s32*, u32*, s32, u32)>
+void Wrap() {
+ s32 param_1 = 0;
+ u32 retval =
+ func(&param_1, (Kernel::Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2), PARAM(3)).raw;
+
+ Core::CPU().SetReg(1, (u32)param_1);
+ FuncReturn(retval);
}
template <ResultCode func(u32, u32, u32, u32, s64)>
@@ -92,9 +97,7 @@ template <ResultCode func(u32, s64)>
void Wrap() {
s32 retval = func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2))).raw;
- if (retval != RESULT_INVALID.raw) {
- FuncReturn(retval);
- }
+ FuncReturn(retval);
}
template <ResultCode func(MemoryInfo*, PageInfo*, u32)>
@@ -226,9 +229,18 @@ void Wrap() {
u32 retval = func(&param_1, &param_2,
reinterpret_cast<const char*>(Memory::GetPointer(PARAM(2))), PARAM(3))
.raw;
- // The first out parameter is moved into R2 and the second is moved into R1.
- Core::CPU().SetReg(1, param_2);
- Core::CPU().SetReg(2, param_1);
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(Kernel::Handle*, Kernel::Handle*)>
+void Wrap() {
+ Kernel::Handle param_1 = 0;
+ Kernel::Handle param_2 = 0;
+ u32 retval = func(&param_1, &param_2).raw;
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
FuncReturn(retval);
}
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 303ca090d..f7f96125a 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -44,6 +44,9 @@ inline u32* GetStaticBuffers(const int offset = 0) {
namespace IPC {
+/// Size of the command buffer area, in 32-bit words.
+constexpr size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
+
// These errors are commonly returned by invalid IPC translations, so alias them here for
// convenience.
// TODO(yuriks): These will probably go away once translation is implemented inside the kernel.
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index d7348c09d..f0d89cffe 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -4,19 +4,28 @@
#pragma once
+#include <array>
+#include <tuple>
+#include <type_traits>
+#include <utility>
#include "core/hle/ipc.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
namespace IPC {
class RequestHelperBase {
protected:
+ Kernel::HLERequestContext* context = nullptr;
u32* cmdbuf;
ptrdiff_t index = 1;
Header header;
public:
+ RequestHelperBase(Kernel::HLERequestContext& context, Header desired_header)
+ : context(&context), cmdbuf(context.CommandBuffer()), header(desired_header) {}
+
RequestHelperBase(u32* command_buffer, Header command_header)
: cmdbuf(command_buffer), header(command_header) {}
@@ -51,12 +60,27 @@ public:
class RequestBuilder : public RequestHelperBase {
public:
+ RequestBuilder(Kernel::HLERequestContext& context, Header command_header)
+ : RequestHelperBase(context, command_header) {
+ // From this point we will start overwriting the existing command buffer, so it's safe to
+ // release all previous incoming Object pointers since they won't be usable anymore.
+ context.ClearIncomingObjects();
+ cmdbuf[0] = header.raw;
+ }
+
+ RequestBuilder(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestBuilder(
+ context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {}
+
RequestBuilder(u32* command_buffer, Header command_header)
: RequestHelperBase(command_buffer, command_header) {
cmdbuf[0] = header.raw;
}
+
explicit RequestBuilder(u32* command_buffer, u32 command_header)
: RequestBuilder(command_buffer, Header{command_header}) {}
+
RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size,
unsigned translate_params_size)
: RequestBuilder(command_buffer,
@@ -88,6 +112,9 @@ public:
template <typename... H>
void PushMoveHandles(H... handles);
+ template <typename... O>
+ void PushObjects(Kernel::SharedPtr<O>... pointers);
+
void PushCurrentPIDHandle();
void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id);
@@ -153,6 +180,11 @@ inline void RequestBuilder::PushMoveHandles(H... handles) {
Push(static_cast<Kernel::Handle>(handles)...);
}
+template <typename... O>
+inline void RequestBuilder::PushObjects(Kernel::SharedPtr<O>... pointers) {
+ PushMoveHandles(context->AddOutgoingHandle(std::move(pointers))...);
+}
+
inline void RequestBuilder::PushCurrentPIDHandle() {
Push(CallingPidDesc());
Push(u32(0));
@@ -171,10 +203,21 @@ inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size,
class RequestParser : public RequestHelperBase {
public:
+ RequestParser(Kernel::HLERequestContext& context, Header desired_header)
+ : RequestHelperBase(context, desired_header) {}
+
+ RequestParser(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestParser(context,
+ Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {
+ }
+
RequestParser(u32* command_buffer, Header command_header)
: RequestHelperBase(command_buffer, command_header) {}
+
explicit RequestParser(u32* command_buffer, u32 command_header)
: RequestParser(command_buffer, Header{command_header}) {}
+
RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size,
unsigned translate_params_size)
: RequestParser(command_buffer,
@@ -186,7 +229,10 @@ public:
ValidateHeader();
Header builderHeader{
MakeHeader(header.command_id, normal_params_size, translate_params_size)};
- return {cmdbuf, builderHeader};
+ if (context != nullptr)
+ return {*context, builderHeader};
+ else
+ return {cmdbuf, builderHeader};
}
template <typename T>
@@ -198,10 +244,52 @@ public:
template <typename First, typename... Other>
void Pop(First& first_value, Other&... other_values);
+ /// Equivalent to calling `PopHandles<1>()[0]`.
Kernel::Handle PopHandle();
+ /**
+ * Pops a descriptor containing `N` handles. The handles are returned as an array. The
+ * descriptor must contain exactly `N` handles, it is not permitted to, for example, call
+ * PopHandles<1>() twice to read a multi-handle descriptor with 2 handles, or to make a single
+ * PopHandles<2>() call to read 2 single-handle descriptors.
+ */
+ template <unsigned int N>
+ std::array<Kernel::Handle, N> PopHandles();
+
+ /// Convenience wrapper around PopHandles() which assigns the handles to the passed references.
template <typename... H>
- void PopHandles(H&... handles);
+ void PopHandles(H&... handles) {
+ std::tie(handles...) = PopHandles<sizeof...(H)>();
+ }
+
+ /// Equivalent to calling `PopGenericObjects<1>()[0]`.
+ Kernel::SharedPtr<Kernel::Object> PopGenericObject();
+
+ /// Equivalent to calling `std::get<0>(PopObjects<T>())`.
+ template <typename T>
+ Kernel::SharedPtr<T> PopObject();
+
+ /**
+ * Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a
+ * handle is invalid, null is returned for that object instead. The same caveats from
+ * PopHandles() apply regarding `N` matching the number of handles in the descriptor.
+ */
+ template <unsigned int N>
+ std::array<Kernel::SharedPtr<Kernel::Object>, N> PopGenericObjects();
+
+ /**
+ * Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to
+ * the passed `T` types, while verifying that the cast is valid. If the type of an object does
+ * not match, null is returned instead.
+ */
+ template <typename... T>
+ std::tuple<Kernel::SharedPtr<T>...> PopObjects();
+
+ /// Convenience wrapper around PopObjects() which assigns the handles to the passed references.
+ template <typename... T>
+ void PopObjects(Kernel::SharedPtr<T>&... pointers) {
+ std::tie(pointers...) = PopObjects<T...>();
+ }
/**
* @brief Pops the static buffer vaddr
@@ -313,15 +401,54 @@ inline Kernel::Handle RequestParser::PopHandle() {
return Pop<Kernel::Handle>();
}
-template <typename... H>
-void RequestParser::PopHandles(H&... handles) {
- const u32 handle_descriptor = Pop<u32>();
- const int handles_number = sizeof...(H);
- DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
- "Tried to pop handle(s) but the descriptor is not a handle descriptor");
- DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor),
- "Number of handles doesn't match the descriptor");
- Pop(static_cast<Kernel::Handle&>(handles)...);
+template <unsigned int N>
+std::array<Kernel::Handle, N> RequestParser::PopHandles() {
+ u32 handle_descriptor = Pop<u32>();
+ ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
+ "Tried to pop handle(s) but the descriptor is not a handle descriptor");
+ ASSERT_MSG(N == HandleNumberFromDesc(handle_descriptor),
+ "Number of handles doesn't match the descriptor");
+
+ std::array<Kernel::Handle, N> handles{};
+ for (Kernel::Handle& handle : handles) {
+ handle = Pop<Kernel::Handle>();
+ }
+ return handles;
+}
+
+inline Kernel::SharedPtr<Kernel::Object> RequestParser::PopGenericObject() {
+ Kernel::Handle handle = PopHandle();
+ return context->GetIncomingHandle(handle);
+}
+
+template <typename T>
+Kernel::SharedPtr<T> RequestParser::PopObject() {
+ return Kernel::DynamicObjectCast<T>(PopGenericObject());
+}
+
+template <unsigned int N>
+inline std::array<Kernel::SharedPtr<Kernel::Object>, N> RequestParser::PopGenericObjects() {
+ std::array<Kernel::Handle, N> handles = PopHandles<N>();
+ std::array<Kernel::SharedPtr<Kernel::Object>, N> pointers;
+ for (int i = 0; i < N; ++i) {
+ pointers[i] = context->GetIncomingHandle(handles[i]);
+ }
+ return pointers;
+}
+
+namespace detail {
+template <typename... T, size_t... I>
+std::tuple<Kernel::SharedPtr<T>...> PopObjectsHelper(
+ std::array<Kernel::SharedPtr<Kernel::Object>, sizeof...(T)>&& pointers,
+ std::index_sequence<I...>) {
+ return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...);
+}
+} // namespace detail
+
+template <typename... T>
+inline std::tuple<Kernel::SharedPtr<T>...> RequestParser::PopObjects() {
+ return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(),
+ std::index_sequence_for<T...>{});
}
inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) {
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 6bc49ff64..646a5cc64 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -8,6 +8,8 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
+#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -16,23 +18,34 @@ ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
// the emulated application.
- if (parent->server) {
- if (parent->server->hle_handler)
- parent->server->hle_handler->ClientDisconnected(parent->server);
+ // Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
+ // will be kept alive until after ClientDisconnected() returns.
+ SharedPtr<ServerSession> server = parent->server;
+ if (server) {
+ std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
+ if (hle_handler)
+ hle_handler->ClientDisconnected(server);
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
// their WaitSynchronization result to 0xC920181A.
+
+ // Clean up the list of client threads with pending requests, they are unneeded now that the
+ // client endpoint is closed.
+ server->pending_requesting_threads.clear();
+ server->currently_handling = nullptr;
}
parent->client = nullptr;
}
-ResultCode ClientSession::SendSyncRequest() {
- // Signal the server session that new data is available
- if (parent->server)
- return parent->server->HandleSyncRequest();
+ResultCode ClientSession::SendSyncRequest(SharedPtr<Thread> thread) {
+ // Keep ServerSession alive until we're done working with it.
+ SharedPtr<ServerSession> server = parent->server;
+ if (server == nullptr)
+ return ERR_SESSION_CLOSED_BY_REMOTE;
- return ERR_SESSION_CLOSED_BY_REMOTE;
+ // Signal the server session that new data is available
+ return server->HandleSyncRequest(std::move(thread));
}
} // namespace
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index 2de379c09..daf521529 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -14,6 +14,7 @@ namespace Kernel {
class ServerSession;
class Session;
+class Thread;
class ClientSession final : public Object {
public:
@@ -34,9 +35,10 @@ public:
/**
* Sends an SyncRequest from the current emulated thread.
+ * @param thread Thread that initiated the request.
* @return ResultCode of the operation.
*/
- ResultCode SendSyncRequest();
+ ResultCode SendSyncRequest(SharedPtr<Thread> thread);
std::string name; ///< Name of client port (optional)
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index b3b60e7df..64aa61460 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -13,6 +13,7 @@ enum {
OutOfHandles = 19,
SessionClosedByRemote = 26,
PortNameTooLong = 30,
+ NoPendingSessions = 35,
WrongPermission = 46,
InvalidBufferDescriptor = 48,
MaxConnectionsReached = 52,
@@ -94,5 +95,9 @@ constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(ErrorDescription::OutOfRange, Error
ErrorLevel::Permanent); // 0xD8E007FD
constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged, ErrorLevel::Info);
+/// Returned when Accept() is called on a port with no sessions to be accepted.
+constexpr ResultCode ERR_NO_PENDING_SESSIONS(ErrCodes::NoPendingSessions, ErrorModule::OS,
+ ErrorSummary::WouldBlock,
+ ErrorLevel::Permanent); // 0xD8401823
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 0922b3f47..5ebe2eca4 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -5,8 +5,10 @@
#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
#include "common/common_types.h"
+#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_session.h"
namespace Kernel {
@@ -21,4 +23,113 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
boost::range::remove_erase(connected_sessions, server_session);
}
+HLERequestContext::HLERequestContext(SharedPtr<ServerSession> session)
+ : session(std::move(session)) {
+ cmd_buf[0] = 0;
+}
+
+HLERequestContext::~HLERequestContext() = default;
+
+SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
+ ASSERT(id_from_cmdbuf < request_handles.size());
+ return request_handles[id_from_cmdbuf];
+}
+
+u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) {
+ request_handles.push_back(std::move(object));
+ return request_handles.size() - 1;
+}
+
+void HLERequestContext::ClearIncomingObjects() {
+ request_handles.clear();
+}
+
+ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
+ Process& src_process,
+ HandleTable& src_table) {
+ IPC::Header header{src_cmdbuf[0]};
+
+ size_t untranslated_size = 1u + header.normal_params_size;
+ size_t command_size = untranslated_size + header.translate_params_size;
+ ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); // TODO(yuriks): Return error
+
+ std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin());
+
+ size_t i = untranslated_size;
+ while (i < command_size) {
+ u32 descriptor = cmd_buf[i] = src_cmdbuf[i];
+ i += 1;
+
+ switch (IPC::GetDescriptorType(descriptor)) {
+ case IPC::DescriptorType::CopyHandle:
+ case IPC::DescriptorType::MoveHandle: {
+ u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
+ ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error
+ for (u32 j = 0; j < num_handles; ++j) {
+ Handle handle = src_cmdbuf[i];
+ SharedPtr<Object> object = nullptr;
+ if (handle != 0) {
+ object = src_table.GetGeneric(handle);
+ ASSERT(object != nullptr); // TODO(yuriks): Return error
+ if (descriptor == IPC::DescriptorType::MoveHandle) {
+ src_table.Close(handle);
+ }
+ }
+
+ cmd_buf[i++] = AddOutgoingHandle(std::move(object));
+ }
+ break;
+ }
+ case IPC::DescriptorType::CallingPid: {
+ cmd_buf[i++] = src_process.process_id;
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor);
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
+ HandleTable& dst_table) const {
+ IPC::Header header{cmd_buf[0]};
+
+ size_t untranslated_size = 1u + header.normal_params_size;
+ size_t command_size = untranslated_size + header.translate_params_size;
+ ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH);
+
+ std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf);
+
+ size_t i = untranslated_size;
+ while (i < command_size) {
+ u32 descriptor = dst_cmdbuf[i] = cmd_buf[i];
+ i += 1;
+
+ switch (IPC::GetDescriptorType(descriptor)) {
+ case IPC::DescriptorType::CopyHandle:
+ case IPC::DescriptorType::MoveHandle: {
+ // HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally
+ u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
+ ASSERT(i + num_handles <= command_size);
+ for (u32 j = 0; j < num_handles; ++j) {
+ SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]);
+ Handle handle = 0;
+ if (object != nullptr) {
+ // TODO(yuriks): Figure out the proper error handling for if this fails
+ handle = dst_table.Create(object).Unwrap();
+ }
+ dst_cmdbuf[i++] = handle;
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor);
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 14f682f44..35795fc1d 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -4,13 +4,24 @@
#pragma once
+#include <array>
#include <memory>
#include <vector>
+#include <boost/container/small_vector.hpp>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/ipc.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/server_session.h"
+
+namespace Service {
+class ServiceFrameworkBase;
+}
namespace Kernel {
-class ServerSession;
+class HandleTable;
+class Process;
/**
* Interface implemented by HLE Session handlers.
@@ -19,6 +30,8 @@ class ServerSession;
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
+ virtual ~SessionRequestHandler() = default;
+
/**
* Handles a sync request from the emulated application.
* @param server_session The ServerSession that was triggered for this sync request,
@@ -27,27 +40,97 @@ public:
* this request (ServerSession, Originator thread, Translated command buffer, etc).
* @returns ResultCode the result code of the translate operation.
*/
- virtual void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) = 0;
+ virtual void HandleSyncRequest(SharedPtr<ServerSession> server_session) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
* associated ServerSession alive for the duration of the connection.
* @param server_session Owning pointer to the ServerSession associated with the connection.
*/
- void ClientConnected(Kernel::SharedPtr<Kernel::ServerSession> server_session);
+ void ClientConnected(SharedPtr<ServerSession> server_session);
/**
* Signals that a client has just disconnected from this HLE handler and releases the
* associated ServerSession.
* @param server_session ServerSession associated with the connection.
*/
- void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session);
+ void ClientDisconnected(SharedPtr<ServerSession> server_session);
protected:
/// List of sessions that are connected to this handler.
/// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list
// for the duration of the connection.
- std::vector<Kernel::SharedPtr<Kernel::ServerSession>> connected_sessions;
+ std::vector<SharedPtr<ServerSession>> connected_sessions;
+};
+
+/**
+ * Class containing information about an in-flight IPC request being handled by an HLE service
+ * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and
+ * when possible use the APIs in this class to service the request.
+ *
+ * HLE handle protocol
+ * ===================
+ *
+ * To avoid needing HLE services to keep a separate handle table, or having to directly modify the
+ * requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel
+ * will decode the incoming handles into object pointers and insert a id in the buffer where the
+ * handle would normally be. The service then calls GetIncomingHandle() with that id to get the
+ * pointer to the object. Similarly, instead of inserting a handle into the command buffer, the
+ * service calls AddOutgoingHandle() and stores the returned id where the handle would normally go.
+ *
+ * The end result is similar to just giving services their own real handle tables, but since these
+ * ids are local to a specific context, it avoids requiring services to manage handles for objects
+ * across multiple calls and ensuring that unneeded handles are cleaned up.
+ */
+class HLERequestContext {
+public:
+ HLERequestContext(SharedPtr<ServerSession> session);
+ ~HLERequestContext();
+
+ /// Returns a pointer to the IPC command buffer for this request.
+ u32* CommandBuffer() {
+ return cmd_buf.data();
+ }
+
+ /**
+ * Returns the session through which this request was made. This can be used as a map key to
+ * access per-client data on services.
+ */
+ SharedPtr<ServerSession> Session() const {
+ return session;
+ }
+
+ /**
+ * Resolves a object id from the request command buffer into a pointer to an object. See the
+ * "HLE handle protocol" section in the class documentation for more details.
+ */
+ SharedPtr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const;
+
+ /**
+ * Adds an outgoing object to the response, returning the id which should be used to reference
+ * it. See the "HLE handle protocol" section in the class documentation for more details.
+ */
+ u32 AddOutgoingHandle(SharedPtr<Object> object);
+
+ /**
+ * Discards all Objects from the context, invalidating all ids. This may be called after reading
+ * out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but
+ * this is not required.
+ */
+ void ClearIncomingObjects();
+
+ /// Populates this context with data from the requesting process/thread.
+ ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process,
+ HandleTable& src_table);
+ /// Writes data from this context back to the requesting process/thread.
+ ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
+ HandleTable& dst_table) const;
+
+private:
+ std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
+ SharedPtr<ServerSession> session;
+ // TODO(yuriks): Check common usage of this and optimize size accordingly
+ boost::container::small_vector<SharedPtr<Object>, 8> request_handles;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 804f23b1c..496d07cb5 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -166,7 +166,7 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin
auto vma = address_space
.MapBackingMemory(mapping.address, target_pointer + offset_into_region,
mapping.size, memory_state)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(vma,
mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite);
}
@@ -176,14 +176,14 @@ void MapSharedPages(VMManager& address_space) {
.MapBackingMemory(Memory::CONFIG_MEMORY_VADDR,
reinterpret_cast<u8*>(&ConfigMem::config_mem),
Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);
auto shared_page_vma = address_space
.MapBackingMemory(Memory::SHARED_PAGE_VADDR,
reinterpret_cast<u8*>(&SharedPage::shared_page),
Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(shared_page_vma, VMAPermission::Read);
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 1c31ec950..522ad2333 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -151,6 +151,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
}
VAddr Process::GetLinearHeapAreaAddress() const {
+ // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of
+ // the extra RAM in the n3DS.
return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR;
}
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 4d20c39a1..49a9cdfa3 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -5,8 +5,10 @@
#include <tuple>
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_port.h"
+#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -14,6 +16,16 @@ namespace Kernel {
ServerPort::ServerPort() {}
ServerPort::~ServerPort() {}
+ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
+ if (pending_sessions.empty()) {
+ return ERR_NO_PENDING_SESSIONS;
+ }
+
+ auto session = std::move(pending_sessions.back());
+ pending_sessions.pop_back();
+ return MakeResult(std::move(session));
+}
+
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index f1419cd46..6fe7c7f2f 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -14,6 +14,7 @@
namespace Kernel {
class ClientPort;
+class ServerSession;
class SessionRequestHandler;
class ServerPort final : public WaitObject {
@@ -41,6 +42,12 @@ public:
}
/**
+ * Accepts a pending incoming connection on this port. If there are no pending sessions, will
+ * return ERR_NO_PENDING_SESSIONS.
+ */
+ ResultVal<SharedPtr<ServerSession>> Accept();
+
+ /**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
@@ -50,8 +57,8 @@ public:
std::string name; ///< Name of port (optional)
- std::vector<SharedPtr<WaitObject>>
- pending_sessions; ///< ServerSessions waiting to be accepted by the port
+ /// ServerSessions waiting to be accepted by the port
+ std::vector<SharedPtr<ServerSession>> pending_sessions;
/// This session's HLE request handler template (optional)
/// ServerSessions created from this port inherit a reference to this handler.
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 2dc709bc9..337896abf 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -32,22 +32,29 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create(std::string name) {
SharedPtr<ServerSession> server_session(new ServerSession);
server_session->name = std::move(name);
- server_session->signaled = false;
server_session->parent = nullptr;
return MakeResult(std::move(server_session));
}
bool ServerSession::ShouldWait(Thread* thread) const {
- return !signaled;
+ // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
+ if (parent->client == nullptr)
+ return false;
+ // Wait if we have no pending requests, or if we're currently handling a request.
+ return pending_requesting_threads.empty() || currently_handling != nullptr;
}
void ServerSession::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- signaled = false;
+ // We are now handling a request, pop it from the stack.
+ // TODO(Subv): What happens if the client endpoint is closed before any requests are made?
+ ASSERT(!pending_requesting_threads.empty());
+ currently_handling = pending_requesting_threads.back();
+ pending_requesting_threads.pop_back();
}
-ResultCode ServerSession::HandleSyncRequest() {
+ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
// similar.
@@ -60,18 +67,21 @@ ResultCode ServerSession::HandleSyncRequest() {
return result;
hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this));
// TODO(Subv): Translate the response command buffer.
+ } else {
+ // Add the thread to the list of threads that have issued a sync request with this
+ // server.
+ pending_requesting_threads.push_back(std::move(thread));
}
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
// on it.
- signaled = true;
WakeupAllWaitingThreads();
return RESULT_SUCCESS;
}
ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name,
SharedPtr<ClientPort> port) {
- auto server_session = ServerSession::Create(name + "_Server").MoveFrom();
+ auto server_session = ServerSession::Create(name + "_Server").Unwrap();
SharedPtr<ClientSession> client_session(new ClientSession);
client_session->name = name + "_Client";
@@ -90,4 +100,4 @@ ResultCode TranslateHLERequest(ServerSession* server_session) {
// TODO(Subv): Implement this function once multiple concurrent processes are supported.
return RESULT_SUCCESS;
}
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 28f365b9e..f4360ddf3 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -9,7 +9,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/session.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -19,6 +18,7 @@ namespace Kernel {
class ClientSession;
class ClientPort;
class ServerSession;
+class Session;
class SessionRequestHandler;
class Thread;
@@ -67,20 +67,30 @@ public:
/**
* Handle a sync request from the emulated application.
+ * @param thread Thread that initiated the request.
* @returns ResultCode from the operation.
*/
- ResultCode HandleSyncRequest();
+ ResultCode HandleSyncRequest(SharedPtr<Thread> thread);
bool ShouldWait(Thread* thread) const override;
void Acquire(Thread* thread) override;
std::string name; ///< The name of this session (optional)
- bool signaled; ///< Whether there's new data available to this ServerSession
std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
std::shared_ptr<SessionRequestHandler>
hle_handler; ///< This session's HLE request handler (optional)
+ /// List of threads that are pending a response after a sync request. This list is processed in
+ /// a LIFO manner, thus, the last request will be dispatched first.
+ /// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
+ std::vector<SharedPtr<Thread>> pending_requesting_threads;
+
+ /// Thread whose request is currently being handled. A request is considered "handled" when a
+ /// response is sent via svcReplyAndReceive.
+ /// TODO(Subv): Find a better name for this.
+ SharedPtr<Thread> currently_handling;
+
private:
ServerSession();
~ServerSession() override;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 922e5ab58..a7b66142f 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -149,7 +149,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
if (base_address == 0 && target_address == 0) {
// Calculate the address at which to map the memory block.
- target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address);
+ target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address).value();
}
// Map the memory block into the target process
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 75ce626f8..f5f2eb2f7 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -389,7 +389,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
- thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
+ thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
thread->owner_process = g_current_process;
// Find the next available TLS index, and mark it as used
@@ -484,7 +484,7 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
Memory::HEAP_VADDR_END);
- SharedPtr<Thread> thread = thread_res.MoveFrom();
+ SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
thread->context.fpscr =
FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 6f2cf3b02..d7ec93672 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -30,7 +30,7 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->name = std::move(name);
timer->initial_delay = 0;
timer->interval_delay = 0;
- timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
+ timer->callback_handle = timer_callback_handle_table.Create(timer).Unwrap();
return timer;
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 5f2cdbb96..47b6e2b23 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -388,13 +388,14 @@ public:
}
/// Asserts that the result succeeded and returns a reference to it.
- T& Unwrap() {
+ T& Unwrap() & {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return **this;
}
- T&& MoveFrom() {
- return std::move(Unwrap());
+ T&& Unwrap() && {
+ ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
+ return std::move(**this);
}
private:
diff --git a/src/core/hle/romfs.cpp b/src/core/hle/romfs.cpp
new file mode 100644
index 000000000..3157df71d
--- /dev/null
+++ b/src/core/hle/romfs.cpp
@@ -0,0 +1,102 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/swap.h"
+#include "core/hle/romfs.h"
+
+namespace RomFS {
+
+struct Header {
+ u32_le header_length;
+ u32_le dir_hash_table_offset;
+ u32_le dir_hash_table_length;
+ u32_le dir_table_offset;
+ u32_le dir_table_length;
+ u32_le file_hash_table_offset;
+ u32_le file_hash_table_length;
+ u32_le file_table_offset;
+ u32_le file_table_length;
+ u32_le data_offset;
+};
+
+static_assert(sizeof(Header) == 0x28, "Header has incorrect size");
+
+struct DirectoryMetadata {
+ u32_le parent_dir_offset;
+ u32_le next_dir_offset;
+ u32_le first_child_dir_offset;
+ u32_le first_file_offset;
+ u32_le same_hash_next_dir_offset;
+ u32_le name_length; // in bytes
+ // followed by directory name
+};
+
+static_assert(sizeof(DirectoryMetadata) == 0x18, "DirectoryMetadata has incorrect size");
+
+struct FileMetadata {
+ u32_le parent_dir_offset;
+ u32_le next_file_offset;
+ u64_le data_offset;
+ u64_le data_length;
+ u32_le same_hash_next_file_offset;
+ u32_le name_length; // in bytes
+ // followed by file name
+};
+
+static_assert(sizeof(FileMetadata) == 0x20, "FileMetadata has incorrect size");
+
+static bool MatchName(const u8* buffer, u32 name_length, const std::u16string& name) {
+ std::vector<char16_t> name_buffer(name_length / sizeof(char16_t));
+ std::memcpy(name_buffer.data(), buffer, name_length);
+ return name == std::u16string(name_buffer.begin(), name_buffer.end());
+}
+
+const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path) {
+ constexpr u32 INVALID_FIELD = 0xFFFFFFFF;
+
+ // Split path into directory names and file name
+ std::vector<std::u16string> dir_names = path;
+ dir_names.pop_back();
+ const std::u16string& file_name = path.back();
+
+ Header header;
+ std::memcpy(&header, romfs, sizeof(header));
+
+ // Find directories of each level
+ DirectoryMetadata dir;
+ const u8* current_dir = romfs + header.dir_table_offset;
+ std::memcpy(&dir, current_dir, sizeof(dir));
+ for (const std::u16string& dir_name : dir_names) {
+ u32 child_dir_offset;
+ child_dir_offset = dir.first_child_dir_offset;
+ while (true) {
+ if (child_dir_offset == INVALID_FIELD) {
+ return nullptr;
+ }
+ const u8* current_child_dir = romfs + header.dir_table_offset + child_dir_offset;
+ std::memcpy(&dir, current_child_dir, sizeof(dir));
+ if (MatchName(current_child_dir + sizeof(dir), dir.name_length, dir_name)) {
+ current_dir = current_child_dir;
+ break;
+ }
+ child_dir_offset = dir.next_dir_offset;
+ }
+ }
+
+ // Find the file
+ FileMetadata file;
+ u32 file_offset = dir.first_file_offset;
+ while (file_offset != INVALID_FIELD) {
+ const u8* current_file = romfs + header.file_table_offset + file_offset;
+ std::memcpy(&file, current_file, sizeof(file));
+ if (MatchName(current_file + sizeof(file), file.name_length, file_name)) {
+ return romfs + header.data_offset + file.data_offset;
+ }
+ file_offset = file.next_file_offset;
+ }
+ return nullptr;
+}
+
+} // namespace RomFS
diff --git a/src/core/hle/romfs.h b/src/core/hle/romfs.h
new file mode 100644
index 000000000..ee9f29760
--- /dev/null
+++ b/src/core/hle/romfs.h
@@ -0,0 +1,22 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+
+namespace RomFS {
+
+/**
+ * Gets the pointer to a file in a RomFS image.
+ * @param romfs The pointer to the RomFS image
+ * @param path A vector containing the directory names and file name of the path to the file
+ * @return the pointer to the file
+ * @todo reimplement this with a full RomFS manager
+ */
+const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path);
+
+} // namespace RomFS
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 4c587e3c8..5c44b43bb 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -6,11 +6,13 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/file_sys/file_backend.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/romfs.h"
#include "core/hle/service/apt/apt.h"
#include "core/hle/service/apt/apt_a.h"
#include "core/hle/service/apt/apt_s.h"
@@ -27,6 +29,7 @@ namespace APT {
/// Handle to shared memory region designated to for shared system font
static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
+static bool shared_font_loaded = false;
static bool shared_font_relocated = false;
static Kernel::SharedPtr<Kernel::Mutex> lock;
@@ -55,8 +58,8 @@ void Initialize(Service::Interface* self) {
u32 flags = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(),
- Kernel::g_handle_table.Create(parameter_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(),
+ Kernel::g_handle_table.Create(parameter_event).Unwrap());
// TODO(bunnei): Check if these events are cleared every time Initialize is called.
notification_event->Clear();
@@ -71,7 +74,7 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
- if (!shared_font_mem) {
+ if (!shared_font_loaded) {
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
rb.Push<u32>(-1); // TODO: Find the right error code
rb.Skip(1 + 2, true);
@@ -82,7 +85,7 @@ void GetSharedFont(Service::Interface* self) {
// The shared font has to be relocated to the new address before being passed to the
// application.
VAddr target_address =
- Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
+ Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address).value();
if (!shared_font_relocated) {
BCFNT::RelocateSharedFont(shared_font_mem, target_address);
shared_font_relocated = true;
@@ -93,7 +96,7 @@ void GetSharedFont(Service::Interface* self) {
// allocated, the real APT service calculates this address by scanning the entire address space
// (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font.
rb.Push(target_address);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).Unwrap());
}
void NotifyToWait(Service::Interface* self) {
@@ -115,7 +118,7 @@ void GetLockHandle(Service::Interface* self) {
rb.Push(RESULT_SUCCESS); // No error
rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable.
rb.Push<u32>(0); // Least significant bit = power button state
- Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom();
+ Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap();
rb.PushCopyHandles(handle_copy);
LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy,
@@ -231,7 +234,7 @@ void ReceiveParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushMoveHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
@@ -261,7 +264,7 @@ void GlanceParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushCopyHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
@@ -644,36 +647,146 @@ void CheckNew3DS(Service::Interface* self) {
LOG_WARNING(Service_APT, "(STUBBED) called");
}
-void Init() {
- AddService(new APT_A_Interface);
- AddService(new APT_S_Interface);
- AddService(new APT_U_Interface);
-
- HLE::Applets::Init();
-
- // Load the shared system font (if available).
+static u32 DecompressLZ11(const u8* in, u8* out) {
+ u32_le decompressed_size;
+ memcpy(&decompressed_size, in, sizeof(u32));
+ in += 4;
+
+ u8 type = decompressed_size & 0xFF;
+ ASSERT(type == 0x11);
+ decompressed_size >>= 8;
+
+ u32 current_out_size = 0;
+ u8 flags = 0, mask = 1;
+ while (current_out_size < decompressed_size) {
+ if (mask == 1) {
+ flags = *(in++);
+ mask = 0x80;
+ } else {
+ mask >>= 1;
+ }
+
+ if (flags & mask) {
+ u8 byte1 = *(in++);
+ u32 length = byte1 >> 4;
+ u32 offset;
+ if (length == 0) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
+ offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1;
+ } else if (length == 1) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ u8 byte4 = *(in++);
+ length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
+ offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1;
+ } else {
+ u8 byte2 = *(in++);
+ length = (byte1 >> 4) + 0x1;
+ offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
+ }
+
+ for (u32 i = 0; i < length; i++) {
+ *out = *(out - offset);
+ ++out;
+ }
+
+ current_out_size += length;
+ } else {
+ *(out++) = *(in++);
+ current_out_size++;
+ }
+ }
+ return decompressed_size;
+}
+
+static bool LoadSharedFont() {
+ // TODO (wwylele): load different font archive for region CHN/KOR/TWN
+ const u64_le shared_font_archive_id_low = 0x0004009b00014002;
+ const u64_le shared_font_archive_id_high = 0x00000001ffffff00;
+ std::vector<u8> shared_font_archive_id(16);
+ std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64));
+ std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64));
+ FileSys::Path archive_path(shared_font_archive_id);
+ auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path);
+ if (archive_result.Failed())
+ return false;
+
+ std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS
+ FileSys::Path file_path(romfs_path);
+ FileSys::Mode open_mode = {};
+ open_mode.read_flag.Assign(1);
+ auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode);
+ if (file_result.Failed())
+ return false;
+
+ auto romfs = std::move(file_result).Unwrap();
+ std::vector<u8> romfs_buffer(romfs->backend->GetSize());
+ romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data());
+ romfs->backend->Close();
+
+ const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"});
+ if (font_file == nullptr)
+ return false;
+
+ struct {
+ u32_le status;
+ u32_le region;
+ u32_le decompressed_size;
+ INSERT_PADDING_WORDS(0x1D);
+ } shared_font_header{};
+ static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size");
+
+ shared_font_header.status = 2; // successfully loaded
+ shared_font_header.region = 1; // region JPN/EUR/USA
+ shared_font_header.decompressed_size =
+ DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80));
+ std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header));
+ *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU"
+
+ return true;
+}
+
+static bool LoadLegacySharedFont() {
+ // This is the legacy method to load shared font.
// The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
// "shared_font.bin" in the Citra "sysdata" directory.
-
std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT;
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
-
if (file.IsOpen()) {
- // Create shared font memory object
- using Kernel::MemoryPermission;
- shared_font_mem =
- Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
- MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
- Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
- // Read shared font data
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
+ return true;
+ }
+
+ return false;
+}
+
+void Init() {
+ AddService(new APT_A_Interface);
+ AddService(new APT_S_Interface);
+ AddService(new APT_U_Interface);
+
+ HLE::Applets::Init();
+
+ using Kernel::MemoryPermission;
+ shared_font_mem =
+ Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
+ MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
+ Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
+
+ if (LoadSharedFont()) {
+ shared_font_loaded = true;
+ } else if (LoadLegacySharedFont()) {
+ LOG_WARNING(Service_APT, "Loaded shared font by legacy method");
+ shared_font_loaded = true;
} else {
- LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str());
- shared_font_mem = nullptr;
+ LOG_WARNING(Service_APT, "Unable to load shared font");
+ shared_font_loaded = false;
}
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
@@ -693,6 +806,7 @@ void Init() {
void Shutdown() {
shared_font_mem = nullptr;
+ shared_font_loaded = false;
shared_font_relocated = false;
lock = nullptr;
notification_event = nullptr;
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
index 57eb39d75..6d2474702 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -78,7 +78,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cmap, data, sizeof(cmap));
// Relocate the offsets in the CMAP section
- cmap.next_cmap_offset += offset;
+ if (cmap.next_cmap_offset != 0)
+ cmap.next_cmap_offset += offset;
memcpy(data, &cmap, sizeof(cmap));
} else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
@@ -86,7 +87,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cwdh, data, sizeof(cwdh));
// Relocate the offsets in the CWDH section
- cwdh.next_cwdh_offset += offset;
+ if (cwdh.next_cwdh_offset != 0)
+ cwdh.next_cwdh_offset += offset;
memcpy(data, &cwdh, sizeof(cwdh));
} else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
diff --git a/src/core/hle/service/boss/boss_p.cpp b/src/core/hle/service/boss/boss_p.cpp
index ee941e228..3990d0d6e 100644
--- a/src/core/hle/service/boss/boss_p.cpp
+++ b/src/core/hle/service/boss/boss_p.cpp
@@ -66,7 +66,10 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00360084, SetTaskQuery, "SetTaskQuery"},
{0x00370084, GetTaskQuery, "GetTaskQuery"},
// boss:p
+ {0x04010082, nullptr, "InitializeSessionPrivileged"},
{0x04040080, nullptr, "GetAppNewFlag"},
+ {0x040D0182, nullptr, "GetNsDataIdListPrivileged"},
+ {0x040E0182, nullptr, "GetNsDataIdListPrivileged1"},
{0x04130082, nullptr, "SendPropertyPrivileged"},
{0x041500C0, nullptr, "DeleteNsDataPrivileged"},
{0x04160142, nullptr, "GetNsDataHeaderInfoPrivileged"},
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 7394c844f..c9f9e9d95 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -347,7 +347,7 @@ void GetVsyncInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -366,7 +366,7 @@ void GetBufferErrorInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -400,7 +400,7 @@ void SetReceiving(Service::Interface* self) {
}
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp
index bd9814244..421006a9e 100644
--- a/src/core/hle/service/cecd/cecd.cpp
+++ b/src/core/hle/service/cecd/cecd.cpp
@@ -31,8 +31,8 @@ void GetCecStateAbbreviated(Service::Interface* self) {
void GetCecInfoEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
@@ -40,8 +40,8 @@ void GetCecInfoEventHandle(Service::Interface* self) {
void GetChangeStateEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 5a7878b31..6624f1711 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -406,7 +406,7 @@ ResultCode UpdateConfigNANDSavegame() {
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
ASSERT_MSG(config_result.Succeeded(), "could not open file");
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
@@ -560,7 +560,7 @@ ResultCode LoadConfigNANDSaveFile() {
// Read the file if it already exists
if (config_result.Succeeded()) {
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
}
diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp
index 1455f20ca..9471ec1ef 100644
--- a/src/core/hle/service/csnd_snd.cpp
+++ b/src/core/hle/service/csnd_snd.cpp
@@ -51,8 +51,8 @@ static void Initialize(Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc(2);
- cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(mutex).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).Unwrap();
LOG_WARNING(Service_CSND, "(STUBBED) called");
}
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 363066d14..7d746054f 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -168,7 +168,7 @@ static void GetSemaphoreEventHandle(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x16, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
// cmd_buff[2] not set
- cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle
+ cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).Unwrap(); // Event handle
LOG_WARNING(Service_DSP, "(STUBBED) called");
}
diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp
index 76ecda8b7..7ad7798da 100644
--- a/src/core/hle/service/frd/frd.cpp
+++ b/src/core/hle/service/frd/frd.cpp
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
#include "core/hle/service/frd/frd.h"
#include "core/hle/service/frd/frd_a.h"
@@ -105,6 +106,48 @@ void GetMyScreenName(Service::Interface* self) {
LOG_WARNING(Service_FRD, "(STUBBED) called");
}
+void UnscrambleLocalFriendCode(Service::Interface* self) {
+ const size_t scrambled_friend_code_size = 12;
+ const size_t friend_code_size = 8;
+
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 2);
+ const u32 friend_code_count = rp.Pop<u32>();
+ size_t in_buffer_size;
+ const VAddr scrambled_friend_codes = rp.PopStaticBuffer(&in_buffer_size, false);
+ ASSERT_MSG(in_buffer_size == (friend_code_count * scrambled_friend_code_size),
+ "Wrong input buffer size");
+
+ size_t out_buffer_size;
+ VAddr unscrambled_friend_codes = rp.PeekStaticBuffer(0, &out_buffer_size);
+ ASSERT_MSG(out_buffer_size == (friend_code_count * friend_code_size),
+ "Wrong output buffer size");
+
+ for (u32 current = 0; current < friend_code_count; ++current) {
+ // TODO(B3N30): Unscramble the codes and compare them against the friend list
+ // Only write 0 if the code isn't in friend list, otherwise write the
+ // unscrambled one
+ //
+ // Code for unscrambling (should be compared to HW):
+ // std::array<u16, 6> scambled_friend_code;
+ // Memory::ReadBlock(scrambled_friend_codes+(current*scrambled_friend_code_size),
+ // scambled_friend_code.data(), scrambled_friend_code_size); std::array<u16, 4>
+ // unscrambled_friend_code; unscrambled_friend_code[0] = scambled_friend_code[0] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[1] = scambled_friend_code[1] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[2] = scambled_friend_code[2] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[3] = scambled_friend_code[3] ^
+ // scambled_friend_code[5];
+
+ u64 result = 0ull;
+ Memory::WriteBlock(unscrambled_friend_codes + (current * sizeof(result)), &result,
+ sizeof(result));
+ }
+
+ LOG_WARNING(Service_FRD, "(STUBBED) called");
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushStaticBuffer(unscrambled_friend_codes, out_buffer_size, 0);
+}
+
void SetClientSdkVersion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h
index e61940ea0..66a87c8cd 100644
--- a/src/core/hle/service/frd/frd.h
+++ b/src/core/hle/service/frd/frd.h
@@ -96,6 +96,19 @@ void GetMyFriendKey(Service::Interface* self);
void GetMyScreenName(Service::Interface* self);
/**
+ * FRD::UnscrambleLocalFriendCode service function
+ * Inputs:
+ * 1 : Friend code count
+ * 2 : ((count * 12) << 14) | 0x402
+ * 3 : Pointer to encoded friend codes. Each is 12 bytes large
+ * 64 : ((count * 8) << 14) | 2
+ * 65 : Pointer to write decoded local friend codes to. Each is 8 bytes large.
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void UnscrambleLocalFriendCode(Service::Interface* self);
+
+/**
* FRD::SetClientSdkVersion service function
* Inputs:
* 1 : Used SDK Version
diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp
index 496f29ca9..6970ff768 100644
--- a/src/core/hle/service/frd/frd_u.cpp
+++ b/src/core/hle/service/frd/frd_u.cpp
@@ -36,7 +36,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00190042, nullptr, "GetFriendFavoriteGame"},
{0x001A00C4, nullptr, "GetFriendInfo"},
{0x001B0080, nullptr, "IsIncludedInFriendList"},
- {0x001C0042, nullptr, "UnscrambleLocalFriendCode"},
+ {0x001C0042, UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"},
{0x001D0002, nullptr, "UpdateGameModeDescription"},
{0x001E02C2, nullptr, "UpdateGameMode"},
{0x001F0042, nullptr, "SendInvitation"},
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 3605ef175..033fbc9aa 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -311,7 +311,7 @@ ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handl
if (backend.Failed())
return backend.Code();
- auto file = std::shared_ptr<File>(new File(backend.MoveFrom(), path));
+ auto file = std::shared_ptr<File>(new File(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<File>>(std::move(file));
}
@@ -401,7 +401,7 @@ ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle arc
if (backend.Failed())
return backend.Code();
- auto directory = std::shared_ptr<Directory>(new Directory(backend.MoveFrom(), path));
+ auto directory = std::shared_ptr<Directory>(new Directory(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<Directory>>(std::move(directory));
}
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 34e1783ec..b9eab7838 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -87,7 +87,7 @@ static void OpenFile(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
rb.PushMoveHandles(
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom());
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap());
} else {
rb.PushMoveHandles(0);
LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str());
@@ -153,7 +153,7 @@ static void OpenFileDirectly(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
cmd_buff[3] = 0;
LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u",
@@ -420,7 +420,7 @@ static void OpenDirectory(Service::Interface* self) {
directory->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s",
dirname_type, dirname_size, dir_path.DebugStr().c_str());
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index 6ff0f4812..88684b82d 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -389,8 +389,8 @@ static void RegisterInterruptRelayQueue(Interface* self) {
} else {
cmd_buff[1] = RESULT_SUCCESS.raw;
}
- cmd_buff[2] = g_thread_id++; // Thread ID
- cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory
+ cmd_buff[2] = g_thread_id++; // Thread ID
+ cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).Unwrap(); // GSP shared memory
g_interrupt_event->Signal(); // TODO(bunnei): Is this correct?
@@ -475,12 +475,11 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
// TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever
// possible/likely
- Memory::RasterizerFlushRegion(
- Memory::VirtualToPhysicalAddress(command.dma_request.source_address),
- command.dma_request.size);
- Memory::RasterizerFlushAndInvalidateRegion(
- Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
- command.dma_request.size);
+ Memory::RasterizerFlushVirtualRegion(command.dma_request.source_address,
+ command.dma_request.size, Memory::FlushMode::Flush);
+ Memory::RasterizerFlushVirtualRegion(command.dma_request.dest_address,
+ command.dma_request.size,
+ Memory::FlushMode::FlushAndInvalidate);
// TODO(Subv): These memory accesses should not go through the application's memory mapping.
// They should go through the GSP module's memory mapping.
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 5255f6dc8..2014b8461 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -253,12 +253,12 @@ void GetIPCHandles(Service::Interface* self) {
cmd_buff[1] = 0; // No error
cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header
// TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling)
- cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom();
- cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom();
- cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom();
- cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom();
- cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap();
+ cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap();
+ cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap();
+ cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap();
+ cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap();
}
void EnableAccelerometer(Service::Interface* self) {
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 0de698003..837413f93 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -145,8 +145,8 @@ static void GetHandles(Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(),
- Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom());
+ rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).Unwrap(),
+ Kernel::g_handle_table.Create(Service::IR::update_event).Unwrap());
}
/**
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index fdecdce64..fbdf7a465 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -337,7 +337,7 @@ void GetReceiveEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -354,7 +354,7 @@ void GetSendEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -394,7 +394,7 @@ static void GetConnectionStatusEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index 35212b59b..23e1ff094 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -160,7 +160,7 @@ static void IsSampling(Interface* self) {
static void GetBufferFullEvent(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).Unwrap();
LOG_WARNING(Service_MIC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index b44a9f668..cb09ed0b7 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -95,7 +95,7 @@ void GetTagInRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
@@ -105,7 +105,7 @@ void GetTagOutOfRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6c4600f25..6dbdff044 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
namespace Service {
@@ -190,7 +191,7 @@ static void InitializeWithVersion(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
sharedmem_size, version, sharedmem_handle);
@@ -215,6 +216,11 @@ static void GetConnectionStatus(Interface* self) {
rb.Push(RESULT_SUCCESS);
rb.PushRaw(connection_status);
+ // Reset the bitmask of changed nodes after each call to this
+ // function to prevent falsely informing games of outstanding
+ // changes in subsequent calls.
+ connection_status.changed_nodes = 0;
+
LOG_DEBUG(Service_NWM, "called");
}
@@ -260,7 +266,7 @@ static void Bind(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(event).Unwrap());
}
/**
@@ -314,8 +320,11 @@ static void BeginHostingNetwork(Interface* self) {
// The host is always the first node
connection_status.network_node_id = 1;
node_info[0].network_node_id = 1;
+ connection_status.nodes[0] = connection_status.network_node_id;
// Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
connection_status.node_bitmask |= 1;
+ // Notify the application that the first node was set.
+ connection_status.changed_nodes |= 1;
// If the game has a preferred channel, use that instead.
if (network_info.channel != 0)
@@ -352,6 +361,8 @@ static void DestroyNetwork(Interface* self) {
// Unschedule the beacon broadcast event.
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
+ // TODO(Subv): Check if connection_status is indeed reset after this call.
+ connection_status = {};
connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);
connection_status_event->Signal();
@@ -363,6 +374,80 @@ static void DestroyNetwork(Interface* self) {
}
/**
+ * NWM_UDS::SendTo service function.
+ * Sends a data frame to the UDS network we're connected to.
+ * Inputs:
+ * 0 : Command header.
+ * 1 : Unknown.
+ * 2 : u16 Destination network node id.
+ * 3 : u8 Data channel.
+ * 4 : Buffer size >> 2
+ * 5 : Data size
+ * 6 : Flags
+ * 7 : Input buffer descriptor
+ * 8 : Input buffer address
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SendTo(Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2);
+
+ rp.Skip(1, false);
+ u16 dest_node_id = rp.Pop<u16>();
+ u8 data_channel = rp.Pop<u8>();
+ rp.Skip(1, false);
+ u32 data_size = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ size_t desc_size;
+ const VAddr input_address = rp.PopStaticBuffer(&desc_size, false);
+ ASSERT(desc_size == data_size);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
+ connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (dest_node_id == connection_status.network_node_id) {
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ return;
+ }
+
+ // TODO(Subv): Do something with the flags.
+
+ constexpr size_t MaxSize = 0x5C6;
+ if (data_size > MaxSize) {
+ rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Usage));
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ Memory::ReadBlock(input_address, data.data(), data.size());
+
+ // TODO(Subv): Increment the sequence number after each sent packet.
+ u16 sequence_number = 0;
+ std::vector<u8> data_payload = GenerateDataPayload(
+ data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
+
+ // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
+ // and encapsulate the payload.
+
+ // TODO(Subv): Send the frame.
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u",
+ static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel));
+}
+
+/**
* NWM_UDS::GetChannel service function.
* Returns the WiFi channel in which the network we're connected to is transmitting.
* Inputs:
@@ -533,6 +618,42 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
beacon_broadcast_event, 0);
}
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u32 GetNextAvailableNodeId() {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+
+ for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
+ if ((connection_status.node_bitmask & (1 << index)) == 0)
+ return index;
+ }
+
+ // Any connection attempts to an already full network should have been refused.
+ ASSERT_MSG(false, "No available connection slots in the network");
+}
+
+/*
+ * Called when a client connects to an UDS network we're hosting,
+ * updates the connection status and signals the update event.
+ * @param network_node_id Network Node Id of the connecting client.
+ */
+void OnClientConnected(u16 network_node_id) {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+ ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes,
+ "Can not accept connections on a full network");
+
+ u32 node_id = GetNextAvailableNodeId();
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = network_node_id;
+ connection_status.total_nodes++;
+ connection_status_event->Signal();
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010442, nullptr, "Initialize (deprecated)"},
{0x00020000, nullptr, "Scrap"},
@@ -554,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00130040, nullptr, "Unbind"},
{0x001400C0, nullptr, "PullPacket"},
{0x00150080, nullptr, "SetMaxSendDelay"},
- {0x00170182, nullptr, "SendTo"},
+ {0x00170182, SendTo, "SendTo"},
{0x001A0000, GetChannel, "GetChannel"},
{0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
{0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 29b146569..141f49f9c 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -24,6 +24,9 @@ const double MillisecondsPerTU = 1.024;
// Interval measured in TU, the default value is 100TU = 102.4ms
const u16 DefaultBeaconInterval = 100;
+/// The maximum number of nodes that can exist in an UDS session.
+constexpr u32 UDSMaxNodes = 16;
+
struct NodeInfo {
u64_le friend_code_seed;
std::array<u16_le, 10> username;
@@ -47,8 +50,8 @@ struct ConnectionStatus {
u32_le status;
INSERT_PADDING_WORDS(1);
u16_le network_node_id;
- INSERT_PADDING_BYTES(2);
- INSERT_PADDING_BYTES(32);
+ u16_le changed_nodes;
+ u16_le nodes[UDSMaxNodes];
u8 total_nodes;
u8 max_nodes;
u16_le node_bitmask;
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index 6df4c4f47..caacf4c6f 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -15,9 +15,6 @@ namespace Service {
namespace NWM {
using MacAddress = std::array<u8, 6>;
-
-/// The maximum number of nodes that can exist in an UDS session.
-constexpr u32 UDSMaxNodes = 16;
constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
/// Additional block tag ids in the Beacon frames
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
new file mode 100644
index 000000000..8c6742dba
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -0,0 +1,278 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <cryptopp/aes.h>
+#include <cryptopp/ccm.h>
+#include <cryptopp/filters.h>
+#include <cryptopp/md5.h>
+#include <cryptopp/modes.h>
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_data.h"
+#include "core/hw/aes/key.h"
+
+namespace Service {
+namespace NWM {
+
+using MacAddress = std::array<u8, 6>;
+
+/*
+ * Generates a SNAP-enabled 802.2 LLC header for the specified protocol.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateLLCHeader(EtherType protocol) {
+ LLCHeader header{};
+ header.protocol = static_cast<u16>(protocol);
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Generates a Nintendo UDS SecureData header with the specified parameters.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id,
+ u16 src_node_id, u16 sequence_number) {
+ SecureDataHeader header{};
+ header.protocol_size = data_size + sizeof(SecureDataHeader);
+ // Note: This size includes everything except the first 4 bytes of the structure,
+ // reinforcing the hypotheses that the first 4 bytes are actually the header of
+ // another container protocol.
+ header.securedata_size = data_size + sizeof(SecureDataHeader) - 4;
+ // Frames sent by the emulated application are never UDS management frames
+ header.is_management = 0;
+ header.data_channel = channel;
+ header.sequence_number = sequence_number;
+ header.dest_node_id = dest_node_id;
+ header.src_node_id = src_node_id;
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Calculates the CTR used for the AES-CTR process that calculates
+ * the CCMP crypto key for data frames.
+ * @returns The CTR used for data frames crypto key generation.
+ */
+static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) {
+ DataFrameCryptoCTR data{};
+
+ data.host_mac = network_info.host_mac_address;
+ data.wlan_comm_id = network_info.wlan_comm_id;
+ data.id = network_info.id;
+ data.network_id = network_info.network_id;
+
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash;
+ CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data));
+
+ return hash;
+}
+
+/*
+ * Generates the key used for encrypting the 802.11 data frames generated by UDS.
+ * @returns The key used for data frames crypto.
+ */
+static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey(
+ const std::vector<u8>& passphrase, const NetworkInfo& network_info) {
+ // Calculate the MD5 hash of the input passphrase.
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash;
+ CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size());
+
+ std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key;
+
+ // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using
+ // keyslot 0x2D.
+ using CryptoPP::AES;
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info);
+ std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey);
+ CryptoPP::CTR_Mode<AES>::Encryption aes;
+ aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data());
+ aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size());
+
+ return ccmp_key;
+}
+
+/*
+ * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame.
+ * @returns a buffer with the bytes of the AAD.
+ */
+static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ // 8.3.3.3.2 Construct AAD (22-30 bytes)
+ // The AAD is constructed from the MPDU header. The AAD does not include the header Duration
+ // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g.,
+ // a rate change during retransmission). For similar reasons, several subfields in the Frame
+ // Control field are masked to 0.
+ struct {
+ u16_be FC; // MPDU Frame Control field
+ MacAddress A1;
+ MacAddress A2;
+ MacAddress A3;
+ u16_be SC; // MPDU Sequence Control field
+ } aad_struct{};
+
+ constexpr u16 AADFrameControlMask = 0x8FC7;
+ aad_struct.FC = frame_control & AADFrameControlMask;
+ aad_struct.SC = 0;
+
+ bool to_ds = (frame_control & (1 << 0)) != 0;
+ bool from_ds = (frame_control & (1 << 1)) != 0;
+ // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration,
+ // however, the 3DS doesn't seem to transmit frames with such combination.
+ ASSERT_MSG(to_ds != from_ds, "Invalid combination");
+
+ // The meaning of the address fields depends on the ToDS and FromDS fields.
+ if (from_ds) {
+ aad_struct.A1 = receiver;
+ aad_struct.A2 = bssid;
+ aad_struct.A3 = sender;
+ }
+
+ if (to_ds) {
+ aad_struct.A1 = bssid;
+ aad_struct.A2 = sender;
+ aad_struct.A3 = receiver;
+ }
+
+ std::vector<u8> aad(sizeof(aad_struct));
+ std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct));
+
+ return aad;
+}
+
+/*
+ * Decrypts the payload of an encrypted 802.11 data frame using the specified key.
+ * @returns The decrypted payload.
+ */
+static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0);
+
+ CryptoPP::AuthenticatedDecryptionFilter df(
+ d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END |
+ CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+
+ // put cipher with mac
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(),
+ encrypted_payload.size() - 8);
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL,
+ encrypted_payload.data() + encrypted_payload.size() - 8, 8);
+
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> pdata(size);
+ df.Get(pdata.data(), size);
+ return pdata;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to decrypt");
+ }
+
+ return {};
+}
+
+/*
+ * Encrypts the payload of an 802.11 data frame using the specified key.
+ * @returns The encrypted payload.
+ */
+static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), payload.size(), 0);
+
+ CryptoPP::AuthenticatedEncryptionFilter df(d);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+
+ // put plaintext
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size());
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> cipher(size);
+ df.Get(cipher.data(), size);
+ return cipher;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to encrypt");
+ }
+
+ return {};
+}
+
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number) {
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
+ std::vector<u8> securedata_header =
+ GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
+
+ buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
+ buffer.insert(buffer.end(), data.begin(), data.end());
+ return buffer;
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
new file mode 100644
index 000000000..a23520a41
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -0,0 +1,78 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+enum class SAP : u8 { SNAPExtensionUsed = 0xAA };
+
+enum class PDUControl : u8 { UnnumberedInformation = 3 };
+
+enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E };
+
+/*
+ * 802.2 header, UDS packets always use SNAP for these headers,
+ * which means the dsap and ssap are always SNAPExtensionUsed (0xAA)
+ * and the OUI is always 0.
+ */
+struct LLCHeader {
+ u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 control = static_cast<u8>(PDUControl::UnnumberedInformation);
+ std::array<u8, 3> OUI = {};
+ u16_be protocol;
+};
+
+static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size");
+
+/*
+ * Nintendo SecureData header, every UDS packet contains one,
+ * it is used to store metadata about the transmission such as
+ * the source and destination network node ids.
+ */
+struct SecureDataHeader {
+ // TODO(Subv): It is likely that the first 4 bytes of this header are
+ // actually part of another container protocol.
+ u16_be protocol_size;
+ INSERT_PADDING_BYTES(2);
+ u16_be securedata_size;
+ u8 is_management;
+ u8 data_channel;
+ u16_be sequence_number;
+ u16_be dest_node_id;
+ u16_be src_node_id;
+};
+
+static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size");
+
+/*
+ * The raw bytes of this structure are the CTR used in the encryption (AES-CTR)
+ * process used to generate the CCMP key for data frame encryption.
+ */
+struct DataFrameCryptoCTR {
+ u32_le wlan_comm_id;
+ u32_le network_id;
+ std::array<u8, 6> host_mac;
+ u16_le id;
+};
+
+static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+
+/**
+ * Generates an unencrypted 802.11 data payload.
+ * @returns The generated frame payload.
+ */
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number);
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 39382ef09..a0b959797 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -152,7 +152,7 @@ void Init() {
auto gamecoin_result =
Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode);
if (gamecoin_result.Succeeded()) {
- auto gamecoin = gamecoin_result.MoveFrom();
+ auto gamecoin = std::move(gamecoin_result).Unwrap();
gamecoin->backend->Write(0, sizeof(GameCoin), true,
reinterpret_cast<const u8*>(&default_game_coin));
gamecoin->backend->Close();
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 0d443aa44..aad950e50 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -2,9 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <fmt/format.h>
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/hle/ipc.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/service/ac/ac.h"
@@ -45,9 +50,14 @@
#include "core/hle/service/ssl_c.h"
#include "core/hle/service/y2r_u.h"
+using Kernel::ClientPort;
+using Kernel::ServerPort;
+using Kernel::ServerSession;
+using Kernel::SharedPtr;
+
namespace Service {
-std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports;
+std::unordered_map<std::string, SharedPtr<ClientPort>> g_kernel_named_ports;
/**
* Creates a function string for logging, complete with the name (or header code, depending
@@ -69,7 +79,7 @@ static std::string MakeFunctionString(const char* name, const char* port_name,
Interface::Interface(u32 max_sessions) : max_sessions(max_sessions) {}
Interface::~Interface() = default;
-void Interface::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) {
+void Interface::HandleSyncRequest(SharedPtr<ServerSession> server_session) {
// TODO(Subv): Make use of the server_session in the HLE service handlers to distinguish which
// session triggered each command.
@@ -103,30 +113,108 @@ void Interface::Register(const FunctionInfo* functions, size_t n) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
+ InvokerFn* handler_invoker)
+ : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
+
+ServiceFrameworkBase::~ServiceFrameworkBase() = default;
+
+void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
+ ASSERT(port == nullptr);
+ port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
+ port->SetHleHandler(shared_from_this());
+}
+
+void ServiceFrameworkBase::InstallAsNamedPort() {
+ ASSERT(port == nullptr);
+ SharedPtr<ServerPort> server_port;
+ SharedPtr<ClientPort> client_port;
+ std::tie(server_port, client_port) = ServerPort::CreatePortPair(max_sessions, service_name);
+ server_port->SetHleHandler(shared_from_this());
+ AddNamedPort(service_name, std::move(client_port));
+}
+
+void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, size_t n) {
+ handlers.reserve(handlers.size() + n);
+ for (size_t i = 0; i < n; ++i) {
+ // Usually this array is sorted by id already, so hint to insert at the end
+ handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]);
+ }
+}
+
+void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info) {
+ IPC::Header header{cmd_buf[0]};
+ int num_params = header.normal_params_size + header.translate_params_size;
+ std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name;
+
+ fmt::MemoryWriter w;
+ w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name,
+ cmd_buf[0]);
+ for (int i = 1; i <= num_params; ++i) {
+ w.write(", [{}]={:#x}", i, cmd_buf[i]);
+ }
+ w << '}';
+
+ LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str());
+ // TODO(bunnei): Hack - ignore error
+ cmd_buf[1] = 0;
+}
+
+void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) {
+ u32* cmd_buf = Kernel::GetCommandBuffer();
+
+ u32 header_code = cmd_buf[0];
+ auto itr = handlers.find(header_code);
+ const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
+ if (info == nullptr || info->handler_callback == nullptr) {
+ return ReportUnimplementedFunction(cmd_buf, info);
+ }
+
+ // TODO(yuriks): The kernel should be the one handling this as part of translation after
+ // everything else is migrated
+ Kernel::HLERequestContext context(std::move(server_session));
+ context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
+ Kernel::g_handle_table);
+
+ LOG_TRACE(Service, "%s",
+ MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str());
+ handler_invoker(this, info->handler_callback, context);
+ context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process,
+ Kernel::g_handle_table);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
// Module interface
+// TODO(yuriks): Move to kernel
+void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
+ g_kernel_named_ports.emplace(std::move(name), std::move(port));
+}
+
static void AddNamedPort(Interface* interface_) {
- Kernel::SharedPtr<Kernel::ServerPort> server_port;
- Kernel::SharedPtr<Kernel::ClientPort> client_port;
+ SharedPtr<ServerPort> server_port;
+ SharedPtr<ClientPort> client_port;
std::tie(server_port, client_port) =
- Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName());
+ ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName());
server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));
- g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port));
+ AddNamedPort(interface_->GetPortName(), std::move(client_port));
}
void AddService(Interface* interface_) {
auto server_port =
SM::g_service_manager
->RegisterService(interface_->GetPortName(), interface_->GetMaxSessions())
- .MoveFrom();
+ .Unwrap();
server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));
}
/// Initialize ServiceManager
void Init() {
- SM::g_service_manager = std::make_unique<SM::ServiceManager>();
- AddNamedPort(new SM::SRV);
+ SM::g_service_manager = std::make_shared<SM::ServiceManager>();
+ SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
+
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 8933d57cc..281ff99bb 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -18,11 +18,16 @@
namespace Kernel {
class ClientPort;
+class ServerPort;
class ServerSession;
}
namespace Service {
+namespace SM {
+class ServiceManager;
+}
+
static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters)
/// Arbitrary default number of maximum connections to an HLE service.
static const u32 DefaultMaxSessions = 10;
@@ -30,6 +35,9 @@ static const u32 DefaultMaxSessions = 10;
/**
* Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a
* table mapping header ids to handler functions.
+ *
+ * @deprecated Use ServiceFramework for new services instead. It allows services to be stateful and
+ * is more extensible going forward.
*/
class Interface : public Kernel::SessionRequestHandler {
public:
@@ -101,6 +109,146 @@ private:
boost::container::flat_map<u32, FunctionInfo> m_functions;
};
+/**
+ * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it
+ * is not meant to be used directly.
+ *
+ * @see ServiceFramework
+ */
+class ServiceFrameworkBase : public Kernel::SessionRequestHandler {
+public:
+ /// Returns the string identifier used to connect to the service.
+ std::string GetServiceName() const {
+ return service_name;
+ }
+
+ /**
+ * Returns the maximum number of sessions that can be connected to this service at the same
+ * time.
+ */
+ u32 GetMaxSessions() const {
+ return max_sessions;
+ }
+
+ /// Creates a port pair and registers this service with the given ServiceManager.
+ void InstallAsService(SM::ServiceManager& service_manager);
+ /// Creates a port pair and registers it on the kernel's global port registry.
+ void InstallAsNamedPort();
+
+ void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override;
+
+protected:
+ /// Member-function pointer type of SyncRequest handlers.
+ template <typename Self>
+ using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&);
+
+private:
+ template <typename T>
+ friend class ServiceFramework;
+
+ struct FunctionInfoBase {
+ u32 expected_header;
+ HandlerFnP<ServiceFrameworkBase> handler_callback;
+ const char* name;
+ };
+
+ using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
+ Kernel::HLERequestContext& ctx);
+
+ ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker);
+ ~ServiceFrameworkBase();
+
+ void RegisterHandlersBase(const FunctionInfoBase* functions, size_t n);
+ void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info);
+
+ /// Identifier string used to connect to the service.
+ std::string service_name;
+ /// Maximum number of concurrent sessions that this service can handle.
+ u32 max_sessions;
+
+ /**
+ * Port where incoming connections will be received. Only created when InstallAsService() or
+ * InstallAsNamedPort() are called.
+ */
+ Kernel::SharedPtr<Kernel::ServerPort> port;
+
+ /// Function used to safely up-cast pointers to the derived class before invoking a handler.
+ InvokerFn* handler_invoker;
+ boost::container::flat_map<u32, FunctionInfoBase> handlers;
+};
+
+/**
+ * Framework for implementing HLE services. Dispatches on the header id of incoming SyncRequests
+ * based on a table mapping header ids to handler functions. Service implementations should inherit
+ * from ServiceFramework using the CRTP (`class Foo : public ServiceFramework<Foo> { ... };`) and
+ * populate it with handlers by calling #RegisterHandlers.
+ *
+ * In order to avoid duplicating code in the binary and exposing too many implementation details in
+ * the header, this class is split into a non-templated base (ServiceFrameworkBase) and a template
+ * deriving from it (ServiceFramework). The functions in this class will mostly only erase the type
+ * of the passed in function pointers and then delegate the actual work to the implementation in the
+ * base class.
+ */
+template <typename Self>
+class ServiceFramework : public ServiceFrameworkBase {
+protected:
+ /// Contains information about a request type which is handled by the service.
+ struct FunctionInfo : FunctionInfoBase {
+ // TODO(yuriks): This function could be constexpr, but clang is the only compiler that
+ // doesn't emit an ICE or a wrong diagnostic because of the static_cast.
+
+ /**
+ * Constructs a FunctionInfo for a function.
+ *
+ * @param expected_header request header in the command buffer which will trigger dispatch
+ * to this handler
+ * @param handler_callback member function in this service which will be called to handle
+ * the request
+ * @param name human-friendly name for the request. Used mostly for logging purposes.
+ */
+ FunctionInfo(u32 expected_header, HandlerFnP<Self> handler_callback, const char* name)
+ : FunctionInfoBase{
+ expected_header,
+ // Type-erase member function pointer by casting it down to the base class.
+ static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback), name} {}
+ };
+
+ /**
+ * Initializes the handler with no functions installed.
+ * @param max_sessions Maximum number of sessions that can be
+ * connected to this service at the same time.
+ */
+ ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions)
+ : ServiceFrameworkBase(service_name, max_sessions, Invoker) {}
+
+ /// Registers handlers in the service.
+ template <size_t N>
+ void RegisterHandlers(const FunctionInfo (&functions)[N]) {
+ RegisterHandlers(functions, N);
+ }
+
+ /**
+ * Registers handlers in the service. Usually prefer using the other RegisterHandlers
+ * overload in order to avoid needing to specify the array size.
+ */
+ void RegisterHandlers(const FunctionInfo* functions, size_t n) {
+ RegisterHandlersBase(functions, n);
+ }
+
+private:
+ /**
+ * This function is used to allow invocation of pointers to handlers stored in the base class
+ * without needing to expose the type of this derived class. Pointers-to-member may require a
+ * fixup when being up or downcast, and thus code that does that needs to know the concrete type
+ * of the derived class in order to invoke one of it's functions through a pointer.
+ */
+ static void Invoker(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
+ Kernel::HLERequestContext& ctx) {
+ // Cast back up to our original types and call the member function
+ (static_cast<Self*>(object)->*static_cast<HandlerFnP<Self>>(member))(ctx);
+ }
+};
+
/// Initialize ServiceManager
void Init();
@@ -110,6 +258,8 @@ void Shutdown();
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC.
extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports;
+/// Adds a port to the named port table
+void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port);
/// Adds a service to the services table
void AddService(Interface* interface_);
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 361f7a0a9..5e7fc68f9 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -3,11 +3,13 @@
// Refer to the license.txt file included.
#include <tuple>
+#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/sm/srv.h"
namespace Service {
namespace SM {
@@ -22,6 +24,14 @@ static ResultCode ValidateServiceName(const std::string& name) {
return RESULT_SUCCESS;
}
+void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self) {
+ ASSERT(self->srv_interface.expired());
+
+ auto srv = std::make_shared<SRV>(self);
+ srv->InstallAsNamedPort();
+ self->srv_interface = srv;
+}
+
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService(
std::string name, unsigned int max_sessions) {
@@ -30,7 +40,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name);
- registered_services.emplace(name, std::move(client_port));
+ registered_services.emplace(std::move(name), std::move(client_port));
return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
}
@@ -53,7 +63,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToSer
return client_port->Connect();
}
-std::unique_ptr<ServiceManager> g_service_manager;
+std::shared_ptr<ServiceManager> g_service_manager;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 5fac5455c..8f0dbf2db 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -20,6 +20,8 @@ class SessionRequestHandler;
namespace Service {
namespace SM {
+class SRV;
+
constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(1, ErrorModule::SRV, ErrorSummary::WouldBlock,
ErrorLevel::Temporary); // 0xD0406401
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(2, ErrorModule::SRV, ErrorSummary::WouldBlock,
@@ -33,17 +35,21 @@ constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::Wr
class ServiceManager {
public:
+ static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
+
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
unsigned int max_sessions);
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
private:
- /// Map of services registered with the "srv:" service, retrieved using GetServiceHandle.
+ std::weak_ptr<SRV> srv_interface;
+
+ /// Map of registered services, retrieved using GetServicePort or ConnectToService.
std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services;
};
-extern std::unique_ptr<ServiceManager> g_service_manager;
+extern std::shared_ptr<ServiceManager> g_service_manager;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp
index 063b1b0fc..352941e69 100644
--- a/src/core/hle/service/sm/srv.cpp
+++ b/src/core/hle/service/sm/srv.cpp
@@ -7,9 +7,11 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/service/sm/sm.h"
@@ -20,8 +22,6 @@ namespace SM {
constexpr int MAX_PENDING_NOTIFICATIONS = 16;
-static Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
-
/**
* SRV::RegisterClient service function
* Inputs:
@@ -31,16 +31,19 @@ static Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
* 0: 0x00010040
* 1: ResultCode
*/
-static void RegisterClient(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::RegisterClient(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x1, 0, 2);
- if (cmd_buff[1] != IPC::CallingPidDesc()) {
- cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); // 0x40
- cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;
+ u32 pid_descriptor = rp.Pop<u32>();
+ if (pid_descriptor != IPC::CallingPidDesc()) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(IPC::ERR_INVALID_BUFFER_DESCRIPTOR);
return;
}
- cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); // 0x10040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ u32 caller_pid = rp.Pop<u32>();
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called");
}
@@ -54,16 +57,15 @@ static void RegisterClient(Interface* self) {
* 2: Translation descriptor: 0x20
* 3: Handle to semaphore signaled on process notification
*/
-static void EnableNotification(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::EnableNotification(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x2, 0, 0);
notification_semaphore =
Kernel::Semaphore::Create(0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification").Unwrap();
- cmd_buff[0] = IPC::MakeHeader(0x2, 0x1, 0x2); // 0x20042
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = IPC::CopyHandleDesc(1);
- cmd_buff[3] = Kernel::g_handle_table.Create(notification_semaphore).MoveFrom();
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushObjects(notification_semaphore);
LOG_WARNING(Service_SRV, "(STUBBED) called");
}
@@ -78,44 +80,50 @@ static void EnableNotification(Interface* self) {
* 1: ResultCode
* 3: Service handle
*/
-static void GetServiceHandle(Interface* self) {
- ResultCode res = RESULT_SUCCESS;
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x5, 4, 0);
+ auto name_buf = rp.PopRaw<std::array<char, 8>>();
+ size_t name_len = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ bool return_port_on_failure = (flags & 1) == 0;
- size_t name_len = cmd_buff[3];
if (name_len > Service::kMaxPortSize) {
- cmd_buff[1] = ERR_INVALID_NAME_SIZE.raw;
- LOG_ERROR(Service_SRV, "called name_len=0x%X, failed with code=0x%08X", name_len,
- cmd_buff[1]);
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ERR_INVALID_NAME_SIZE);
+ LOG_ERROR(Service_SRV, "called name_len=0x%X -> ERR_INVALID_NAME_SIZE", name_len);
return;
}
- std::string name(reinterpret_cast<const char*>(&cmd_buff[1]), name_len);
- bool return_port_on_failure = (cmd_buff[4] & 1) == 0;
+ std::string name(name_buf.data(), name_len);
// TODO(yuriks): Permission checks go here
- auto client_port = g_service_manager->GetServicePort(name);
+ auto client_port = service_manager->GetServicePort(name);
if (client_port.Failed()) {
- cmd_buff[1] = client_port.Code().raw;
- LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(),
- cmd_buff[1]);
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(client_port.Code());
+ LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(),
+ client_port.Code().raw);
return;
}
auto session = client_port.Unwrap()->Connect();
- cmd_buff[1] = session.Code().raw;
if (session.Succeeded()) {
- cmd_buff[3] = Kernel::g_handle_table.Create(session.MoveFrom()).MoveFrom();
- LOG_DEBUG(Service_SRV, "called service=%s, session handle=0x%08X", name.c_str(),
- cmd_buff[3]);
+ LOG_DEBUG(Service_SRV, "called service=%s -> session=%u", name.c_str(),
+ (*session)->GetObjectId());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(session.Code());
+ rb.PushObjects(std::move(session).Unwrap());
} else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && return_port_on_failure) {
- cmd_buff[1] = ERR_MAX_CONNECTIONS_REACHED.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(client_port.MoveFrom()).MoveFrom();
- LOG_WARNING(Service_SRV, "called service=%s, *port* handle=0x%08X", name.c_str(),
- cmd_buff[3]);
+ LOG_WARNING(Service_SRV, "called service=%s -> ERR_MAX_CONNECTIONS_REACHED, *port*=%u",
+ name.c_str(), (*client_port)->GetObjectId());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(ERR_MAX_CONNECTIONS_REACHED);
+ rb.PushObjects(std::move(client_port).Unwrap());
} else {
- LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(),
- cmd_buff[1]);
+ LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), session.Code());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(session.Code());
}
}
@@ -128,13 +136,12 @@ static void GetServiceHandle(Interface* self) {
* 0: 0x00090040
* 1: ResultCode
*/
-static void Subscribe(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 notification_id = cmd_buff[1];
+void SRV::Subscribe(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x9, 1, 0);
+ u32 notification_id = rp.Pop<u32>();
- cmd_buff[0] = IPC::MakeHeader(0x9, 0x1, 0); // 0x90040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
@@ -147,13 +154,12 @@ static void Subscribe(Interface* self) {
* 0: 0x000A0040
* 1: ResultCode
*/
-static void Unsubscribe(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::Unsubscribe(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0xA, 1, 0);
+ u32 notification_id = rp.Pop<u32>();
- u32 notification_id = cmd_buff[1];
-
- cmd_buff[0] = IPC::MakeHeader(0xA, 0x1, 0); // 0xA0040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
@@ -167,43 +173,39 @@ static void Unsubscribe(Interface* self) {
* 0: 0x000C0040
* 1: ResultCode
*/
-static void PublishToSubscriber(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 notification_id = cmd_buff[1];
- u8 flags = cmd_buff[2] & 0xFF;
+void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0xC, 2, 0);
+ u32 notification_id = rp.Pop<u32>();
+ u8 flags = rp.Pop<u8>();
- cmd_buff[0] = IPC::MakeHeader(0xC, 0x1, 0); // 0xC0040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id,
flags);
}
-const Interface::FunctionInfo FunctionTable[] = {
- {0x00010002, RegisterClient, "RegisterClient"},
- {0x00020000, EnableNotification, "EnableNotification"},
- {0x00030100, nullptr, "RegisterService"},
- {0x000400C0, nullptr, "UnregisterService"},
- {0x00050100, GetServiceHandle, "GetServiceHandle"},
- {0x000600C2, nullptr, "RegisterPort"},
- {0x000700C0, nullptr, "UnregisterPort"},
- {0x00080100, nullptr, "GetPort"},
- {0x00090040, Subscribe, "Subscribe"},
- {0x000A0040, Unsubscribe, "Unsubscribe"},
- {0x000B0000, nullptr, "ReceiveNotification"},
- {0x000C0080, PublishToSubscriber, "PublishToSubscriber"},
- {0x000D0040, nullptr, "PublishAndGetSubscriber"},
- {0x000E00C0, nullptr, "IsServiceRegistered"},
-};
-
-SRV::SRV() {
- Register(FunctionTable);
- notification_semaphore = nullptr;
+SRV::SRV(std::shared_ptr<ServiceManager> service_manager)
+ : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) {
+ static const FunctionInfo functions[] = {
+ {0x00010002, &SRV::RegisterClient, "RegisterClient"},
+ {0x00020000, &SRV::EnableNotification, "EnableNotification"},
+ {0x00030100, nullptr, "RegisterService"},
+ {0x000400C0, nullptr, "UnregisterService"},
+ {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"},
+ {0x000600C2, nullptr, "RegisterPort"},
+ {0x000700C0, nullptr, "UnregisterPort"},
+ {0x00080100, nullptr, "GetPort"},
+ {0x00090040, &SRV::Subscribe, "Subscribe"},
+ {0x000A0040, &SRV::Unsubscribe, "Unsubscribe"},
+ {0x000B0000, nullptr, "ReceiveNotification"},
+ {0x000C0080, &SRV::PublishToSubscriber, "PublishToSubscriber"},
+ {0x000D0040, nullptr, "PublishAndGetSubscriber"},
+ {0x000E00C0, nullptr, "IsServiceRegistered"},
+ };
+ RegisterHandlers(functions);
}
-SRV::~SRV() {
- notification_semaphore = nullptr;
-}
+SRV::~SRV() = default;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h
index 4196ca1e2..75cca5184 100644
--- a/src/core/hle/service/sm/srv.h
+++ b/src/core/hle/service/sm/srv.h
@@ -4,21 +4,33 @@
#pragma once
-#include <string>
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
+namespace Kernel {
+class HLERequestContext;
+class Semaphore;
+}
+
namespace Service {
namespace SM {
/// Interface to "srv:" service
-class SRV final : public Interface {
+class SRV final : public ServiceFramework<SRV> {
public:
- SRV();
- ~SRV() override;
+ explicit SRV(std::shared_ptr<ServiceManager> service_manager);
+ ~SRV();
+
+private:
+ void RegisterClient(Kernel::HLERequestContext& ctx);
+ void EnableNotification(Kernel::HLERequestContext& ctx);
+ void GetServiceHandle(Kernel::HLERequestContext& ctx);
+ void Subscribe(Kernel::HLERequestContext& ctx);
+ void Unsubscribe(Kernel::HLERequestContext& ctx);
+ void PublishToSubscriber(Kernel::HLERequestContext& ctx);
- std::string GetPortName() const override {
- return "srv:";
- }
+ std::shared_ptr<ServiceManager> service_manager;
+ Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
};
} // namespace SM
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index bb7bf2d67..57172ddd6 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -275,7 +275,7 @@ static void GetTransferEndEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap();
LOG_DEBUG(Service_Y2R, "called");
}
@@ -587,8 +587,8 @@ static void StartConversion(Interface* self) {
// dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
u32 total_output_size =
conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap);
- Memory::RasterizerFlushAndInvalidateRegion(
- Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size);
+ Memory::RasterizerFlushVirtualRegion(conversion.dst.address, total_output_size,
+ Memory::FlushMode::FlushAndInvalidate);
HW::Y2R::PerformConversion(conversion);
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index e68b9f16a..e4b803046 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -25,6 +25,7 @@
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
@@ -36,8 +37,9 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace SVC
-using Kernel::SharedPtr;
using Kernel::ERR_INVALID_HANDLE;
+using Kernel::Handle;
+using Kernel::SharedPtr;
namespace SVC {
@@ -236,7 +238,7 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) {
// TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
// responds and cause a reschedule.
- return session->SendSyncRequest();
+ return session->SendSyncRequest(Kernel::GetCurrentThread());
}
/// Close a handle
@@ -397,6 +399,112 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
}
}
+/// In a single operation, sends a IPC reply and waits for a new request.
+static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handle_count,
+ Kernel::Handle reply_target) {
+ // 'handles' has to be a valid pointer even if 'handle_count' is 0.
+ if (handles == nullptr)
+ return Kernel::ERR_INVALID_POINTER;
+
+ // Check if 'handle_count' is invalid
+ if (handle_count < 0)
+ return Kernel::ERR_OUT_OF_RANGE;
+
+ using ObjectPtr = SharedPtr<Kernel::WaitObject>;
+ std::vector<ObjectPtr> objects(handle_count);
+
+ for (int i = 0; i < handle_count; ++i) {
+ auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handles[i]);
+ if (object == nullptr)
+ return ERR_INVALID_HANDLE;
+ objects[i] = object;
+ }
+
+ // We are also sending a command reply.
+ // Do not send a reply if the command id in the command buffer is 0xFFFF.
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ IPC::Header header{cmd_buff[0]};
+ if (reply_target != 0 && header.command_id != 0xFFFF) {
+ auto session = Kernel::g_handle_table.Get<Kernel::ServerSession>(reply_target);
+ if (session == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ auto request_thread = std::move(session->currently_handling);
+
+ // Mark the request as "handled".
+ session->currently_handling = nullptr;
+
+ // Error out if there's no request thread or the session was closed.
+ // TODO(Subv): Is the same error code (ClosedByRemote) returned for both of these cases?
+ if (request_thread == nullptr || session->parent->client == nullptr) {
+ *index = -1;
+ return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+ }
+
+ // TODO(Subv): Perform IPC translation from the current thread to request_thread.
+
+ // Note: The scheduler is not invoked here.
+ request_thread->ResumeFromWait();
+ }
+
+ if (handle_count == 0) {
+ *index = 0;
+ // The kernel uses this value as a placeholder for the real error, and returns it when we
+ // pass no handles and do not perform any reply.
+ if (reply_target == 0 || header.command_id == 0xFFFF)
+ return ResultCode(0xE7E3FFFF);
+
+ return RESULT_SUCCESS;
+ }
+
+ auto thread = Kernel::GetCurrentThread();
+
+ // Find the first object that is acquirable in the provided list of objects
+ auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) {
+ return !object->ShouldWait(thread);
+ });
+
+ if (itr != objects.end()) {
+ // We found a ready object, acquire it and set the result value
+ Kernel::WaitObject* object = itr->get();
+ object->Acquire(thread);
+ *index = std::distance(objects.begin(), itr);
+
+ if (object->GetHandleType() == Kernel::HandleType::ServerSession) {
+ auto server_session = static_cast<Kernel::ServerSession*>(object);
+ if (server_session->parent->client == nullptr)
+ return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+
+ // TODO(Subv): Perform IPC translation from the ServerSession to the current thread.
+ }
+ return RESULT_SUCCESS;
+ }
+
+ // No objects were ready to be acquired, prepare to suspend the thread.
+
+ // TODO(Subv): Perform IPC translation upon wakeup.
+
+ // Put the thread to sleep
+ thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
+
+ // Add the thread to each of the objects' waiting threads.
+ for (size_t i = 0; i < objects.size(); ++i) {
+ Kernel::WaitObject* object = objects[i].get();
+ object->AddWaitingThread(thread);
+ }
+
+ thread->wait_objects = std::move(objects);
+
+ Core::System::GetInstance().PrepareReschedule();
+
+ // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
+ // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error.
+ // By default the index is set to -1.
+ thread->wait_set_output = true;
+ *index = -1;
+ return RESULT_SUCCESS;
+}
+
/// Create an address arbiter (to allocate access to shared resources)
static ResultCode CreateAddressArbiter(Kernel::Handle* out_handle) {
using Kernel::AddressArbiter;
@@ -933,7 +1041,6 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
using Kernel::ServerPort;
using Kernel::ClientPort;
- using Kernel::SharedPtr;
auto ports = ServerPort::CreatePortPair(max_sessions);
CASCADE_RESULT(*client_port, Kernel::g_handle_table.Create(
@@ -947,6 +1054,41 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
return RESULT_SUCCESS;
}
+static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) {
+ using Kernel::ClientPort;
+ SharedPtr<ClientPort> client_port = Kernel::g_handle_table.Get<ClientPort>(client_port_handle);
+ if (client_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, client_port->Connect());
+ CASCADE_RESULT(*out_client_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
+static ResultCode CreateSession(Handle* server_session, Handle* client_session) {
+ auto sessions = Kernel::ServerSession::CreateSessionPair();
+
+ auto& server = std::get<SharedPtr<Kernel::ServerSession>>(sessions);
+ CASCADE_RESULT(*server_session, Kernel::g_handle_table.Create(std::move(server)));
+
+ auto& client = std::get<SharedPtr<Kernel::ClientSession>>(sessions);
+ CASCADE_RESULT(*client_session, Kernel::g_handle_table.Create(std::move(client)));
+
+ LOG_TRACE(Kernel_SVC, "called");
+ return RESULT_SUCCESS;
+}
+
+static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) {
+ using Kernel::ServerPort;
+ SharedPtr<ServerPort> server_port = Kernel::g_handle_table.Get<ServerPort>(server_port_handle);
+ if (server_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, server_port->Accept());
+ CASCADE_RESULT(*out_server_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) {
using Kernel::MemoryRegion;
@@ -1121,14 +1263,14 @@ static const FunctionDef SVC_Table[] = {
{0x45, nullptr, "Unknown"},
{0x46, nullptr, "Unknown"},
{0x47, HLE::Wrap<CreatePort>, "CreatePort"},
- {0x48, nullptr, "CreateSessionToPort"},
- {0x49, nullptr, "CreateSession"},
- {0x4A, nullptr, "AcceptSession"},
+ {0x48, HLE::Wrap<CreateSessionToPort>, "CreateSessionToPort"},
+ {0x49, HLE::Wrap<CreateSession>, "CreateSession"},
+ {0x4A, HLE::Wrap<AcceptSession>, "AcceptSession"},
{0x4B, nullptr, "ReplyAndReceive1"},
{0x4C, nullptr, "ReplyAndReceive2"},
{0x4D, nullptr, "ReplyAndReceive3"},
{0x4E, nullptr, "ReplyAndReceive4"},
- {0x4F, nullptr, "ReplyAndReceive"},
+ {0x4F, HLE::Wrap<ReplyAndReceive>, "ReplyAndReceive"},
{0x50, nullptr, "BindInterrupt"},
{0x51, nullptr, "UnbindInterrupt"},
{0x52, nullptr, "InvalidateProcessDataCache"},
diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h
index b01d04f13..c9f1342f4 100644
--- a/src/core/hw/aes/key.h
+++ b/src/core/hw/aes/key.h
@@ -12,6 +12,8 @@ namespace HW {
namespace AES {
enum KeySlotID : size_t {
+ // AES Keyslot used to generate the UDS data frame CCMP key.
+ UDSDataKey = 0x2D,
APTWrap = 0x31,
MaxKeySlotID = 0x40,
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 42809c731..6838e449c 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -5,6 +5,7 @@
#include <cstring>
#include <numeric>
#include <type_traits>
+#include "common/alignment.h"
#include "common/color.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -313,7 +314,7 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
const PAddr src_addr = config.GetPhysicalInputAddress();
const PAddr dst_addr = config.GetPhysicalOutputAddress();
- // TODO: do hwtest with these cases
+ // TODO: do hwtest with invalid addresses
if (!Memory::IsValidPhysicalAddress(src_addr)) {
LOG_CRITICAL(HW_GPU, "invalid input address 0x%08X", src_addr);
return;
@@ -324,31 +325,36 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
return;
}
- if (config.texture_copy.input_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero input width");
+ if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
return;
- }
- if (config.texture_copy.output_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero output width");
+ u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
+ u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
+
+ u32 remaining_size = Common::AlignDown(config.texture_copy.size, 16);
+
+ if (remaining_size == 0) {
+ LOG_CRITICAL(HW_GPU, "zero size. Real hardware freezes on this.");
return;
}
- if (config.texture_copy.size == 0) {
- LOG_CRITICAL(HW_GPU, "zero size");
+ u32 input_gap = config.texture_copy.input_gap * 16;
+ u32 output_gap = config.texture_copy.output_gap * 16;
+
+ // Zero gap means contiguous input/output even if width = 0. To avoid infinite loop below, width
+ // is assigned with the total size if gap = 0.
+ u32 input_width = input_gap == 0 ? remaining_size : config.texture_copy.input_width * 16;
+ u32 output_width = output_gap == 0 ? remaining_size : config.texture_copy.output_width * 16;
+
+ if (input_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero input width. Real hardware freezes on this.");
return;
}
- if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
+ if (output_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero output width. Real hardware freezes on this.");
return;
-
- u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
- u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
-
- u32 input_width = config.texture_copy.input_width * 16;
- u32 input_gap = config.texture_copy.input_gap * 16;
- u32 output_width = config.texture_copy.output_width * 16;
- u32 output_gap = config.texture_copy.output_gap * 16;
+ }
size_t contiguous_input_size =
config.texture_copy.size / input_width * (input_width + input_gap);
@@ -360,7 +366,6 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(),
static_cast<u32>(contiguous_output_size));
- u32 remaining_size = config.texture_copy.size;
u32 remaining_input = input_width;
u32 remaining_output = output_width;
while (remaining_size > 0) {
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index bdd997b2a..21b127fee 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -225,7 +225,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x1);
struct {
- u32 size;
+ u32 size; // The lower 4 bits are ignored
union {
u32 input_size;
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index ffc019560..fc4d14a59 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -342,9 +342,11 @@ ResultStatus AppLoader_NCCH::Load() {
if (result != ResultStatus::Success)
return result;
- LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id);
+ std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)};
- Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id);
+ LOG_INFO(Loader, "Program ID: %s", program_id.c_str());
+
+ Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
is_loaded = true; // Set state to loaded
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index b8438e490..65649d9d7 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -83,19 +83,13 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
- u32 end = base + size;
+ RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
+ FlushMode::FlushAndInvalidate);
+ u32 end = base + size;
while (base != end) {
ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
- // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
- // null here
- if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory ||
- current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS),
- PAGE_SIZE);
- }
-
current_page_table->attributes[base] = type;
current_page_table->pointers[base] = memory;
current_page_table->cached_res_count[base] = 0;
@@ -139,7 +133,12 @@ void UnmapRegion(VAddr base, u32 size) {
static u8* GetPointerFromVMA(VAddr vaddr) {
u8* direct_pointer = nullptr;
- auto& vma = Kernel::g_current_process->vm_manager.FindVMA(vaddr)->second;
+ auto& vm_manager = Kernel::g_current_process->vm_manager;
+
+ auto it = vm_manager.FindVMA(vaddr);
+ ASSERT(it != vm_manager.vma_map.end());
+
+ auto& vma = it->second;
switch (vma.type) {
case Kernel::VMAType::AllocatedMemoryBlock:
direct_pointer = vma.backing_block->data() + vma.offset;
@@ -147,6 +146,8 @@ static u8* GetPointerFromVMA(VAddr vaddr) {
case Kernel::VMAType::BackingMemory:
direct_pointer = vma.backing_memory;
break;
+ case Kernel::VMAType::Free:
+ return nullptr;
default:
UNREACHABLE();
}
@@ -189,7 +190,7 @@ T Read(const VAddr vaddr) {
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
T value;
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
@@ -198,8 +199,7 @@ T Read(const VAddr vaddr) {
case PageType::Special:
return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
case PageType::RasterizerCachedSpecial: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
}
default:
@@ -229,8 +229,7 @@ void Write(const VAddr vaddr, const T data) {
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
break;
}
@@ -238,8 +237,7 @@ void Write(const VAddr vaddr, const T data) {
WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
break;
case PageType::RasterizerCachedSpecial: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
break;
}
@@ -268,7 +266,8 @@ bool IsValidVirtualAddress(const VAddr vaddr) {
}
bool IsValidPhysicalAddress(const PAddr paddr) {
- return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr));
+ boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(paddr);
+ return vaddr && IsValidVirtualAddress(*vaddr);
}
u8* GetPointer(const VAddr vaddr) {
@@ -301,7 +300,8 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
u8* GetPhysicalPointer(PAddr address) {
// TODO(Subv): This call should not go through the application's memory mapping.
- return GetPointer(PhysicalToVirtualAddress(address));
+ boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(address);
+ return vaddr ? GetPointer(*vaddr) : nullptr;
}
void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
@@ -312,8 +312,12 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
u32 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1;
PAddr paddr = start;
- for (unsigned i = 0; i < num_pages; ++i) {
- VAddr vaddr = PhysicalToVirtualAddress(paddr);
+ for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) {
+ boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr);
+ if (!maybe_vaddr)
+ continue;
+ VAddr vaddr = *maybe_vaddr;
+
u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS];
ASSERT_MSG(count_delta <= UINT8_MAX - res_count,
"Rasterizer resource cache counter overflow!");
@@ -341,11 +345,19 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
if (res_count == 0) {
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (page_type) {
- case PageType::RasterizerCachedMemory:
- page_type = PageType::Memory;
- current_page_table->pointers[vaddr >> PAGE_BITS] =
- GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ case PageType::RasterizerCachedMemory: {
+ u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ if (pointer == nullptr) {
+ // It's possible that this function has called been while updating the pagetable
+ // after unmapping a VMA. In that case the underlying VMA will no longer exist,
+ // and we should just leave the pagetable entry blank.
+ page_type = PageType::Unmapped;
+ } else {
+ page_type = PageType::Memory;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
+ }
break;
+ }
case PageType::RasterizerCachedSpecial:
page_type = PageType::Special;
break;
@@ -353,7 +365,6 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
UNREACHABLE();
}
}
- paddr += PAGE_SIZE;
}
}
@@ -364,11 +375,48 @@ void RasterizerFlushRegion(PAddr start, u32 size) {
}
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) {
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+ // null here
if (VideoCore::g_renderer != nullptr) {
VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size);
}
}
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+ // null here
+ if (VideoCore::g_renderer != nullptr) {
+ VAddr end = start + size;
+
+ auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
+ if (start >= region_end || end <= region_start) {
+ // No overlap with region
+ return;
+ }
+
+ VAddr overlap_start = std::max(start, region_start);
+ VAddr overlap_end = std::min(end, region_end);
+
+ PAddr physical_start = TryVirtualToPhysicalAddress(overlap_start).value();
+ u32 overlap_size = overlap_end - overlap_start;
+
+ auto* rasterizer = VideoCore::g_renderer->Rasterizer();
+ switch (mode) {
+ case FlushMode::Flush:
+ rasterizer->FlushRegion(physical_start, overlap_size);
+ break;
+ case FlushMode::FlushAndInvalidate:
+ rasterizer->FlushAndInvalidateRegion(physical_start, overlap_size);
+ break;
+ }
+ };
+
+ CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END);
+ CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END);
+ CheckRegion(VRAM_VADDR, VRAM_VADDR_END);
+ }
+}
+
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -415,16 +463,13 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
break;
}
@@ -485,18 +530,13 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
break;
}
@@ -542,18 +582,13 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
break;
}
@@ -598,15 +633,13 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
std::vector<u8> buffer(copy_amount);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
@@ -665,7 +698,7 @@ void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data)
mmio_handler->Write64(addr, data);
}
-PAddr VirtualToPhysicalAddress(const VAddr addr) {
+boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
if (addr == 0) {
return 0;
} else if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) {
@@ -682,12 +715,20 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) {
return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
}
- LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
- // To help with debugging, set bit on address so that it's obviously invalid.
- return addr | 0x80000000;
+ return boost::none;
+}
+
+PAddr VirtualToPhysicalAddress(const VAddr addr) {
+ auto paddr = TryVirtualToPhysicalAddress(addr);
+ if (!paddr) {
+ LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
+ // To help with debugging, set bit on address so that it's obviously invalid.
+ return addr | 0x80000000;
+ }
+ return *paddr;
}
-VAddr PhysicalToVirtualAddress(const PAddr addr) {
+boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
if (addr == 0) {
return 0;
} else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
@@ -702,9 +743,7 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) {
return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
}
- LOG_ERROR(HW_Memory, "Unknown physical address @ 0x%08X", addr);
- // To help with debugging, set bit on address so that it's obviously invalid.
- return addr | 0x80000000;
+ return boost::none;
}
} // namespace
diff --git a/src/core/memory.h b/src/core/memory.h
index 802aa465e..c8c56babd 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <boost/optional.hpp>
#include "common/common_types.h"
namespace Memory {
@@ -55,8 +56,10 @@ enum : PAddr {
/// Main FCRAM
FCRAM_PADDR = 0x20000000,
- FCRAM_SIZE = 0x08000000, ///< FCRAM size (128MB)
+ FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
+ FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
+ FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
@@ -146,15 +149,23 @@ u8* GetPointer(VAddr virtual_address);
std::string ReadCString(VAddr virtual_address, std::size_t max_length);
/**
-* Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
-* address. This should be used by services to translate addresses for use by the hardware.
-*/
+ * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
+ * address. This should be used by services to translate addresses for use by the hardware.
+ */
+boost::optional<PAddr> TryVirtualToPhysicalAddress(VAddr addr);
+
+/**
+ * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
+ * address. This should be used by services to translate addresses for use by the hardware.
+ *
+ * @deprecated Use TryVirtualToPhysicalAddress(), which reports failure.
+ */
PAddr VirtualToPhysicalAddress(VAddr addr);
/**
-* Undoes a mapping performed by VirtualToPhysicalAddress().
-*/
-VAddr PhysicalToVirtualAddress(PAddr addr);
+ * Undoes a mapping performed by VirtualToPhysicalAddress().
+ */
+boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr);
/**
* Gets a pointer to the memory region beginning at the specified physical address.
@@ -179,6 +190,19 @@ void RasterizerFlushRegion(PAddr start, u32 size);
*/
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
+enum class FlushMode {
+ /// Write back modified surfaces to RAM
+ Flush,
+ /// Write back modified surfaces to RAM, and also remove them from the cache
+ FlushAndInvalidate,
+};
+
+/**
+ * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
+ * address region.
+ */
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode);
+
/**
* Dynarmic has an optimization to memory accesses when the pointer to the page exists that
* can be used by setting up the current page table as a callback. This function is used to
diff --git a/src/core/settings.h b/src/core/settings.h
index 03c64c94c..ee16bb90a 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -126,6 +126,9 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
+
+ // WebService
+ std::string telemetry_endpoint_url;
} extern values;
// a special value for Values::region_value indicating that citra will automatically select a region
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index ddc8b262e..841d6cfa1 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -4,32 +4,94 @@
#include <cstring>
+#include "common/assert.h"
#include "common/scm_rev.h"
+#include "common/x64/cpu_detect.h"
+#include "core/settings.h"
#include "core/telemetry_session.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/telemetry_json.h"
+#endif
+
namespace Core {
+static const char* CpuVendorToStr(Common::CPUVendor vendor) {
+ switch (vendor) {
+ case Common::CPUVendor::INTEL:
+ return "Intel";
+ case Common::CPUVendor::AMD:
+ return "Amd";
+ case Common::CPUVendor::OTHER:
+ return "Other";
+ }
+ UNREACHABLE();
+}
+
TelemetrySession::TelemetrySession() {
- // TODO(bunnei): Replace with a backend that logs to our web service
+#ifdef ENABLE_WEB_SERVICE
+ backend = std::make_unique<WebService::TelemetryJson>();
+#else
backend = std::make_unique<Telemetry::NullVisitor>();
-
+#endif
// Log one-time session start information
- const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
- const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
- AddField(Telemetry::FieldType::Session, "StartTime", start_time);
+ const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
- // Log one-time application information
+ // Log application information
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
- AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty);
- AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch);
- AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev);
+ AddField(Telemetry::FieldType::App, "Git_IsDirty", is_git_dirty);
+ AddField(Telemetry::FieldType::App, "Git_Branch", Common::g_scm_branch);
+ AddField(Telemetry::FieldType::App, "Git_Revision", Common::g_scm_rev);
+
+ // Log user system information
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
+ Common::GetCPUCaps().brand_string);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Vendor",
+ CpuVendorToStr(Common::GetCPUCaps().vendor));
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSSE3",
+ Common::GetCPUCaps().ssse3);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE41",
+ Common::GetCPUCaps().sse4_1);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
+ Common::GetCPUCaps().sse4_2);
+
+ // Log user configuration information
+ AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
+ Settings::values.enable_audio_stretching);
+ AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
+ Settings::values.resolution_factor);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
+ Settings::values.toggle_framelimit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwRenderer",
+ Settings::values.use_hw_renderer);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit",
+ Settings::values.use_shader_jit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.use_vsync);
+ AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds", Settings::values.is_new_3ds);
+ AddField(Telemetry::FieldType::UserConfig, "System_RegionValue", Settings::values.region_value);
}
TelemetrySession::~TelemetrySession() {
// Log one-time session end information
- const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
- const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
- AddField(Telemetry::FieldType::Session, "EndTime", end_time);
+ const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
// Complete the session, submitting to web service if necessary
// This is just a placeholder to wrap up the session once the core completes and this is