7#include <unordered_map>
10#include "absl/status/status.h"
11#include "absl/strings/match.h"
12#include "absl/strings/str_cat.h"
13#include "absl/strings/str_format.h"
28#include "imgui/misc/cpp/imgui_stdlib.h"
39using ImGui::BeginChild;
40using ImGui::BeginTable;
44using ImGui::InputTextMultiline;
48using ImGui::Separator;
49using ImGui::TableHeadersRow;
50using ImGui::TableNextColumn;
51using ImGui::TableSetupColumn;
53using ImGui::TextWrapped;
56 ImGuiTableFlags_Borders |
57 ImGuiTableFlags_Resizable;
74 window_manager->RegisterWindowContent(
76 window_manager->RegisterWindowContent(
78 window_manager->RegisterWindowContent(
79 std::make_unique<FontAtlasPanel>([
this]() {
83 window_manager->RegisterWindowContent(
84 std::make_unique<DictionaryPanel>([
this]() {
91 window_manager->OpenWindow(session_id,
"message.message_list");
98 const int message_read_limit =
static_cast<int>(
99 std::min(
static_cast<size_t>(
rom()->size()),
100 static_cast<size_t>(std::numeric_limits<int>::max())));
103 LOG_INFO(
"MessageEditor",
"Loaded %zu messages from ROM",
127 LOG_ERROR(
"MessageEditor",
"No messages found in ROM!");
138 const int vanilla_count =
static_cast<int>(
list_of_texts_.size());
143 expanded_base_id, expanded_count);
147 if (!resolved.has_value() && expanded_count == 0 &&
151 const size_t rom_size =
rom_->
size();
152 if (start >= 0 && end >= start &&
static_cast<size_t>(end) < rom_size) {
156 "OpenMessageById: expanded load skipped/failed: %s",
157 std::string(status.message()).c_str());
161 expanded_base_id, expanded_count);
164 "OpenMessageById: expanded region out of bounds (0x%X-0x%X, "
166 start, end, rom_size);
170 if (!resolved.has_value()) {
177 "message.message_list");
179 "message.message_editor");
182 if (!resolved->is_expanded) {
183 const int idx = resolved->index;
184 if (idx < 0 || idx >= vanilla_count) {
193 const int parsed_idx = resolved->display_id;
194 if (parsed_idx >= 0 &&
208 const int idx = resolved->index;
209 if (idx < 0 || idx >= expanded_count) {
218 const int parsed_idx = resolved->display_id;
219 if (parsed_idx >= 0 &&
236 if (layout.first_expanded_id != 0) {
237 base_id =
static_cast<int>(layout.first_expanded_id);
243 base_id = std::max(base_id,
static_cast<int>(
list_of_texts_.size()));
258 std::vector<gfx::SnesColor> colors;
260 for (
int i = 0; i < 16; ++i) {
261 const float value =
static_cast<float>(i) / 15.0f;
262 colors.emplace_back(ImVec4(value, value, value, 1.0f));
265 if (!colors.empty()) {
266 colors[0].set_transparent(
true);
274 if (!
rom() || !
rom()->is_loaded()) {
275 LOG_WARN(
"MessageEditor",
"ROM not loaded - skipping font graphics load");
281 const size_t rom_size =
rom()->
size();
282 if (rom_size >
static_cast<size_t>(
kGfxFont)) {
284 rom_size -
static_cast<size_t>(
kGfxFont));
289 "Font graphics truncated (ROM size %zu, read %zu bytes)",
290 rom_size, available);
294 "ROM size %zu too small for font graphics offset 0x%X", rom_size,
302 if (load_font.ok()) {
305 const std::string error_message(load_font.status().message());
306 LOG_WARN(
"MessageEditor",
"LoadFontGraphics failed: %s",
307 error_message.c_str());
318 const std::vector<uint8_t>& font_data) {
319 if (font_data.empty()) {
320 LOG_WARN(
"MessageEditor",
"Font graphics data missing - atlas stays empty");
325 const size_t row_count = (font_data.size() + atlas_width - 1) / atlas_width;
326 const int atlas_height =
static_cast<int>(std::max<size_t>(1, row_count));
328 const size_t expected_size =
static_cast<size_t>(atlas_width) * atlas_height;
329 std::vector<uint8_t> padded(font_data.begin(), font_data.end());
330 if (padded.size() < expected_size) {
331 padded.resize(expected_size, 0);
332 }
else if (padded.size() > expected_size) {
333 padded.resize(expected_size);
347 return absl::OkStatus();
354 return absl::OkStatus();
370 if (!bitmap.is_active()) {
373 const auto command = bitmap.texture()
409 if (BeginChild(
"##MessagesList", ImVec2(0, 0),
true,
410 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
412 if (ImGui::Button(tr(
"Import Bundle"))) {
419 if (ImGui::Button(tr(
"Export Bundle"))) {
426 absl::StrFormat(
"Export failed: %s", status.message());
441 TableSetupColumn(
"ID", ImGuiTableColumnFlags_WidthFixed, 50);
442 TableSetupColumn(
"Type", ImGuiTableColumnFlags_WidthFixed, 80);
443 TableSetupColumn(
"Contents", ImGuiTableColumnFlags_WidthStretch);
444 TableSetupColumn(
"Address", ImGuiTableColumnFlags_WidthFixed, 100);
449 const int vanilla_count =
static_cast<int>(
list_of_texts_.size());
451 const int total_rows = vanilla_count + expanded_count;
454 ImGuiListClipper clipper;
455 clipper.Begin(total_rows);
457 while (clipper.Step()) {
458 for (
int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
459 if (row < vanilla_count) {
482 int expanded_idx = row - vanilla_count;
484 const int display_id =
486 const char* display_text =
"Missing text";
487 if (display_id >= 0 &&
505 TextWrapped(
"%s", display_text);
508 TextWrapped(
"%s",
util::HexLong(expanded_message.Address).c_str());
522 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
525 if (ImGui::IsItemDeactivatedAfterEdit()) {
531 ImGui::BulletText(
"%s", error.c_str());
537 ImGui::BulletText(
"%s", warning.c_str());
541 if (!line_warnings.empty()) {
543 for (
const auto& warning : line_warnings) {
544 ImGui::BulletText(
"%s", warning.c_str());
550 ImGui::BeginChild(
"##MessagePreview", ImVec2(0, 0),
true);
552 Text(tr(
"Message Preview"));
553 if (Button(tr(
"View Palette"))) {
554 ImGui::OpenPopup(
"Palette");
556 if (ImGui::BeginPopup(
"Palette")) {
561 BeginChild(
"CurrentGfxFont", ImVec2(348, 0),
true,
562 ImGuiWindowFlags_NoScrollWithMouse);
570 if (ImGui::IsWindowHovered()) {
571 float wheel = ImGui::GetIO().MouseWheel;
574 }
else if (wheel < 0 &&
582 const float dest_width = std::max(0.0f, preview_canvas_size.x - 8.0f);
583 const float dest_height = std::max(0.0f, preview_canvas_size.y - 8.0f);
584 float src_height = 0.0f;
585 if (dest_width > 0.0f && dest_height > 0.0f) {
586 const float src_width =
592 ImVec2(dest_width, dest_height),
594 ImVec2(src_width, src_height)
600 ImDrawList* overlay_draw_list = ImGui::GetWindowDrawList();
603 float line_height = 16.0f;
606 float scale_y = 1.0f;
607 if (dest_height > 0.0f && src_height > 0.0f) {
608 scale_y = dest_height / src_height;
612 float y = canvas_p0.y + src_y * scale_y;
613 if (y >= canvas_p0.y && y <= canvas_p0.y + canvas_sz.y) {
614 overlay_draw_list->AddLine(ImVec2(canvas_p0.x, y),
615 ImVec2(canvas_p0.x + canvas_sz.x, y),
616 IM_COL32(100, 180, 255, 180), 1.5f);
617 overlay_draw_list->AddText(ImVec2(canvas_p0.x + canvas_sz.x + 4, y - 6),
618 IM_COL32(100, 180, 255, 200),
"[V]");
628 if (ImGui::CollapsingHeader(tr(
"Message Structure"),
629 ImGuiTreeNodeFlags_DefaultOpen)) {
632 int scroll_count = 0;
633 int current_line_chars = 0;
641 tr(
" [V] Scroll at byte %zu (line %d, %d chars)"),
642 i, line_num, current_line_chars);
643 current_line_chars = 0;
645 }
else if (
byte ==
kLine1) {
646 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
647 tr(
" [1] Line 1 at byte %zu"), i);
648 current_line_chars = 0;
650 }
else if (
byte ==
kLine2) {
651 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
652 tr(
" [2] Line 2 at byte %zu"), i);
653 current_line_chars = 0;
655 }
else if (
byte ==
kLine3) {
656 ImGui::TextColored(ImVec4(0.7f, 0.85f, 0.5f, 1.0f),
657 tr(
" [3] Line 3 at byte %zu"), i);
658 current_line_chars = 0;
660 }
else if (
byte < 100) {
661 current_line_chars++;
665 if (scroll_count == 0) {
666 ImGui::TextDisabled(tr(
"No scroll breaks in this message"));
668 ImGui::Text(tr(
"Total scroll breaks: %d"), scroll_count);
673 ImGui::TextDisabled(tr(
"Line width budget (max ~170px):"));
674 int estimated_line_width = current_line_chars * 8;
675 float width_ratio =
static_cast<float>(estimated_line_width) / 170.0f;
679 ImGui::TextColored(width_color, tr(
"Last line: ~%dpx / 170px (%d chars)"),
680 estimated_line_width, current_line_chars);
696 ImGui::BeginChild(
"##ExpandedMessageSettings", ImVec2(0, 130),
true,
697 ImGuiWindowFlags_AlwaysVerticalScrollbar);
698 ImGui::Text(tr(
"Expanded Messages"));
700 if (ImGui::Button(tr(
"Load from ROM"))) {
703 LOG_WARN(
"MessageEditor",
"Load from ROM: %s",
704 std::string(status.message()).c_str());
708 if (ImGui::Button(tr(
"Load from File"))) {
715 std::vector<std::string> parsed_expanded;
721 popup_manager->Show(
"Error");
725 parsed_expanded.end());
738 int remaining = capacity - used;
739 float usage_ratio =
static_cast<float>(used) /
static_cast<float>(capacity);
741 ImVec4 capacity_color;
742 if (usage_ratio < 0.75f) {
744 }
else if (usage_ratio < 0.90f) {
749 ImGui::TextColored(capacity_color, tr(
"Bank: %d / %d bytes (%d free)"),
750 used, capacity, remaining);
752 if (ImGui::Button(tr(
"Add New Message"))) {
760 if (display_id >= 0 &&
767 if (ImGui::Button(tr(
"Export to JSON"))) {
779 ImGui::BeginChild(
"##TextCommands",
780 ImVec2(0, ImGui::GetContentRegionAvail().y / 2),
true,
781 ImGuiWindowFlags_AlwaysVerticalScrollbar);
782 static uint8_t command_parameter = 0;
784 for (
const auto& text_element : TextCommands) {
785 if (Button(text_element.GenericToken.c_str())) {
787 text_element.GetParamToken(command_parameter));
791 TextWrapped(
"%s", text_element.Description.c_str());
798 ImGui::BeginChild(
"##SpecialChars",
799 ImVec2(0, ImGui::GetContentRegionAvail().y / 2),
true,
800 ImGuiWindowFlags_AlwaysVerticalScrollbar);
801 for (
const auto& text_element : SpecialChars) {
802 if (Button(text_element.GenericToken.c_str())) {
807 TextWrapped(
"%s", text_element.Description.c_str());
814 if (ImGui::BeginChild(
"##DictionaryChild",
815 ImVec2(0, ImGui::GetContentRegionAvail().y),
true,
816 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
818 TableSetupColumn(
"ID");
819 TableSetupColumn(
"Contents");
823 const int dict_count =
825 ImGuiListClipper clipper;
826 clipper.Begin(dict_count);
828 while (clipper.Step()) {
829 for (
int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row) {
834 Text(
"%s", dictionary.Contents.c_str());
859 if (!parse_result.ok()) {
863 std::string raw_text = text;
864 raw_text.erase(std::remove(raw_text.begin(), raw_text.end(),
'\n'),
876 if (parsed_index >= 0) {
903 if (!entries_or.ok()) {
905 absl::StrFormat(
"Import failed: %s", entries_or.status().message());
913 int duplicate_errors = 0;
914 int parse_error_entries = 0;
915 int vanilla_updated = 0;
916 int expanded_updated = 0;
917 int expanded_created = 0;
918 bool expanded_modified =
false;
919 std::vector<std::string> issue_samples;
921 auto add_issue_sample = [&issue_samples](
const std::string& issue) {
922 constexpr size_t kMaxIssueSamples = 4;
923 if (issue_samples.size() < kMaxIssueSamples) {
924 issue_samples.push_back(issue);
932 std::unordered_map<std::string, int> seen_entries;
934 auto entries = entries_or.value();
935 for (
const auto& entry : entries) {
936 const std::string entry_key = make_entry_key(entry);
937 if (seen_entries.find(entry_key) != seen_entries.end()) {
940 add_issue_sample(absl::StrFormat(
"Duplicate entry for %s", entry_key));
943 seen_entries.emplace(entry_key, 1);
947 warnings +=
static_cast<int>(parse_result.warnings.size());
948 warnings +=
static_cast<int>(line_warnings.size());
950 if (!parse_result.ok()) {
952 parse_error_entries++;
953 if (!parse_result.errors.empty()) {
954 add_issue_sample(absl::StrFormat(
"Parse error for %s: %s", entry_key,
955 parse_result.errors.front()));
957 add_issue_sample(absl::StrFormat(
"Parse error for %s", entry_key));
963 if (entry.id < 0 || entry.id >=
static_cast<int>(
list_of_texts_.size())) {
966 absl::StrFormat(
"Vanilla ID out of range: %d", entry.id));
970 message.RawString = entry.text;
971 message.ContentsParsed = entry.text;
972 message.Data = parse_result.bytes;
973 message.DataParsed = parse_result.bytes;
984 absl::StrFormat(
"Expanded ID out of range: %d", entry.id));
989 const int target_size = entry.id + 1;
991 for (
int i = old_size; i < target_size; ++i) {
994 expanded_created += target_size - old_size;
997 message.RawString = entry.text;
998 message.ContentsParsed = entry.text;
999 message.Data = parse_result.bytes;
1000 message.DataParsed = parse_result.bytes;
1002 if (parsed_index >= 0) {
1008 expanded_modified =
true;
1014 if (expanded_modified) {
1017 message.Address = pos;
1018 pos +=
static_cast<int>(message.Data.size()) + 1;
1026 if (current_display_id >= 0) {
1032 "Import finished with %d errors (%d applied: vanilla %d updated, "
1033 "expanded %d updated/%d created; %d warnings, %d duplicates, %d "
1035 errors, applied, vanilla_updated, expanded_updated, expanded_created,
1036 warnings, duplicate_errors, parse_error_entries);
1037 if (!issue_samples.empty()) {
1044 "Imported %d messages (vanilla %d updated, expanded %d updated/%d "
1045 "created, %d warnings).",
1046 applied, vanilla_updated, expanded_updated, expanded_created, warnings);
1048 if (applied > 0 &&
rom_) {
1051 if (vanilla_updated > 0) {
1054 if (expanded_modified) {
1065 LOG_WARN(
"MessageEditor",
"Preview data is empty, skipping bitmap update");
1081 LOG_ERROR(
"MessageEditor",
"Bitmap surface is null after set_data()");
1093 "Updated message preview bitmap (size: %zu) and queued texture update",
1105 "Created message preview bitmap (%dx%d) with 8-bit depth and "
1106 "queued texture creation",
1113 return absl::FailedPreconditionError(
"ROM not loaded");
1116 return absl::FailedPreconditionError(absl::StrFormat(
1126 bool include_font_widths,
bool include_vanilla_messages,
1127 bool include_expanded_messages)
const {
1129 return absl::FailedPreconditionError(
"ROM not loaded");
1143 std::optional<size_t> expected_message_count;
1146 const int manifest_count =
1148 if (manifest_count > 0) {
1149 expected_message_count =
static_cast<size_t>(manifest_count);
1157 for (
const auto& write : vanilla_plan.
writes()) {
1167 if (start < 0 || end < start) {
1168 return absl::InvalidArgumentError(
"Invalid expanded message region");
1170 if (
static_cast<uint64_t
>(start) >=
rom_->
size() ||
1171 static_cast<uint64_t
>(end) >=
rom_->
size()) {
1172 return absl::OutOfRangeError(
1173 "Expanded message region is outside the ROM");
1176 const int64_t capacity =
static_cast<int64_t
>(end) - start + 1;
1177 std::vector<uint8_t> bytes;
1178 bytes.reserve(
static_cast<size_t>(capacity));
1185 return absl::InvalidArgumentError(
1186 absl::StrFormat(
"Expanded message %d is invalid: %s",
1187 static_cast<int>(index), parsed.errors.front()));
1189 if (message.RawString.find(
"[BANK]") != std::string::npos) {
1190 return absl::InvalidArgumentError(absl::StrFormat(
1191 "Expanded message %d contains [BANK], which is only valid in the "
1192 "vanilla message stream",
1193 static_cast<int>(index)));
1196 const int64_t needed =
static_cast<int64_t
>(parsed.bytes.size()) + 1;
1197 if (
static_cast<int64_t
>(bytes.size()) + needed + 1 > capacity) {
1198 return absl::ResourceExhaustedError(absl::StrFormat(
1199 "Expanded message data exceeds bank boundary "
1200 "(at message %d, used=%d, needed=%d, capacity=%d, end=0x%06X)",
1201 static_cast<int>(index),
static_cast<int>(bytes.size()),
1202 static_cast<int>(needed),
static_cast<int>(capacity), end));
1206 static_cast<int>(bytes.size()));
1207 bytes.insert(bytes.end(), parsed.bytes.begin(), parsed.bytes.end());
1210 bytes.push_back(0xFF);
1214 static_cast<uint32_t
>(start), std::move(bytes)});
1217 for (
const auto& write : plan.
writes) {
1218 const uint64_t end =
1219 static_cast<uint64_t
>(write.start) + write.bytes.size();
1220 if (end >
rom_->
size() || end > std::numeric_limits<uint32_t>::max()) {
1221 return absl::OutOfRangeError(absl::StrFormat(
1222 "Message save range [0x%06X, 0x%06llX) is outside the ROM",
1223 write.start,
static_cast<unsigned long long>(end)));
1233 return absl::OkStatus();
1236 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1237 ranges.reserve(plan.
writes.size());
1238 for (
const auto& write : plan.
writes) {
1239 ranges.emplace_back(write.start, write.end());
1243 if (yaze_project.rom_metadata.write_policy ==
1246 absl::StrContains(yaze_project.hack_manifest.hack_name(),
1247 "Oracle of Secrets")) {
1248 return absl::PermissionDeniedError(
1249 "Oracle of Secrets expanded messages are owned by ASM. No message "
1250 "bytes were written; keep the draft in Yaze, edit Core/message.asm, "
1251 "rebuild Oracle of Secrets, and reopen the rebuilt ROM before "
1256 yaze_project.hack_manifest, yaze_project.rom_metadata.write_policy,
1262 if (plan.
writes.empty()) {
1263 return absl::OkStatus();
1267 for (
const auto& write : plan.
writes) {
1268 const char* label =
"MessageData";
1269 switch (write.domain) {
1271 label =
"MessageFontWidths";
1274 label =
"VanillaMessageBank";
1277 label =
"ExpandedMessageBank";
1285 for (
const auto& write : plan.
writes) {
1301 return absl::OkStatus();
1305 bool include_vanilla_messages,
1306 bool include_expanded_messages) {
1308 BuildSavePlan(include_font_widths, include_vanilla_messages,
1309 include_expanded_messages));
1313 return absl::OkStatus();
1324 return absl::FailedPreconditionError(
1325 "Message save transaction is already active");
1335 return absl::OkStatus();
1368 bool* dirty =
nullptr;
1369 bool* transaction_saved =
nullptr;
1386 *transaction_saved =
false;
1427 return absl::FailedPreconditionError(
"ROM not loaded");
1439 return absl::NotFoundError(
1440 "No expanded messages found in ROM at expanded text region");
1444 auto parsed_expanded =
1447 if (msg.ID >= 0 && msg.ID <
static_cast<int>(parsed_expanded.size())) {
1454 return absl::OkStatus();
1462 total +=
static_cast<int>(msg.Data.size()) + 1;
1474 return absl::OkStatus();
1479 if (ImGui::GetClipboardText() !=
nullptr) {
1483 return absl::OkStatus();
1492 return absl::OkStatus();
1512 if (parsed_index >= 0 &&
1532 if (message_index < 0 ||
1539 if (message_index < 0 ||
1547 int parsed_index = message_index;
1552 if (parsed_index >= 0 &&
1557 message_index, is_expanded};
1570 const auto diagnostics =
1579 if (parsed_index >= 0 &&
1633 if (ImGui::Begin(
"Find & Replace",
nullptr,
1634 ImGuiWindowFlags_AlwaysAutoResize)) {
1635 static char find_text[256] =
"";
1636 static char replace_text[256] =
"";
1637 ImGui::InputText(tr(
"Search"), find_text, IM_ARRAYSIZE(find_text));
1638 ImGui::InputText(tr(
"Replace with"), replace_text,
1639 IM_ARRAYSIZE(replace_text));
1641 if (ImGui::Button(tr(
"Find Next"))) {
1647 if (ImGui::Button(tr(
"Find All"))) {
1653 if (ImGui::Button(tr(
"Replace"))) {
1667 if (ImGui::Button(tr(
"Replace All"))) {
1673 count == 1 ?
"" :
"s");
1690 return absl::OkStatus();
1699 std::string source = text;
1702 std::transform(search.begin(), search.end(), search.begin(), ::tolower);
1703 std::transform(source.begin(), source.end(), source.begin(), ::tolower);
1706 size_t pos = source.find(search);
1707 if (pos == std::string::npos)
1712 bool start_ok = (pos == 0 || !std::isalnum(source[pos - 1]));
1713 bool end_ok = (pos + search.size() >= source.size() ||
1714 !std::isalnum(source[pos + search.size()]));
1715 if (!start_ok || !end_ok) {
1717 while (pos != std::string::npos) {
1718 start_ok = (pos == 0 || !std::isalnum(source[pos - 1]));
1719 end_ok = (pos + search.size() >= source.size() ||
1720 !std::isalnum(source[pos + search.size()]));
1721 if (start_ok && end_ok)
1723 pos = source.find(search, pos + 1);
1725 if (pos == std::string::npos)
1743 "Replace All blocked: resolve the current message parse errors first";
1748 auto replace_in_text = [&](std::string& text) ->
int {
1753 std::transform(search.begin(), search.end(), search.begin(), ::tolower);
1757 while (pos < text.size()) {
1758 std::string source = text;
1760 std::transform(source.begin(), source.end(), source.begin(), ::tolower);
1763 size_t found = source.find(search, pos);
1764 if (found == std::string::npos)
1768 bool start_ok = (found == 0 || !std::isalnum(source[found - 1]));
1769 bool end_ok = (found + search.size() >= source.size() ||
1770 !std::isalnum(source[found + search.size()]));
1771 if (!start_ok || !end_ok) {
1784 struct PlannedReplacement {
1790 std::vector<PlannedReplacement> plan;
1792 const auto plan_replacements = [&](
const std::vector<MessageData>& messages,
1793 bool is_expanded) ->
bool {
1794 for (
size_t i = 0; i < messages.size(); ++i) {
1795 const int parsed_index =
1797 if (parsed_index < 0 ||
1803 const int count = replace_in_text(text);
1810 (is_expanded && text.find(
"[BANK]") != std::string::npos)) {
1811 const std::string error =
1812 !parsed.ok() ? parsed.errors.front()
1813 :
"[BANK] is not valid in expanded messages";
1815 "Replace All aborted at %s message %d: %s",
1816 is_expanded ?
"expanded" :
"vanilla",
static_cast<int>(i), error);
1822 {
static_cast<int>(i), is_expanded, std::move(text), count});
1835 int total_replacements = 0;
1837 for (
const auto& replacement : plan) {
1846 total_replacements += replacement.count;
1857 if (current_parsed_idx >= 0 &&
1875 const auto current_diagnostics =
1880 return total_replacements;
void set_dirty(bool dirty)
absl::Status WriteVector(int addr, std::vector< uint8_t > data)
const MessageLayout & message_layout() const
bool loaded() const
Check if the manifest has been loaded.
virtual void SetGameData(zelda3::GameData *game_data)
UndoManager undo_manager_
zelda3::GameData * game_data() const
EditorDependencies dependencies_
DirtyState transaction_dirty_snapshot_
std::vector< std::string > parsed_messages_
absl::Status Copy() override
bool current_message_is_expanded_
bool replace_status_error_
void CommitSaveTransaction() override
std::vector< MessageData > expanded_messages_
int CalculateExpandedBankUsage() const
absl::Status Find() override
void ResolveFontPalette()
void EnsureFontTexturesReady()
absl::Status SaveExpandedMessages()
DirtyState transaction_saved_domains_
void RollbackSaveTransaction() override
bool font_graphics_loaded_
absl::Status Update() override
void ClearSavedDomains(const DirtyState &saved)
void FinalizePendingUndo()
absl::Status LoadExpandedMessagesFromRom()
void RecordSavedDomains(const SavePlan &plan)
void DrawExpandedMessageSettings()
void UpdateCurrentMessageFromText(const std::string &text)
absl::Status Paste() override
void ApplySnapshot(const MessageSnapshot &snapshot)
bool save_transaction_active_
absl::StatusOr< SavePlan > BuildSavePlan(bool include_font_widths, bool include_vanilla_messages, bool include_expanded_messages) const
std::vector< int > transaction_expanded_address_snapshot_
void SyncCurrentExpandedMessageAddress()
void MarkDomainDirty(SaveDomain domain)
gui::Canvas current_font_gfx16_canvas_
gui::Canvas font_gfx_canvas_
void RefreshFontAtlasBitmap(const std::vector< uint8_t > &font_data)
std::vector< std::string > current_parse_errors_
absl::Status Undo() override
absl::Status Load() override
std::string replace_status_
std::vector< std::string > current_parse_warnings_
std::string message_bundle_status_
std::array< uint8_t, 0x4000 > raw_font_gfx_data_
gfx::SnesPalette BuildFallbackFontPalette() const
void Initialize() override
std::string replace_text_
absl::Status SaveDirtyDomains(bool include_font_widths, bool include_vanilla_messages, bool include_expanded_messages)
absl::Status Cut() override
void DrawMessagePreview()
int expanded_message_base_id_
bool message_bundle_status_error_
std::optional< MessageSnapshot > pending_undo_before_
absl::Status BeginSaveTransaction() override
std::vector< MessageData > list_of_texts_
bool OpenMessageById(int display_id)
gfx::SnesPalette font_preview_colors_
MessageData current_message_
MessagePreview message_preview_
gui::TextBox message_text_box_
void DrawCurrentMessage()
void DrawSpecialCharacters()
absl::Status Redo() override
int ResolveExpandedMessageBaseId() const
void ImportMessageBundleFromFile(const std::string &path)
gfx::Bitmap current_font_gfx16_bitmap_
absl::Status ValidateSavePlan(const SavePlan &plan) const
int ReplaceCurrentMatch()
int current_message_index_
absl::Status ApplySavePlan(const SavePlan &plan)
gfx::Bitmap font_gfx_bitmap_
std::string expanded_message_path_
absl::Status Save() override
void SetGameData(zelda3::GameData *game_data) override
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
const std::vector< VanillaMessageWrite > & writes() const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
const SnesPalette & palette() const
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
TextureHandle texture() const
SnesPalette * mutable_palette()
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
SDL_Surface * surface() const
RAII timer for automatic timing management.
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
bool DrawTileSelector(int size, int size_y=0)
CanvasConfig & GetConfig()
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
absl::Status Allow(uint32_t start, uint32_t end, std::string_view label)
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define PRINT_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr int kCharactersWidth
int GetExpandedTextDataStart()
constexpr uint8_t kScrollVertical
std::optional< ResolvedMessageId > ResolveMessageDisplayId(int display_id, int vanilla_count, int expanded_base_id, int expanded_count)
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
std::string MessageBankToString(MessageBank bank)
constexpr int kCurrentMessageWidth
constexpr int kCurrentMessageHeight
absl::StatusOr< std::vector< MessageBundleEntry > > LoadMessageBundleFromJson(const std::string &path)
constexpr int kFontGfxMessageSize
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)
constexpr uint8_t kMessageTerminator
constexpr int kFontGfxMessageDepth
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
absl::Status ExportMessagesToJson(const std::string &path, const std::vector< MessageData > &messages)
constexpr uint8_t kWidthArraySize
absl::Status ExportMessageBundleToJson(const std::string &path, const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
constexpr ImGuiTableFlags kMessageTableFlags
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)
MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str)
int GetExpandedTextDataEnd()
std::vector< std::string > ValidateMessageLineWidths(const std::string &message)
std::vector< uint8_t > SnesTo8bppSheet(std::span< const uint8_t > sheet, int bpp, int num_sheets)
void EndCanvas(Canvas &canvas)
void BeginCanvas(Canvas &canvas, ImVec2 child_size)
void MemoryEditorPopup(const std::string &label, std::span< uint8_t > memory)
IMGUI_API bool DisplayPalette(gfx::SnesPalette &palette, bool loaded)
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
std::string HexWord(uint16_t word, HexStringParams params)
std::string HexLong(uint32_t dword, HexStringParams params)
absl::StatusOr< gfx::Bitmap > LoadFontGraphics(const Rom &rom)
Loads font graphics from ROM.
#define RETURN_IF_ERROR(expr)
project::YazeProject * project
ToastManager * toast_manager
WorkspaceWindowManager * window_manager
PopupManager * popup_manager
std::vector< uint8_t > Data
std::string ContentsParsed
std::vector< int > expanded_message_addresses
bool saves_vanilla_messages
bool saves_expanded_messages
std::vector< PlannedWrite > writes
std::vector< uint8_t > current_preview_data_
void DrawMessagePreview(const MessageData &message)
std::array< uint8_t, kWidthArraySize > width_array
std::vector< uint8_t > font_gfx16_data_2_
std::vector< uint8_t > font_gfx16_data_
std::vector< int > scroll_marker_lines
std::vector< DictionaryEntry > all_dictionaries_
auto palette(int i) const
core::HackManifest hack_manifest
gfx::PaletteGroupMap palette_groups