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 <cctype>
4#include <chrono>
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(
64 ImVec4(0.9f, 0.3f, 0.3f, 1.0f),
65 "No manifest loaded — write conflict detection disabled");
66 }
67
68 // Metadata table
69 if (manifest.loaded()) {
70 if (ImGui::BeginTable("ManifestInfo", 2,
71 ImGuiTableFlags_SizingStretchProp)) {
72 ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_WidthFixed, 120);
73 ImGui::TableSetupColumn("Value");
74
75 ImGui::TableNextRow();
76 ImGui::TableSetColumnIndex(0);
77 ImGui::TextDisabled("Version");
78 ImGui::TableSetColumnIndex(1);
79 ImGui::Text("%d", manifest.manifest_version());
80
81 ImGui::TableNextRow();
82 ImGui::TableSetColumnIndex(0);
83 ImGui::TextDisabled("Total Hooks");
84 ImGui::TableSetColumnIndex(1);
85 ImGui::Text("%d", manifest.total_hooks());
86
87 ImGui::TableNextRow();
88 ImGui::TableSetColumnIndex(0);
89 ImGui::TextDisabled("Protected Regions");
90 ImGui::TableSetColumnIndex(1);
91 ImGui::Text("%zu", manifest.protected_regions().size());
92
93 ImGui::TableNextRow();
94 ImGui::TableSetColumnIndex(0);
95 ImGui::TextDisabled("Feature Flags");
96 ImGui::TableSetColumnIndex(1);
97 ImGui::Text("%zu", manifest.feature_flags().size());
98
99 ImGui::TableNextRow();
100 ImGui::TableSetColumnIndex(0);
101 ImGui::TextDisabled("SRAM Variables");
102 ImGui::TableSetColumnIndex(1);
103 ImGui::Text("%zu", manifest.sram_variables().size());
104
105 ImGui::EndTable();
106 }
107 }
108
109 // File path and mtime
110 ImGui::Spacing();
111 if (!manifest_path.empty()) {
112 ImGui::TextDisabled("Path:");
113 ImGui::SameLine();
114 ImGui::TextWrapped("%s", manifest_path.c_str());
115
116 // Show file modification time
117 std::error_code ec;
118 if (std::filesystem::exists(manifest_path, ec)) {
119 auto mtime = std::filesystem::last_write_time(manifest_path, ec);
120 if (!ec) {
121 ImGui::TextDisabled("Modified:");
122 ImGui::SameLine();
123 ImGui::Text("%s", FormatFileTime(mtime).c_str());
124 }
125 } else {
126 ImGui::TextColored(ImVec4(0.9f, 0.6f, 0.2f, 1.0f),
127 ICON_MD_WARNING " File not found on disk");
128 }
129 } else {
130 ImGui::TextDisabled("Path: (not configured)");
131 }
132
133 // Reload button
134 ImGui::Spacing();
135 if (ImGui::Button(ICON_MD_REFRESH " Reload Manifest")) {
138 status_message_ = "Manifest reloaded successfully.";
139 status_is_error_ = false;
140 } else {
141 status_message_ = "Reload failed — check path and file format.";
142 status_is_error_ = true;
143 }
144 }
145
146 // Status feedback
147 if (!status_message_.empty()) {
148 ImGui::SameLine();
149 if (status_is_error_) {
150 ImGui::TextColored(ImVec4(0.9f, 0.3f, 0.3f, 1.0f), "%s",
151 status_message_.c_str());
152 } else {
153 ImGui::TextColored(ImVec4(0.3f, 0.9f, 0.3f, 1.0f), "%s",
154 status_message_.c_str());
155 }
156 }
157}
158
159// ---------------------------------------------------------------------------
160// Card B4: Protected Regions Inspector
161// ---------------------------------------------------------------------------
162
164 ImGui::TextColored(ImVec4(0.7f, 0.85f, 1.0f, 1.0f),
165 ICON_MD_SHIELD " Protected Regions");
166 ImGui::Spacing();
167
169 ImGui::TextDisabled("Load a manifest to view protected regions.");
170 return;
171 }
172
173 const auto& regions = project_->hack_manifest.protected_regions();
174
175 // Filter
176 ImGui::SetNextItemWidth(200);
177 ImGui::InputTextWithHint("##RegionFilter", "Filter by module...",
178 filter_text_, sizeof(filter_text_));
179 ImGui::SameLine();
180 ImGui::Text("%zu regions", regions.size());
181
182 ImGui::Spacing();
183
184 // Regions table
185 constexpr ImGuiTableFlags kTableFlags =
186 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
187 ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY |
188 ImGuiTableFlags_SizingFixedFit;
189
190 float table_height = ImGui::GetContentRegionAvail().y - 4;
191 if (table_height < 100)
192 table_height = 200;
193
194 if (ImGui::BeginTable("ProtectedRegions", 5, kTableFlags,
195 ImVec2(0, table_height))) {
196 ImGui::TableSetupScrollFreeze(0, 1);
197 ImGui::TableSetupColumn("Start", ImGuiTableColumnFlags_WidthFixed, 80);
198 ImGui::TableSetupColumn("End", ImGuiTableColumnFlags_WidthFixed, 80);
199 ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 50);
200 ImGui::TableSetupColumn("Hooks", ImGuiTableColumnFlags_WidthFixed, 45);
201 ImGui::TableSetupColumn("Module", ImGuiTableColumnFlags_WidthStretch);
202 ImGui::TableHeadersRow();
203
204 std::string filter_lower;
205 if (filter_text_[0] != '\0') {
206 filter_lower = filter_text_;
207 for (auto& c : filter_lower) {
208 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
209 }
210 }
211
212 for (const auto& region : regions) {
213 // Filter by module name
214 if (!filter_lower.empty()) {
215 std::string module_lower = region.module;
216 for (auto& c : module_lower) {
217 c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
218 }
219 if (module_lower.find(filter_lower) == std::string::npos) {
220 continue;
221 }
222 }
223
224 ImGui::TableNextRow();
225
226 // Start address
227 ImGui::TableSetColumnIndex(0);
228 ImGui::Text("$%06X", region.start);
229
230 // End address
231 ImGui::TableSetColumnIndex(1);
232 ImGui::Text("$%06X", region.end);
233
234 // Size in bytes
235 ImGui::TableSetColumnIndex(2);
236 uint32_t size =
237 (region.end > region.start) ? region.end - region.start : 0;
238 ImGui::Text("%u", size);
239
240 // Hook count
241 ImGui::TableSetColumnIndex(3);
242 ImGui::Text("%d", region.hook_count);
243
244 // Module
245 ImGui::TableSetColumnIndex(4);
246 ImGui::TextUnformatted(region.module.c_str());
247
248 // Copy address range on right-click
249 if (ImGui::IsItemHovered() &&
250 ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
251 std::string range_str =
252 absl::StrFormat("$%06X-$%06X", region.start, region.end);
253 ImGui::SetClipboardText(range_str.c_str());
254 }
255 }
256
257 ImGui::EndTable();
258 }
259
260 ImGui::TextDisabled("Right-click a module to copy the address range.");
261}
262
263std::string ManifestPanel::ResolveManifestPath() const {
264 if (!project_)
265 return "";
266
267 // Priority 1: Explicit hack_manifest_file setting
268 if (!project_->hack_manifest_file.empty()) {
270 }
271
272 // Priority 2: Auto-discover in code_folder
273 if (!project_->code_folder.empty()) {
274 std::string auto_path = project_->GetAbsolutePath(project_->code_folder +
275 "/hack_manifest.json");
276 std::error_code ec;
277 if (std::filesystem::exists(auto_path, ec)) {
278 return auto_path;
279 }
280 }
281
282 return "";
283}
284
285} // 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:204
std::string hack_manifest_file
Definition project.h:186
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:1319