Debugger: Add API to trigger buttons.

This commit is contained in:
Unknown W. Brackets 2021-01-18 16:58:34 -08:00
parent a88f6b45d2
commit de3277c679
9 changed files with 299 additions and 0 deletions

View File

@ -1492,6 +1492,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Debugger/WebSocket/GPURecordSubscriber.h
Core/Debugger/WebSocket/HLESubscriber.cpp
Core/Debugger/WebSocket/HLESubscriber.h
Core/Debugger/WebSocket/InputSubscriber.cpp
Core/Debugger/WebSocket/InputSubscriber.h
Core/Debugger/WebSocket/LogBroadcaster.cpp
Core/Debugger/WebSocket/LogBroadcaster.h
Core/Debugger/WebSocket/MemorySubscriber.cpp

View File

@ -437,6 +437,7 @@
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\GPURecordSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\InputSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\LogBroadcaster.cpp" />
<ClCompile Include="Debugger\WebSocket\DisasmSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\MemorySubscriber.cpp" />
@ -981,6 +982,7 @@
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GPURecordSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="Debugger\WebSocket\InputSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\SteppingSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\WebSocketUtils.h" />
<ClInclude Include="Debugger\WebSocket\CPUCoreSubscriber.h" />

View File

@ -965,6 +965,9 @@
<ClCompile Include="ThreadPools.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\InputSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
@ -1649,6 +1652,9 @@
<ClInclude Include="ThreadPools.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\InputSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />

View File

@ -55,6 +55,7 @@
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
#include "Core/Debugger/WebSocket/GPURecordSubscriber.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/InputSubscriber.h"
#include "Core/Debugger/WebSocket/MemorySubscriber.h"
#include "Core/Debugger/WebSocket/SteppingSubscriber.h"
@ -67,6 +68,7 @@ static const std::vector<SubscriberInit> subscribers({
&WebSocketGPUBufferInit,
&WebSocketGPURecordInit,
&WebSocketHLEInit,
&WebSocketInputInit,
&WebSocketMemoryInit,
&WebSocketSteppingInit,
});

View File

