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 =
340 metadata_json["metadata"].get<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").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(absl::StrFormat("Snapshot not found: %s", name));
419 }
420
421 if (!rom || !rom->is_loaded()) {
422 return absl::FailedPreconditionError("ROM not loaded");
423 }
424
425 const ProjectSnapshot& snapshot = it->second;
426
427 // Validate ROM checksum
428 auto current_checksum = ProjectToolUtils::ComputeRomChecksum(rom);
429 if (current_checksum != snapshot.rom_checksum) {
430 return absl::FailedPreconditionError(
431 "ROM checksum mismatch - snapshot is for a different ROM");
432 }
433
434 // Apply edits
435 if (auto status = rom->CheckLayoutWritable(); !status.ok()) {
436 return status;
437 }
438 for (const auto& edit : snapshot.edits) {
439 for (size_t i = 0; i < edit.new_value.size(); ++i) {
440 if (auto status = rom->WriteByte(edit.address + i, edit.new_value[i]);
441 !status.ok()) {
442 return status;
443 }
444 }
445 }
446
447 return absl::OkStatus();
448}
449
450std::vector<std::string> ProjectManager::ListSnapshots() const {
451 std::vector<std::string> names;
452 for (const auto& [name, _] : snapshots_) {
453 names.push_back(name);
454 }
455 std::sort(names.begin(), names.end());
456 return names;
457}
458
459absl::StatusOr<ProjectSnapshot> ProjectManager::GetSnapshot(
460 const std::string& name) const {
461 auto it = snapshots_.find(name);
462 if (it == snapshots_.end()) {
463 return absl::NotFoundError(absl::StrFormat("Snapshot not found: %s", name));
464 }
465 return it->second;
466}
467
468absl::Status ProjectManager::DeleteSnapshot(const std::string& name) {
469 auto it = snapshots_.find(name);
470 if (it == snapshots_.end()) {
471 return absl::NotFoundError(absl::StrFormat("Snapshot not found: %s", name));
472 }
473
474 // Delete file
475 std::string filepath = GetSnapshotFilePath(name);
476 try {
477 fs::remove(filepath);
478 } catch (const std::exception& e) {
479 return absl::InternalError(
480 absl::StrFormat("Failed to delete snapshot file: %s", e.what()));
481 }
482
483 snapshots_.erase(it);
484 return SaveProjectMetadata();
485}
486
487absl::Status ProjectManager::ExportProject(const std::string& export_path,
488 bool include_rom) {
489 // TODO: Implement project export (create tar/zip archive)
490 return absl::UnimplementedError("Project export not yet implemented");
491}
492
493absl::Status ProjectManager::ImportProject(const std::string& archive_path) {
494 // TODO: Implement project import (extract tar/zip archive)
495 return absl::UnimplementedError("Project import not yet implemented");
496}
497
498absl::StatusOr<std::string> ProjectManager::DiffSnapshots(
499 const std::string& snapshot1, const std::string& snapshot2) const {
500 auto snap1_result = GetSnapshot(snapshot1);
501 if (!snap1_result.ok()) {
502 return snap1_result.status();
503 }
504
505 auto snap2_result = GetSnapshot(snapshot2);
506 if (!snap2_result.ok()) {
507 return snap2_result.status();
508 }
509
510 const ProjectSnapshot& snap1 = *snap1_result;
511 const ProjectSnapshot& snap2 = *snap2_result;
512
513 std::ostringstream diff;
514 diff << absl::StrFormat("Comparing snapshots:\n");
515 diff << absl::StrFormat(" %s (%zu edits)\n", snapshot1, snap1.edits.size());
516 diff << absl::StrFormat(" %s (%zu edits)\n", snapshot2, snap2.edits.size());
517 diff << "\n";
518
519 // Build address maps for comparison
520 std::map<uint32_t, const RomEdit*> snap1_map;
521 std::map<uint32_t, const RomEdit*> snap2_map;
522
523 for (const auto& edit : snap1.edits) {
524 snap1_map[edit.address] = &edit;
525 }
526 for (const auto& edit : snap2.edits) {
527 snap2_map[edit.address] = &edit;
528 }
529
530 // Find differences
531 std::set<uint32_t> all_addresses;
532 for (const auto& [addr, _] : snap1_map) {
533 all_addresses.insert(addr);
534 }
535 for (const auto& [addr, _] : snap2_map) {
536 all_addresses.insert(addr);
537 }
538
539 int added = 0, removed = 0, modified = 0;
540
541 for (uint32_t addr : all_addresses) {
542 bool in_snap1 = snap1_map.find(addr) != snap1_map.end();
543 bool in_snap2 = snap2_map.find(addr) != snap2_map.end();
544
545 if (in_snap1 && !in_snap2) {
546 diff << absl::StrFormat("- Removed at 0x%06X\n", addr);
547 removed++;
548 } else if (!in_snap1 && in_snap2) {
549 diff << absl::StrFormat("+ Added at 0x%06X\n", addr);
550 added++;
551 } else if (in_snap1 && in_snap2) {
552 const auto& edit1 = *snap1_map[addr];
553 const auto& edit2 = *snap2_map[addr];
554
555 if (edit1.new_value != edit2.new_value) {
556 diff << absl::StrFormat("~ Modified at 0x%06X\n", addr);
557 modified++;
558 }
559 }
560 }
561
562 diff << absl::StrFormat("\nSummary: +%d -%d ~%d\n", added, removed, modified);
563
564 return diff.str();
565}
566
568 try {
569 if (!fs::exists(snapshots_path_)) {
570 return absl::OkStatus();
571 }
572
573 for (const auto& entry : fs::directory_iterator(snapshots_path_)) {
574 if (entry.path().extension() == ".edits") {
575 auto snapshot_result =
576 ProjectSnapshot::LoadFromFile(entry.path().string());
577 if (snapshot_result.ok()) {
578 ProjectSnapshot& snapshot = *snapshot_result;
579 snapshots_[snapshot.name] = snapshot;
580 }
581 }
582 }
583
584 return absl::OkStatus();
585
586 } catch (const std::exception& e) {
587 return absl::InternalError(
588 absl::StrFormat("Failed to load snapshots: %s", e.what()));
589 }
590}
591
593 try {
594 std::string metadata_path =
595 (fs::path(project_path_) / "project.json").string();
596
597 json metadata = {{"version", "1.0"},
599 std::chrono::system_clock::now())},
600 {"snapshots", json::array()}};
601
602 for (const auto& [name, snapshot] : snapshots_) {
603 metadata["snapshots"].push_back({
604 {"name", name},
605 {"created", ProjectToolUtils::FormatTimestamp(snapshot.created)},
606 {"edit_count", snapshot.edits.size()},
607 });
608 }
609
610 std::ofstream file(metadata_path);
611 file << metadata.dump(2);
612
613 return absl::OkStatus();
614
615 } catch (const std::exception& e) {
616 return absl::InternalError(
617 absl::StrFormat("Failed to save metadata: %s", e.what()));
618 }
619}
620
621std::string ProjectManager::GetSnapshotFilePath(const std::string& name) const {
622 // Sanitize name for filename
623 std::string safe_name = name;
624 std::replace(safe_name.begin(), safe_name.end(), ' ', '_');
625 std::replace(safe_name.begin(), safe_name.end(), '/', '_');
626 std::replace(safe_name.begin(), safe_name.end(), '\\', '_');
627
628 return (fs::path(snapshots_path_) / (safe_name + ".edits")).string();
629}
630
631// ============================================================================
632// ProjectToolBase Implementation
633// ============================================================================
634
635absl::StatusOr<ProjectManager*> ProjectToolBase::GetProjectManager(
636 AgentContext* /*context*/) const {
637 // For now, create a temporary project manager
638 // In a full implementation, this would be stored in AgentContext
639 static ProjectManager manager;
640 return &manager;
641}
642
644 const std::vector<RomEdit>& edits) const {
645 std::ostringstream oss;
646 for (const auto& edit : edits) {
647 oss << absl::StrFormat(" Address: 0x%06X, Length: %zu bytes, %s\n",
648 edit.address, edit.new_value.size(),
649 edit.description);
650 }
651 return oss.str();
652}
653
655 const ProjectSnapshot& snapshot) const {
656 std::ostringstream oss;
657 oss << absl::StrFormat("Snapshot: %s\n", snapshot.name);
658 oss << absl::StrFormat("Description: %s\n", snapshot.description);
659 oss << absl::StrFormat("Created: %s\n",
661 oss << absl::StrFormat("Edits: %zu\n", snapshot.edits.size());
662 oss << absl::StrFormat("ROM Checksum: %s\n", ProjectToolUtils::FormatChecksum(
663 snapshot.rom_checksum));
664 return oss.str();
665}
666
667// ============================================================================
668// Tool Implementations
669// ============================================================================
670
672 Rom* rom, const resources::ArgumentParser& /*parser*/,
673 resources::OutputFormatter& formatter) {
674 formatter.BeginObject("Project Status");
675
676 // Get project manager (would come from context in full implementation)
677 auto manager_result = GetProjectManager(nullptr);
678 if (!manager_result.ok()) {
679 return manager_result.status();
680 }
681
682 ProjectManager* manager = *manager_result;
683
684 if (manager->IsInitialized()) {
685 formatter.AddField("project_path", manager->GetProjectPath());
686 formatter.AddField("initialized", true);
687
688 auto snapshots = manager->ListSnapshots();
689 formatter.AddField("snapshot_count", static_cast<int>(snapshots.size()));
690
691 formatter.BeginArray("snapshots");
692 for (const auto& name : snapshots) {
693 formatter.AddArrayItem(name);
694 }
695 formatter.EndArray();
696 } else {
697 formatter.AddField("initialized", false);
698 formatter.AddField("message",
699 "No project initialized. Use project-snapshot to create "
700 "first snapshot.");
701 }
702
703 if (rom && rom->is_loaded()) {
704 auto checksum = ProjectToolUtils::ComputeRomChecksum(rom);
705 formatter.AddField("rom_checksum",
707 formatter.AddField("rom_size", static_cast<uint64_t>(rom->size()));
708 }
709
710 formatter.EndObject();
711 return absl::OkStatus();
712}
713
715 Rom* rom, const resources::ArgumentParser& parser,
716 resources::OutputFormatter& formatter) {
717 if (!rom || !rom->is_loaded()) {
718 return absl::FailedPreconditionError("ROM not loaded");
719 }
720
721 auto name = parser.GetString("name");
722 if (!name.has_value()) {
723 return absl::InvalidArgumentError("Missing --name argument");
724 }
725
726 auto description = parser.GetString("description").value_or("");
727
728 // Get project manager
729 auto manager_result = GetProjectManager(nullptr);
730 if (!manager_result.ok()) {
731 return manager_result.status();
732 }
733 ProjectManager* manager = *manager_result;
734
735 // Initialize project if not already done
736 if (!manager->IsInitialized()) {
737 auto init_status = manager->Initialize(".");
738 if (!init_status.ok()) {
739 return init_status;
740 }
741 }
742
743 // Get edits from context (empty for now - would come from AgentContext)
744 std::vector<RomEdit> edits;
745
746 // Compute ROM checksum
747 auto checksum = ProjectToolUtils::ComputeRomChecksum(rom);
748
749 // Create snapshot
750 auto status = manager->CreateSnapshot(*name, description, edits, checksum);
751 if (!status.ok()) {
752 return status;
753 }
754
755 formatter.BeginObject("Snapshot Created");
756 formatter.AddField("name", *name);
757 formatter.AddField("description", description);
758 formatter.AddField("edit_count", static_cast<int>(edits.size()));
759 formatter.AddField("rom_checksum",
761 formatter.EndObject();
762
763 return absl::OkStatus();
764}
765
767 Rom* rom, const resources::ArgumentParser& parser,
768 resources::OutputFormatter& formatter) {
769 if (!rom || !rom->is_loaded()) {
770 return absl::FailedPreconditionError("ROM not loaded");
771 }
772
773 auto name = parser.GetString("name");
774 if (!name.has_value()) {
775 return absl::InvalidArgumentError("Missing --name argument");
776 }
777
778 auto manager_result = GetProjectManager(nullptr);
779 if (!manager_result.ok()) {
780 return manager_result.status();
781 }
782 ProjectManager* manager = *manager_result;
783
784 if (!manager->IsInitialized()) {
785 return absl::FailedPreconditionError("No project initialized");
786 }
787
788 auto status = manager->RestoreSnapshot(*name, rom);
789 if (!status.ok()) {
790 return status;
791 }
792
793 auto snapshot_result = manager->GetSnapshot(*name);
794 if (!snapshot_result.ok()) {
795 return snapshot_result.status();
796 }
797
798 const ProjectSnapshot& snapshot = *snapshot_result;
799
800 formatter.BeginObject("Snapshot Restored");
801 formatter.AddField("name", snapshot.name);
802 formatter.AddField("edits_applied", static_cast<int>(snapshot.edits.size()));
803 formatter.AddField("description", snapshot.description);
804 formatter.EndObject();
805
806 return absl::OkStatus();
807}
808
809absl::Status ProjectExportTool::Execute(Rom* /*rom*/,
810 const resources::ArgumentParser& parser,
811 resources::OutputFormatter& formatter) {
812 auto path = parser.GetString("path");
813 if (!path.has_value()) {
814 return absl::InvalidArgumentError("Missing --path argument");
815 }
816
817 bool include_rom = parser.HasFlag("include-rom");
818
819 auto manager_result = GetProjectManager(nullptr);
820 if (!manager_result.ok()) {
821 return manager_result.status();
822 }
823 ProjectManager* manager = *manager_result;
824
825 auto status = manager->ExportProject(*path, include_rom);
826 if (!status.ok()) {
827 return status;
828 }
829
830 formatter.BeginObject("Project Exported");
831 formatter.AddField("path", *path);
832 formatter.AddField("include_rom", include_rom);
833 formatter.EndObject();
834
835 return absl::OkStatus();
836}
837
838absl::Status ProjectImportTool::Execute(Rom* /*rom*/,
839 const resources::ArgumentParser& parser,
840 resources::OutputFormatter& formatter) {
841 auto path = parser.GetString("path");
842 if (!path.has_value()) {
843 return absl::InvalidArgumentError("Missing --path argument");
844 }
845
846 auto manager_result = GetProjectManager(nullptr);
847 if (!manager_result.ok()) {
848 return manager_result.status();
849 }
850 ProjectManager* manager = *manager_result;
851
852 auto status = manager->ImportProject(*path);
853 if (!status.ok()) {
854 return status;
855 }
856
857 formatter.BeginObject("Project Imported");
858 formatter.AddField("path", *path);
859 formatter.EndObject();
860
861 return absl::OkStatus();
862}
863
864absl::Status ProjectDiffTool::Execute(Rom* /*rom*/,
865 const resources::ArgumentParser& parser,
866 resources::OutputFormatter& formatter) {
867 auto snapshot1 = parser.GetString("snapshot1");
868 auto snapshot2 = parser.GetString("snapshot2");
869
870 if (!snapshot1.has_value() || !snapshot2.has_value()) {
871 return absl::InvalidArgumentError("Missing snapshot names");
872 }
873
874 auto manager_result = GetProjectManager(nullptr);
875 if (!manager_result.ok()) {
876 return manager_result.status();
877 }
878 ProjectManager* manager = *manager_result;
879
880 if (!manager->IsInitialized()) {
881 return absl::FailedPreconditionError("No project initialized");
882 }
883
884 auto diff_result = manager->DiffSnapshots(*snapshot1, *snapshot2);
885 if (!diff_result.ok()) {
886 return diff_result.status();
887 }
888
889 formatter.BeginObject("Snapshot Diff");
890 formatter.AddField("snapshot1", *snapshot1);
891 formatter.AddField("snapshot2", *snapshot2);
892 formatter.AddField("diff", *diff_result);
893 formatter.EndObject();
894
895 return absl::OkStatus();
896}
897
898} // namespace tools
899} // namespace agent
900} // namespace cli
901} // 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:49
absl::Status CheckLayoutWritable() const
Definition rom.cc:429
absl::Status WriteByte(int addr, uint8_t value)
Definition rom.cc:853
auto data() const
Definition rom.h:201
auto size() const
Definition rom.h:200
bool is_loaded() const
Definition rom.h:187
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.