15#define close closesocket
18#include <sys/socket.h>
30#include "absl/strings/numbers.h"
31#include "absl/strings/str_cat.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/str_split.h"
45 std::string search =
"\"" + key +
"\":";
46 size_t pos = json.find(search);
47 if (pos == std::string::npos)
50 pos += search.length();
51 while (pos < json.length() && (json[pos] ==
' ' || json[pos] ==
'\t'))
54 if (pos >= json.length())
57 if (json[pos] ==
'"') {
59 size_t start = pos + 1;
60 size_t end = json.find(
'"', start);
61 while (end != std::string::npos && end > 0 && json[end - 1] ==
'\\') {
62 end = json.find(
'"', end + 1);
64 if (end == std::string::npos)
66 return json.substr(start, end - start);
67 }
else if (json[pos] ==
'{') {
72 while (pos < json.length() && depth > 0) {
75 else if (json[pos] ==
'}')
79 return json.substr(start, pos - start);
80 }
else if (json[pos] ==
'[') {
85 while (pos < json.length() && depth > 0) {
88 else if (json[pos] ==
']')
92 return json.substr(start, pos - start);
96 while (pos < json.length() && json[pos] !=
',' && json[pos] !=
'}' &&
100 return json.substr(start, pos - start);
105 int64_t default_value = 0) {
108 return default_value;
111 if (value.length() > 2 && value[0] ==
'0' &&
112 (value[1] ==
'x' || value[1] ==
'X')) {
114 std::string hex_str = value.substr(2);
115 auto [ptr, ec] = std::from_chars(
116 hex_str.data(), hex_str.data() + hex_str.size(), result, 16);
117 if (ec == std::errc()) {
123 if (absl::SimpleAtoi(value, &result)) {
126 return default_value;
130 double default_value = 0.0) {
133 return default_value;
136 if (absl::SimpleAtod(value, &result)) {
139 return default_value;
143 bool default_value =
false) {
146 return default_value;
147 return value ==
"true";
151 return absl::StrFormat(
"{\"type\":\"%s\"}\n", type);
156 escaped.reserve(value.size());
157 for (
char c : value) {
183 const std::string& type,
184 const std::vector<std::pair<std::string, std::string>>& params) {
185 std::stringstream ss;
186 ss <<
"{\"type\":\"" << type <<
"\"";
187 for (
const auto& [key, value] : params) {
198 if (socket_fd ==
nullptr) {
199 return absl::InvalidArgumentError(
"socket_fd output pointer is null");
202 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
204 return absl::InternalError(
205 absl::StrCat(
"Failed to create socket: ", strerror(errno)));
208 struct sockaddr_un addr;
209 memset(&addr, 0,
sizeof(addr));
210 addr.sun_family = AF_UNIX;
211 strncpy(addr.sun_path, socket_path.c_str(),
sizeof(addr.sun_path) - 1);
213 if (connect(fd,
reinterpret_cast<struct sockaddr*
>(&addr),
sizeof(addr)) < 0) {
215 return absl::UnavailableError(absl::StrCat(
216 "Failed to connect to ", socket_path,
": ", strerror(errno)));
220 return absl::OkStatus();
228 shutdown(fd, SD_BOTH);
230 shutdown(fd, SHUT_RDWR);
245 return absl::NotFoundError(
246 "No Mesen2 socket found. Is Mesen2-OoS running?");
257 auto connect_status = ConnectSocketToPath(socket_path, &fd);
258 if (!connect_status.ok()) {
259 return connect_status;
267 auto status =
Ping();
273 return absl::OkStatus();
293 const char* env_path = std::getenv(
"MESEN2_SOCKET_PATH");
294 if (env_path && env_path[0] !=
'\0') {
297 if (std::filesystem::exists(env_path)) {
298 return {std::string(env_path)};
302 if (stat(env_path, &st) == 0 && (st.st_mode & S_IFMT) == S_IFSOCK) {
303 return {std::string(env_path)};
308 std::vector<std::string> paths;
309 namespace fs = std::filesystem;
310 std::vector<fs::path> search_paths;
313 search_paths.push_back(fs::temp_directory_path());
315 search_paths.push_back(
"/tmp");
318 std::regex socket_pattern(
"mesen2-\\d+\\.sock");
320 for (
const auto& search_path : search_paths) {
322 if (!fs::exists(search_path, ec))
325 for (
const auto& entry : fs::directory_iterator(search_path, ec)) {
330 std::string filename = entry.path().filename().string();
331 if (std::regex_match(filename, socket_pattern)) {
332 paths.push_back(entry.path().string());
345 int fd,
const std::string& json,
bool update_connection_state) {
347 return absl::FailedPreconditionError(
"Socket is not connected");
351 ssize_t sent = send(fd, json.c_str(), json.length(), 0);
353 if (update_connection_state) {
356 return absl::InternalError(
357 absl::StrCat(
"Failed to send command: ", strerror(errno)));
364 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*
>(&tv),
367 std::string response;
370 ssize_t received = recv(fd, buffer,
sizeof(buffer), 0);
373 int err = WSAGetLastError();
374 if (err == WSAEWOULDBLOCK || err == WSAETIMEDOUT) {
376 if (errno == EAGAIN || errno == EWOULDBLOCK) {
378 if (response.empty()) {
379 return absl::DeadlineExceededError(
"Timeout waiting for response");
383 if (update_connection_state) {
386 return absl::InternalError(
387 absl::StrCat(
"Failed to receive response: ", strerror(errno)));
392 response.append(buffer,
static_cast<size_t>(received));
393 if (response.size() > kMaxResponseSize) {
394 return absl::ResourceExhaustedError(
"Mesen2 response too large");
396 if (response.find(
'\n') != std::string::npos) {
400 if (response.empty()) {
401 return absl::DeadlineExceededError(
"Empty response from Mesen2");
403 auto newline_pos = response.find(
'\n');
404 if (newline_pos != std::string::npos) {
405 response = response.substr(0, newline_pos);
412 const std::string& json) {
414 return absl::FailedPreconditionError(
"Not connected to Mesen2");
422 const std::string& response) {
423 bool success = ExtractJsonBool(response,
"success");
425 std::string error = ExtractJsonString(response,
"error");
427 error =
"Unknown Mesen2 error";
428 return absl::InternalError(error);
431 std::string data = ExtractJsonString(response,
"data");
432 return data.empty() ? response : data;
440 auto result =
SendCommand(BuildJsonCommand(
"PING"));
442 return result.status();
443 return absl::OkStatus();
447 auto result =
SendCommand(BuildJsonCommand(
"STATE"));
449 return result.status();
452 state.
running = ExtractJsonBool(*result,
"running");
453 state.
paused = ExtractJsonBool(*result,
"paused");
454 state.
debugging = ExtractJsonBool(*result,
"debugging");
455 state.
frame = ExtractJsonInt(*result,
"frame");
456 state.
fps = ExtractJsonDouble(*result,
"fps");
457 state.
console_type = ExtractJsonInt(*result,
"consoleType");
462 auto result =
SendCommand(BuildJsonCommand(
"PAUSE"));
463 return result.status();
467 auto result =
SendCommand(BuildJsonCommand(
"RESUME"));
468 return result.status();
472 auto result =
SendCommand(BuildJsonCommand(
"RESET"));
473 return result.status();
477 auto result =
SendCommand(BuildJsonCommand(
"FRAME"));
478 return result.status();
483 "STEP", {{
"count", std::to_string(count)}, {
"mode", mode}}));
484 return result.status();
501 std::vector<std::string> buttons;
509 buttons.push_back(
"select");
511 buttons.push_back(
"start");
517 std::string buttons_str;
518 for (
size_t i = 0; i < buttons.size(); ++i) {
519 if (i > 0) buttons_str +=
",";
520 buttons_str += buttons[i];
523 auto result =
SendCommand(BuildJsonCommand(
"INPUT", {{
"buttons", buttons_str}}));
524 return result.status();
533 BuildJsonCommand(
"READ", {{
"addr", absl::StrFormat(
"0x%06X", addr)}}));
535 return result.status();
536 return static_cast<uint8_t
>(ExtractJsonInt(*result,
"data", 0));
541 BuildJsonCommand(
"READ16", {{
"addr", absl::StrFormat(
"0x%06X", addr)}}));
543 return result.status();
544 return static_cast<uint16_t
>(ExtractJsonInt(*result,
"data", 0));
550 BuildJsonCommand(
"READBLOCK", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
551 {
"len", std::to_string(len)}}));
553 return result.status();
556 std::string hex = ExtractJsonString(*result,
"data");
562 std::vector<uint8_t> data;
565 for (
size_t i = 0; i + 1 < hex.length(); i += 2) {
567 std::string byte_hex = hex.substr(i, 2);
568 auto [ptr, ec] = std::from_chars(
569 byte_hex.data(), byte_hex.data() + byte_hex.size(),
byte, 16);
570 if (ec == std::errc()) {
571 data.push_back(
static_cast<uint8_t
>(
byte));
579 BuildJsonCommand(
"WRITE", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
580 {
"value", absl::StrFormat(
"0x%02X", value)}}));
581 return result.status();
586 "WRITE16", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
587 {
"value", absl::StrFormat(
"0x%04X", value)}}));
588 return result.status();
592 const std::vector<uint8_t>& data) {
593 std::stringstream hex;
594 for (uint8_t
byte : data) {
595 hex << absl::StrFormat(
"%02X",
byte);
599 {{
"addr", absl::StrFormat(
"0x%06X", addr)}, {
"hex", hex.str()}}));
600 return result.status();
608 auto result =
SendCommand(BuildJsonCommand(
"CPU"));
610 return result.status();
613 state.
A =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"A"));
614 state.
X =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"X"));
615 state.
Y =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"Y"));
616 state.
SP =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"SP"));
617 state.
D =
static_cast<uint16_t
>(ExtractJsonInt(*result,
"D"));
618 state.
PC =
static_cast<uint32_t
>(ExtractJsonInt(*result,
"PC"));
619 state.
K =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"K"));
620 state.
DBR =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"DBR"));
621 state.
P =
static_cast<uint8_t
>(ExtractJsonInt(*result,
"P"));
629 BuildJsonCommand(
"DISASM", {{
"addr", absl::StrFormat(
"0x%06X", addr)},
630 {
"count", std::to_string(count)}}));
632 return result.status();
637 uint32_t addr,
BreakpointType type,
const std::string& condition) {
638 std::string type_str;
654 std::vector<std::pair<std::string, std::string>> params = {
656 {
"addr", absl::StrFormat(
"0x%06X", addr)},
657 {
"bptype", type_str}};
659 if (!condition.empty()) {
660 params.push_back({
"condition", condition});
663 auto result =
SendCommand(BuildJsonCommand(
"BREAKPOINT", params));
665 return result.status();
667 return static_cast<int>(ExtractJsonInt(*result,
"id", -1));
672 "BREAKPOINT", {{
"action",
"remove"}, {
"id", std::to_string(
id)}}));
673 return result.status();
678 SendCommand(BuildJsonCommand(
"BREAKPOINT", {{
"action",
"clear"}}));
679 return result.status();
684 BuildJsonCommand(
"TRACE", {{
"count", std::to_string(count)}}));
686 return result.status();
695 auto result =
SendCommand(BuildJsonCommand(
"GAMESTATE"));
697 return result.status();
702 std::string link_json = ExtractJsonString(*result,
"link");
703 state.
link.
x =
static_cast<uint16_t
>(ExtractJsonInt(link_json,
"x"));
704 state.
link.
y =
static_cast<uint16_t
>(ExtractJsonInt(link_json,
"y"));
705 state.
link.
layer =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"layer"));
707 static_cast<uint8_t
>(ExtractJsonInt(link_json,
"direction"));
708 state.
link.
state =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"state"));
709 state.
link.
pose =
static_cast<uint8_t
>(ExtractJsonInt(link_json,
"pose"));
712 std::string health_json = ExtractJsonString(*result,
"health");
714 static_cast<uint8_t
>(ExtractJsonInt(health_json,
"current"));
716 static_cast<uint8_t
>(ExtractJsonInt(health_json,
"max"));
719 std::string items_json = ExtractJsonString(*result,
"items");
720 state.
items.
magic =
static_cast<uint8_t
>(ExtractJsonInt(items_json,
"magic"));
722 static_cast<uint16_t
>(ExtractJsonInt(items_json,
"rupees"));
723 state.
items.
bombs =
static_cast<uint8_t
>(ExtractJsonInt(items_json,
"bombs"));
725 static_cast<uint8_t
>(ExtractJsonInt(items_json,
"arrows"));
728 std::string game_json = ExtractJsonString(*result,
"game");
729 state.
game.
mode =
static_cast<uint8_t
>(ExtractJsonInt(game_json,
"mode"));
731 static_cast<uint8_t
>(ExtractJsonInt(game_json,
"submode"));
732 state.
game.
indoors = ExtractJsonBool(game_json,
"indoors");
734 static_cast<uint16_t
>(ExtractJsonInt(game_json,
"room_id"));
736 static_cast<uint8_t
>(ExtractJsonInt(game_json,
"overworld_area"));
743 std::vector<std::pair<std::string, std::string>> params;
745 params.push_back({
"all",
"true"});
749 SendCommand(params.empty() ? BuildJsonCommand(
"SPRITES")
750 : BuildJsonCommand(
"SPRITES", params));
752 return result.status();
754 std::vector<SpriteInfo> sprites;
757 size_t array_start = result->find(
"[");
758 size_t array_end = result->rfind(
"]");
759 if (array_start == std::string::npos || array_end == std::string::npos) {
763 std::string array_content =
764 result->substr(array_start + 1, array_end - array_start - 1);
768 while ((pos = array_content.find(
"{", pos)) != std::string::npos) {
769 size_t end = array_content.find(
"}", pos);
770 if (end == std::string::npos)
773 std::string sprite_json = array_content.substr(pos, end - pos + 1);
776 sprite.
slot =
static_cast<int>(ExtractJsonInt(sprite_json,
"slot"));
777 sprite.
type =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"type"));
778 sprite.
state =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"state"));
779 sprite.
x =
static_cast<uint16_t
>(ExtractJsonInt(sprite_json,
"x"));
780 sprite.
y =
static_cast<uint16_t
>(ExtractJsonInt(sprite_json,
"y"));
781 sprite.
health =
static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"health"));
783 static_cast<uint8_t
>(ExtractJsonInt(sprite_json,
"subtype"));
785 sprites.push_back(sprite);
793 const std::string& colmap) {
796 {{
"action", enable ?
"enable" :
"disable"}, {
"colmap", colmap}}));
797 return result.status();
806 BuildJsonCommand(
"SAVESTATE", {{
"slot", std::to_string(slot)}}));
807 return result.status();
812 BuildJsonCommand(
"LOADSTATE", {{
"slot", std::to_string(slot)}}));
813 return result.status();
817 auto result =
SendCommand(BuildJsonCommand(
"SCREENSHOT"));
819 return result.status();
828 const std::vector<std::string>& events) {
830 return absl::FailedPreconditionError(
"Not connected to Mesen2");
832 if (events.empty()) {
833 return absl::InvalidArgumentError(
"At least one event must be requested");
839 std::stringstream ss;
840 for (
size_t i = 0; i < events.size(); ++i) {
847 auto connect_status = ConnectSocketToPath(
socket_path_, &event_fd);
848 if (!connect_status.ok()) {
849 return connect_status;
852 const std::string subscribe_command =
853 BuildJsonCommand(
"SUBSCRIBE", {{
"events", ss.str()}});
855 send(event_fd, subscribe_command.c_str(), subscribe_command.length(), 0);
858 return absl::InternalError(
859 absl::StrCat(
"Failed to send command: ", strerror(errno)));
865 setsockopt(event_fd, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<const char*
>(&tv),
868 std::string response;
871 const ssize_t received = recv(event_fd, buffer,
sizeof(buffer), 0);
874 int err = WSAGetLastError();
875 if (err == WSAEWOULDBLOCK || err == WSAETIMEDOUT) {
877 if (errno == EAGAIN || errno == EWOULDBLOCK) {
880 return absl::DeadlineExceededError(
881 "Timeout waiting for subscribe response");
884 return absl::InternalError(
885 absl::StrCat(
"Failed to receive response: ", strerror(errno)));
890 response.append(buffer,
static_cast<size_t>(received));
891 if (response.size() > kMaxResponseSize) {
893 return absl::ResourceExhaustedError(
"Mesen2 response too large");
895 if (response.find(
'\n') != std::string::npos) {
900 const size_t newline_pos = response.find(
'\n');
901 if (newline_pos == std::string::npos) {
903 return absl::DeadlineExceededError(
"Malformed subscribe response");
906 auto parse_status =
ParseResponse(response.substr(0, newline_pos));
907 if (!parse_status.ok()) {
909 return parse_status.status();
916 return absl::OkStatus();
932 return absl::OkStatus();
968 auto dispatch_pending_lines = [&]() {
969 size_t newline_pos = pending.find(
'\n');
970 while (newline_pos != std::string::npos) {
971 std::string line = pending.substr(0, newline_pos);
972 pending.erase(0, newline_pos + 1);
974 if (!line.empty() && line.find(
"\"success\"") == std::string::npos) {
977 event.type = ExtractJsonString(line,
"event");
978 if (event.
type.empty()) {
979 event.type = ExtractJsonString(line,
"type");
981 event.address =
static_cast<uint32_t
>(
982 ExtractJsonInt(line,
"address", ExtractJsonInt(line,
"addr", 0)));
983 event.frame =
static_cast<uint64_t
>(ExtractJsonInt(line,
"frame", 0));
986 std::vector<EventCallback> listeners;
993 listeners.push_back(listener);
997 if (callback && !event.
type.empty()) {
1000 if (!event.
type.empty()) {
1001 for (
const auto& listener : listeners) {
1007 newline_pos = pending.find(
'\n');
1012 dispatch_pending_lines();
1014 const ssize_t received = recv(event_fd, buffer,
sizeof(buffer), 0);
1017 const int err = WSAGetLastError();
1018 if (err == WSAEINTR || err == WSAEWOULDBLOCK) {
1022 if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
1029 if (received == 0) {
1033 pending.append(buffer,
static_cast<size_t>(received));
1034 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_
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::StatusOr< std::string > SendCommandOnSocket(int fd, const std::string &json, bool update_connection_state)
Send a command using a specific socket descriptor.
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.
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, int *socket_fd)
void ShutdownSocketFd(int fd)
std::string ExtractJsonString(const std::string &json, const std::string &key)
int64_t ExtractJsonInt(const std::string &json, const std::string &key, int64_t default_value=0)
constexpr size_t kMaxResponseSize
std::string EscapeJsonValue(const std::string &value)
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)
std::function< void(const MesenEvent &)> EventCallback
BreakpointType
Breakpoint types.
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.