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"
18#include "absl/strings/str_join.h"
35 return absl::AsciiStrToLower(range);
39 return range ==
"all" || range ==
"vanilla";
43 return range ==
"all" || range ==
"expanded";
52 if (start < 0 ||
static_cast<size_t>(start) >= rom.
size()) {
56 static_cast<int>(rom.
size()) - 1);
63 if (start < 0 || end < start ||
static_cast<size_t>(start) >= rom.
size()) {
66 end = std::min(end,
static_cast<int>(rom.
size()) - 1);
79 int message_limit = -1;
87 : provider_(provider), previous_(provider.hack_manifest()) {}
102 const std::string& path, absl::string_view label) {
104 return absl::FailedPreconditionError(
105 absl::StrFormat(
"%s path is empty", label));
109 auto canonical_path = std::filesystem::canonical(path, ec);
111 return absl::FailedPreconditionError(absl::StrFormat(
112 "Cannot resolve %s path '%s': %s", label, path, ec.message()));
114 return canonical_path;
120 return absl::OkStatus();
123 const std::string manifest_path =
125 const absl::Status manifest_status =
127 if (manifest_status.ok()) {
128 return absl::OkStatus();
131 manifest_status.code(),
132 absl::StrFormat(
"Cannot load project '%s': hack manifest '%s': %s",
133 project_path, manifest_path, manifest_status.message()));
138 absl::StrFormat(
"address 0x%06X is %s", conflict.
address,
140 if (!conflict.module.empty()) {
141 absl::StrAppend(&result,
" (Module: ", conflict.module,
")");
146bool IsCanonicalMappedLoRomAddress(uint32_t address) {
147 const uint8_t bank =
static_cast<uint8_t
>((address >> 16) & 0xFFu);
148 if (bank == 0x7E || bank == 0x7F || (address & 0xFFFFu) < 0x8000u) {
156 absl::string_view mutation_scope) {
157 auto project_path = parser.
GetString(
"project");
158 if (!project_path.has_value() || project_path->empty()) {
159 return absl::FailedPreconditionError(absl::StrFormat(
160 "%s requires explicit --project so Yaze can verify ROM ownership and "
165 return absl::FailedPreconditionError(
"ROM is not loaded");
170 absl::Status open_status;
179 }
catch (
const std::exception& error) {
180 return absl::InvalidArgumentError(absl::StrFormat(
181 "Cannot load project '%s': %s", *project_path, error.what()));
183 return absl::InvalidArgumentError(absl::StrFormat(
184 "Cannot load project '%s': unknown project parse error",
188 if (!open_status.ok()) {
189 return absl::Status(open_status.code(),
190 absl::StrFormat(
"Cannot load project '%s': %s",
191 *project_path, open_status.message()));
193 if (absl::Status manifest_status =
195 !manifest_status.ok()) {
196 return manifest_status;
200 if (!active_rom_path_or.ok()) {
201 return active_rom_path_or.status();
206 if (!project_rom_path_or.ok()) {
207 return project_rom_path_or.status();
209 if (*active_rom_path_or != *project_rom_path_or) {
210 return absl::FailedPreconditionError(absl::StrFormat(
211 "Project ROM mismatch: active ROM is '%s' but --project binds to '%s'",
212 active_rom_path_or->string(), project_rom_path_or->string()));
214 std::error_code file_size_ec;
215 const uintmax_t raw_file_size =
216 std::filesystem::file_size(*active_rom_path_or, file_size_ec);
218 return absl::FailedPreconditionError(
219 absl::StrFormat(
"Cannot inspect active ROM size '%s': %s",
220 active_rom_path_or->string(), file_size_ec.message()));
222 constexpr uintmax_t kSnesBankSize = 0x8000;
226 return absl::FailedPreconditionError(
227 "Message mutation requires a headerless ROM file; remove the "
228 "512-byte copier header and reopen the ROM");
230 if (raw_file_size != rom.
size()) {
231 return absl::FailedPreconditionError(absl::StrFormat(
232 "Message mutation requires a headerless ROM file: disk size is 0x%zX "
233 "but the loaded ROM is 0x%zX bytes",
234 static_cast<size_t>(raw_file_size), rom.
size()));
239 if (!manifest.loaded()) {
240 return absl::FailedPreconditionError(absl::StrFormat(
241 "Project '%s' has no loaded hack manifest; configure a valid "
242 "hack_manifest_file before mutating messages",
254 if (layout.data_start == 0 || layout.data_end == 0 ||
255 layout.data_end < layout.data_start ||
256 !IsCanonicalMappedLoRomAddress(layout.data_start) ||
257 !IsCanonicalMappedLoRomAddress(layout.data_end)) {
258 return absl::FailedPreconditionError(
259 "Hack manifest does not define a valid LoROM expanded message region");
262 const uint32_t start =
SnesToPc(layout.data_start);
263 const uint32_t end =
SnesToPc(layout.data_end);
265 end >
static_cast<uint32_t
>(std::numeric_limits<int>::max()) ||
267 return absl::OutOfRangeError(absl::StrFormat(
268 "Configured expanded message region [0x%06X, 0x%06X] is outside the "
269 "active ROM (size=0x%zX)",
270 start, end, rom.
size()));
272 context.
start =
static_cast<int>(start);
273 context.
end =
static_cast<int>(end);
279 const int region_capacity = context.
end - context.
start + 1;
280 const int capacity_message_limit = std::max(0, region_capacity - 1);
282 if (layout.expanded_count > 0) {
284 std::min(layout.expanded_count, capacity_message_limit);
286 if (layout.last_expanded_id >= layout.first_expanded_id &&
287 (layout.first_expanded_id != 0 || layout.last_expanded_id != 0)) {
288 const int declared_limit =
289 static_cast<int>(layout.last_expanded_id - layout.first_expanded_id) +
294 const std::string lowered_hack_name =
295 absl::AsciiStrToLower(manifest.hack_name());
298 absl::StrContains(lowered_hack_name,
"oracle of secrets")) {
302 const auto conflicts =
303 manifest.AnalyzePcWriteRanges({{start,
static_cast<uint32_t
>(end + 1u)}});
304 if (!conflicts.empty()) {
308 return absl::PermissionDeniedError(absl::StrFormat(
309 "Expanded message mutation blocked by hack manifest: %s", detail));
314 "Expanded message region conflicts with hack manifest: %s", detail);
323 auto project_context_or =
325 if (!project_context_or.ok()) {
326 return project_context_or.status();
329 std::move(project_context_or.value());
334 std::vector<uint8_t>* baseline) {
336 return absl::FailedPreconditionError(
337 "Message bundle apply requires a clean ROM; save or discard existing "
338 "in-memory changes before retrying");
344 const absl::Status load_status =
346 if (!load_status.ok()) {
350 return absl::FailedPreconditionError(
351 "Active ROM no longer matches the file on disk; reopen it before "
352 "applying a message bundle");
354 if (baseline !=
nullptr) {
355 *baseline = disk_rom.
vector();
357 return absl::OkStatus();
361 const std::filesystem::path& rom_path,
362 const std::vector<uint8_t>& expected_bytes) {
363 std::error_code file_size_ec;
364 const uintmax_t raw_file_size =
365 std::filesystem::file_size(rom_path, file_size_ec);
367 raw_file_size !=
static_cast<uintmax_t
>(expected_bytes.size())) {
368 return absl::AbortedError(
369 "ROM size changed on disk after message preflight; no bytes were "
370 "saved. Reopen the ROM and retry");
375 const absl::Status load_status =
377 if (!load_status.ok()) {
380 if (disk_rom.
vector() != expected_bytes) {
381 return absl::AbortedError(
382 "ROM changed on disk after message preflight; no bytes were saved. "
383 "Reopen the ROM and retry");
385 return absl::OkStatus();
390 const std::vector<std::pair<uint32_t, uint32_t>>& changed_ranges,
391 std::string* policy_warning) {
392 const auto conflicts =
394 if (conflicts.empty()) {
395 return absl::OkStatus();
401 return absl::PermissionDeniedError(absl::StrFormat(
402 "Vanilla message mutation blocked by hack manifest: %s; no message "
403 "bytes were written",
408 policy_warning !=
nullptr) {
409 *policy_warning = absl::StrFormat(
410 "Vanilla message write conflicts with hack manifest: %s", detail);
412 return absl::OkStatus();
431 size_t previous_length = 0;
432 size_t encoded_length = 0;
433 bool unchanged =
false;
440 size_t expected_count = 0;
441 bool secondary_length_pinned =
false;
442 std::vector<EditedVanillaMessage>
edited;
459 const std::vector<std::pair<
int, std::vector<uint8_t>>>& edits,
460 std::string* policy_warning) {
462 if (layout.vanilla_count <= 0) {
463 return absl::FailedPreconditionError(
464 "Hack manifest must declare a positive messages.vanilla_count "
465 "before applying vanilla messages");
467 if (rom.
size() >
static_cast<size_t>(std::numeric_limits<int>::max())) {
468 return absl::OutOfRangeError(
469 "ROM is too large for bounded vanilla-message parsing");
471 const size_t expected_count =
static_cast<size_t>(layout.vanilla_count);
477 if (!current_or.ok()) {
478 return current_or.status();
481 const auto dictionary =
483 if (dictionary.empty()) {
484 return absl::FailedPreconditionError(absl::StrFormat(
485 "Word dictionary table at PC 0x%06X is outside the ROM or malformed; "
486 "no message bytes were written",
489 std::vector<EditedVanillaMessage> edited;
490 for (
const auto& [
id, bytes] : edits) {
491 if (id < 0 || id >=
static_cast<int>(messages.size())) {
492 return absl::InvalidArgumentError(
493 absl::StrFormat(
"Vanilla message ID 0x%03X is outside 0x000-0x%03X",
494 id,
static_cast<int>(messages.size()) - 1));
497 std::vector<uint8_t> encoded = bytes;
501 if (!message.
Data.empty() &&
506 const size_t previous_length = message.
Data.size();
513 edited.push_back({id, previous_length, previous_length,
true});
518 edited.push_back({id, previous_length, message.
Data.size(),
false});
523 return plan_or.status();
526 std::move(current_or.value()),
529 layout.hook_address != 0,
533 const auto& new_writes = edit.plan.writes();
534 const auto& old_writes = edit.current.writes();
535 if (edit.secondary_length_pinned && new_writes.size() > 1 &&
536 old_writes.size() > 1 && new_writes[1].end() != old_writes[1].end()) {
537 return absl::FailedPreconditionError(absl::StrFormat(
538 "Vanilla text region 2 (PC 0x%06X) must stay %zu bytes because the "
539 "manifest chains expanded messages from its end (the byte at PC "
540 "0x%06X becomes the switch into the expanded bank); this edit makes "
541 "it %zu bytes. Keep messages in region 2 the same total length. No "
542 "message bytes were written",
544 old_writes[1].end() - 1, new_writes[1].bytes().size() - 1));
562 const uint64_t used =
563 index < new_writes.size() ? new_writes[index].bytes().size() : 0;
564 const uint64_t current_used =
565 index < old_writes.size() ? old_writes[index].bytes().size() : 0;
570 formatter.
AddField(
"capacity", capacity);
571 formatter.
AddField(
"current_used", current_used);
576 "free", pinned || used > capacity ? uint64_t{0} : capacity - used);
578 formatter.
AddField(
"length_pinned",
true);
584 uint64_t changed_bytes = 0;
587 changed_bytes += end - start;
588 formatter.
AddArrayItem(absl::StrFormat(
"0x%06X-0x%06X", start, end));
591 formatter.
AddField(
"changed_bytes", changed_bytes);
596 formatter.
AddHexField(
"id",
static_cast<uint64_t
>(message.id), 3);
597 formatter.
AddField(
"previous_length",
598 static_cast<uint64_t
>(message.previous_length));
599 formatter.
AddField(
"encoded_length",
600 static_cast<uint64_t
>(message.encoded_length));
601 if (message.unchanged) {
602 formatter.
AddField(
"unchanged",
true);
613 const std::vector<std::pair<
int, std::vector<uint8_t>>>& edits,
616 Rom* active_rom = rom;
617 if (active_rom ==
nullptr || !active_rom->
is_loaded()) {
619 if (!rom_path.has_value() || rom_path->empty()) {
620 const std::string global_rom_path = absl::GetFlag(FLAGS_rom);
621 if (!global_rom_path.empty()) {
622 rom_path = global_rom_path;
625 if (!rom_path.has_value() || rom_path->empty()) {
626 formatter.
AddField(
"apply_preflight",
"skipped: no --rom");
631 const absl::Status load_status =
633 if (!load_status.ok()) {
634 formatter.
AddField(
"apply_preflight",
635 absl::StrCat(
"error: ", load_status.message()));
638 active_rom = &owned_rom;
641 parser, *active_rom,
"Message bundle preflight");
642 if (!project_context_or.ok()) {
645 absl::StrCat(
"skipped: ", project_context_or.status().message()));
648 std::string policy_warning;
650 edits, &policy_warning);
652 formatter.
AddField(
"apply_preflight",
653 absl::StrCat(
"blocked: ", edit_or.status().message()));
656 formatter.
AddField(
"apply_preflight",
"ok");
657 if (!policy_warning.empty()) {
658 formatter.
AddField(
"write_policy_warning", policy_warning);
664 const Rom& active_rom,
const std::filesystem::path& saved_rom_path,
665 const std::optional<editor::VanillaMessageSavePlan>& vanilla_plan,
666 std::optional<size_t> expected_vanilla_count) {
670 const absl::Status reopen_status =
671 reopened_rom.
LoadFromFile(saved_rom_path.string(), load_options);
672 if (!reopen_status.ok()) {
673 return absl::DataLossError(absl::StrFormat(
674 "ROM was saved but could not be reopened for verification: %s. "
675 "Restore the required backup before retrying",
676 reopen_status.message()));
679 return absl::DataLossError(
680 "ROM was saved but external readback differs from the planned bytes. "
681 "Restore the required backup before retrying");
684 if (vanilla_plan.has_value()) {
685 if (reopened_rom.
size() >
686 static_cast<size_t>(std::numeric_limits<int>::max())) {
687 return absl::DataLossError(
688 "ROM was saved but is too large for bounded vanilla-message "
689 "readback. Restore the required backup before retrying");
691 const auto reopened_messages =
693 static_cast<int>(reopened_rom.
size()));
695 reopened_messages, expected_vanilla_count);
696 if (!readback_plan_or.ok() ||
697 readback_plan_or.value() != vanilla_plan.value()) {
698 const std::string detail =
699 readback_plan_or.ok()
700 ?
"serialized message bytes differ from the save plan"
701 : std::string(readback_plan_or.status().message());
702 return absl::DataLossError(absl::StrFormat(
703 "ROM was saved but vanilla-message readback failed: %s. Restore the "
704 "required backup before retrying",
709 return absl::OkStatus();
715 return absl::InvalidArgumentError(
716 absl::StrFormat(
"Invalid expanded message ID: %d",
id));
719 return absl::OutOfRangeError(absl::StrFormat(
720 "Expanded message ID %d is outside the manifest range (count=%d)",
id,
723 return absl::OkStatus();
729 message_count >
static_cast<size_t>(context.
message_limit)) {
730 return absl::FailedPreconditionError(absl::StrFormat(
731 "Expanded message bank contains %zu messages, exceeding the manifest "
732 "limit of %d; no changes were applied",
735 return absl::OkStatus();
746 auto limit = parser.
GetInt(
"limit").value_or(50);
753 if (limit >
static_cast<int>(messages.size())) {
754 limit =
static_cast<int>(messages.size());
759 formatter.
AddField(
"total_messages",
static_cast<int>(messages.size()));
760 formatter.
AddField(
"status",
"success");
763 for (
int i = 0; i < limit; ++i) {
764 const auto& msg = messages[i];
768 formatter.
AddField(
"text", msg.ContentsParsed);
769 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
775 return absl::OkStatus();
781 auto message_id_or = parser.
GetInt(
"id");
782 if (!message_id_or.ok()) {
783 return message_id_or.status();
785 int message_id = message_id_or.value();
789 if (message_id < 0 || message_id >=
static_cast<int>(messages.size())) {
790 return absl::NotFoundError(
791 absl::StrFormat(
"Message ID %d not found (max: %d)", message_id,
792 static_cast<int>(messages.size()) - 1));
795 const auto& msg = messages[message_id];
800 formatter.
AddField(
"text", msg.ContentsParsed);
801 formatter.
AddField(
"length",
static_cast<int>(msg.Data.size()));
804 return absl::OkStatus();
810 auto query = parser.
GetString(
"query").value();
811 auto limit = parser.
GetInt(
"limit").value_or(10);
822 formatter.
AddField(
"status",
"success");
824 std::string lowered_query = absl::AsciiStrToLower(query);
825 std::vector<const editor::MessageData*> matches;
826 for (
const auto& msg : messages) {
827 std::string lowered_text = absl::AsciiStrToLower(msg.ContentsParsed);
828 if (lowered_text.find(lowered_query) == std::string::npos) {
831 matches.push_back(&msg);
834 formatter.
AddField(
"matches_found",
static_cast<int>(matches.size()));
837 for (
const auto* msg : matches) {
838 if (limit > 0 && match_count >= limit) {
844 formatter.
AddField(
"text", msg->ContentsParsed);
851 return absl::OkStatus();
861 auto text = parser.
GetString(
"text").value();
864 auto bytes = parse_result.bytes;
868 for (
size_t i = 0; i < bytes.size(); ++i) {
871 hex_str += absl::StrFormat(
"%02X", bytes[i]);
877 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
881 for (uint8_t
byte : bytes) {
882 formatter.
AddArrayItem(absl::StrFormat(
"0x%02X",
byte));
888 if (!warnings.empty()) {
890 for (
const auto& warning : warnings) {
895 if (!parse_result.warnings.empty()) {
897 for (
const auto& warning : parse_result.warnings) {
902 if (!parse_result.errors.empty()) {
904 for (
const auto& error : parse_result.errors) {
912 return absl::OkStatus();
922 auto hex_input = parser.
GetString(
"hex").value();
925 std::vector<uint8_t> bytes;
926 std::istringstream hex_stream(hex_input);
927 std::string hex_byte;
928 while (hex_stream >> hex_byte) {
930 bytes.push_back(
static_cast<uint8_t
>(std::stoi(hex_byte,
nullptr, 16)));
931 }
catch (
const std::exception&) {
932 return absl::InvalidArgumentError(
933 absl::StrFormat(
"Invalid hex byte: '%s'", hex_byte));
939 for (
size_t i = 0; i < bytes.size(); ++i) {
940 uint8_t
byte = bytes[i];
944 if (cmd.has_value()) {
945 if (cmd->HasArgument && i + 1 < bytes.size()) {
946 decoded += cmd->GetParamToken(bytes[++i]);
948 decoded += cmd->GetParamToken();
958 formatter.
AddField(
"hex", hex_input);
959 formatter.
AddField(
"text", decoded);
960 formatter.
AddField(
"length",
static_cast<int>(bytes.size()));
963 return absl::OkStatus();
973 auto file_path = parser.
GetString(
"file").value();
976 std::ifstream file(file_path);
977 if (!file.is_open()) {
978 return absl::NotFoundError(
979 absl::StrFormat(
"Cannot open file: %s", file_path));
982 std::string content((std::istreambuf_iterator<char>(file)),
983 std::istreambuf_iterator<char>());
989 formatter.
AddField(
"file", file_path);
990 formatter.
AddField(
"messages_parsed",
static_cast<int>(messages.size()));
993 for (
const auto& [msg_id, body] : messages) {
996 auto bytes = parse_result.bytes;
1000 for (
size_t i = 0; i < bytes.size(); ++i) {
1003 hex_str += absl::StrFormat(
"%02X", bytes[i]);
1009 formatter.
AddField(
"hex", hex_str);
1010 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
1011 if (!warnings.empty()) {
1013 for (
const auto& warning : warnings) {
1018 if (!parse_result.warnings.empty()) {
1020 for (
const auto& warning : parse_result.warnings) {
1025 if (!parse_result.errors.empty()) {
1027 for (
const auto& error : parse_result.errors) {
1037 return absl::OkStatus();
1047 auto output_path = parser.
GetString(
"output").value();
1053 std::vector<std::pair<int, std::string>> msg_pairs;
1054 std::vector<std::string> labels;
1055 for (
const auto& msg : messages) {
1056 msg_pairs.push_back({msg.ID, msg.RawString});
1057 labels.push_back(absl::StrFormat(
"Message %02X", msg.ID));
1063 std::ofstream file(output_path);
1064 if (!file.is_open()) {
1065 return absl::InternalError(
1066 absl::StrFormat(
"Cannot write to file: %s", output_path));
1068 file << org_content;
1072 formatter.
AddField(
"output", output_path);
1073 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
1074 formatter.
AddField(
"status",
"success");
1077 return absl::OkStatus();
1087 auto output_path = parser.
GetString(
"output").value();
1088 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
1089 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
1090 return absl::InvalidArgumentError(
1091 absl::StrFormat(
"Invalid range: %s", range));
1094 std::vector<editor::MessageData> vanilla;
1095 std::vector<editor::MessageData> expanded;
1097 if (IncludeVanilla(range)) {
1102 if (IncludeExpanded(range)) {
1103 expanded = ReadExpandedMessages(*rom);
1113 formatter.
AddField(
"output", output_path);
1114 formatter.
AddField(
"range", range);
1115 formatter.
AddField(
"vanilla_count",
static_cast<int>(vanilla.size()));
1116 formatter.
AddField(
"expanded_count",
static_cast<int>(expanded.size()));
1117 formatter.
AddField(
"status",
"success");
1120 return absl::OkStatus();
1130 auto file_path = parser.
GetString(
"file").value();
1131 const bool apply = parser.
HasFlag(
"apply");
1132 const bool strict = parser.
HasFlag(
"strict");
1133#ifdef __EMSCRIPTEN__
1135 return absl::FailedPreconditionError(
1136 "message-import-bundle --apply is unavailable in WebAssembly because "
1137 "the browser filesystem cannot provide durable backup and readback "
1141 auto range = NormalizeRange(parser.
GetString(
"range").value_or(
"all"));
1142 if (!IncludeVanilla(range) && !IncludeExpanded(range)) {
1143 return absl::InvalidArgumentError(
1144 absl::StrFormat(
"Invalid range: %s", range));
1148 if (!entries_or.ok()) {
1149 return entries_or.status();
1151 auto entries = entries_or.value();
1154 formatter.
AddField(
"file", file_path);
1155 formatter.
AddField(
"range", range);
1156 formatter.
AddField(
"apply", apply);
1157 formatter.
AddField(
"strict", strict);
1158 formatter.
AddField(
"entries",
static_cast<int>(entries.size()));
1160 bool has_errors =
false;
1161 int parse_error_count = 0;
1162 int error_count = 0;
1163 int applied_updates = 0;
1164 bool has_vanilla_entries =
false;
1165 bool has_expanded_entries =
false;
1167 struct ParsedEntry {
1170 std::vector<std::string> line_warnings;
1172 std::vector<ParsedEntry> parsed_entries;
1173 parsed_entries.reserve(entries.size());
1175 for (
const auto& entry : entries) {
1177 !IncludeVanilla(range)) ||
1179 !IncludeExpanded(range))) {
1183 ParsedEntry parsed{entry,
1186 if (!parsed.parse.ok()) {
1188 parse_error_count +=
static_cast<int>(parsed.parse.errors.size());
1189 error_count +=
static_cast<int>(parsed.parse.errors.size());
1192 has_vanilla_entries =
true;
1194 has_expanded_entries =
true;
1196 parsed_entries.push_back(std::move(parsed));
1200 for (
const auto& parsed : parsed_entries) {
1202 formatter.
AddField(
"id", parsed.entry.id);
1203 formatter.
AddField(
"bank", BankLabel(parsed.entry.bank));
1204 formatter.
AddField(
"text", parsed.entry.text);
1205 formatter.
AddField(
"encoded_length",
1206 static_cast<int>(parsed.parse.bytes.size()));
1207 if (!parsed.line_warnings.empty()) {
1209 for (
const auto& warning : parsed.line_warnings) {
1214 if (!parsed.parse.warnings.empty()) {
1216 for (
const auto& warning : parsed.parse.warnings) {
1221 if (!parsed.parse.errors.empty()) {
1223 for (
const auto& error : parsed.parse.errors) {
1233 Rom* active_rom = rom;
1236 if (active_rom ==
nullptr || !active_rom->
is_loaded()) {
1237 auto rom_path = parser.
GetString(
"rom");
1238 if (!rom_path.has_value() || rom_path->empty()) {
1239 std::string global_rom_path = absl::GetFlag(FLAGS_rom);
1240 if (!global_rom_path.empty()) {
1241 rom_path = global_rom_path;
1244 if (!rom_path.has_value() || rom_path->empty()) {
1246 formatter.
AddField(
"status",
"error");
1248 "ROM not loaded; provide --rom when using --apply");
1249 formatter.
AddField(
"parse_error_count", parse_error_count);
1250 formatter.
AddField(
"error_count", error_count);
1252 return absl::OkStatus();
1255 if (!load_status.ok()) {
1257 formatter.
AddField(
"status",
"error");
1258 formatter.
AddField(
"error", std::string(load_status.message()));
1259 formatter.
AddField(
"parse_error_count", parse_error_count);
1260 formatter.
AddField(
"error_count", error_count);
1262 return absl::OkStatus();
1264 active_rom = &owned_rom;
1268 formatter.
AddField(
"status",
"error");
1269 formatter.
AddField(
"error",
"Parse errors present; no changes applied");
1270 formatter.
AddField(
"parse_error_count", parse_error_count);
1271 formatter.
AddField(
"error_count", error_count);
1273 if (strict && parse_error_count > 0) {
1276 return absl::FailedPreconditionError(
1277 "Strict validation failed due to parse errors");
1279 return absl::OkStatus();
1282 std::optional<ProjectMutationContext> project_context;
1283 std::vector<uint8_t> disk_baseline;
1284 if (has_vanilla_entries || has_expanded_entries) {
1285 const absl::Status snapshot_status =
1286 VerifyCleanDiskSnapshot(*active_rom, &disk_baseline);
1287 if (!snapshot_status.ok()) {
1289 formatter.
AddField(
"status",
"error");
1290 formatter.
AddField(
"error", std::string(snapshot_status.message()));
1291 formatter.
AddField(
"parse_error_count", parse_error_count);
1292 formatter.
AddField(
"error_count", error_count);
1294 return snapshot_status;
1297 auto project_context_or = LoadProjectMutationContext(
1298 parser, *active_rom,
"Message bundle apply");
1299 if (!project_context_or.ok()) {
1301 formatter.
AddField(
"status",
"error");
1303 std::string(project_context_or.status().message()));
1304 formatter.
AddField(
"parse_error_count", parse_error_count);
1305 formatter.
AddField(
"error_count", error_count);
1307 return project_context_or.status();
1309 project_context.emplace(std::move(project_context_or.value()));
1312 std::optional<ExpandedMutationContext> expanded_context;
1313 if (IncludeExpanded(range) && has_expanded_entries) {
1315 PreflightExpandedMutation(project_context->project, *active_rom);
1316 if (!context_or.ok()) {
1318 formatter.
AddField(
"status",
"error");
1319 formatter.
AddField(
"error", std::string(context_or.status().message()));
1320 formatter.
AddField(
"parse_error_count", parse_error_count);
1321 formatter.
AddField(
"error_count", error_count);
1323 return context_or.status();
1325 expanded_context.emplace(std::move(context_or.value()));
1331 std::vector<editor::MessageData> vanilla_messages;
1332 std::vector<std::pair<int, std::vector<uint8_t>>> vanilla_edits;
1333 if (IncludeVanilla(range) && has_vanilla_entries) {
1334 if (active_rom->
size() >
1335 static_cast<size_t>(std::numeric_limits<int>::max())) {
1337 return absl::OutOfRangeError(
1338 "ROM is too large for bounded vanilla-message parsing");
1342 static_cast<int>(active_rom->
size()));
1345 std::vector<std::string> expanded_texts;
1346 if (expanded_context.has_value()) {
1347 const auto expanded_messages = ReadExpandedMessages(
1348 *active_rom, expanded_context->start, expanded_context->
end);
1349 auto bank_size_status = ValidateExpandedMessageBankSize(
1350 expanded_messages.size(), *expanded_context);
1351 if (!bank_size_status.ok()) {
1352 formatter.
AddField(
"status",
"error");
1353 formatter.
AddField(
"error", std::string(bank_size_status.message()));
1355 return bank_size_status;
1357 expanded_texts.reserve(expanded_messages.size());
1358 for (
const auto& message : expanded_messages) {
1359 expanded_texts.push_back(message.RawString);
1363 for (
const auto& parsed : parsed_entries) {
1365 if (parsed.entry.id < 0 ||
1366 parsed.entry.id >=
static_cast<int>(vanilla_messages.size())) {
1371 vanilla_edits.emplace_back(parsed.entry.id, parsed.parse.bytes);
1373 if (!expanded_context.has_value()) {
1376 const auto id_status =
1377 ValidateExpandedMessageId(parsed.entry.id, *expanded_context);
1378 if (!id_status.ok()) {
1383 if (parsed.entry.id >=
static_cast<int>(expanded_texts.size())) {
1384 expanded_texts.resize(parsed.entry.id + 1);
1386 expanded_texts[parsed.entry.id] = parsed.entry.text;
1391 std::optional<editor::VanillaMessageSavePlan> vanilla_plan;
1392 std::optional<size_t> expected_vanilla_count;
1393 std::string write_policy_warning;
1394 if (!has_errors && IncludeVanilla(range) && has_vanilla_entries) {
1395 auto edit_or = PlanVanillaEdits(project_context->project, *active_rom,
1396 vanilla_edits, &write_policy_warning);
1397 if (!edit_or.ok()) {
1398 formatter.
AddField(
"status",
"error");
1399 formatter.
AddField(
"error", std::string(edit_or.status().message()));
1401 return edit_or.status();
1403 AddVanillaPlanReport(formatter, *edit_or);
1404 expected_vanilla_count = edit_or->expected_count;
1405 vanilla_plan.emplace(std::move(edit_or->plan));
1408 if (expanded_context.has_value() &&
1409 !expanded_context->policy_warning.empty()) {
1410 if (!write_policy_warning.empty()) {
1411 absl::StrAppend(&write_policy_warning,
"; ");
1413 absl::StrAppend(&write_policy_warning, expanded_context->policy_warning);
1415 if (!write_policy_warning.empty()) {
1416 formatter.
AddField(
"write_policy_warning", write_policy_warning);
1420 formatter.
AddField(
"status",
"error");
1421 formatter.
AddField(
"error",
"Invalid message IDs; no changes applied");
1424 if (vanilla_plan.has_value()) {
1428 formatter.
AddField(
"status",
"error");
1429 formatter.
AddField(
"error", std::string(status.message()));
1435 if (expanded_context.has_value()) {
1437 active_rom, expanded_context->start, expanded_context->
end,
1440 formatter.
AddField(
"status",
"error");
1441 formatter.
AddField(
"error", std::string(status.message()));
1447 if (active_rom->
dirty() && project_context.has_value()) {
1448 const absl::Status unchanged_status = VerifyDiskSnapshotUnchanged(
1449 project_context->canonical_rom_path, disk_baseline);
1450 if (!unchanged_status.ok()) {
1451 formatter.
AddField(
"status",
"error");
1452 formatter.
AddField(
"error", std::string(unchanged_status.message()));
1454 return unchanged_status;
1459 save_settings.
filename = project_context->canonical_rom_path.string();
1460 auto save_status = active_rom->
SaveToFile(save_settings);
1461 if (!save_status.ok()) {
1462 formatter.
AddField(
"status",
"error");
1463 formatter.
AddField(
"error", std::string(save_status.message()));
1470 if (project_context.has_value()) {
1471 const absl::Status readback_status =
1472 VerifySavedRom(*active_rom, project_context->canonical_rom_path,
1473 vanilla_plan, expected_vanilla_count);
1474 if (!readback_status.ok()) {
1475 formatter.
AddField(
"status",
"error");
1476 formatter.
AddField(
"error", std::string(readback_status.message()));
1478 return readback_status;
1481 formatter.
AddField(
"status",
"success");
1482 formatter.
AddField(
"applied_messages", applied_updates);
1483 if (project_context.has_value()) {
1484 formatter.
AddField(
"readback_verified",
true);
1488 if (!has_errors && IncludeVanilla(range) && has_vanilla_entries) {
1489 std::vector<std::pair<int, std::vector<uint8_t>>> vanilla_edits;
1490 for (
const auto& parsed : parsed_entries) {
1492 vanilla_edits.emplace_back(parsed.entry.id, parsed.parse.bytes);
1495 AddVanillaApplyPreflight(parser, rom, vanilla_edits, formatter);
1497 formatter.
AddField(
"status", has_errors ?
"error" :
"success");
1500 formatter.
AddField(
"error_count", error_count);
1501 formatter.
AddField(
"parse_error_count", parse_error_count);
1503 if (strict && parse_error_count > 0) {
1506 return absl::FailedPreconditionError(
"Strict validation failed");
1508 return absl::OkStatus();
1523 const std::string& text) {
1524#ifdef __EMSCRIPTEN__
1525 return absl::FailedPreconditionError(
1526 "message-write --range vanilla is unavailable in WebAssembly because "
1527 "the browser filesystem cannot provide durable backup and readback "
1530 if (rom ==
nullptr || !rom->
is_loaded()) {
1531 return absl::FailedPreconditionError(
"ROM not loaded; provide --rom");
1536 return absl::InvalidArgumentError(absl::StrCat(
1537 "Message text does not encode: ", absl::StrJoin(parse.
errors,
"; ")));
1540 std::vector<uint8_t> disk_baseline;
1545 std::vector<std::pair<int, std::vector<uint8_t>>> edits;
1546 edits.emplace_back(
id, parse.
bytes);
1547 std::string policy_warning;
1553 formatter.
AddHexField(
"id",
static_cast<uint64_t
>(
id), 3);
1554 formatter.
AddField(
"range",
"vanilla");
1557 if (!policy_warning.empty()) {
1558 formatter.
AddField(
"write_policy_warning", policy_warning);
1561 if (!line_warnings.empty()) {
1563 for (
const auto& warning : line_warnings) {
1571 auto finish = [&formatter](absl::Status status) {
1573 formatter.
AddField(
"status",
"success");
1574 formatter.
AddField(
"readback_verified",
true);
1576 formatter.
AddField(
"status",
"error");
1577 formatter.
AddField(
"error", std::string(status.message()));
1585 if (status.ok() && rom->
dirty()) {
1597 return finish(status);
1609 auto id_or = parser.
GetInt(
"id");
1611 return id_or.status();
1612 int msg_id = id_or.value();
1614 auto text = parser.
GetString(
"text").value();
1616 const std::string range =
1617 NormalizeRange(parser.
GetString(
"range").value_or(
"expanded"));
1618 if (range ==
"vanilla") {
1619 return WriteVanillaMessage(rom, parser, formatter, msg_id, text);
1621 if (range !=
"expanded") {
1622 return absl::InvalidArgumentError(absl::StrFormat(
1623 "message-write --range must be vanilla or expanded, got %s", range));
1631 if (bytes.empty() && !text.empty()) {
1632 return absl::InvalidArgumentError(
"Encoding produced no bytes");
1635 auto context_or = PreflightExpandedMutation(parser, *rom);
1636 if (!context_or.ok()) {
1637 return context_or.status();
1639 auto context = std::move(context_or.value());
1640 auto id_status = ValidateExpandedMessageId(msg_id, context);
1641 if (!id_status.ok()) {
1646 auto expanded = ReadExpandedMessages(*rom, context.start, context.
end);
1647 auto bank_size_status =
1648 ValidateExpandedMessageBankSize(expanded.size(), context);
1649 if (!bank_size_status.ok()) {
1650 return bank_size_status;
1654 std::vector<std::string> all_texts;
1655 all_texts.reserve(expanded.size());
1656 for (
const auto& msg : expanded) {
1657 all_texts.push_back(msg.RawString);
1659 if (msg_id >=
static_cast<int>(all_texts.size())) {
1660 all_texts.resize(msg_id + 1);
1662 all_texts[msg_id] = text;
1675 formatter.
AddField(
"encoded_length",
static_cast<int>(bytes.size()));
1676 formatter.
AddField(
"status",
"success");
1677 if (!context.policy_warning.empty()) {
1678 formatter.
AddField(
"write_policy_warning", context.policy_warning);
1680 if (!warnings.empty()) {
1682 for (
const auto& warning : warnings) {
1689 return absl::OkStatus();
1699 auto output_path = parser.
GetString(
"output").value();
1700 auto range = parser.
GetString(
"range").value_or(
"expanded");
1702 int start, end_addr;
1703 if (range ==
"expanded") {
1712 const uint8_t* data = rom->
data();
1713 int data_end = start;
1714 while (data_end <= end_addr && data[data_end] != 0xFF) {
1717 if (data_end <= end_addr) {
1721 int size = data_end - start;
1723 std::ofstream file(output_path, std::ios::binary);
1724 if (!file.is_open()) {
1725 return absl::InternalError(
1726 absl::StrFormat(
"Cannot write to file: %s", output_path));
1728 file.write(
reinterpret_cast<const char*
>(data + start), size);
1732 formatter.
AddField(
"output", output_path);
1733 formatter.
AddField(
"range", range);
1735 formatter.
AddHexField(
"end_address", data_end - 1, 6);
1736 formatter.
AddField(
"size_bytes", size);
1737 formatter.
AddField(
"status",
"success");
1740 return absl::OkStatus();
1750 auto output_path = parser.
GetString(
"output").value();
1751 auto range = parser.
GetString(
"range").value_or(
"expanded");
1755 if (range ==
"expanded") {
1757 snes_addr = 0x2F8000;
1760 snes_addr = 0x1C0000;
1767 std::ofstream file(output_path);
1768 if (!file.is_open()) {
1769 return absl::InternalError(
1770 absl::StrFormat(
"Cannot write to file: %s", output_path));
1774 file <<
"; Auto-generated message data\n";
1775 file << absl::StrFormat(
"; Source: %s region\n", range);
1776 file << absl::StrFormat(
"; Messages: %d\n\n", messages.size());
1777 file << absl::StrFormat(
"org $%06X\n\n", snes_addr);
1780 for (
const auto& msg : messages) {
1781 file << absl::StrFormat(
"; Message $%02X: %s\n", msg.ID,
1782 msg.ContentsParsed.substr(0, 60));
1784 for (
size_t i = 0; i < msg.Data.size(); ++i) {
1787 file << absl::StrFormat(
"$%02X", msg.Data[i]);
1789 file <<
", $7F ; terminator\n\n";
1793 file <<
"db $FF ; end of message data\n";
1797 formatter.
AddField(
"output", output_path);
1798 formatter.
AddField(
"range", range);
1799 formatter.
AddField(
"messages_exported",
static_cast<int>(messages.size()));
1800 formatter.
AddField(
"status",
"success");
1803 return absl::OkStatus();
1809 const std::string project_path = parser.
GetString(
"project").value();
1810 const std::string incoming_path = parser.
GetString(
"file").value();
1811 const bool write = parser.
HasFlag(
"write");
1815 absl::Status open_status;
1817 ScopedHackManifestBindingRestore restore_manifest(resource_labels);
1819 open_status = source_project.
Open(project_path);
1820 }
catch (
const std::exception& error) {
1821 return absl::InvalidArgumentError(absl::StrFormat(
1822 "Cannot load project '%s': %s", project_path, error.what()));
1824 return absl::InvalidArgumentError(absl::StrFormat(
1825 "Cannot load project '%s': unknown project parse error",
1829 if (!open_status.ok()) {
1830 return absl::Status(open_status.code(),
1831 absl::StrFormat(
"Cannot load project '%s': %s",
1832 project_path, open_status.message()));
1834 if (absl::Status manifest_status =
1835 EnsureConfiguredHackManifestLoaded(&source_project, project_path);
1836 !manifest_status.ok()) {
1837 return manifest_status;
1842 .expected_source_sha256 =
1843 parser.
GetString(
"expected-source-sha256").value_or(
""),
1847 if (!result_or.ok()) {
1848 return result_or.status();
1850 const auto& result = *result_or;
1853 formatter.
AddField(
"status",
"success");
1854 formatter.
AddField(
"mode", write ?
"write" :
"dry-run");
1855 formatter.
AddField(
"changed", result.changed);
1856 formatter.
AddField(
"wrote", result.wrote);
1857 formatter.
AddField(
"canonical_bundle", result.canonical_bundle_path.string());
1858 formatter.
AddField(
"generated_asm_include",
1859 result.generated_asm_include_path.string());
1860 formatter.
AddHexField(
"first_expanded_id", result.first_expanded_id, 3);
1861 formatter.
AddHexField(
"last_expanded_id", result.last_expanded_id, 3);
1862 formatter.
AddField(
"expanded_count", result.expanded_count);
1863 formatter.
AddField(
"incoming_updates", result.incoming_updates);
1864 formatter.
AddField(
"encoded_size",
static_cast<int>(result.encoded_size));
1865 formatter.
AddField(
"capacity",
static_cast<int>(result.capacity));
1866 formatter.
AddField(
"source_sha256_before", result.source_sha256_before);
1867 formatter.
AddField(
"proposed_source_sha256", result.proposed_source_sha256);
1869 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())
const auto & vector() const
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.
const MessageLayout & message_layout() const
absl::Status LoadFromFile(const std::string &filepath)
Load manifest from a JSON file path.
std::vector< WriteConflict > AnalyzePcWriteRanges(const std::vector< std::pair< uint32_t, uint32_t > > &pc_ranges) const
Analyze a set of PC-offset ranges for write conflicts.
bool loaded() const
Check if the manifest has been loaded.
const std::vector< VanillaMessageWrite > & writes() const
Unified interface for accessing resource labels with project overrides.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
ABSL_DECLARE_FLAG(std::string, rom)
absl::Status EnsureConfiguredHackManifestLoaded(project::YazeProject *project, absl::string_view project_path)
absl::StatusOr< VanillaEditPlan > PlanVanillaEdits(const project::YazeProject &yaze_project, const Rom &rom, const std::vector< std::pair< int, std::vector< uint8_t > > > &edits, std::string *policy_warning)
std::string BankLabel(editor::MessageBank bank)
absl::Status ValidateExpandedMessageBankSize(size_t message_count, const ExpandedMutationContext &context)
void AddVanillaApplyPreflight(const resources::ArgumentParser &parser, Rom *rom, const std::vector< std::pair< int, std::vector< uint8_t > > > &edits, resources::OutputFormatter &formatter)
absl::Status VerifyDiskSnapshotUnchanged(const std::filesystem::path &rom_path, const std::vector< uint8_t > &expected_bytes)
void AddVanillaPlanReport(resources::OutputFormatter &formatter, const VanillaEditPlan &edit)
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)
absl::Status WriteVanillaMessage(Rom *rom, const resources::ArgumentParser &parser, resources::OutputFormatter &formatter, int id, const std::string &text)
std::string NormalizeRange(const std::string &range)
absl::StatusOr< ProjectMutationContext > LoadProjectMutationContext(const resources::ArgumentParser &parser, const Rom &rom, absl::string_view mutation_scope)
std::string FormatManifestConflict(const core::WriteConflict &conflict)
constexpr VanillaTextRegion kVanillaTextRegions[]
absl::StatusOr< ExpandedMutationContext > PreflightExpandedMutation(const project::YazeProject &yaze_project, const Rom &rom)
absl::Status VerifyCleanDiskSnapshot(const Rom &rom, std::vector< uint8_t > *baseline)
absl::Status ValidateVanillaMutation(const project::YazeProject &yaze_project, const std::vector< std::pair< uint32_t, uint32_t > > &changed_ranges, std::string *policy_warning)
absl::Status VerifySavedRom(const Rom &active_rom, const std::filesystem::path &saved_rom_path, const std::optional< editor::VanillaMessageSavePlan > &vanilla_plan, std::optional< size_t > expected_vanilla_count)
std::vector< editor::MessageData > ReadExpandedMessages(const Rom &rom)
std::string AddressOwnershipToString(AddressOwnership ownership)
int GetExpandedTextDataStart()
std::string ParseTextDataByte(uint8_t value)
std::vector< uint8_t > CompressMessageWithDictionary(const std::vector< uint8_t > &data, const std::vector< std::vector< uint8_t > > &dictionary)
std::string MessageBankToString(MessageBank bank)
absl::Status WriteExpandedTextData(Rom *rom, int start, int end, const std::vector< std::string > &messages)
constexpr int kPointersDictionaries
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)
constexpr int kTextData2End
constexpr uint8_t kBankSwitchCommand
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)
absl::StatusOr< VanillaMessageSavePlan > BuildVanillaMessageSavePlan(const std::vector< MessageData > &messages, std::optional< size_t > expected_message_count)
constexpr char kOracleExpandedRomWriteRefusal[]
std::vector< MessageData > ReadExpandedTextData(uint8_t *rom, int pos)
std::vector< uint8_t > ExpandMessageDictionary(const std::vector< uint8_t > &data, const std::vector< std::vector< uint8_t > > &dictionary)
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::vector< uint8_t > > ReadDictionaryEntryBytes(const uint8_t *rom, size_t rom_size)
std::vector< std::pair< int, std::string > > ParseOrgContent(const std::string &content)
absl::Status ApplyVanillaMessageSavePlan(Rom *rom, const VanillaMessageSavePlan &plan)
constexpr int kTextDataEnd
ResourceLabelProvider & GetResourceLabels()
Get the global ResourceLabelProvider instance.
uint32_t PcToSnes(uint32_t addr)
constexpr size_t kCopierHeaderSize
Size of the SMC/SWC copier header that some ROM dumps start with.
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
bool load_resource_labels
std::string policy_warning
project::YazeProject project
std::filesystem::path canonical_rom_path
editor::VanillaMessageSavePlan current
bool secondary_length_pinned
editor::VanillaMessageSavePlan plan
std::vector< std::pair< uint32_t, uint32_t > > changed_ranges
std::vector< EditedVanillaMessage > edited
A conflict detected when yaze wants to write to an ASM-owned address.
AddressOwnership ownership
std::vector< uint8_t > Data
std::vector< uint8_t > DataParsed
std::vector< uint8_t > bytes
std::vector< std::string > errors
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)