yaze 0.2.2
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#include "imgui/misc/cpp/imgui_stdlib.h"
19#include "util/hex.h"
20
21namespace yaze {
22namespace editor {
23
24using core::Renderer;
25
26using ImGui::BeginChild;
27using ImGui::BeginTable;
28using ImGui::Button;
29using ImGui::EndChild;
30using ImGui::EndTable;
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
70
73 for (int i = 0; i < kCurrentMessageWidth * kCurrentMessageHeight; i++) {
74 current_font_gfx16_data_.push_back(0);
75 }
76
77 // 8bpp
81
82 *font_gfx_bitmap_.mutable_palette() = font_preview_colors_;
83
86}
87
88absl::Status MessageEditor::Load() { return absl::OkStatus(); }
89
90absl::Status MessageEditor::Update() {
91 if (rom()->is_loaded() && !data_loaded_) {
92 Initialize();
94 data_loaded_ = true;
95 }
96
97 if (BeginTable("##MessageEditor", 4, kDictTableFlags)) {
98 TableSetupColumn("List");
99 TableSetupColumn("Contents");
100 TableSetupColumn("Commands");
101 TableSetupColumn("Dictionary");
102
103 TableHeadersRow();
104
105 TableNextColumn();
107
108 TableNextColumn();
110
111 TableNextColumn();
113
114 TableNextColumn();
116
117 EndTable();
118 }
119
120 return absl::OkStatus();
121}
122
124 if (BeginChild("##MessagesList", ImVec2(0, 0), true,
125 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
126 if (BeginTable("##MessagesTable", 3, kMessageTableFlags)) {
127 TableSetupColumn("ID");
128 TableSetupColumn("Contents");
129 TableSetupColumn("Data");
130
131 TableHeadersRow();
132
133 for (const auto &message : list_of_texts_) {
134 TableNextColumn();
135 if (Button(util::HexWord(message.ID).c_str())) {
136 current_message_ = message;
138 }
139 TableNextColumn();
140 TextWrapped("%s", parsed_messages_[message.ID].c_str());
141 TableNextColumn();
142 TextWrapped("%s",
143 util::HexLong(list_of_texts_[message.ID].Address).c_str());
144 }
145
146 EndTable();
147 }
148 EndChild();
149 }
150}
151
153 if (!rom()->is_loaded()) {
154 return;
155 }
156 Button(absl::StrCat("Message ", current_message_.ID).c_str());
157 if (InputTextMultiline("##MessageEditor",
159 ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
162 }
163 Separator();
164
165 Text("Font Graphics");
167 BeginChild("MessageEditorCanvas", ImVec2(0, 130));
168 font_gfx_canvas_.DrawBackground();
169 font_gfx_canvas_.DrawContextMenu();
170 font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
171 font_gfx_canvas_.DrawGrid();
172 font_gfx_canvas_.DrawOverlay();
173 EndChild();
175 Separator();
176
177 Text("Message Preview");
178 if (Button("Refresh Bitmap")) {
180 }
182 BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
183 ImGuiWindowFlags_AlwaysVerticalScrollbar);
184 current_font_gfx16_canvas_.DrawBackground();
186 current_font_gfx16_canvas_.DrawContextMenu();
189 current_font_gfx16_canvas_.DrawOverlay();
190 EndChild();
191}
192
194 if (BeginChild("##TextCommands", ImVec2(0, 0), true,
195 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
196 for (const auto &text_element : TextCommands) {
197 if (Button(text_element.GenericToken.c_str())) {
198 }
199 SameLine();
200 TextWrapped("%s", text_element.Description.c_str());
201 Separator();
202 }
203 EndChild();
204 }
205}
206
208 if (ImGui::BeginChild("##DictionaryChild", ImVec2(0, 0), true,
209 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
210 if (BeginTable("##Dictionary", 2, kDictTableFlags)) {
211 TableSetupColumn("ID");
212 TableSetupColumn("Contents");
213
214 for (const auto &dictionary : all_dictionaries_) {
215 TableNextColumn();
216 Text("%s", util::HexWord(dictionary.ID).c_str());
217 TableNextColumn();
218 Text("%s", dictionary.Contents.c_str());
219 }
220 EndTable();
221 }
222
223 EndChild();
224 }
225}
226
227// TODO: Fix the command parsing.
229 // Read all text data from the ROM.
230 int pos = kTextData;
231 int message_id = 0;
232
233 std::vector<uint8_t> raw_message;
234 std::vector<uint8_t> parsed_message;
235
236 std::string current_raw_message;
237 std::string current_parsed_message;
238
239 uint8_t current_byte = 0;
240 while (current_byte != 0xFF) {
241 current_byte = rom()->data()[pos++];
242 if (current_byte == kMessageTerminator) {
243 list_of_texts_.push_back(
244 MessageData(message_id++, pos, current_raw_message, raw_message,
245 current_parsed_message, parsed_message));
246 std::cout << "Message ID: " << message_id << std::endl;
247 std::cout << "Raw: " << current_raw_message << std::endl;
248 std::cout << "Parsed: " << current_parsed_message << std::endl;
249 std::cout << "Raw Bytes: ";
250 for (const auto &byte : raw_message) {
251 std::cout << util::HexByte(byte) << " ";
252 }
253 std::cout << std::endl;
254 std::cout << "Parsed Bytes: ";
255 for (const auto &byte : parsed_message) {
256 std::cout << util::HexByte(byte) << " ";
257 }
258 std::cout << std::endl;
259 raw_message.clear();
260 parsed_message.clear();
261 current_raw_message.clear();
262 current_parsed_message.clear();
263 continue;
264 }
265
266 raw_message.push_back(current_byte);
267
268 TextElement text_element = FindMatchingCommand(current_byte);
269 if (!text_element.Empty()) {
270 parsed_message.push_back(current_byte);
271 if (text_element.HasArgument) {
272 current_byte = rom()->data()[pos++];
273 raw_message.push_back(current_byte);
274 parsed_message.push_back(current_byte);
275 }
276
277 current_raw_message.append(text_element.GetParamToken(current_byte));
278 current_parsed_message.append(text_element.GetParamToken(current_byte));
279
280 if (text_element.Token == kBankToken) {
281 pos = kTextData2;
282 }
283
284 continue;
285 }
286
287 // Check for special characters.
288 text_element = FindMatchingSpecial(current_byte);
289 if (!text_element.Empty()) {
290 current_raw_message.append(text_element.GetParamToken());
291 current_parsed_message.append(text_element.GetParamToken());
292 parsed_message.push_back(current_byte);
293 continue;
294 }
295
296 // Check for dictionary.
297 int dictionary = FindDictionaryEntry(current_byte);
298 if (dictionary >= 0) {
299 current_raw_message.append("[");
300 current_raw_message.append(DICTIONARYTOKEN);
301 current_raw_message.append(":");
302 current_raw_message.append(util::HexWord(dictionary));
303 current_raw_message.append("]");
304
305 uint32_t address = Get24LocalFromPC(
306 rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
307 uint32_t address_end =
308 Get24LocalFromPC(rom()->mutable_data(),
309 kPointersDictionaries + ((dictionary + 1) * 2));
310
311 for (uint32_t i = address; i < address_end; i++) {
312 parsed_message.push_back(rom()->data()[i]);
313 current_parsed_message.append(ParseTextDataByte(rom()->data()[i]));
314 }
315
316 continue;
317 }
318
319 // Everything else.
320 if (CharEncoder.contains(current_byte)) {
321 std::string str = "";
322 str.push_back(CharEncoder.at(current_byte));
323 current_raw_message.append(str);
324 current_parsed_message.append(str);
325 parsed_message.push_back(current_byte);
326 }
327 }
328}
329
331 int pos = kTextData;
332 int message_id = 0;
333 uint8_t current_byte;
334 std::vector<uint8_t> temp_bytes_raw;
335 std::vector<uint8_t> temp_bytes_parsed;
336
337 std::string current_message_raw;
338 std::string current_message_parsed;
339 TextElement text_element;
340
341 while (true) {
342 current_byte = rom()->data()[pos++];
343
344 if (current_byte == kMessageTerminator) {
345 auto message =
346 MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
347 current_message_parsed, temp_bytes_parsed);
348
349 list_of_texts_.push_back(message);
350
351 temp_bytes_raw.clear();
352 temp_bytes_parsed.clear();
353 current_message_raw.clear();
354 current_message_parsed.clear();
355
356 continue;
357 } else if (current_byte == 0xFF) {
358 break;
359 }
360
361 temp_bytes_raw.push_back(current_byte);
362
363 // Check for command.
364 text_element = FindMatchingCommand(current_byte);
365
366 if (!text_element.Empty()) {
367 temp_bytes_parsed.push_back(current_byte);
368 if (text_element.HasArgument) {
369 current_byte = rom()->data()[pos++];
370 temp_bytes_raw.push_back(current_byte);
371 temp_bytes_parsed.push_back(current_byte);
372 }
373
374 current_message_raw.append(text_element.GetParamToken(current_byte));
375 current_message_parsed.append(text_element.GetParamToken(current_byte));
376
377 if (text_element.Token == kBankToken) {
378 pos = kTextData2;
379 }
380
381 continue;
382 }
383
384 // Check for special characters.
385 text_element = FindMatchingSpecial(current_byte);
386 if (!text_element.Empty()) {
387 current_message_raw.append(text_element.GetParamToken());
388 current_message_parsed.append(text_element.GetParamToken());
389 temp_bytes_parsed.push_back(current_byte);
390 continue;
391 }
392
393 // Check for dictionary.
394 int dictionary = FindDictionaryEntry(current_byte);
395
396 if (dictionary >= 0) {
397 current_message_raw.append("[");
398 current_message_raw.append(DICTIONARYTOKEN);
399 current_message_raw.append(":");
400 current_message_raw.append(util::HexWord(dictionary));
401 current_message_raw.append("]");
402
403 uint32_t address = Get24LocalFromPC(
404 rom()->mutable_data(), kPointersDictionaries + (dictionary * 2));
405 uint32_t address_end =
406 Get24LocalFromPC(rom()->mutable_data(),
407 kPointersDictionaries + ((dictionary + 1) * 2));
408
409 for (uint32_t i = address; i < address_end; i++) {
410 temp_bytes_parsed.push_back(rom()->data()[i]);
411 current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
412 }
413
414 continue;
415 }
416
417 // Everything else.
418 if (CharEncoder.contains(current_byte)) {
419 std::string str = "";
420 str.push_back(CharEncoder.at(current_byte));
421 current_message_raw.append(str);
422 current_message_parsed.append(str);
423 temp_bytes_parsed.push_back(current_byte);
424 }
425 }
426}
427
428std::string ReplaceAllDictionaryWords(std::string str,
429 std::vector<DictionaryEntry> dictionary) {
430 std::string temp = str;
431 for (const auto &entry : dictionary) {
432 if (absl::StrContains(temp, entry.Contents)) {
433 temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
434 }
435 }
436 return temp;
437}
438
440 if (value < 0 || value >= all_dictionaries_.size()) {
441 return DictionaryEntry();
442 }
443 return all_dictionaries_[value];
444}
445
446void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
447 int sizex, int sizey) {
448 const int num_x_tiles = 16;
449 const int img_width = 512; // (imgwidth/2)
450 int draw_id = srcx + (srcy * 32);
451 for (int yl = 0; yl < sizey * 8; yl++) {
452 for (int xl = 0; xl < 4; xl++) {
453 int mx = xl;
454 int my = yl;
455
456 // Formula information to get tile index position in the array.
457 // ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
458 int tx = ((draw_id / num_x_tiles) * img_width) +
459 ((draw_id - ((draw_id / 16) * 16)) * 4);
460 uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];
461
462 // nx,ny = object position, xx,yy = tile position, xl,yl = pixel
463 // position
464 int index = x + (y * 172) + (mx * 2) + (my * 172);
465 if ((pixel & 0x0F) != 0) {
466 current_font_gfx16_data_[index + 1] =
467 (uint8_t)((pixel & 0x0F) + (0 * 4));
468 }
469
470 if (((pixel >> 4) & 0x0F) != 0) {
471 current_font_gfx16_data_[index + 0] =
472 (uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
473 }
474 }
475 }
476}
477
479 for (const auto c : str) {
481 }
482}
483
487
488void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t> &text) {
489 for (const uint8_t &value : text) {
490 if (skip_next) {
491 skip_next = false;
492 continue;
493 }
494
495 if (value < 100) {
496 int srcy = value / 16;
497 int srcx = value - (value & (~0xF));
498
499 if (text_position_ >= 170) {
500 text_position_ = 0;
501 text_line_++;
502 }
503
504 DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
505 text_position_ += width_array[value];
506 } else if (value == kLine1) {
507 text_position_ = 0;
508 text_line_ = 0;
509 } else if (value == kScrollVertical) {
510 text_position_ = 0;
511 text_line_ += 1;
512 } else if (value == kLine2) {
513 text_position_ = 0;
514 text_line_ = 1;
515 } else if (value == kLine3) {
516 text_position_ = 0;
517 text_line_ = 2;
518 } else if (value == 0x6B || value == 0x6D || value == 0x6E ||
519 value == 0x77 || value == 0x78 || value == 0x79 ||
520 value == 0x7A) {
521 skip_next = true;
522
523 continue;
524 } else if (value == 0x6C) // BCD numbers.
525 {
527 skip_next = true;
528
529 continue;
530 } else if (value == 0x6A) {
531 // Includes parentheses to be longer, since player names can be up to 6
532 // characters.
533 DrawStringToPreview("(NAME)");
534 } else if (value >= DICTOFF && value < (DICTOFF + 97)) {
535 auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
536 DrawCharacterToPreview(dictionaryEntry.Data);
537 }
538 }
539}
540
542 // From Parsing.
543 text_line_ = 0;
544 for (int i = 0; i < kFontGfx16Size; i++) {
546 }
547 text_position_ = 0;
549 shown_lines_ = 0;
550}
551
552absl::Status MessageEditor::Cut() {
553 // Ensure that text is currently selected in the text box.
554 if (!message_text_box_.text.empty()) {
555 // Cut the selected text in the control and paste it into the Clipboard.
556 message_text_box_.Cut();
557 }
558 return absl::OkStatus();
559}
560
561absl::Status MessageEditor::Paste() {
562 // Determine if there is any text in the Clipboard to paste into the
563 if (ImGui::GetClipboardText() != nullptr) {
564 // Paste the text from the Clipboard into the text box.
565 message_text_box_.Paste();
566 }
567 return absl::OkStatus();
568}
569
570absl::Status MessageEditor::Copy() {
571 // Ensure that text is selected in the text box.
572 if (message_text_box_.selection_length > 0) {
573 // Copy the selected text to the Clipboard.
574 message_text_box_.Copy();
575 }
576 return absl::OkStatus();
577}
578
579absl::Status MessageEditor::Undo() {
580 // Determine if last operation can be undone in text box.
581 if (message_text_box_.can_undo) {
582 // Undo the last operation.
583 message_text_box_.Undo();
584
585 // clear the undo buffer to prevent last action from being redone.
586 message_text_box_.clearUndo();
587 }
588 return absl::OkStatus();
589}
590
591absl::Status MessageEditor::Save() {
592 std::vector<uint8_t> backup = rom()->vector();
593
594 for (int i = 0; i < kWidthArraySize; i++) {
595 RETURN_IF_ERROR(rom()->WriteByte(kCharactersWidth + i, width_array[i]));
596 }
597
598 int pos = kTextData;
599 bool in_second_bank = false;
600
601 for (const auto &message : list_of_texts_) {
602 for (const auto value : message.Data) {
603 RETURN_IF_ERROR(rom()->WriteByte(pos, value));
604
605 if (value == kBlockTerminator) {
606 // Make sure we didn't go over the space available in the first block.
607 // 0x7FFF available.
608 if ((!in_second_bank & pos) > kTextDataEnd) {
609 return absl::InternalError(DisplayTextOverflowError(pos, true));
610 }
611
612 // Switch to the second block.
613 pos = kTextData2 - 1;
614 in_second_bank = true;
615 }
616
617 pos++;
618 }
619
620 RETURN_IF_ERROR(rom()->WriteByte(pos++, kMessageTerminator));
621 }
622
623 // Verify that we didn't go over the space available for the second block.
624 // 0x14BF available.
625 if ((in_second_bank & pos) > kTextData2End) {
626 // TODO: Restore the backup.
627 return absl::InternalError(DisplayTextOverflowError(pos, false));
628 }
629
630 RETURN_IF_ERROR(rom()->WriteByte(pos, 0xFF));
631
632 return absl::OkStatus();
633}
634
635std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
636 int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
637 std::string bankSTR = bank ? "1st" : "2nd";
638 std::string posSTR =
639 bank ? absl::StrFormat("%X4", pos & 0xFFFF)
640 : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
641 std::string message = absl::StrFormat(
642 "There is too much text data in the %s block to save.\n"
643 "Available: %X4 | Used: %s",
644 bankSTR, space, posSTR);
645 return message;
646}
647
649 // Determine if any text is selected in the TextBox control.
650 if (message_text_box_.selection_length == 0) {
651 // clear all of the text in the textbox.
652 message_text_box_.clear();
653 }
654}
655
657 // Determine if any text is selected in the TextBox control.
658 if (message_text_box_.selection_length == 0) {
659 // Select all text in the text box.
660 message_text_box_.SelectAll();
661
662 // Move the cursor to the text box.
663 message_text_box_.Focus();
664 }
665}
666
667} // namespace editor
668} // namespace yaze
auto rom()
Definition rom.h:382
void CreateAndRenderBitmap(int width, int height, int depth, const std::vector< uint8_t > &data, gfx::Bitmap &bitmap, gfx::SnesPalette &palette)
Definition renderer.h:59
void UpdateBitmap(gfx::Bitmap *bitmap)
Used to update a bitmap on the screen.
Definition renderer.h:55
std::vector< std::string > parsed_messages_
absl::Status Copy() override
absl::Status Update() override
std::array< uint8_t, kWidthArraySize > width_array
absl::Status Paste() override
std::string DisplayTextOverflowError(int pos, bool bank)
void DrawStringToPreview(std::string str)
absl::Status Undo() override
absl::Status Load() override
void DrawTileToPreview(int x, int y, int srcx, int srcy, int pal, int sizex=1, int sizey=1)
absl::Status Cut() override
std::vector< MessageData > list_of_texts_
std::vector< uint8_t > font_gfx16_data_
std::vector< uint8_t > current_font_gfx16_data_
gfx::SnesPalette font_preview_colors_
DictionaryEntry GetDictionaryFromID(uint8_t value)
std::vector< DictionaryEntry > all_dictionaries_
absl::Status Save() override
static Renderer & GetInstance()
Definition renderer.h:26
#define RETURN_IF_ERROR(expression)
Definition macro.h:51
Editors are the view controllers for the application.
constexpr int kCharactersWidth
uint8_t FindMatchingCharacter(char value)
const std::string kBankToken
const uint8_t kMessageTerminator
const std::string DICTIONARYTOKEN
constexpr uint8_t kScrollVertical
std::string ParseTextDataByte(uint8_t value)
constexpr int kFontGfx16Size
constexpr uint8_t kLine1
constexpr int kTextData
constexpr int kCurrentMessageWidth
constexpr int kTextData2
uint8_t FindDictionaryEntry(uint8_t value)
constexpr int kCurrentMessageHeight
constexpr uint8_t kBlockTerminator
constexpr uint8_t kLine2
std::string ReplaceAllDictionaryWords(std::string str, std::vector< DictionaryEntry > dictionary)
constexpr int kPointersDictionaries
constexpr int kGfxFont
constexpr int kFontGfxMessageSize
std::vector< std::string > ParseMessageData(std::vector< MessageData > &message_data, const std::vector< DictionaryEntry > &dictionary_entries)
constexpr int kFontGfxMessageDepth
constexpr int kTextData2End
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
TextElement FindMatchingSpecial(uint8_t value)
std::vector< uint8_t > ParseMessageToData(std::string str)
constexpr uint8_t kWidthArraySize
constexpr uint8_t DICTOFF
constexpr ImGuiTableFlags kMessageTableFlags
constexpr ImGuiTableFlags kDictTableFlags
TextElement FindMatchingCommand(uint8_t b)
constexpr uint8_t kLine3
constexpr int kTextDataEnd
std::vector< uint8_t > SnesTo8bppSheet(const std::vector< uint8_t > &sheet, int bpp, int num_sheets)
Definition snes_tile.cc:139
void BeginPadding(int i)
Definition style.cc:372
void EndPadding()
Definition style.cc:376
std::string HexWord(uint16_t word, HexStringParams params)
Definition hex.cc:46
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:33
std::string HexLong(uint32_t dword, HexStringParams params)
Definition hex.cc:59
Main namespace for the application.
Definition controller.cc:18
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc=true)
Definition rom.h:348
std::string GetParamToken(uint8_t value=0) const