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 \