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"
33 return absl::AsciiStrToLower(range);
37 return range ==
"all" || range ==
"vanilla";
41 return range ==
"all" || range ==
"expanded";
50 if (start < 0 ||
static_cast<size_t>(start) >= rom.
size()) {
54 static_cast<int>(rom.
size()) - 1);
61 if (start < 0 || end < start ||
static_cast<size_t>(start) >= rom.
size()) {
64 end = std::min(end,
static_cast<int>(rom.
size()) - 1);
73 int message_limit = -1;
81 : provider_(provider), previous_(provider.hack_manifest()) {}
96 const std::string& path, absl::string_view label) {
98 return absl::FailedPreconditionError(
99 absl::StrFormat(
"%s path is empty", label));
103 auto canonical_path = std::filesystem::canonical(path, ec);
105 return absl::FailedPreconditionError(absl::StrFormat(
106 "Cannot resolve %s path '%s': %s", label, path, ec.message()));
108 return canonical_path;
114 return absl::OkStatus();
117 const std::string manifest_path =
119 const absl::Status manifest_status =
121 if (manifest_status.ok()) {
122 return absl::OkStatus();
125 manifest_status.code(),
126 absl::StrFormat(
"Cannot load project '%s': hack manifest '%s': %s",
127 project_path, manifest_path, manifest_status.message()));
132 absl::StrFormat(
"address 0x%06X is %s", conflict.
address,
134 if (!conflict.module.empty()) {
135 absl::StrAppend(&result,
" (Module: ", conflict.module,
")");
140bool IsCanonicalMappedLoRomAddress(uint32_t address) {
141 const uint8_t bank =
static_cast<uint8_t
>((address >> 16) & 0xFFu);
142 if (bank == 0x7E || bank == 0x7F || (address & 0xFFFFu) < 0x8000u) {
150 auto project_path = parser.
GetString(
"project");
151 if (!project_path.has_value() || project_path->empty()) {
152 return absl::FailedPreconditionError(
153 "Expanded message mutation requires explicit --project so Yaze can "
154 "verify ROM ownership and write policy");
157 return absl::FailedPreconditionError(
"ROM is not loaded");
162 absl::Status open_status;
171 }
catch (
const std::exception& error) {
172 return absl::InvalidArgumentError(absl::StrFormat(
173 "Cannot load project '%s': %s", *project_path, error.what()));
175 return absl::InvalidArgumentError(absl::StrFormat(
176 "Cannot load project '%s': unknown project parse error",
180 if (!open_status.ok()) {
181 return absl::Status(open_status.code(),
182 absl::StrFormat(
"Cannot load project '%s': %s",
183 *project_path, open_status.message()));
185 if (absl::Status manifest_status =
187 !manifest_status.ok()) {
188 return manifest_status;
192 if (!active_rom_path_or.ok()) {
193 return active_rom_path_or.status();
198 if (!project_rom_path_or.ok()) {
199 return project_rom_path_or.status();
201 if (*active_rom_path_or != *project_rom_path_or) {
202 return absl::FailedPreconditionError(absl::StrFormat(
203 "Project ROM mismatch: active ROM is '%s' but --project binds to '%s'",
204 active_rom_path_or->string(), project_rom_path_or->string()));
208 if (!manifest.loaded()) {
209 return absl::FailedPreconditionError(absl::StrFormat(
210 "Project '%s' has no loaded hack manifest; configure a valid "
211 "hack_manifest_file before mutating expanded messages",
215 const auto& layout = manifest.message_layout();
216 if (layout.data_start == 0 || layout.data_end == 0 ||
217 layout.data_end < layout.data_start ||
218 !IsCanonicalMappedLoRomAddress(layout.data_start) ||
219 !IsCanonicalMappedLoRomAddress(layout.data_end)) {
220 return absl::FailedPreconditionError(
221 "Hack manifest does not define a valid LoROM expanded message region");
224 const uint32_t start =
SnesToPc(layout.data_start);
225 const uint32_t end =
SnesToPc(layout.data_end);
227 end >
static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
229 return absl::OutOfRangeError(absl::StrFormat(
230 "Configured expanded message region [0x%06X, 0x%06X] is outside the "
231 "active ROM (size=0x%zX)",
232 start, end, rom.
size()));
234 context.
start =
static_cast<int>(start);
235 context.
end =
static_cast<int>(end);
241 const int region_capacity = context.
end - context.
start + 1;
242 const int capacity_message_limit = std::max(0, region_capacity - 1);
244 if (layout.expanded_count > 0) {
246 std::min(layout.expanded_count, capacity_message_limit);
248 if (layout.last_expanded_id >= layout.first_expanded_id &&
249 (layout.first_expanded_id != 0 || layout.last_expanded_id != 0)) {
250 const int declared_limit =
251 static_cast<int>(layout.last_expanded_id - layout.first_expanded_id) +
256 const std::string lowered_hack_name =
257 absl::AsciiStrToLower(manifest.hack_name());
260 absl::StrContains(lowered_hack_name,
"oracle of secrets")) {
261 return absl::PermissionDeniedError(
262 "Oracle of Secrets expanded messages are owned by ASM. No message "
263 "bytes were written; edit Core/message.asm, rebuild Oracle of Secrets, "
264 "and reopen the rebuilt ROM before retrying.");
267 const auto conflicts =
268 manifest.AnalyzePcWriteRanges({{start,
static_cast<uint32_t
>(end + 1u)}});
269 if (!conflicts.empty()) {
273 return absl::PermissionDeniedError(absl::StrFormat(
274 "Expanded message mutation blocked by hack manifest: %s", detail));
279 "Expanded message region conflicts with hack manifest: %s", detail);
289 return absl::InvalidArgumentError(
290 absl::StrFormat(
"Invalid expanded message ID: %d",
id));
293 return absl::OutOfRangeError(absl::StrFormat(
294 "Expanded message ID %d is outside the manifest range (count=%d)",
id,
297 return absl::OkStatus();
303 message_count >
static_cast<size_t>(context.
message_limit)) {
304 return absl::FailedPreconditionError(absl::StrFormat(
305 "Expanded message bank contains %zu messages, exceeding the manifest "
306 "limit of %d; no changes were applied",
309 return absl::OkStatus();
320 auto limit = parser.
GetInt(
"limit").value_or(50);
327 if (limit >
static_cast<int>(messages.size())) {
328 limit =
static_cast<int>(messages.size());
333 formatter.
AddField(
"total_messages",
static_cast<int>(messages.size()));
334 formatter.
AddField(
"status",
"success");
337 for (
int i = 0; i < limit; ++i) {
338 const auto& msg = messages[i];
342 formatter.
AddField(
"text", msg.ContentsParsed);
343 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
349 return absl::OkStatus();
355 auto message_id_or = parser.
GetInt(
"id");
356 if (!message_id_or.ok()) {
357 return message_id_or.status();
359 int message_id = message_id_or.value();
363 if (message_id < 0 || message_id >=
static_cast<int>(messages.size())) {
364 return absl::NotFoundError(
365 absl::StrFormat(
"Message ID %d not found (max: %d)", message_id,
366 static_cast<int>(messages.size()) - 1));
369 const auto& msg = messages[message_id];
374 formatter.
AddField(
"text", msg.ContentsParsed);
375 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
378 return absl::OkStatus();
384 auto query = parser.
GetString(
"query").value();
385 auto limit = parser.
GetInt(
"limit").value_or(10);
396 formatter.
AddField(
"status",
"success");
398 std::string lowered_query = absl::AsciiStrToLower(query);
399 std::vector<const editor::MessageData*> matches;
400 for (
const auto& msg : messages) {
401 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
402 if (lowered_text.find(lowered_query) == std::string::npos) {
405 matches.push_back(&msg);
408 formatter.
AddField(
"matches_found",
static_cast<int>(matches.size()));
411 for (
const auto* msg : matches) {
412 if (limit > 0 && match_count >= limit) {
418 formatter.
AddField(
"text", msg->ContentsParsed);
425 return absl::OkStatus();
435 auto text = parser.
GetString(
"text").value();
438 auto bytes = parse_result.bytes;
442 for (
size_t i = 0; i < bytes.size(); ++i) {
445 hex_str += absl::StrFormat(
"%02X", bytes[i]);
451 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
455 for (uint8_t
byte : bytes) {
456 formatter.
AddArrayItem(absl::StrFormat(
"0x%02X",
byte));
462 if (!warnings.empty()) {
464 for (
const auto& warning : warnings) {
469 if (!parse_result.warnings.empty()) {
471 for (
const auto& warning : parse_result.warnings) {
476 if (!parse_result.errors.empty()) {
478 for (
const auto& error : parse_result.errors) {
486 return absl::OkStatus();
496 auto hex_input = parser.
GetString(
"hex").value();
499 std::vector<uint8_t> bytes;
500 std::istringstream hex_stream(hex_input);
501 std::string hex_byte;
502 while (hex_stream >> hex_byte) {
504 bytes.push_back(
static_cast<uint8_t
>(std::stoi(hex_byte,
nullptr, 16)));
505 }
catch (
const std::exception&) {
506 return absl::InvalidArgumentError(
507 absl::StrFormat(
"Invalid hex byte: '%s'", hex_byte));
513 for (
size_t i = 0; i < bytes.size(); ++i) {
514 uint8_t
byte = bytes[i];
518 if (cmd.has_value()) {
519 if (cmd->HasArgument && i + 1 < bytes.size()) {
520 decoded += cmd->GetParamToken(bytes[++i]);
522 decoded += cmd->GetParamToken();
532 formatter.
AddField(
"hex", hex_input);
533 formatter.
AddField(
"text", decoded);
534 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
537 return absl::OkStatus();
547 auto file_path = parser.
GetString(
"file").value();
550 std::ifstream file(file_path);
551 if (!file.is_open()) {
552 return absl::NotFoundError(
553 absl::StrFormat(
"Cannot open file: %s", file_path));
556 std::string content((std::istreambuf_iterator<char>(file)),
557 std::istreambuf_iterator<char>());
563 formatter.
AddField(
"file", file_path);
564 formatter.
AddField(
"messages_parsed",
static_cast<int>(messages.size()));
567 for (
const auto& [msg_id, body] : messages) {
570 auto bytes = parse_result.bytes;
574 for (
size_t i = 0; i < bytes.size(); ++i) {
577 hex_str += absl::StrFormat(
"%02X", bytes[i]);
584 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
585 if (!warnings.empty()) {
587 for (
const auto& warning : warnings) {
592 if (!parse_result.warnings.empty()) {
594 for (
const auto& warning : parse_result.warnings) {
599 if (!parse_result.errors.empty()) {
601 for (
const auto& error : parse_result.errors) {
611 return absl::OkStatus();
621 auto output_path = parser.
GetString(
"output").value();
627 std::vector<std::pair<int, std::string>> msg_pairs;
628 std::vector<std::string> labels;
629 for (
const auto& msg : messages) {
630 msg_pairs.push_back({msg.ID, msg.RawString});
631 labels.push_back(absl::StrFormat(
"Message %02X", msg.ID));
637 std::ofstream file(output_path);
638 if (!file.is_open()) {
639 return absl::InternalError(
640 absl::StrFormat(
"Cannot write to file: %s", output_path));
646 formatter.
AddField(
"output", output_path);
647 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
648 formatter.
AddField(
"status",
"success");
651 return absl::OkStatus();
661 auto output_path = parser.
GetString(
"output").value();
662 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
663 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
664 return absl::InvalidArgumentError(
665 absl::StrFormat(
"Invalid range: %s", range));
668 std::vector<editor::MessageData> vanilla;
669 std::vector<editor::MessageData> expanded;
671 if (IncludeVanilla(range)) {
676 if (IncludeExpanded(range)) {
677 expanded = ReadExpandedMessages(*rom);
687 formatter.
AddField(
"output", output_path);
689 formatter.
AddField(
"vanilla_count",
static_cast<int>(vanilla.size()));
690 formatter.
AddField(
"expanded_count",
static_cast<int>(expanded.size()));
691 formatter.
AddField(
"status",
"success");
694 return absl::OkStatus();
704 auto file_path = parser.
GetString(
"file").value();
705 const bool apply = parser.
HasFlag(
"apply");
706 const bool strict = parser.
HasFlag(
"strict");
707 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
708 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
709 return absl::InvalidArgumentError(
710 absl::StrFormat(
"Invalid range: %s", range));
714 if (!entries_or.ok()) {
715 return entries_or.status();
717 auto entries = entries_or.value();
720 formatter.
AddField(
"file", file_path);
723 formatter.
AddField(
"strict", strict);
724 formatter.
AddField(
"entries",
static_cast<int>(entries.size()));
726 bool has_errors =
false;
727 int parse_error_count = 0;
729 int applied_updates = 0;
730 bool has_vanilla_entries =
false;
731 bool has_expanded_entries =
false;
736 std::vector<std::string> line_warnings;
738 std::vector<ParsedEntry> parsed_entries;
739 parsed_entries.reserve(entries.size());
741 for (
const auto& entry : entries) {
743 !IncludeVanilla(range)) ||
745 !IncludeExpanded(range))) {
749 ParsedEntry parsed{entry,
752 if (!parsed.parse.ok()) {
754 parse_error_count +=
static_cast<int>(parsed.parse.errors.size());
755 error_count +=
static_cast<int>(parsed.parse.errors.size());
758 has_vanilla_entries =
true;
760 has_expanded_entries =
true;
762 parsed_entries.push_back(std::move(parsed));
766 for (
const auto& parsed : parsed_entries) {
768 formatter.
AddField(
"id", parsed.entry.id);
769 formatter.
AddField(
"bank", BankLabel(parsed.entry.bank));
770 formatter.
AddField(
"text", parsed.entry.text);
771 formatter.
AddField(
"encoded_length",
772 static_cast<int>(parsed.parse.bytes.size()));
773 if (!parsed.line_warnings.empty()) {
775 for (
const auto& warning : parsed.line_warnings) {
780 if (!parsed.parse.warnings.empty()) {
782 for (
const auto& warning : parsed.parse.warnings) {
787 if (!parsed.parse.errors.empty()) {
789 for (
const auto& error : parsed.parse.errors) {
799 Rom* active_rom = rom;
802 if (active_rom ==
nullptr || !active_rom->
is_loaded()) {
804 if (!rom_path.has_value() || rom_path->empty()) {
805 std::string global_rom_path = absl::GetFlag(FLAGS_rom);
806 if (!global_rom_path.empty()) {
807 rom_path = global_rom_path;
810 if (!rom_path.has_value() || rom_path->empty()) {
812 formatter.
AddField(
"status",
"error");
814 "ROM not loaded; provide --rom when using --apply");
815 formatter.
AddField(
"parse_error_count", parse_error_count);
816 formatter.
AddField(
"error_count", error_count);
818 return absl::OkStatus();
821 if (!load_status.ok()) {
823 formatter.
AddField(
"status",
"error");
824 formatter.
AddField(
"error", std::string(load_status.message()));
825 formatter.
AddField(
"parse_error_count", parse_error_count);
826 formatter.
AddField(
"error_count", error_count);
828 return absl::OkStatus();
830 active_rom = &owned_rom;
834 formatter.
AddField(
"status",
"error");
835 formatter.
AddField(
"error",
"Parse errors present; no changes applied");
836 formatter.
AddField(
"parse_error_count", parse_error_count);
837 formatter.
AddField(
"error_count", error_count);
839 if (strict && parse_error_count > 0) {
842 return absl::FailedPreconditionError(
843 "Strict validation failed due to parse errors");
845 return absl::OkStatus();
848 std::optional<ExpandedMutationContext> expanded_context;
849 if (IncludeExpanded(range) && has_expanded_entries) {
850 auto context_or = PreflightExpandedMutation(parser, *active_rom);
851 if (!context_or.ok()) {
853 formatter.
AddField(
"status",
"error");
854 formatter.
AddField(
"error", std::string(context_or.status().message()));
855 formatter.
AddField(
"parse_error_count", parse_error_count);
856 formatter.
AddField(
"error_count", error_count);
858 return context_or.status();
860 expanded_context.emplace(std::move(context_or.value()));
861 if (!expanded_context->policy_warning.empty()) {
862 formatter.
AddField(
"write_policy_warning",
863 expanded_context->policy_warning);
870 std::vector<editor::MessageData> vanilla_messages;
871 if (IncludeVanilla(range) && has_vanilla_entries) {
876 std::vector<std::string> expanded_texts;
877 if (expanded_context.has_value()) {
878 const auto expanded_messages = ReadExpandedMessages(
879 *active_rom, expanded_context->start, expanded_context->
end);
880 auto bank_size_status = ValidateExpandedMessageBankSize(
881 expanded_messages.size(), *expanded_context);
882 if (!bank_size_status.ok()) {
883 formatter.
AddField(
"status",
"error");
884 formatter.
AddField(
"error", std::string(bank_size_status.message()));
886 return bank_size_status;
888 expanded_texts.reserve(expanded_messages.size());
889 for (
const auto& message : expanded_messages) {
890 expanded_texts.push_back(message.RawString);
894 for (
const auto& parsed : parsed_entries) {
896 if (parsed.entry.id < 0 ||
897 parsed.entry.id >=
static_cast<int>(vanilla_messages.size())) {
902 auto& message = vanilla_messages[parsed.entry.id];
903 message.RawString = parsed.entry.text;
904 message.ContentsParsed = parsed.entry.text;
905 message.Data = parsed.parse.bytes;
906 message.DataParsed = parsed.parse.bytes;
908 if (!expanded_context.has_value()) {
911 const auto id_status =
912 ValidateExpandedMessageId(parsed.entry.id, *expanded_context);
913 if (!id_status.ok()) {
918 if (parsed.entry.id >=
static_cast<int>(expanded_texts.size())) {
919 expanded_texts.resize(parsed.entry.id + 1);
921 expanded_texts[parsed.entry.id] = parsed.entry.text;
927 formatter.
AddField(
"status",
"error");
928 formatter.
AddField(
"error",
"Invalid message IDs; no changes applied");
931 if (IncludeVanilla(range) && has_vanilla_entries) {
934 formatter.
AddField(
"status",
"error");
935 formatter.
AddField(
"error", std::string(status.message()));
941 if (expanded_context.has_value()) {
943 active_rom, expanded_context->start, expanded_context->
end,
946 formatter.
AddField(
"status",
"error");
947 formatter.
AddField(
"error", std::string(status.message()));
953 if (active_rom->
dirty()) {
954 auto save_status = active_rom->
SaveToFile({.save_new =
false});
955 if (!save_status.ok()) {
956 formatter.
AddField(
"status",
"error");
957 formatter.
AddField(
"error", std::string(save_status.message()));
963 formatter.
AddField(
"status",
"success");
964 formatter.
AddField(
"applied_messages", applied_updates);
967 formatter.
AddField(
"status", has_errors ?
"error" :
"success");
970 formatter.
AddField(
"error_count", error_count);
971 formatter.
AddField(
"parse_error_count", parse_error_count);
973 if (strict && parse_error_count > 0) {
976 return absl::FailedPreconditionError(
"Strict validation failed");
978 return absl::OkStatus();
988 auto id_or = parser.
GetInt(
"id");
990 return id_or.status();
991 int msg_id = id_or.value();
993 auto text = parser.
GetString(
"text").value();
1000 if (bytes.empty() && !text.empty()) {
1001 return absl::InvalidArgumentError(
"Encoding produced no bytes");
1004 auto context_or = PreflightExpandedMutation(parser, *rom);
1005 if (!context_or.ok()) {
1006 return context_or.status();
1008 auto context = std::move(context_or.value());
1009 auto id_status = ValidateExpandedMessageId(msg_id, context);
1010 if (!id_status.ok()) {
1015 auto expanded = ReadExpandedMessages(*rom, context.start, context.
end);
1016 auto bank_size_status =
1017 ValidateExpandedMessageBankSize(expanded.size(), context);
1018 if (!bank_size_status.ok()) {
1019 return bank_size_status;
1023 std::vector<std::string> all_texts;
1024 all_texts.reserve(expanded.size());
1025 for (
const auto& msg : expanded) {
1026 all_texts.push_back(msg.RawString);
1028 if (msg_id >=
static_cast<int>(all_texts.size())) {
1029 all_texts.resize(msg_id + 1);
1031 all_texts[msg_id] = text;
1044 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
1045 formatter.
AddField(
"status",
"success");
1046 if (!context.policy_warning.empty()) {
1047 formatter.
AddField(
"write_policy_warning", context.policy_warning);
1049 if (!warnings.empty()) {
1051 for (
const auto& warning : warnings) {
1058 return absl::OkStatus();
1068 auto output_path = parser.
GetString(
"output").value();
1069 auto range = parser.
GetString(
"range").value_or(
"expanded");
1071 int start, end_addr;
1072 if (range ==
"expanded") {
1081 const uint8_t* data = rom->
data();
1082 int data_end = start;
1083 while (data_end <= end_addr && data[data_end] != 0xFF) {
1086 if (data_end <= end_addr) {
1090 int size = data_end - start;
1092 std::ofstream file(output_path, std::ios::binary);
1093 if (!file.is_open()) {
1094 return absl::InternalError(
1095 absl::StrFormat(
"Cannot write to file: %s", output_path));
1097 file.write(
reinterpret_cast<const char*
>(data + start), size);
1101 formatter.
AddField(
"output", output_path);
1102 formatter.
AddField(
"range", range);
1104 formatter.
AddHexField(
"end_address", data_end - 1, 6);
1105 formatter.
AddField(
"size_bytes", size);
1106 formatter.
AddField(
"status",
"success");
1109 return absl::OkStatus();
1119 auto output_path = parser.
GetString(
"output").value();
1120 auto range = parser.
GetString(
"range").value_or(
"expanded");
1124 if (range ==
"expanded") {
1126 snes_addr = 0x2F8000;
1129 snes_addr = 0x1C0000;
1136 std::ofstream file(output_path);
1137 if (!file.is_open()) {
1138 return absl::InternalError(
1139 absl::StrFormat(
"Cannot write to file: %s", output_path));
1143 file <<
"; Auto-generated message data\n";
1144 file << absl::StrFormat(
"; Source: %s region\n", range);
1145 file << absl::StrFormat(
"; Messages: %d\n\n", messages.size());
1146 file << absl::StrFormat(
"org $%06X\n\n", snes_addr);
1149 for (
const auto& msg : messages) {
1150 file << absl::StrFormat(
"; Message $%02X: %s\n", msg.ID,
1151 msg.ContentsParsed.substr(0, 60));
1153 for (
size_t i = 0; i < msg.Data.size(); ++i) {
1156 file << absl::StrFormat(
"$%02X", msg.Data[i]);
1158 file <<
", $7F ; terminator\n\n";
1162 file <<
"db $FF ; end of message data\n";
1166 formatter.
AddField(
"output", output_path);
1167 formatter.
AddField(
"range", range);
1168 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
1169 formatter.
AddField(
"status",
"success");
1172 return absl::OkStatus();
1178 const std::string project_path = parser.
GetString(
"project").value();
1179 const std::string incoming_path = parser.
GetString(
"file").value();
1180 const bool write = parser.
HasFlag(
"write");
1184 absl::Status open_status;
1186 ScopedHackManifestBindingRestore restore_manifest(resource_labels);
1188 open_status = source_project.
Open(project_path);
1189 }
catch (
const std::exception& error) {
1190 return absl::InvalidArgumentError(absl::StrFormat(
1191 "Cannot load project '%s': %s", project_path, error.what()));
1193 return absl::InvalidArgumentError(absl::StrFormat(
1194 "Cannot load project '%s': unknown project parse error",
1198 if (!open_status.ok()) {
1199 return absl::Status(open_status.code(),
1200 absl::StrFormat(
"Cannot load project '%s': %s",
1201 project_path, open_status.message()));
1203 if (absl::Status manifest_status =
1204 EnsureConfiguredHackManifestLoaded(&source_project, project_path);
1205 !manifest_status.ok()) {
1206 return manifest_status;
1211 .expected_source_sha256 =
1212 parser.
GetString(
"expected-source-sha256").value_or(
""),
1216 if (!result_or.ok()) {
1217 return result_or.status();
1219 const auto& result = *result_or;
1222 formatter.
AddField(
"status",
"success");
1223 formatter.
AddField(
"mode", write ?
"write" :
"dry-run");
1224 formatter.
AddField(
"changed", result.changed);
1225 formatter.
AddField(
"wrote", result.wrote);
1226 formatter.
AddField(
"canonical_bundle", result.canonical_bundle_path.string());
1227 formatter.
AddField(
"generated_asm_include",
1228 result.generated_asm_include_path.string());
1229 formatter.
AddHexField(
"first_expanded_id", result.first_expanded_id, 3);
1230 formatter.
AddHexField(
"last_expanded_id", result.last_expanded_id, 3);
1231 formatter.
AddField(
"expanded_count", result.expanded_count);
1232 formatter.
AddField(
"incoming_updates", result.incoming_updates);
1233 formatter.
AddField(
"encoded_size",
static_cast<int>(result.encoded_size));
1234 formatter.
AddField(
"capacity",
static_cast<int>(result.capacity));
1235 formatter.
AddField(
"source_sha256_before", result.source_sha256_before);
1236 formatter.
AddField(
"proposed_source_sha256", result.proposed_source_sha256);
1238 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.
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.
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
bool loaded() const
Check if the manifest has been loaded.
Unified interface for accessing resource labels with project overrides.
ABSL_DECLARE_FLAG(std::string, rom)
absl::Status EnsureConfiguredHackManifestLoaded(project::YazeProject *project, absl::string_view project_path)
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)
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()
absl::StatusOr< MessageSourceSyncResult > SyncMessageSource(const project::YazeProject &project, const fs::path &incoming_bundle_path, const MessageSourceSyncOptions &options)
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 hack_manifest_file
std::string GetAbsolutePath(const std::string &relative_path) const
absl::Status Open(const std::string &project_path)