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
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()) return;
118
119 // Get emulator state
120 auto emu_result = client_->GetState();
121 if (emu_result.ok()) {
122 emu_state_ = *emu_result;
123 }
124
125 // Get ALTTP game state
126 auto game_result = client_->GetGameState();
127 if (game_result.ok()) {
128 game_state_ = *game_result;
129 }
130
131 // Get sprites
132 auto sprite_result = client_->GetSprites(show_all_sprites_);
133 if (sprite_result.ok()) {
134 sprites_ = *sprite_result;
135 }
136
137 // Get CPU state if expanded
138 if (show_cpu_state_) {
139 auto cpu_result = client_->GetCpuState();
140 if (cpu_result.ok()) {
141 cpu_state_ = *cpu_result;
142 }
143 }
144}
145
147 ImGui::PushID("MesenDebugPanel");
148
149 // Auto-refresh logic
150 if (IsConnected() && auto_refresh_) {
151 time_since_refresh_ += ImGui::GetIO().DeltaTime;
153 RefreshState();
154 time_since_refresh_ = 0.0f;
155 }
156 }
157
159 if (ImGui::BeginChild("MesenDebug_Panel", ImVec2(0, 0), true)) {
160 if (ImGui::IsWindowAppearing()) {
162 }
164
165 if (IsConnected()) {
166 ImGui::Spacing();
168 ImGui::Spacing();
170 ImGui::Spacing();
171 DrawGameMode();
172 ImGui::Spacing();
174 }
175 }
176 ImGui::EndChild();
178
179 ImGui::PopID();
180}
181
183 const auto& theme = AgentUI::GetTheme();
184
185 // Header
186 ImGui::TextColored(theme.accent_color, "%s Mesen2 Debug", ICON_MD_BUG_REPORT);
187
188 // Connection status indicator
189 ImGui::SameLine(ImGui::GetWindowWidth() - 100);
190 if (IsConnected()) {
191 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 2.0f);
192 ImVec4 connected_color = ImVec4(0.1f, pulse, 0.3f, 1.0f);
193 ImGui::TextColored(connected_color, "%s Connected", ICON_MD_CHECK_CIRCLE);
194 } else {
195 ImGui::TextColored(theme.status_error, "%s Disconnected", ICON_MD_ERROR);
196 }
197
198 ImGui::Separator();
199
200 // Connection controls
201 if (!IsConnected()) {
202 ImGui::TextDisabled("Socket");
203 const char* preview =
205 selected_socket_index_ < static_cast<int>(socket_paths_.size()))
207 : "No sockets found";
208 ImGui::SetNextItemWidth(-40);
209 if (ImGui::BeginCombo("##mesen_socket_combo", preview)) {
210 for (int i = 0; i < static_cast<int>(socket_paths_.size()); ++i) {
211 bool selected = (i == selected_socket_index_);
212 if (ImGui::Selectable(socket_paths_[i].c_str(), selected)) {
214 std::snprintf(socket_path_buffer_, sizeof(socket_path_buffer_), "%s",
215 socket_paths_[i].c_str());
216 }
217 if (selected) {
218 ImGui::SetItemDefaultFocus();
219 }
220 }
221 ImGui::EndCombo();
222 }
223 ImGui::SameLine();
224 if (ImGui::SmallButton(ICON_MD_REFRESH "##mesen_refresh")) {
226 }
227
228 ImGui::TextDisabled("Path");
229 ImGui::SetNextItemWidth(-1);
230 ImGui::InputTextWithHint("##mesen_socket_path",
231 "/tmp/mesen2-12345.sock",
233 sizeof(socket_path_buffer_));
234
235 if (ImGui::Button(ICON_MD_LINK " Connect")) {
236 std::string path = socket_path_buffer_;
237 if (path.empty() && selected_socket_index_ >= 0 &&
238 selected_socket_index_ < static_cast<int>(socket_paths_.size())) {
240 }
241 if (path.empty()) {
242 Connect();
243 } else {
244 ConnectToPath(path);
245 }
246 }
247 ImGui::SameLine();
248 if (ImGui::SmallButton(ICON_MD_AUTO_MODE " Auto")) {
249 Connect();
250 }
251 if (!connection_error_.empty()) {
252 ImGui::Spacing();
253 ImGui::TextColored(theme.status_error, "%s", connection_error_.c_str());
254 }
255 } else {
256 if (ImGui::Button(ICON_MD_LINK_OFF " Disconnect")) {
257 Disconnect();
258 }
259 ImGui::SameLine();
260 ImGui::Checkbox("Auto-refresh", &auto_refresh_);
261 if (auto_refresh_) {
262 ImGui::SameLine();
263 ImGui::SetNextItemWidth(60);
264 ImGui::SliderFloat("##RefreshRate", &refresh_interval_, 0.05f, 1.0f,
265 "%.2fs");
266 }
267 }
268
269 if (!status_message_.empty()) {
270 ImGui::Spacing();
271 ImGui::TextColored(theme.text_secondary_color, "%s",
272 status_message_.c_str());
273 }
274}
275
277 const auto& theme = AgentUI::GetTheme();
278
279 if (ImGui::CollapsingHeader(ICON_MD_PERSON " Link State",
280 link_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen
281 : 0)) {
282 link_expanded_ = true;
283
284 const auto& link = game_state_.link;
285 const auto& items = game_state_.items;
286
287 // Position
288 ImGui::Text("Position:");
289 ImGui::SameLine();
290 ImGui::TextColored(theme.text_secondary_color, "X=%d, Y=%d Layer: %d",
291 link.x, link.y, link.layer);
292
293 // Direction and state
294 ImGui::Text("Direction:");
295 ImGui::SameLine();
296 ImGui::TextColored(theme.text_secondary_color, "%s (0x%02X)",
297 DirectionToString(link.direction), link.direction);
298 ImGui::SameLine();
299 ImGui::Text(" State:");
300 ImGui::SameLine();
301 ImGui::TextColored(theme.text_secondary_color, "0x%02X", link.state);
302
303 // Health bar
304 float health_ratio =
305 items.max_health > 0 ? static_cast<float>(items.current_health) /
306 static_cast<float>(items.max_health)
307 : 0.0f;
308 ImVec4 health_color = HealthColor(health_ratio);
309
310 ImGui::Text("Health:");
311 ImGui::SameLine();
312
313 // Draw hearts
314 int full_hearts = items.current_health / 8;
315 int max_hearts = items.max_health / 8;
316 {
317 gui::StyleColorGuard full_heart_guard(ImGuiCol_Text, health_color);
318 for (int i = 0; i < full_hearts; ++i) {
319 ImGui::SameLine(0, 0);
320 ImGui::Text("%s", ICON_MD_FAVORITE);
321 }
322 }
323 {
324 gui::StyleColorGuard empty_heart_guard(ImGuiCol_Text,
325 ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
326 for (int i = full_hearts; i < max_hearts; ++i) {
327 ImGui::SameLine(0, 0);
328 ImGui::Text("%s", ICON_MD_FAVORITE_BORDER);
329 }
330 }
331
332 ImGui::SameLine();
333 ImGui::TextColored(theme.text_secondary_color, " (%d/%d)",
334 items.current_health, items.max_health);
335
336 // Items row
337 ImGui::Text("Items:");
338 ImGui::SameLine();
339 ImGui::TextColored(theme.text_secondary_color,
340 "Magic: %d Rupees: %d Bombs: %d Arrows: %d",
341 items.magic, items.rupees, items.bombs, items.arrows);
342 } else {
343 link_expanded_ = false;
344 }
345}
346
348 const auto& theme = AgentUI::GetTheme();
349
350 std::string header = absl::StrFormat("%s Active Sprites (%zu/16)",
352
353 if (ImGui::CollapsingHeader(header.c_str(),
355 ? ImGuiTreeNodeFlags_DefaultOpen
356 : 0)) {
357 sprites_expanded_ = true;
358
359 ImGui::Checkbox("Show inactive", &show_all_sprites_);
360
361 if (sprites_.empty()) {
362 ImGui::TextDisabled(" No active sprites");
363 } else {
364 // Scrollable sprite list
365 if (ImGui::BeginChild("SpriteList", ImVec2(0, 120), true)) {
366 for (const auto& sprite : sprites_) {
367 ImGui::PushID(sprite.slot);
368
369 // Slot indicator
370 ImGui::TextColored(theme.text_secondary_color, "[%d]", sprite.slot);
371 ImGui::SameLine();
372
373 // Type with color based on state
374 ImVec4 sprite_color =
375 sprite.state > 0
376 ? ImVec4(0.4f, 0.8f, 0.4f, 1.0f)
377 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
378 ImGui::TextColored(sprite_color, "Type: 0x%02X", sprite.type);
379
380 ImGui::SameLine();
381 ImGui::Text("@ %d,%d", sprite.x, sprite.y);
382
383 ImGui::SameLine();
384 if (sprite.health > 0) {
385 ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "HP:%d",
386 sprite.health);
387 }
388
389 ImGui::SameLine();
390 ImGui::TextColored(theme.text_secondary_color, "State:%d",
391 sprite.state);
392
393 ImGui::PopID();
394 }
395 }
396 ImGui::EndChild();
397 }
398 } else {
399 sprites_expanded_ = false;
400 }
401}
402
404 const auto& theme = AgentUI::GetTheme();
405 const auto& game = game_state_.game;
406
407 if (ImGui::CollapsingHeader(ICON_MD_GAMEPAD " Game Mode",
409 ? ImGuiTreeNodeFlags_DefaultOpen
410 : 0)) {
411 game_mode_expanded_ = true;
412
413 ImGui::Text("Mode:");
414 ImGui::SameLine();
415 ImGui::TextColored(theme.text_secondary_color, "%d (Submode: %d)",
416 game.mode, game.submode);
417
418 ImGui::Text("Location:");
419 ImGui::SameLine();
420 if (game.indoors) {
421 ImGui::TextColored(ImVec4(0.6f, 0.4f, 0.2f, 1.0f),
422 "Dungeon Room: 0x%04X", game.room_id);
423 } else {
424 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.2f, 1.0f),
425 "Overworld Area: 0x%02X", game.overworld_area);
426 }
427
428 // Frame counter
429 ImGui::Text("Frame:");
430 ImGui::SameLine();
431 ImGui::TextColored(theme.text_secondary_color, "%llu", emu_state_.frame);
432
433 ImGui::SameLine();
434 ImGui::Text(" FPS:");
435 ImGui::SameLine();
436 ImGui::TextColored(theme.text_secondary_color, "%.1f", emu_state_.fps);
437
438 // CPU state toggle
439 ImGui::Checkbox("Show CPU State", &show_cpu_state_);
440 if (show_cpu_state_) {
441 ImGui::Indent();
442 ImGui::TextColored(theme.text_secondary_color,
443 "PC=$%02X:%04X A=$%04X X=$%04X Y=$%04X",
446 ImGui::TextColored(theme.text_secondary_color,
447 "SP=$%04X D=$%04X DBR=$%02X P=$%02X",
449 cpu_state_.P);
450 ImGui::Unindent();
451 }
452 } else {
453 game_mode_expanded_ = false;
454 }
455}
456
458 if (!IsConnected()) return;
459
460 ImGui::Separator();
461
462 // Emulation control buttons
463 if (emu_state_.paused) {
464 if (ImGui::Button(ICON_MD_PLAY_ARROW " Resume")) {
465 client_->Resume();
466 RefreshState();
467 }
468 } else {
469 if (ImGui::Button(ICON_MD_PAUSE " Pause")) {
470 client_->Pause();
471 RefreshState();
472 }
473 }
474
475 ImGui::SameLine();
476 if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
477 client_->Step(1);
478 RefreshState();
479 }
480
481 ImGui::SameLine();
482 if (ImGui::Button(ICON_MD_FAST_FORWARD " Frame")) {
483 client_->Frame();
484 RefreshState();
485 }
486
487 ImGui::SameLine();
488 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
489 RefreshState();
490 }
491
492 ImGui::Spacing();
494 ImGui::Spacing();
496}
497
499 if (!IsConnected()) return;
500
501 const char* colmaps[] = {"A", "B", "C"};
502 bool overlay_changed = false;
503
504 ImGui::TextDisabled("Collision Overlay");
505 if (ImGui::Checkbox("Enable overlay", &collision_overlay_enabled_)) {
506 overlay_changed = true;
507 }
508 ImGui::SameLine();
509 ImGui::SetNextItemWidth(60);
510 if (ImGui::Combo("##colmap", &collision_map_index_, colmaps,
511 IM_ARRAYSIZE(colmaps))) {
512 overlay_changed = true;
513 }
514
515 if (overlay_changed) {
516 auto status = client_->SetCollisionOverlay(
518 if (!status.ok()) {
519 status_message_ = std::string(status.message());
520 } else {
522 ? "Collision overlay enabled"
523 : "Collision overlay disabled";
524 }
525 }
526}
527
529 if (!IsConnected()) return;
530
531 ImGui::TextDisabled("Save States");
532 ImGui::SetNextItemWidth(60);
533 ImGui::InputInt("##state_slot", &save_state_slot_, 1, 1);
534 save_state_slot_ = std::clamp(save_state_slot_, 0, 9);
535
536 ImGui::SameLine();
537 if (ImGui::SmallButton(ICON_MD_SAVE " Save")) {
538 auto status = client_->SaveState(save_state_slot_);
539 status_message_ = status.ok()
540 ? absl::StrFormat("Saved state %d", save_state_slot_)
541 : std::string(status.message());
542 }
543 ImGui::SameLine();
544 if (ImGui::SmallButton(ICON_MD_UPLOAD " Load")) {
545 auto status = client_->LoadState(save_state_slot_);
546 status_message_ = status.ok()
547 ? absl::StrFormat("Loaded state %d", save_state_slot_)
548 : std::string(status.message());
549 }
550
551 ImGui::SameLine();
552 if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Screenshot")) {
553 auto screenshot = client_->Screenshot();
554 if (!screenshot.ok()) {
555 status_message_ = std::string(screenshot.status().message());
556 } else {
557 status_message_ = "Screenshot captured (base64 in clipboard)";
558 ImGui::SetClipboardText(screenshot->c_str());
559 }
560 }
561}
562
563} // namespace editor
564} // 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.
RAII guard for ImGui style colors.
Definition style_guard.h:27
#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()