Debugger: Add replay execute/flush APIs.

This commit is contained in:
Unknown W. Brackets 2021-05-30 15:25:07 -07:00
parent 85c64d3ec3
commit ea59fa3562
3 changed files with 79 additions and 7 deletions

View file

@ -16,6 +16,7 @@
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <cstdint>
#include "Common/Data/Encoding/Base64.h"
#include "Common/Swap.h"
#include "Core/HLE/sceRtc.h"
#include "Core/Replay.h"
@ -54,28 +55,69 @@ void WebSocketReplayBegin(DebuggerRequest &req) {
//
// No parameters.
//
// Empty response.
// Response (same event name) with no extra data.
void WebSocketReplayAbort(DebuggerRequest &req) {
ReplayAbort();
req.Respond();
}
// Flush current recording data (replay.flush)
//
// Flushes event data and returns it. Note when combining, you must decode first.
//
// No parameters.
//
// Response (same event name):
// - version: unsigned integer, version number of data.
// - base64: base64 encode of binary data.
void WebSocketReplayFlush(DebuggerRequest &req) {
if (!PSP_IsInited())
return req.Fail("Game not running");
// TODO
std::vector<uint8_t> data;
ReplayFlushBlob(&data);
JsonWriter &json = req.Respond();
json.writeInt("version", ReplayVersion());
json.writeString("base64", Base64Encode(data.data(), data.size()));
}
// Begin executing a replay (replay.execute)
//
// Parameters:
// - version: unsigned integer, same version from replay.flush.
// - base64: base64 encoded replay data.
//
// Response (same event name) with no extra data.
void WebSocketReplayExecute(DebuggerRequest &req) {
if (!PSP_IsInited())
return req.Fail("Game not running");
// TODO
uint32_t version = -1;
if (!req.ParamU32("version", &version))
return;
std::string encoded;
if (!req.ParamString("base64", &encoded))
return;
std::vector<uint8_t> data = Base64Decode(encoded.data(), encoded.size());
if (!ReplayExecuteBlob(version, data))
return req.Fail("Invalid replay data or version");
req.Respond();
}
// Get replay status (replay.status)
//
// No parameters.
//
// Response (same event name):
// - executing: boolean if a replay is being executed.
// - saving: boolean if a replay is being recorded.
void WebSocketReplayStatus(DebuggerRequest &req) {
// TODO
JsonWriter &json = req.Respond();
json.writeBool("executing", ReplayIsExecuting());
json.writeBool("saving", ReplayIsSaving());
}
// Get the base RTC (real time clock) time for replay data (replay.time.get)

View file

@ -136,7 +136,16 @@ static uint8_t lastAnalog[2][2]{};
static size_t replayDiskPos = 0;
static bool diskFailed = false;
void ReplayExecuteBlob(const std::vector<uint8_t> &data) {
bool ReplayExecuteBlob(int version, const std::vector<uint8_t> &data) {
if (version < REPLAY_VERSION_MIN || version > REPLAY_VERSION_CURRENT) {
ERROR_LOG(SYSTEM, "Bad replay data version: %d", version);
return false;
}
if (data.size() == 0) {
ERROR_LOG(SYSTEM, "Empty replay data");
return false;
}
ReplayAbort();
// Rough estimate.
@ -167,6 +176,7 @@ void ReplayExecuteBlob(const std::vector<uint8_t> &data) {
replayState = ReplayState::EXECUTE;
INFO_LOG(SYSTEM, "Executing replay with %lld items", (long long)replayItems.size());
return true;
}
bool ReplayExecuteFile(const Path &filename) {
@ -178,6 +188,7 @@ bool ReplayExecuteFile(const Path &filename) {
return false;
}
int version = -1;
std::vector<uint8_t> data;
auto loadData = [&]() {
// TODO: Maybe stream instead.
@ -207,6 +218,7 @@ bool ReplayExecuteFile(const Path &filename) {
}
RtcSetBaseTime((int32_t)fh.rtcBaseSeconds, 0);
version = fh.version;
data.resize(sz);
@ -220,7 +232,7 @@ bool ReplayExecuteFile(const Path &filename) {
if (loadData()) {
fclose(fp);
ReplayExecuteBlob(data);
ReplayExecuteBlob(version, data);
return true;
}
@ -306,6 +318,10 @@ bool ReplayFlushFile(const Path &filename) {
return success;
}
int ReplayVersion() {
return REPLAY_VERSION_CURRENT;
}
void ReplayAbort() {
replayItems.clear();
replayExecPos = 0;
@ -321,6 +337,14 @@ void ReplayAbort() {
diskFailed = false;
}
bool ReplayIsExecuting() {
return replayState == ReplayState::EXECUTE;
}
bool ReplayIsSaving() {
return replayState == ReplayState::SAVE;
}
static void ReplaySaveCtrl(uint32_t &buttons, uint8_t analog[2][2], uint64_t t) {
if (lastButtons != buttons) {
replayItems.push_back(ReplayItemHeader(ReplayAction::BUTTONS, t, buttons));

View file

@ -49,7 +49,7 @@ enum class ReplayAction : uint8_t {
struct PSPFileInfo;
// Replay from data in memory. Does not manipulate base time / RNG state.
void ReplayExecuteBlob(const std::vector<uint8_t> &data);
bool ReplayExecuteBlob(int version, const std::vector<uint8_t> &data);
// Replay from data in a file. Returns false if invalid.
bool ReplayExecuteFile(const Path &filename);
// Returns whether there are unexecuted events to replay.
@ -63,10 +63,16 @@ void ReplayFlushBlob(std::vector<uint8_t> *data);
// Flush buffered events to file. Continues recording (next call will receive new events only.)
// Do not call with a different filename before ReplayAbort().
bool ReplayFlushFile(const Path &filename);
// Get current replay data version.
int ReplayVersion();
// Abort any execute or record operation in progress.
void ReplayAbort();
// Check if replay data is being executed or saved.
bool ReplayIsExecuting();
bool ReplayIsSaving();
void ReplayApplyCtrl(uint32_t &buttons, uint8_t analog[2][2], uint64_t t);
uint32_t ReplayApplyDisk(ReplayAction action, uint32_t result, uint64_t t);
uint64_t ReplayApplyDisk64(ReplayAction action, uint64_t result, uint64_t t);