201 if (ent.id == state_id) {
207 return absl::NotFoundError(
"State not found: " + state_id);
211 const char* home = std::getenv(
"HOME");
212 std::string socket_arg;
214 std::string path =
client_->GetSocketPath();
216 escaped.reserve(path.size() + 2);
218 for (
char c : path) {
219 if (c ==
'\\' || c ==
'"')
224 socket_arg =
" --socket " + escaped;
226 std::string cmd = absl::StrFormat(
227 "python3 %s/src/hobby/oracle-of-secrets/scripts/mesen2_client.py%s "
229 home ? home :
"", socket_arg, state_id.c_str());
231 FILE* pipe = popen(cmd.c_str(),
"r");
233 return absl::InternalError(
"Failed to execute lib-load command");
238 while (fgets(buffer,
sizeof(buffer), pipe)) {
241 int ret = pclose(pipe);
244 return absl::InternalError(
"lib-load failed: " + output);
249 return absl::OkStatus();
327 ImGui::SetNextItemWidth(150);
328 ImGui::InputTextWithHint(
"##filter",
"Filter...",
filter_text_,
340 int canon_count = 0, draft_count = 0, depr_count = 0;
342 if (e.status ==
"canon")
344 else if (e.status ==
"draft")
346 else if (e.status ==
"deprecated")
350 ImGui::TextDisabled(
"| %d canon, %d draft, %d deprecated", canon_count,
351 draft_count, depr_count);
356 std::transform(filter_lower.begin(), filter_lower.end(), filter_lower.begin(),
359 int visible_index = 0;
360 for (
size_t i = 0; i <
entries_.size(); ++i) {
372 if (!filter_lower.empty()) {
373 std::string label_lower = entry.label;
374 std::transform(label_lower.begin(), label_lower.end(),
375 label_lower.begin(), ::tolower);
376 std::string id_lower = entry.id;
377 std::transform(id_lower.begin(), id_lower.end(), id_lower.begin(),
379 if (label_lower.find(filter_lower) == std::string::npos &&
380 id_lower.find(filter_lower) == std::string::npos) {
386 ImGui::TextColored(GetStatusColor(entry.status),
"%s",
387 GetStatusBadge(entry.status).c_str());
392 if (ImGui::Selectable(entry.label.c_str(), is_selected,
393 ImGuiSelectableFlags_SpanAllColumns)) {
399 const std::string
id = entry.id;
400 std::vector<gui::MenuItemSpec> items;
408 items.back().separator_after =
true;
410 if (entry.status ==
"draft") {
411 items.emplace_back(
"Verify as Canon...",
ICON_MD_CHECK, [
this,
id]() {
416 if (entry.status !=
"deprecated") {
417 items.back().separator_after =
true;
431 if (ImGui::IsItemHovered() && !entry.tags.empty()) {
432 ImGui::BeginTooltip();
433 ImGui::Text(
"Tags: ");
434 for (
size_t t = 0; t < entry.tags.size(); ++t) {
437 ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f),
"[%s]",
438 entry.tags[t].c_str());
450 ImGui::TextDisabled(
"Select a state to view details");
457 ImGui::TextColored(GetStatusColor(entry.status),
"%s",
458 GetStatusBadge(entry.status).c_str());
460 ImGui::Text(
"%s", entry.label.c_str());
466 ImGui::TextDisabled(
"Connect to Mesen2 to load states");
477 if (entry.status ==
"draft") {
478 if (ImGui::Button(
ICON_MD_CHECK " Verify as Canon", ImVec2(-1, 0))) {
484 if (entry.status !=
"deprecated") {
495 ImGui::Text(
"ID: %s", entry.id.c_str());
496 ImGui::Text(
"Path: %s", entry.path.c_str());
498 if (!entry.md5.empty()) {
499 ImGui::Text(
"MD5: %s", entry.md5.substr(0, 16).c_str());
502 if (!entry.location.empty()) {
503 ImGui::Text(
"Location: %s", entry.location.c_str());
506 if (entry.area > 0 || entry.link_x > 0) {
507 ImGui::Text(
"Area: 0x%02X Pos: (%d, %d)", entry.area, entry.link_x,
511 if (entry.health > 0) {
513 static_cast<float>(entry.health) / std::max(1, entry.max_health);
514 ImGui::Text(
"Health: %d/%d", entry.health, entry.max_health);
515 ImGui::ProgressBar(ratio, ImVec2(-1, 0));
518 if (!entry.captured_by.empty()) {
519 ImGui::Text(
"Captured by: %s", entry.captured_by.c_str());
522 if (!entry.verified_by.empty()) {
523 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f),
"Verified by: %s",
524 entry.verified_by.c_str());
525 if (!entry.verified_at.empty()) {
526 ImGui::TextDisabled(
"at %s", entry.verified_at.c_str());
530 if (!entry.deprecated_reason.empty()) {
531 ImGui::TextColored(ImVec4(0.8f, 0.3f, 0.3f, 1.0f),
"Deprecated: %s",
532 entry.deprecated_reason.c_str());
536 if (!entry.tags.empty()) {
538 ImGui::Text(
"Tags:");
539 for (
const auto& tag : entry.tags) {
541 ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f),
"[%s]", tag.c_str());
549 ImGui::OpenPopup(
"Verify State");
552 if (ImGui::BeginPopupModal(
"Verify State",
nullptr,
553 ImGuiWindowFlags_AlwaysAutoResize)) {
555 ImGui::TextDisabled(
"This marks the state as verified and trusted.");
559 if (ImGui::Button(
"Verify", ImVec2(120, 0))) {
566 ImGui::CloseCurrentPopup();
569 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
570 ImGui::CloseCurrentPopup();
577 ImGui::OpenPopup(
"Deprecate State");
580 if (ImGui::BeginPopupModal(
"Deprecate State",
nullptr,
581 ImGuiWindowFlags_AlwaysAutoResize)) {
583 ImGui::TextDisabled(
"This excludes the state from testing.");
587 if (ImGui::Button(
"Deprecate", ImVec2(120, 0))) {
594 ImGui::CloseCurrentPopup();
597 if (ImGui::Button(
"Cancel", ImVec2(120, 0))) {
598 ImGui::CloseCurrentPopup();
bool ItemContextMenu(const char *id, const std::function< std::vector< MenuItemSpec >()> &build, ImGuiPopupFlags flags)
Right-click menu for the last submitted ImGui item.
State entry from the Oracle save state library.
std::vector< std::string > tags
std::string deprecated_reason