yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
mesen_debug_panel.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <cstdio>
6#include <cstring>
7
8#include "absl/strings/str_format.h"
11#include "app/gui/core/icons.h"
13#include "imgui/imgui.h"
14
15namespace yaze {
16namespace editor {
17
18namespace {
19
20const char* DirectionToString(uint8_t dir) {
21 switch (dir) {
22 case 0:
23 return "Up";
24 case 2:
25 return "Down";
26 case 4:
27 return "Left";
28 case 6:
29 return "Right";
30 default:
31 return "???";
32 }
33}
34
35ImVec4 HealthColor(float ratio) {
36 if (ratio > 0.66f)
37 return ImVec4(0.2f, 0.8f, 0.2f, 1.0f); // Green
38 if (ratio > 0.33f)
39 return ImVec4(0.9f, 0.7f, 0.1f, 1.0f); // Yellow
40 return ImVec4(0.9f, 0.2f, 0.2f, 1.0f); // Red
41}
42
43} // namespace
44
48 if (!socket_paths_.empty()) {
50 std::snprintf(socket_path_buffer_, sizeof(socket_path_buffer_), "%s",
51 socket_paths_[0].c_str());
52 }
53}
55
57 std::shared_ptr<emu::mesen::MesenSocketClient> client) {
58 client_ = std::move(client);
60}
61
63 return client_ && client_->IsConnected();
64}
65
67 if (!client_) {
69 }
70 auto status = client_->Connect();
71 if (!status.ok()) {
72 connection_error_ = std::string(status.message());
73 } else {
74 connection_error_.clear();
77 }
78}
79
80void MesenDebugPanel::ConnectToPath(const std::string& socket_path) {
81 if (!client_) {
83 }
84 auto status = client_->Connect(socket_path);
85 if (!status.ok()) {
86 connection_error_ = std::string(status.message());
87 } else {
88 connection_error_.clear();
91 }
92}
93
95 if (client_) {
96 client_->Disconnect();
97 }
98}
99
102 if (!socket_paths_.empty()) {
103 if (selected_socket_index_ < 0 ||
104 selected_socket_index_ >= static_cast<int>(socket_paths_.size())) {
106 }
107 if (socket_path_buffer_[0] == '\0') {
108 std::snprintf(socket_path_buffer_, sizeof(socket_path_buffer_), "%s",
110 }
111 } else {
113 }
114}
115
117 if (!IsConnected())
118 return;
119
120 // Get emulator state
121 auto emu_result = client_->GetState();
122 if (emu_result.ok()) {
123 emu_state_ = *emu_result;
124 }
125
126 // Get ALTTP game state
127 auto game_result = client_->GetGameState();
128 if (game_result.ok()) {
129 game_state_ = *game_result;
130 }
131
132 // Get sprites
133 auto sprite_result = client_->GetSprites(show_all_sprites_);
134 if (sprite_result.ok()) {
135 sprites_ = *sprite_result;
136 }
137
138 // Get CPU state if expanded
139 if (show_cpu_state_) {
140 auto cpu_result = client_->GetCpuState();
141 if (cpu_result.ok()) {
142 cpu_state_ = *cpu_result;
143 }
144 }
145}
146
148 ImGui::PushID("MesenDebugPanel");
149
150 // Auto-refresh logic
151 if (IsConnected() && auto_refresh_) {
152 time_since_refresh_ += ImGui::GetIO().DeltaTime;
154 RefreshState();
155 time_since_refresh_ = 0.0f;
156 }
157 }
158
160 if (ImGui::BeginChild("MesenDebug_Panel", ImVec2(0, 0), true)) {
161 if (ImGui::IsWindowAppearing()) {
163 }
165
166 if (IsConnected()) {
167 ImGui::Spacing();
169 ImGui::Spacing();
171 ImGui::Spacing();
172 DrawGameMode();
173 ImGui::Spacing();
175 }
176 }
177 ImGui::EndChild();
179
180 ImGui::PopID();
181}
182
184 const auto& theme = AgentUI::GetTheme();
185
186 // Header
187 ImGui::TextColored(theme.accent_color, "%s Mesen2 Debug", ICON_MD_BUG_REPORT);
188
189 // Connection status indicator
190 ImGui::SameLine(ImGui::GetWindowWidth() - 100);
191 if (IsConnected()) {
192 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 2.0f);
193 ImVec4 connected_color = ImVec4(0.1f, pulse, 0.3f, 1.0f);
194 ImGui::TextColored(connected_color, "%s Connected", ICON_MD_CHECK_CIRCLE);
195 } else {
196 ImGui::TextColored(theme.status_error, "%s Disconnected", ICON_MD_ERROR);
197 }
198
199 ImGui::Separator();
200
201 // Connection controls
202 if (!IsConnected()) {
203 ImGui::TextDisabled("Socket");
204 const char* preview =
206 selected_socket_index_ < static_cast<int>(socket_paths_.size()))
208 : "No sockets found";
209 ImGui::SetNextItemWidth(-40);
210 if (ImGui::BeginCombo("##mesen_socket_combo", preview)) {
211 for (int i = 0; i < static_cast<int>(socket_paths_.size()); ++i) {
212 bool selected = (i == selected_socket_index_);
213 if (ImGui::Selectable(socket_paths_[i].c_str(), selected)) {
215 std::snprintf(socket_path_buffer_, sizeof(socket_path_buffer_), "%s",
216 socket_paths_[i].c_str());
217 }
218 if (selected) {
219 ImGui::SetItemDefaultFocus();
220 }
221 }
222 ImGui::EndCombo();
223 }
224 ImGui::SameLine();
225 if (ImGui::SmallButton(ICON_MD_REFRESH "##mesen_refresh")) {
227 }
228
229 ImGui::TextDisabled("Path");
230 ImGui::SetNextItemWidth(-1);
231 ImGui::InputTextWithHint("##mesen_socket_path", "/tmp/mesen2-12345.sock",
233
234 if (ImGui::Button(ICON_MD_LINK " Connect")) {
235 std::string path = socket_path_buffer_;
236 if (path.empty() && selected_socket_index_ >= 0 &&
237 selected_socket_index_ < static_cast<int>(socket_paths_.size())) {
239 }
240 if (path.empty()) {
241 Connect();
242 } else {
243 ConnectToPath(path);
244 }
245 }
246 ImGui::SameLine();
247 if (ImGui::SmallButton(ICON_MD_AUTO_MODE " Auto")) {
248 Connect();
249 }
250 if (!connection_error_.empty()) {
251 ImGui::Spacing();
252 ImGui::TextColored(theme.status_error, "%s", connection_error_.c_str());
253 }
254 } else {
255 if (ImGui::Button(ICON_MD_LINK_OFF " Disconnect")) {
256 Disconnect();
257 }
258 ImGui::SameLine();
259 ImGui::Checkbox("Auto-refresh", &auto_refresh_);
260 if (auto_refresh_) {
261 ImGui::SameLine();
262 ImGui::SetNextItemWidth(60);
263 ImGui::SliderFloat("##RefreshRate", &refresh_interval_, 0.05f, 1.0f,
264 "%.2fs");
265 }
266 }
267
268 if (!status_message_.empty()) {
269 ImGui::Spacing();
270 ImGui::TextColored(theme.text_secondary_color, "%s",
271 status_message_.c_str());
272 }
273}
274
276 const auto& theme = AgentUI::GetTheme();
277
278 if (ImGui::CollapsingHeader(
279 ICON_MD_PERSON " Link State",
280 link_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
281 link_expanded_ = true;
282
283 const auto& link = game_state_.link;
284 const auto& items = game_state_.items;
285
286 // Position
287 ImGui::Text("Position:");
288 ImGui::SameLine();
289 ImGui::TextColored(theme.text_secondary_color, "X=%d, Y=%d Layer: %d",
290 link.x, link.y, link.layer);
291
292 // Direction and state
293 ImGui::Text("Direction:");
294 ImGui::SameLine();
295 ImGui::TextColored(theme.text_secondary_color, "%s (0x%02X)",
296 DirectionToString(link.direction), link.direction);
297 ImGui::SameLine();
298 ImGui::Text(" State:");
299 ImGui::SameLine();
300 ImGui::TextColored(theme.text_secondary_color, "0x%02X", link.state);
301
302 // Health bar
303 float health_ratio = items.max_health > 0
304 ? static_cast<float>(items.current_health) /
305 static_cast<float>(items.max_health)
306 : 0.0f;
307 ImVec4 health_color = HealthColor(health_ratio);
308
309 ImGui::Text("Health:");
310 ImGui::SameLine();
311
312 // Draw hearts
313 int full_hearts = items.current_health / 8;
314 int max_hearts = items.max_health / 8;
315 {
316 gui::StyleColorGuard full_heart_guard(ImGuiCol_Text, health_color);
317 for (int i = 0; i < full_hearts; ++i) {
318 ImGui::SameLine(0, 0);
319 ImGui::Text("%s", ICON_MD_FAVORITE);
320 }
321 }
322 {
323 gui::StyleColorGuard empty_heart_guard(ImGuiCol_Text,
324 ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
325 for (int i = full_hearts; i < max_hearts; ++i) {
326 ImGui::SameLine(0, 0);
327 ImGui::Text("%s", ICON_MD_FAVORITE_BORDER);
328 }
329 }
330
331 ImGui::SameLine();
332 ImGui::TextColored(theme.text_secondary_color, " (%d/%d)",
333 items.current_health, items.max_health);
334
335 // Items row
336 ImGui::Text("Items:");
337 ImGui::SameLine();
338 ImGui::TextColored(theme.text_secondary_color,
339 "Magic: %d Rupees: %d Bombs: %d Arrows: %d",
340 items.magic, items.rupees, items.bombs, items.arrows);
341 } else {
342 link_expanded_ = false;
343 }
344}
345
347 const auto& theme = AgentUI::GetTheme();
348
349 std::string header = absl::StrFormat("%s Active Sprites (%zu/16)",
351
352 if (ImGui::CollapsingHeader(
353 header.c_str(),
354 sprites_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
355 sprites_expanded_ = true;
356
357 ImGui::Checkbox("Show inactive", &show_all_sprites_);
358
359 if (sprites_.empty()) {
360 ImGui::TextDisabled(" No active sprites");
361 } else {
362 // Scrollable sprite list
363 if (ImGui::BeginChild("SpriteList", ImVec2(0, 120), true)) {
364 for (const auto& sprite : sprites_) {
365 ImGui::PushID(sprite.slot);
366
367 // Slot indicator
368 ImGui::TextColored(theme.text_secondary_color, "[%d]", sprite.slot);
369 ImGui::SameLine();
370
371 // Type with color based on state
372 ImVec4 sprite_color = sprite.state > 0
373 ? ImVec4(0.4f, 0.8f, 0.4f, 1.0f)
374 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
375 ImGui::TextColored(sprite_color, "Type: 0x%02X", sprite.type);
376
377 ImGui::SameLine();
378 ImGui::Text("@ %d,%d", sprite.x, sprite.y);
379
380 ImGui::SameLine();
381 if (sprite.health > 0) {
382 ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "HP:%d",
383 sprite.health);
384 }
385
386 ImGui::SameLine();
387 ImGui::TextColored(theme.text_secondary_color, "State:%d",
388 sprite.state);
389
390 ImGui::PopID();
391 }
392 }
393 ImGui::EndChild();
394 }
395 } else {
396 sprites_expanded_ = false;
397 }
398}
399
401 const auto& theme = AgentUI::GetTheme();
402 const auto& game = game_state_.game;
403
404 if (ImGui::CollapsingHeader(
405 ICON_MD_GAMEPAD " Game Mode",
406 game_mode_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
407 game_mode_expanded_ = true;
408
409 ImGui::Text("Mode:");
410 ImGui::SameLine();
411 ImGui::TextColored(theme.text_secondary_color, "%d (Submode: %d)",
412 game.mode, game.submode);
413
414 ImGui::Text("Location:");
415 ImGui::SameLine();
416 if (game.indoors) {
417 ImGui::TextColored(ImVec4(0.6f, 0.4f, 0.2f, 1.0f), "Dungeon Room: 0x%04X",
418 game.room_id);
419 } else {
420 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.2f, 1.0f),
421 "Overworld Area: 0x%02X", game.overworld_area);
422 }
423
424 // Frame counter
425 ImGui::Text("Frame:");
426 ImGui::SameLine();
427 ImGui::TextColored(theme.text_secondary_color, "%llu", emu_state_.frame);
428
429 ImGui::SameLine();
430 ImGui::Text(" FPS:");
431 ImGui::SameLine();
432 ImGui::TextColored(theme.text_secondary_color, "%.1f", emu_state_.fps);
433
434 // CPU state toggle
435 ImGui::Checkbox("Show CPU State", &show_cpu_state_);
436 if (show_cpu_state_) {
437 ImGui::Indent();
438 ImGui::TextColored(theme.text_secondary_color,
439 "PC=$%02X:%04X A=$%04X X=$%04X Y=$%04X",
442 ImGui::TextColored(theme.text_secondary_color,
443 "SP=$%04X D=$%04X DBR=$%02X P=$%02X", cpu_state_.SP,
445 ImGui::Unindent();
446 }
447 } else {
448 game_mode_expanded_ = false;
449 }
450}
451
453 if (!IsConnected())
454 return;
455
456 ImGui::Separator();
457
458 // Emulation control buttons
459 if (emu_state_.paused) {
460 if (ImGui::Button(ICON_MD_PLAY_ARROW " Resume")) {
461 client_->Resume();
462 RefreshState();
463 }
464 } else {
465 if (ImGui::Button(ICON_MD_PAUSE " Pause")) {
466 client_->Pause();
467 RefreshState();
468 }
469 }
470
471 ImGui::SameLine();
472 if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
473 client_->Step(1);
474 RefreshState();
475 }
476
477 ImGui::SameLine();
478 if (ImGui::Button(ICON_MD_FAST_FORWARD " Frame")) {
479 client_->Frame();
480 RefreshState();
481 }
482
483 ImGui::SameLine();
484 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
485 RefreshState();
486 }
487
488 ImGui::Spacing();
490 ImGui::Spacing();
492}
493
495 if (!IsConnected())
496 return;
497
498 const char* colmaps[] = {"A", "B", "C"};
499 bool overlay_changed = false;
500
501 ImGui::TextDisabled("Collision Overlay");
502 if (ImGui::Checkbox("Enable overlay", &collision_overlay_enabled_)) {
503 overlay_changed = true;
504 }
505 ImGui::SameLine();
506 ImGui::SetNextItemWidth(60);
507 if (ImGui::Combo("##colmap", &collision_map_index_, colmaps,
508 IM_ARRAYSIZE(colmaps))) {
509 overlay_changed = true;
510 }
511
512 if (overlay_changed) {
513 auto status = client_->SetCollisionOverlay(collision_overlay_enabled_,
514 colmaps[collision_map_index_]);
515 if (!status.ok()) {
516 status_message_ = std::string(status.message());
517 } else {
519 ? "Collision overlay enabled"
520 : "Collision overlay disabled";
521 }
522 }
523}
524
526 if (!IsConnected())
527 return;
528
529 ImGui::TextDisabled("Save States");
530 ImGui::SetNextItemWidth(60);
531 ImGui::InputInt("##state_slot", &save_state_slot_, 1, 1);
532 save_state_slot_ = std::clamp(save_state_slot_, 0, 9);
533
534 ImGui::SameLine();
535 if (ImGui::SmallButton(ICON_MD_SAVE " Save")) {
536 auto status = client_->SaveState(save_state_slot_);
537 status_message_ = status.ok()
538 ? absl::StrFormat("Saved state %d", save_state_slot_)
539 : std::string(status.message());
540 }
541 ImGui::SameLine();
542 if (ImGui::SmallButton(ICON_MD_UPLOAD " Load")) {
543 auto status = client_->LoadState(save_state_slot_);
544 status_message_ = status.ok()
545 ? absl::StrFormat("Loaded state %d", save_state_slot_)
546 : std::string(status.message());
547 }
548
549 ImGui::SameLine();
550 if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Screenshot")) {
551 auto screenshot = client_->Screenshot();
552 if (!screenshot.ok()) {
553 status_message_ = std::string(screenshot.status().message());
554 } else {
555 status_message_ = "Screenshot captured (base64 in clipboard)";
556 ImGui::SetClipboardText(screenshot->c_str());
557 }
558 }
559}
560
561} // namespace editor
562} // namespace yaze
void Connect()
Attempt to connect to Mesen2.
emu::mesen::GameState game_state_
void Disconnect()
Disconnect from Mesen2.
void SetClient(std::shared_ptr< emu::mesen::MesenSocketClient > client)
Set the socket client for Mesen2 communication.
std::vector< std::string > socket_paths_
emu::mesen::CpuState cpu_state_
bool IsConnected() const
Get connection status.
std::vector< emu::mesen::SpriteInfo > sprites_
emu::mesen::MesenState emu_state_
void ConnectToPath(const std::string &socket_path)
std::shared_ptr< emu::mesen::MesenSocketClient > client_
static void SetClient(std::shared_ptr< MesenSocketClient > client)
static std::shared_ptr< MesenSocketClient > GetOrCreate()
static std::vector< std::string > ListAvailableSockets()
List available Mesen2 sockets on the system.
#define ICON_MD_PAUSE
Definition icons.h:1389
#define ICON_MD_LINK
Definition icons.h:1090
#define ICON_MD_FAST_FORWARD
Definition icons.h:724
#define ICON_MD_PLAY_ARROW
Definition icons.h:1479
#define ICON_MD_FAVORITE_BORDER
Definition icons.h:728
#define ICON_MD_LINK_OFF
Definition icons.h:1091
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_BUG_REPORT
Definition icons.h:327
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_UPLOAD
Definition icons.h:2048
#define ICON_MD_AUTO_MODE
Definition icons.h:222
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_SKIP_NEXT
Definition icons.h:1773
#define ICON_MD_PEST_CONTROL
Definition icons.h:1429
#define ICON_MD_PERSON
Definition icons.h:1415
#define ICON_MD_FAVORITE
Definition icons.h:727
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_GAMEPAD
Definition icons.h:866
#define ICON_MD_PHOTO_CAMERA
Definition icons.h:1453
const AgentUITheme & GetTheme()