252 ImGui::TextColored(theme.accent_color,
"%s SRAM Viewer",
ICON_MD_MEMORY);
255 ImGui::SameLine(ImGui::GetWindowWidth() - 100);
257 float pulse = 0.7f + 0.3f * std::sin(ImGui::GetTime() * 2.0f);
258 ImVec4 connected_color = ImVec4(0.1f, pulse, 0.3f, 1.0f);
261 ImGui::TextColored(theme.status_error,
"%s Disconnected",
ICON_MD_ERROR);
267 ImGui::TextDisabled(
"Socket");
268 const char* preview =
270 selected_socket_index_ < static_cast<int>(
socket_paths_.size()))
272 :
"No sockets found";
273 ImGui::SetNextItemWidth(-40);
274 if (ImGui::BeginCombo(
"##sram_socket_combo", preview)) {
275 for (
int i = 0; i < static_cast<int>(
socket_paths_.size()); ++i) {
283 ImGui::SetItemDefaultFocus();
293 ImGui::TextDisabled(
"Path");
294 ImGui::SetNextItemWidth(-1);
295 ImGui::InputTextWithHint(
"##sram_socket_path",
296 "tcp://127.0.0.1:27015 or /tmp/mesen2-*.sock",
327 ImGui::SetNextItemWidth(60);
340 ImGui::TextColored(theme.text_secondary_color,
"%s",
347 std::vector<const core::SramVariable*> story_vars;
348 std::vector<const core::SramVariable*> dungeon_vars;
349 std::vector<const core::SramVariable*> item_vars;
350 std::vector<const core::SramVariable*> other_vars;
352 std::string filter_lower;
355 std::transform(filter_lower.begin(), filter_lower.end(),
356 filter_lower.begin(), ::tolower);
361 if (!filter_lower.empty()) {
362 std::string name_lower = var.name;
363 std::transform(name_lower.begin(), name_lower.end(), name_lower.begin(),
365 std::string purpose_lower = var.purpose;
366 std::transform(purpose_lower.begin(), purpose_lower.end(),
367 purpose_lower.begin(), ::tolower);
368 std::string addr_str = absl::StrFormat(
"$%06X", var.address);
369 std::string addr_lower = addr_str;
370 std::transform(addr_lower.begin(), addr_lower.end(), addr_lower.begin(),
373 if (name_lower.find(filter_lower) == std::string::npos &&
374 purpose_lower.find(filter_lower) == std::string::npos &&
375 addr_lower.find(filter_lower) == std::string::npos) {
380 if (IsInStoryRange(var.address)) {
381 story_vars.push_back(&var);
382 }
else if (IsDungeonAddress(var.address)) {
383 dungeon_vars.push_back(&var);
384 }
else if (IsInItemsRange(var.address)) {
385 item_vars.push_back(&var);
387 other_vars.push_back(&var);
392 if (!story_vars.empty()) {
393 std::string story_label = absl::StrFormat(
395 if (ImGui::CollapsingHeader(
399 for (
const auto* var : story_vars) {
408 if (!dungeon_vars.empty()) {
409 std::string dungeon_label = absl::StrFormat(
411 if (ImGui::CollapsingHeader(
412 dungeon_label.c_str(),
415 for (
const auto* var : dungeon_vars) {
424 if (!item_vars.empty()) {
425 std::string items_label = absl::StrFormat(
427 if (ImGui::CollapsingHeader(
431 for (
const auto* var : item_vars) {
440 if (!other_vars.empty()) {
441 std::string other_label = absl::StrFormat(
443 if (ImGui::CollapsingHeader(other_label.c_str())) {
444 for (
const auto* var : other_vars) {
453 float current_time =
static_cast<float>(ImGui::GetTime());
455 ImGui::PushID(
static_cast<int>(var.
address));
458 bool recently_changed =
false;
461 float elapsed = current_time - ts_it->second;
462 if (elapsed < kChangeHighlightDuration) {
463 recently_changed =
true;
465 float alpha = 1.0f - (elapsed / kChangeHighlightDuration);
466 ImVec4 highlight_color = ImVec4(0.9f, 0.8f, 0.1f, alpha * 0.3f);
467 ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
468 ImVec2 row_size = ImVec2(ImGui::GetContentRegionAvail().x,
469 ImGui::GetTextLineHeightWithSpacing());
470 ImGui::GetWindowDrawList()->AddRectFilled(
472 ImVec2(cursor_pos.x + row_size.x, cursor_pos.y + row_size.y),
473 ImGui::ColorConvertFloat4ToU32(highlight_color));
478 ImGui::TextColored(theme.text_secondary_color,
"$%06X", var.
address);
482 if (recently_changed) {
483 ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.2f, 1.0f),
"%s", var.
name.c_str());
485 ImGui::Text(
"%s", var.
name.c_str());
489 if (!var.
purpose.empty() && ImGui::IsItemHovered()) {
490 ImGui::SetTooltip(
"%s", var.
purpose.c_str());
496 uint8_t value = val_it->second;
499 ImGui::SameLine(240);
500 ImGui::Text(
"%d", value);
503 ImGui::SameLine(290);
504 ImGui::TextColored(theme.text_secondary_color,
"$%02X", value);
507 ImGui::SameLine(340);
508 std::string edit_label =
510 if (ImGui::SmallButton(edit_label.c_str())) {
514 ImGui::OpenPopup(
"SramEditPopup");
520 if (var.
address == kDungeonCrystals) {
522 }
else if (var.
address == kStoryRangeStart) {
560 int current_index = value;
561 if (current_index >= kGameStateLabelCount) {
565 const char* preview =
566 (current_index >= 0 && current_index < kGameStateLabelCount)
567 ? kGameStateLabels[current_index]
570 ImGui::SetNextItemWidth(200);
571 if (ImGui::BeginCombo(
"##gamestate_combo", preview)) {
572 for (
int i = 0; i < kGameStateLabelCount; ++i) {
573 bool selected = (i == current_index);
574 if (ImGui::Selectable(kGameStateLabels[i], selected)) {
575 PokeValue(address,
static_cast<uint8_t
>(i));
578 ImGui::SetItemDefaultFocus();