yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_tool.cc
Go to the documentation of this file.
1
7
8#include <algorithm>
9#include <cstring>
10#include <filesystem>
11#include <fstream>
12#include <iomanip>
13#include <set>
14#include <sstream>
15
16#include "absl/strings/str_format.h"
17#include "nlohmann/json.hpp"
18
19namespace yaze {
20namespace cli {
21namespace agent {
22namespace tools {
23
24using json = nlohmann::json;
25namespace fs = std::filesystem;
26
27// ============================================================================
28// SHA-256 Implementation (Simplified)
29// ============================================================================
30
31namespace {
32
33// SHA-256 constants
34constexpr uint32_t kSHA256_K[64] = {
35 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
36 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
37 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
38 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
39 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
40 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
41 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
42 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
43 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
44 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
45 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
46
47constexpr uint32_t RightRotate(uint32_t value, uint32_t count) {
48 return (value >> count) | (value << (32 - count));
49}
50
51void SHA256Transform(uint32_t state[8], const uint8_t block[64]) {
52 uint32_t w[64];
53 uint32_t a, b, c, d, e, f, g, h;
54
55 // Prepare message schedule
56 for (int i = 0; i < 16; ++i) {
57 w[i] = (static_cast<uint32_t>(block[i * 4]) << 24) |
58 (static_cast<uint32_t>(block[i * 4 + 1]) << 16) |
59 (static_cast<uint32_t>(block[i * 4 + 2]) << 8) |
60 (static_cast<uint32_t>(block[i * 4 + 3]));
61 }
62
63 for (int i = 16; i < 64; ++i) {
64 uint32_t s0 = RightRotate(w[i - 15], 7) ^ RightRotate(w[i - 15], 18) ^
65 (w[i - 15] >> 3);
66 uint32_t s1 = RightRotate(w[i - 2], 17) ^ RightRotate(w[i - 2], 19) ^
67 (w[i - 2] >> 10);
68 w[i] = w[i - 16] + s0 + w[i - 7] + s1;
69 }
70
71 // Initialize working variables
72 a = state[0];
73 b = state[1];
74 c = state[2];
75 d = state[3];
76 e = state[4];
77 f = state[5];
78 g = state[6];
79 h = state[7];
80
81 // Main loop
82 for (int i = 0; i < 64; ++i) {
83 uint32_t S1 = RightRotate(e, 6) ^ RightRotate(e, 11) ^ RightRotate(e, 25);
84 uint32_t ch = (e & f) ^ ((~e) & g);
85 uint32_t temp1 = h + S1 + ch + kSHA256_K[i] + w[i];
86 uint32_t S0 = RightRotate(a, 2) ^ RightRotate(a, 13) ^ RightRotate(a, 22);
87 uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
88 uint32_t temp2 = S0 + maj;
89
90 h = g;
91 g = f;
92 f = e;
93 e = d + temp1;
94 d = c;
95 c = b;
96 b = a;
97 a = temp1 + temp2;
98 }
99
100 // Add compressed chunk to current hash value
101 state[0] += a;
102 state[1] += b;
103 state[2] += c;
104 state[3] += d;
105 state[4] += e;
106 state[5] += f;
107 state[6] += g;
108 state[7] += h;
109}
110
111std::array<uint8_t, 32> ComputeSHA256Internal(const uint8_t* data,
112 size_t length) {
113 // Initialize hash values
114 uint32_t state[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
115 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
116
117 size_t total_length = length;
118 const uint8_t* ptr = data;
119
120 // Process complete 64-byte blocks
121 while (length >= 64) {
122 SHA256Transform(state, ptr);
123 ptr += 64;
124 length -= 64;
125 }
126
127 // Handle remaining bytes + padding
128 uint8_t block[64];
129 std::memset(block, 0, sizeof(block));
130 std::memcpy(block, ptr, length);
131
132 // Append '1' bit (0x80) and pad with zeros
133 block[length] = 0x80;
134
135 // If not enough room for length, process block and create new one
136 if (length >= 56) {
137 SHA256Transform(state, block);
138 std::memset(block, 0, sizeof(block));
139 }
140
141 // Append length in bits as 64-bit big-endian
142 uint64_t bit_length = total_length * 8;
143 for (int i = 0; i < 8; ++i) {
144 block[63 - i] = static_cast<uint8_t>(bit_length >> (i * 8));
145 }
146
147 SHA256Transform(state, block);
148
149 // Convert state to byte array (big-endian)
150 std::array<uint8_t, 32> result;
151 for (int i = 0; i < 8; ++i) {
152 result[i * 4] = static_cast<uint8_t>(state[i] >> 24);
153 result[i * 4 + 1] = static_cast<uint8_t>(state[i] >> 16);
154 result[i * 4 + 2] = static_cast<uint8_t>(state[i] >> 8);
155 result[i * 4 + 3] = static_cast<uint8_t>(state[i]);
156 }
157
158 return result;
159}
160
161} // namespace
162
163// ============================================================================
164// ProjectToolUtils Implementation
165// ============================================================================
166
167std::array<uint8_t, 32> ProjectToolUtils::ComputeRomChecksum(const Rom* rom) {
168 if (!rom || !rom->is_loaded()) {
169 // Return empty checksum
170 std::array<uint8_t, 32> result;
171 result.fill(0);
172 return result;
173 }
174
175 // Compute checksum of ROM data
176 return ComputeSHA256Internal(rom->data(), rom->size());
177}
178
179std::array<uint8_t, 32> ProjectToolUtils::ComputeSHA256(const uint8_t* data,
180 size_t length) {
181 return ComputeSHA256Internal(data, length);
182}
183
185 const std::array<uint8_t, 32>& checksum) {
186 std::ostringstream oss;
187 for (uint8_t byte : checksum) {
188 oss << absl::StrFormat("%02x", byte);
189 }
190 return oss.str();
191}
192
194 const std::chrono::system_clock::time_point& time) {
195 auto time_t = std::chrono::system_clock::to_time_t(time);
196 std::tm tm = *std::gmtime(&time_t);
197 std::ostringstream oss;
198 oss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
199 return oss.str();
200}
201
202absl::StatusOr<std::chrono::system_clock::time_point>
203ProjectToolUtils::ParseTimestamp(const std::string& timestamp) {
204 // Simple ISO 8601 parser (YYYY-MM-DDTHH:MM:SSZ)
205 std::tm tm = {};
206 std::istringstream ss(timestamp);
207 ss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%SZ");
208
209 if (ss.fail()) {
210 return absl::InvalidArgumentError(
211 absl::StrFormat("Invalid timestamp format: %s", timestamp));
212 }
213
214 auto time_t = std::mktime(&tm);
215 return std::chrono::system_clock::from_time_t(time_t);
216}
217
218// ============================================================================
219// ProjectSnapshot Implementation
220// ============================================================================
221
222absl::Status ProjectSnapshot::SaveToFile(const std::string& filepath) const {
223 try {
224 std::ofstream file(filepath, std::ios::binary);
225 if (!file) {
226 return absl::InternalError(
227 absl::StrFormat("Failed to open file for writing: %s", filepath));
228 }
229
230 // Write header
231 EditFileHeader header;
232 header.edit_count = static_cast<uint32_t>(edits.size());
234
235 file.write(reinterpret_cast<const char*>(&header), sizeof(header));
236
237 // Write edits
238 for (const auto& edit : edits) {
239 SerializedEdit ser_edit;
240 ser_edit.address = edit.address;
241 ser_edit.length = static_cast<uint32_t>(edit.new_value.size());
242
243 file.write(reinterpret_cast<const char*>(&ser_edit), sizeof(ser_edit));
244 file.write(reinterpret_cast<const char*>(edit.old_value.data()),
245 edit.old_value.size());
246 file.write(reinterpret_cast<const char*>(edit.new_value.data()),
247 edit.new_value.size());
248 }
249
250 // Write metadata as JSON
251 json metadata_json = {
252 {"name", name},
253 {"description", description},
255 {"metadata", metadata}};
256
257 std::string metadata_str = metadata_json.dump();
258 uint32_t metadata_length = static_cast<uint32_t>(metadata_str.size());
259 file.write(reinterpret_cast<const char*>(&metadata_length),
260 sizeof(metadata_length));
261 file.write(metadata_str.data(), metadata_str.size());
262
263 return absl::OkStatus();
264
265 } catch (const std::exception& e) {
266 return absl::InternalError(
267 absl::StrFormat("Failed to save snapshot: %s", e.what()));
268 }
269}
270
271absl::StatusOr<ProjectSnapshot> ProjectSnapshot::LoadFromFile(
272 const std::string& filepath) {
273 try {
274 std::ifstream file(filepath, std::ios::binary);
275 if (!file) {
276 return absl::NotFoundError(
277 absl::StrFormat("Failed to open file: %s", filepath));
278 }
279
280 // Read header
281 EditFileHeader header;
282 file.read(reinterpret_cast<char*>(&header), sizeof(header));
283
284 if (header.magic != EditFileHeader::kMagic) {
285 return absl::InvalidArgumentError(
286 absl::StrFormat("Invalid file format (magic: 0x%08X)", header.magic));
287 }
288
290 return absl::InvalidArgumentError(
291 absl::StrFormat("Unsupported version: %u", header.version));
292 }
293
294 ProjectSnapshot snapshot;
295 snapshot.rom_checksum = header.base_rom_sha256;
296
297 // Read edits
298 for (uint32_t i = 0; i < header.edit_count; ++i) {
299 SerializedEdit ser_edit;
300 file.read(reinterpret_cast<char*>(&ser_edit), sizeof(ser_edit));
301
302 RomEdit edit;
303 edit.address = ser_edit.address;
304 edit.old_value.resize(ser_edit.length);
305 edit.new_value.resize(ser_edit.length);
306
307 file.read(reinterpret_cast<char*>(edit.old_value.data()),
308 ser_edit.length);
309 file.read(reinterpret_cast<char*>(edit.new_value.data()),
310 ser_edit.length);
311
312 edit.timestamp = std::chrono::system_clock::now();
313 edit.description = "Restored from snapshot";
314
315 snapshot.edits.push_back(edit);
316 }
317
318 // Read metadata
319 uint32_t metadata_length = 0;
320 file.read(reinterpret_cast<char*>(&metadata_length),
321 sizeof(metadata_length));
322
323 std::string metadata_str(metadata_length, '\0');
324 file.read(&metadata_str[0], metadata_length);
325
326 json metadata_json = json::parse(metadata_str);
327 snapshot.name = metadata_json["name"];
328 snapshot.description = metadata_json["description"];
329
330 auto timestamp_result =
331 ProjectToolUtils::ParseTimestamp(metadata_json["created"]);
332 if (timestamp_result.ok()) {
333 snapshot.created = *timestamp_result;
334 } else {
335 snapshot.created = std::chrono::system_clock::now();
336 }
337
338 if (metadata_json.contains("metadata")) {
339 snapshot.metadata = metadata_json["metadata"].get<
340 std::map<std::string, std::string>>();
341 }
342
343 return snapshot;
344
345 } catch (const std::exception& e) {
346 return absl::InternalError(
347 absl::StrFormat("Failed to load snapshot: %s", e.what()));
348 }
349}
350
351// ============================================================================
352// ProjectManager Implementation
353// ============================================================================
354
355absl::Status ProjectManager::Initialize(const std::string& base_path) {
356 try {
357 project_path_ = (fs::path(base_path) / ".yaze-project").string();
358 snapshots_path_ = (fs::path(project_path_) / "snapshots").string();
359
360 // Create directory structure
361 fs::create_directories(snapshots_path_);
362
363 // Create metadata file if it doesn't exist
364 std::string metadata_path =
365 (fs::path(project_path_) / "project.json").string();
366 if (!fs::exists(metadata_path)) {
367 json metadata = {{"version", "1.0"},
369 std::chrono::system_clock::now())}};
370
371 std::ofstream file(metadata_path);
372 file << metadata.dump(2);
373 }
374
375 return LoadSnapshots();
376
377 } catch (const std::exception& e) {
378 return absl::InternalError(
379 absl::StrFormat("Failed to initialize project: %s", e.what()));
380 }
381}
382
384 const std::string& name, const std::string& description,
385 const std::vector<RomEdit>& edits,
386 const std::array<uint8_t, 32>& rom_checksum) {
387 if (name.empty()) {
388 return absl::InvalidArgumentError("Snapshot name cannot be empty");
389 }
390
391 if (snapshots_.find(name) != snapshots_.end()) {
392 return absl::AlreadyExistsError(
393 absl::StrFormat("Snapshot already exists: %s", name));
394 }
395
396 ProjectSnapshot snapshot;
397 snapshot.name = name;
398 snapshot.description = description;
399 snapshot.created = std::chrono::system_clock::now();
400 snapshot.edits = edits;
401 snapshot.rom_checksum = rom_checksum;
402
403 // Save to file
404 std::string filepath = GetSnapshotFilePath(name);
405 auto status = snapshot.SaveToFile(filepath);
406 if (!status.ok()) {
407 return status;
408 }
409
410 snapshots_[name] = snapshot;
411 return SaveProjectMetadata();
412}
413
414absl::Status ProjectManager::RestoreSnapshot(const std::string& name,
415 Rom* rom) {
416 auto it = snapshots_.find(name);
417 if (it == snapshots_.end()) {
418 return absl::NotFoundError(
419 absl::StrFormat("Snapshot not found: %s", name));
420 }
421
422 if (!rom || !rom->is_loaded()) {
423 return absl::FailedPreconditionError("ROM not loaded");
424 }
425
426 const ProjectSnapshot& snapshot = it->second;
427
428 // Validate ROM checksum
429 auto current_checksum = ProjectToolUtils::ComputeRomChecksum(rom);
430 if (current_checksum != snapshot.rom_checksum) {
431 return absl::FailedPreconditionError(
432 "ROM checksum mismatch - snapshot is for a different ROM");
433 }
434
435 // Apply edits
436 for (const auto& edit : snapshot.edits) {
437 for (size_t i = 0; i < edit.new_value.size(); ++i) {
438 rom->WriteByte(edit.address + i, edit.new_value[i]);
439 }
440 }
441
442 return absl::OkStatus();
443}
444
445std::vector<std::string> ProjectManager::ListSnapshots() const {
446 std::vector<std::string> names;
447 for (const auto& [name, _] : snapshots_) {
448 names.push_back(name);
449 }
450 std::sort(names.begin(), names.end());
451 return names;
452}
453
454absl::StatusOr<ProjectSnapshot> ProjectManager::GetSnapshot(
455 const std::string& name) const {
456 auto it = snapshots_.find(name);
457 if (it == snapshots_.end()) {
458 return absl::NotFoundError(
459 absl::StrFormat("Snapshot not found: %s", name));
460 }
461 return it->second;
462}
463
464absl::Status ProjectManager::DeleteSnapshot(const std::string& name) {
465 auto it = snapshots_.find(name);
466 if (it == snapshots_.end()) {
467 return absl::NotFoundError(
468 absl::StrFormat("Snapshot not found: %s", name));
469 }
470
471 // Delete file
472 std::string filepath = GetSnapshotFilePath(name);
473 try {
474 fs::remove(filepath);
475 } catch (const std::exception& e) {
476 return absl::InternalError(
477 absl::StrFormat("Failed to delete snapshot file: %s", e.what()));
478 }
479
480 snapshots_.erase(it);
481 return SaveProjectMetadata();
482}
483
484absl::Status ProjectManager::ExportProject(const std::string& export_path,
485 bool include_rom) {
486 // TODO: Implement project export (create tar/zip archive)
487 return absl::UnimplementedError("Project export not yet implemented");
488}
489
490absl::Status ProjectManager::ImportProject(const std::string& archive_path) {
491 // TODO: Implement project import (extract tar/zip archive)
492 return absl::UnimplementedError("Project import not yet implemented");
493}
494
495absl::StatusOr<std::string> ProjectManager::DiffSnapshots(
496 const std::string& snapshot1, const std::string& snapshot2) const {
497 auto snap1_result = GetSnapshot(snapshot1);
498 if (!snap1_result.ok()) {
499 return snap1_result.status();
500 }
501
502 auto snap2_result = GetSnapshot(snapshot2);
503 if (!snap2_result.ok()) {
504 return snap2_result.status();
505 }
506
507 const ProjectSnapshot& snap1 = *snap1_result;
508 const ProjectSnapshot& snap2 = *snap2_result;
509
510 std::ostringstream diff;
511 diff << absl::StrFormat("Comparing snapshots:\n");
512 diff << absl::StrFormat(" %s (%zu edits)\n", snapshot1, snap1.edits.size());
513 diff << absl::StrFormat(" %s (%zu edits)\n", snapshot2, snap2.edits.size());
514 diff << "\n";
515
516 // Build address maps for comparison
517 std::map<uint32_t, const RomEdit*> snap1_map;
518 std::map<uint32_t, const RomEdit*> snap2_map;
519
520 for (const auto& edit : snap1.edits) {
521 snap1_map[edit.address] = &edit;
522 }
523 for (const auto& edit : snap2.edits) {
524 snap2_map[edit.address] = &edit;
525 }
526
527 // Find differences
528 std::set<uint32_t> all_addresses;
529 for (const auto& [addr, _] : snap1_map) {
530 all_addresses.insert(addr);
531 }
532 for (const auto& [addr, _] : snap2_map) {
533 all_addresses.insert(addr);
534 }
535
536 int added = 0, removed = 0, modified = 0;
537
538 for (uint32_t addr : all_addresses) {
539 bool in_snap1 = snap1_map.find(addr) != snap1_map.end();
540 bool in_snap2 = snap2_map.find(addr) != snap2_map.end();
541
542 if (in_snap1 && !in_snap2) {
543 diff << absl::StrFormat("- Removed at 0x%06X\n", addr);
544 removed++;
545 } else if (!in_snap1 && in_snap2) {
546 diff << absl::StrFormat("+ Added at 0x%06X\n", addr);
547 added++;
548 } else if (in_snap1 && in_snap2) {
549 const auto& edit1 = *snap1_map[addr];
550 const auto& edit2 = *snap2_map[addr];
551
552 if (edit1.new_value != edit2.new_value) {
553 diff << absl::StrFormat("~ Modified at 0x%06X\n", addr);
554 modified++;
555 }
556 }
557 }
558
559 diff << absl::StrFormat("\nSummary: +%d -%d ~%d\n", added, removed,
560 modified);
561
562 return diff.str();
563}
564
566 try {
567 if (!fs::exists(snapshots_path_)) {
568 return absl::OkStatus();
569 }
570
571 for (const auto& entry : fs::directory_iterator(snapshots_path_)) {
572 if (entry.path().extension() == ".edits") {
573 auto snapshot_result = ProjectSnapshot::LoadFromFile(entry.path());
574 if (snapshot_result.ok()) {
575 ProjectSnapshot& snapshot = *snapshot_result;
576 snapshots_[snapshot.name] = snapshot;
577 }
578 }
579 }
580
581 return absl::OkStatus();
582
583 } catch (const std::exception& e) {
584 return absl::InternalError(
585 absl::StrFormat("Failed to load snapshots: %s", e.what()));
586 }
587}
588
590 try {
591 std::string metadata_path =
592 (fs::path(project_path_) / "project.json").string();
593
594 json metadata = {
595 {"version", "1.0"},
597 std::chrono::system_clock::now())},
598 {"snapshots", json::array()}};
599
600 for (const auto& [name, snapshot] : snapshots_) {
601 metadata["snapshots"].push_back({
602 {"name", name},
603 {"created", ProjectToolUtils::FormatTimestamp(snapshot.created)},
604 {"edit_count", snapshot.edits.size()},
605 });
606 }
607
608 std::ofstream file(metadata_path);
609 file << metadata.dump(2);
610
611 return absl::OkStatus();
612
613 } catch (const std::exception& e) {
614 return absl::InternalError(
615 absl::StrFormat("Failed to save metadata: %s", e.what()));
616 }
617}
618
619std::string ProjectManager::GetSnapshotFilePath(const std::string& name) const {
620 // Sanitize name for filename
621 std::string safe_name = name;
622 std::replace(safe_name.begin(), safe_name.end(), ' ', '_');
623 std::replace(safe_name.begin(), safe_name.end(), '/', '_');
624 std::replace(safe_name.begin(), safe_name.end(), '\\', '_');
625
626 return (fs::path(snapshots_path_) / (safe_name + ".edits")).string();
627}
628
629// ============================================================================
630// ProjectToolBase Implementation
631// ============================================================================
632
633absl::StatusOr<ProjectManager*> ProjectToolBase::GetProjectManager(
634 AgentContext* /*context*/) const {
635 // For now, create a temporary project manager
636 // In a full implementation, this would be stored in AgentContext
637 static ProjectManager manager;
638 return &manager;
639}
640
642 const std::vector<RomEdit>& edits) const {
643 std::ostringstream oss;
644 for (const auto& edit : edits) {
645 oss << absl::StrFormat(" Address: 0x%06X, Length: %zu bytes, %s\n",
646 edit.address, edit.new_value.size(),
647 edit.description);
648 }
649 return oss.str();
650}
651
653 const ProjectSnapshot& snapshot) const {
654 std::ostringstream oss;
655 oss << absl::StrFormat("Snapshot: %s\n", snapshot.name);
656 oss << absl::StrFormat("Description: %s\n", snapshot.description);
657 oss << absl::StrFormat("Created: %s\n",
659 oss << absl::StrFormat("Edits: %zu\n", snapshot.edits.size());
660 oss << absl::StrFormat(
661 "ROM Checksum: %s\n",
663 return oss.str();
664}
665
666// ============================================================================
667// Tool Implementations
668// ============================================================================
669
671 Rom* rom, const resources::ArgumentParser& /*parser*/,
672 resources::OutputFormatter& formatter) {
673 formatter.BeginObject("Project Status");
674
675 // Get project manager (would come from context in full implementation)
676 auto manager_result = GetProjectManager(nullptr);
677 if (!manager_result.ok()) {
678 return manager_result.status();
679 }
680
681 ProjectManager* manager = *manager_result;
682
683 if (manager->IsInitialized()) {
684 formatter.AddField("project_path", manager->GetProjectPath());
685 formatter.AddField("initialized", true);
686
687 auto snapshots = manager->ListSnapshots();
688 formatter.AddField("snapshot_count", static_cast<int>(snapshots.size()));
689
690 formatter.BeginArray("snapshots");
691 for (const auto& name : snapshots) {
692 formatter.AddArrayItem(name);
693 }
694 formatter.EndArray();
695 } else {
696 formatter.AddField("initialized", false);
697 formatter.AddField("message",
698 "No project initialized. Use project-snapshot to create "
699 "first snapshot.");
700 }
701
702 if (rom && rom->is_loaded()) {
703 auto checksum = ProjectToolUtils::ComputeRomChecksum(rom);
704 formatter.AddField("rom_checksum",
706 formatter.AddField("rom_size", static_cast<uint64_t>(rom->size()));
707 }
708
709 formatter.EndObject();
710 return absl::OkStatus();
711}
712
714 Rom* rom, const resources::ArgumentParser& parser,
715 resources::OutputFormatter& formatter) {
716 if (!rom || !rom->is_loaded()) {
717 return absl::FailedPreconditionError("ROM not loaded");
718 }
719
720 auto name = parser.GetString("name");
721 if (!name.has_value()) {
722 return absl::InvalidArgumentError("Missing --name argument");
723 }
724
725 auto description = parser.GetString("description").value_or("");
726
727 // Get project manager
728 auto manager_result = GetProjectManager(nullptr);
729 if (!manager_result.ok()) {
730 return manager_result.status();
731 }
732 ProjectManager* manager = *manager_result;
733
734 // Initialize project if not already done
735 if (!manager->IsInitialized()) {
736 auto init_status = manager->Initialize(".");
737 if (!init_status.ok()) {
738 return init_status;
739 }
740 }
741
742 // Get edits from context (empty for now - would come from AgentContext)
743 std::vector<RomEdit> edits;
744
745 // Compute ROM checksum
746 auto checksum = ProjectToolUtils::ComputeRomChecksum(rom);
747
748 // Create snapshot
749 auto status = manager->CreateSnapshot(*name, description, edits, checksum);
750 if (!status.ok()) {
751 return status;
752 }
753
754 formatter.BeginObject("Snapshot Created");
755 formatter.AddField("name", *name);
756 formatter.AddField("description", description);
757 formatter.AddField("edit_count", static_cast<int>(edits.size()));
758 formatter.AddField("rom_checksum", ProjectToolUtils::FormatChecksum(checksum));
759 formatter.EndObject();
760
761 return absl::OkStatus();
762}
763
765 Rom* rom, const resources::ArgumentParser& parser,
766 resources::OutputFormatter& formatter) {
767 if (!rom || !rom->is_loaded()) {
768 return absl::FailedPreconditionError("ROM not loaded");
769 }
770
771 auto name = parser.GetString("name");
772 if (!name.has_value()) {
773 return absl::InvalidArgumentError("Missing --name argument");
774 }
775
776 auto manager_result = GetProjectManager(nullptr);
777 if (!manager_result.ok()) {
778 return manager_result.status();
779 }
780 ProjectManager* manager = *manager_result;
781
782 if (!manager->IsInitialized()) {
783 return absl::FailedPreconditionError("No project initialized");
784 }
785
786 auto status = manager->RestoreSnapshot(*name, rom);
787 if (!status.ok()) {
788 return status;
789 }
790
791 auto snapshot_result = manager->GetSnapshot(*name);
792 if (!snapshot_result.ok()) {
793 return snapshot_result.status();
794 }
795
796 const ProjectSnapshot& snapshot = *snapshot_result;
797
798 formatter.BeginObject("Snapshot Restored");
799 formatter.AddField("name", snapshot.name);
800 formatter.AddField("edits_applied", static_cast<int>(snapshot.edits.size()));
801 formatter.AddField("description", snapshot.description);
802 formatter.EndObject();
803
804 return absl::OkStatus();
805}
806
808 Rom* /*rom*/, const resources::ArgumentParser& parser,
809 resources::OutputFormatter& formatter) {
810 auto path = parser.GetString("path");
811 if (!path.has_value()) {
812 return absl::InvalidArgumentError("Missing --path argument");
813 }
814
815 bool include_rom = parser.HasFlag("include-rom");
816
817 auto manager_result = GetProjectManager(nullptr);
818 if (!manager_result.ok()) {
819 return manager_result.status();
820 }
821 ProjectManager* manager = *manager_result;
822
823 auto status = manager->ExportProject(*path, include_rom);
824 if (!status.ok()) {
825 return status;
826 }
827
828 formatter.BeginObject("Project Exported");
829 formatter.AddField("path", *path);
830 formatter.AddField("include_rom", include_rom);
831 formatter.EndObject();
832
833 return absl::OkStatus();
834}
835
837 Rom* /*rom*/, const resources::ArgumentParser& parser,
838 resources::OutputFormatter& formatter) {
839 auto path = parser.GetString("path");
840 if (!path.has_value()) {
841 return absl::InvalidArgumentError("Missing --path argument");
842 }
843
844 auto manager_result = GetProjectManager(nullptr);
845 if (!manager_result.ok()) {
846 return manager_result.status();
847 }
848 ProjectManager* manager = *manager_result;
849
850 auto status = manager->ImportProject(*path);
851 if (!status.ok()) {
852 return status;
853 }
854
855 formatter.BeginObject("Project Imported");
856 formatter.AddField("path", *path);
857 formatter.EndObject();
858
859 return absl::OkStatus();
860}
861
862absl::Status ProjectDiffTool::Execute(Rom* /*rom*/,
863 const resources::ArgumentParser& parser,
864 resources::OutputFormatter& formatter) {
865 auto snapshot1 = parser.GetString("snapshot1");
866 auto snapshot2 = parser.GetString("snapshot2");
867
868 if (!snapshot1.has_value() || !snapshot2.has_value()) {
869 return absl::InvalidArgumentError("Missing snapshot names");
870 }
871
872 auto manager_result = GetProjectManager(nullptr);
873 if (!manager_result.ok()) {
874 return manager_result.status();
875 }
876 ProjectManager* manager = *manager_result;
877
878 if (!manager->IsInitialized()) {
879 return absl::FailedPreconditionError("No project initialized");
880 }
881
882 auto diff_result = manager->DiffSnapshots(*snapshot1, *snapshot2);
883 if (!diff_result.ok()) {
884 return diff_result.status();
885 }
886
887 formatter.BeginObject("Snapshot Diff");
888 formatter.AddField("snapshot1", *snapshot1);
889 formatter.AddField("snapshot2", *snapshot2);
890 formatter.AddField("diff", *diff_result);
891 formatter.EndObject();
892
893 return absl::OkStatus();
894}
895
896} // namespace tools
897} // namespace agent
898} // namespace cli
899} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:286
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
bool is_loaded() const
Definition rom.h:128
Agent context for maintaining state across tool calls.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
Project manager for snapshot and version control.
std::map< std::string, ProjectSnapshot > snapshots_
std::string GetSnapshotFilePath(const std::string &name) const
absl::Status ExportProject(const std::string &export_path, bool include_rom=false)
Export project as portable archive.
std::vector< std::string > ListSnapshots() const
List all available snapshots.
absl::Status Initialize(const std::string &base_path)
Initialize project directory structure.
const std::string & GetProjectPath() const
Get project directory path.
absl::StatusOr< ProjectSnapshot > GetSnapshot(const std::string &name) const
Get snapshot details.
absl::StatusOr< std::string > DiffSnapshots(const std::string &snapshot1, const std::string &snapshot2) const
Compare two snapshots.
absl::Status CreateSnapshot(const std::string &name, const std::string &description, const std::vector< RomEdit > &edits, const std::array< uint8_t, 32 > &rom_checksum)
Create a named snapshot of current state.
absl::Status DeleteSnapshot(const std::string &name)
Delete a snapshot.
absl::Status RestoreSnapshot(const std::string &name, Rom *rom)
Restore ROM to a named snapshot.
absl::Status ImportProject(const std::string &archive_path)
Import project archive.
bool IsInitialized() const
Check if project is initialized.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::Status Execute(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter) override
Execute the command business logic.
absl::StatusOr< ProjectManager * > GetProjectManager(AgentContext *context) const
Get or create project manager from context.
std::string FormatSnapshot(const ProjectSnapshot &snapshot) const
Format snapshot info as text.
std::string FormatEdits(const std::vector< RomEdit > &edits) const
Format edit list as text.
static std::string FormatTimestamp(const std::chrono::system_clock::time_point &time)
Format timestamp for display.
static std::array< uint8_t, 32 > ComputeSHA256(const uint8_t *data, size_t length)
Compute SHA-256 checksum of arbitrary data.
static std::array< uint8_t, 32 > ComputeRomChecksum(const Rom *rom)
Compute SHA-256 checksum of ROM data.
static absl::StatusOr< std::chrono::system_clock::time_point > ParseTimestamp(const std::string &timestamp)
Parse timestamp from string.
static std::string FormatChecksum(const std::array< uint8_t, 32 > &checksum)
Format checksum as hex string.
Utility for parsing common CLI argument patterns.
std::optional< std::string > GetString(const std::string &name) const
Parse a named argument (e.g., –format=json or –format json)
bool HasFlag(const std::string &name) const
Check if a flag is present.
Utility for consistent output formatting across commands.
void BeginArray(const std::string &key)
Begin an array.
void AddArrayItem(const std::string &item)
Add an item to current array.
void BeginObject(const std::string &title="")
Start a JSON object or text section.
void EndObject()
End a JSON object or text section.
void AddField(const std::string &key, const std::string &value)
Add a key-value pair.
std::array< uint8_t, 32 > ComputeSHA256Internal(const uint8_t *data, size_t length)
constexpr uint32_t RightRotate(uint32_t value, uint32_t count)
void SHA256Transform(uint32_t state[8], const uint8_t block[64])
Project management tools for AI agents.
Record of a ROM edit operation.
std::vector< uint8_t > new_value
std::chrono::system_clock::time_point timestamp
std::vector< uint8_t > old_value
Binary format for .edits file.
static constexpr uint32_t kMagic
static constexpr uint32_t kCurrentVersion
std::array< uint8_t, 32 > base_rom_sha256
Project snapshot with edit deltas.
absl::Status SaveToFile(const std::string &filepath) const
std::array< uint8_t, 32 > rom_checksum
std::map< std::string, std::string > metadata
std::chrono::system_clock::time_point created
static absl::StatusOr< ProjectSnapshot > LoadFromFile(const std::string &filepath)
Serialized edit record in binary format.