11#include "absl/strings/ascii.h"
12#include "absl/strings/str_format.h"
13#include "absl/strings/str_split.h"
27 const unsigned char uc =
static_cast<unsigned char>(c);
28 return std::isalnum(uc) || c ==
'_';
32 const bool left_boundary = (pos == 0) || !
IsWordChar(text[pos - 1]);
33 const size_t right_index = pos + len;
34 const bool right_boundary =
35 (right_index >= text.size()) || !
IsWordChar(text[right_index]);
36 return left_boundary && right_boundary;
39std::string LowercaseCopy(std::string_view input) {
40 std::string lowered(input);
41 for (
char& c : lowered) {
42 c =
static_cast<char>(std::tolower(
static_cast<unsigned char>(c)));
62 uint8_t best_match = 0xFF;
63 const wchar_t target =
64 static_cast<wchar_t>(
static_cast<unsigned char>(value));
65 for (
const auto& [key, char_value] : CharEncoder) {
66 if (char_value != target) {
69 if (best_match == 0xFF || key < best_match) {
77 if (value <
DICTOFF || value == 0xFF) {
84 for (
const auto& text_element : TextCommands) {
85 if (text_element.ID == b) {
93 auto it = std::ranges::find_if(SpecialChars,
95 return text_element.
ID == value;
97 if (it != SpecialChars.end()) {
105 std::vector<TextElement> commands_and_chars = TextCommands;
106 commands_and_chars.insert(commands_and_chars.end(), SpecialChars.begin(),
108 for (
auto& text_element : commands_and_chars) {
109 match = text_element.MatchMe(str);
110 if (match.size() > 0) {
111 if (text_element.HasArgument) {
112 std::string arg = match[1].str().substr(1);
114 return ParsedElement(text_element, std::stoi(arg,
nullptr, 16));
115 }
catch (
const std::invalid_argument& e) {
116 util::logf(
"Error parsing argument for %s: %s",
117 text_element.GenericToken.c_str(), arg.c_str());
119 }
catch (
const std::out_of_range& e) {
120 util::logf(
"Argument out of range for %s: %s",
121 text_element.GenericToken.c_str(), arg.c_str());
130 const auto dictionary_element =
136 const std::regex dictionary_pattern(
138 std::regex_match(str, match, dictionary_pattern);
139 if (match.size() > 0) {
141 std::string dict_arg = match[1].str();
142 const int dictionary_index = std::stoi(dict_arg,
nullptr, 16);
144 util::logf(
"Dictionary index out of range: %s", dict_arg.c_str());
148 }
catch (
const std::exception& e) {
149 util::logf(
"Error parsing dictionary token: %s", match[1].str().c_str());
157 if (CharEncoder.contains(value)) {
158 char c = CharEncoder.at(value);
159 std::string str =
"";
166 text_element != std::nullopt) {
167 return text_element->GenericToken;
172 special_element != std::nullopt) {
173 return special_element->GenericToken;
178 if (dictionary >= 0) {
180 static_cast<unsigned char>(dictionary));
187 std::vector<uint8_t> bytes;
188 std::string temp_string = std::move(str);
190 while (pos < temp_string.size()) {
192 if (temp_string[pos] ==
'[') {
193 int next = temp_string.find(
']', pos);
201 if (!parsedElement.
Active) {
202 util::logf(
"Error parsing message: %s", temp_string);
205 bytes.push_back(parsedElement.
Value);
207 bytes.push_back(parsedElement.
Parent.
ID);
210 bytes.push_back(parsedElement.
Value);
230 std::string temp_string(str);
232 bool warned_newline =
false;
234 while (pos < temp_string.size()) {
235 char current = temp_string[pos];
236 if (current ==
'\r' || current ==
'\n') {
237 if (!warned_newline) {
239 "Literal newlines are ignored; use [1], [2], [3], [V], or [K] "
240 "tokens for line breaks.");
241 warned_newline =
true;
247 if (current ==
'[') {
248 size_t close = temp_string.find(
']', pos);
249 if (close == std::string::npos) {
251 absl::StrFormat(
"Unclosed token starting at position %zu", pos));
255 std::string token = temp_string.substr(pos, close - pos + 1);
258 if (!parsed_element.
Active) {
259 result.
errors.push_back(absl::StrFormat(
"Unknown token: %s", token));
266 result.
errors.push_back(absl::StrFormat(
"Unknown token: %s", token));
287 result.
errors.push_back(absl::StrFormat(
288 "Unsupported character '%c' at position %zu", current, pos));
293 result.
bytes.push_back(bb);
311 const std::string lowered = absl::AsciiStrToLower(std::string(value));
312 if (lowered ==
"vanilla") {
315 if (lowered ==
"expanded") {
318 return absl::InvalidArgumentError(
319 absl::StrFormat(
"Unknown message bank: %s", std::string(value)));
323 std::vector<DictionaryEntry> AllDictionaries;
325 std::vector<uint8_t> bytes;
326 std::stringstream stringBuilder;
332 int temppush_backress =
337 while (address < temppush_backress) {
338 uint8_t uint8_tDictionary = rom->
data()[address++];
339 bytes.push_back(uint8_tDictionary);
343 AllDictionaries.push_back(
DictionaryEntry{(uint8_t)i, stringBuilder.str()});
346 std::ranges::sort(AllDictionaries,
351 return AllDictionaries;
355 std::string str,
const std::vector<DictionaryEntry>& dictionary) {
356 std::string temp = std::move(str);
357 for (
const auto& entry : dictionary) {
358 if (entry.ContainedInString(temp)) {
359 temp = entry.ReplaceInstancesOfIn(temp);
366 std::string_view query,
size_t start_pos,
368 bool match_whole_word) {
369 if (query.empty() || start_pos > text.size()) {
373 std::string haystack_storage;
374 std::string query_storage;
375 std::string_view haystack = text;
376 std::string_view needle = query;
377 if (!case_sensitive) {
378 haystack_storage = LowercaseCopy(text);
379 query_storage = LowercaseCopy(query);
380 haystack = haystack_storage;
381 needle = query_storage;
384 size_t pos = haystack.find(needle, start_pos);
385 while (pos != std::string::npos) {
386 if (!match_whole_word || MatchesWholeWordAt(text, pos, query.size())) {
389 pos = haystack.find(needle, pos + 1);
396 std::string_view replacement,
size_t start_pos,
397 bool replace_all,
bool case_sensitive,
398 bool match_whole_word,
size_t* first_replaced_pos) {
399 if (!text || query.empty() || start_pos > text->size()) {
403 int replacements = 0;
404 size_t cursor = start_pos;
406 const auto match_pos =
407 FindTextMatch(*text, query, cursor, case_sensitive, match_whole_word);
408 if (!match_pos.has_value()) {
412 text->replace(*match_pos, query.size(), replacement);
413 if (replacements == 0 && first_replaced_pos !=
nullptr) {
414 *first_replaced_pos = *match_pos;
418 cursor = *match_pos + replacement.size();
423 if (cursor > text->size()) {
432 uint8_t value,
const std::vector<DictionaryEntry>& dictionary) {
433 for (
const auto& entry : dictionary) {
434 if (entry.ID +
DICTOFF == value) {
442 const std::vector<uint8_t>& rom_data,
int* current_pos) {
443 if (current_pos ==
nullptr) {
444 return absl::InvalidArgumentError(
"current_pos is null");
446 if (*current_pos < 0 ||
447 static_cast<size_t>(*current_pos) >= rom_data.size()) {
448 return absl::OutOfRangeError(
"current_pos is out of range");
452 int pos = *current_pos;
453 uint8_t current_byte;
454 std::vector<uint8_t> temp_bytes_raw;
455 std::vector<uint8_t> temp_bytes_parsed;
456 std::string current_message_raw;
457 std::string current_message_parsed;
460 while (pos <
static_cast<int>(rom_data.size())) {
461 current_byte = rom_data[pos++];
464 message_data.
ID = message_data.
ID + 1;
466 message_data.
RawString = current_message_raw;
467 message_data.
Data = temp_bytes_raw;
471 temp_bytes_raw.clear();
472 temp_bytes_parsed.clear();
473 current_message_raw.clear();
474 current_message_parsed.clear();
478 }
else if (current_byte == 0xFF) {
479 return absl::InvalidArgumentError(
"message terminator not found");
482 temp_bytes_raw.push_back(current_byte);
486 if (text_element != std::nullopt) {
487 temp_bytes_parsed.push_back(current_byte);
488 if (text_element->HasArgument) {
489 if (pos >=
static_cast<int>(rom_data.size())) {
490 return absl::OutOfRangeError(
"message command argument out of range");
492 uint8_t arg_byte = rom_data[pos++];
493 temp_bytes_raw.push_back(arg_byte);
494 temp_bytes_parsed.push_back(arg_byte);
495 current_message_raw.append(text_element->GetParamToken(arg_byte));
496 current_message_parsed.append(text_element->GetParamToken(arg_byte));
498 current_message_raw.append(text_element->GetParamToken());
499 current_message_parsed.append(text_element->GetParamToken());
506 special_element != std::nullopt) {
507 current_message_raw.append(special_element->GetParamToken());
508 current_message_parsed.append(special_element->GetParamToken());
509 temp_bytes_parsed.push_back(current_byte);
515 if (dictionary >= 0) {
516 std::string token = absl::StrFormat(
518 current_message_raw.append(token);
519 current_message_parsed.append(token);
520 temp_bytes_parsed.push_back(current_byte);
525 if (CharEncoder.contains(current_byte)) {
526 std::string str =
"";
527 str.push_back(CharEncoder.at(current_byte));
528 current_message_raw.append(str);
529 current_message_parsed.append(str);
530 temp_bytes_parsed.push_back(current_byte);
535 return absl::InvalidArgumentError(
"message terminator not found");
539 std::vector<MessageData>& message_data,
540 const std::vector<DictionaryEntry>& dictionary_entries) {
541 std::vector<std::string> parsed_messages;
543 for (
auto& message : message_data) {
544 std::string parsed_message =
"";
546 for (
size_t pos = 0; pos < message.Data.size(); ++pos) {
547 uint8_t
byte = message.Data[pos];
551 if (text_element != std::nullopt) {
554 text_element->ID ==
kLine3) {
555 parsed_message.append(
"\n");
558 if (text_element->HasArgument && pos + 1 < message.Data.size()) {
559 uint8_t arg_byte = message.Data[pos + 1];
560 parsed_message.append(text_element->GetParamToken(arg_byte));
563 parsed_message.append(text_element->GetParamToken());
570 if (special_element != std::nullopt) {
571 parsed_message.append(special_element->GetParamToken());
578 for (
const auto& entry : dictionary_entries) {
579 if (entry.ID ==
byte -
DICTOFF) {
584 parsed_message.append(dic_entry.
Contents);
589 if (CharEncoder.contains(
byte)) {
590 parsed_message.push_back(CharEncoder.at(
byte));
593 parsed_messages.push_back(parsed_message);
596 return parsed_messages;
600 bool allow_bank_switch) {
601 std::vector<MessageData> list_of_texts;
605 return list_of_texts;
607 if (max_pos > 0 && (pos < 0 || pos >= max_pos)) {
608 return list_of_texts;
611 std::vector<uint8_t> raw_message;
612 std::vector<uint8_t> parsed_message;
613 std::string current_raw_message;
614 std::string current_parsed_message;
616 bool did_bank_switch =
false;
617 uint8_t current_byte = 0;
618 while (current_byte != 0xFF) {
619 if (max_pos > 0 && (pos < 0 || pos >= max_pos))
621 current_byte = rom[pos++];
623 list_of_texts.push_back(
624 MessageData(message_id++, pos, current_raw_message, raw_message,
625 current_parsed_message, parsed_message));
627 parsed_message.clear();
628 current_raw_message.clear();
629 current_parsed_message.clear();
631 }
else if (current_byte == 0xFF) {
635 raw_message.push_back(current_byte);
638 if (text_element != std::nullopt) {
639 parsed_message.push_back(current_byte);
640 if (text_element->HasArgument) {
641 if (max_pos > 0 && (pos < 0 || pos >= max_pos))
643 current_byte = rom[pos++];
644 raw_message.push_back(current_byte);
645 parsed_message.push_back(current_byte);
648 current_raw_message.append(text_element->GetParamToken(current_byte));
649 current_parsed_message.append(text_element->GetParamToken(current_byte));
651 if (allow_bank_switch && text_element->Token ==
kBankToken &&
653 did_bank_switch =
true;
662 if (special_element != std::nullopt) {
663 current_raw_message.append(special_element->GetParamToken());
664 current_parsed_message.append(special_element->GetParamToken());
665 parsed_message.push_back(current_byte);
671 if (dictionary >= 0) {
672 current_raw_message.append(
674 static_cast<unsigned char>(dictionary)));
682 if (ptr_a < 0 || ptr_a + 1 >= max_pos || ptr_b < 0 ||
683 ptr_b + 1 >= max_pos) {
690 uint32_t address_end =
694 const uint32_t max_u =
static_cast<uint32_t
>(max_pos);
695 if (address >= max_u || address_end > max_u || address_end < address) {
700 for (uint32_t i = address; i < address_end; i++) {
701 if (max_pos > 0 && i >=
static_cast<uint32_t
>(max_pos))
703 parsed_message.push_back(rom[i]);
711 if (CharEncoder.contains(current_byte)) {
712 std::string str =
"";
713 str.push_back(CharEncoder.at(current_byte));
714 current_raw_message.append(str);
715 current_parsed_message.append(str);
716 parsed_message.push_back(current_byte);
720 return list_of_texts;
724 std::vector<std::string>& parsed_messages,
725 std::vector<MessageData>& expanded_messages,
726 std::vector<DictionaryEntry>& dictionary) {
727 static Rom expanded_message_rom;
728 if (!expanded_message_rom.
LoadFromFile(expanded_message_path).ok()) {
729 return absl::InternalError(
"Failed to load expanded message ROM");
732 auto parsed_expanded_messages =
735 for (
const auto& expanded_message : expanded_messages) {
736 parsed_messages.push_back(parsed_expanded_messages[expanded_message.ID]);
738 return absl::OkStatus();
742 const std::vector<MessageData>& messages) {
743 nlohmann::json j = nlohmann::json::array();
744 for (
const auto& msg : messages) {
745 j.push_back({{
"id", msg.ID},
746 {
"address", msg.Address},
747 {
"raw_string", msg.RawString},
748 {
"parsed_string", msg.ContentsParsed}});
754 const std::vector<MessageData>& messages) {
757 std::ofstream file(path);
758 if (!file.is_open()) {
759 return absl::InternalError(
760 absl::StrFormat(
"Failed to open file for writing: %s", path));
763 return absl::OkStatus();
764 }
catch (
const std::exception& e) {
765 return absl::InternalError(
766 absl::StrFormat(
"JSON export failed: %s", e.what()));
771 const std::vector<MessageData>& vanilla,
772 const std::vector<MessageData>& expanded) {
774 j[
"format"] =
"yaze-message-bundle";
776 j[
"counts"] = {{
"vanilla", vanilla.size()}, {
"expanded", expanded.size()}};
777 j[
"messages"] = nlohmann::json::array();
779 auto append_messages = [&j](
const std::vector<MessageData>& messages,
781 for (
const auto& msg : messages) {
782 nlohmann::json entry;
783 entry[
"id"] = msg.ID;
785 entry[
"address"] = msg.Address;
786 entry[
"raw"] = msg.RawString;
787 entry[
"parsed"] = msg.ContentsParsed;
789 !msg.RawString.empty() ? msg.RawString : msg.ContentsParsed;
790 entry[
"length"] = msg.Data.size();
791 const std::string validation_text =
792 !msg.RawString.empty() ? msg.RawString : msg.ContentsParsed;
794 if (!warnings.empty()) {
795 entry[
"line_width_warnings"] = warnings;
797 j[
"messages"].push_back(entry);
808 const std::string& path,
const std::vector<MessageData>& vanilla,
809 const std::vector<MessageData>& expanded) {
812 std::ofstream file(path);
813 if (!file.is_open()) {
814 return absl::InternalError(
815 absl::StrFormat(
"Failed to open file for writing: %s", path));
818 return absl::OkStatus();
819 }
catch (
const std::exception& e) {
820 return absl::InternalError(
821 absl::StrFormat(
"Message bundle export failed: %s", e.what()));
827 const nlohmann::json& entry,
MessageBank default_bank) {
828 if (!entry.is_object()) {
829 return absl::InvalidArgumentError(
"Message entry must be an object");
833 result.
id = entry.value(
"id", -1);
835 return absl::InvalidArgumentError(
"Message entry missing valid id");
838 if (entry.contains(
"bank")) {
839 if (!entry[
"bank"].is_string()) {
840 return absl::InvalidArgumentError(
"Message entry bank must be string");
844 return bank_or.status();
846 result.
bank = bank_or.value();
848 result.
bank = default_bank;
851 if (entry.contains(
"raw") && entry[
"raw"].is_string()) {
852 result.
raw = entry[
"raw"].get<std::string>();
853 }
else if (entry.contains(
"raw_string") && entry[
"raw_string"].is_string()) {
854 result.
raw = entry[
"raw_string"].get<std::string>();
857 if (entry.contains(
"parsed") && entry[
"parsed"].is_string()) {
858 result.
parsed = entry[
"parsed"].get<std::string>();
859 }
else if (entry.contains(
"parsed_string") &&
860 entry[
"parsed_string"].is_string()) {
861 result.
parsed = entry[
"parsed_string"].get<std::string>();
864 if (entry.contains(
"text") && entry[
"text"].is_string()) {
865 result.
text = entry[
"text"].get<std::string>();
868 if (result.
text.empty()) {
869 if (!result.
raw.empty()) {
871 }
else if (!result.
parsed.empty()) {
876 if (result.
text.empty()) {
877 return absl::InvalidArgumentError(
878 absl::StrFormat(
"Message entry %d missing text content", result.
id));
886 const nlohmann::json& json) {
887 std::vector<MessageBundleEntry> entries;
889 if (json.is_array()) {
890 for (
const auto& entry : json) {
892 if (!parsed_or.ok()) {
893 return parsed_or.status();
895 entries.push_back(parsed_or.value());
900 if (!json.is_object()) {
901 return absl::InvalidArgumentError(
"Message bundle JSON must be object");
904 if (json.contains(
"version") && json[
"version"].is_number_integer()) {
905 int version = json[
"version"].get<
int>();
907 return absl::InvalidArgumentError(
908 absl::StrFormat(
"Unsupported message bundle version: %d", version));
912 if (!json.contains(
"messages") || !json[
"messages"].is_array()) {
913 return absl::InvalidArgumentError(
"Message bundle missing messages array");
916 for (
const auto& entry : json[
"messages"]) {
918 if (!parsed_or.ok()) {
919 return parsed_or.status();
921 entries.push_back(parsed_or.value());
928 const std::string& path) {
929 std::ifstream file(path);
930 if (!file.is_open()) {
931 return absl::NotFoundError(
932 absl::StrFormat(
"Cannot open message bundle: %s", path));
938 }
catch (
const std::exception& e) {
939 return absl::InvalidArgumentError(
940 absl::StrFormat(
"Failed to parse JSON: %s", e.what()));
951 std::vector<std::string> warnings;
956 int visible_chars = 0;
957 bool all_spaces_this_line =
true;
960 while (pos < message.size()) {
961 if (message[pos] ==
'[') {
963 size_t close = message.find(
']', pos);
964 if (close == std::string::npos)
967 std::string token = message.substr(pos, close - pos + 1);
972 if (token ==
"[1]" || token ==
"[2]" || token ==
"[3]" ||
973 token ==
"[V]" || token ==
"[K]") {
976 if (visible_chars >
kMaxLineWidth && !all_spaces_this_line) {
978 absl::StrFormat(
"Line %d: %d visible characters (max %d)",
983 all_spaces_this_line =
true;
994 if (message[pos] !=
' ')
995 all_spaces_this_line =
false;
1001 if (visible_chars >
kMaxLineWidth && !all_spaces_this_line) {
1003 absl::StrFormat(
"Line %d: %d visible characters (max %d)", line_num,
1015 const std::string& line) {
1018 if (line.size() < 6 || line[0] !=
'*' || line[1] !=
'*' || line[2] !=
' ') {
1019 return std::nullopt;
1023 size_t sep = line.find(
" - ", 3);
1024 if (sep == std::string::npos) {
1025 return std::nullopt;
1029 std::string hex_id = line.substr(3, sep - 3);
1032 message_id = std::stoi(hex_id,
nullptr, 16);
1033 }
catch (
const std::exception&) {
1034 return std::nullopt;
1038 std::string label = line.substr(sep + 3);
1040 return std::make_pair(message_id, label);
1044 const std::string& content) {
1045 std::vector<std::pair<int, std::string>> messages;
1046 std::istringstream stream(content);
1049 int current_id = -1;
1050 std::string current_body;
1052 while (std::getline(stream, line)) {
1055 if (header.has_value()) {
1057 if (current_id >= 0) {
1059 while (!current_body.empty() && current_body.back() ==
'\n') {
1060 current_body.pop_back();
1062 messages.push_back({current_id, current_body});
1065 current_id = header->first;
1066 current_body.clear();
1071 if (!line.empty() && line[0] ==
'*' &&
1072 (line.size() < 2 || line[1] !=
'*')) {
1077 if (current_id >= 0) {
1078 if (!current_body.empty()) {
1079 current_body +=
"\n";
1081 current_body += line;
1086 if (current_id >= 0) {
1087 while (!current_body.empty() && current_body.back() ==
'\n') {
1088 current_body.pop_back();
1090 messages.push_back({current_id, current_body});
1097 const std::vector<std::pair<int, std::string>>& messages,
1098 const std::vector<std::string>& labels) {
1100 output +=
"* Oracle of Secrets English Dialogue\n";
1102 for (
size_t i = 0; i < messages.size(); ++i) {
1103 const auto& [msg_id, body] = messages[i];
1104 std::string label = (i < labels.size())
1106 : absl::StrFormat(
"Message %02X", msg_id);
1108 output += absl::StrFormat(
"** %02X - %s\n", msg_id, label);
1137 const std::vector<std::string>& messages) {
1138 if (rom ==
nullptr || !rom->
is_loaded()) {
1139 return absl::InvalidArgumentError(
"ROM not loaded");
1141 if (start < 0 || end < start) {
1142 return absl::InvalidArgumentError(
"Invalid expanded message region");
1145 const int capacity = end - start + 1;
1146 if (capacity <= 0) {
1147 return absl::InvalidArgumentError(
1148 "Expanded message region has no capacity");
1151 const auto& data = rom->
vector();
1152 if (end >=
static_cast<int>(data.size())) {
1153 return absl::OutOfRangeError(
"Expanded message region out of ROM range");
1158 std::vector<uint8_t> blob;
1159 blob.reserve(
static_cast<size_t>(capacity));
1162 for (
size_t i = 0; i < messages.size(); ++i) {
1165 return absl::InvalidArgumentError(
1166 absl::StrFormat(
"Expanded message %d is invalid: %s",
1167 static_cast<int>(i), parsed.errors.front()));
1169 if (messages[i].find(
"[BANK]") != std::string::npos) {
1170 return absl::InvalidArgumentError(absl::StrFormat(
1171 "Expanded message %d contains [BANK], which is only valid in the "
1172 "vanilla message stream",
1173 static_cast<int>(i)));
1175 auto bytes = std::move(parsed.bytes);
1176 const int needed =
static_cast<int>(bytes.size()) + 1;
1179 if (used + needed + 1 > capacity) {
1180 return absl::ResourceExhaustedError(absl::StrFormat(
1181 "Expanded message data exceeds bank boundary "
1182 "(at message %d, used=%d, needed=%d, capacity=%d, end=0x%06X)",
1183 static_cast<int>(i), used, needed, capacity, end));
1186 blob.insert(blob.end(), bytes.begin(), bytes.end());
1191 if (used + 1 > capacity) {
1192 return absl::ResourceExhaustedError(
1193 "No space for end-of-region marker (0xFF)");
1195 blob.push_back(0xFF);
1200 const uint32_t fence_start =
static_cast<uint32_t
>(start);
1201 const uint32_t fence_end =
1202 static_cast<uint32_t
>(
static_cast<uint64_t
>(end) + 1ULL);
1210 const std::vector<std::string>& messages) {
1211 if (rom ==
nullptr || start < 0 || end < start) {
1212 return absl::InvalidArgumentError(
"Invalid expanded message region");
1215 const int capacity = end - start + 1;
1217 std::vector<std::vector<uint8_t>> encoded_messages;
1218 encoded_messages.reserve(messages.size());
1220 for (
size_t i = 0; i < messages.size(); ++i) {
1223 return absl::InvalidArgumentError(
1224 absl::StrFormat(
"Expanded message %d is invalid: %s",
1225 static_cast<int>(i), parsed.errors.front()));
1227 if (messages[i].find(
"[BANK]") != std::string::npos) {
1228 return absl::InvalidArgumentError(absl::StrFormat(
1229 "Expanded message %d contains [BANK], which is only valid in the "
1230 "vanilla message stream",
1231 static_cast<int>(i)));
1233 auto bytes = std::move(parsed.bytes);
1235 const int needed =
static_cast<int>(bytes.size()) + 1;
1236 if (used + needed + 1 > capacity) {
1237 return absl::ResourceExhaustedError(
1238 absl::StrFormat(
"Expanded message data exceeds bank boundary "
1239 "(at message %d, used %d, end 0x%06X)",
1240 static_cast<int>(i), used, end));
1243 encoded_messages.push_back(std::move(bytes));
1247 for (
const auto& bytes : encoded_messages) {
1248 for (uint8_t
byte : bytes) {
1256 return absl::OkStatus();
1259std::vector<std::pair<uint32_t, uint32_t>>
1261 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1262 ranges.reserve(
writes_.size());
1264 ranges.emplace_back(write.start(), write.end());
1270 const std::vector<MessageData>& messages,
1271 std::optional<size_t> expected_message_count) {
1272 if (expected_message_count.has_value() &&
1273 messages.size() != *expected_message_count) {
1274 return absl::FailedPreconditionError(
1275 absl::StrFormat(
"Vanilla message count mismatch: expected %zu, got %zu",
1276 *expected_message_count, messages.size()));
1281 std::vector<uint8_t> primary;
1282 std::vector<uint8_t> secondary;
1283 primary.reserve(kPrimaryCapacity);
1284 secondary.reserve(kSecondaryCapacity);
1286 bool in_second_bank =
false;
1287 size_t bank_switch_count = 0;
1288 auto append_byte = [&](uint8_t value,
size_t message_index) -> absl::Status {
1289 auto&
destination = in_second_bank ? secondary : primary;
1290 const size_t capacity =
1291 in_second_bank ? kSecondaryCapacity : kPrimaryCapacity;
1293 const uint32_t address =
1296 return absl::ResourceExhaustedError(absl::StrFormat(
1297 "Vanilla message %zu exceeds %s bank capacity at 0x%06X",
1298 message_index, in_second_bank ?
"second" :
"first", address));
1301 return absl::OkStatus();
1304 for (
size_t message_index = 0; message_index < messages.size();
1306 bool next_byte_is_command_argument =
false;
1307 for (uint8_t value : messages[message_index].Data) {
1308 const bool is_command_argument = next_byte_is_command_argument;
1309 next_byte_is_command_argument =
false;
1310 if (!is_command_argument &&
1312 return absl::InvalidArgumentError(absl::StrFormat(
1313 "Vanilla message %zu contains reserved stream marker 0x%02X",
1314 message_index, value));
1316 const bool is_bank_switch =
1318 if (is_bank_switch && bank_switch_count != 0) {
1319 return absl::InvalidArgumentError(absl::StrFormat(
1320 "Vanilla message %zu contains a duplicate [BANK] command",
1325 if (is_bank_switch) {
1326 ++bank_switch_count;
1327 in_second_bank =
true;
1328 }
else if (!is_command_argument) {
1330 next_byte_is_command_argument =
1331 command.has_value() && command->HasArgument;
1334 if (next_byte_is_command_argument) {
1335 return absl::InvalidArgumentError(absl::StrFormat(
1336 "Vanilla message %zu ends with a command missing its argument",
1342 if (bank_switch_count != 1) {
1343 return absl::InvalidArgumentError(
1344 "Vanilla message stream is missing the required [BANK] command");
1348 std::vector<VanillaMessageWrite> writes;
1349 writes.reserve(in_second_bank ? 2 : 1);
1350 writes.emplace_back(
static_cast<uint32_t
>(
kTextData), std::move(primary));
1351 if (in_second_bank) {
1352 writes.emplace_back(
static_cast<uint32_t
>(
kTextData2),
1353 std::move(secondary));
1361 if (rom ==
nullptr || !rom->
is_loaded()) {
1362 return absl::InvalidArgumentError(
"ROM not loaded");
1367 if (write.end() > rom->
size()) {
1368 return absl::OutOfRangeError(absl::StrFormat(
1369 "Vanilla message write range [0x%06X, 0x%06X) is outside the ROM",
1370 write.start(), write.end()));
1373 fence.
Allow(write.start(), write.end(),
"VanillaMessageBank"));
1380 rom->
WriteVector(
static_cast<int>(write.start()), write.bytes()));
1383 return absl::OkStatus();
1387 const std::vector<MessageData>& messages) {
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 WriteVector(int addr, std::vector< uint8_t > data)
static RomSettings & Get()
uint32_t GetAddressOr(const std::string &key, uint32_t default_value) const
const std::vector< VanillaMessageWrite > & writes() const
std::vector< std::pair< uint32_t, uint32_t > > write_ranges() const
std::vector< VanillaMessageWrite > writes_
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr char kExpandedMessageEnd[]
constexpr char kExpandedMessageStart[]
bool MatchesWholeWordAt(std::string_view text, size_t pos, size_t len)
absl::StatusOr< MessageBundleEntry > ParseMessageBundleEntry(const nlohmann::json &entry, MessageBank default_bank)
uint8_t FindMatchingCharacter(char value)
const std::string kBankToken
nlohmann::json SerializeMessagesToJson(const std::vector< MessageData > &messages)
absl::StatusOr< MessageBank > MessageBankFromString(std::string_view value)
DictionaryEntry FindRealDictionaryEntry(uint8_t value, const std::vector< DictionaryEntry > &dictionary)
constexpr int kMaxLineWidth
int GetExpandedTextDataStart()
constexpr int kMessageBundleVersion
const std::string DICTIONARYTOKEN
constexpr uint8_t kScrollVertical
std::string ParseTextDataByte(uint8_t value)
absl::Status WriteAllTextData(Rom *rom, const std::vector< MessageData > &messages)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
std::optional< std::pair< int, std::string > > ParseOrgHeader(const std::string &line)
std::string MessageBankToString(MessageBank bank)
constexpr int kExpandedTextDataEndDefault
std::string ReplaceAllDictionaryWords(std::string str, const std::vector< DictionaryEntry > &dictionary)
absl::Status WriteExpandedTextData(Rom *rom, int start, int end, const std::vector< std::string > &messages)
nlohmann::json SerializeMessageBundle(const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
constexpr int kPointersDictionaries
absl::StatusOr< std::vector< MessageBundleEntry > > LoadMessageBundleFromJson(const std::string &path)
constexpr int kNumDictionaryEntries
absl::StatusOr< MessageData > ParseSingleMessage(const std::vector< uint8_t > &rom_data, int *current_pos)
absl::StatusOr< std::vector< MessageBundleEntry > > ParseMessageBundleJson(const nlohmann::json &json)
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos, int max_pos, bool allow_bank_switch)
std::vector< std::string > ParseMessageData(std::vector< MessageData > &message_data, const std::vector< DictionaryEntry > &dictionary_entries)
std::optional< TextElement > FindMatchingSpecial(uint8_t value)
constexpr uint8_t kMessageTerminator
constexpr int kTextData2End
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
constexpr uint8_t kBankSwitchCommand
int ReplaceTextMatches(std::string *text, std::string_view query, std::string_view replacement, size_t start_pos, bool replace_all, bool case_sensitive, bool match_whole_word, size_t *first_replaced_pos)
std::optional< size_t > FindTextMatch(std::string_view text, std::string_view query, size_t start_pos, bool case_sensitive, bool match_whole_word)
std::vector< uint8_t > ParseMessageToData(std::string str)
absl::Status ExportMessagesToJson(const std::string &path, const std::vector< MessageData > &messages)
absl::Status ExportMessageBundleToJson(const std::string &path, const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
constexpr uint8_t DICTOFF
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)
std::vector< MessageData > ReadExpandedTextData(uint8_t *rom, int pos)
std::optional< TextElement > FindMatchingCommand(uint8_t b)
MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str)
int GetExpandedTextDataEnd()
ParsedElement FindMatchingElement(const std::string &str)
std::vector< std::string > ValidateMessageLineWidths(const std::string &message)
std::vector< std::pair< int, std::string > > ParseOrgContent(const std::string &content)
constexpr int kExpandedTextDataDefault
int8_t FindDictionaryEntry(uint8_t value)
absl::Status ApplyVanillaMessageSavePlan(Rom *rom, const VanillaMessageSavePlan &plan)
constexpr int kTextDataEnd
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc=true)
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
Room transition destination.
std::vector< uint8_t > Data
std::vector< uint8_t > DataParsed
std::string ContentsParsed
std::vector< uint8_t > bytes
std::vector< std::string > errors
std::vector< std::string > warnings
std::string GetParamToken(uint8_t value=0) const