16#include "absl/strings/str_format.h"
17#include "nlohmann/json.hpp"
24using json = nlohmann::json;
25namespace fs = std::filesystem;
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};
47constexpr uint32_t
RightRotate(uint32_t value, uint32_t count) {
48 return (value >> count) | (value << (32 - count));
53 uint32_t a, b, c, d, e, f, g, h;
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]));
63 for (
int i = 16; i < 64; ++i) {
68 w[i] = w[i - 16] + s0 + w[i - 7] + s1;
82 for (
int i = 0; i < 64; ++i) {
84 uint32_t ch = (e & f) ^ ((~e) & g);
85 uint32_t temp1 = h + S1 + ch +
kSHA256_K[i] + w[i];
87 uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
88 uint32_t temp2 = S0 + maj;
114 uint32_t state[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
115 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
117 size_t total_length = length;
118 const uint8_t* ptr = data;
121 while (length >= 64) {
129 std::memset(block, 0,
sizeof(block));
130 std::memcpy(block, ptr, length);
133 block[length] = 0x80;
138 std::memset(block, 0,
sizeof(block));
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));
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]);
170 std::array<uint8_t, 32> result;
176 return ComputeSHA256Internal(rom->
data(), rom->
size());
181 return ComputeSHA256Internal(data, length);
185 const std::array<uint8_t, 32>& checksum) {
186 std::ostringstream oss;
187 for (uint8_t
byte : checksum) {
188 oss << absl::StrFormat(
"%02x",
byte);
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");
202absl::StatusOr<std::chrono::system_clock::time_point>
206 std::istringstream ss(timestamp);
207 ss >> std::get_time(&tm,
"%Y-%m-%dT%H:%M:%SZ");
210 return absl::InvalidArgumentError(
211 absl::StrFormat(
"Invalid timestamp format: %s", timestamp));
214 auto time_t = std::mktime(&tm);
215 return std::chrono::system_clock::from_time_t(time_t);
224 std::ofstream file(filepath, std::ios::binary);
226 return absl::InternalError(
227 absl::StrFormat(
"Failed to open file for writing: %s", filepath));
235 file.write(
reinterpret_cast<const char*
>(&header),
sizeof(header));
238 for (
const auto& edit :
edits) {
240 ser_edit.
address = edit.address;
241 ser_edit.
length =
static_cast<uint32_t
>(edit.new_value.size());
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());
251 json metadata_json = {
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());
263 return absl::OkStatus();
265 }
catch (
const std::exception& e) {
266 return absl::InternalError(
267 absl::StrFormat(
"Failed to save snapshot: %s", e.what()));
272 const std::string& filepath) {
274 std::ifstream file(filepath, std::ios::binary);
276 return absl::NotFoundError(
277 absl::StrFormat(
"Failed to open file: %s", filepath));
282 file.read(
reinterpret_cast<char*
>(&header),
sizeof(header));
285 return absl::InvalidArgumentError(
286 absl::StrFormat(
"Invalid file format (magic: 0x%08X)", header.
magic));
290 return absl::InvalidArgumentError(
291 absl::StrFormat(
"Unsupported version: %u", header.
version));
298 for (uint32_t i = 0; i < header.
edit_count; ++i) {
300 file.read(
reinterpret_cast<char*
>(&ser_edit),
sizeof(ser_edit));
307 file.read(
reinterpret_cast<char*
>(edit.
old_value.data()),
309 file.read(
reinterpret_cast<char*
>(edit.
new_value.data()),
312 edit.
timestamp = std::chrono::system_clock::now();
315 snapshot.
edits.push_back(edit);
319 uint32_t metadata_length = 0;
320 file.read(
reinterpret_cast<char*
>(&metadata_length),
321 sizeof(metadata_length));
323 std::string metadata_str(metadata_length,
'\0');
324 file.read(&metadata_str[0], metadata_length);
326 json metadata_json = json::parse(metadata_str);
327 snapshot.
name = metadata_json[
"name"];
328 snapshot.
description = metadata_json[
"description"];
330 auto timestamp_result =
332 if (timestamp_result.ok()) {
333 snapshot.
created = *timestamp_result;
335 snapshot.
created = std::chrono::system_clock::now();
338 if (metadata_json.contains(
"metadata")) {
339 snapshot.
metadata = metadata_json[
"metadata"].get<
340 std::map<std::string, std::string>>();
345 }
catch (
const std::exception& e) {
346 return absl::InternalError(
347 absl::StrFormat(
"Failed to load snapshot: %s", e.what()));
357 project_path_ = (fs::path(base_path) /
".yaze-project").
string();
364 std::string metadata_path =
366 if (!fs::exists(metadata_path)) {
367 json metadata = {{
"version",
"1.0"},
369 std::chrono::system_clock::now())}};
371 std::ofstream file(metadata_path);
372 file << metadata.dump(2);
377 }
catch (
const std::exception& e) {
378 return absl::InternalError(
379 absl::StrFormat(
"Failed to initialize project: %s", e.what()));
385 const std::vector<RomEdit>& edits,
386 const std::array<uint8_t, 32>& rom_checksum) {
388 return absl::InvalidArgumentError(
"Snapshot name cannot be empty");
392 return absl::AlreadyExistsError(
393 absl::StrFormat(
"Snapshot already exists: %s",
name));
399 snapshot.
created = std::chrono::system_clock::now();
400 snapshot.
edits = edits;
418 return absl::NotFoundError(
419 absl::StrFormat(
"Snapshot not found: %s",
name));
423 return absl::FailedPreconditionError(
"ROM not loaded");
431 return absl::FailedPreconditionError(
432 "ROM checksum mismatch - snapshot is for a different ROM");
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]);
442 return absl::OkStatus();
446 std::vector<std::string> names;
448 names.push_back(
name);
450 std::sort(names.begin(), names.end());
455 const std::string&
name)
const {
458 return absl::NotFoundError(
459 absl::StrFormat(
"Snapshot not found: %s",
name));
467 return absl::NotFoundError(
468 absl::StrFormat(
"Snapshot not found: %s",
name));
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()));
487 return absl::UnimplementedError(
"Project export not yet implemented");
492 return absl::UnimplementedError(
"Project import not yet implemented");
496 const std::string& snapshot1,
const std::string& snapshot2)
const {
498 if (!snap1_result.ok()) {
499 return snap1_result.status();
503 if (!snap2_result.ok()) {
504 return snap2_result.status();
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());
517 std::map<uint32_t, const RomEdit*> snap1_map;
518 std::map<uint32_t, const RomEdit*> snap2_map;
520 for (
const auto& edit : snap1.
edits) {
521 snap1_map[edit.address] = &edit;
523 for (
const auto& edit : snap2.
edits) {
524 snap2_map[edit.address] = &edit;
528 std::set<uint32_t> all_addresses;
529 for (
const auto& [addr, _] : snap1_map) {
530 all_addresses.insert(addr);
532 for (
const auto& [addr, _] : snap2_map) {
533 all_addresses.insert(addr);
536 int added = 0, removed = 0, modified = 0;
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();
542 if (in_snap1 && !in_snap2) {
543 diff << absl::StrFormat(
"- Removed at 0x%06X\n", addr);
545 }
else if (!in_snap1 && in_snap2) {
546 diff << absl::StrFormat(
"+ Added at 0x%06X\n", addr);
548 }
else if (in_snap1 && in_snap2) {
549 const auto& edit1 = *snap1_map[addr];
550 const auto& edit2 = *snap2_map[addr];
552 if (edit1.new_value != edit2.new_value) {
553 diff << absl::StrFormat(
"~ Modified at 0x%06X\n", addr);
559 diff << absl::StrFormat(
"\nSummary: +%d -%d ~%d\n", added, removed,
568 return absl::OkStatus();
572 if (entry.path().extension() ==
".edits") {
574 if (snapshot_result.ok()) {
581 return absl::OkStatus();
583 }
catch (
const std::exception& e) {
584 return absl::InternalError(
585 absl::StrFormat(
"Failed to load snapshots: %s", e.what()));
591 std::string metadata_path =
597 std::chrono::system_clock::now())},
598 {
"snapshots", json::array()}};
601 metadata[
"snapshots"].push_back({
604 {
"edit_count", snapshot.edits.size()},
608 std::ofstream file(metadata_path);
609 file << metadata.dump(2);
611 return absl::OkStatus();
613 }
catch (
const std::exception& e) {
614 return absl::InternalError(
615 absl::StrFormat(
"Failed to save metadata: %s", e.what()));
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(),
'\\',
'_');
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(),
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",
677 if (!manager_result.ok()) {
678 return manager_result.status();
685 formatter.
AddField(
"initialized",
true);
688 formatter.
AddField(
"snapshot_count",
static_cast<int>(snapshots.size()));
691 for (
const auto&
name : snapshots) {
696 formatter.
AddField(
"initialized",
false);
698 "No project initialized. Use project-snapshot to create "
706 formatter.
AddField(
"rom_size",
static_cast<uint64_t
>(rom->
size()));
710 return absl::OkStatus();
717 return absl::FailedPreconditionError(
"ROM not loaded");
721 if (!
name.has_value()) {
722 return absl::InvalidArgumentError(
"Missing --name argument");
729 if (!manager_result.ok()) {
730 return manager_result.status();
737 if (!init_status.ok()) {
743 std::vector<RomEdit> edits;
757 formatter.
AddField(
"edit_count",
static_cast<int>(edits.size()));
761 return absl::OkStatus();
768 return absl::FailedPreconditionError(
"ROM not loaded");
772 if (!
name.has_value()) {
773 return absl::InvalidArgumentError(
"Missing --name argument");
777 if (!manager_result.ok()) {
778 return manager_result.status();
783 return absl::FailedPreconditionError(
"No project initialized");
792 if (!snapshot_result.ok()) {
793 return snapshot_result.status();
800 formatter.
AddField(
"edits_applied",
static_cast<int>(snapshot.
edits.size()));
804 return absl::OkStatus();
811 if (!path.has_value()) {
812 return absl::InvalidArgumentError(
"Missing --path argument");
815 bool include_rom = parser.
HasFlag(
"include-rom");
818 if (!manager_result.ok()) {
819 return manager_result.status();
830 formatter.
AddField(
"include_rom", include_rom);
833 return absl::OkStatus();
840 if (!path.has_value()) {
841 return absl::InvalidArgumentError(
"Missing --path argument");
845 if (!manager_result.ok()) {
846 return manager_result.status();
859 return absl::OkStatus();
865 auto snapshot1 = parser.
GetString(
"snapshot1");
866 auto snapshot2 = parser.
GetString(
"snapshot2");
868 if (!snapshot1.has_value() || !snapshot2.has_value()) {
869 return absl::InvalidArgumentError(
"Missing snapshot names");
873 if (!manager_result.ok()) {
874 return manager_result.status();
879 return absl::FailedPreconditionError(
"No project initialized");
882 auto diff_result = manager->
DiffSnapshots(*snapshot1, *snapshot2);
883 if (!diff_result.ok()) {
884 return diff_result.status();
888 formatter.
AddField(
"snapshot1", *snapshot1);
889 formatter.
AddField(
"snapshot2", *snapshot2);
890 formatter.
AddField(
"diff", *diff_result);
893 return absl::OkStatus();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::Status WriteByte(int addr, uint8_t value)
Agent context for maintaining state across tool calls.
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.
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