12#include "absl/flags/declare.h"
13#include "absl/flags/flag.h"
14#include "absl/strings/ascii.h"
15#include "absl/strings/match.h"
16#include "absl/strings/str_cat.h"
17#include "absl/strings/str_format.h"
32 return absl::AsciiStrToLower(range);
36 return range ==
"all" || range ==
"vanilla";
40 return range ==
"all" || range ==
"expanded";
49 if (start < 0 ||
static_cast<size_t>(start) >= rom.
size()) {
53 static_cast<int>(rom.
size()) - 1);
60 if (start < 0 || end < start ||
static_cast<size_t>(start) >= rom.
size()) {
63 end = std::min(end,
static_cast<int>(rom.
size()) - 1);
72 int message_limit = -1;
80 : provider_(provider), previous_(provider.hack_manifest()) {}
95 const std::string& path, absl::string_view label) {
97 return absl::FailedPreconditionError(
98 absl::StrFormat(
"%s path is empty", label));
102 auto canonical_path = std::filesystem::canonical(path, ec);
104 return absl::FailedPreconditionError(absl::StrFormat(
105 "Cannot resolve %s path '%s': %s", label, path, ec.message()));
107 return canonical_path;
112 absl::StrFormat(
"address 0x%06X is %s", conflict.
address,
114 if (!conflict.module.empty()) {
115 absl::StrAppend(&result,
" (Module: ", conflict.module,
")");
121 const uint8_t bank =
static_cast<uint8_t
>((address >> 16) & 0xFFu);
122 if (bank == 0x7E || bank == 0x7F || (address & 0xFFFFu) < 0x8000u) {
130 auto project_path = parser.
GetString(
"project");
131 if (!project_path.has_value() || project_path->empty()) {
132 return absl::FailedPreconditionError(
133 "Expanded message mutation requires explicit --project so Yaze can "
134 "verify ROM ownership and write policy");
137 return absl::FailedPreconditionError(
"ROM is not loaded");
142 absl::Status open_status;
151 }
catch (
const std::exception& error) {
152 return absl::InvalidArgumentError(absl::StrFormat(
153 "Cannot load project '%s': %s", *project_path, error.what()));
155 return absl::InvalidArgumentError(absl::StrFormat(
156 "Cannot load project '%s': unknown project parse error",
160 if (!open_status.ok()) {
161 return absl::Status(open_status.code(),
162 absl::StrFormat(
"Cannot load project '%s': %s",
163 *project_path, open_status.message()));
167 if (!active_rom_path_or.ok()) {
168 return active_rom_path_or.status();
173 if (!project_rom_path_or.ok()) {
174 return project_rom_path_or.status();
176 if (*active_rom_path_or != *project_rom_path_or) {
177 return absl::FailedPreconditionError(absl::StrFormat(
178 "Project ROM mismatch: active ROM is '%s' but --project binds to '%s'",
179 active_rom_path_or->string(), project_rom_path_or->string()));
183 if (!manifest.loaded()) {
184 return absl::FailedPreconditionError(absl::StrFormat(
185 "Project '%s' has no loaded hack manifest; configure a valid "
186 "hack_manifest_file before mutating expanded messages",
190 const auto& layout = manifest.message_layout();
191 if (layout.data_start == 0 || layout.data_end == 0 ||
192 layout.data_end < layout.data_start ||
195 return absl::FailedPreconditionError(
196 "Hack manifest does not define a valid LoROM expanded message region");
199 const uint32_t start =
SnesToPc(layout.data_start);
200 const uint32_t end =
SnesToPc(layout.data_end);
202 end >
static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
204 return absl::OutOfRangeError(absl::StrFormat(
205 "Configured expanded message region [0x%06X, 0x%06X] is outside the "
206 "active ROM (size=0x%zX)",
207 start, end, rom.
size()));
209 context.
start =
static_cast<int>(start);
210 context.
end =
static_cast<int>(end);
216 const int region_capacity = context.
end - context.
start + 1;
217 const int capacity_message_limit = std::max(0, region_capacity - 1);
219 if (layout.expanded_count > 0) {
221 std::min(layout.expanded_count, capacity_message_limit);
223 if (layout.last_expanded_id >= layout.first_expanded_id &&
224 (layout.first_expanded_id != 0 || layout.last_expanded_id != 0)) {
225 const int declared_limit =
226 static_cast<int>(layout.last_expanded_id - layout.first_expanded_id) +
231 const std::string lowered_hack_name =
232 absl::AsciiStrToLower(manifest.hack_name());
235 absl::StrContains(lowered_hack_name,
"oracle of secrets")) {
236 return absl::PermissionDeniedError(
237 "Oracle of Secrets expanded messages are owned by ASM. No message "
238 "bytes were written; edit Core/message.asm, rebuild Oracle of Secrets, "
239 "and reopen the rebuilt ROM before retrying.");
242 const auto conflicts =
243 manifest.AnalyzePcWriteRanges({{start,
static_cast<uint32_t
>(end + 1u)}});
244 if (!conflicts.empty()) {
248 return absl::PermissionDeniedError(absl::StrFormat(
249 "Expanded message mutation blocked by hack manifest: %s", detail));
254 "Expanded message region conflicts with hack manifest: %s", detail);
264 return absl::InvalidArgumentError(
265 absl::StrFormat(
"Invalid expanded message ID: %d",
id));
268 return absl::OutOfRangeError(absl::StrFormat(
269 "Expanded message ID %d is outside the manifest range (count=%d)",
id,
272 return absl::OkStatus();
278 message_count >
static_cast<size_t>(context.
message_limit)) {
279 return absl::FailedPreconditionError(absl::StrFormat(
280 "Expanded message bank contains %zu messages, exceeding the manifest "
281 "limit of %d; no changes were applied",
284 return absl::OkStatus();
295 auto limit = parser.
GetInt(
"limit").value_or(50);
302 if (limit >
static_cast<int>(messages.size())) {
303 limit =
static_cast<int>(messages.size());
308 formatter.
AddField(
"total_messages",
static_cast<int>(messages.size()));
309 formatter.
AddField(
"status",
"success");
312 for (
int i = 0; i < limit; ++i) {
313 const auto& msg = messages[i];
317 formatter.
AddField(
"text", msg.ContentsParsed);
318 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
324 return absl::OkStatus();
330 auto message_id_or = parser.
GetInt(
"id");
331 if (!message_id_or.ok()) {
332 return message_id_or.status();
334 int message_id = message_id_or.value();
338 if (message_id < 0 || message_id >=
static_cast<int>(messages.size())) {
339 return absl::NotFoundError(
340 absl::StrFormat(
"Message ID %d not found (max: %d)", message_id,
341 static_cast<int>(messages.size()) - 1));
344 const auto& msg = messages[message_id];
349 formatter.
AddField(
"text", msg.ContentsParsed);
350 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
353 return absl::OkStatus();
359 auto query = parser.
GetString(
"query").value();
360 auto limit = parser.
GetInt(
"limit").value_or(10);
371 formatter.
AddField(
"status",
"success");
373 std::string lowered_query = absl::AsciiStrToLower(query);
374 std::vector<const editor::MessageData*> matches;
375 for (
const auto& msg : messages) {
376 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
377 if (lowered_text.find(lowered_query) == std::string::npos) {
380 matches.push_back(&msg);
383 formatter.
AddField(
"matches_found",
static_cast<int>(matches.size()));
386 for (
const auto* msg : matches) {
387 if (limit > 0 && match_count >= limit) {
393 formatter.
AddField(
"text", msg->ContentsParsed);
400 return absl::OkStatus();
410 auto text = parser.
GetString(
"text").value();
413 auto bytes = parse_result.bytes;
417 for (
size_t i = 0; i < bytes.size(); ++i) {
420 hex_str += absl::StrFormat(
"%02X", bytes[i]);
426 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
430 for (uint8_t
byte : bytes) {
431 formatter.
AddArrayItem(absl::StrFormat(
"0x%02X",
byte));
437 if (!warnings.empty()) {
439 for (
const auto& warning : warnings) {
444 if (!parse_result.warnings.empty()) {
446 for (
const auto& warning : parse_result.warnings) {
451 if (!parse_result.errors.empty()) {
453 for (
const auto& error : parse_result.errors) {
461 return absl::OkStatus();
471 auto hex_input = parser.
GetString(
"hex").value();
474 std::vector<uint8_t> bytes;
475 std::istringstream hex_stream(hex_input);
476 std::string hex_byte;
477 while (hex_stream >> hex_byte) {
479 bytes.push_back(
static_cast<uint8_t
>(std::stoi(hex_byte,
nullptr, 16)));
480 }
catch (
const std::exception&) {
481 return absl::InvalidArgumentError(
482 absl::StrFormat(
"Invalid hex byte: '%s'", hex_byte));
488 for (
size_t i = 0; i < bytes.size(); ++i) {
489 uint8_t
byte = bytes[i];
493 if (cmd.has_value()) {
494 if (cmd->HasArgument && i + 1 < bytes.size()) {
495 decoded += cmd->GetParamToken(bytes[++i]);
497 decoded += cmd->GetParamToken();
507 formatter.
AddField(
"hex", hex_input);
508 formatter.
AddField(
"text", decoded);
509 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
512 return absl::OkStatus();
522 auto file_path = parser.
GetString(
"file").value();
525 std::ifstream file(file_path);
526 if (!file.is_open()) {
527 return absl::NotFoundError(
528 absl::StrFormat(
"Cannot open file: %s", file_path));
531 std::string content((std::istreambuf_iterator<char>(file)),
532 std::istreambuf_iterator<char>());
538 formatter.
AddField(
"file", file_path);
539 formatter.
AddField(
"messages_parsed",
static_cast<int>(messages.size()));
542 for (
const auto& [msg_id, body] : messages) {
545 auto bytes = parse_result.bytes;
549 for (
size_t i = 0; i < bytes.size(); ++i) {
552 hex_str += absl::StrFormat(
"%02X", bytes[i]);
559 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
560 if (!warnings.empty()) {
562 for (
const auto& warning : warnings) {
567 if (!parse_result.warnings.empty()) {
569 for (
const auto& warning : parse_result.warnings) {
574 if (!parse_result.errors.empty()) {
576 for (
const auto& error : parse_result.errors) {
586 return absl::OkStatus();
596 auto output_path = parser.
GetString(
"output").value();
602 std::vector<std::pair<int, std::string>> msg_pairs;
603 std::vector<std::string> labels;
604 for (
const auto& msg : messages) {
605 msg_pairs.push_back({msg.ID, msg.RawString});
606 labels.push_back(absl::StrFormat(
"Message %02X", msg.ID));
612 std::ofstream file(output_path);
613 if (!file.is_open()) {
614 return absl::InternalError(
615 absl::StrFormat(
"Cannot write to file: %s", output_path));
621 formatter.
AddField(
"output", output_path);
622 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
623 formatter.
AddField(
"status",
"success");
626 return absl::OkStatus();
636 auto output_path = parser.
GetString(
"output").value();
637 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
638 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
639 return absl::InvalidArgumentError(
640 absl::StrFormat(
"Invalid range: %s", range));
643 std::vector<editor::MessageData> vanilla;
644 std::vector<editor::MessageData> expanded;
646 if (IncludeVanilla(range)) {
651 if (IncludeExpanded(range)) {
652 expanded = ReadExpandedMessages(*rom);
662 formatter.
AddField(
"output", output_path);
664 formatter.
AddField(
"vanilla_count",
static_cast<int>(vanilla.size()));
665 formatter.
AddField(
"expanded_count",
static_cast<int>(expanded.size()));
666 formatter.
AddField(
"status",
"success");
669 return absl::OkStatus();
679 auto file_path = parser.
GetString(
"file").value();
680 const bool apply = parser.
HasFlag(
"apply");
681 const bool strict = parser.
HasFlag(
"strict");
682 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
683 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
684 return absl::InvalidArgumentError(
685 absl::StrFormat(
"Invalid range: %s", range));
689 if (!entries_or.ok()) {
690 return entries_or.status();
692 auto entries = entries_or.value();
695 formatter.
AddField(
"file", file_path);
698 formatter.
AddField(
"strict", strict);
699 formatter.
AddField(
"entries",
static_cast<int>(entries.size()));
701 bool has_errors =
false;
702 int parse_error_count = 0;
704 int applied_updates = 0;
705 bool has_vanilla_entries =
false;
706 bool has_expanded_entries =
false;
711 std::vector<std::string> line_warnings;
713 std::vector<ParsedEntry> parsed_entries;
714 parsed_entries.reserve(entries.size());
716 for (
const auto& entry : entries) {
718 !IncludeVanilla(range)) ||
720 !IncludeExpanded(range))) {
724 ParsedEntry parsed{entry,
727 if (!parsed.parse.ok()) {
729 parse_error_count +=
static_cast<int>(parsed.parse.errors.size());
730 error_count +=
static_cast<int>(parsed.parse.errors.size());
733 has_vanilla_entries =
true;
735 has_expanded_entries =
true;
737 parsed_entries.push_back(std::move(parsed));
741 for (
const auto& parsed : parsed_entries) {
743 formatter.
AddField(
"id", parsed.entry.id);
744 formatter.
AddField(
"bank", BankLabel(parsed.entry.bank));
745 formatter.
AddField(
"text", parsed.entry.text);
746 formatter.
AddField(
"encoded_length",
747 static_cast<int>(parsed.parse.bytes.size()));
748 if (!parsed.line_warnings.empty()) {
750 for (
const auto& warning : parsed.line_warnings) {
755 if (!parsed.parse.warnings.empty()) {
757 for (
const auto& warning : parsed.parse.warnings) {
762 if (!parsed.parse.errors.empty()) {
764 for (
const auto& error : parsed.parse.errors) {
774 Rom* active_rom = rom;
777 if (active_rom ==
nullptr || !active_rom->
is_loaded()) {
779 if (!rom_path.has_value() || rom_path->empty()) {
780 std::string global_rom_path = absl::GetFlag(FLAGS_rom);
781 if (!global_rom_path.empty()) {
782 rom_path = global_rom_path;
785 if (!rom_path.has_value() || rom_path->empty()) {
787 formatter.
AddField(
"status",
"error");
789 "ROM not loaded; provide --rom when using --apply");
790 formatter.
AddField(
"parse_error_count", parse_error_count);
791 formatter.
AddField(
"error_count", error_count);
793 return absl::OkStatus();
796 if (!load_status.ok()) {
798 formatter.
AddField(
"status",
"error");
799 formatter.
AddField(
"error", std::string(load_status.message()));
800 formatter.
AddField(
"parse_error_count", parse_error_count);
801 formatter.
AddField(
"error_count", error_count);
803 return absl::OkStatus();
805 active_rom = &owned_rom;
809 formatter.
AddField(
"status",
"error");
810 formatter.
AddField(
"error",
"Parse errors present; no changes applied");
811 formatter.
AddField(
"parse_error_count", parse_error_count);
812 formatter.
AddField(
"error_count", error_count);
814 if (strict && parse_error_count > 0) {
817 return absl::FailedPreconditionError(
818 "Strict validation failed due to parse errors");
820 return absl::OkStatus();
823 std::optional<ExpandedMutationContext> expanded_context;
824 if (IncludeExpanded(range) && has_expanded_entries) {
825 auto context_or = PreflightExpandedMutation(parser, *active_rom);
826 if (!context_or.ok()) {
828 formatter.
AddField(
"status",
"error");
829 formatter.
AddField(
"error", std::string(context_or.status().message()));
830 formatter.
AddField(
"parse_error_count", parse_error_count);
831 formatter.
AddField(
"error_count", error_count);
833 return context_or.status();
835 expanded_context.emplace(std::move(context_or.value()));
836 if (!expanded_context->policy_warning.empty()) {
837 formatter.
AddField(
"write_policy_warning",
838 expanded_context->policy_warning);
845 std::vector<editor::MessageData> vanilla_messages;
846 if (IncludeVanilla(range) && has_vanilla_entries) {
851 std::vector<std::string> expanded_texts;
852 if (expanded_context.has_value()) {
853 const auto expanded_messages = ReadExpandedMessages(
854 *active_rom, expanded_context->start, expanded_context->
end);
855 auto bank_size_status = ValidateExpandedMessageBankSize(
856 expanded_messages.size(), *expanded_context);
857 if (!bank_size_status.ok()) {
858 formatter.
AddField(
"status",
"error");
859 formatter.
AddField(
"error", std::string(bank_size_status.message()));
861 return bank_size_status;
863 expanded_texts.reserve(expanded_messages.size());
864 for (
const auto& message : expanded_messages) {
865 expanded_texts.push_back(message.RawString);
869 for (
const auto& parsed : parsed_entries) {
871 if (parsed.entry.id < 0 ||
872 parsed.entry.id >=
static_cast<int>(vanilla_messages.size())) {
877 auto& message = vanilla_messages[parsed.entry.id];
878 message.RawString = parsed.entry.text;
879 message.ContentsParsed = parsed.entry.text;
880 message.Data = parsed.parse.bytes;
881 message.DataParsed = parsed.parse.bytes;
883 if (!expanded_context.has_value()) {
886 const auto id_status =
887 ValidateExpandedMessageId(parsed.entry.id, *expanded_context);
888 if (!id_status.ok()) {
893 if (parsed.entry.id >=
static_cast<int>(expanded_texts.size())) {
894 expanded_texts.resize(parsed.entry.id + 1);
896 expanded_texts[parsed.entry.id] = parsed.entry.text;
902 formatter.
AddField(
"status",
"error");
903 formatter.
AddField(
"error",
"Invalid message IDs; no changes applied");
906 if (IncludeVanilla(range) && has_vanilla_entries) {
909 formatter.
AddField(
"status",
"error");
910 formatter.
AddField(
"error", std::string(status.message()));
916 if (expanded_context.has_value()) {
918 active_rom, expanded_context->start, expanded_context->
end,
921 formatter.
AddField(
"status",
"error");
922 formatter.
AddField(
"error", std::string(status.message()));
928 if (active_rom->
dirty()) {
929 auto save_status = active_rom->
SaveToFile({.save_new =
false});
930 if (!save_status.ok()) {
931 formatter.
AddField(
"status",
"error");
932 formatter.
AddField(
"error", std::string(save_status.message()));
938 formatter.
AddField(
"status",
"success");
939 formatter.
AddField(
"applied_messages", applied_updates);
942 formatter.
AddField(
"status", has_errors ?
"error" :
"success");
945 formatter.
AddField(
"error_count", error_count);
946 formatter.
AddField(
"parse_error_count", parse_error_count);
948 if (strict && parse_error_count > 0) {
951 return absl::FailedPreconditionError(
"Strict validation failed");
953 return absl::OkStatus();
963 auto id_or = parser.
GetInt(
"id");
965 return id_or.status();
966 int msg_id = id_or.value();
968 auto text = parser.
GetString(
"text").value();
975 if (bytes.empty() && !text.empty()) {
976 return absl::InvalidArgumentError(
"Encoding produced no bytes");
979 auto context_or = PreflightExpandedMutation(parser, *rom);
980 if (!context_or.ok()) {
981 return context_or.status();
983 auto context = std::move(context_or.value());
984 auto id_status = ValidateExpandedMessageId(msg_id, context);
985 if (!id_status.ok()) {
990 auto expanded = ReadExpandedMessages(*rom, context.start, context.
end);
991 auto bank_size_status =
992 ValidateExpandedMessageBankSize(expanded.size(), context);
993 if (!bank_size_status.ok()) {
994 return bank_size_status;
998 std::vector<std::string> all_texts;
999 all_texts.reserve(expanded.size());
1000 for (
const auto& msg : expanded) {
1001 all_texts.push_back(msg.RawString);
1003 if (msg_id >=
static_cast<int>(all_texts.size())) {
1004 all_texts.resize(msg_id + 1);
1006 all_texts[msg_id] = text;
1019 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
1020 formatter.
AddField(
"status",
"success");
1021 if (!context.policy_warning.empty()) {
1022 formatter.
AddField(
"write_policy_warning", context.policy_warning);
1024 if (!warnings.empty()) {
1026 for (
const auto& warning : warnings) {
1033 return absl::OkStatus();
1043 auto output_path = parser.
GetString(
"output").value();
1044 auto range = parser.
GetString(
"range").value_or(
"expanded");
1046 int start, end_addr;
1047 if (range ==
"expanded") {
1056 const uint8_t* data = rom->
data();
1057 int data_end = start;
1058 while (data_end <= end_addr && data[data_end] != 0xFF) {
1061 if (data_end <= end_addr) {
1065 int size = data_end - start;
1067 std::ofstream file(output_path, std::ios::binary);
1068 if (!file.is_open()) {
1069 return absl::InternalError(
1070 absl::StrFormat(
"Cannot write to file: %s", output_path));
1072 file.write(
reinterpret_cast<const char*
>(data + start), size);
1076 formatter.
AddField(
"output", output_path);
1077 formatter.
AddField(
"range", range);
1079 formatter.
AddHexField(
"end_address", data_end - 1, 6);
1080 formatter.
AddField(
"size_bytes", size);
1081 formatter.
AddField(
"status",
"success");
1084 return absl::OkStatus();
1094 auto output_path = parser.
GetString(
"output").value();
1095 auto range = parser.
GetString(
"range").value_or(
"expanded");
1099 if (range ==
"expanded") {
1101 snes_addr = 0x2F8000;
1104 snes_addr = 0x1C0000;
1111 std::ofstream file(output_path);
1112 if (!file.is_open()) {
1113 return absl::InternalError(
1114 absl::StrFormat(
"Cannot write to file: %s", output_path));
1118 file <<
"; Auto-generated message data\n";
1119 file << absl::StrFormat(
"; Source: %s region\n", range);
1120 file << absl::StrFormat(
"; Messages: %d\n\n", messages.size());
1121 file << absl::StrFormat(
"org $%06X\n\n", snes_addr);
1124 for (
const auto& msg : messages) {
1125 file << absl::StrFormat(
"; Message $%02X: %s\n", msg.ID,
1126 msg.ContentsParsed.substr(0, 60));
1128 for (
size_t i = 0; i < msg.Data.size(); ++i) {
1131 file << absl::StrFormat(
"$%02X", msg.Data[i]);
1133 file <<
", $7F ; terminator\n\n";
1137 file <<
"db $FF ; end of message data\n";
1141 formatter.
AddField(
"output", output_path);
1142 formatter.
AddField(
"range", range);
1143 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
1144 formatter.
AddField(
"status",
"success");
1147 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.
~ScopedHackManifestBindingRestore()
zelda3::ResourceLabelProvider & provider_
ScopedHackManifestBindingRestore(const ScopedHackManifestBindingRestore &)=delete
ScopedHackManifestBindingRestore & operator=(const ScopedHackManifestBindingRestore &)=delete
ScopedHackManifestBindingRestore(zelda3::ResourceLabelProvider &provider)
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)
Loads and queries the hack manifest JSON for yaze-ASM integration.
Unified interface for accessing resource labels with project overrides.
ABSL_DECLARE_FLAG(std::string, rom)
std::string BankLabel(editor::MessageBank bank)
absl::Status ValidateExpandedMessageBankSize(size_t message_count, const ExpandedMutationContext &context)
bool IncludeVanilla(const std::string &range)
absl::Status ValidateExpandedMessageId(int id, const ExpandedMutationContext &context)
absl::StatusOr< std::filesystem::path > CanonicalExistingPath(const std::string &path, absl::string_view label)
bool IncludeExpanded(const std::string &range)
std::string NormalizeRange(const std::string &range)
std::string FormatManifestConflict(const core::WriteConflict &conflict)
bool IsCanonicalMappedLoRomAddress(uint32_t address)
std::vector< editor::MessageData > ReadExpandedMessages(const Rom &rom)
absl::StatusOr< ExpandedMutationContext > PreflightExpandedMutation(const resources::ArgumentParser &parser, const Rom &rom)
std::string AddressOwnershipToString(AddressOwnership ownership)
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, bool allow_bank_switch)
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
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
uint32_t PcToSnes(uint32_t addr)
uint32_t SnesToPc(uint32_t addr) noexcept
project::YazeProject project
std::string policy_warning
A conflict detected when yaze wants to write to an ASM-owned address.
AddressOwnership ownership
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
std::string GetAbsolutePath(const std::string &relative_path) const
absl::Status Open(const std::string &project_path)