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"
21 return absl::AsciiStrToLower(range);
25 return range ==
"all" || range ==
"vanilla";
29 return range ==
"all" || range ==
"expanded";
44 auto limit = parser.
GetInt(
"limit").value_or(50);
52 if (limit >
static_cast<int>(messages.size())) {
53 limit =
static_cast<int>(messages.size());
58 formatter.
AddField(
"total_messages",
static_cast<int>(messages.size()));
59 formatter.
AddField(
"status",
"success");
62 for (
int i = 0; i < limit; ++i) {
63 const auto& msg = messages[i];
67 formatter.
AddField(
"text", msg.ContentsParsed);
68 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
74 return absl::OkStatus();
80 auto message_id_or = parser.
GetInt(
"id");
81 if (!message_id_or.ok()) {
82 return message_id_or.status();
84 int message_id = message_id_or.value();
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));
95 const auto& msg = messages[message_id];
100 formatter.
AddField(
"text", msg.ContentsParsed);
101 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
104 return absl::OkStatus();
110 auto query = parser.
GetString(
"query").value();
111 auto limit = parser.
GetInt(
"limit").value_or(10);
123 formatter.
AddField(
"status",
"success");
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) {
132 matches.push_back(&msg);
135 formatter.
AddField(
"matches_found",
static_cast<int>(matches.size()));
138 for (
const auto* msg : matches) {
139 if (limit > 0 && match_count >= limit) {
145 formatter.
AddField(
"text", msg->ContentsParsed);
152 return absl::OkStatus();
162 auto text = parser.
GetString(
"text").value();
165 auto bytes = parse_result.bytes;
169 for (
size_t i = 0; i < bytes.size(); ++i) {
170 if (i > 0) hex_str +=
" ";
171 hex_str += absl::StrFormat(
"%02X", bytes[i]);
177 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
181 for (uint8_t
byte : bytes) {
182 formatter.
AddArrayItem(absl::StrFormat(
"0x%02X",
byte));
188 if (!warnings.empty()) {
190 for (
const auto& warning : warnings) {
195 if (!parse_result.warnings.empty()) {
197 for (
const auto& warning : parse_result.warnings) {
202 if (!parse_result.errors.empty()) {
204 for (
const auto& error : parse_result.errors) {
212 return absl::OkStatus();
222 auto hex_input = parser.
GetString(
"hex").value();
225 std::vector<uint8_t> bytes;
226 std::istringstream hex_stream(hex_input);
227 std::string hex_byte;
228 while (hex_stream >> hex_byte) {
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));
240 for (
size_t i = 0; i < bytes.size(); ++i) {
241 uint8_t
byte = bytes[i];
245 if (cmd.has_value()) {
246 if (cmd->HasArgument && i + 1 < bytes.size()) {
247 decoded += cmd->GetParamToken(bytes[++i]);
249 decoded += cmd->GetParamToken();
259 formatter.
AddField(
"hex", hex_input);
260 formatter.
AddField(
"text", decoded);
261 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
264 return absl::OkStatus();
274 auto file_path = parser.
GetString(
"file").value();
277 std::ifstream file(file_path);
278 if (!file.is_open()) {
279 return absl::NotFoundError(
280 absl::StrFormat(
"Cannot open file: %s", file_path));
283 std::string content((std::istreambuf_iterator<char>(file)),
284 std::istreambuf_iterator<char>());
290 formatter.
AddField(
"file", file_path);
291 formatter.
AddField(
"messages_parsed",
static_cast<int>(messages.size()));
294 for (
const auto& [msg_id, body] : messages) {
297 auto bytes = parse_result.bytes;
301 for (
size_t i = 0; i < bytes.size(); ++i) {
302 if (i > 0) hex_str +=
" ";
303 hex_str += absl::StrFormat(
"%02X", bytes[i]);
310 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
311 if (!warnings.empty()) {
313 for (
const auto& warning : warnings) {
318 if (!parse_result.warnings.empty()) {
320 for (
const auto& warning : parse_result.warnings) {
325 if (!parse_result.errors.empty()) {
327 for (
const auto& error : parse_result.errors) {
337 return absl::OkStatus();
347 auto output_path = parser.
GetString(
"output").value();
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));
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));
372 formatter.
AddField(
"output", output_path);
373 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
374 formatter.
AddField(
"status",
"success");
377 return absl::OkStatus();
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));
394 std::vector<editor::MessageData> vanilla;
395 std::vector<editor::MessageData> expanded;
397 if (IncludeVanilla(range)) {
402 if (IncludeExpanded(range)) {
414 formatter.
AddField(
"output", output_path);
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");
421 return absl::OkStatus();
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));
441 if (!entries_or.ok()) {
442 return entries_or.status();
444 auto entries = entries_or.value();
447 formatter.
AddField(
"file", file_path);
450 formatter.
AddField(
"strict", strict);
451 formatter.
AddField(
"entries",
static_cast<int>(entries.size()));
453 bool has_errors =
false;
454 int parse_error_count = 0;
456 int applied_updates = 0;
457 bool has_vanilla_entries =
false;
458 bool has_expanded_entries =
false;
463 std::vector<std::string> line_warnings;
465 std::vector<ParsedEntry> parsed_entries;
466 parsed_entries.reserve(entries.size());
468 for (
const auto& entry : entries) {
470 !IncludeVanilla(range)) ||
472 !IncludeExpanded(range))) {
478 if (!parsed.parse.ok()) {
480 parse_error_count +=
static_cast<int>(parsed.parse.errors.size());
481 error_count +=
static_cast<int>(parsed.parse.errors.size());
484 has_vanilla_entries =
true;
486 has_expanded_entries =
true;
488 parsed_entries.push_back(std::move(parsed));
492 for (
const auto& parsed : parsed_entries) {
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()) {
501 for (
const auto& warning : parsed.line_warnings) {
506 if (!parsed.parse.warnings.empty()) {
508 for (
const auto& warning : parsed.parse.warnings) {
513 if (!parsed.parse.errors.empty()) {
515 for (
const auto& error : parsed.parse.errors) {
525 Rom* active_rom = rom;
528 if (active_rom ==
nullptr || !active_rom->
is_loaded()) {
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;
536 if (!rom_path.has_value() || rom_path->empty()) {
538 formatter.
AddField(
"status",
"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);
544 return absl::OkStatus();
547 if (!load_status.ok()) {
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);
554 return absl::OkStatus();
556 active_rom = &owned_rom;
560 formatter.
AddField(
"status",
"error");
562 "Parse errors present; no changes applied");
563 formatter.
AddField(
"parse_error_count", parse_error_count);
564 formatter.
AddField(
"error_count", error_count);
566 if (strict && parse_error_count > 0) {
569 return absl::FailedPreconditionError(
570 "Strict validation failed due to parse errors");
572 return absl::OkStatus();
575 if (IncludeVanilla(range) && has_vanilla_entries) {
578 for (
const auto& parsed : parsed_entries) {
582 if (parsed.entry.id < 0 ||
583 parsed.entry.id >=
static_cast<int>(vanilla_messages.size())) {
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;
599 formatter.
AddField(
"status",
"error");
600 formatter.
AddField(
"error", std::string(status.message()));
602 return absl::OkStatus();
607 if (IncludeExpanded(range) && has_expanded_entries) {
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);
617 for (
const auto& parsed : parsed_entries) {
621 if (parsed.entry.id < 0) {
626 if (parsed.entry.id >=
static_cast<int>(expanded_texts.size())) {
627 expanded_texts.resize(parsed.entry.id + 1);
629 expanded_texts[parsed.entry.id] = parsed.entry.text;
638 formatter.
AddField(
"status",
"error");
639 formatter.
AddField(
"error", std::string(status.message()));
641 return absl::OkStatus();
647 formatter.
AddField(
"status",
"error");
649 "Invalid message IDs; no changes applied");
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()));
657 return absl::OkStatus();
660 formatter.
AddField(
"status",
"success");
661 formatter.
AddField(
"applied_messages", applied_updates);
664 formatter.
AddField(
"status", has_errors ?
"error" :
"success");
667 formatter.
AddField(
"error_count", error_count);
668 formatter.
AddField(
"parse_error_count", parse_error_count);
670 if (strict && parse_error_count > 0) {
673 return absl::FailedPreconditionError(
"Strict validation failed");
675 return absl::OkStatus();
685 auto id_or = parser.
GetInt(
"id");
686 if (!id_or.ok())
return id_or.status();
687 int msg_id = id_or.value();
689 auto text = parser.
GetString(
"text").value();
696 if (bytes.empty() && !text.empty()) {
697 return absl::InvalidArgumentError(
"Encoding produced no bytes");
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);
712 all_texts.push_back(msg.RawString);
717 all_texts.push_back(text);
724 if (!status.ok())
return status;
729 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
730 formatter.
AddField(
"status",
"success");
731 if (!warnings.empty()) {
733 for (
const auto& warning : warnings) {
740 return absl::OkStatus();
750 auto output_path = parser.
GetString(
"output").value();
751 auto range = parser.
GetString(
"range").value_or(
"expanded");
754 if (range ==
"expanded") {
763 const uint8_t* data = rom->
data();
764 int data_end = start;
765 while (data_end <= end_addr && data[data_end] != 0xFF) {
768 if (data_end <= end_addr) {
772 int size = data_end - start;
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));
779 file.write(
reinterpret_cast<const char*
>(data + start), size);
783 formatter.
AddField(
"output", output_path);
786 formatter.
AddHexField(
"end_address", data_end - 1, 6);
787 formatter.
AddField(
"size_bytes", size);
788 formatter.
AddField(
"status",
"success");
791 return absl::OkStatus();
801 auto output_path = parser.
GetString(
"output").value();
802 auto range = parser.
GetString(
"range").value_or(
"expanded");
806 if (range ==
"expanded") {
808 snes_addr = 0x2F8000;
811 snes_addr = 0x1C0000;
816 const_cast<uint8_t*
>(rom->
data()), start);
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));
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);
831 for (
const auto& msg : messages) {
832 file << absl::StrFormat(
"; Message $%02X: %s\n", msg.ID,
833 msg.ContentsParsed.substr(0, 60));
835 for (
size_t i = 0; i < msg.Data.size(); ++i) {
836 if (i > 0) file <<
", ";
837 file << absl::StrFormat(
"$%02X", msg.Data[i]);
839 file <<
", $7F ; terminator\n\n";
843 file <<
"db $FF ; end of message data\n";
847 formatter.
AddField(
"output", output_path);
849 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
850 formatter.
AddField(
"status",
"success");
853 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 LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
absl::Status SaveToFile(const SaveSettings &settings)
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)
ABSL_DECLARE_FLAG(std::string, rom)
std::string BankLabel(editor::MessageBank bank)
bool IncludeVanilla(const std::string &range)
bool IncludeExpanded(const std::string &range)
std::string NormalizeRange(const std::string &range)
int GetExpandedTextDataStart()
std::string ParseTextDataByte(uint8_t value)
absl::Status WriteAllTextData(Rom *rom, const std::vector< MessageData > &messages)
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