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())
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",
232 "tcp://127.0.0.1:27015 or /tmp/mesen2-*.sock",
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(
280 ICON_MD_PERSON " Link State",
281 link_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 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 = items.max_health > 0
305 ? 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(
354 header.c_str(),
355 sprites_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
356 sprites_expanded_ = true;
357
358 ImGui::Checkbox("Show inactive", &show_all_sprites_);
359
360 if (sprites_.empty()) {
361 ImGui::TextDisabled(" No active sprites");
362 } else {
363 // Scrollable sprite list
364 if (ImGui::BeginChild("SpriteList", ImVec2(0, 120), true)) {
365 for (const auto& sprite : sprites_) {
366 ImGui::PushID(sprite.slot);
367
368 // Slot indicator
369 ImGui::TextColored(theme.text_secondary_color, "[%d]", sprite.slot);
370 ImGui::SameLine();
371
372 // Type with color based on state
373 ImVec4 sprite_color = sprite.state > 0
374 ? ImVec4(0.4f, 0.8f, 0.4f, 1.0f)
375 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f);
376 ImGui::TextColored(sprite_color, "Type: 0x%02X", sprite.type);
377
378 ImGui::SameLine();
379 ImGui::Text("@ %d,%d", sprite.x, sprite.y);
380
381 ImGui::SameLine();
382 if (sprite.health > 0) {
383 ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f), "HP:%d",
384 sprite.health);
385 }
386
387 ImGui::SameLine();
388 ImGui::TextColored(theme.text_secondary_color, "State:%d",
389 sprite.state);
390
391 ImGui::PopID();
392 }
393 }
394 ImGui::EndChild();
395 }
396 } else {
397 sprites_expanded_ = false;
398 }
399}
400
402 const auto& theme = AgentUI::GetTheme();
403 const auto& game = game_state_.game;
404
405 if (ImGui::CollapsingHeader(
406 ICON_MD_GAMEPAD " Game Mode",
407 game_mode_expanded_ ? ImGuiTreeNodeFlags_DefaultOpen : 0)) {
408 game_mode_expanded_ = true;
409
410 ImGui::Text("Mode:");
411 ImGui::SameLine();
412 ImGui::TextColored(theme.text_secondary_color, "%d (Submode: %d)",
413 game.mode, game.submode);
414
415 ImGui::Text("Location:");
416 ImGui::SameLine();
417 if (game.indoors) {
418 ImGui::TextColored(ImVec4(0.6f, 0.4f, 0.2f, 1.0f), "Dungeon Room: 0x%04X",
419 game.room_id);
420 } else {
421 ImGui::TextColored(ImVec4(0.2f, 0.6f, 0.2f, 1.0f),
422 "Overworld Area: 0x%02X", game.overworld_area);
423 }
424
425 // Frame counter
426 ImGui::Text("Frame:");
427 ImGui::SameLine();
428 ImGui::TextColored(theme.text_secondary_color, "%llu", emu_state_.frame);
429
430 ImGui::SameLine();
431 ImGui::Text(" FPS:");
432 ImGui::SameLine();
433 ImGui::TextColored(theme.text_secondary_color, "%.1f", emu_state_.fps);
434
435 // CPU state toggle
436 ImGui::Checkbox("Show CPU State", &show_cpu_state_);
437 if (show_cpu_state_) {
438 ImGui::Indent();
439 ImGui::TextColored(theme.text_secondary_color,
440 "PC=$%02X:%04X A=$%04X X=$%04X Y=$%04X",
443 ImGui::TextColored(theme.text_secondary_color,
444 "SP=$%04X D=$%04X DBR=$%02X P=$%02X", cpu_state_.SP,
446 ImGui::Unindent();
447 }
448 } else {
449 game_mode_expanded_ = false;
450 }
451}
452
454 if (!IsConnected())
455 return;
456
457 ImGui::Separator();
458
459 // Emulation control buttons
460 if (emu_state_.paused) {
461 if (ImGui::Button(ICON_MD_PLAY_ARROW " Resume")) {
462 client_->Resume();
463 RefreshState();
464 }
465 } else {
466 if (ImGui::Button(ICON_MD_PAUSE " Pause")) {
467 client_->Pause();
468 RefreshState();
469 }
470 }
471
472 ImGui::SameLine();
473 if (ImGui::Button(ICON_MD_SKIP_NEXT " Step")) {
474 client_->Step(1);
475 RefreshState();
476 }
477
478 ImGui::SameLine();
479 if (ImGui::Button(ICON_MD_FAST_FORWARD " Frame")) {
480 client_->Frame();
481 RefreshState();
482 }
483
484 ImGui::SameLine();
485 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
486 RefreshState();
487 }
488
489 ImGui::Spacing();
491 ImGui::Spacing();
493}
494
496 if (!IsConnected())
497 return;
498
499 const char* colmaps[] = {"A", "B", "C"};
500 bool overlay_changed = false;
501
502 ImGui::TextDisabled("Collision Overlay");
503 if (ImGui::Checkbox("Enable overlay", &collision_overlay_enabled_)) {
504 overlay_changed = true;
505 }
506 ImGui::SameLine();
507 ImGui::SetNextItemWidth(60);
508 if (ImGui::Combo("##colmap", &collision_map_index_, colmaps,
509 IM_ARRAYSIZE(colmaps))) {
510 overlay_changed = true;
511 }
512
513 if (overlay_changed) {
514 auto status = client_->SetCollisionOverlay(collision_overlay_enabled_,
515 colmaps[collision_map_index_]);
516 if (!status.ok()) {
517 status_message_ = std::string(status.message());
518 } else {
520 ? "Collision overlay enabled"
521 : "Collision overlay disabled";
522 }
523 }
524}
525
527 if (!IsConnected())
528 return;
529
530 ImGui::TextDisabled("Save States");
531 ImGui::SetNextItemWidth(60);
532 ImGui::InputInt("##state_slot", &save_state_slot_, 1, 1);
533 save_state_slot_ = std::clamp(save_state_slot_, 0, 9);
534
535 ImGui::SameLine();
536 if (ImGui::SmallButton(ICON_MD_SAVE " Save")) {
537 auto status = client_->SaveState(save_state_slot_);
538 status_message_ = status.ok()
539 ? absl::StrFormat("Saved state %d", save_state_slot_)
540 : std::string(status.message());
541 }
542 ImGui::SameLine();
543 if (ImGui::SmallButton(ICON_MD_UPLOAD " Load")) {
544 auto status = client_->LoadState(save_state_slot_);
545 status_message_ = status.ok()
546 ? absl::StrFormat("Loaded state %d", save_state_slot_)
547 : std::string(status.message());
548 }
549
550 ImGui::SameLine();
551 if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Screenshot")) {
552 auto screenshot = client_->Screenshot();
553 if (!screenshot.ok()) {
554 status_message_ = std::string(screenshot.status().message());
555 } else {
556 status_message_ = "Screenshot captured (base64 in clipboard)";
557 ImGui::SetClipboardText(screenshot->c_str());
558 }
559 }
560}
561
562} // namespace editor
563} // 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()