diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f50989f4163..d92c93b073b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1428,6 +1428,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/SymbolMap.h Core/Debugger/DisassemblyManager.cpp Core/Debugger/DisassemblyManager.h + Core/Debugger/WebSocket.cpp + Core/Debugger/WebSocket.h Core/Dialog/PSPDialog.cpp Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 209890e9f13f..672adc6c1dca 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -184,6 +184,7 @@ + @@ -531,6 +532,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 2efbdbd29adb..89a505a8f4ec 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -689,6 +689,9 @@ Core + + Debugger + @@ -1268,6 +1271,9 @@ Core + + Debugger + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp new file mode 100644 index 000000000000..b07e6c723539 --- /dev/null +++ b/Core/Debugger/WebSocket.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2017- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include "net/websocket_server.h" +#include "Core/Debugger/WebSocket.h" +#include "Common/LogManager.h" + +// TODO: Move this to its own file? +class DebuggerLogListener : public LogListener { +public: + void Log(const LogMessage &msg) override { + std::lock_guard guard(lock_); + messages_[nextMessage_] = msg; + nextMessage_++; + if (nextMessage_ >= BUFFER_SIZE) + nextMessage_ -= BUFFER_SIZE; + count_++; + } + + std::vector GetMessages() { + std::lock_guard guard(lock_); + int splitPoint; + int readCount; + if (read_ + BUFFER_SIZE < count_) { + // We'll start with our oldest then. + splitPoint = nextMessage_; + readCount = Count(); + } else { + splitPoint = read_; + readCount = count_ - read_; + } + + read_ = count_; + + std::vector results; + int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE); + for (int i = splitPoint; i < splitEnd; ++i) { + results.push_back(messages_[i]); + readCount--; + } + for (int i = 0; i < readCount; ++i) { + results.push_back(messages_[i]); + } + + return results; + } + + int Count() const { + return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE; + } + +private: + enum { BUFFER_SIZE = 128 }; + LogMessage messages_[BUFFER_SIZE]; + std::mutex lock_; + int nextMessage_ = 0; + int count_ = 0; + int read_ = 0; +}; + +// TODO: Use a real json serializer? +static std::string JSONString(const std::string &str) { + size_t pos = 0; + std::string escaped; + + auto update = [&](size_t current, size_t skip = 0) { + size_t end = current; + if (pos < end) + escaped += str.substr(pos, end - pos); + pos = end + skip; + }; + + for (size_t i = 0; i < str.size(); ++i) { + if (str[i] == '\\' || str[i] == '"' || str[i] == '/') { + update(i); + escaped += '\\'; + } else if (str[i] == '\r') { + update(i, 1); + escaped += "\\r"; + } else if (str[i] == '\n') { + update(i, 1); + escaped += "\\n"; + } else if (str[i] == '\t') { + update(i, 1); + escaped += "\\t"; + } else if (str[i] < 32) { + update(i, 1); + escaped += StringFromFormat("\\u%04x", str[i]); + } + } + + if (pos != 0) { + update(str.size()); + return escaped; + } + return str; +} + +struct DebuggerLogEvent { + std::string header; + std::string message; + int level; + const char *channel; + + operator std::string() { + static const char *const fmt = R"({"event":"log","header":"%s","message":"%s","level":%d,"channel":"%s"})"; + return StringFromFormat(fmt, JSONString(header).c_str(), JSONString(message).c_str(), level, JSONString(channel).c_str()); + } +}; + +void HandleDebuggerRequest(const http::Request &request) { + net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); + if (!ws) + return; + + DebuggerLogListener *logListener = new DebuggerLogListener(); + if (LogManager::GetInstance()) + LogManager::GetInstance()->AddListener(logListener); + + // TODO: Handle incoming messages. + ws->SetTextHandler([&](const std::string &t) { + ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + }); + ws->SetBinaryHandler([&](const std::vector &d) { + ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + }); + + while (ws->Process(0.1f)) { + auto messages = logListener->GetMessages(); + // TODO: Check for other conditions? + for (auto msg : messages) { + ws->Send(DebuggerLogEvent{msg.header, msg.msg, msg.level, msg.log}); + } + continue; + } + + if (LogManager::GetInstance()) + LogManager::GetInstance()->RemoveListener(logListener); + delete logListener; + delete ws; +} diff --git a/Core/Debugger/WebSocket.h b/Core/Debugger/WebSocket.h new file mode 100644 index 000000000000..73b4e5fe8ff0 --- /dev/null +++ b/Core/Debugger/WebSocket.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +namespace http { +class Request; +} + +void HandleDebuggerRequest(const http::Request &request); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 2767e72761f6..d5cb9ba9cf35 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -32,6 +32,7 @@ #include "Common/Common.h" #include "Common/FileUtil.h" #include "Core/Config.h" +#include "Core/Debugger/WebSocket.h" #include "UI/RemoteISOScreen.h" using namespace UI; @@ -86,7 +87,7 @@ static void RegisterServer(int port) { static void ExecuteServer() { setCurrentThreadName("HTTPServer"); - auto http = new http::Server(new threading::SameThreadExecutor()); + auto http = new http::Server(new threading::NewThreadExecutor()); std::map paths; for (std::string filename : g_Config.recentIsos) { @@ -161,6 +162,10 @@ static void ExecuteServer() { http->RegisterHandler(pair.first.c_str(), handler); } + // TODO: This isn't an ideal place to put this... should separate. + // But kinda nice to only have it listen on one port? + http->RegisterHandler("/debugger", &HandleDebuggerRequest); + if (!http->Listen(g_Config.iRemoteISOPort)) { if (!http->Listen(0)) { ERROR_LOG(FILESYS, "Unable to listen on any port"); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index fc04485bb646..7e2d18ba935d 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -298,6 +298,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/TextureReplacer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ + $(SRC)/Core/Debugger/WebSocket.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \