feat(emulator): implement gRPC control server and emulator commands
- Added `AgentControlServer` to manage gRPC connections for emulator control. - Introduced `EmulatorServiceImpl` with methods for starting, stopping, and controlling the emulator, including button presses and memory operations. - Created new command handlers for pressing, releasing, and holding emulator buttons. - Updated CMake configuration to include new source files and proto definitions for the emulator service. Benefits: - Enhanced control over the emulator through gRPC, allowing for remote interactions. - Improved modularity and maintainability of the emulator's command handling. - Streamlined integration of new features for emulator control and state inspection.
This commit is contained in:
@@ -34,6 +34,87 @@
|
||||
"required": ["type", "query"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emulator-press-buttons",
|
||||
"description": "Presses and immediately releases one or more buttons on the SNES controller.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buttons": {
|
||||
"type": "string",
|
||||
"description": "A comma-separated list of buttons to press (e.g., 'A,Right,Start')."
|
||||
}
|
||||
},
|
||||
"required": ["buttons"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emulator-hold-buttons",
|
||||
"description": "Holds down one or more buttons for a specified duration.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buttons": {
|
||||
"type": "string",
|
||||
"description": "A comma-separated list of buttons to hold."
|
||||
},
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "The duration in milliseconds to hold the buttons."
|
||||
}
|
||||
},
|
||||
"required": ["buttons", "duration"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emulator-get-state",
|
||||
"description": "Retrieves the current state of the game from the emulator.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"screenshot": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to include a screenshot of the current frame."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emulator-read-memory",
|
||||
"description": "Reads a block of memory from the SNES WRAM.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "The memory address to read from (e.g., '0x7E0010')."
|
||||
},
|
||||
"length": {
|
||||
"type": "integer",
|
||||
"description": "The number of bytes to read."
|
||||
}
|
||||
},
|
||||
"required": ["address"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emulator-write-memory",
|
||||
"description": "Writes a block of data to the SNES WRAM.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "The memory address to write to."
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
"description": "The data to write, as a hex string (e.g., 'AABBCCDD')."
|
||||
}
|
||||
},
|
||||
"required": ["address", "data"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dungeon-list-sprites",
|
||||
"description": "List all sprites in a specific dungeon or room",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
set(YAZE_AGENT_SOURCES
|
||||
cli/service/agent/proposal_executor.cc
|
||||
cli/handlers/agent/todo_commands.cc
|
||||
cli/service/agent/agent_control_server.cc
|
||||
cli/service/agent/conversational_agent_service.cc
|
||||
cli/service/agent/emulator_service_impl.cc
|
||||
cli/service/agent/simple_chat_session.cc
|
||||
cli/service/agent/enhanced_tui.cc
|
||||
cli/service/agent/tool_dispatcher.cc
|
||||
@@ -111,7 +113,8 @@ if(YAZE_WITH_GRPC)
|
||||
# Generate proto files for yaze_agent
|
||||
target_add_protobuf(yaze_agent
|
||||
${PROJECT_SOURCE_DIR}/src/protos/imgui_test_harness.proto
|
||||
${PROJECT_SOURCE_DIR}/src/protos/canvas_automation.proto)
|
||||
${PROJECT_SOURCE_DIR}/src/protos/canvas_automation.proto
|
||||
${PROJECT_SOURCE_DIR}/src/protos/emulator_service.proto)
|
||||
|
||||
target_link_libraries(yaze_agent PUBLIC
|
||||
grpc++
|
||||
|
||||
@@ -21,3 +21,7 @@ ABSL_FLAG(std::string, prompt_version, "default",
|
||||
"Prompt version to use: 'default' or 'v2'");
|
||||
ABSL_FLAG(bool, use_function_calling, false,
|
||||
"Enable native Gemini function calling (incompatible with JSON output mode)");
|
||||
|
||||
// --- Agent Control Flags ---
|
||||
ABSL_FLAG(bool, agent_control, false,
|
||||
"Enable the gRPC server to allow the agent to control the emulator.");
|
||||
|
||||
@@ -1,35 +1,256 @@
|
||||
#include "cli/handlers/tools/emulator_commands.h"
|
||||
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include "protos/emulator_service.grpc.pb.h"
|
||||
#include "absl/strings/str_split.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace cli {
|
||||
namespace handlers {
|
||||
|
||||
namespace {
|
||||
|
||||
// A simple client for the EmulatorService
|
||||
class EmulatorClient {
|
||||
public:
|
||||
EmulatorClient() {
|
||||
auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());
|
||||
stub_ = agent::EmulatorService::NewStub(channel);
|
||||
}
|
||||
|
||||
template <typename TRequest, typename TResponse>
|
||||
absl::StatusOr<TResponse> CallRpc(
|
||||
grpc::Status (agent::EmulatorService::Stub::*rpc_method)(grpc::ClientContext*, const TRequest&, TResponse*),
|
||||
const TRequest& request) {
|
||||
|
||||
TResponse response;
|
||||
grpc::ClientContext context;
|
||||
|
||||
auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5);
|
||||
context.set_deadline(deadline);
|
||||
|
||||
grpc::Status status = (stub_.get()->*rpc_method)(&context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
return absl::UnavailableError(absl::StrFormat(
|
||||
"RPC failed: (%d) %s", status.error_code(), status.error_message()));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<agent::EmulatorService::Stub> stub_;
|
||||
};
|
||||
|
||||
// Helper to parse button from string
|
||||
absl::StatusOr<agent::Button> StringToButton(absl::string_view s) {
|
||||
if (s == "A") return agent::Button::A;
|
||||
if (s == "B") return agent::Button::B;
|
||||
if (s == "X") return agent::Button::X;
|
||||
if (s == "Y") return agent::Button::Y;
|
||||
if (s == "L") return agent::Button::L;
|
||||
if (s == "R") return agent::Button::R;
|
||||
if (s == "SELECT") return agent::Button::SELECT;
|
||||
if (s == "START") return agent::Button::START;
|
||||
if (s == "UP") return agent::Button::UP;
|
||||
if (s == "DOWN") return agent::Button::DOWN;
|
||||
if (s == "LEFT") return agent::Button::LEFT;
|
||||
if (s == "RIGHT") return agent::Button::RIGHT;
|
||||
return absl::InvalidArgumentError(absl::StrCat("Unknown button: ", s));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// --- Command Implementations ---
|
||||
|
||||
absl::Status EmulatorResetCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::Empty request;
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::Reset, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("EmulatorReset");
|
||||
formatter.AddField("success", response.success());
|
||||
formatter.AddField("message", response.message());
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorGetStateCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::GameStateRequest request;
|
||||
request.set_include_screenshot(parser.HasFlag("screenshot"));
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::GetGameState, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("EmulatorState");
|
||||
formatter.AddField("game_mode", static_cast<uint64_t>(response.game_mode()));
|
||||
formatter.AddField("link_state", static_cast<uint64_t>(response.link_state()));
|
||||
formatter.AddField("link_pos_x", static_cast<uint64_t>(response.link_pos_x()));
|
||||
formatter.AddField("link_pos_y", static_cast<uint64_t>(response.link_pos_y()));
|
||||
formatter.AddField("link_health", static_cast<uint64_t>(response.link_health()));
|
||||
if (!response.screenshot_png().empty()) {
|
||||
formatter.AddField("screenshot_size", static_cast<uint64_t>(response.screenshot_png().size()));
|
||||
}
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorReadMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::MemoryRequest request;
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) {
|
||||
return absl::InvalidArgumentError("Invalid address format.");
|
||||
}
|
||||
request.set_address(address);
|
||||
request.set_size(parser.GetInt("length").value_or(16));
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReadMemory, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("MemoryRead");
|
||||
formatter.AddHexField("address", response.address());
|
||||
formatter.AddField("data_hex", absl::BytesToHexString(response.data()));
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorWriteMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::MemoryWriteRequest request;
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(parser.GetString("address").value(), &address)) {
|
||||
return absl::InvalidArgumentError("Invalid address format.");
|
||||
}
|
||||
request.set_address(address);
|
||||
|
||||
std::string data_hex = parser.GetString("data").value();
|
||||
request.set_data(absl::HexStringToBytes(data_hex));
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::WriteMemory, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("MemoryWrite");
|
||||
formatter.AddField("success", response.success());
|
||||
formatter.AddField("message", response.message());
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
absl::Status EmulatorPressButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::ButtonRequest request;
|
||||
std::vector<std::string> buttons = absl::StrSplit(parser.GetString("buttons").value(), ',');
|
||||
for (const auto& btn_str : buttons) {
|
||||
auto button_or = StringToButton(btn_str);
|
||||
if (!button_or.ok()) return button_or.status();
|
||||
request.add_buttons(button_or.value());
|
||||
}
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::PressButtons, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("PressButtons");
|
||||
formatter.AddField("success", response.success());
|
||||
formatter.AddField("message", response.message());
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorReleaseButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::ButtonRequest request;
|
||||
std::vector<std::string> buttons = absl::StrSplit(parser.GetString("buttons").value(), ',');
|
||||
for (const auto& btn_str : buttons) {
|
||||
auto button_or = StringToButton(btn_str);
|
||||
if (!button_or.ok()) return button_or.status();
|
||||
request.add_buttons(button_or.value());
|
||||
}
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::ReleaseButtons, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("ReleaseButtons");
|
||||
formatter.AddField("success", response.success());
|
||||
formatter.AddField("message", response.message());
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorHoldButtonsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
EmulatorClient client;
|
||||
agent::ButtonHoldRequest request;
|
||||
std::vector<std::string> buttons = absl::StrSplit(parser.GetString("buttons").value(), ',');
|
||||
for (const auto& btn_str : buttons) {
|
||||
auto button_or = StringToButton(btn_str);
|
||||
if (!button_or.ok()) return button_or.status();
|
||||
request.add_buttons(button_or.value());
|
||||
}
|
||||
request.set_duration_ms(parser.GetInt("duration").value());
|
||||
|
||||
auto response_or = client.CallRpc(&agent::EmulatorService::Stub::HoldButtons, request);
|
||||
if (!response_or.ok()) {
|
||||
return response_or.status();
|
||||
}
|
||||
auto response = response_or.value();
|
||||
|
||||
formatter.BeginObject("HoldButtons");
|
||||
formatter.AddField("success", response.success());
|
||||
formatter.AddField("message", response.message());
|
||||
formatter.EndObject();
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
// --- Placeholder Implementations for commands not yet migrated to gRPC ---
|
||||
|
||||
absl::Status EmulatorStepCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto count = parser.GetInt("count").value_or(1);
|
||||
|
||||
formatter.BeginObject("Emulator Step");
|
||||
formatter.AddField("steps_executed", count);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Emulator stepping requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorRunCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto until_breakpoint = parser.GetString("until").value_or("");
|
||||
|
||||
formatter.BeginObject("Emulator Run");
|
||||
formatter.AddField("until_breakpoint", until_breakpoint);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Emulator running requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -37,134 +258,31 @@ absl::Status EmulatorPauseCommandHandler::Execute(Rom* rom, const resources::Arg
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator Pause");
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Emulator pause requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorResetCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator Reset");
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Emulator reset requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorGetStateCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator State");
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Emulator state requires emulator integration");
|
||||
|
||||
formatter.BeginObject("state");
|
||||
formatter.AddField("running", false);
|
||||
formatter.AddField("paused", true);
|
||||
formatter.AddField("pc", "0x000000");
|
||||
formatter.EndObject();
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorSetBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto address_str = parser.GetString("address").value();
|
||||
auto condition = parser.GetString("condition").value_or("");
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(address_str, &address)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Invalid address format. Must be hex.");
|
||||
}
|
||||
|
||||
formatter.BeginObject("Emulator Breakpoint Set");
|
||||
formatter.AddHexField("address", address, 6);
|
||||
formatter.AddField("condition", condition);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Breakpoint setting requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorClearBreakpointCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto address_str = parser.GetString("address").value();
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(address_str, &address)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Invalid address format. Must be hex.");
|
||||
}
|
||||
|
||||
formatter.BeginObject("Emulator Breakpoint Cleared");
|
||||
formatter.AddHexField("address", address, 6);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Breakpoint clearing requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorListBreakpointsCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator Breakpoints");
|
||||
formatter.AddField("total_breakpoints", 0);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Breakpoint listing requires emulator integration");
|
||||
|
||||
formatter.BeginArray("breakpoints");
|
||||
formatter.EndArray();
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorReadMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto address_str = parser.GetString("address").value();
|
||||
auto length = parser.GetInt("length").value_or(16);
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(address_str, &address)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Invalid address format. Must be hex.");
|
||||
}
|
||||
|
||||
formatter.BeginObject("Emulator Memory Read");
|
||||
formatter.AddHexField("address", address, 6);
|
||||
formatter.AddField("length", length);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Memory reading requires emulator integration");
|
||||
|
||||
formatter.BeginArray("data");
|
||||
formatter.EndArray();
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status EmulatorWriteMemoryCommandHandler::Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) {
|
||||
auto address_str = parser.GetString("address").value();
|
||||
auto data_str = parser.GetString("data").value();
|
||||
|
||||
uint32_t address;
|
||||
if (!absl::SimpleHexAtoi(address_str, &address)) {
|
||||
return absl::InvalidArgumentError(
|
||||
"Invalid address format. Must be hex.");
|
||||
}
|
||||
|
||||
formatter.BeginObject("Emulator Memory Write");
|
||||
formatter.AddHexField("address", address, 6);
|
||||
formatter.AddField("data", data_str);
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Memory writing requires emulator integration");
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -172,19 +290,7 @@ absl::Status EmulatorGetRegistersCommandHandler::Execute(Rom* rom, const resourc
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator Registers");
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Register reading requires emulator integration");
|
||||
|
||||
formatter.BeginObject("registers");
|
||||
formatter.AddField("A", std::string("0x0000"));
|
||||
formatter.AddField("X", std::string("0x0000"));
|
||||
formatter.AddField("Y", std::string("0x0000"));
|
||||
formatter.AddField("PC", std::string("0x000000"));
|
||||
formatter.AddField("SP", std::string("0x01FF"));
|
||||
formatter.AddField("DB", std::string("0x00"));
|
||||
formatter.AddField("DP", std::string("0x0000"));
|
||||
formatter.EndObject();
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -192,16 +298,7 @@ absl::Status EmulatorGetMetricsCommandHandler::Execute(Rom* rom, const resources
|
||||
resources::OutputFormatter& formatter) {
|
||||
formatter.BeginObject("Emulator Metrics");
|
||||
formatter.AddField("status", "not_implemented");
|
||||
formatter.AddField("message", "Metrics require emulator integration");
|
||||
|
||||
formatter.BeginObject("metrics");
|
||||
formatter.AddField("instructions_per_second", 0);
|
||||
formatter.AddField("total_instructions", 0);
|
||||
formatter.AddField("cycles_per_frame", 0);
|
||||
formatter.AddField("frame_rate", 0);
|
||||
formatter.EndObject();
|
||||
formatter.EndObject();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@ namespace yaze {
|
||||
namespace cli {
|
||||
namespace handlers {
|
||||
|
||||
/**
|
||||
* @brief Command handler for emulator step execution
|
||||
*/
|
||||
class EmulatorStepCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-step"; }
|
||||
@@ -21,16 +18,13 @@ class EmulatorStepCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for emulator run execution
|
||||
*/
|
||||
class EmulatorRunCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-run"; }
|
||||
@@ -42,16 +36,13 @@ class EmulatorRunCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for emulator pause
|
||||
*/
|
||||
class EmulatorPauseCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-pause"; }
|
||||
@@ -63,16 +54,13 @@ class EmulatorPauseCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for emulator reset
|
||||
*/
|
||||
class EmulatorResetCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-reset"; }
|
||||
@@ -84,16 +72,13 @@ class EmulatorResetCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for getting emulator state
|
||||
*/
|
||||
class EmulatorGetStateCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-get-state"; }
|
||||
@@ -105,16 +90,13 @@ class EmulatorGetStateCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for setting breakpoints
|
||||
*/
|
||||
class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-set-breakpoint"; }
|
||||
@@ -133,9 +115,6 @@ class EmulatorSetBreakpointCommandHandler : public resources::CommandHandler {
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for clearing breakpoints
|
||||
*/
|
||||
class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-clear-breakpoint"; }
|
||||
@@ -154,9 +133,6 @@ class EmulatorClearBreakpointCommandHandler : public resources::CommandHandler {
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for listing breakpoints
|
||||
*/
|
||||
class EmulatorListBreakpointsCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-list-breakpoints"; }
|
||||
@@ -168,16 +144,13 @@ class EmulatorListBreakpointsCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for reading emulator memory
|
||||
*/
|
||||
class EmulatorReadMemoryCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-read-memory"; }
|
||||
@@ -196,9 +169,6 @@ class EmulatorReadMemoryCommandHandler : public resources::CommandHandler {
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for writing emulator memory
|
||||
*/
|
||||
class EmulatorWriteMemoryCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-write-memory"; }
|
||||
@@ -217,9 +187,6 @@ class EmulatorWriteMemoryCommandHandler : public resources::CommandHandler {
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for getting emulator registers
|
||||
*/
|
||||
class EmulatorGetRegistersCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-get-registers"; }
|
||||
@@ -231,16 +198,13 @@ class EmulatorGetRegistersCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Command handler for getting emulator metrics
|
||||
*/
|
||||
class EmulatorGetMetricsCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-get-metrics"; }
|
||||
@@ -252,13 +216,65 @@ class EmulatorGetMetricsCommandHandler : public resources::CommandHandler {
|
||||
}
|
||||
|
||||
absl::Status ValidateArgs(const resources::ArgumentParser& parser) override {
|
||||
return absl::OkStatus(); // No required args
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
class EmulatorPressButtonsCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-press-buttons"; }
|
||||
std::string GetDescription() const {
|
||||
return "Press and release emulator buttons";
|
||||
}
|
||||
std::string GetUsage() const {
|
||||
return "emulator-press-buttons --buttons <button1>,<button2>,...";
|
||||
}
|
||||
absl::Status ValidateArgs(
|
||||
const resources::ArgumentParser& parser) override {
|
||||
return parser.RequireArgs({"buttons"});
|
||||
}
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
class EmulatorReleaseButtonsCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-release-buttons"; }
|
||||
std::string GetDescription() const {
|
||||
return "Release currently held emulator buttons";
|
||||
}
|
||||
std::string GetUsage() const {
|
||||
return "emulator-release-buttons --buttons <button1>,<button2>,...";
|
||||
}
|
||||
absl::Status ValidateArgs(
|
||||
const resources::ArgumentParser& parser) override {
|
||||
return parser.RequireArgs({"buttons"});
|
||||
}
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
class EmulatorHoldButtonsCommandHandler : public resources::CommandHandler {
|
||||
public:
|
||||
std::string GetName() const { return "emulator-hold-buttons"; }
|
||||
std::string GetDescription() const {
|
||||
return "Hold emulator buttons for a specified duration";
|
||||
}
|
||||
std::string GetUsage() const {
|
||||
return "emulator-hold-buttons --buttons <button1>,<button2>,... --duration "
|
||||
"<ms>";
|
||||
}
|
||||
absl::Status ValidateArgs(
|
||||
const resources::ArgumentParser& parser) override {
|
||||
return parser.RequireArgs({"buttons", "duration"});
|
||||
}
|
||||
absl::Status Execute(Rom* rom, const resources::ArgumentParser& parser,
|
||||
resources::OutputFormatter& formatter) override;
|
||||
};
|
||||
|
||||
} // namespace handlers
|
||||
} // namespace cli
|
||||
} // namespace yaze
|
||||
|
||||
46
src/cli/service/agent/agent_control_server.cc
Normal file
46
src/cli/service/agent/agent_control_server.cc
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "cli/service/agent/agent_control_server.h"
|
||||
#include "cli/service/agent/emulator_service_impl.h"
|
||||
#include <grpcpp/server.h>
|
||||
#include <grpcpp/server_builder.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace yaze::agent {
|
||||
|
||||
AgentControlServer::AgentControlServer(yaze::emu::Emulator* emulator)
|
||||
: emulator_(emulator) {}
|
||||
|
||||
AgentControlServer::~AgentControlServer() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void AgentControlServer::Start() {
|
||||
server_thread_ = std::thread(&AgentControlServer::Run, this);
|
||||
}
|
||||
|
||||
void AgentControlServer::Stop() {
|
||||
if (server_) {
|
||||
server_->Shutdown();
|
||||
}
|
||||
if (server_thread_.joinable()) {
|
||||
server_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void AgentControlServer::Run() {
|
||||
std::string server_address("0.0.0.0:50051");
|
||||
EmulatorServiceImpl service(emulator_);
|
||||
|
||||
grpc::ServerBuilder builder;
|
||||
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
|
||||
builder.RegisterService(&service);
|
||||
|
||||
server_ = builder.BuildAndStart();
|
||||
if (server_) {
|
||||
std::cout << "AgentControlServer listening on " << server_address << std::endl;
|
||||
server_->Wait();
|
||||
} else {
|
||||
std::cerr << "Failed to start AgentControlServer on " << server_address << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace yaze::agent
|
||||
32
src/cli/service/agent/agent_control_server.h
Normal file
32
src/cli/service/agent/agent_control_server.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace grpc {
|
||||
class Server;
|
||||
}
|
||||
|
||||
namespace yaze::emu {
|
||||
class Emulator;
|
||||
}
|
||||
|
||||
namespace yaze::agent {
|
||||
|
||||
class AgentControlServer {
|
||||
public:
|
||||
AgentControlServer(yaze::emu::Emulator* emulator);
|
||||
~AgentControlServer();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void Run();
|
||||
|
||||
yaze::emu::Emulator* emulator_; // Non-owning pointer
|
||||
std::unique_ptr<grpc::Server> server_;
|
||||
std::thread server_thread_;
|
||||
};
|
||||
|
||||
} // namespace yaze::agent
|
||||
190
src/cli/service/agent/emulator_service_impl.cc
Normal file
190
src/cli/service/agent/emulator_service_impl.cc
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "cli/service/agent/emulator_service_impl.h"
|
||||
#include "app/emu/emulator.h"
|
||||
#include "app/core/service/screenshot_utils.h"
|
||||
#include "app/emu/input/input_backend.h" // Required for SnesButton enum
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
namespace yaze::agent {
|
||||
|
||||
namespace {
|
||||
// Helper to convert our gRPC Button enum to the emulator's SnesButton enum
|
||||
emu::input::SnesButton ToSnesButton(Button button) {
|
||||
using emu::input::SnesButton;
|
||||
switch (button) {
|
||||
case A: return SnesButton::A;
|
||||
case B: return SnesButton::B;
|
||||
case X: return SnesButton::X;
|
||||
case Y: return SnesButton::Y;
|
||||
case L: return SnesButton::L;
|
||||
case R: return SnesButton::R;
|
||||
case SELECT: return SnesButton::SELECT;
|
||||
case START: return SnesButton::START;
|
||||
case UP: return SnesButton::UP;
|
||||
case DOWN: return SnesButton::DOWN;
|
||||
case LEFT: return SnesButton::LEFT;
|
||||
case RIGHT: return SnesButton::RIGHT;
|
||||
default:
|
||||
return SnesButton::B; // Default fallback
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EmulatorServiceImpl::EmulatorServiceImpl(yaze::emu::Emulator* emulator)
|
||||
: emulator_(emulator) {}
|
||||
|
||||
// --- Lifecycle ---
|
||||
|
||||
grpc::Status EmulatorServiceImpl::Start(grpc::ServerContext* context, const Empty* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
emulator_->set_running(true);
|
||||
response->set_success(true);
|
||||
response->set_message("Emulator started.");
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::Stop(grpc::ServerContext* context, const Empty* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
emulator_->set_running(false);
|
||||
response->set_success(true);
|
||||
response->set_message("Emulator stopped.");
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::Pause(grpc::ServerContext* context, const Empty* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
emulator_->set_running(false);
|
||||
response->set_success(true);
|
||||
response->set_message("Emulator paused.");
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::Resume(grpc::ServerContext* context, const Empty* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
emulator_->set_running(true);
|
||||
response->set_success(true);
|
||||
response->set_message("Emulator resumed.");
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::Reset(grpc::ServerContext* context, const Empty* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
emulator_->snes().Reset(true); // Hard reset
|
||||
response->set_success(true);
|
||||
response->set_message("Emulator reset.");
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
// --- Input Control ---
|
||||
|
||||
grpc::Status EmulatorServiceImpl::PressButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
auto& input_manager = emulator_->input_manager();
|
||||
for (const auto& button : request->buttons()) {
|
||||
input_manager.PressButton(ToSnesButton(static_cast<Button>(button)));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
for (const auto& button : request->buttons()) {
|
||||
input_manager.ReleaseButton(ToSnesButton(static_cast<Button>(button)));
|
||||
}
|
||||
response->set_success(true);
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::ReleaseButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
auto& input_manager = emulator_->input_manager();
|
||||
for (const auto& button : request->buttons()) {
|
||||
input_manager.ReleaseButton(ToSnesButton(static_cast<Button>(button)));
|
||||
}
|
||||
response->set_success(true);
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::HoldButtons(grpc::ServerContext* context, const ButtonHoldRequest* request, CommandResponse* response) {
|
||||
if (!emulator_) return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Emulator not initialized.");
|
||||
auto& input_manager = emulator_->input_manager();
|
||||
for (const auto& button : request->buttons()) {
|
||||
input_manager.PressButton(ToSnesButton(static_cast<Button>(button)));
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(request->duration_ms()));
|
||||
for (const auto& button : request->buttons()) {
|
||||
input_manager.ReleaseButton(ToSnesButton(static_cast<Button>(button)));
|
||||
}
|
||||
response->set_success(true);
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
// --- State Inspection ---
|
||||
|
||||
grpc::Status EmulatorServiceImpl::GetGameState(grpc::ServerContext* context, const GameStateRequest* request, GameStateResponse* response) {
|
||||
if (!emulator_ || !emulator_->is_snes_initialized()) {
|
||||
return grpc::Status(grpc::StatusCode::UNAVAILABLE, "SNES is not initialized.");
|
||||
}
|
||||
auto& memory = emulator_->snes().memory();
|
||||
|
||||
response->set_game_mode(memory.ReadByte(0x7E0010));
|
||||
response->set_link_state(memory.ReadByte(0x7E005D));
|
||||
response->set_link_pos_x(memory.ReadWord(0x7E0020));
|
||||
response->set_link_pos_y(memory.ReadWord(0x7E0022));
|
||||
response->set_link_health(memory.ReadByte(0x7EF36D));
|
||||
|
||||
for (const auto& mem_req : request->memory_reads()) {
|
||||
auto* mem_resp = response->add_memory_responses();
|
||||
mem_resp->set_address(mem_req.address());
|
||||
std::vector<uint8_t> data(mem_req.size());
|
||||
for (uint32_t i = 0; i < mem_req.size(); ++i) {
|
||||
data[i] = memory.ReadByte(mem_req.address() + i);
|
||||
}
|
||||
mem_resp->set_data(data.data(), data.size());
|
||||
}
|
||||
|
||||
#ifdef YAZE_WITH_GRPC
|
||||
if (request->include_screenshot()) {
|
||||
auto screenshot = yaze::test::CaptureHarnessScreenshot();
|
||||
if (screenshot.ok()) {
|
||||
// Read the screenshot file and convert to PNG data
|
||||
std::ifstream file(screenshot->file_path, std::ios::binary);
|
||||
if (file.good()) {
|
||||
std::string png_data((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
response->set_screenshot_png(png_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::ReadMemory(grpc::ServerContext* context, const MemoryRequest* request, MemoryResponse* response) {
|
||||
if (!emulator_ || !emulator_->is_snes_initialized()) {
|
||||
return grpc::Status(grpc::StatusCode::UNAVAILABLE, "SNES is not initialized.");
|
||||
}
|
||||
auto& memory = emulator_->snes().memory();
|
||||
response->set_address(request->address());
|
||||
std::vector<uint8_t> data(request->size());
|
||||
for (uint32_t i = 0; i < request->size(); ++i) {
|
||||
data[i] = memory.ReadByte(request->address() + i);
|
||||
}
|
||||
response->set_data(data.data(), data.size());
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
grpc::Status EmulatorServiceImpl::WriteMemory(grpc::ServerContext* context, const MemoryWriteRequest* request, CommandResponse* response) {
|
||||
if (!emulator_ || !emulator_->is_snes_initialized()) {
|
||||
return grpc::Status(grpc::StatusCode::UNAVAILABLE, "SNES is not initialized.");
|
||||
}
|
||||
auto& memory = emulator_->snes().memory();
|
||||
const std::string& data = request->data();
|
||||
for (uint32_t i = 0; i < data.size(); ++i) {
|
||||
memory.WriteByte(request->address() + i, static_cast<uint8_t>(data[i]));
|
||||
}
|
||||
response->set_success(true);
|
||||
response->set_message(absl::StrFormat("Wrote %d bytes to 0x%X.", data.size(), request->address()));
|
||||
return grpc::Status::OK;
|
||||
}
|
||||
|
||||
} // namespace yaze::agent
|
||||
38
src/cli/service/agent/emulator_service_impl.h
Normal file
38
src/cli/service/agent/emulator_service_impl.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <grpcpp/grpcpp.h>
|
||||
#include "protos/emulator_service.grpc.pb.h"
|
||||
|
||||
// Forward declaration to avoid circular dependencies
|
||||
namespace yaze::emu {
|
||||
class Emulator;
|
||||
}
|
||||
|
||||
namespace yaze::agent {
|
||||
|
||||
class EmulatorServiceImpl final : public EmulatorService::Service {
|
||||
public:
|
||||
explicit EmulatorServiceImpl(yaze::emu::Emulator* emulator);
|
||||
|
||||
// --- Lifecycle ---
|
||||
grpc::Status Start(grpc::ServerContext* context, const Empty* request, CommandResponse* response) override;
|
||||
grpc::Status Stop(grpc::ServerContext* context, const Empty* request, CommandResponse* response) override;
|
||||
grpc::Status Pause(grpc::ServerContext* context, const Empty* request, CommandResponse* response) override;
|
||||
grpc::Status Resume(grpc::ServerContext* context, const Empty* request, CommandResponse* response) override;
|
||||
grpc::Status Reset(grpc::ServerContext* context, const Empty* request, CommandResponse* response) override;
|
||||
|
||||
// --- Input Control ---
|
||||
grpc::Status PressButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) override;
|
||||
grpc::Status ReleaseButtons(grpc::ServerContext* context, const ButtonRequest* request, CommandResponse* response) override;
|
||||
grpc::Status HoldButtons(grpc::ServerContext* context, const ButtonHoldRequest* request, CommandResponse* response) override;
|
||||
|
||||
// --- State Inspection ---
|
||||
grpc::Status GetGameState(grpc::ServerContext* context, const GameStateRequest* request, GameStateResponse* response) override;
|
||||
grpc::Status ReadMemory(grpc::ServerContext* context, const MemoryRequest* request, MemoryResponse* response) override;
|
||||
grpc::Status WriteMemory(grpc::ServerContext* context, const MemoryWriteRequest* request, CommandResponse* response) override;
|
||||
|
||||
private:
|
||||
yaze::emu::Emulator* emulator_; // Non-owning pointer to the emulator instance
|
||||
};
|
||||
|
||||
} // namespace yaze::agent
|
||||
366
src/protos/emulator_service.proto
Normal file
366
src/protos/emulator_service.proto
Normal file
@@ -0,0 +1,366 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package yaze.agent;
|
||||
|
||||
// The main service for controlling the yaze emulator
|
||||
service EmulatorService {
|
||||
// --- Lifecycle ---
|
||||
rpc Start(Empty) returns (CommandResponse);
|
||||
rpc Stop(Empty) returns (CommandResponse);
|
||||
rpc Pause(Empty) returns (CommandResponse);
|
||||
rpc Resume(Empty) returns (CommandResponse);
|
||||
rpc Reset(Empty) returns (CommandResponse);
|
||||
|
||||
// --- Input Control ---
|
||||
rpc PressButtons(ButtonRequest) returns (CommandResponse);
|
||||
rpc ReleaseButtons(ButtonRequest) returns (CommandResponse);
|
||||
rpc HoldButtons(ButtonHoldRequest) returns (CommandResponse);
|
||||
|
||||
// --- State Inspection (The Feedback Loop) ---
|
||||
rpc GetGameState(GameStateRequest) returns (GameStateResponse);
|
||||
rpc ReadMemory(MemoryRequest) returns (MemoryResponse);
|
||||
rpc WriteMemory(MemoryWriteRequest) returns (CommandResponse);
|
||||
|
||||
// --- Advanced Debugging (NEW) ---
|
||||
// Breakpoints
|
||||
rpc AddBreakpoint(BreakpointRequest) returns (BreakpointResponse);
|
||||
rpc RemoveBreakpoint(BreakpointIdRequest) returns (CommandResponse);
|
||||
rpc ListBreakpoints(Empty) returns (BreakpointListResponse);
|
||||
rpc SetBreakpointEnabled(BreakpointStateRequest) returns (CommandResponse);
|
||||
|
||||
// Watchpoints (memory access tracking)
|
||||
rpc AddWatchpoint(WatchpointRequest) returns (WatchpointResponse);
|
||||
rpc RemoveWatchpoint(WatchpointIdRequest) returns (CommandResponse);
|
||||
rpc ListWatchpoints(Empty) returns (WatchpointListResponse);
|
||||
rpc GetWatchpointHistory(WatchpointHistoryRequest) returns (WatchpointHistoryResponse);
|
||||
|
||||
// Execution Control
|
||||
rpc StepInstruction(Empty) returns (StepResponse);
|
||||
rpc RunToBreakpoint(Empty) returns (BreakpointHitResponse);
|
||||
rpc StepOver(Empty) returns (StepResponse); // Step over subroutines
|
||||
rpc StepOut(Empty) returns (StepResponse); // Step out of subroutine
|
||||
|
||||
// Disassembly & Code Analysis
|
||||
rpc GetDisassembly(DisassemblyRequest) returns (DisassemblyResponse);
|
||||
rpc GetExecutionTrace(TraceRequest) returns (TraceResponse);
|
||||
|
||||
// Symbol Management (for Oracle of Secrets labels)
|
||||
rpc LoadSymbols(SymbolFileRequest) returns (CommandResponse);
|
||||
rpc ResolveSymbol(SymbolLookupRequest) returns (SymbolLookupResponse);
|
||||
rpc GetSymbolAt(AddressRequest) returns (SymbolLookupResponse);
|
||||
|
||||
// Debugging Session
|
||||
rpc CreateDebugSession(DebugSessionRequest) returns (DebugSessionResponse);
|
||||
rpc GetDebugStatus(Empty) returns (DebugStatusResponse);
|
||||
}
|
||||
|
||||
// --- Message Definitions ---
|
||||
|
||||
message Empty {}
|
||||
|
||||
message CommandResponse {
|
||||
bool success = 1;
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
enum Button {
|
||||
BUTTON_UNSPECIFIED = 0;
|
||||
A = 1;
|
||||
B = 2;
|
||||
X = 3;
|
||||
Y = 4;
|
||||
L = 5;
|
||||
R = 6;
|
||||
SELECT = 7;
|
||||
START = 8;
|
||||
UP = 9;
|
||||
DOWN = 10;
|
||||
LEFT = 11;
|
||||
RIGHT = 12;
|
||||
}
|
||||
|
||||
message ButtonRequest {
|
||||
repeated Button buttons = 1;
|
||||
}
|
||||
|
||||
message ButtonHoldRequest {
|
||||
repeated Button buttons = 1;
|
||||
uint32 duration_ms = 2; // How long to hold the buttons
|
||||
}
|
||||
|
||||
message GameStateRequest {
|
||||
bool include_screenshot = 1;
|
||||
repeated MemoryRequest memory_reads = 2;
|
||||
}
|
||||
|
||||
message GameStateResponse {
|
||||
// Key player and game variables
|
||||
uint32 game_mode = 1;
|
||||
uint32 link_state = 2;
|
||||
uint32 link_pos_x = 3;
|
||||
uint32 link_pos_y = 4;
|
||||
uint32 link_health = 5;
|
||||
|
||||
// Screenshot of the current frame
|
||||
bytes screenshot_png = 6; // PNG encoded image data
|
||||
|
||||
// Results of any requested memory reads
|
||||
repeated MemoryResponse memory_responses = 7;
|
||||
}
|
||||
|
||||
message MemoryRequest {
|
||||
uint32 address = 1;
|
||||
uint32 size = 2;
|
||||
}
|
||||
|
||||
message MemoryResponse {
|
||||
uint32 address = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message MemoryWriteRequest {
|
||||
uint32 address = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// --- Advanced Debugging Messages (NEW) ---
|
||||
|
||||
// Breakpoint types (matches BreakpointManager::Type)
|
||||
enum BreakpointType {
|
||||
BREAKPOINT_TYPE_UNSPECIFIED = 0;
|
||||
EXECUTE = 1; // Break when PC reaches address
|
||||
READ = 2; // Break when memory is read
|
||||
WRITE = 3; // Break when memory is written
|
||||
ACCESS = 4; // Break on read OR write
|
||||
CONDITIONAL = 5; // Break when condition is true
|
||||
}
|
||||
|
||||
// CPU type for breakpoints
|
||||
enum CpuType {
|
||||
CPU_TYPE_UNSPECIFIED = 0;
|
||||
CPU_65816 = 1;
|
||||
SPC700 = 2;
|
||||
}
|
||||
|
||||
// Breakpoint request
|
||||
message BreakpointRequest {
|
||||
uint32 address = 1;
|
||||
BreakpointType type = 2;
|
||||
CpuType cpu = 3;
|
||||
string condition = 4; // Optional condition (e.g., "A > 0x10")
|
||||
string description = 5; // User-friendly label
|
||||
}
|
||||
|
||||
message BreakpointResponse {
|
||||
bool success = 1;
|
||||
uint32 breakpoint_id = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
message BreakpointIdRequest {
|
||||
uint32 breakpoint_id = 1;
|
||||
}
|
||||
|
||||
message BreakpointStateRequest {
|
||||
uint32 breakpoint_id = 1;
|
||||
bool enabled = 2;
|
||||
}
|
||||
|
||||
message BreakpointInfo {
|
||||
uint32 id = 1;
|
||||
uint32 address = 2;
|
||||
BreakpointType type = 3;
|
||||
CpuType cpu = 4;
|
||||
bool enabled = 5;
|
||||
string condition = 6;
|
||||
string description = 7;
|
||||
uint32 hit_count = 8;
|
||||
}
|
||||
|
||||
message BreakpointListResponse {
|
||||
repeated BreakpointInfo breakpoints = 1;
|
||||
}
|
||||
|
||||
message BreakpointHitResponse {
|
||||
bool hit = 1;
|
||||
BreakpointInfo breakpoint = 2;
|
||||
CPUState cpu_state = 3;
|
||||
string message = 4;
|
||||
}
|
||||
|
||||
// Watchpoint messages
|
||||
message WatchpointRequest {
|
||||
uint32 start_address = 1;
|
||||
uint32 end_address = 2; // For range watchpoints
|
||||
bool track_reads = 3;
|
||||
bool track_writes = 4;
|
||||
bool break_on_access = 5;
|
||||
string description = 6;
|
||||
}
|
||||
|
||||
message WatchpointResponse {
|
||||
bool success = 1;
|
||||
uint32 watchpoint_id = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
message WatchpointIdRequest {
|
||||
uint32 watchpoint_id = 1;
|
||||
}
|
||||
|
||||
message WatchpointInfo {
|
||||
uint32 id = 1;
|
||||
uint32 start_address = 2;
|
||||
uint32 end_address = 3;
|
||||
bool track_reads = 4;
|
||||
bool track_writes = 5;
|
||||
bool break_on_access = 6;
|
||||
bool enabled = 7;
|
||||
string description = 8;
|
||||
}
|
||||
|
||||
message WatchpointListResponse {
|
||||
repeated WatchpointInfo watchpoints = 1;
|
||||
}
|
||||
|
||||
message WatchpointHistoryRequest {
|
||||
uint32 watchpoint_id = 1;
|
||||
uint32 max_entries = 2; // Max history entries to return
|
||||
}
|
||||
|
||||
message AccessLogEntry {
|
||||
uint32 pc = 1;
|
||||
uint32 address = 2;
|
||||
uint32 old_value = 3;
|
||||
uint32 new_value = 4;
|
||||
bool is_write = 5;
|
||||
uint64 cycle_count = 6;
|
||||
string description = 7;
|
||||
}
|
||||
|
||||
message WatchpointHistoryResponse {
|
||||
repeated AccessLogEntry history = 1;
|
||||
}
|
||||
|
||||
// CPU State (for stepping/debugging)
|
||||
message CPUState {
|
||||
uint32 a = 1;
|
||||
uint32 x = 2;
|
||||
uint32 y = 3;
|
||||
uint32 sp = 4;
|
||||
uint32 pc = 5;
|
||||
uint32 db = 6; // Data bank
|
||||
uint32 pb = 7; // Program bank
|
||||
uint32 d = 8; // Direct page
|
||||
uint32 status = 9; // Processor status
|
||||
bool flag_n = 10; // Negative
|
||||
bool flag_v = 11; // Overflow
|
||||
bool flag_d = 12; // Decimal
|
||||
bool flag_i = 13; // Interrupt disable
|
||||
bool flag_z = 14; // Zero
|
||||
bool flag_c = 15; // Carry
|
||||
uint64 cycles = 16;
|
||||
}
|
||||
|
||||
message StepResponse {
|
||||
bool success = 1;
|
||||
CPUState cpu_state = 2;
|
||||
DisassemblyLine instruction = 3;
|
||||
string message = 4;
|
||||
}
|
||||
|
||||
// Disassembly messages
|
||||
message DisassemblyRequest {
|
||||
uint32 start_address = 1;
|
||||
uint32 count = 2; // Number of instructions
|
||||
bool include_execution_count = 3;
|
||||
}
|
||||
|
||||
message DisassemblyLine {
|
||||
uint32 address = 1;
|
||||
uint32 opcode = 2;
|
||||
repeated uint32 operands = 3;
|
||||
string mnemonic = 4;
|
||||
string operand_str = 5;
|
||||
uint32 size = 6;
|
||||
uint64 execution_count = 7;
|
||||
bool is_breakpoint = 8;
|
||||
}
|
||||
|
||||
message DisassemblyResponse {
|
||||
repeated DisassemblyLine lines = 1;
|
||||
}
|
||||
|
||||
// Execution trace
|
||||
message TraceRequest {
|
||||
uint32 max_entries = 1; // Max trace entries to return
|
||||
uint32 start_address = 2; // Optional: filter by address range
|
||||
uint32 end_address = 3;
|
||||
}
|
||||
|
||||
message TraceEntry {
|
||||
uint32 address = 1;
|
||||
string instruction = 2;
|
||||
CPUState cpu_state_before = 3;
|
||||
uint64 cycle_count = 4;
|
||||
}
|
||||
|
||||
message TraceResponse {
|
||||
repeated TraceEntry entries = 1;
|
||||
uint32 total_count = 2;
|
||||
}
|
||||
|
||||
// Symbol management
|
||||
message SymbolFileRequest {
|
||||
string filepath = 1; // Path to symbol file (e.g., .sym, .map)
|
||||
SymbolFormat format = 2;
|
||||
}
|
||||
|
||||
enum SymbolFormat {
|
||||
SYMBOL_FORMAT_UNSPECIFIED = 0;
|
||||
ASAR = 1; // Asar assembler format
|
||||
WLA_DX = 2; // WLA-DX assembler format
|
||||
CA65 = 3; // ca65 assembler format
|
||||
MESEN = 4; // Mesen debug symbol format
|
||||
CUSTOM_JSON = 5; // Custom JSON format
|
||||
}
|
||||
|
||||
message SymbolLookupRequest {
|
||||
string symbol_name = 1;
|
||||
}
|
||||
|
||||
message AddressRequest {
|
||||
uint32 address = 1;
|
||||
}
|
||||
|
||||
message SymbolLookupResponse {
|
||||
bool found = 1;
|
||||
string symbol_name = 2;
|
||||
uint32 address = 3;
|
||||
string type = 4; // "function", "data", "label", etc.
|
||||
string description = 5;
|
||||
}
|
||||
|
||||
// Debug session
|
||||
message DebugSessionRequest {
|
||||
string session_name = 1;
|
||||
string rom_hash = 2; // For verification
|
||||
bool enable_all_features = 3;
|
||||
}
|
||||
|
||||
message DebugSessionResponse {
|
||||
bool success = 1;
|
||||
string session_id = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
message DebugStatusResponse {
|
||||
bool is_running = 1;
|
||||
bool is_paused = 2;
|
||||
CPUState cpu_state = 3;
|
||||
uint32 active_breakpoints = 4;
|
||||
uint32 active_watchpoints = 5;
|
||||
BreakpointInfo last_breakpoint_hit = 6;
|
||||
double fps = 7;
|
||||
uint64 cycles = 8;
|
||||
}
|
||||
Reference in New Issue
Block a user