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