yaze 0.2.0
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_editor.cc
Go to the documentation of this file.
1#include "message_editor.h"
2
3#include <string>
4#include <unordered_map>
5#include <vector>
6
7#include "absl/status/status.h"
8#include "absl/strings/str_format.h"
9#include "absl/strings/str_replace.h"
11#include "app/gfx/bitmap.h"
13#include "app/gfx/snes_tile.h"
14#include "app/gui/canvas.h"
15#include "app/gui/style.h"
16#include "app/rom.h"
17#include "imgui.h"
18
19namespace yaze {
20namespace app {
21namespace editor {
22
23using core::Renderer;
24
25using ImGui::BeginChild;
26using ImGui::BeginTable;
27using ImGui::Button;
28using ImGui::EndChild;
29using ImGui::EndTable;
30using ImGui::InputText;
31using ImGui::InputTextMultiline;
32using ImGui::SameLine;
33using ImGui::Separator;
34using ImGui::TableHeadersRow;
35using ImGui::TableNextColumn;
36using ImGui::TableSetupColumn;
37using ImGui::Text;
38using ImGui::TextWrapped;
39
40constexpr ImGuiTableFlags kMessageTableFlags = ImGuiTableFlags_Hideable |
41 ImGuiTableFlags_Borders |
42 ImGuiTableFlags_Resizable;
43
44constexpr ImGuiTableFlags kDictTableFlags =
45 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable;
46
48 for (int i = 0; i < kWidthArraySize; i++) {
49 width_array[i] = rom()->data()[kCharactersWidth + i];
50 }
51
54
55 font_preview_colors_.AddColor(0x7FFF); // White
56 font_preview_colors_.AddColor(0x7C00); // Red
57 font_preview_colors_.AddColor(0x03E0); // Green
58 font_preview_colors_.AddColor(0x001F); // Blue
59
60 std::vector<uint8_t> data(0x4000, 0);
61 for (int i = 0; i < 0x4000; i++) {
62 data[i] = rom()->data()[kGfxFont + i];
63 }
64 font_gfx16_data_ = gfx::SnesTo8bppSheet(data, /*bpp=*/2, /*num_sheets=*/2);
65
66 // 4bpp
67 RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
70
73 for (int i = 0; i < kCurrentMessageWidth * kCurrentMessageHeight; i++) {
74 current_font_gfx16_data_.push_back(0);
75 }
76
77 // 8bpp
78 RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
81
83 for (int i = 0; i < font_preview_colors_.size(); i++) {
84 *color_palette.mutable_color(i) = font_preview_colors_[i];
85 }
86
87 *font_gfx_bitmap_.mutable_palette() = color_palette;
88
89 for (const auto& each_message : list_of_texts_) {
90 std::cout << "Message #" << each_message.ID << " at address "
91 << core::UppercaseHexLong(each_message.Address) << std::endl;
92 std::cout << " " << each_message.RawString << std::endl;
93
94 // Each string has a [:XX] char encoded
95 // The corresponding character is found in CharEncoder unordered_map
96 std::string parsed_message = "";
97 for (const auto& byte : each_message.Data) {
98 // Find the char byte in the CharEncoder map
99 if (CharEncoder.contains(byte)) {
100 parsed_message.push_back(CharEncoder.at(byte));
101 } else {
102 // If the byte is not found in the CharEncoder map, it is a command
103 // or a dictionary entry
104 if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
105 // Dictionary entry
106 auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
107 parsed_message.append(dictionaryEntry.Contents);
108 } else {
109 // Command
110 TextElement textElement = FindMatchingCommand(byte);
111 if (!textElement.Empty()) {
112 // If the element is line 2, 3 or V we add a newline
113 if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
114 textElement.ID == kLine3)
115 parsed_message.append("\n");
116
117 parsed_message.append(textElement.GenericToken);
118 }
119 }
120 }
121 }
122 std::cout << " > " << parsed_message << std::endl;
123 parsed_messages_.push_back(parsed_message);
124 }
125
127
128 return absl::OkStatus();
129}
130
131absl::Status MessageEditor::Update() {
132 if (rom()->is_loaded() && !data_loaded_) {
135 data_loaded_ = true;
136 }
137
138 if (BeginTable("##MessageEditor", 4, kDictTableFlags)) {
139 TableSetupColumn("List");
140 TableSetupColumn("Contents");
141 TableSetupColumn("Commands");
142 TableSetupColumn("Dictionary");
143
144 TableHeadersRow();
145
146 TableNextColumn();
148
149 TableNextColumn();
151
152 TableNextColumn();
154
155 TableNextColumn();
157
158 EndTable();
159 }
160
161 return absl::OkStatus();
162}
163
165 if (InputText("Search", &search_text_)) {
166 // TODO: ImGui style text filtering
167 }
168
169 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
170 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
171 if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
172 TableSetupColumn("ID");
173 TableSetupColumn("Contents");
174 TableSetupColumn("Data");
175
176 TableHeadersRow();
177
178 for (const auto& message : list_of_texts_) {
179 TableNextColumn();
180 if (Button(core::UppercaseHexWord(message.ID).c_str())) {
181 current_message_ = message;
183 }
184 TableNextColumn();
185 TextWrapped("%s", parsed_messages_[message.ID].c_str());
186 TableNextColumn();
187 TextWrapped(
188 "%s",
189 core::UppercaseHexLong(list_of_texts_[message.ID].Address).c_str());
190 }
191
192 EndTable();
193 }
194 EndChild();
195 }
196}
197
199 Button(absl::StrCat("Message ", current_message_.ID).c_str());
200 if (InputTextMultiline("##MessageEditor",
202 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
205 }
206 Separator();
207
208 Text("Font Graphics");
210 BeginChild("MessageEditorCanvas", ImVec2(0, 130));
216 EndChild();
218 Separator();
219
220 Text("Message Preview");
221 if (Button("Refresh Bitmap")) {
223 }
225 BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
226 ImGuiWindowFlags_AlwaysVerticalScrollbar);
233 EndChild();
234}
235
237 if (BeginChild("##TextCommands", ImVec2(0, 0), true,
238 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
239 for (const auto& text_element : TextCommands) {
240 if (Button(text_element.GenericToken.c_str())) {
241 }
242 SameLine();
243 TextWrapped("%s", text_element.Description.c_str());
244 Separator();
245 }
246 EndChild();
247 }
248}
249
251 if (ImGui::BeginChild("##DictionaryChild", ImVec2(0, 0), true,
252 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
253 if (BeginTable("##Dictionary", 2, kDictTableFlags)) {
254 TableSetupColumn("ID");
255 TableSetupColumn("Contents");
256
257 for (const auto& dictionary : all_dictionaries_) {
258 TableNextColumn();
259 Text("%s", core::UppercaseHexWord(dictionary.ID).c_str());
260 TableNextColumn();
261 Text("%s", dictionary.Contents.c_str());
262 }
263 EndTable();
264 }
265
266 EndChild();
267 }
268}
269
270// TODO: Fix the command parsing.
272 // Read all text data from the ROM.
273 int pos = kTextData;
274 int message_id = 0;
275
276 std::vector<uint8_t> raw_message;
277 std::vector<uint8_t> parsed_message;
278
279 std::string current_raw_message;
280 std::string current_parsed_message;
281
282 uint8_t current_byte = 0;
283 while (current_byte != 0xFF) {
284 current_byte = rom()->data()[pos++];
285 if (current_byte == kMessageTerminator) {
286 auto message =
287 MessageData(message_id++, pos, current_raw_message, raw_message,
288 current_parsed_message, parsed_message);
289
290 list_of_texts_.push_back(message);
291
292 raw_message.clear();
293 parsed_message.clear();
294 current_raw_message.clear();
295 current_parsed_message.clear();
296
297 continue;
298 }
299
300 raw_message.push_back(current_byte);
301
302 // Check for command.
303 TextElement text_element = FindMatchingCommand(current_byte);
304 if (!text_element.Empty()) {
305 parsed_message.push_back(current_byte);
306 if (text_element.HasArgument) {
307 current_byte = rom()->data()[pos++];
308 raw_message.push_back(current_byte);
309 parsed_message.push_back(current_byte);
310 }
311
312 current_raw_message.append(
313 text_element.GetParameterizedToken(current_byte));
314 current_parsed_message.append(
315 text_element.GetParameterizedToken(current_byte));
316
317 if (text_element.Token == BANKToken) {
318 pos = kTextData2;
319 }
320
321 continue;
322 }
323
324 // Check for special characters.
325 text_element = FindMatchingSpecial(current_byte);
326 if (!text_element.Empty()) {
327 current_raw_message.append(text_element.GetParameterizedToken());
328 current_parsed_message.append(text_element.GetParameterizedToken());
329 parsed_message.push_back(current_byte);
330 continue;
331 }
332
333 // Check for dictionary.
334 int dictionary = FindDictionaryEntry(current_byte);
335 if (dictionary >= 0) {
336 current_raw_message.append("[");
337 current_raw_message.append(DICTIONARYTOKEN);
338 current_raw_message.append(":");
339 current_raw_message.append(core::UppercaseHexWord(dictionary));
340 current_raw_message.append("]");
341
342 uint32_t address = core::Get24LocalFromPC(
343 rom()->data(), kPointersDictionaries + (dictionary * 2));
344 uint32_t address_end = core::Get24LocalFromPC(
345 rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
346
347 for (uint32_t i = address; i < address_end; i++) {
348 parsed_message.push_back(rom()->data()[i]);
349 current_parsed_message.append(ParseTextDataByte(rom()->data()[i]));
350 }
351
352 continue;
353 }
354
355 // Everything else.
356 if (CharEncoder.contains(current_byte)) {
357 std::string str = "";
358 str.push_back(CharEncoder.at(current_byte));
359 current_raw_message.append(str);
360 current_parsed_message.append(str);
361 parsed_message.push_back(current_byte);
362 }
363 }
364}
365
367 int pos = kTextData;
368 int message_id = 0;
369 uint8_t current_byte;
370 std::vector<uint8_t> temp_bytes_raw;
371 std::vector<uint8_t> temp_bytes_parsed;
372
373 std::string current_message_raw;
374 std::string current_message_parsed;
375 TextElement text_element;
376
377 while (true) {
378 current_byte = rom()->data()[pos++];
379
380 if (current_byte == kMessageTerminator) {
381 auto message =
382 MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
383 current_message_parsed, temp_bytes_parsed);
384
385 list_of_texts_.push_back(message);
386
387 temp_bytes_raw.clear();
388 temp_bytes_parsed.clear();
389 current_message_raw.clear();
390 current_message_parsed.clear();
391
392 continue;
393 } else if (current_byte == 0xFF) {
394 break;
395 }
396
397 temp_bytes_raw.push_back(current_byte);
398
399 // Check for command.
400 text_element = FindMatchingCommand(current_byte);
401
402 if (!text_element.Empty()) {
403 temp_bytes_parsed.push_back(current_byte);
404 if (text_element.HasArgument) {
405 current_byte = rom()->data()[pos++];
406 temp_bytes_raw.push_back(current_byte);
407 temp_bytes_parsed.push_back(current_byte);
408 }
409
410 current_message_raw.append(
411 text_element.GetParameterizedToken(current_byte));
412 current_message_parsed.append(
413 text_element.GetParameterizedToken(current_byte));
414
415 if (text_element.Token == BANKToken) {
416 pos = kTextData2;
417 }
418
419 continue;
420 }
421
422 // Check for special characters.
423 text_element = FindMatchingSpecial(current_byte);
424 if (!text_element.Empty()) {
425 current_message_raw.append(text_element.GetParameterizedToken());
426 current_message_parsed.append(text_element.GetParameterizedToken());
427 temp_bytes_parsed.push_back(current_byte);
428 continue;
429 }
430
431 // Check for dictionary.
432 int dictionary = FindDictionaryEntry(current_byte);
433
434 if (dictionary >= 0) {
435 current_message_raw.append("[");
436 current_message_raw.append(DICTIONARYTOKEN);
437 current_message_raw.append(":");
438 current_message_raw.append(core::UppercaseHexWord(dictionary));
439 current_message_raw.append("]");
440
441 uint32_t address = core::Get24LocalFromPC(
442 rom()->data(), kPointersDictionaries + (dictionary * 2));
443 uint32_t address_end = core::Get24LocalFromPC(
444 rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));
445
446 for (uint32_t i = address; i < address_end; i++) {
447 temp_bytes_parsed.push_back(rom()->data()[i]);
448 current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
449 }
450
451 continue;
452 }
453
454 // Everything else.
455 if (CharEncoder.contains(current_byte)) {
456 std::string str = "";
457 str.push_back(CharEncoder.at(current_byte));
458 current_message_raw.append(str);
459 current_message_parsed.append(str);
460 temp_bytes_parsed.push_back(current_byte);
461 }
462 }
463}
464
465std::string ReplaceAllDictionaryWords(std::string str,
466 std::vector<DictionaryEntry> dictionary) {
467 std::string temp = str;
468 for (const auto& entry : dictionary) {
469 if (absl::StrContains(temp, entry.Contents)) {
470 temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
471 }
472 }
473 return temp;
474}
475
477 if (value < 0 || value >= all_dictionaries_.size()) {
478 return DictionaryEntry();
479 }
480 return all_dictionaries_[value];
481}
482
483void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
484 int sizex, int sizey) {
485 const int num_x_tiles = 16;
486 const int img_width = 512; // (imgwidth/2)
487 int draw_id = srcx + (srcy * 32);
488 for (int yl = 0; yl < sizey * 8; yl++) {
489 for (int xl = 0; xl < 4; xl++) {
490 int mx = xl;
491 int my = yl;
492
493 // Formula information to get tile index position in the array.
494 // ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
495 int tx = ((draw_id / num_x_tiles) * img_width) +
496 ((draw_id - ((draw_id / 16) * 16)) * 4);
497 uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];
498
499 // nx,ny = object position, xx,yy = tile position, xl,yl = pixel
500 // position
501 int index = x + (y * 172) + (mx * 2) + (my * 172);
502 if ((pixel & 0x0F) != 0) {
503 current_font_gfx16_data_[index + 1] =
504 (uint8_t)((pixel & 0x0F) + (0 * 4));
505 }
506
507 if (((pixel >> 4) & 0x0F) != 0) {
508 current_font_gfx16_data_[index + 0] =
509 (uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
510 }
511 }
512 }
513}
514
516 for (const auto c : str) {
518 }
519}
520
524
525void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
526 for (const uint8_t& value : text) {
527 if (skip_next) {
528 skip_next = false;
529 continue;
530 }
531
532 if (value < 100) {
533 int srcy = value / 16;
534 int srcx = value - (value & (~0xF));
535
536 if (text_position_ >= 170) {
537 text_position_ = 0;
538 text_line_++;
539 }
540
541 DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
542 text_position_ += width_array[value];
543 } else if (value == kLine1) {
544 text_position_ = 0;
545 text_line_ = 0;
546 } else if (value == kScrollVertical) {
547 text_position_ = 0;
548 text_line_ += 1;
549 } else if (value == kLine2) {
550 text_position_ = 0;
551 text_line_ = 1;
552 } else if (value == kLine3) {
553 text_position_ = 0;
554 text_line_ = 2;
555 } else if (value == 0x6B || value == 0x6D || value == 0x6E ||
556 value == 0x77 || value == 0x78 || value == 0x79 ||
557 value == 0x7A) {
558 skip_next = true;
559
560 continue;
561 } else if (value == 0x6C) // BCD numbers.
562 {
564 skip_next = true;
565
566 continue;
567 } else if (value == 0x6A) {
568 // Includes parentheses to be longer, since player names can be up to 6
569 // characters.
570 DrawStringToPreview("(NAME)");
571 } else if (value >= DICTOFF && value < (DICTOFF + 97)) {
572 auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
573 DrawCharacterToPreview(dictionaryEntry.Data);
574 }
575 }
576}
577
579 // From Parsing.
580 text_line_ = 0;
581 for (int i = 0; i < (172 * 4096); i++) {
583 }
584 text_position_ = 0;
586 shown_lines_ = 0;
587}
588
589absl::Status MessageEditor::Cut() {
590 // Ensure that text is currently selected in the text box.
591 if (!message_text_box_.text.empty()) {
592 // Cut the selected text in the control and paste it into the Clipboard.
594 }
595 return absl::OkStatus();
596}
597
598absl::Status MessageEditor::Paste() {
599 // Determine if there is any text in the Clipboard to paste into the
600 if (ImGui::GetClipboardText() != nullptr) {
601 // Paste the text from the Clipboard into the text box.
603 }
604 return absl::OkStatus();
605}
606
607absl::Status MessageEditor::Copy() {
608 // Ensure that text is selected in the text box.
610 // Copy the selected text to the Clipboard.
612 }
613 return absl::OkStatus();
614}
615
616absl::Status MessageEditor::Undo() {
617 // Determine if last operation can be undone in text box.
619 // Undo the last operation.
621
622 // clear the undo buffer to prevent last action from being redone.
624 }
625 return absl::OkStatus();
626}
627
628absl::Status MessageEditor::Save() {
629 std::vector<uint8_t> backup = rom()->vector();
630
631 for (int i = 0; i < 100; i++) {
633 }
634
635 int pos = kTextData;
636 bool in_second_bank = false;
637
638 for (const auto& message : list_of_texts_) {
639 for (const auto value : message.Data) {
640 RETURN_IF_ERROR(rom()->Write(pos, value));
641
642 if (value == kBlockTerminator) {
643 // Make sure we didn't go over the space available in the first block.
644 // 0x7FFF available.
645 if ((!in_second_bank & pos) > kTextDataEnd) {
646 return absl::InternalError(DisplayTextOverflowError(pos, true));
647 }
648
649 // Switch to the second block.
650 pos = kTextData2 - 1;
651 in_second_bank = true;
652 }
653
654 pos++;
655 }
656
658 rom()->Write(pos++, kMessageTerminator)); // , true, "Terminator text"
659 }
660
661 // Verify that we didn't go over the space available for the second block.
662 // 0x14BF available.
663 if ((in_second_bank & pos) > kTextData2End) {
664 // rom()->data() = backup;
665 return absl::InternalError(DisplayTextOverflowError(pos, false));
666 }
667
668 RETURN_IF_ERROR(rom()->Write(pos, 0xFF)); // , true, "End of text"
669
670 return absl::OkStatus();
671}
672
673std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
674 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
675 std::string bankSTR = bank ? "1st" : "2nd";
676 std::string posSTR =
677 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
678 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
679 std::string message = absl::StrFormat(
680 "There is too much text data in the %s block to save.\n"
681 "Available: %X4 | Used: %s",
682 bankSTR, space, posSTR);
683 return message;
684}
685
687 // Determine if any text is selected in the TextBox control.
689 // clear all of the text in the textbox.
691 }
692}
693
695 // Determine if any text is selected in the TextBox control.
697 // Select all text in the text box.
699
700 // Move the cursor to the text box.
702 }
703}
704
705} // namespace editor
706} // namespace app
707} // namespace yaze
void UpdateBitmap(gfx::Bitmap *bitmap)
Used to update a bitmap on the screen.
Definition renderer.h:56
static Renderer & GetInstance()
Definition renderer.h:27
DictionaryEntry GetDictionaryFromID(uint8_t value)
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal, int sizex=1, int sizey=1)
absl::Status Paste() override
std::string DisplayTextOverflowError(int pos, bool bank)
std::vector< std::string > parsed_messages_
std::vector< uint8_t > current_font_gfx16_data_
absl::Status Update() override
std::vector< MessageData > list_of_texts_
std::vector< uint8_t > font_gfx16_data_
std::vector< DictionaryEntry > all_dictionaries_
void DrawStringToPreview(std::string str)
uint8_t width_array[kWidthArraySize]
auto palette() const
Definition bitmap.h:168
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0), bool drag=false)
Definition canvas.cc:66
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:686
void DrawBitmap(const Bitmap &bitmap, int border_offset=0, bool ready=true)
Definition canvas.cc:461
void DrawContextMenu(gfx::Bitmap *bitmap=nullptr)
Definition canvas.cc:101
#define RETURN_IF_ERROR(expression)
Definition constants.h:62
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc)
Definition common.cc:139
std::string UppercaseHexLong(uint32_t dword)
Definition common.cc:119
std::string UppercaseHexWord(uint16_t word, bool leading)
Definition common.cc:111
constexpr uint8_t kLine3
const std::string DICTIONARYTOKEN
uint8_t FindDictionaryEntry(uint8_t value)
constexpr uint8_t DICTOFF
constexpr int kPointersDictionaries
constexpr int kTextDataEnd
constexpr int kCurrentMessageHeight
constexpr int kGfxFont
constexpr int kFontGfxMessageDepth
constexpr int kTextData
constexpr int kTextData2
std::vector< DictionaryEntry > BuildDictionaryEntries(app::Rom *rom)
constexpr int kFontGfxMessageSize
constexpr uint8_t kWidthArraySize
const uint8_t kMessageTerminator
const std::string BANKToken
constexpr ImGuiTableFlags kDictTableFlags
uint8_t FindMatchingCharacter(char value)
constexpr uint8_t kLine1
constexpr int kCurrentMessageWidth
TextElement FindMatchingSpecial(uint8_t value)
constexpr uint8_t kLine2
constexpr ImGuiTableFlags kMessageTableFlags
constexpr int kTextData2End
constexpr uint8_t kBlockTerminator
TextElement FindMatchingCommand(uint8_t b)
constexpr int kCharactersWidth
std::vector< uint8_t > ParseMessageToData(std::string str)
std::string ParseTextDataByte(uint8_t value)
constexpr uint8_t kScrollVertical
std::string ReplaceAllDictionaryWords(std::string str, std::vector< DictionaryEntry > dictionary)
std::vector< uint8_t > SnesTo8bppSheet(const std::vector< uint8_t > &sheet, int bpp, int num_sheets)
Definition snes_tile.cc:152
void EndPadding()
Definition style.cc:40
void BeginPadding(int i)
Definition style.cc:36
Definition common.cc:22
std::vector< uint8_t > Data
std::string GetParameterizedToken(uint8_t value=0)