yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_data.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
2#define YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
3
4// ===========================================================================
5// Message Data System for Zelda 3 (A Link to the Past)
6// ===========================================================================
7//
8// This system handles the parsing, editing, and serialization of in-game text
9// messages from The Legend of Zelda: A Link to the Past (SNES).
10//
11// ## Architecture Overview
12//
13// The message system consists of several key components:
14//
15// 1. **Character Encoding** (`CharEncoder`):
16// Maps byte values (0x00-0x66) to displayable characters (A-Z, a-z, 0-9,
17// punctuation). This is the basic text representation in the ROM.
18//
19// 2. **Text Commands** (`TextCommands`):
20// Special control codes (0x67-0x80) that control message display behavior:
21// - Window appearance (border, position)
22// - Text flow (line breaks, scrolling, delays)
23// - Interactive elements (choices, player name insertion)
24// - Some commands have arguments (e.g., [W:02] = window border type 2)
25//
26// 3. **Special Characters** (`SpecialChars`):
27// Extended character set (0x43-0x5E) for game-specific symbols:
28// - Directional arrows
29// - Button prompts (A, B, X, Y)
30// - HP indicators
31// - Hieroglyphs
32//
33// 4. **Dictionary System** (`DictionaryEntry`):
34// Compression system using byte values 0x88+ to reference common
35// words/phrases stored separately in ROM. This saves space by replacing
36// frequently-used text with single-byte references.
37//
38// 5. **Message Data** (`MessageData`):
39// Represents a single in-game message with both raw binary data and parsed
40// human-readable text. Each message is terminated by 0x7F in ROM.
41//
42// ## Data Flow
43//
44// ### Reading from ROM:
45// ROM bytes → ReadAllTextData() → MessageData (raw) → ParseMessageData() →
46// Human-readable string with [command] tokens
47//
48// ### Writing to ROM:
49// User edits text → ParseMessageToData() → Binary bytes → ROM
50//
51// ### Dictionary Optimization:
52// Text string → OptimizeMessageForDictionary() → Replace common phrases with
53// [D:XX] tokens → Smaller binary representation
54//
55// ## ROM Memory Layout (SNES)
56//
57// - Text Data Block 1: 0xE0000 - 0xE7FFF (32KB)
58// - Text Data Block 2: 0x75F40 - 0x773FF (5.3KB)
59// - Dictionary Pointers: 0x74703
60// - Character Widths: Table storing pixel widths for proportional font
61// - Font Graphics: 0x70000+ (2bpp tile data)
62//
63// ## Message Format
64//
65// Messages are stored as byte sequences terminated by 0x7F:
66// Example: [0x00, 0x01, 0x02, 0x7F] = "ABC"
67// Example: [0x6A, 0x59, 0x2C, 0x61, 0x32, 0x28, 0x2B, 0x23, 0x7F]
68// = "[L] saved Hyrule" (0x6A = player name command)
69//
70// ## Token Syntax (Human-Readable Format)
71//
72// Commands: [TOKEN:HEX] or [TOKEN]
73// Examples: [W:02] (window border), [K] (wait for key)
74// Dictionary: [D:HEX]
75// Examples: [D:00] (first dictionary entry)
76// Special Chars:[TOKEN]
77// Examples: [A] (A button), [UP] (up arrow)
78//
79// ===========================================================================
80
81#include <optional>
82#include <regex>
83#include <string>
84#include <string_view>
85#include <unordered_map>
86#include <vector>
87
88#include <nlohmann/json.hpp>
89#include "absl/status/status.h"
90#include "absl/status/statusor.h"
91#include "absl/strings/match.h"
92#include "absl/strings/str_format.h"
93#include "absl/strings/str_replace.h"
94#include "rom/rom.h"
95
96namespace yaze {
97namespace editor {
98
99const std::string kBankToken = "BANK";
100const std::string DICTIONARYTOKEN = "D";
101constexpr uint8_t kMessageTerminator = 0x7F; // Marks end of message in ROM
102constexpr uint8_t DICTOFF = 0x88; // Dictionary entries start at byte 0x88
103constexpr uint8_t kWidthArraySize = 100;
104constexpr uint8_t kBankSwitchCommand = 0x80;
105
106// Character encoding table: Maps ROM byte values to displayable characters
107// Used for both parsing ROM data into text and converting text back to bytes
108static const std::unordered_map<uint8_t, wchar_t> CharEncoder = {
109 {0x00, 'A'}, {0x01, 'B'}, {0x02, 'C'}, {0x03, 'D'}, {0x04, 'E'},
110 {0x05, 'F'}, {0x06, 'G'}, {0x07, 'H'}, {0x08, 'I'}, {0x09, 'J'},
111 {0x0A, 'K'}, {0x0B, 'L'}, {0x0C, 'M'}, {0x0D, 'N'}, {0x0E, 'O'},
112 {0x0F, 'P'}, {0x10, 'Q'}, {0x11, 'R'}, {0x12, 'S'}, {0x13, 'T'},
113 {0x14, 'U'}, {0x15, 'V'}, {0x16, 'W'}, {0x17, 'X'}, {0x18, 'Y'},
114 {0x19, 'Z'}, {0x1A, 'a'}, {0x1B, 'b'}, {0x1C, 'c'}, {0x1D, 'd'},
115 {0x1E, 'e'}, {0x1F, 'f'}, {0x20, 'g'}, {0x21, 'h'}, {0x22, 'i'},
116 {0x23, 'j'}, {0x24, 'k'}, {0x25, 'l'}, {0x26, 'm'}, {0x27, 'n'},
117 {0x28, 'o'}, {0x29, 'p'}, {0x2A, 'q'}, {0x2B, 'r'}, {0x2C, 's'},
118 {0x2D, 't'}, {0x2E, 'u'}, {0x2F, 'v'}, {0x30, 'w'}, {0x31, 'x'},
119 {0x32, 'y'}, {0x33, 'z'}, {0x34, '0'}, {0x35, '1'}, {0x36, '2'},
120 {0x37, '3'}, {0x38, '4'}, {0x39, '5'}, {0x3A, '6'}, {0x3B, '7'},
121 {0x3C, '8'}, {0x3D, '9'}, {0x3E, '!'}, {0x3F, '?'}, {0x40, '-'},
122 {0x41, '.'}, {0x42, ','}, {0x44, '>'}, {0x45, '('}, {0x46, ')'},
123 {0x4C, '"'}, {0x51, '\''}, {0x59, ' '}, {0x5A, '<'}, {0x5F, L'¡'},
124 {0x60, L'¡'}, {0x61, L'¡'}, {0x62, L' '}, {0x63, L' '}, {0x64, L' '},
125 {0x65, ' '}, {0x66, '_'},
126};
127
128// Finds the ROM byte value for a given character (reverse lookup in
129// CharEncoder) Returns 0xFF if character is not found
130uint8_t FindMatchingCharacter(char value);
131
132// Checks if a byte value represents a dictionary entry
133// Returns dictionary index (0-96) or -1 if not a dictionary entry
134int8_t FindDictionaryEntry(uint8_t value);
135
136// Converts a human-readable message string (with [command] tokens) into ROM
137// bytes This is the inverse operation of ParseMessageData
138std::vector<uint8_t> ParseMessageToData(std::string str);
139
140// Result of parsing text into message bytes with diagnostics.
142 std::vector<uint8_t> bytes;
143 std::vector<std::string> errors;
144 std::vector<std::string> warnings;
145
146 bool ok() const { return errors.empty(); }
147};
148
149// Converts text into message bytes and captures parse errors/warnings.
150MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str);
151
152// Message bank for bundle import/export.
153enum class MessageBank {
154 kVanilla,
155 kExpanded,
156};
157
158std::string MessageBankToString(MessageBank bank);
159absl::StatusOr<MessageBank> MessageBankFromString(std::string_view value);
160
161// Represents a single dictionary entry (common word/phrase) used for text
162// compression Dictionary entries are stored separately in ROM and referenced by
163// bytes 0x88-0xE8 Example: Dictionary entry 0x00 might contain "the" and be
164// referenced as [D:00]
166 uint8_t ID = 0; // Dictionary index (0-96)
167 std::string Contents = ""; // The actual text this entry represents
168 std::vector<uint8_t> Data; // Binary representation of Contents
169 int Length = 0; // Character count
170 std::string Token = ""; // Human-readable token like "[D:00]"
171
172 DictionaryEntry() = default;
173 DictionaryEntry(uint8_t i, std::string_view s)
174 : ID(i), Contents(s), Length(s.length()) {
175 Token = absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN, ID);
177 }
178
179 // Checks if this dictionary entry's text appears in the given string
180 bool ContainedInString(std::string_view s) const {
181 // Convert to std::string to avoid Debian string_view bug with
182 // absl::StrContains
183 return absl::StrContains(std::string(s), Contents);
184 }
185
186 // Replaces all occurrences of this dictionary entry's text with its token
187 // Example: "the cat" with dictionary[0]="the" becomes "[D:00] cat"
188 std::string ReplaceInstancesOfIn(std::string_view s) const {
189 auto replaced_string = std::string(s);
190 size_t pos = replaced_string.find(Contents);
191 while (pos != std::string::npos) {
192 replaced_string.replace(pos, Contents.length(), Token);
193 pos = replaced_string.find(Contents, pos + Token.length());
194 }
195 return replaced_string;
196 }
197};
198
199constexpr int kTextData = 0xE0000;
200constexpr int kTextDataEnd = 0xE7FFF;
201constexpr int kNumDictionaryEntries = 0x61;
202constexpr int kPointersDictionaries = 0x74703;
203constexpr uint8_t kScrollVertical = 0x73;
204constexpr uint8_t kLine1 = 0x74;
205constexpr uint8_t kLine2 = 0x75;
206constexpr uint8_t kLine3 = 0x76;
207
208// Reads all dictionary entries from ROM and builds the dictionary table
209std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom);
210
211// Replaces all dictionary words in a string with their [D:XX] tokens
212// Used for text compression when saving messages back to ROM
213std::string ReplaceAllDictionaryWords(
214 std::string str, const std::vector<DictionaryEntry>& dictionary);
215
216// Finds the next match position for query in text starting at start_pos.
217// Returns std::nullopt when no match is found or query is empty.
218std::optional<size_t> FindTextMatch(std::string_view text,
219 std::string_view query, size_t start_pos,
220 bool case_sensitive,
221 bool match_whole_word);
222
223// Replaces query occurrences in text and returns replacement count.
224// If replace_all is false, only the first matching occurrence at/after
225// start_pos is replaced.
226int ReplaceTextMatches(std::string* text, std::string_view query,
227 std::string_view replacement, size_t start_pos,
228 bool replace_all, bool case_sensitive,
229 bool match_whole_word,
230 size_t* first_replaced_pos = nullptr);
231
232// Looks up a dictionary entry by its ROM byte value
234 uint8_t value, const std::vector<DictionaryEntry>& dictionary);
235
236// Special marker inserted into commands to protect them from dictionary
237// replacements during optimization. Removed after dictionary replacement is
238// complete.
239const std::string CHEESE = "\uBEBE";
240
241// Represents a complete in-game message with both raw and parsed
242// representations Messages can exist in two forms:
243// 1. Raw: Direct ROM bytes with dictionary references as [D:XX] tokens
244// 2. Parsed: Fully expanded with dictionary words replaced by actual text
246 int ID = 0; // Message index in the ROM
247 int Address = 0; // ROM address where this message is stored
248 std::string RawString; // Human-readable with [D:XX] dictionary tokens
249 std::string ContentsParsed; // Fully expanded human-readable text
250 std::vector<uint8_t> Data; // Raw ROM bytes (may contain dict references)
251 std::vector<uint8_t> DataParsed; // Expanded bytes (dict entries expanded)
252
253 MessageData() = default;
254 MessageData(int id, int address, const std::string& rawString,
255 const std::vector<uint8_t>& rawData,
256 const std::string& parsedString,
257 const std::vector<uint8_t>& parsedData)
258 : ID(id),
259 Address(address),
260 RawString(rawString),
261 ContentsParsed(parsedString),
262 Data(rawData),
263 DataParsed(parsedData) {}
264
265 // Copy constructor
266 MessageData(const MessageData& other) {
267 ID = other.ID;
268 Address = other.Address;
269 RawString = other.RawString;
270 Data = other.Data;
271 DataParsed = other.DataParsed;
273 }
274
275 // Optimizes a message by replacing common phrases with dictionary tokens
276 // Inserts CHEESE markers inside commands to prevent dictionary replacement
277 // from corrupting command syntax like [W:02]
278 // Example: "Link saved the day" → "[D:00] saved [D:01] day"
280 std::string_view message_string,
281 const std::vector<DictionaryEntry>& dictionary) {
282 std::stringstream protons;
283 bool command = false;
284 // Insert CHEESE markers inside commands to protect them
285 for (const auto& c : message_string) {
286 if (c == '[') {
287 command = true;
288 } else if (c == ']') {
289 command = false;
290 }
291
292 protons << c;
293 if (command) {
294 protons << CHEESE; // Protect command contents from replacement
295 }
296 }
297
298 std::string protons_string = protons.str();
299 std::string replaced_string =
300 ReplaceAllDictionaryWords(protons_string, dictionary);
301 std::string final_string =
302 absl::StrReplaceAll(replaced_string, {{CHEESE, ""}});
303
304 return final_string;
305 }
306
307 // Updates this message with new text content
308 // Automatically optimizes the message using dictionary compression
309 void SetMessage(const std::string& message,
310 const std::vector<DictionaryEntry>& dictionary) {
311 RawString = message;
312 ContentsParsed = OptimizeMessageForDictionary(message, dictionary);
313 }
314};
315
316// Message bundle entry for JSON import/export.
318 int id = 0;
320 std::string raw;
321 std::string parsed;
322 std::string text;
323};
324
325constexpr int kMessageBundleVersion = 1;
326
327// Represents a text command or special character definition
328// Text commands control message display (line breaks, colors, choices, etc.)
329// Special characters are game-specific symbols (arrows, buttons, HP hearts)
331 uint8_t ID; // ROM byte value for this element
332 std::string Token; // Short token like "W" or "UP"
333 std::string GenericToken; // Display format like "[W:##]" or "[UP]"
334 std::string Pattern; // Regex pattern for parsing
335 std::string StrictPattern; // Strict regex pattern for exact matching
336 std::string Description; // Human-readable description
337 bool HasArgument; // True if command takes a parameter byte
338
339 TextElement() = default;
340 TextElement(uint8_t id, const std::string& token, bool arg,
341 const std::string& description) {
342 ID = id;
343 Token = token;
344 if (arg) {
345 GenericToken = absl::StrFormat("[%s:##]", Token);
346 } else {
347 GenericToken = absl::StrFormat("[%s]", Token);
348 }
349 HasArgument = arg;
350 Description = description;
351 if (arg) {
352 Pattern = absl::StrFormat(
353 "\\[%s(:[0-9A-F]{1,2})?\\]",
354 absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
355 } else {
356 Pattern = absl::StrFormat(
357 "\\[%s\\]", absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
358 }
359 StrictPattern = absl::StrFormat("^%s$", Pattern);
360 }
361
362 std::string GetParamToken(uint8_t value = 0) const {
363 if (HasArgument) {
364 return absl::StrFormat("[%s:%02X]", Token, value);
365 } else {
366 return absl::StrFormat("[%s]", Token);
367 }
368 }
369
370 std::smatch MatchMe(const std::string& dfrag) const {
371 std::regex pattern(StrictPattern);
372 std::smatch match;
373 std::regex_match(dfrag, match, pattern);
374 return match;
375 }
376
377 bool Empty() const { return ID == 0; }
378
379 // Comparison operator
380 bool operator==(const TextElement& other) const { return ID == other.ID; }
381};
382
383const static std::string kWindowBorder = "Window border";
384const static std::string kWindowPosition = "Window position";
385const static std::string kScrollSpeed = "Scroll speed";
386const static std::string kTextDrawSpeed = "Text draw speed";
387const static std::string kTextColor = "Text color";
388const static std::string kPlayerName = "Player name";
389const static std::string kLine1Str = "Line 1";
390const static std::string kLine2Str = "Line 2";
391const static std::string kLine3Str = "Line 3";
392const static std::string kWaitForKey = "Wait for key";
393const static std::string kScrollText = "Scroll text";
394const static std::string kDelayX = "Delay X";
395const static std::string kBCDNumber = "BCD number";
396const static std::string kSoundEffect = "Sound effect";
397const static std::string kChoose3 = "Choose 3";
398const static std::string kChoose2High = "Choose 2 high";
399const static std::string kChoose2Low = "Choose 2 low";
400const static std::string kChoose2Indented = "Choose 2 indented";
401const static std::string kChooseItem = "Choose item";
402const static std::string kNextAttractImage = "Next attract image";
403const static std::string kBankMarker = "Bank marker (automatic)";
404const static std::string kCrash = "Crash";
405
406static const std::vector<TextElement> TextCommands = {
407 TextElement(0x6B, "W", true, kWindowBorder),
408 TextElement(0x6D, "P", true, kWindowPosition),
409 TextElement(0x6E, "SPD", true, kScrollSpeed),
410 TextElement(0x7A, "S", true, kTextDrawSpeed),
411 TextElement(0x77, "C", true, kTextColor),
412 TextElement(0x6A, "L", false, kPlayerName),
413 TextElement(0x74, "1", false, kLine1Str),
414 TextElement(0x75, "2", false, kLine2Str),
415 TextElement(0x76, "3", false, kLine3Str),
416 TextElement(0x7E, "K", false, kWaitForKey),
417 TextElement(0x73, "V", false, kScrollText),
418 TextElement(0x78, "WT", true, kDelayX),
419 TextElement(0x6C, "N", true, kBCDNumber),
420 TextElement(0x79, "SFX", true, kSoundEffect),
421 TextElement(0x71, "CH3", false, kChoose3),
422 TextElement(0x72, "CH2", false, kChoose2High),
423 TextElement(0x6F, "CH2L", false, kChoose2Low),
424 TextElement(0x68, "CH2I", false, kChoose2Indented),
425 TextElement(0x69, "CHI", false, kChooseItem),
426 TextElement(0x67, "IMG", false, kNextAttractImage),
427 TextElement(0x80, kBankToken, false, kBankMarker),
428 TextElement(0x70, "NONO", false, kCrash),
429};
430
431// Finds the TextElement definition for a command byte value
432// Returns nullopt if the byte is not a recognized command
433std::optional<TextElement> FindMatchingCommand(uint8_t b);
434
435// Special characters available in Zelda 3 messages
436// These are symbols and game-specific icons that appear in text
437static const std::vector<TextElement> SpecialChars = {
438 TextElement(0x43, "...", false, "Ellipsis …"),
439 TextElement(0x4D, "UP", false, "Arrow ↑"),
440 TextElement(0x4E, "DOWN", false, "Arrow ↓"),
441 TextElement(0x4F, "LEFT", false, "Arrow ←"),
442 TextElement(0x50, "RIGHT", false, "Arrow →"),
443 TextElement(0x5B, "A", false, "Button Ⓐ"),
444 TextElement(0x5C, "B", false, "Button Ⓑ"),
445 TextElement(0x5D, "X", false, "Button ⓧ"),
446 TextElement(0x5E, "Y", false, "Button ⓨ"),
447 TextElement(0x52, "HP1L", false, "1 HP left"),
448 TextElement(0x53, "HP1R", false, "1 HP right"),
449 TextElement(0x54, "HP2L", false, "2 HP left"),
450 TextElement(0x55, "HP3L", false, "3 HP left"),
451 TextElement(0x56, "HP3R", false, "3 HP right"),
452 TextElement(0x57, "HP4L", false, "4 HP left"),
453 TextElement(0x58, "HP4R", false, "4 HP right"),
454 TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
455 TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
456 TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
457 TextElement(0x4A, "LFL", false, "Link face left"),
458 TextElement(0x4B, "LFR", false, "Link face right"),
459};
460
461// Finds the TextElement definition for a special character byte
462// Returns nullopt if the byte is not a recognized special character
463std::optional<TextElement> FindMatchingSpecial(uint8_t b);
464
465// Result of parsing a text token like "[W:02]"
466// Contains both the command definition and its argument value
468 TextElement Parent; // The command or special character definition
469 uint8_t Value; // Argument value (if command has argument)
470 bool Active = false; // True if parsing was successful
471
472 ParsedElement() = default;
473 ParsedElement(const TextElement& textElement, uint8_t value)
474 : Parent(textElement), Value(value), Active(true) {}
475};
476
477// Parses a token string like "[W:02]" and returns its ParsedElement
478// Returns inactive ParsedElement if token is invalid
479ParsedElement FindMatchingElement(const std::string& str);
480
481// Converts a single ROM byte into its human-readable text representation
482// Handles characters, commands, special chars, and dictionary references
483std::string ParseTextDataByte(uint8_t value);
484
485// Parses a single message from ROM data starting at current_pos
486// Updates current_pos to point after the message terminator
487// Returns error if message is malformed (e.g., missing terminator)
488absl::StatusOr<MessageData> ParseSingleMessage(
489 const std::vector<uint8_t>& rom_data, int* current_pos);
490
491// Converts MessageData objects into human-readable strings with [command]
492// tokens This is the main function for displaying messages in the editor
493// Properly handles commands with arguments to avoid parsing errors
494std::vector<std::string> ParseMessageData(
495 std::vector<MessageData>& message_data,
496 const std::vector<DictionaryEntry>& dictionary_entries);
497
498constexpr int kTextData2 = 0x75F40;
499constexpr int kTextData2End = 0x773FF;
500
501// Reads all text data from the ROM and returns a vector of MessageData objects.
502// When max_pos > 0, the parser stops if pos exceeds max_pos (safety bound).
503std::vector<MessageData> ReadAllTextData(uint8_t* rom, int pos = kTextData,
504 int max_pos = -1);
505
506// Calls the file dialog and loads expanded messages from a BIN file.
507absl::Status LoadExpandedMessages(std::string& expanded_message_path,
508 std::vector<std::string>& parsed_messages,
509 std::vector<MessageData>& expanded_messages,
510 std::vector<DictionaryEntry>& dictionary);
511
512// Serializes a vector of MessageData to a JSON object.
513nlohmann::json SerializeMessagesToJson(const std::vector<MessageData>& messages);
514
515// Exports messages to a JSON file at the specified path.
516absl::Status ExportMessagesToJson(const std::string& path,
517 const std::vector<MessageData>& messages);
518
519// Serializes message bundles (vanilla + expanded) to JSON.
520nlohmann::json SerializeMessageBundle(const std::vector<MessageData>& vanilla,
521 const std::vector<MessageData>& expanded);
522
523// Exports message bundle to JSON file.
524absl::Status ExportMessageBundleToJson(
525 const std::string& path, const std::vector<MessageData>& vanilla,
526 const std::vector<MessageData>& expanded);
527
528// Parses message bundle JSON into entries.
529absl::StatusOr<std::vector<MessageBundleEntry>> ParseMessageBundleJson(
530 const nlohmann::json& json);
531
532// Loads message bundle entries from a JSON file.
533absl::StatusOr<std::vector<MessageBundleEntry>> LoadMessageBundleFromJson(
534 const std::string& path);
535
536// ===========================================================================
537// Line Width Validation
538// ===========================================================================
539
540constexpr int kMaxLineWidth = 32; // Maximum visible characters per line
541
542// Validates that no line in a message exceeds kMaxLineWidth visible characters.
543// Splits on line break tokens: [1], [2], [3], [V], [K]
544// Returns a vector of warning strings (empty if all lines are within bounds).
545// Command tokens like [W:02], [SFX:2D] etc. are not counted as visible chars.
546std::vector<std::string> ValidateMessageLineWidths(const std::string& message);
547
548// ===========================================================================
549// Org Format (.org) Import/Export
550// ===========================================================================
551
552// Parses an org-mode header line like "** 0F - Skeleton Guard"
553// Returns {message_id, label} pair, or nullopt if not a valid header.
554std::optional<std::pair<int, std::string>> ParseOrgHeader(
555 const std::string& line);
556
557// Parses the full content of a .org file into message entries.
558// Returns a vector of {message_id, body_text} pairs.
559std::vector<std::pair<int, std::string>> ParseOrgContent(
560 const std::string& content);
561
562// Exports messages to .org format string.
563// messages: vector of {message_id, body_text} pairs
564// labels: parallel vector of human-readable labels for each message
565std::string ExportToOrgFormat(
566 const std::vector<std::pair<int, std::string>>& messages,
567 const std::vector<std::string>& labels);
568
569// ===========================================================================
570// Expanded Message Bank (Oracle of Secrets: $2F8000)
571// ===========================================================================
572
573// PC address of SNES $2F8000 (expanded message region start)
574constexpr int kExpandedTextDataDefault = 0x178000;
575// PC address of SNES $2FFFFF (expanded message region end)
576constexpr int kExpandedTextDataEndDefault = 0x17FFFF;
577
580
581// Reads expanded messages from a ROM buffer at the given PC address.
582// Messages are 0x7F-terminated, region is 0xFF-terminated.
583// Uses the same parsing as ReadAllTextData but for the expanded bank.
584std::vector<MessageData> ReadExpandedTextData(uint8_t* rom, int pos);
585
586// Writes encoded messages to the expanded region of a ROM buffer.
587// Each message text is encoded via ParseMessageToData, terminated with 0x7F.
588// The region is terminated with 0xFF.
589// Returns error if total size exceeds (end - start + 1).
590//
591// Prefer the Rom* overload when possible:
592// - updates Rom dirty state
593// - is write-fence aware (ROM safety guardrails)
594absl::Status WriteExpandedTextData(Rom* rom, int start, int end,
595 const std::vector<std::string>& messages);
596
597// Legacy buffer overload. This bypasses Rom write fences and does not mark the
598// ROM dirty. Prefer WriteExpandedTextData(Rom*, ...) for new code.
599absl::Status WriteExpandedTextData(uint8_t* rom, int start, int end,
600 const std::vector<std::string>& messages);
601
602// Writes all vanilla message data back to the ROM with bank switching.
603absl::Status WriteAllTextData(Rom* rom,
604 const std::vector<MessageData>& messages);
605
606} // namespace editor
607} // namespace yaze
608
609#endif // YAZE_APP_EDITOR_MESSAGE_MESSAGE_DATA_H
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:28
uint8_t FindMatchingCharacter(char value)
const std::string kBankToken
nlohmann::json SerializeMessagesToJson(const std::vector< MessageData > &messages)
absl::StatusOr< MessageBank > MessageBankFromString(std::string_view value)
DictionaryEntry FindRealDictionaryEntry(uint8_t value, const std::vector< DictionaryEntry > &dictionary)
const std::string CHEESE
constexpr int kMaxLineWidth
int GetExpandedTextDataStart()
constexpr int kMessageBundleVersion
const std::string DICTIONARYTOKEN
constexpr uint8_t kScrollVertical
std::string ParseTextDataByte(uint8_t value)
absl::Status WriteAllTextData(Rom *rom, const std::vector< MessageData > &messages)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
constexpr uint8_t kLine1
constexpr int kTextData
std::optional< std::pair< int, std::string > > ParseOrgHeader(const std::string &line)
std::string MessageBankToString(MessageBank bank)
constexpr int kExpandedTextDataEndDefault
constexpr int kTextData2
std::string ReplaceAllDictionaryWords(std::string str, const std::vector< DictionaryEntry > &dictionary)
absl::Status WriteExpandedTextData(Rom *rom, int start, int end, const std::vector< std::string > &messages)
nlohmann::json SerializeMessageBundle(const std::vector< MessageData > &vanilla, const std::vector< MessageData > &expanded)
constexpr uint8_t kLine2
constexpr int kPointersDictionaries
absl::StatusOr< std::vector< MessageBundleEntry > > LoadMessageBundleFromJson(const std::string &path)
constexpr int kNumDictionaryEntries
absl::StatusOr< MessageData > ParseSingleMessage(const std::vector< uint8_t > &rom_data, int *current_pos)
absl::StatusOr< std::vector< MessageBundleEntry > > ParseMessageBundleJson(const nlohmann::json &json)
std::vector< std::string > ParseMessageData(std::vector< MessageData > &message_data, const std::vector< DictionaryEntry > &dictionary_entries)
std::optional< TextElement > FindMatchingSpecial(uint8_t value)
constexpr uint8_t kMessageTerminator
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos, int max_pos)
constexpr int kTextData2End
std::vector< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
constexpr uint8_t kBankSwitchCommand
int ReplaceTextMatches(std::string *text, std::string_view query, std::string_view replacement, size_t start_pos, bool replace_all, bool case_sensitive, bool match_whole_word, size_t *first_replaced_pos)
std::optional< size_t > FindTextMatch(std::string_view text, std::string_view query, size_t start_pos, bool case_sensitive, bool match_whole_word)
std::vector< uint8_t > ParseMessageToData(std::string str)
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 uint8_t DICTOFF
std::string ExportToOrgFormat(const std::vector< std::pair< int, std::string > > &messages, const std::vector< std::string > &labels)
std::vector< MessageData > ReadExpandedTextData(uint8_t *rom, int pos)
std::optional< TextElement > FindMatchingCommand(uint8_t b)
MessageParseResult ParseMessageToDataWithDiagnostics(std::string_view str)
int GetExpandedTextDataEnd()
ParsedElement FindMatchingElement(const std::string &str)
std::vector< std::string > ValidateMessageLineWidths(const std::string &message)
std::vector< std::pair< int, std::string > > ParseOrgContent(const std::string &content)
constexpr int kExpandedTextDataDefault
constexpr uint8_t kLine3
int8_t FindDictionaryEntry(uint8_t value)
constexpr int kTextDataEnd
bool ContainedInString(std::string_view s) const
std::string ReplaceInstancesOfIn(std::string_view s) const
DictionaryEntry(uint8_t i, std::string_view s)
std::vector< uint8_t > Data
MessageData(const MessageData &other)
std::vector< uint8_t > Data
std::vector< uint8_t > DataParsed
std::string OptimizeMessageForDictionary(std::string_view message_string, const std::vector< DictionaryEntry > &dictionary)
void SetMessage(const std::string &message, const std::vector< DictionaryEntry > &dictionary)
MessageData(int id, int address, const std::string &rawString, const std::vector< uint8_t > &rawData, const std::string &parsedString, const std::vector< uint8_t > &parsedData)
std::vector< uint8_t > bytes
std::vector< std::string > errors
std::vector< std::string > warnings
ParsedElement(const TextElement &textElement, uint8_t value)
std::smatch MatchMe(const std::string &dfrag) const
TextElement(uint8_t id, const std::string &token, bool arg, const std::string &description)
bool operator==(const TextElement &other) const
std::string GetParamToken(uint8_t value=0) const