yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
manifest_panel.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <cctype>
5#include <cstring>
6#include <ctime>
7#include <filesystem>
8#include <string>
9
10#include "absl/strings/str_format.h"
11#include "app/gui/core/icons.h"
12#include "core/project.h"
13#include "imgui/imgui.h"
14
15namespace yaze::editor {
16
17namespace {
18
19// Format a file_time_type to a human-readable string.
20std::string FormatFileTime(std::filesystem::file_time_type ftime) {
21 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
22 ftime - std::filesystem::file_time_type::clock::now() +
23 std::chrono::system_clock::now());
24 std::time_t cftime = std::chrono::system_clock::to_time_t(sctp);
25 char buf[64];
26 std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&cftime));
27 return std::string(buf);
28}
29
30} // namespace
31
34 ImGui::Separator();
36}
37
38// ---------------------------------------------------------------------------
39// Card B3: Manifest Freshness UX
40// ---------------------------------------------------------------------------
41
43 ImGui::TextColored(ImVec4(0.7f, 0.85f, 1.0f, 1.0f),
44 ICON_MD_DESCRIPTION " Hack Manifest");
45 ImGui::Spacing();
46
47 if (!project_) {
48 ImGui::TextDisabled("No project loaded.");
49 return;
50 }
51
52 const auto& manifest = project_->hack_manifest;
53 std::string manifest_path = ResolveManifestPath();
54
55 // Status indicator
56 if (manifest.loaded()) {
57 ImGui::TextColored(ImVec4(0.3f, 0.9f, 0.3f, 1.0f), ICON_MD_CHECK_CIRCLE);
58 ImGui::SameLine();
59 ImGui::Text("Loaded: %s", manifest.hack_name().c_str());
60 } else {
61 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), ICON_MD_ERROR);
62 ImGui::SameLine();
63 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f),
64 "No manifest loaded — write conflict detection disabled");
65 }
66
67 // Metadata table
68 if (manifest.loaded()) {
69 if (ImGui::BeginTable("ManifestInfo", 2,
70 ImGuiTableFlags_SizingStretchProp)) {
71 ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 120);
72 ImGui::TableSetupColumn("Value");
73
74 ImGui::TableNextRow();
75 ImGui::TableSetColumnIndex(0);
76 ImGui::TextDisabled("Version");
77 ImGui::TableSetColumnIndex(1);
78 ImGui::Text("%d", manifest.manifest_version());
79
80 ImGui::TableNextRow();
81 ImGui::TableSetColumnIndex(0);
82 ImGui::TextDisabled("Total Hooks");
83 ImGui::TableSetColumnIndex(1);
84 ImGui::Text("%d", manifest.total_hooks());
85
86 ImGui::TableNextRow();
87 ImGui::TableSetColumnIndex(0);
88 ImGui::TextDisabled("Protected Regions");
89 ImGui::TableSetColumnIndex(1);
90 ImGui::Text("%zu", manifest.protected_regions().size());
91
92 ImGui::TableNextRow();
93 ImGui::TableSetColumnIndex(0);
94 ImGui::TextDisabled("Feature Flags");
95 ImGui::TableSetColumnIndex(1);
96 ImGui::Text("%zu", manifest.feature_flags().size());
97
98 ImGui::TableNextRow();
99 ImGui::TableSetColumnIndex(0);
100 ImGui::TextDisabled("SRAM Variables");
101 ImGui::TableSetColumnIndex(1);
102 ImGui::Text("%zu", manifest.sram_variables().size());
103
104 ImGui::EndTable();
105 }
106 }
107
108 // File path and mtime
109 ImGui::Spacing();
110 if (!manifest_path.empty()) {
111 ImGui::TextDisabled("Path:");
112 ImGui::SameLine();
113 ImGui::TextWrapped("%s", manifest_path.c_str());
114
115 // Show file modification time
116 std::error_code ec;
117 if (std::filesystem::exists(manifest_path, ec)) {
118 auto mtime = std::filesystem::last_write_time(manifest_path, ec);
119 if (!ec) {
120 ImGui::TextDisabled("Modified:");
121 ImGui::SameLine();
122 ImGui::Text("%s", FormatFileTime(mtime).c_str());
123 }
124 } else {
125 ImGui::TextColored(ImVec4(0.9f, 0.6f, 0.2f, 1.0f),
126 ICON_MD_WARNING " File not found on disk");
127 }
128 } else {
129 ImGui::TextDisabled("Path: (not configured)");
130 }
131
132 // Reload button
133 ImGui::Spacing();
134 if (ImGui::Button(ICON_MD_REFRESH " Reload Manifest")) {
137 status_message_ = "Manifest reloaded successfully.";
138 status_is_error_ = false;
139 } else {
140 status_message_ = "Reload failed — check path and file format.";
141 status_is_error_ = true;
142 }
143 }
144
145 // Status feedback
146 if (!status_message_.empty()) {
147 ImGui::SameLine();
148 if (status_is_error_) {
149 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), "%s",
150 status_message_.c_str());
151 } else {
152 ImGui::TextColored(ImVec4(0.3f, 0.9f, 0.3f, 1.0f), "%s",
153 status_message_.c_str());
154 }
155 }
156}
157
158// ---------------------------------------------------------------------------
159// Card B4: Protected Regions Inspector
160// ---------------------------------------------------------------------------
161
163 ImGui::TextColored(ImVec4(0.7f, 0.85f, 1.0f, 1.0f),
164 ICON_MD_SHIELD " Protected Regions");
165 ImGui::Spacing();
166
168 ImGui::TextDisabled("Load a manifest to view protected regions.");
169 return;
170 }
171
172 const auto& regions = project_->hack_manifest.protected_regions();
173
174 // Filter
175 ImGui::SetNextItemWidth(200);
176 ImGui::InputTextWithHint("##RegionFilter", "Filter by module...",
177 filter_text_, sizeof(filter_text_));
178 ImGui::SameLine();
179 ImGui::Text("%zu regions", regions.size());
180
181 ImGui::Spacing();
182
183 // Regions table
184 constexpr ImGuiTableFlags kTableFlags =
185 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
186 ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
187 ImGuiTableFlags_SizingFixedFit;
188
189 float table_height = ImGui::GetContentRegionAvail().y - 4;
190 if (table_height < 100) table_height = 200;
191
192 if (ImGui::BeginTable("ProtectedRegions", 5, kTableFlags,
193 ImVec2(0, table_height))) {
194 ImGui::TableSetupScrollFreeze(0, 1);
195 ImGui::TableSetupColumn("Start", ImGuiTableColumnFlags_WidthFixed, 80);
196 ImGui::TableSetupColumn("End", ImGuiTableColumnFlags_WidthFixed, 80);
197 ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 50);
198 ImGui::TableSetupColumn("Hooks", ImGuiTableColumnFlags_WidthFixed, 45);
199 ImGui::TableSetupColumn("Module", ImGuiTableColumnFlags_WidthStretch);
200 ImGui::TableHeadersRow();
201
202 std::string filter_lower;
203 if (filter_text_[0] != '\0') {
204 filter_lower = filter_text_;
205 for (auto& c : filter_lower) {
206 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
207 }
208 }
209
210 for (const auto& region : regions) {
211 // Filter by module name
212 if (!filter_lower.empty()) {
213 std::string module_lower = region.module;
214 for (auto& c : module_lower) {
215 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
216 }
217 if (module_lower.find(filter_lower) == std::string::npos) {
218 continue;
219 }
220 }
221
222 ImGui::TableNextRow();
223
224 // Start address
225 ImGui::TableSetColumnIndex(0);
226 ImGui::Text("$%06X", region.start);
227
228 // End address
229 ImGui::TableSetColumnIndex(1);
230 ImGui::Text("$%06X", region.end);
231
232 // Size in bytes
233 ImGui::TableSetColumnIndex(2);
234 uint32_t size = (region.end > region.start) ? region.end - region.start : 0;
235 ImGui::Text("%u", size);
236
237 // Hook count
238 ImGui::TableSetColumnIndex(3);
239 ImGui::Text("%d", region.hook_count);
240
241 // Module
242 ImGui::TableSetColumnIndex(4);
243 ImGui::TextUnformatted(region.module.c_str());
244
245 // Copy address range on right-click
246 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
247 std::string range_str =
248 absl::StrFormat("$%06X-$%06X", region.start, region.end);
249 ImGui::SetClipboardText(range_str.c_str());
250 }
251 }
252
253 ImGui::EndTable();
254 }
255
256 ImGui::TextDisabled("Right-click a module to copy the address range.");
257}
258
260 if (!project_) return "";
261
262 // Priority 1: Explicit hack_manifest_file setting
263 if (!project_->hack_manifest_file.empty()) {
265 }
266
267 // Priority 2: Auto-discover in code_folder
268 if (!project_->code_folder.empty()) {
269 std::string auto_path =
270 project_->GetAbsolutePath(project_->code_folder + "/hack_manifest.json");
271 std::error_code ec;
272 if (std::filesystem::exists(auto_path, ec)) {
273 return auto_path;
274 }
275 }
276
277 return "";
278}
279
280} // namespace yaze::editor
const std::vector< ProtectedRegion > & protected_regions() const
bool loaded() const
Check if the manifest has been loaded.
void DrawManifestStatus()
Draw Card B3: manifest status, path, mtime, reload button.
void DrawProtectedRegions()
Draw Card B4: searchable protected regions table.
project::YazeProject * project_
void Draw()
Draw the combined panel content (no ImGui::Begin/End wrapper).
std::string ResolveManifestPath() const
Resolve the absolute path to hack_manifest.json.
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_SHIELD
Definition icons.h:1724
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DESCRIPTION
Definition icons.h:539
std::string FormatFileTime(std::filesystem::file_time_type ftime)
Editors are the view controllers for the application.
core::HackManifest hack_manifest
Definition project.h:160
std::string hack_manifest_file
Definition project.h:142
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1287