diff options
Diffstat (limited to 'src')
52 files changed, 1443 insertions, 108 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a45439481..e11940f59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(common) add_subdirectory(core) add_subdirectory(video_core) add_subdirectory(audio_core) +add_subdirectory(network) add_subdirectory(input_common) add_subdirectory(tests) if (ENABLE_SDL2) @@ -13,3 +14,6 @@ endif() if (ENABLE_QT) add_subdirectory(citra_qt) endif() +if (ENABLE_WEB_SERVICE) + add_subdirectory(web_service) +endif() diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index dd357ff72..14574e56c 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -18,7 +18,10 @@ #endif #ifdef _WIN32 +// windows.h needs to be included before shellapi.h #include <windows.h> + +#include <shellapi.h> #endif #include "citra/config.h" diff --git a/src/citra/config.cpp b/src/citra/config.cpp index f08b4069c..69247b166 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -88,9 +88,9 @@ void Config::ReadValues() { Settings::values.toggle_framelimit = sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true); - Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0); - Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0); - Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 1.0); + Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); + Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); + Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); // Layout Settings::values.layout_option = @@ -151,6 +151,10 @@ void Config::ReadValues() { Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); Settings::values.gdbstub_port = static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); + + // Web Service + Settings::values.telemetry_endpoint_url = sdl2_config->Get( + "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); } void Config::Reload() { diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index d8a8fe44f..a12498e0f 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -168,5 +168,9 @@ log_filter = *:Info # Port for listening to GDB connections. use_gdbstub=false gdbstub_port=24689 + +[WebService] +# Endpoint URL for submitting telemetry data +telemetry_endpoint_url = )"; } diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 4841cbf05..9572d3e28 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -91,7 +91,7 @@ if (APPLE) else() add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS}) endif() -target_link_libraries(citra-qt PRIVATE audio_core common core input_common video_core) +target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core) target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets) target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index a8a4aed8b..30554890f 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -17,6 +17,7 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" +#include "network/network.h" EmuThread::EmuThread(GRenderWindow* render_window) : exec_step(false), running(false), stop_run(false), render_window(render_window) {} @@ -110,10 +111,12 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setWindowTitle(QString::fromStdString(window_title)); InputCommon::Init(); + Network::Init(); } GRenderWindow::~GRenderWindow() { InputCommon::Shutdown(); + Network::Shutdown(); } void GRenderWindow::moveContext() { diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 2b99447ec..40142b6d9 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -70,9 +70,9 @@ void Config::ReadValues() { Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool(); Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool(); - Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat(); - Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat(); - Settings::values.bg_blue = qt_config->value("bg_blue", 1.0).toFloat(); + Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat(); + Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat(); + Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat(); qt_config->endGroup(); qt_config->beginGroup("Layout"); @@ -133,6 +133,13 @@ void Config::ReadValues() { Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); qt_config->endGroup(); + qt_config->beginGroup("WebService"); + Settings::values.telemetry_endpoint_url = + qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry") + .toString() + .toStdString(); + qt_config->endGroup(); + qt_config->beginGroup("UI"); qt_config->beginGroup("UILayout"); @@ -268,6 +275,11 @@ void Config::SaveValues() { qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); qt_config->endGroup(); + qt_config->beginGroup("WebService"); + qt_config->setValue("telemetry_endpoint_url", + QString::fromStdString(Settings::values.telemetry_endpoint_url)); + qt_config->endGroup(); + qt_config->beginGroup("UI"); qt_config->beginGroup("UILayout"); diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index bbbb0e3f4..96638ebdb 100644 --- a/src/citra_qt/configuration/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui @@ -23,6 +23,13 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> + <widget class="QLabel"> + <property name="text"> + <string>The GDB Stub only works correctly when the CPU JIT is off.</string> + </property> + </widget> + </item> + <item> <layout class="QHBoxLayout" name="horizontalLayout_3"> <item> <widget class="QCheckBox" name="toggle_gdbstub"> diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 42f6a9918..4b83eeb28 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -72,7 +72,9 @@ namespace Log { SUB(Audio, DSP) \ SUB(Audio, Sink) \ CLS(Input) \ - CLS(Loader) + CLS(Network) \ + CLS(Loader) \ + CLS(WebService) // GetClassName is a macro defined by Windows.h, grrr... const char* GetLogClassName(Class log_class) { diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 1b905f66c..fe4dfed69 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -90,6 +90,8 @@ enum class Class : ClassType { Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader Input, ///< Input emulation + Network, ///< Network emulation + WebService, ///< Interface to Citra Web Services Count ///< Total number of logging classes }; diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 9d423766f..f71e748d1 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -6,7 +6,6 @@ #include <cstdio> #ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN #include <windows.h> #endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2408e659c..360f407f3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -145,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 @@ -343,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 @@ -388,3 +390,6 @@ 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 fmt) +if (ENABLE_WEB_SERVICE) + target_link_libraries(core PUBLIC json-headers web_service) +endif() 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/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index fef97af1f..646a5cc64 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -9,6 +9,7 @@ #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 { @@ -27,19 +28,24 @@ ClientSession::~ClientSession() { // 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() { +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; // Signal the server session that new data is available - return server->HandleSyncRequest(); + 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 1cac1d0c9..5ebe2eca4 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -67,10 +67,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr 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 = src_table.GetGeneric(handle); - ASSERT(object != nullptr); // TODO(yuriks): Return error - if (descriptor == IPC::DescriptorType::MoveHandle) { - src_table.Close(handle); + 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)); @@ -112,9 +115,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P ASSERT(i + num_handles <= command_size); for (u32 j = 0; j < num_handles; ++j) { SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]); - - // TODO(yuriks): Figure out the proper error handling for if this fails - Handle handle = dst_table.Create(object).Unwrap(); + 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; 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 d197137c3..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,11 +67,14 @@ 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; } @@ -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 5365605da..f4360ddf3 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -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/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/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index a7149c9e8..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 { @@ -373,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: @@ -600,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/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/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/memory.cpp b/src/core/memory.cpp index b8438e490..9024f4922 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -139,7 +139,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 +152,8 @@ static u8* GetPointerFromVMA(VAddr vaddr) { case Kernel::VMAType::BackingMemory: direct_pointer = vma.backing_memory; break; + case Kernel::VMAType::Free: + return nullptr; default: UNREACHABLE(); } @@ -341,11 +348,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; 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..70eff4340 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -7,12 +7,18 @@ #include "common/scm_rev.h" #include "core/telemetry_session.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/telemetry_json.h" +#endif + namespace Core { 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()}; diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt new file mode 100644 index 000000000..aeabe430e --- /dev/null +++ b/src/network/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SRCS + network.cpp + room.cpp + room_member.cpp + ) + +set(HEADERS + network.h + room.h + room_member.h + ) + +create_directory_groups(${SRCS} ${HEADERS}) + +add_library(network STATIC ${SRCS} ${HEADERS}) +target_link_libraries(network PRIVATE common enet) diff --git a/src/network/network.cpp b/src/network/network.cpp new file mode 100644 index 000000000..51b5d6a9f --- /dev/null +++ b/src/network/network.cpp @@ -0,0 +1,50 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "enet/enet.h" +#include "network/network.h" + +namespace Network { + +static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games +static std::shared_ptr<Room> g_room; ///< Room (Server) for network games +// TODO(B3N30): Put these globals into a networking class + +bool Init() { + if (enet_initialize() != 0) { + LOG_ERROR(Network, "Error initalizing ENet"); + return false; + } + g_room = std::make_shared<Room>(); + g_room_member = std::make_shared<RoomMember>(); + LOG_DEBUG(Network, "initialized OK"); + return true; +} + +std::weak_ptr<Room> GetRoom() { + return g_room; +} + +std::weak_ptr<RoomMember> GetRoomMember() { + return g_room_member; +} + +void Shutdown() { + if (g_room_member) { + if (g_room_member->IsConnected()) + g_room_member->Leave(); + g_room_member.reset(); + } + if (g_room) { + if (g_room->GetState() == Room::State::Open) + g_room->Destroy(); + g_room.reset(); + } + enet_deinitialize(); + LOG_DEBUG(Network, "shutdown OK"); +} + +} // namespace Network diff --git a/src/network/network.h b/src/network/network.h new file mode 100644 index 000000000..6d002d693 --- /dev/null +++ b/src/network/network.h @@ -0,0 +1,25 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "network/room.h" +#include "network/room_member.h" + +namespace Network { + +/// Initializes and registers the network device, the room, and the room member. +bool Init(); + +/// Returns a pointer to the room handle +std::weak_ptr<Room> GetRoom(); + +/// Returns a pointer to the room member handle +std::weak_ptr<RoomMember> GetRoomMember(); + +/// Unregisters the network device, the room, and the room member and shut them down. +void Shutdown(); + +} // namespace Network diff --git a/src/network/room.cpp b/src/network/room.cpp new file mode 100644 index 000000000..48de2f5cb --- /dev/null +++ b/src/network/room.cpp @@ -0,0 +1,60 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "enet/enet.h" +#include "network/room.h" + +namespace Network { + +/// Maximum number of concurrent connections allowed to this room. +static constexpr u32 MaxConcurrentConnections = 10; + +class Room::RoomImpl { +public: + ENetHost* server = nullptr; ///< Network interface. + + std::atomic<State> state{State::Closed}; ///< Current state of the room. + RoomInformation room_information; ///< Information about this room. +}; + +Room::Room() : room_impl{std::make_unique<RoomImpl>()} {} + +Room::~Room() = default; + +void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) { + ENetAddress address; + address.host = ENET_HOST_ANY; + enet_address_set_host(&address, server_address.c_str()); + address.port = server_port; + + room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); + // TODO(B3N30): Allow specifying the maximum number of concurrent connections. + room_impl->state = State::Open; + + room_impl->room_information.name = name; + room_impl->room_information.member_slots = MaxConcurrentConnections; + + // TODO(B3N30): Start the receiving thread +} + +Room::State Room::GetState() const { + return room_impl->state; +} + +const RoomInformation& Room::GetRoomInformation() const { + return room_impl->room_information; +} + +void Room::Destroy() { + room_impl->state = State::Closed; + // TODO(B3n30): Join the receiving thread + + if (room_impl->server) { + enet_host_destroy(room_impl->server); + } + room_impl->room_information = {}; + room_impl->server = nullptr; +} + +} // namespace Network diff --git a/src/network/room.h b/src/network/room.h new file mode 100644 index 000000000..70c64d5f1 --- /dev/null +++ b/src/network/room.h @@ -0,0 +1,60 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <memory> +#include <string> +#include "common/common_types.h" + +namespace Network { + +constexpr u16 DefaultRoomPort = 1234; +constexpr size_t NumChannels = 1; // Number of channels used for the connection + +struct RoomInformation { + std::string name; ///< Name of the server + u32 member_slots; ///< Maximum number of members in this room +}; + +/// This is what a server [person creating a server] would use. +class Room final { +public: + enum class State : u8 { + Open, ///< The room is open and ready to accept connections. + Closed, ///< The room is not opened and can not accept connections. + }; + + Room(); + ~Room(); + + /** + * Gets the current state of the room. + */ + State GetState() const; + + /** + * Gets the room information of the room. + */ + const RoomInformation& GetRoomInformation() const; + + /** + * Creates the socket for this room. Will bind to default address if + * server is empty string. + */ + void Create(const std::string& name, const std::string& server = "", + u16 server_port = DefaultRoomPort); + + /** + * Destroys the socket + */ + void Destroy(); + +private: + class RoomImpl; + std::unique_ptr<RoomImpl> room_impl; +}; + +} // namespace Network diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp new file mode 100644 index 000000000..c87f009f4 --- /dev/null +++ b/src/network/room_member.cpp @@ -0,0 +1,74 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "enet/enet.h" +#include "network/room_member.h" + +namespace Network { + +constexpr u32 ConnectionTimeoutMs = 5000; + +class RoomMember::RoomMemberImpl { +public: + ENetHost* client = nullptr; ///< ENet network interface. + ENetPeer* server = nullptr; ///< The server peer the client is connected to + + std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember. + + std::string nickname; ///< The nickname of this member. +}; + +RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} { + room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); + ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client"); +} + +RoomMember::~RoomMember() { + ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected"); + enet_host_destroy(room_member_impl->client); +} + +RoomMember::State RoomMember::GetState() const { + return room_member_impl->state; +} + +void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, + u16 client_port) { + ENetAddress address{}; + enet_address_set_host(&address, server_addr); + address.port = server_port; + + room_member_impl->server = + enet_host_connect(room_member_impl->client, &address, NumChannels, 0); + + if (!room_member_impl->server) { + room_member_impl->state = State::Error; + return; + } + + ENetEvent event{}; + int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs); + if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { + room_member_impl->nickname = nick; + room_member_impl->state = State::Joining; + // TODO(B3N30): Send a join request with the nickname to the server + // TODO(B3N30): Start the receive thread + } else { + room_member_impl->state = State::CouldNotConnect; + } +} + +bool RoomMember::IsConnected() const { + return room_member_impl->state == State::Joining || room_member_impl->state == State::Joined; +} + +void RoomMember::Leave() { + enet_peer_disconnect(room_member_impl->server, 0); + room_member_impl->state = State::Idle; + // TODO(B3N30): Close the receive thread + enet_peer_reset(room_member_impl->server); +} + +} // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h new file mode 100644 index 000000000..177622b69 --- /dev/null +++ b/src/network/room_member.h @@ -0,0 +1,65 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <memory> +#include <string> +#include "common/common_types.h" +#include "network/room.h" + +namespace Network { + +/** + * This is what a client [person joining a server] would use. + * It also has to be used if you host a game yourself (You'd create both, a Room and a + * RoomMembership for yourself) + */ +class RoomMember final { +public: + enum class State : u8 { + Idle, ///< Default state + Error, ///< Some error [permissions to network device missing or something] + Joining, ///< The client is attempting to join a room. + Joined, ///< The client is connected to the room and is ready to send/receive packets. + LostConnection, ///< Connection closed + + // Reasons why connection was rejected + NameCollision, ///< Somebody is already using this name + MacCollision, ///< Somebody is already using that mac-address + CouldNotConnect ///< The room is not responding to a connection attempt + }; + + RoomMember(); + ~RoomMember(); + + /** + * Returns the status of our connection to the room. + */ + State GetState() const; + + /** + * Returns whether we're connected to a server or not. + */ + bool IsConnected() const; + + /** + * Attempts to join a room at the specified address and port, using the specified nickname. + * This may fail if the username is already taken. + */ + void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", + const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0); + + /** + * Leaves the current room. + */ + void Leave(); + +private: + class RoomMemberImpl; + std::unique_ptr<RoomMemberImpl> room_member_impl; +}; + +} // namespace Network diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp index e07a28c5b..52336d027 100644 --- a/src/tests/core/hle/kernel/hle_ipc.cpp +++ b/src/tests/core/hle/kernel/hle_ipc.cpp @@ -18,7 +18,7 @@ static SharedPtr<Object> MakeObject() { return Event::Create(ResetType::OneShot); } -TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kernel]") { +TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") { auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair()); HLERequestContext context(std::move(session)); @@ -94,6 +94,18 @@ TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kerne REQUIRE(context.GetIncomingHandle(output[5]) == c); } + SECTION("translates null handles") { + const u32_le input[]{ + IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), 0, + }; + + auto result = context.PopulateFromIncomingCommandBuffer(input, *process, handle_table); + + REQUIRE(result == RESULT_SUCCESS); + auto* output = context.CommandBuffer(); + REQUIRE(context.GetIncomingHandle(output[2]) == nullptr); + } + SECTION("translates CallingPid descriptors") { const u32_le input[]{ IPC::MakeHeader(0, 0, 2), IPC::CallingPidDesc(), 0x98989898, @@ -171,6 +183,17 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") { REQUIRE(handle_table.GetGeneric(output[4]) == b); } + SECTION("translates null handles") { + input[0] = IPC::MakeHeader(0, 0, 2); + input[1] = IPC::MoveHandleDesc(1); + input[2] = context.AddOutgoingHandle(nullptr); + + auto result = context.WriteToOutgoingCommandBuffer(output, *process, handle_table); + + REQUIRE(result == RESULT_SUCCESS); + REQUIRE(output[2] == 0); + } + SECTION("translates multi-handle descriptors") { auto a = MakeObject(); auto b = MakeObject(); diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index 3b00df0b3..2d23d34e6 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -111,6 +111,14 @@ struct State { BitField<0, 13, s32> difference; // 1.1.11 fixed point BitField<13, 11, u32> value; // 0.0.11 fixed point + + float ToFloat() const { + return static_cast<float>(value) / 2047.0f; + } + + float DiffToFloat() const { + return static_cast<float>(difference) / 2047.0f; + } }; std::array<LutEntry, 128> lut; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8b7991c04..ff3f69ba3 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -94,10 +94,10 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { framebuffer.Create(); // Allocate and bind lighting lut textures - lighting_lut_buffer.Create(); + lighting_lut.Create(); state.lighting_lut.texture_buffer = lighting_lut.handle; state.Apply(); - lighting_lut.Create(); + lighting_lut_buffer.Create(); glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle); glBufferData(GL_TEXTURE_BUFFER, sizeof(GLfloat) * 2 * 256 * Pica::LightingRegs::NumLightingSampler, nullptr, @@ -106,16 +106,14 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, lighting_lut_buffer.handle); // Setup the LUT for the fog - { - fog_lut.Create(); - state.fog_lut.texture_1d = fog_lut.handle; - } + fog_lut.Create(); + state.fog_lut.texture_buffer = fog_lut.handle; state.Apply(); - + fog_lut_buffer.Create(); + glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle); + glBufferData(GL_TEXTURE_BUFFER, sizeof(GLfloat) * 2 * 128, nullptr, GL_DYNAMIC_DRAW); glActiveTexture(TextureUnits::FogLUT.Enum()); - glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, 128, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, fog_lut_buffer.handle); // Setup the noise LUT for proctex proctex_noise_lut.Create(); @@ -1356,16 +1354,17 @@ void RasterizerOpenGL::SyncFogColor() { } void RasterizerOpenGL::SyncFogLUT() { - std::array<GLuint, 128> new_data; + std::array<GLvec2, 128> new_data; std::transform(Pica::g_state.fog.lut.begin(), Pica::g_state.fog.lut.end(), new_data.begin(), - [](const auto& entry) { return entry.raw; }); + [](const auto& entry) { + return GLvec2{entry.ToFloat(), entry.DiffToFloat()}; + }); if (new_data != fog_lut_data) { fog_lut_data = new_data; - glActiveTexture(TextureUnits::FogLUT.Enum()); - glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 128, GL_RED_INTEGER, GL_UNSIGNED_INT, - fog_lut_data.data()); + glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle); + glBufferSubData(GL_TEXTURE_BUFFER, 0, new_data.size() * sizeof(GLvec2), new_data.data()); } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 79acd4230..a433c1d4a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -283,8 +283,9 @@ private: OGLTexture lighting_lut; std::array<std::array<GLvec2, 256>, Pica::LightingRegs::NumLightingSampler> lighting_lut_data{}; + OGLBuffer fog_lut_buffer; OGLTexture fog_lut; - std::array<GLuint, 128> fog_lut_data{}; + std::array<GLvec2, 128> fog_lut_data{}; OGLTexture proctex_noise_lut; std::array<GLvec2, 128> proctex_noise_lut_data{}; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 0c7c4dd5c..c93b108fb 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -1052,7 +1052,7 @@ layout (std140) uniform shader_data { uniform sampler2D tex[3]; uniform samplerBuffer lighting_lut; -uniform usampler1D fog_lut; +uniform samplerBuffer fog_lut; uniform sampler1D proctex_noise_lut; uniform sampler1D proctex_color_map; uniform sampler1D proctex_alpha_map; @@ -1145,12 +1145,8 @@ vec4 secondary_fragment_color = vec4(0.0); // Generate clamped fog factor from LUT for given fog index out += "float fog_i = clamp(floor(fog_index), 0.0, 127.0);\n"; out += "float fog_f = fog_index - fog_i;\n"; - out += "uint fog_lut_entry = texelFetch(fog_lut, int(fog_i), 0).r;\n"; - out += "float fog_lut_entry_difference = float(int((fog_lut_entry & 0x1FFFU) << 19U) >> " - "19);\n"; // Extract signed difference - out += "float fog_lut_entry_value = float((fog_lut_entry >> 13U) & 0x7FFU);\n"; - out += "float fog_factor = (fog_lut_entry_value + fog_lut_entry_difference * fog_f) / " - "2047.0;\n"; + out += "vec2 fog_lut_entry = texelFetch(fog_lut, int(fog_i)).rg;\n"; + out += "float fog_factor = fog_lut_entry.r + fog_lut_entry.g * fog_f;\n"; out += "fog_factor = clamp(fog_factor, 0.0, 1.0);\n"; // Blend the fog diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 14e63115c..eface2dea 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -54,7 +54,7 @@ OpenGLState::OpenGLState() { lighting_lut.texture_buffer = 0; - fog_lut.texture_1d = 0; + fog_lut.texture_buffer = 0; proctex_lut.texture_1d = 0; proctex_diff_lut.texture_1d = 0; @@ -198,9 +198,9 @@ void OpenGLState::Apply() const { } // Fog LUT - if (fog_lut.texture_1d != cur_state.fog_lut.texture_1d) { + if (fog_lut.texture_buffer != cur_state.fog_lut.texture_buffer) { glActiveTexture(TextureUnits::FogLUT.Enum()); - glBindTexture(GL_TEXTURE_1D, fog_lut.texture_1d); + glBindTexture(GL_TEXTURE_BUFFER, fog_lut.texture_buffer); } // ProcTex Noise LUT @@ -272,8 +272,8 @@ void OpenGLState::ResetTexture(GLuint handle) { } if (cur_state.lighting_lut.texture_buffer == handle) cur_state.lighting_lut.texture_buffer = 0; - if (cur_state.fog_lut.texture_1d == handle) - cur_state.fog_lut.texture_1d = 0; + if (cur_state.fog_lut.texture_buffer == handle) + cur_state.fog_lut.texture_buffer = 0; if (cur_state.proctex_noise_lut.texture_1d == handle) cur_state.proctex_noise_lut.texture_1d = 0; if (cur_state.proctex_color_map.texture_1d == handle) diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index bb0218708..1efcf0811 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -91,7 +91,7 @@ public: } lighting_lut; struct { - GLuint texture_1d; // GL_TEXTURE_BINDING_1D + GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER } fog_lut; struct { diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index cd7b6c39d..512e81c08 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp @@ -584,8 +584,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve float fog_i = MathUtil::Clamp(floorf(fog_index), 0.0f, 127.0f); float fog_f = fog_index - fog_i; const auto& fog_lut_entry = g_state.fog.lut[static_cast<unsigned int>(fog_i)]; - float fog_factor = (fog_lut_entry.value + fog_lut_entry.difference * fog_f) / - 2047.0f; // This is signed fixed point 1.11 + float fog_factor = fog_lut_entry.ToFloat() + fog_lut_entry.DiffToFloat() * fog_f; fog_factor = MathUtil::Clamp(fog_factor, 0.0f, 1.0f); // Blend the fog diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt new file mode 100644 index 000000000..334d82a8a --- /dev/null +++ b/src/web_service/CMakeLists.txt @@ -0,0 +1,14 @@ +set(SRCS + telemetry_json.cpp + web_backend.cpp + ) + +set(HEADERS + telemetry_json.h + web_backend.h + ) + +create_directory_groups(${SRCS} ${HEADERS}) + +add_library(web_service STATIC ${SRCS} ${HEADERS}) +target_link_libraries(web_service PUBLIC common cpr json-headers) diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp new file mode 100644 index 000000000..a2d007e77 --- /dev/null +++ b/src/web_service/telemetry_json.cpp @@ -0,0 +1,87 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/settings.h" +#include "web_service/telemetry_json.h" +#include "web_service/web_backend.h" + +namespace WebService { + +template <class T> +void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { + sections[static_cast<u8>(type)][name] = value; +} + +void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { + TopSection()[name] = sections[static_cast<unsigned>(type)]; +} + +void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<double>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<float>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { + Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); +} + +void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { + Serialize(field.GetType(), field.GetName(), field.GetValue().count()); +} + +void TelemetryJson::Complete() { + SerializeSection(Telemetry::FieldType::App, "App"); + SerializeSection(Telemetry::FieldType::Session, "Session"); + SerializeSection(Telemetry::FieldType::Performance, "Performance"); + SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); + SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); + SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); + PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump()); +} + +} // namespace WebService diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h new file mode 100644 index 000000000..39038b4f9 --- /dev/null +++ b/src/web_service/telemetry_json.h @@ -0,0 +1,54 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <json.hpp> +#include "common/telemetry.h" + +namespace WebService { + +/** + * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the + * Citra web service + */ +class TelemetryJson : public Telemetry::VisitorInterface { +public: + TelemetryJson() = default; + ~TelemetryJson() = default; + + void Visit(const Telemetry::Field<bool>& field) override; + void Visit(const Telemetry::Field<double>& field) override; + void Visit(const Telemetry::Field<float>& field) override; + void Visit(const Telemetry::Field<u8>& field) override; + void Visit(const Telemetry::Field<u16>& field) override; + void Visit(const Telemetry::Field<u32>& field) override; + void Visit(const Telemetry::Field<u64>& field) override; + void Visit(const Telemetry::Field<s8>& field) override; + void Visit(const Telemetry::Field<s16>& field) override; + void Visit(const Telemetry::Field<s32>& field) override; + void Visit(const Telemetry::Field<s64>& field) override; + void Visit(const Telemetry::Field<std::string>& field) override; + void Visit(const Telemetry::Field<const char*>& field) override; + void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; + + void Complete() override; + +private: + nlohmann::json& TopSection() { + return sections[static_cast<u8>(Telemetry::FieldType::None)]; + } + + template <class T> + void Serialize(Telemetry::FieldType type, const std::string& name, T value); + + void SerializeSection(Telemetry::FieldType type, const std::string& name); + + nlohmann::json output; + std::array<nlohmann::json, 7> sections; +}; + +} // namespace WebService diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp new file mode 100644 index 000000000..13e4555ac --- /dev/null +++ b/src/web_service/web_backend.cpp @@ -0,0 +1,52 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cpr/cpr.h> +#include <stdlib.h> +#include "common/logging/log.h" +#include "web_service/web_backend.h" + +namespace WebService { + +static constexpr char API_VERSION[]{"1"}; +static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"}; +static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"}; + +static std::string GetEnvironmentVariable(const char* name) { + const char* value{getenv(name)}; + if (value) { + return value; + } + return {}; +} + +const std::string& GetUsername() { + static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)}; + return username; +} + +const std::string& GetToken() { + static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)}; + return token; +} + +void PostJson(const std::string& url, const std::string& data) { + if (url.empty()) { + LOG_ERROR(WebService, "URL is invalid"); + return; + } + + if (GetUsername().empty() || GetToken().empty()) { + LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON", + ENV_VAR_USERNAME, ENV_VAR_TOKEN); + return; + } + + cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"}, + {"x-username", GetUsername()}, + {"x-token", GetToken()}, + {"api-version", API_VERSION}}); +} + +} // namespace WebService diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h new file mode 100644 index 000000000..2753d3b68 --- /dev/null +++ b/src/web_service/web_backend.h @@ -0,0 +1,31 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" + +namespace WebService { + +/** + * Gets the current username for accessing services.citra-emu.org. + * @returns Username as a string, empty if not set. + */ +const std::string& GetUsername(); + +/** + * Gets the current token for accessing services.citra-emu.org. + * @returns Token as a string, empty if not set. + */ +const std::string& GetToken(); + +/** + * Posts JSON to services.citra-emu.org. + * @param url URL of the services.citra-emu.org endpoint to post data to. + * @param data String of JSON data to use for the body of the POST request. + */ +void PostJson(const std::string& url, const std::string& data); + +} // namespace WebService |