@ -0,0 +1,256 @@
// Copyright (c) 2021- 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 <algorithm>
#include <unordered_map>
#include "Common/StringUtils.h"
#include "Core/Debugger/WebSocket/InputSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceDisplay.h"
static const std::unordered_map<std::string, uint32_t> buttonLookup = {
{ "cross", CTRL_CROSS },
{ "circle", CTRL_CIRCLE },
{ "triangle", CTRL_TRIANGLE },
{ "square", CTRL_SQUARE },
{ "up", CTRL_UP },
{ "down", CTRL_DOWN },
{ "left", CTRL_LEFT },
{ "right", CTRL_RIGHT },
{ "start", CTRL_START },
{ "select", CTRL_SELECT },
{ "home", CTRL_HOME },
{ "screen", CTRL_SCREEN },
{ "note", CTRL_NOTE },
{ "ltrigger", CTRL_LTRIGGER },
{ "rtrigger", CTRL_RTRIGGER },
{ "hold", CTRL_HOLD },
{ "wlan", CTRL_WLAN },
{ "remote_hold", CTRL_REMOTE_HOLD },
{ "vol_up", CTRL_VOL_UP },
{ "vol_down", CTRL_VOL_DOWN },
{ "disc", CTRL_DISC },
{ "memstick", CTRL_MEMSTICK },
{ "forward", CTRL_FORWARD },
{ "back", CTRL_BACK },
{ "playpause", CTRL_PLAYPAUSE },
};
struct WebSocketInputState : public DebuggerSubscriber {
void Buttons(DebuggerRequest &req);
void Press(DebuggerRequest &req);
void Analog(DebuggerRequest &req);
void Broadcast(net::WebSocketServer *ws) override;
protected:
struct PressInfo {
std::string ticket;
uint32_t button;
uint32_t duration;
std::string Event();
};
std::vector<PressInfo> pressTickets_;
int lastCounter_ = -1;
};
std::string WebSocketInputState::PressInfo::Event() {
JsonWriter j;
j.begin();
j.writeString("event", "input.press");
if (!ticket.empty()) {
j.writeRaw("ticket", ticket);
}
j.end();
return j.str();
}
DebuggerSubscriber *WebSocketInputInit(DebuggerEventHandlerMap &map) {
auto p = new WebSocketInputState();
map["input.buttons"] = std::bind(&WebSocketInputState::Buttons, p, std::placeholders::_1);
map["input.press"] = std::bind(&WebSocketInputState::Press, p, std::placeholders::_1);
map["input.analog"] = std::bind(&WebSocketInputState::Analog, p, std::placeholders::_1);
return p;
}
// Alter PSP button press flags (input.buttons)
//
// Parameters:
// - buttons: object containing button names as string keys, boolean press state as value.
//
// Button names (some are not respected by PPSSPP):
// - cross: button on bottom side of right pad.
// - circle: button on right side of right pad.
// - triangle: button on top side of right pad.
// - square: button on left side of right pad.
// - up: d-pad up button.
// - down: d-pad down button.
// - left: d-pad left button.
// - right: d-pad right button.
// - start: rightmost button at bottom of device.
// - select: second to the right at bottom of device.
// - home: leftmost button at bottom of device.
// - screen: brightness control button at bottom of device.
// - note: mute control button at bottom of device.
// - ltrigger: left shoulder trigger button.
// - rtrigger: right shoulder trigger button.
// - hold: hold setting of power switch.
// - wlan: wireless networking switch.
// - remote_hold: hold switch on headset.
// - vol_up: volume up button next to home at bottom of device.
// - vol_down: volume down button next to home at bottom of device.
// - disc: UMD disc sensor.
// - memstick: memory stick sensor.
// - forward: forward button on headset.
// - back: back button on headset.
// - playpause: play/pause button on headset.
//
// Empty response.
void WebSocketInputState::Buttons(DebuggerRequest &req) {
const JsonNode *jsonButtons = req.data.get("buttons");
if (!jsonButtons) {
return req.Fail("Missing 'buttons' parameter");
}
if (jsonButtons->value.getTag() != JSON_OBJECT) {
return req.Fail("Invalid 'buttons' parameter type");
}
uint32_t downFlags = 0;
uint32_t upFlags = 0;
for (const JsonNode *button : jsonButtons->value) {
auto info = buttonLookup.find(button->key);
if (info == buttonLookup.end()) {
return req.Fail(StringFromFormat("Unsupported 'buttons' object key '%s'", button->key));
}
if (button->value.getTag() == JSON_TRUE) {
downFlags |= info->second;
} else if (button->value.getTag() == JSON_FALSE) {
upFlags |= info->second;
} else if (button->value.getTag() != JSON_NULL) {
return req.Fail(StringFromFormat("Unsupported 'buttons' object type for key '%s'", button->key));
}
}
if (downFlags) {
__CtrlButtonDown(downFlags);
}
if (upFlags) {
__CtrlButtonUp(upFlags);
}
req.Respond();
}
// Press and release a button (input.press)
//
// Parameters:
// - button: required string indicating button name (see input.buttons.)
// - duration: optional integer indicating frames to press for, defaults to 1.
//
// Empty response once released.
void WebSocketInputState::Press(DebuggerRequest &req) {
std::string button;
if (!req.ParamString("button", &button))
return;
PressInfo press;
press.duration = 1;
if (!req.ParamU32("duration", &press.duration, false, DebuggerParamType::OPTIONAL))
return;
if (press.duration < 0)
return req.Fail("Parameter 'duration' must not be negative");
const JsonNode *value = req.data.get("ticket");
press.ticket = value ? json_stringify(value) : "";
auto info = buttonLookup.find(button);
if (info == buttonLookup.end()) {
return req.Fail(StringFromFormat("Unsupported button value '%s'", button.c_str()));
}
press.button = info->second;
__CtrlButtonDown(press.button);
pressTickets_.push_back(press);
}
void WebSocketInputState::Broadcast(net::WebSocketServer *ws) {
int counter = __DisplayGetNumVblanks();
if (pressTickets_.empty() || lastCounter_ == counter)
return;
lastCounter_ = counter;
for (PressInfo &press : pressTickets_) {
press.duration--;
if (press.duration == -1) {
__CtrlButtonUp(press.button);
ws->Send(press.Event());
}
}
auto negative = [](const PressInfo &press) -> bool {
return press.duration < 0;
};
pressTickets_.erase(std::remove_if(pressTickets_.begin(), pressTickets_.end(), negative), pressTickets_.end());
}
static bool AnalogValue(DebuggerRequest &req, float *value, const char *name) {
const JsonNode *node = req.data.get(name);
if (!node) {
req.Fail(StringFromFormat("Missing '%s' parameter", name));
return false;
}
if (node->value.getTag() != JSON_NUMBER) {
req.Fail(StringFromFormat("Invalid '%s' parameter type", name));
return false;
}
double val = node->value.toNumber();
if (val < 1.0 || val > 1.0) {
req.Fail(StringFromFormat("Parameter '%s' must be between -1.0 and 1.0", name));
return false;
}
*value = (float)val;
return true;
}
// Set coordinates of analog stick (input.analog)
//
// Parameters:
// - x: required number from -1.0 to 1.0.
// - y: required number from -1.0 to 1.0.
// - stick: optional string, either "left" (default) or "right".
//
// Empty response.
void WebSocketInputState::Analog(DebuggerRequest &req) {
std::string stick = "left";
if (!req.ParamString("stick", &stick, DebuggerParamType::OPTIONAL))
return;
if (stick != "left" && stick != "right")
return req.Fail(StringFromFormat("Parameter 'stick' must be 'left' or 'right', not '%s'", stick.c_str()));
float x, y;
if (!AnalogValue(req, &x, "x") || !AnalogValue(req, &y, "y"))
return;
__CtrlSetAnalogX(x, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT);
__CtrlSetAnalogY(y, stick == "left" ? CTRL_STICK_LEFT : CTRL_STICK_RIGHT);
req.Respond();
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2021- 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
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
DebuggerSubscriber *WebSocketInputInit(DebuggerEventHandlerMap &map);

View File

@ -399,6 +399,7 @@
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\LogBroadcaster.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\MemorySubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\SteppingBroadcaster.h" />
@ -629,6 +630,7 @@
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\LogBroadcaster.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\MemorySubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\SteppingBroadcaster.cpp" />

View File

@ -682,6 +682,9 @@
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLESubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\Debugger\WebSocket\LogBroadcaster.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
@ -1488,6 +1491,9 @@
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLESubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\Debugger\WebSocket\LogBroadcaster.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>

View File

@ -413,6 +413,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/GPURecordSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/InputSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \
$(SRC)/Core/Debugger/WebSocket/MemorySubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \