diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2265f2b5de..9ea61d912b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1404,13 +1404,16 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Debugger/DisassemblyManager.h
Core/Debugger/WebSocket.cpp
Core/Debugger/WebSocket.h
- Core/Debugger/WebSocket/Common.h
+ Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
+ Core/Debugger/WebSocket/CPUCoreSubscriber.h
Core/Debugger/WebSocket/GameBroadcaster.cpp
Core/Debugger/WebSocket/GameBroadcaster.h
Core/Debugger/WebSocket/LogBroadcaster.cpp
Core/Debugger/WebSocket/LogBroadcaster.h
Core/Debugger/WebSocket/SteppingBroadcaster.cpp
Core/Debugger/WebSocket/SteppingBroadcaster.h
+ Core/Debugger/WebSocket/WebSocketUtils.cpp
+ Core/Debugger/WebSocket/WebSocketUtils.h
Core/Dialog/PSPDialog.cpp
Core/Dialog/PSPDialog.h
Core/Dialog/PSPGamedataInstallDialog.cpp
diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index e713cd5057..c102fd2cb7 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -185,9 +185,11 @@
+
+
@@ -537,7 +539,8 @@
-
+
+
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index 3449fa0158..3a8a72a82e 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -707,6 +707,12 @@
Debugger\WebSocket
+
+ Debugger\WebSocket
+
+
+ Debugger\WebSocket
+
@@ -1301,7 +1307,10 @@
Debugger\WebSocket
-
+
+ Debugger\WebSocket
+
+
Debugger\WebSocket
diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp
index 770772a1de..2e3e540843 100644
--- a/Core/Debugger/WebSocket.cpp
+++ b/Core/Debugger/WebSocket.cpp
@@ -16,11 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Debugger/WebSocket.h"
-#include "Core/Debugger/WebSocket/Common.h"
-
-#include "Core/Debugger/WebSocket/GameBroadcaster.h"
-#include "Core/Debugger/WebSocket/LogBroadcaster.h"
-#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
// This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP.
// Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface.
@@ -40,14 +36,17 @@
// - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE)
// - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value.
-// TODO: Just for now, testing...
-static void WebSocketTestEvent(net::WebSocketServer *ws, const JsonGet &data) {
- ws->Send(DebuggerErrorEvent("Test message", LogTypes::LNOTICE, data));
-}
+#include "Core/Debugger/WebSocket/GameBroadcaster.h"
+#include "Core/Debugger/WebSocket/LogBroadcaster.h"
+#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
-typedef void (*DebuggerEventHandler)(net::WebSocketServer *ws, const JsonGet &data);
+#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
+
+typedef void (*DebuggerEventHandler)(DebuggerRequest &req);
static const std::unordered_map debuggerEvents({
- {"test", &WebSocketTestEvent},
+ {"cpu.getAllRegs", &WebSocketCPUGetAllRegs},
+ {"cpu.getReg", &WebSocketCPUGetReg},
+ {"cpu.setReg", &WebSocketCPUSetReg},
});
void HandleDebuggerRequest(const http::Request &request) {
@@ -73,11 +72,14 @@ void HandleDebuggerRequest(const http::Request &request) {
return;
}
+ DebuggerRequest req(event, ws, root);
+
auto eventFunc = debuggerEvents.find(event);
if (eventFunc != debuggerEvents.end()) {
- eventFunc->second(ws, root);
+ eventFunc->second(req);
+ req.Finish();
} else {
- ws->Send(DebuggerErrorEvent("Bad message: unknown event", LogTypes::LERROR, root));
+ req.Fail("Bad message: unknown event");
}
});
ws->SetBinaryHandler([&](const std::vector &d) {
diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
new file mode 100644
index 0000000000..c3ef6e6ff5
--- /dev/null
+++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
@@ -0,0 +1,224 @@
+// Copyright (c) 2018- 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 "Common/StringUtils.h"
+#include "Core/Core.h"
+#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
+#include "Core/MIPS/MIPS.h"
+#include "Core/MIPS/MIPSDebugInterface.h"
+
+static std::string RegValueAsFloat(uint32_t u) {
+ union {
+ uint32_t u;
+ float f;
+ } bits = { u };
+ return StringFromFormat("%f", bits.f);
+}
+
+void WebSocketCPUGetAllRegs(DebuggerRequest &req) {
+ JsonWriter &json = req.Respond();
+
+ json.pushArray("categories");
+ for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {
+ json.pushDict();
+ json.writeInt("id", c);
+ json.writeString("name", currentDebugMIPS->GetCategoryName(c));
+
+ int total = currentDebugMIPS->GetNumRegsInCategory(c);
+
+ json.pushArray("names");
+ for (int r = 0; r < total; ++r)
+ json.writeString(currentDebugMIPS->GetRegName(c, r));
+ if (c == 0) {
+ json.writeString("pc");
+ json.writeString("hi");
+ json.writeString("lo");
+ }
+ json.pop();
+
+ json.pushArray("intValues");
+ // Writing as floating point to avoid negatives. Actually double, so safe.
+ for (int r = 0; r < total; ++r)
+ json.writeFloat(currentDebugMIPS->GetRegValue(c, r));
+ if (c == 0) {
+ json.writeFloat(currentDebugMIPS->GetPC());
+ json.writeFloat(currentDebugMIPS->GetHi());
+ json.writeFloat(currentDebugMIPS->GetLo());
+ }
+ json.pop();
+
+ json.pushArray("floatValues");
+ // Note: String so it can have Infinity and NaN.
+ for (int r = 0; r < total; ++r)
+ json.writeString(RegValueAsFloat(currentDebugMIPS->GetRegValue(c, r)));
+ if (c == 0) {
+ json.writeString(RegValueAsFloat(currentDebugMIPS->GetPC()));
+ json.writeString(RegValueAsFloat(currentDebugMIPS->GetHi()));
+ json.writeString(RegValueAsFloat(currentDebugMIPS->GetLo()));
+ }
+ json.pop();
+
+ json.pop();
+ }
+ json.pop();
+}
+
+enum class DebuggerRegType {
+ INVALID,
+ NORMAL,
+ PC,
+ HI,
+ LO,
+};
+
+static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {
+ if (name == "pc") {
+ *cat = 0;
+ *reg = 32;
+ return DebuggerRegType::PC;
+ }
+ if (name == "hi") {
+ *cat = 0;
+ *reg = 33;
+ return DebuggerRegType::HI;
+ }
+ if (name == "lo") {
+ *cat = 0;
+ *reg = 34;
+ return DebuggerRegType::LO;
+ }
+
+ for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {
+ int total = currentDebugMIPS->GetNumRegsInCategory(c);
+ for (int r = 0; r < total; ++r) {
+ if (name == currentDebugMIPS->GetRegName(c, r)) {
+ *cat = c;
+ *reg = r;
+ return DebuggerRegType::NORMAL;
+ }
+ }
+ }
+
+ req.Fail("Invalid 'name' parameter");
+ return DebuggerRegType::INVALID;
+}
+
+static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {
+ const char *name = req.data.getString("name", nullptr);
+ if (name)
+ return ValidateRegName(req, name, cat, reg);
+
+ *cat = req.data.getInt("category", -1);
+ *reg = req.data.getInt("register", -1);
+
+ if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {
+ req.Fail("Invalid 'category' parameter");
+ return DebuggerRegType::INVALID;
+ }
+
+ // TODO: We fake it for GPR... not sure yet if this is a good thing.
+ if (*cat == 0) {
+ // Intentionally retains the reg value.
+ if (*reg == 32)
+ return DebuggerRegType::PC;
+ if (*reg == 33)
+ return DebuggerRegType::HI;
+ if (*reg == 34)
+ return DebuggerRegType::LO;
+ }
+
+ if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {
+ req.Fail("Invalid 'register' parameter");
+ return DebuggerRegType::INVALID;
+ }
+
+ return DebuggerRegType::NORMAL;
+}
+
+void WebSocketCPUGetReg(DebuggerRequest &req) {
+ int cat, reg;
+ uint32_t val;
+ switch (ValidateCatReg(req, &cat, ®)) {
+ case DebuggerRegType::NORMAL:
+ val = currentDebugMIPS->GetRegValue(cat, reg);
+ break;
+
+ case DebuggerRegType::PC:
+ val = currentDebugMIPS->GetPC();
+ break;
+ case DebuggerRegType::HI:
+ val = currentDebugMIPS->GetHi();
+ break;
+ case DebuggerRegType::LO:
+ val = currentDebugMIPS->GetLo();
+ break;
+
+ case DebuggerRegType::INVALID:
+ // Error response already sent.
+ return;
+ }
+
+ JsonWriter &json = req.Respond();
+ json.writeInt("category", cat);
+ json.writeInt("register", reg);
+ json.writeFloat("intValue", val);
+ json.writeString("floatValue", RegValueAsFloat(val));
+}
+
+void WebSocketCPUSetReg(DebuggerRequest &req) {
+ if (!currentDebugMIPS->isAlive()) {
+ return req.Fail("CPU not started");
+ }
+ if (!Core_IsStepping()) {
+ return req.Fail("CPU currently running (cpu.interrupt first)");
+ }
+
+ uint32_t val;
+ if (!req.ParamU32OrFloatBits("value", &val)) {
+ // Already sent error.
+ return;
+ }
+
+ int cat, reg;
+ switch (ValidateCatReg(req, &cat, ®)) {
+ case DebuggerRegType::NORMAL:
+ currentDebugMIPS->SetRegValue(cat, reg, val);
+ break;
+
+ case DebuggerRegType::PC:
+ currentDebugMIPS->SetPC(val);
+ break;
+ case DebuggerRegType::HI:
+ currentDebugMIPS->SetHi(val);
+ break;
+ case DebuggerRegType::LO:
+ currentDebugMIPS->SetLo(val);
+ break;
+
+ case DebuggerRegType::INVALID:
+ // Error response already sent.
+ return;
+ }
+
+ JsonWriter &json = req.Respond();
+ // Repeat it back just to avoid confusion on how it parsed.
+ json.writeInt("category", cat);
+ json.writeInt("register", reg);
+ json.writeFloat("intValue", val);
+ json.writeString("floatValue", RegValueAsFloat(val));
+}
diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h
new file mode 100644
index 0000000000..e24b16398f
--- /dev/null
+++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2018- 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
+
+struct DebuggerRequest;
+
+void WebSocketCPUGetAllRegs(DebuggerRequest &req);
+void WebSocketCPUGetReg(DebuggerRequest &req);
+void WebSocketCPUSetReg(DebuggerRequest &req);
diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp
index 86ee9df777..e1a9d2ae4e 100644
--- a/Core/Debugger/WebSocket/GameBroadcaster.cpp
+++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp
@@ -15,8 +15,8 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
-#include "Core/Debugger/WebSocket/Common.h"
#include "Core/Debugger/WebSocket/GameBroadcaster.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/System.h"
void GameBroadcaster::Broadcast(net::WebSocketServer *ws) {
diff --git a/Core/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp
index 3f23751d7c..2474294e0a 100644
--- a/Core/Debugger/WebSocket/LogBroadcaster.cpp
+++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp
@@ -18,8 +18,8 @@
#include
#include
#include "Common/LogManager.h"
-#include "Core/Debugger/WebSocket/Common.h"
#include "Core/Debugger/WebSocket/LogBroadcaster.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
class DebuggerLogListener : public LogListener {
public:
diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp
index 60405b4c48..ca44bc06b4 100644
--- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp
+++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp
@@ -16,8 +16,8 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "Core/Core.h"
-#include "Core/Debugger/WebSocket/Common.h"
#include "Core/Debugger/WebSocket/SteppingBroadcaster.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/System.h"
void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) {
diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp
new file mode 100644
index 0000000000..3293e5da22
--- /dev/null
+++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp
@@ -0,0 +1,106 @@
+// Copyright (c) 2018- 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 "Common/StringUtils.h"
+#include "Core/Debugger/WebSocket/WebSocketUtils.h"
+
+JsonWriter &DebuggerRequest::Respond() {
+ writer_.begin();
+ writer_.writeString("event", name);
+ DebuggerJsonAddTicket(writer_, data);
+
+ responseBegun_ = true;
+ return writer_;
+}
+
+void DebuggerRequest::Finish() {
+ if (responseBegun_ && !responseSent_) {
+ writer_.end();
+ ws->Send(writer_.str());
+ responseBegun_ = false;
+ responseSent_ = true;
+ }
+}
+
+static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) {
+ if (TryParse(str, out))
+ return true;
+
+ // Now let's try signed (the above parses only positive.)
+ if (str[0] == '-' && TryParse(&str[1], out)) {
+ *out = static_cast(-static_cast(*out));
+ return true;
+ }
+
+ // We have to try float last because we use float bits.
+ union {
+ uint32_t u;
+ float f;
+ } bits;
+ if (allowFloat && TryParse(str, &bits.f)) {
+ *out = bits.u;
+ return true;
+ }
+
+ return false;
+}
+
+bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) {
+ const JsonNode *node = data.get(name);
+ if (!node) {
+ Fail(StringFromFormat("Missing '%s' parameter", name));
+ return false;
+ }
+
+ // TODO: For now, only supporting strings. Switch to gason?
+ // Otherwise we get overflow (signed integer parsing.)
+ if (node->value.getTag() != JSON_STRING) {
+ Fail(StringFromFormat("Invalid '%s' parameter type", name));
+ return false;
+ }
+
+ if (U32FromString(node->value.toString(), out, false))
+ return true;
+
+ Fail(StringFromFormat("Could not parse '%s' parameter", name));
+ return false;
+}
+
+bool DebuggerRequest::ParamU32OrFloatBits(const char *name, uint32_t *out) {
+ const JsonNode *node = data.get(name);
+ if (!node) {
+ Fail(StringFromFormat("Missing '%s' parameter", name));
+ return false;
+ }
+
+ // TODO: For now, only supporting strings and floats. Switch to gason?
+ // Otherwise we get overflow (signed integer parsing.)
+ if (node->value.getTag() == JSON_NUMBER) {
+ Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name));
+ return false;
+ }
+ if (node->value.getTag() != JSON_STRING) {
+ Fail(StringFromFormat("Invalid '%s' parameter type", name));
+ return false;
+ }
+
+ if (U32FromString(node->value.toString(), out, true))
+ return true;
+
+ Fail(StringFromFormat("Could not parse '%s' parameter", name));
+ return false;
+}
diff --git a/Core/Debugger/WebSocket/Common.h b/Core/Debugger/WebSocket/WebSocketUtils.h
similarity index 66%
rename from Core/Debugger/WebSocket/Common.h
rename to Core/Debugger/WebSocket/WebSocketUtils.h
index bdca223656..19894939bb 100644
--- a/Core/Debugger/WebSocket/Common.h
+++ b/Core/Debugger/WebSocket/WebSocketUtils.h
@@ -17,12 +17,19 @@
#pragma once
+#include
#include
#include "json/json_reader.h"
#include "json/json_writer.h"
#include "net/websocket_server.h"
#include "Common/Log.h"
+static inline void DebuggerJsonAddTicket(JsonWriter &writer, const JsonGet &data) {
+ const JsonNode *value = data.get("ticket");
+ if (value)
+ writer.writeRaw("ticket", json_stringify(value));
+}
+
struct DebuggerErrorEvent {
DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l, const JsonGet data = JsonValue(JSON_NULL))
: message(m), level(l) {
@@ -51,3 +58,29 @@ struct DebuggerErrorEvent {
return j.str();
}
};
+
+struct DebuggerRequest {
+ DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d)
+ : name(n), ws(w), data(d) {
+ }
+
+ const char *name;
+ net::WebSocketServer *ws;
+ const JsonGet data;
+
+ void Fail(const std::string &message) {
+ ws->Send(DebuggerErrorEvent(message, LogTypes::LERROR, data));
+ responseSent_ = true;
+ }
+
+ bool ParamU32(const char *name, uint32_t *out);
+ bool ParamU32OrFloatBits(const char *name, uint32_t *out);
+
+ JsonWriter &Respond();
+ void Finish();
+
+private:
+ JsonWriter writer_;
+ bool responseBegun_ = false;
+ bool responseSent_ = false;
+};
diff --git a/android/jni/Android.mk b/android/jni/Android.mk
index d21c854849..b5b8dda1d2 100644
--- a/android/jni/Android.mk
+++ b/android/jni/Android.mk
@@ -301,9 +301,11 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/Debugger/Breakpoints.cpp \
$(SRC)/Core/Debugger/SymbolMap.cpp \
$(SRC)/Core/Debugger/WebSocket.cpp \
+ $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \
$(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \
$(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \
+ $(SRC)/Core/Debugger/WebSocket/WebSocketUtils.cpp \
$(SRC)/Core/Dialog/PSPDialog.cpp \
$(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \
$(SRC)/Core/Dialog/PSPMsgDialog.cpp \