mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 02:39:05 +02:00
input.buttons.press, gpu.stats.get, gpu.record.dump and the game.state.* operations with wait answer later with a message carrying the request's ticket. Their deferred acknowledgement carried that ticket too, and wsdbg took it for the answer, so a waited save that failed exited 0 and --sync moved on before it was done. Handlers that answer this way now call ReplyLater(), and the deferred event says "replyFollows": true. wsdbg keeps waiting for the reply in one-shot mode, under --sync and for :nowait lines. An error reply or a missing one to a :nowait line now fails the run, a raw line can't reuse a ticket that is still pending, and auto-assigned tickets skip past ones a raw line chose. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
173 lines
5.3 KiB
C++
173 lines
5.3 KiB
C++
// 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
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <string>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Data/Format/JSONReader.h"
|
|
#include "Common/Data/Format/JSONWriter.h"
|
|
#include "Common/Net/WebsocketServer.h"
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
// Enum name overlapped with UWP macro, quick hack to disable it
|
|
#undef OPTIONAL
|
|
#endif
|
|
|
|
using namespace json;
|
|
|
|
struct WebSocketClientInfo {
|
|
WebSocketClientInfo() = default;
|
|
|
|
std::string name;
|
|
std::string version;
|
|
std::map<std::string, bool> disallowed;
|
|
// Whether to send a "deferred" event for requests that only finish later. Off by default,
|
|
// and it has to stay that way: an extra message would break a client that correlates purely
|
|
// by ticket, or one that waits for the bare event name a resume or a button press answers
|
|
// with. See client.config.set.
|
|
bool acknowledgeDeferred = false;
|
|
};
|
|
|
|
// Sent, if the client asked for it, when a handler accepted a request but arranged to finish it
|
|
// later - a resume, a step, a button held for some frames. Deliberately not named after the
|
|
// request: the event that reports the actual outcome uses that name, and for input.buttons.press
|
|
// it carries the same ticket too, so anything less distinct would be impossible to tell apart.
|
|
// replyFollows says that outcome is a reply carrying the request's ticket (see ReplyLater()).
|
|
struct DebuggerDeferredEvent {
|
|
DebuggerDeferredEvent(const char *n, const JsonGet data, bool replyFollows) : name(n), replyFollows(replyFollows) {
|
|
const JsonNode *value = data ? data.get("ticket") : nullptr;
|
|
if (value)
|
|
ticketRaw = json_stringify(value);
|
|
}
|
|
|
|
const char *name;
|
|
bool replyFollows;
|
|
std::string ticketRaw;
|
|
|
|
operator std::string() const {
|
|
JsonWriter j;
|
|
j.begin();
|
|
j.writeString("event", "deferred");
|
|
j.writeString("for", name);
|
|
if (replyFollows) {
|
|
j.writeBool("replyFollows", true);
|
|
}
|
|
if (!ticketRaw.empty()) {
|
|
j.writeRaw("ticket", ticketRaw);
|
|
}
|
|
j.end();
|
|
return j.str();
|
|
}
|
|
};
|
|
|
|
struct DebuggerErrorEvent {
|
|
DebuggerErrorEvent(const std::string m, LogLevel l, const JsonGet data = JsonValue(JSON_NULL))
|
|
: message(m), level(l) {
|
|
// Need to format right away, before it's out of scope.
|
|
if (data) {
|
|
const JsonNode *value = data.get("ticket");
|
|
if (value)
|
|
ticketRaw = json_stringify(value);
|
|
}
|
|
}
|
|
|
|
std::string message;
|
|
LogLevel level;
|
|
std::string ticketRaw;
|
|
|
|
operator std::string() const {
|
|
JsonWriter j;
|
|
j.begin();
|
|
j.writeString("event", "error");
|
|
j.writeString("message", message);
|
|
j.writeInt("level", (int)level);
|
|
if (!ticketRaw.empty()) {
|
|
j.writeRaw("ticket", ticketRaw);
|
|
}
|
|
j.end();
|
|
return j.str();
|
|
}
|
|
};
|
|
|
|
enum class DebuggerParamType {
|
|
REQUIRED,
|
|
OPTIONAL,
|
|
REQUIRED_LOOSE,
|
|
OPTIONAL_LOOSE,
|
|
};
|
|
|
|
struct DebuggerRequest {
|
|
DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d, WebSocketClientInfo *client_info)
|
|
: name(n), ws(w), data(d), client(client_info) {
|
|
}
|
|
|
|
const char *name;
|
|
net::WebSocketServer *ws;
|
|
const JsonGet data;
|
|
WebSocketClientInfo *client;
|
|
|
|
void Fail(const std::string &message) {
|
|
ws->Send(DebuggerErrorEvent(message, LogLevel::LERROR, data));
|
|
responseSent_ = true;
|
|
}
|
|
|
|
bool HasParam(const char *name, bool ignoreNull = false);
|
|
bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
// For quantities that don't fit in 32 bits, like emulated microseconds - JSON numbers are
|
|
// doubles anyway, which is exact well past any plausible session length.
|
|
bool ParamF64(const char *name, double *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
|
|
|
|
JsonWriter &Respond();
|
|
void Flush();
|
|
bool Finish();
|
|
|
|
// For a handler that answers later with a message carrying the request's ticket, rather than
|
|
// a ticketless broadcast. The deferred event then tells the client to keep waiting for it.
|
|
void ReplyLater() {
|
|
replyLater_ = true;
|
|
}
|
|
bool RepliesLater() const {
|
|
return replyLater_;
|
|
}
|
|
|
|
private:
|
|
JsonWriter writer_;
|
|
bool responseBegun_ = false;
|
|
bool responseSent_ = false;
|
|
bool responsePartial_ = false;
|
|
bool replyLater_ = false;
|
|
};
|
|
|
|
class DebuggerSubscriber {
|
|
public:
|
|
virtual ~DebuggerSubscriber() {}
|
|
|
|
// Subscribers can also broadcast if they have simple cases to.
|
|
virtual void Broadcast(net::WebSocketServer *ws) {}
|
|
};
|
|
|
|
typedef std::function<void(DebuggerRequest &req)> DebuggerEventHandler;
|
|
typedef std::unordered_map<std::string_view, DebuggerEventHandler> DebuggerEventHandlerMap;
|
|
|
|
uint32_t RoundMemAddressUp(uint32_t addr);
|