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