yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_commands.cc
Go to the documentation of this file.
2
3#include <fstream>
4#include <sstream>
5
6#include "absl/flags/declare.h"
7#include "absl/flags/flag.h"
8#include "absl/strings/ascii.h"
9#include "absl/strings/str_format.h"
10
12
13ABSL_DECLARE_FLAG(std::string, rom);
14
15namespace yaze {
16namespace cli {
17namespace handlers {
18
19namespace {
20std::string NormalizeRange(const std::string& range) {
21 return absl::AsciiStrToLower(range);
22}
23
24bool IncludeVanilla(const std::string& range) {
25 return range == "all" || range == "vanilla";
26}
27
28bool IncludeExpanded(const std::string& range) {
29 return range == "all" || range == "expanded";
30}
31
32std::string BankLabel(editor::MessageBank bank) {
33 return editor::MessageBankToString(bank);
34}
35} // namespace
36
37// ===========================================================================
38// Existing Commands
39// ===========================================================================
40
42 Rom* rom, const resources::ArgumentParser& parser,
43 resources::OutputFormatter& formatter) {
44 auto limit = parser.GetInt("limit").value_or(50);
45 if (limit < 0) {
46 limit = 0;
47 }
48
49 auto messages =
50 editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
52 if (limit > static_cast<int>(messages.size())) {
53 limit = static_cast<int>(messages.size());
54 }
55
56 formatter.BeginObject("Message List");
57 formatter.AddField("limit", limit);
58 formatter.AddField("total_messages", static_cast<int>(messages.size()));
59 formatter.AddField("status", "success");
60
61 formatter.BeginArray("messages");
62 for (int i = 0; i < limit; ++i) {
63 const auto& msg = messages[i];
64 formatter.BeginObject();
65 formatter.AddField("id", msg.ID);
66 formatter.AddHexField("address", msg.Address, 6);
67 formatter.AddField("text", msg.ContentsParsed);
68 formatter.AddField("length", static_cast<int>(msg.Data.size()));
69 formatter.EndObject();
70 }
71 formatter.EndArray();
72 formatter.EndObject();
73
74 return absl::OkStatus();
75}
76
78 Rom* rom, const resources::ArgumentParser& parser,
79 resources::OutputFormatter& formatter) {
80 auto message_id_or = parser.GetInt("id");
81 if (!message_id_or.ok()) {
82 return message_id_or.status();
83 }
84 int message_id = message_id_or.value();
85
86 auto messages =
87 editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
89 if (message_id < 0 || message_id >= static_cast<int>(messages.size())) {
90 return absl::NotFoundError(absl::StrFormat(
91 "Message ID %d not found (max: %d)", message_id,
92 static_cast<int>(messages.size()) - 1));
93 }
94
95 const auto& msg = messages[message_id];
96
97 formatter.BeginObject("Message");
98 formatter.AddField("id", msg.ID);
99 formatter.AddHexField("address", msg.Address, 6);
100 formatter.AddField("text", msg.ContentsParsed);
101 formatter.AddField("length", static_cast<int>(msg.Data.size()));
102 formatter.EndObject();
103
104 return absl::OkStatus();
105}
106
108 Rom* rom, const resources::ArgumentParser& parser,
109 resources::OutputFormatter& formatter) {
110 auto query = parser.GetString("query").value();
111 auto limit = parser.GetInt("limit").value_or(10);
112 if (limit < 0) {
113 limit = 0;
114 }
115
116 auto messages =
117 editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
119
120 formatter.BeginObject("Message Search Results");
121 formatter.AddField("query", query);
122 formatter.AddField("limit", limit);
123 formatter.AddField("status", "success");
124
125 std::string lowered_query = absl::AsciiStrToLower(query);
126 std::vector<const editor::MessageData*> matches;
127 for (const auto& msg : messages) {
128 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
129 if (lowered_text.find(lowered_query) == std::string::npos) {
130 continue;
131 }
132 matches.push_back(&msg);
133 }
134
135 formatter.AddField("matches_found", static_cast<int>(matches.size()));
136 formatter.BeginArray("matches");
137 int match_count = 0;
138 for (const auto* msg : matches) {
139 if (limit > 0 && match_count >= limit) {
140 break;
141 }
142 formatter.BeginObject();
143 formatter.AddField("id", msg->ID);
144 formatter.AddHexField("address", msg->Address, 6);
145 formatter.AddField("text", msg->ContentsParsed);
146 formatter.EndObject();
147 match_count++;
148 }
149 formatter.EndArray();
150 formatter.EndObject();
151
152 return absl::OkStatus();
153}
154
155// ===========================================================================
156// New: Encode Command
157// ===========================================================================
158
160 Rom* rom, const resources::ArgumentParser& parser,
161 resources::OutputFormatter& formatter) {
162 auto text = parser.GetString("text").value();
163
164 auto parse_result = editor::ParseMessageToDataWithDiagnostics(text);
165 auto bytes = parse_result.bytes;
166
167 // Build hex string
168 std::string hex_str;
169 for (size_t i = 0; i < bytes.size(); ++i) {
170 if (i > 0) hex_str += " ";
171 hex_str += absl::StrFormat("%02X", bytes[i]);
172 }
173
174 formatter.BeginObject("Encoded Message");
175 formatter.AddField("input", text);
176 formatter.AddField("hex", hex_str);
177 formatter.AddField("length", static_cast<int>(bytes.size()));
178
179 // Also output as byte array for JSON consumers
180 formatter.BeginArray("bytes");
181 for (uint8_t byte : bytes) {
182 formatter.AddArrayItem(absl::StrFormat("0x%02X", byte));
183 }
184 formatter.EndArray();
185
186 // Run line width validation
187 auto warnings = editor::ValidateMessageLineWidths(text);
188 if (!warnings.empty()) {
189 formatter.BeginArray("line_width_warnings");
190 for (const auto& warning : warnings) {
191 formatter.AddArrayItem(warning);
192 }
193 formatter.EndArray();
194 }
195 if (!parse_result.warnings.empty()) {
196 formatter.BeginArray("warnings");
197 for (const auto& warning : parse_result.warnings) {
198 formatter.AddArrayItem(warning);
199 }
200 formatter.EndArray();
201 }
202 if (!parse_result.errors.empty()) {
203 formatter.BeginArray("errors");
204 for (const auto& error : parse_result.errors) {
205 formatter.AddArrayItem(error);
206 }
207 formatter.EndArray();
208 }
209
210 formatter.EndObject();
211
212 return absl::OkStatus();
213}
214
215// ===========================================================================
216// New: Decode Command
217// ===========================================================================
218
220 Rom* rom, const resources::ArgumentParser& parser,
221 resources::OutputFormatter& formatter) {
222 auto hex_input = parser.GetString("hex").value();
223
224 // Parse hex string to bytes
225 std::vector<uint8_t> bytes;
226 std::istringstream hex_stream(hex_input);
227 std::string hex_byte;
228 while (hex_stream >> hex_byte) {
229 try {
230 bytes.push_back(
231 static_cast<uint8_t>(std::stoi(hex_byte, nullptr, 16)));
232 } catch (const std::exception&) {
233 return absl::InvalidArgumentError(
234 absl::StrFormat("Invalid hex byte: '%s'", hex_byte));
235 }
236 }
237
238 // Decode bytes to text, handling commands with arguments
239 std::string decoded;
240 for (size_t i = 0; i < bytes.size(); ++i) {
241 uint8_t byte = bytes[i];
242
243 // Check for command (may consume next byte as argument)
244 auto cmd = editor::FindMatchingCommand(byte);
245 if (cmd.has_value()) {
246 if (cmd->HasArgument && i + 1 < bytes.size()) {
247 decoded += cmd->GetParamToken(bytes[++i]);
248 } else {
249 decoded += cmd->GetParamToken();
250 }
251 continue;
252 }
253
254 // Single-byte lookup for chars, specials, dictionary
255 decoded += editor::ParseTextDataByte(byte);
256 }
257
258 formatter.BeginObject("Decoded Message");
259 formatter.AddField("hex", hex_input);
260 formatter.AddField("text", decoded);
261 formatter.AddField("length", static_cast<int>(bytes.size()));
262 formatter.EndObject();
263
264 return absl::OkStatus();
265}
266
267// ===========================================================================
268// New: Import Org Command
269// ===========================================================================
270
272 Rom* rom, const resources::ArgumentParser& parser,
273 resources::OutputFormatter& formatter) {
274 auto file_path = parser.GetString("file").value();
275
276 // Read the .org file
277 std::ifstream file(file_path);
278 if (!file.is_open()) {
279 return absl::NotFoundError(
280 absl::StrFormat("Cannot open file: %s", file_path));
281 }
282
283 std::string content((std::istreambuf_iterator<char>(file)),
284 std::istreambuf_iterator<char>());
285 file.close();
286
287 auto messages = editor::ParseOrgContent(content);
288
289 formatter.BeginObject("Org Import Results");
290 formatter.AddField("file", file_path);
291 formatter.AddField("messages_parsed", static_cast<int>(messages.size()));
292
293 formatter.BeginArray("messages");
294 for (const auto& [msg_id, body] : messages) {
295 // Encode the body text to bytes with diagnostics
296 auto parse_result = editor::ParseMessageToDataWithDiagnostics(body);
297 auto bytes = parse_result.bytes;
298 auto warnings = editor::ValidateMessageLineWidths(body);
299
300 std::string hex_str;
301 for (size_t i = 0; i < bytes.size(); ++i) {
302 if (i > 0) hex_str += " ";
303 hex_str += absl::StrFormat("%02X", bytes[i]);
304 }
305
306 formatter.BeginObject();
307 formatter.AddHexField("id", msg_id, 2);
308 formatter.AddField("text", body);
309 formatter.AddField("hex", hex_str);
310 formatter.AddField("encoded_length", static_cast<int>(bytes.size()));
311 if (!warnings.empty()) {
312 formatter.BeginArray("warnings");
313 for (const auto& warning : warnings) {
314 formatter.AddArrayItem(warning);
315 }
316 formatter.EndArray();
317 }
318 if (!parse_result.warnings.empty()) {
319 formatter.BeginArray("parse_warnings");
320 for (const auto& warning : parse_result.warnings) {
321 formatter.AddArrayItem(warning);
322 }
323 formatter.EndArray();
324 }
325 if (!parse_result.errors.empty()) {
326 formatter.BeginArray("errors");
327 for (const auto& error : parse_result.errors) {
328 formatter.AddArrayItem(error);
329 }
330 formatter.EndArray();
331 }
332 formatter.EndObject();
333 }
334 formatter.EndArray();
335 formatter.EndObject();
336
337 return absl::OkStatus();
338}
339
340// ===========================================================================
341// New: Export Org Command
342// ===========================================================================
343
345 Rom* rom, const resources::ArgumentParser& parser,
346 resources::OutputFormatter& formatter) {
347 auto output_path = parser.GetString("output").value();
348
349 auto messages = editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
351
352 // Build message pairs and labels
353 std::vector<std::pair<int, std::string>> msg_pairs;
354 std::vector<std::string> labels;
355 for (const auto& msg : messages) {
356 msg_pairs.push_back({msg.ID, msg.RawString});
357 labels.push_back(absl::StrFormat("Message %02X", msg.ID));
358 }
359
360 std::string org_content = editor::ExportToOrgFormat(msg_pairs, labels);
361
362 // Write to file
363 std::ofstream file(output_path);
364 if (!file.is_open()) {
365 return absl::InternalError(
366 absl::StrFormat("Cannot write to file: %s", output_path));
367 }
368 file << org_content;
369 file.close();
370
371 formatter.BeginObject("Org Export Results");
372 formatter.AddField("output", output_path);
373 formatter.AddField("messages_exported", static_cast<int>(messages.size()));
374 formatter.AddField("status", "success");
375 formatter.EndObject();
376
377 return absl::OkStatus();
378}
379
380// ===========================================================================
381// New: Export Bundle Command
382// ===========================================================================
383
385 Rom* rom, const resources::ArgumentParser& parser,
386 resources::OutputFormatter& formatter) {
387 auto output_path = parser.GetString("output").value();
388 auto range = NormalizeRange(parser.GetString("range").value_or("all"));
389 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
390 return absl::InvalidArgumentError(
391 absl::StrFormat("Invalid range: %s", range));
392 }
393
394 std::vector<editor::MessageData> vanilla;
395 std::vector<editor::MessageData> expanded;
396
397 if (IncludeVanilla(range)) {
398 vanilla = editor::ReadAllTextData(const_cast<uint8_t*>(rom->data()),
400 }
401
402 if (IncludeExpanded(range)) {
404 const_cast<uint8_t*>(rom->data()), editor::GetExpandedTextDataStart());
405 }
406
407 auto status =
408 editor::ExportMessageBundleToJson(output_path, vanilla, expanded);
409 if (!status.ok()) {
410 return status;
411 }
412
413 formatter.BeginObject("Message Bundle Export");
414 formatter.AddField("output", output_path);
415 formatter.AddField("range", range);
416 formatter.AddField("vanilla_count", static_cast<int>(vanilla.size()));
417 formatter.AddField("expanded_count", static_cast<int>(expanded.size()));
418 formatter.AddField("status", "success");
419 formatter.EndObject();
420
421 return absl::OkStatus();
422}
423
424// ===========================================================================
425// New: Import Bundle Command
426// ===========================================================================
427
429 Rom* rom, const resources::ArgumentParser& parser,
430 resources::OutputFormatter& formatter) {
431 auto file_path = parser.GetString("file").value();
432 const bool apply = parser.HasFlag("apply");
433 const bool strict = parser.HasFlag("strict");
434 auto range = NormalizeRange(parser.GetString("range").value_or("all"));
435 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
436 return absl::InvalidArgumentError(
437 absl::StrFormat("Invalid range: %s", range));
438 }
439
440 auto entries_or = editor::LoadMessageBundleFromJson(file_path);
441 if (!entries_or.ok()) {
442 return entries_or.status();
443 }
444 auto entries = entries_or.value();
445
446 formatter.BeginObject("Message Bundle Import");
447 formatter.AddField("file", file_path);
448 formatter.AddField("range", range);
449 formatter.AddField("apply", apply);
450 formatter.AddField("strict", strict);
451 formatter.AddField("entries", static_cast<int>(entries.size()));
452
453 bool has_errors = false;
454 int parse_error_count = 0;
455 int error_count = 0;
456 int applied_updates = 0;
457 bool has_vanilla_entries = false;
458 bool has_expanded_entries = false;
459
460 struct ParsedEntry {
463 std::vector<std::string> line_warnings;
464 };
465 std::vector<ParsedEntry> parsed_entries;
466 parsed_entries.reserve(entries.size());
467
468 for (const auto& entry : entries) {
469 if ((entry.bank == editor::MessageBank::kVanilla &&
470 !IncludeVanilla(range)) ||
471 (entry.bank == editor::MessageBank::kExpanded &&
472 !IncludeExpanded(range))) {
473 continue;
474 }
475
476 ParsedEntry parsed{entry, editor::ParseMessageToDataWithDiagnostics(entry.text),
478 if (!parsed.parse.ok()) {
479 has_errors = true;
480 parse_error_count += static_cast<int>(parsed.parse.errors.size());
481 error_count += static_cast<int>(parsed.parse.errors.size());
482 }
483 if (entry.bank == editor::MessageBank::kVanilla) {
484 has_vanilla_entries = true;
485 } else {
486 has_expanded_entries = true;
487 }
488 parsed_entries.push_back(std::move(parsed));
489 }
490
491 formatter.BeginArray("messages");
492 for (const auto& parsed : parsed_entries) {
493 formatter.BeginObject();
494 formatter.AddField("id", parsed.entry.id);
495 formatter.AddField("bank", BankLabel(parsed.entry.bank));
496 formatter.AddField("text", parsed.entry.text);
497 formatter.AddField("encoded_length",
498 static_cast<int>(parsed.parse.bytes.size()));
499 if (!parsed.line_warnings.empty()) {
500 formatter.BeginArray("line_width_warnings");
501 for (const auto& warning : parsed.line_warnings) {
502 formatter.AddArrayItem(warning);
503 }
504 formatter.EndArray();
505 }
506 if (!parsed.parse.warnings.empty()) {
507 formatter.BeginArray("warnings");
508 for (const auto& warning : parsed.parse.warnings) {
509 formatter.AddArrayItem(warning);
510 }
511 formatter.EndArray();
512 }
513 if (!parsed.parse.errors.empty()) {
514 formatter.BeginArray("errors");
515 for (const auto& error : parsed.parse.errors) {
516 formatter.AddArrayItem(error);
517 }
518 formatter.EndArray();
519 }
520 formatter.EndObject();
521 }
522 formatter.EndArray();
523
524 Rom owned_rom;
525 Rom* active_rom = rom;
526
527 if (apply) {
528 if (active_rom == nullptr || !active_rom->is_loaded()) {
529 auto rom_path = parser.GetString("rom");
530 if (!rom_path.has_value() || rom_path->empty()) {
531 std::string global_rom_path = absl::GetFlag(FLAGS_rom);
532 if (!global_rom_path.empty()) {
533 rom_path = global_rom_path;
534 }
535 }
536 if (!rom_path.has_value() || rom_path->empty()) {
537 error_count++;
538 formatter.AddField("status", "error");
539 formatter.AddField("error",
540 "ROM not loaded; provide --rom when using --apply");
541 formatter.AddField("parse_error_count", parse_error_count);
542 formatter.AddField("error_count", error_count);
543 formatter.EndObject();
544 return absl::OkStatus();
545 }
546 auto load_status = owned_rom.LoadFromFile(*rom_path);
547 if (!load_status.ok()) {
548 error_count++;
549 formatter.AddField("status", "error");
550 formatter.AddField("error", std::string(load_status.message()));
551 formatter.AddField("parse_error_count", parse_error_count);
552 formatter.AddField("error_count", error_count);
553 formatter.EndObject();
554 return absl::OkStatus();
555 }
556 active_rom = &owned_rom;
557 }
558
559 if (has_errors) {
560 formatter.AddField("status", "error");
561 formatter.AddField("error",
562 "Parse errors present; no changes applied");
563 formatter.AddField("parse_error_count", parse_error_count);
564 formatter.AddField("error_count", error_count);
565 formatter.EndObject();
566 if (strict && parse_error_count > 0) {
567 formatter.EndObject();
568 formatter.Print();
569 return absl::FailedPreconditionError(
570 "Strict validation failed due to parse errors");
571 }
572 return absl::OkStatus();
573 }
574
575 if (IncludeVanilla(range) && has_vanilla_entries) {
576 auto vanilla_messages = editor::ReadAllTextData(
577 const_cast<uint8_t*>(active_rom->data()), editor::kTextData);
578 for (const auto& parsed : parsed_entries) {
579 if (parsed.entry.bank != editor::MessageBank::kVanilla) {
580 continue;
581 }
582 if (parsed.entry.id < 0 ||
583 parsed.entry.id >= static_cast<int>(vanilla_messages.size())) {
584 has_errors = true;
585 error_count++;
586 continue;
587 }
588 auto& msg = vanilla_messages[parsed.entry.id];
589 msg.RawString = parsed.entry.text;
590 msg.ContentsParsed = parsed.entry.text;
591 msg.Data = parsed.parse.bytes;
592 msg.DataParsed = parsed.parse.bytes;
593 applied_updates++;
594 }
595
596 if (!has_errors) {
597 auto status = editor::WriteAllTextData(active_rom, vanilla_messages);
598 if (!status.ok()) {
599 formatter.AddField("status", "error");
600 formatter.AddField("error", std::string(status.message()));
601 formatter.EndObject();
602 return absl::OkStatus();
603 }
604 }
605 }
606
607 if (IncludeExpanded(range) && has_expanded_entries) {
608 auto expanded_messages = editor::ReadExpandedTextData(
609 const_cast<uint8_t*>(active_rom->data()),
611 std::vector<std::string> expanded_texts;
612 expanded_texts.reserve(expanded_messages.size());
613 for (const auto& msg : expanded_messages) {
614 expanded_texts.push_back(msg.RawString);
615 }
616
617 for (const auto& parsed : parsed_entries) {
618 if (parsed.entry.bank != editor::MessageBank::kExpanded) {
619 continue;
620 }
621 if (parsed.entry.id < 0) {
622 has_errors = true;
623 error_count++;
624 continue;
625 }
626 if (parsed.entry.id >= static_cast<int>(expanded_texts.size())) {
627 expanded_texts.resize(parsed.entry.id + 1);
628 }
629 expanded_texts[parsed.entry.id] = parsed.entry.text;
630 applied_updates++;
631 }
632
633 if (!has_errors) {
634 auto status = editor::WriteExpandedTextData(
636 editor::GetExpandedTextDataEnd(), expanded_texts);
637 if (!status.ok()) {
638 formatter.AddField("status", "error");
639 formatter.AddField("error", std::string(status.message()));
640 formatter.EndObject();
641 return absl::OkStatus();
642 }
643 }
644 }
645
646 if (has_errors) {
647 formatter.AddField("status", "error");
648 formatter.AddField("error",
649 "Invalid message IDs; no changes applied");
650 } else {
651 if (active_rom->dirty()) {
652 auto save_status = active_rom->SaveToFile({.save_new = false});
653 if (!save_status.ok()) {
654 formatter.AddField("status", "error");
655 formatter.AddField("error", std::string(save_status.message()));
656 formatter.EndObject();
657 return absl::OkStatus();
658 }
659 }
660 formatter.AddField("status", "success");
661 formatter.AddField("applied_messages", applied_updates);
662 }
663 } else {
664 formatter.AddField("status", has_errors ? "error" : "success");
665 }
666
667 formatter.AddField("error_count", error_count);
668 formatter.AddField("parse_error_count", parse_error_count);
669 formatter.EndObject();
670 if (strict && parse_error_count > 0) {
671 formatter.EndObject();
672 formatter.Print();
673 return absl::FailedPreconditionError("Strict validation failed");
674 }
675 return absl::OkStatus();
676}
677
678// ===========================================================================
679// New: Message Write Command
680// ===========================================================================
681
683 Rom* rom, const resources::ArgumentParser& parser,
684 resources::OutputFormatter& formatter) {
685 auto id_or = parser.GetInt("id");
686 if (!id_or.ok()) return id_or.status();
687 int msg_id = id_or.value();
688
689 auto text = parser.GetString("text").value();
690
691 // Validate line widths first
692 auto warnings = editor::ValidateMessageLineWidths(text);
693
694 // Encode to bytes
695 auto bytes = editor::ParseMessageToData(text);
696 if (bytes.empty() && !text.empty()) {
697 return absl::InvalidArgumentError("Encoding produced no bytes");
698 }
699
700 // Read existing expanded messages to find the target
701 auto expanded = editor::ReadExpandedTextData(
702 const_cast<uint8_t*>(rom->data()), editor::GetExpandedTextDataStart());
703
704 // Build the full message list, inserting/replacing at msg_id
705 std::vector<std::string> all_texts;
706 bool replaced = false;
707 for (const auto& msg : expanded) {
708 if (msg.ID == msg_id) {
709 all_texts.push_back(text);
710 replaced = true;
711 } else {
712 all_texts.push_back(msg.RawString);
713 }
714 }
715 if (!replaced) {
716 // Append as new message
717 all_texts.push_back(text);
718 }
719
720 // Write back
721 auto status = editor::WriteExpandedTextData(
723 all_texts);
724 if (!status.ok()) return status;
725
726 formatter.BeginObject("Message Write Result");
727 formatter.AddField("id", msg_id);
728 formatter.AddField("text", text);
729 formatter.AddField("encoded_length", static_cast<int>(bytes.size()));
730 formatter.AddField("status", "success");
731 if (!warnings.empty()) {
732 formatter.BeginArray("line_width_warnings");
733 for (const auto& warning : warnings) {
734 formatter.AddArrayItem(warning);
735 }
736 formatter.EndArray();
737 }
738 formatter.EndObject();
739
740 return absl::OkStatus();
741}
742
743// ===========================================================================
744// New: Export BIN Command
745// ===========================================================================
746
748 Rom* rom, const resources::ArgumentParser& parser,
749 resources::OutputFormatter& formatter) {
750 auto output_path = parser.GetString("output").value();
751 auto range = parser.GetString("range").value_or("expanded");
752
753 int start, end_addr;
754 if (range == "expanded") {
757 } else {
758 start = editor::kTextData;
759 end_addr = editor::kTextDataEnd;
760 }
761
762 // Find the actual end of data (scan for 0xFF terminator)
763 const uint8_t* data = rom->data();
764 int data_end = start;
765 while (data_end <= end_addr && data[data_end] != 0xFF) {
766 data_end++;
767 }
768 if (data_end <= end_addr) {
769 data_end++; // Include the 0xFF terminator
770 }
771
772 int size = data_end - start;
773
774 std::ofstream file(output_path, std::ios::binary);
775 if (!file.is_open()) {
776 return absl::InternalError(
777 absl::StrFormat("Cannot write to file: %s", output_path));
778 }
779 file.write(reinterpret_cast<const char*>(data + start), size);
780 file.close();
781
782 formatter.BeginObject("BIN Export Result");
783 formatter.AddField("output", output_path);
784 formatter.AddField("range", range);
785 formatter.AddHexField("start_address", start, 6);
786 formatter.AddHexField("end_address", data_end - 1, 6);
787 formatter.AddField("size_bytes", size);
788 formatter.AddField("status", "success");
789 formatter.EndObject();
790
791 return absl::OkStatus();
792}
793
794// ===========================================================================
795// New: Export ASM Command
796// ===========================================================================
797
799 Rom* rom, const resources::ArgumentParser& parser,
800 resources::OutputFormatter& formatter) {
801 auto output_path = parser.GetString("output").value();
802 auto range = parser.GetString("range").value_or("expanded");
803
804 int start;
805 uint32_t snes_addr;
806 if (range == "expanded") {
808 snes_addr = 0x2F8000;
809 } else {
810 start = editor::kTextData;
811 snes_addr = 0x1C0000;
812 }
813
814 // Read messages from the specified region
815 auto messages = editor::ReadAllTextData(
816 const_cast<uint8_t*>(rom->data()), start);
817
818 std::ofstream file(output_path);
819 if (!file.is_open()) {
820 return absl::InternalError(
821 absl::StrFormat("Cannot write to file: %s", output_path));
822 }
823
824 // Write ASM header
825 file << "; Auto-generated message data\n";
826 file << absl::StrFormat("; Source: %s region\n", range);
827 file << absl::StrFormat("; Messages: %d\n\n", messages.size());
828 file << absl::StrFormat("org $%06X\n\n", snes_addr);
829
830 // Write each message as db directives
831 for (const auto& msg : messages) {
832 file << absl::StrFormat("; Message $%02X: %s\n", msg.ID,
833 msg.ContentsParsed.substr(0, 60));
834 file << "db ";
835 for (size_t i = 0; i < msg.Data.size(); ++i) {
836 if (i > 0) file << ", ";
837 file << absl::StrFormat("$%02X", msg.Data[i]);
838 }
839 file << ", $7F ; terminator\n\n";
840 }
841
842 // End-of-region marker
843 file << "db $FF ; end of message data\n";
844 file.close();
845
846 formatter.BeginObject("ASM Export Result");
847 formatter.AddField("output", output_path);
848 formatter.AddField("range", range);
849 formatter.AddField("messages_exported", static_cast<int>(messages.size()));
850 formatter.AddField("status", "success");
851 formatter.EndObject();
852
853 return absl::OkStatus();
854}
855
856} // namespace handlers
857} // namespace cli
858} // 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:28
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:155
absl::Status SaveToFile(const SaveSettings &settings)
Definition rom.cc:291
auto data() const
Definition rom.h:139
bool dirty() const
Definition rom.h:133
bool is_loaded() const
Definition rom.h:132
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::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::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::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.
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.
absl::StatusOr< int > GetInt(const std::string &name) const
Parse an integer argument (supports hex with 0x prefix)
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.
void AddHexField(const std::string &key, uint64_t value, int width=2)
Add a hex-formatted field.
void Print() const
Print the formatted output to stdout.
ABSL_DECLARE_FLAG(std::string, rom)
int GetExpandedTextDataStart()
std::string ParseTextDataByte(uint8_t value)
absl::Status WriteAllTextData(Rom *rom, const std::vector< MessageData > &messages)
constexpr int kTextData
std::string MessageBankToString(MessageBank bank)
absl::Status WriteExpandedTextData(Rom *rom, int start, int end, const std::vector< std::string > &messages)
absl::StatusOr< std::vector< MessageBundleEntry > > LoadMessageBundleFromJson(const std::string &path)
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos, int max_pos)
std::vector< uint8_t > ParseMessageToData(std::string str)
absl::Status ExportMessageBundleToJson(const std::string &path, const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
std::string ExportToOrgFormat(const std::vector< std::pair< int, std::string > > &messages, const std::vector< std::string > &labels)
std::vector< MessageData > ReadExpandedTextData(uint8_t *rom, int pos)
std::optional< TextElement > FindMatchingCommand(uint8_t b)
MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str)
int GetExpandedTextDataEnd()
std::vector< std::string > ValidateMessageLineWidths(const std::string &message)
std::vector< std::pair< int, std::string > > ParseOrgContent(const std::string &content)
constexpr int kTextDataEnd