16#define close closesocket
21#include <netinet/in.h>
23#include <sys/socket.h>
37#include "absl/cleanup/cleanup.h"
38#include "absl/strings/numbers.h"
39#include "absl/strings/str_cat.h"
40#include "absl/strings/str_format.h"
41#include "absl/strings/str_split.h"
51 std::string search =
"\"" + key +
"\":";
52 size_t pos = json.find(search);
53 if (pos == std::string::npos)
56 pos += search.length();
57 while (pos < json.length() && (json[pos] ==
' ' || json[pos] ==
'\t'))
60 if (pos >= json.length())
63 if (json[pos] ==
'"') {
65 size_t start = pos + 1;
66 size_t end = json.find(
'"', start);
67 while (end != std::string::npos && end > 0 && json[end - 1] ==
'\\') {
68 end = json.find(
'"', end + 1);
70 if (end == std::string::npos)
72 return json.substr(start, end - start);
73 }
else if (json[pos] ==
'{') {
78 while (pos < json.length() && depth > 0) {
81 else if (json[pos] ==
'}')
85 return json.substr(start, pos - start);
86 }
else if (json[pos] ==
'[') {
91 while (pos < json.length() && depth > 0) {
94 else if (json[pos] ==
']')
98 return json.substr(start, pos - start);
102 while (pos < json.length() && json[pos] !=
',' && json[pos] !=
'}' &&
106 return json.substr(start, pos - start);
111 int64_t default_value = 0) {
114 return default_value;
117 if (value.length() > 2 && value[0] ==
'0' &&
118 (value[1] ==
'x' || value[1] ==
'X')) {
120 std::string hex_str = value.substr(2);
121 auto [ptr, ec] = std::from_chars(
122 hex_str.data(), hex_str.data() + hex_str.size(), result, 16);
123 if (ec == std::errc()) {
129 if (absl::SimpleAtoi(value, &result)) {
132 return default_value;
136 double default_value = 0.0) {
139 return default_value;
142 if (absl::SimpleAtod(value, &result)) {
145 return default_value;
149 bool default_value =
false) {
152 return default_value;
153 return value ==
"true";
157 return absl::StrFormat(
"{\"type\":\"%s\"}\n", type);
162 escaped.reserve(value.size());
163 for (
char c : value) {
189 const std::string& type,
190 const std::vector<std::pair<std::string, std::string>>& params) {
191 std::stringstream ss;
192 ss <<
"{\"type\":\"" << type <<
"\"";
193 for (
const auto& [key, value] : params) {
205 return WSAGetLastError();
213 return absl::StrCat(
"Winsock error ", error);
215 return strerror(error);
228 const char* raw = std::getenv(
"YAZE_MESEN_SEND_TIMEOUT_MS");
230 if (raw !=
nullptr && absl::SimpleAtoi(raw, &parsed) && parsed > 0) {
242 const char* raw = std::getenv(
"YAZE_MESEN_EVENT_POLL_MS");
244 if (raw !=
nullptr && absl::SimpleAtoi(raw, &parsed) && parsed > 0) {
252#if defined(MSG_NOSIGNAL)
253constexpr int kSendFlags = MSG_NOSIGNAL;
259#if defined(SO_NOSIGPIPE)
260 const int enable = 1;
261 (void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE,
262 reinterpret_cast<const char*
>(&enable),
sizeof(enable));
270 const DWORD timeout =
static_cast<DWORD
>(timeout_ms);
271 (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO,
272 reinterpret_cast<const char*
>(&timeout),
sizeof(timeout));
275 timeout.tv_sec = timeout_ms / 1000;
276 timeout.tv_usec = (timeout_ms % 1000) * 1000;
277 (void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
sizeof(timeout));
283 const DWORD timeout =
static_cast<DWORD
>(timeout_ms);
284 (void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
285 reinterpret_cast<const char*
>(&timeout),
sizeof(timeout));
288 timeout.tv_sec = timeout_ms / 1000;
289 timeout.tv_usec = (timeout_ms % 1000) * 1000;
290 (void)setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
sizeof(timeout));
295 return path.rfind(
"tcp://", 0) == 0 || path.rfind(
"tcp:", 0) == 0;
299 const std::string& path) {
300 std::string raw = path;
301 if (raw.rfind(
"tcp://", 0) == 0) {
303 }
else if (raw.rfind(
"tcp:", 0) == 0) {
306 return absl::InvalidArgumentError(
307 absl::StrCat(
"Not a tcp:// endpoint: ", path));
310 std::string host =
"127.0.0.1";
311 std::string port_str = raw;
312 const auto colon = raw.rfind(
':');
313 if (colon != std::string::npos) {
314 host = raw.substr(0, colon);
315 port_str = raw.substr(colon + 1);
321 if (!absl::SimpleAtoi(port_str, &port) || port <= 0 || port > 65535) {
322 return absl::InvalidArgumentError(
323 absl::StrCat(
"Invalid TCP port in ", path));
325 return std::make_pair(host,
static_cast<uint16_t
>(port));
330 u_long mode = blocking ? 0UL : 1UL;
331 return ioctlsocket(fd, FIONBIO, &mode) == 0;
333 const int flags = fcntl(fd, F_GETFL, 0);
337 const int next_flags =
338 blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
339 return fcntl(fd, F_SETFL, next_flags) == 0;
349 FD_SET(fd, &write_fds);
351 timeout.tv_sec = timeout_ms / 1000;
352 timeout.tv_usec = (timeout_ms % 1000) * 1000;
353 const int ready = select(0,
nullptr, &write_fds,
nullptr, &timeout);
357 descriptor.events = POLLOUT;
358 const int ready = poll(&descriptor, 1, timeout_ms);
361 return absl::DeadlineExceededError(
"Socket did not become writable");
366 if (error == WSAEINTR) {
367 return absl::OkStatus();
370 if (error == EINTR) {
371 return absl::OkStatus();
374 return absl::InternalError(
377 return absl::OkStatus();
385 const auto deadline = std::chrono::steady_clock::now() +
388 absl::Cleanup restore = [fd, was_blocking] {
395 while (offset < data.size()) {
396 const auto now = std::chrono::steady_clock::now();
397 const auto remaining =
398 std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now)
400 if (remaining <= 0) {
401 return absl::DeadlineExceededError(
402 absl::StrCat(
"Timed out sending command after ",
SendTimeoutMs(),
403 "ms (", offset,
" of ", data.size(),
" bytes)"));
406 send(fd, data.c_str() + offset,
static_cast<int>(data.size() - offset),
409 offset +=
static_cast<size_t>(sent);
414 if (error == WSAEINTR) {
417 if (error == WSAEWOULDBLOCK || error == WSAETIMEDOUT) {
419 if (error == EINTR) {
422 if (error == EAGAIN || error == EWOULDBLOCK) {
424 const absl::Status writable =
426 if (absl::IsDeadlineExceeded(writable)) {
427 return absl::DeadlineExceededError(
428 absl::StrCat(
"Timed out sending command after ",
SendTimeoutMs(),
429 "ms (", offset,
" of ", data.size(),
" bytes)"));
431 if (!writable.ok()) {
436 return absl::InternalError(
439 return absl::OkStatus();
443 const std::string& socket_path) {
446 fd_set exception_fds;
448 FD_ZERO(&exception_fds);
449 FD_SET(fd, &write_fds);
450 FD_SET(fd, &exception_fds);
456 const int ready = select(0,
nullptr, &write_fds, &exception_fds, &timeout);
460 descriptor.events = POLLOUT;
464 return absl::DeadlineExceededError(absl::StrCat(
"Timed out connecting to ",
465 socket_path,
" after ",
470 return absl::UnavailableError(
471 absl::StrCat(
"Failed while waiting to connect to ", socket_path,
": ",
475 int connect_error = 0;
477 int error_length =
sizeof(connect_error);
478 const int get_error =
479 getsockopt(fd, SOL_SOCKET, SO_ERROR,
480 reinterpret_cast<char*
>(&connect_error), &error_length);
482 socklen_t error_length =
sizeof(connect_error);
483 const int get_error =
484 getsockopt(fd, SOL_SOCKET, SO_ERROR, &connect_error, &error_length);
486 if (get_error != 0) {
488 return absl::UnavailableError(
489 absl::StrCat(
"Failed to read connect status for ", socket_path,
": ",
492 if (connect_error != 0) {
493 return absl::UnavailableError(
494 absl::StrCat(
"Failed to connect to ", socket_path,
": ",
497 return absl::OkStatus();
504 return parsed.status();
506 const std::string& host = parsed->first;
507 const uint16_t port = parsed->second;
512 return absl::InternalError(absl::StrCat(
"Failed to create TCP socket: ",
521 return absl::InternalError(
522 absl::StrCat(
"Failed to set non-blocking mode on TCP socket: ",
527 memset(&addr, 0,
sizeof(addr));
528 addr.sin_family = AF_INET;
529 addr.sin_port = htons(port);
530 if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) != 1) {
532 memset(&hints, 0,
sizeof(hints));
533 hints.ai_family = AF_INET;
534 hints.ai_socktype = SOCK_STREAM;
535 addrinfo* result =
nullptr;
536 const int gai = getaddrinfo(host.c_str(),
nullptr, &hints, &result);
537 if (gai != 0 || result ==
nullptr) {
539 return absl::UnavailableError(
540 absl::StrCat(
"Failed to resolve ", host,
": ",
541 gai != 0 ? gai_strerror(gai) :
"no address"));
543 addr.sin_addr =
reinterpret_cast<sockaddr_in*
>(result->ai_addr)->sin_addr;
544 freeaddrinfo(result);
548 connect(fd,
reinterpret_cast<sockaddr*
>(&addr),
sizeof(addr));
551 const bool in_progress = result < 0 && (connect_error == WSAEWOULDBLOCK ||
552 connect_error == WSAEINPROGRESS ||
553 connect_error == WSAEALREADY);
555 const bool in_progress = result < 0 && connect_error == EINPROGRESS;
557 if (result < 0 && !in_progress) {
559 return absl::UnavailableError(
560 absl::StrCat(
"Failed to connect to ", socket_path,
": ",
565 if (!wait_status.ok()) {
574 return absl::InternalError(
575 absl::StrCat(
"Failed to restore blocking mode on TCP socket: ",
580 return absl::OkStatus();
588 return absl::InternalError(
594 struct sockaddr_un addr;
595 memset(&addr, 0,
sizeof(addr));
596 addr.sun_family = AF_UNIX;
597 strncpy(addr.sun_path, socket_path.c_str(),
sizeof(addr.sun_path) - 1);
599 if (connect(fd,
reinterpret_cast<struct sockaddr*
>(&addr),
sizeof(addr)) <
603 return absl::UnavailableError(absl::StrCat(
608 return absl::OkStatus();
613 if (socket_fd ==
nullptr) {
614 return absl::InvalidArgumentError(
"socket_fd output pointer is null");
627 shutdown(fd, SD_BOTH);
629 shutdown(fd, SHUT_RDWR);
637 WSADATA winsock_data{};
638 winsock_startup_error_ = WSAStartup(MAKEWORD(2, 2), &winsock_data);
639 winsock_started_ = winsock_startup_error_ == 0;
646 if (winsock_started_) {
655 return absl::NotFoundError(
656 "No Mesen2 socket found. Is Mesen2-OoS running?");
663 if (!winsock_started_) {
664 return absl::InternalError(
665 absl::StrCat(
"Failed to initialize Winsock: ",
666 SocketErrorMessage(winsock_startup_error_)));
675 auto connect_status = ConnectSocketToPath(socket_path, &fd);
676 if (!connect_status.ok()) {
677 return connect_status;
685 auto status =
Ping();
691 return absl::OkStatus();
711 const char* env_path = std::getenv(
"MESEN2_SOCKET_PATH");
712 if (env_path && env_path[0] !=
'\0') {
713 std::string env(env_path);
714 if (LooksLikeTcpEndpoint(env)) {
722 if (std::filesystem::exists(env_path)) {
727 if (stat(env_path, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) {
733 std::vector<std::string> paths;
734 namespace fs = std::filesystem;
735 std::vector<fs::path> search_paths;
738 search_paths.push_back(fs::temp_directory_path());
740 search_paths.push_back(
"/tmp");
743 std::regex socket_pattern(
"mesen2-\\d+\\.sock");
745 for (
const auto& search_path : search_paths) {
747 if (!fs::exists(search_path, ec))
750 for (
const auto& entry : fs::directory_iterator(search_path, ec)) {
755 std::string filename = entry.path().filename().string();
756 if (std::regex_match(filename, socket_pattern)) {
757 paths.push_back(entry.path().string());
770 SocketHandle fd,
const std::string& json,
bool update_connection_state) {
772 return absl::FailedPreconditionError(
"Socket is not connected");
776 if (
const absl::Status sent = SendAll(fd, json); !sent.ok()) {
777 if (update_connection_state) {
784 SetSocketReceiveTimeout(fd, 5000);
786 std::string response;
789 ssize_t received = recv(fd, buffer,
sizeof(buffer), 0);
791 const int error = LastSocketError();
795 if (error == WSAEINTR) {
798 if (error == WSAEWOULDBLOCK || error == WSAETIMEDOUT) {
800 if (error == EINTR) {
803 if (error == EAGAIN || error == EWOULDBLOCK) {
805 if (response.empty()) {
806 return absl::DeadlineExceededError(
"Timeout waiting for response");
810 if (update_connection_state) {
813 return absl::InternalError(absl::StrCat(
"Failed to receive response: ",
814 SocketErrorMessage(error)));
819 response.append(buffer,
static_cast<size_t>(received));
820 if (response.size() > kMaxResponseSize) {
821 return absl::ResourceExhaustedError(
"Mesen2 response too large");
823 if (response.find(
'\n') != std::string::npos) {
827 if (response.empty()) {
828 return absl::DeadlineExceededError(
"Empty response from Mesen2");
830 auto newline_pos = response.find(
'\n');
831 if (newline_pos != std::string::npos) {
832 response = response.substr(0, newline_pos);
839 const std::string& json) {
841 return absl::FailedPreconditionError(
"Not connected to Mesen2");
850 const std::string& response) {
851 bool success = ExtractJsonBool(response,
"success");
853 std::string error = ExtractJsonString(response,
"error");
855 error =
"Unknown Mesen2 error";
856 return absl::InternalError(error);
859 std::string data = ExtractJsonString(response,
"data");
860 return data.empty() ? response : data;
868 auto result =
SendCommand(BuildJsonCommand(
"PING"));
870 return result.status();
871 return absl::OkStatus();
875 auto result =
SendCommand(BuildJsonCommand(
"STATE"));
877 return result.status();
880 state.
running = ExtractJsonBool(*result,
"running");
881 state.
paused = ExtractJsonBool(*result,
"paused");
882 state.
debugging = ExtractJsonBool(*result,
"debugging");
883 state.
frame = ExtractJsonInt(*result,
"frame");
884 state.
fps = ExtractJsonDouble(*result,
"fps");
885 state.
console_type = ExtractJsonInt(*result,
"consoleType");
890 auto result =
SendCommand(BuildJsonCommand(
"PAUSE"));
891 return result.status();
895 auto result =
SendCommand(BuildJsonCommand(
"RESUME"));
896 return result.status();
900 auto result =
SendCommand(BuildJsonCommand(
"RESET"));
901 return result.status();
905 auto result =
SendCommand(BuildJsonCommand(
"FRAME"));
906 return result.status();
911 "STEP", {{
"count", std::to_string(count)}, {
"mode", mode}}));
912 return result.status();
929 std::vector<std::string> buttons;
931 buttons.push_back(
"a");
933 buttons.push_back(
"b");
935 buttons.push_back(
"x");
937 buttons.push_back(
"y");
939 buttons.push_back(
"l");
941 buttons.push_back(
"r");
943 buttons.push_back(
"select");
945 buttons.push_back(
"start");
947 buttons.push_back(
"up");
949 buttons.push_back(
"down");
951 buttons.push_back(
"left");
953 buttons.push_back(
"right");
955 std::string buttons_str;
956 for (
size_t i = 0; i < buttons.size(); ++i) {
959 buttons_str += buttons[i];
963 SendCommand(BuildJsonCommand(
"INPUT", {{
"buttons", buttons_str}}));
964 return result.status();
973 BuildJsonCommand(
"READ", {{
"addr", absl::StrFormat(
"0x%06X", addr)}}));
975 return result.status();
976 return static_cast<uint8_t
>(ExtractJsonInt(*result,
"data", 0));
981 BuildJsonCommand(
"READ16", {{
"addr", absl::StrFormat(
"0x%06X", addr)}}));
983 return result.status();
984 return static_cast<uint16_t
>(ExtractJsonInt(*result,
"data", 0));
990 BuildJsonCommand(
"READBLOCK", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
991 {
"len", std::to_string(len)}}));
993 return result.status();
996 std::string hex = ExtractJsonString(*result,
"data");
1002 std::vector<uint8_t> data;
1005 for (
size_t i = 0; i + 1 < hex.length(); i += 2) {
1007 std::string byte_hex = hex.substr(i, 2);
1008 auto [ptr, ec] = std::from_chars(
1009 byte_hex.data(), byte_hex.data() + byte_hex.size(),
byte, 16);
1010 if (ec == std::errc()) {
1011 data.push_back(
static_cast<uint8_t
>(
byte));
1019 BuildJsonCommand(
"WRITE", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
1020 {
"value", absl::StrFormat(
"0x%02X", value)}}));
1021 return result.status();
1026 "WRITE16", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
1027 {
"value", absl::StrFormat(
"0x%04X", value)}}));
1028 return result.status();
1032 const std::vector<uint8_t>& data) {
1033 std::stringstream hex;
1034 for (uint8_t
byte : data) {
1035 hex << absl::StrFormat(
"%02X",
byte);
1039 {{
"addr", absl::StrFormat(
"0x%06X", addr)}, {
"hex", hex.str()}}));
1040 return result.status();
1048 auto result =
SendCommand(BuildJsonCommand(
"CPU"));
1050 return result.status();
1053 state.
A =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"a"));
1054 state.X =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"x"));
1055 state.Y =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"y"));
1056 state.SP =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"sp"));
1057 state.D =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"d"));
1058 state.PC =
static_cast<uint32_t
>(ExtractJsonInt(*result,
"pc"));
1059 state.K =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"k"));
1060 state.DBR =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"dbr"));
1061 state.P =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"p"));
1062 state.emulation_mode = ExtractJsonBool(*result,
"emulationMode");
1069 BuildJsonCommand(
"DISASM", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
1070 {
"count", std::to_string(count)}}));
1072 return result.status();
1077 uint32_t addr,
BreakpointType type,
const std::string& condition) {
1078 std::string type_str;
1094 std::vector<std::pair<std::string, std::string>> params = {
1096 {
"addr", absl::StrFormat(
"0x%06X", addr)},
1097 {
"bptype", type_str}};
1099 if (!condition.empty()) {
1100 params.push_back({
"condition", condition});
1103 auto result =
SendCommand(BuildJsonCommand(
"BREAKPOINT", params));
1105 return result.status();
1107 return static_cast<int>(ExtractJsonInt(*result,
"id", -1));
1112 "BREAKPOINT", {{
"action",
"remove"}, {
"id", std::to_string(
id)}}));
1113 return result.status();
1118 SendCommand(BuildJsonCommand(
"BREAKPOINT", {{
"action",
"clear"}}));
1119 return result.status();
1124 BuildJsonCommand(
"TRACE", {{
"count", std::to_string(count)}}));
1126 return result.status();
1135 auto result =
SendCommand(BuildJsonCommand(
"GAMESTATE"));
1137 return result.status();
1142 std::string link_json = ExtractJsonString(*result,
"link");
1143 state.
link.
x =
static_cast<uint16_t
>(ExtractJsonInt(link_json,
"x"));
1144 state.
link.
y =
static_cast<uint16_t
>(ExtractJsonInt(link_json,
"y"));
1145 state.
link.
layer =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"layer"));
1147 static_cast<uint8_t
>(ExtractJsonInt(link_json,
"direction"));
1148 state.
link.
state =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"state"));
1149 state.
link.
pose =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"pose"));
1152 std::string health_json = ExtractJsonString(*result,
"health");
1154 static_cast<uint8_t
>(ExtractJsonInt(health_json,
"current"));
1156 static_cast<uint8_t
>(ExtractJsonInt(health_json,
"max"));
1159 std::string items_json = ExtractJsonString(*result,
"items");
1160 state.
items.
magic =
static_cast<uint8_t
>(ExtractJsonInt(items_json,
"magic"));
1162 static_cast<uint16_t
>(ExtractJsonInt(items_json,
"rupees"));
1163 state.
items.
bombs =
static_cast<uint8_t
>(ExtractJsonInt(items_json,
"bombs"));
1165 static_cast<uint8_t
>(ExtractJsonInt(items_json,
"arrows"));
1168 std::string game_json = ExtractJsonString(*result,
"game");
1169 state.
game.
mode =
static_cast<uint8_t
>(ExtractJsonInt(game_json,
"mode"));
1171 static_cast<uint8_t
>(ExtractJsonInt(game_json,
"submode"));
1172 state.
game.
indoors = ExtractJsonBool(game_json,
"indoors");
1174 static_cast<uint16_t
>(ExtractJsonInt(game_json,
"room_id"));
1176 static_cast<uint8_t
>(ExtractJsonInt(game_json,
"overworld_area"));
1183 std::vector<std::pair<std::string, std::string>> params;
1185 params.push_back({
"all",
"true"});
1189 SendCommand(params.empty() ? BuildJsonCommand(
"SPRITES")
1190 : BuildJsonCommand(
"SPRITES", params));
1192 return result.status();
1194 std::vector<SpriteInfo> sprites;
1197 size_t array_start = result->find(
"[");
1198 size_t array_end = result->rfind(
"]");
1199 if (array_start == std::string::npos || array_end == std::string::npos) {
1203 std::string array_content =
1204 result->substr(array_start + 1, array_end - array_start - 1);
1208 while ((pos = array_content.find(
"{", pos)) != std::string::npos) {
1209 size_t end = array_content.find(
"}", pos);
1210 if (end == std::string::npos)
1213 std::string sprite_json = array_content.substr(pos, end - pos + 1);
1216 sprite.
slot =
static_cast<int>(ExtractJsonInt(sprite_json,
"slot"));
1217 sprite.
type =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"type"));
1218 sprite.
state =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"state"));
1219 sprite.
x =
static_cast<uint16_t
>(ExtractJsonInt(sprite_json,
"x"));
1220 sprite.
y =
static_cast<uint16_t
>(ExtractJsonInt(sprite_json,
"y"));
1221 sprite.
health =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"health"));
1223 static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"subtype"));
1225 sprites.push_back(sprite);
1233 const std::string& colmap) {
1235 "COLLISION_OVERLAY",
1236 {{
"action", enable ?
"enable" :
"disable"}, {
"colmap", colmap}}));
1237 return result.status();
1246 BuildJsonCommand(
"SAVESTATE", {{
"slot", std::to_string(slot)}}));
1247 return result.status();
1252 BuildJsonCommand(
"LOADSTATE", {{
"slot", std::to_string(slot)}}));
1253 return result.status();
1257 auto result =
SendCommand(BuildJsonCommand(
"SCREENSHOT"));
1259 return result.status();
1268 const std::vector<std::string>& events) {
1270 return absl::FailedPreconditionError(
"Not connected to Mesen2");
1272 if (events.empty()) {
1273 return absl::InvalidArgumentError(
"At least one event must be requested");
1279 std::stringstream ss;
1280 for (
size_t i = 0; i < events.size(); ++i) {
1287 auto connect_status = ConnectSocketToPath(
socket_path_, &event_fd);
1288 if (!connect_status.ok()) {
1289 return connect_status;
1292 const std::string subscribe_command =
1293 BuildJsonCommand(
"SUBSCRIBE", {{
"events", ss.str()}});
1294 if (
const absl::Status sent = SendAll(event_fd, subscribe_command);
1300 SetSocketReceiveTimeout(event_fd, 5000);
1302 std::string response;
1305 const ssize_t received = recv(event_fd, buffer,
sizeof(buffer), 0);
1307 const int error = LastSocketError();
1309 if (error == WSAEINTR) {
1312 if (error == WSAEWOULDBLOCK || error == WSAETIMEDOUT) {
1314 if (error == EINTR) {
1317 if (error == EAGAIN || error == EWOULDBLOCK) {
1320 return absl::DeadlineExceededError(
1321 "Timeout waiting for subscribe response");
1324 return absl::InternalError(absl::StrCat(
"Failed to receive response: ",
1325 SocketErrorMessage(error)));
1327 if (received == 0) {
1330 response.append(buffer,
static_cast<size_t>(received));
1331 if (response.size() > kMaxResponseSize) {
1333 return absl::ResourceExhaustedError(
"Mesen2 response too large");
1335 if (response.find(
'\n') != std::string::npos) {
1340 const size_t newline_pos = response.find(
'\n');
1341 if (newline_pos == std::string::npos) {
1343 return absl::DeadlineExceededError(
"Malformed subscribe response");
1346 auto parse_status =
ParseResponse(response.substr(0, newline_pos));
1347 if (!parse_status.ok()) {
1349 return parse_status.status();
1357 SetSocketReceiveTimeout(event_fd, EventPollMs());
1363 return absl::OkStatus();
1379 return absl::OkStatus();
1415 auto dispatch_pending_lines = [&]() {
1416 size_t newline_pos = pending.find(
'\n');
1417 while (newline_pos != std::string::npos) {
1418 std::string line = pending.substr(0, newline_pos);
1419 pending.erase(0, newline_pos + 1);
1421 if (!line.empty() && line.find(
"\"success\"") == std::string::npos) {
1424 event.type = ExtractJsonString(line,
"event");
1425 if (event.
type.empty()) {
1426 event.type = ExtractJsonString(line,
"type");
1428 event.address =
static_cast<uint32_t
>(
1429 ExtractJsonInt(line,
"address", ExtractJsonInt(line,
"addr", 0)));
1430 event.frame =
static_cast<uint64_t
>(ExtractJsonInt(line,
"frame", 0));
1433 std::vector<EventCallback> listeners;
1440 listeners.push_back(listener);
1444 if (callback && !event.
type.empty()) {
1447 if (!event.
type.empty()) {
1448 for (
const auto& listener : listeners) {
1454 newline_pos = pending.find(
'\n');
1459 dispatch_pending_lines();
1461 const ssize_t received = recv(event_fd, buffer,
sizeof(buffer), 0);
1466 const int err = WSAGetLastError();
1467 if (err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAETIMEDOUT) {
1471 if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK ||
1472 errno == ETIMEDOUT) {
1479 if (received == 0) {
1483 pending.append(buffer,
static_cast<size_t>(received));
1484 dispatch_pending_lines();
std::mutex command_mutex_
absl::Status ClearBreakpoints()
Clear all breakpoints.
absl::Status WriteWord(uint32_t addr, uint16_t value)
Write a 16-bit word to memory.
absl::StatusOr< uint8_t > ReadByte(uint32_t addr)
Read a single byte from memory.
void RemoveEventListener(EventListenerId id)
Remove a previously added event listener.
absl::Status Step(int count=1, const std::string &mode="into")
Step N CPU instructions (default 1)
void EventLoop()
Event listening thread function.
void SetEventCallback(EventCallback callback)
Set callback for received events.
absl::Status Ping()
Ping Mesen2 to check connectivity.
absl::Status Resume()
Resume emulation.
absl::StatusOr< CpuState > GetCpuState()
Get CPU register state.
absl::StatusOr< std::string > Screenshot()
Take a screenshot.
absl::StatusOr< std::string > ParseResponse(const std::string &response)
Parse JSON response for success/error.
std::atomic< bool > connected_
absl::Status LoadState(int slot)
Load state from slot.
std::unordered_map< EventListenerId, EventCallback > event_listeners_
absl::StatusOr< uint16_t > ReadWord(uint32_t addr)
Read a 16-bit word from memory.
absl::StatusOr< int > AddBreakpoint(uint32_t addr, BreakpointType type, const std::string &condition="")
Add a breakpoint.
emu::input::ControllerState current_input_
static std::vector< std::string > ListAvailableSockets()
List available Mesen2 sockets on the system.
absl::Status SetButtons(const emu::input::ControllerState &state)
Set all buttons at once.
absl::StatusOr< std::string > GetTrace(int count=20)
Get execution trace log.
absl::StatusOr< GameState > GetGameState()
Get comprehensive ALTTP game state.
absl::Status SetButton(emu::input::SnesButton button, bool pressed)
Set controller button state.
absl::StatusOr< MesenState > GetState()
Get current emulation state.
EventCallback event_callback_
SocketHandle event_socket_fd_
absl::Status Frame()
Run exactly one frame.
absl::Status SetCollisionOverlay(bool enable, const std::string &colmap="A")
Enable/disable collision overlay.
bool IsConnected() const
Check if connected to Mesen2.
absl::StatusOr< std::string > SendCommand(const std::string &json)
Send a raw JSON command and get raw response.
EventListenerId AddEventListener(EventCallback callback)
Add an event listener without replacing existing listeners.
absl::StatusOr< std::vector< SpriteInfo > > GetSprites(bool all=false)
Get active sprites.
std::thread event_thread_
absl::Status RemoveBreakpoint(int id)
Remove a breakpoint by ID.
EventListenerId next_event_listener_id_
absl::Status Unsubscribe()
Unsubscribe from events.
std::mutex event_callback_mutex_
absl::Status Subscribe(const std::vector< std::string > &events)
Subscribe to events.
absl::StatusOr< std::string > Disassemble(uint32_t addr, int count=10)
Disassemble instructions at address.
std::string pending_event_payload_
absl::Status Pause()
Pause emulation.
absl::Status Reset()
Reset the console.
std::atomic< bool > event_thread_running_
absl::Status SaveState(int slot)
Save state to slot.
void Disconnect()
Disconnect from Mesen2.
absl::StatusOr< std::string > SendCommandOnSocket(SocketHandle fd, const std::string &json, bool update_connection_state)
Send a command using a specific socket descriptor.
static std::vector< std::string > FindSocketPaths()
Find available Mesen2 socket paths.
absl::Status WriteByte(uint32_t addr, uint8_t value)
Write a single byte to memory.
absl::StatusOr< std::vector< uint8_t > > ReadBlock(uint32_t addr, size_t len)
Read a block of bytes from memory.
absl::Status Connect()
Auto-discover and connect to first available Mesen2 socket.
absl::Status WriteBlock(uint32_t addr, const std::vector< uint8_t > &data)
Write a block of bytes to memory.
absl::Status ConnectSocketToPath(const std::string &socket_path, SocketHandle *socket_fd)
std::string ExtractJsonString(const std::string &json, const std::string &key)
void ShutdownSocketFd(SocketHandle fd)
absl::Status ConnectTcpEndpoint(const std::string &socket_path, SocketHandle *socket_fd)
int64_t ExtractJsonInt(const std::string &json, const std::string &key, int64_t default_value=0)
void SetSocketSendTimeout(SocketHandle fd, int timeout_ms)
constexpr size_t kMaxResponseSize
absl::Status SendAll(SocketHandle fd, const std::string &data)
std::string EscapeJsonValue(const std::string &value)
absl::Status ConnectUnixEndpoint(const std::string &socket_path, SocketHandle *socket_fd)
constexpr int kConnectTimeoutMs
absl::Status WaitForConnectComplete(SocketHandle fd, const std::string &socket_path)
void SetSocketReceiveTimeout(SocketHandle fd, int timeout_ms)
bool ExtractJsonBool(const std::string &json, const std::string &key, bool default_value=false)
std::string BuildJsonCommand(const std::string &type)
double ExtractJsonDouble(const std::string &json, const std::string &key, double default_value=0.0)
bool LooksLikeTcpEndpoint(const std::string &path)
void SuppressSigpipe(SocketHandle fd)
constexpr int kDefaultEventPollMs
std::string SocketErrorMessage(int error)
constexpr int kDefaultSendTimeoutMs
bool SetSocketBlocking(SocketHandle fd, bool blocking)
absl::Status WaitForWritable(SocketHandle fd, int timeout_ms)
absl::StatusOr< std::pair< std::string, uint16_t > > ParseTcpEndpoint(const std::string &path)
std::function< void(const MesenEvent &)> EventCallback
BreakpointType
Breakpoint types.
constexpr SocketHandle kInvalidSocketHandle
CPU register state from Mesen2.
Complete ALTTP game state from GAMESTATE command.
Event from Mesen2 subscription.
Emulation state from Mesen2.
Sprite information from SPRITES command.