diff options
Diffstat (limited to 'src/core')
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(¶m_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(¶m_1, ¶m_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(¶m_1, ¶m_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 |
