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, bool match_whole_word);
221
222// Replaces query occurrences in text and returns replacement count.
223// If replace_all is false, only the first matching occurrence at/after
224// start_pos is replaced.
225int ReplaceTextMatches(std::string* text, std::string_view query,
226 std::string_view replacement, size_t start_pos,
227 bool replace_all, bool case_sensitive,
228 bool match_whole_word,
229 size_t* first_replaced_pos = nullptr);
230
231// Looks up a dictionary entry by its ROM byte value
233 uint8_t value, const std::vector<DictionaryEntry>& dictionary);
234
235// Special marker inserted into commands to protect them from dictionary
236// replacements during optimization. Removed after dictionary replacement is
237// complete.
238const std::string CHEESE = "\uBEBE";
239
240// Represents a complete in-game message with both raw and parsed
241// representations Messages can exist in two forms:
242// 1. Raw: Direct ROM bytes with dictionary references as [D:XX] tokens
243// 2. Parsed: Fully expanded with dictionary words replaced by actual text
245 int ID = 0; // Message index in the ROM
246 int Address = 0; // ROM address where this message is stored
247 std::string RawString; // Human-readable with [D:XX] dictionary tokens
248 std::string ContentsParsed; // Fully expanded human-readable text
249 std::vector<uint8_t> Data; // Raw ROM bytes (may contain dict references)
250 std::vector<uint8_t> DataParsed; // Expanded bytes (dict entries expanded)
251
252 MessageData() = default;
253 MessageData(int id, int address, const std::string& rawString,
254 const std::vector<uint8_t>& rawData,
255 const std::string& parsedString,
256 const std::vector<uint8_t>& parsedData)
257 : ID(id),
258 Address(address),
259 RawString(rawString),
260 ContentsParsed(parsedString),
261 Data(rawData),
262 DataParsed(parsedData) {}
263
264 // Copy constructor
265 MessageData(const MessageData& other) {
266 ID = other.ID;
267 Address = other.Address;
268 RawString = other.RawString;
269 Data = other.Data;
270 DataParsed = other.DataParsed;
272 }
273
274 // Optimizes a message by replacing common phrases with dictionary tokens
275 // Inserts CHEESE markers inside commands to prevent dictionary replacement
276 // from corrupting command syntax like [W:02]
277 // Example: "Link saved the day" → "[D:00] saved [D:01] day"
279 std::string_view message_string,
280 const std::vector<DictionaryEntry>& dictionary) {
281 std::stringstream protons;
282 bool command = false;
283 // Insert CHEESE markers inside commands to protect them
284 for (const auto& c : message_string) {
285 if (c == '[') {
286 command = true;
287 } else if (c == ']') {
288 command = false;
289 }
290
291 protons << c;
292 if (command) {
293 protons << CHEESE; // Protect command contents from replacement
294 }
295 }
296
297 std::string protons_string = protons.str();
298 std::string replaced_string =
299 ReplaceAllDictionaryWords(protons_string, dictionary);
300 std::string final_string =
301 absl::StrReplaceAll(replaced_string, {{CHEESE, ""}});
302
303 return final_string;
304 }
305
306 // Updates this message with new text content
307 // Automatically optimizes the message using dictionary compression
308 void SetMessage(const std::string& message,
309 const std::vector<DictionaryEntry>& dictionary) {
310 RawString = message;
311 ContentsParsed = OptimizeMessageForDictionary(message, dictionary);
312 }
313};
314
315// Message bundle entry for JSON import/export.
317 int id = 0;
319 std::string raw;
320 std::string parsed;
321 std::string text;
322};
323
324constexpr int kMessageBundleVersion = 1;
325
326// Represents a text command or special character definition
327// Text commands control message display (line breaks, colors, choices, etc.)
328// Special characters are game-specific symbols (arrows, buttons, HP hearts)
330 uint8_t ID; // ROM byte value for this element
331 std::string Token; // Short token like "W" or "UP"
332 std::string GenericToken; // Display format like "[W:##]" or "[UP]"
333 std::string Pattern; // Regex pattern for parsing
334 std::string StrictPattern; // Strict regex pattern for exact matching
335 std::string Description; // Human-readable description
336 bool HasArgument; // True if command takes a parameter byte
337
338 TextElement() = default;
339 TextElement(uint8_t id, const std::string& token, bool arg,
340 const std::string& description) {
341 ID = id;
342 Token = token;
343 if (arg) {
344 GenericToken = absl::StrFormat("[%s:##]", Token);
345 } else {
346 GenericToken = absl::StrFormat("[%s]", Token);
347 }
348 HasArgument = arg;
349 Description = description;
350 if (arg) {
351 Pattern = absl::StrFormat(
352 "\\[%s(:[0-9A-F]{1,2})?\\]",
353 absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
354 } else {
355 Pattern = absl::StrFormat(
356 "\\[%s\\]", absl::StrReplaceAll(Token, {{"[", "\\["}, {"]", "\\]"}}));
357 }
358 StrictPattern = absl::StrFormat("^%s$", Pattern);
359 }
360
361 std::string GetParamToken(uint8_t value = 0) const {
362 if (HasArgument) {
363 return absl::StrFormat("[%s:%02X]", Token, value);
364 } else {
365 return absl::StrFormat("[%s]", Token);
366 }
367 }
368
369 std::smatch MatchMe(const std::string& dfrag) const {
370 std::regex pattern(StrictPattern);
371 std::smatch match;
372 std::regex_match(dfrag, match, pattern);
373 return match;
374 }
375
376 bool Empty() const { return ID == 0; }
377
378 // Comparison operator
379 bool operator==(const TextElement& other) const { return ID == other.ID; }
380};
381
382const static std::string kWindowBorder = "Window border";
383const static std::string kWindowPosition = "Window position";
384const static std::string kScrollSpeed = "Scroll speed";
385const static std::string kTextDrawSpeed = "Text draw speed";
386const static std::string kTextColor = "Text color";
387const static std::string kPlayerName = "Player name";
388const static std::string kLine1Str = "Line 1";
389const static std::string kLine2Str = "Line 2";
390const static std::string kLine3Str = "Line 3";
391const static std::string kWaitForKey = "Wait for key";
392const static std::string kScrollText = "Scroll text";
393const static std::string kDelayX = "Delay X";
394const static std::string kBCDNumber = "BCD number";
395const static std::string kSoundEffect = "Sound effect";
396const static std::string kChoose3 = "Choose 3";
397const static std::string kChoose2High = "Choose 2 high";
398const static std::string kChoose2Low = "Choose 2 low";
399const static std::string kChoose2Indented = "Choose 2 indented";
400const static std::string kChooseItem = "Choose item";
401const static std::string kNextAttractImage = "Next attract image";
402const static std::string kBankMarker = "Bank marker (automatic)";
403const static std::string kCrash = "Crash";
404
405static const std::vector<TextElement> TextCommands = {
406 TextElement(0x6B, "W", true, kWindowBorder),
407 TextElement(0x6D, "P", true, kWindowPosition),
408 TextElement(0x6E, "SPD", true, kScrollSpeed),
409 TextElement(0x7A, "S", true, kTextDrawSpeed),
410 TextElement(0x77, "C", true, kTextColor),
411 TextElement(0x6A, "L", false, kPlayerName),
412 TextElement(0x74, "1", false, kLine1Str),
413 TextElement(0x75, "2", false, kLine2Str),
414 TextElement(0x76, "3", false, kLine3Str),
415 TextElement(0x7E, "K", false, kWaitForKey),
416 TextElement(0x73, "V", false, kScrollText),
417 TextElement(0x78, "WT", true, kDelayX),
418 TextElement(0x6C, "N", true, kBCDNumber),
419 TextElement(0x79, "SFX", true, kSoundEffect),
420 TextElement(0x71, "CH3", false, kChoose3),
421 TextElement(0x72, "CH2", false, kChoose2High),
422 TextElement(0x6F, "CH2L", false, kChoose2Low),
423 TextElement(0x68, "CH2I", false, kChoose2Indented),
424 TextElement(0x69, "CHI", false, kChooseItem),
425 TextElement(0x67, "IMG", false, kNextAttractImage),
426 TextElement(0x80, kBankToken, false, kBankMarker),
427 TextElement(0x70, "NONO", false, kCrash),
428};
429
430// Finds the TextElement definition for a command byte value
431// Returns nullopt if the byte is not a recognized command
432std::optional<TextElement> FindMatchingCommand(uint8_t b);
433
434// Special characters available in Zelda 3 messages
435// These are symbols and game-specific icons that appear in text
436static const std::vector<TextElement> SpecialChars = {
437 TextElement(0x43, "...", false, "Ellipsis …"),
438 TextElement(0x4D, "UP", false, "Arrow ↑"),
439 TextElement(0x4E, "DOWN", false, "Arrow ↓"),
440 TextElement(0x4F, "LEFT", false, "Arrow ←"),
441 TextElement(0x50, "RIGHT", false, "Arrow →"),
442 TextElement(0x5B, "A", false, "Button Ⓐ"),
443 TextElement(0x5C, "B", false, "Button Ⓑ"),
444 TextElement(0x5D, "X", false, "Button ⓧ"),
445 TextElement(0x5E, "Y", false, "Button ⓨ"),
446 TextElement(0x52, "HP1L", false, "1 HP left"),
447 TextElement(0x53, "HP1R", false, "1 HP right"),
448 TextElement(0x54, "HP2L", false, "2 HP left"),
449 TextElement(0x55, "HP3L", false, "3 HP left"),
450 TextElement(0x56, "HP3R", false, "3 HP right"),
451 TextElement(0x57, "HP4L", false, "4 HP left"),
452 TextElement(0x58, "HP4R", false, "4 HP right"),
453 TextElement(0x47, "HY0", false, "Hieroglyph ☥"),
454 TextElement(0x48, "HY1", false, "Hieroglyph 𓈗"),
455 TextElement(0x49, "HY2", false, "Hieroglyph Ƨ"),
456 TextElement(0x4A, "LFL", false, "Link face left"),
457 TextElement(0x4B, "LFR", false, "Link face right"),
458};
459
460// Finds the TextElement definition for a special character byte
461// Returns nullopt if the byte is not a recognized special character
462std::optional<TextElement> FindMatchingSpecial(uint8_t b);
463
464// Result of parsing a text token like "[W:02]"
465// Contains both the command definition and its argument value
467 TextElement Parent; // The command or special character definition
468 uint8_t Value; // Argument value (if command has argument)
469 bool Active = false; // True if parsing was successful
470
471 ParsedElement() = default;
472 ParsedElement(const TextElement& textElement, uint8_t value)
473 : Parent(textElement), Value(value), Active(true) {}
474};
475
476// Parses a token string like "[W:02]" and returns its ParsedElement
477// Returns inactive ParsedElement if token is invalid
478ParsedElement FindMatchingElement(const std::string& str);
479
480// Converts a single ROM byte into its human-readable text representation
481// Handles characters, commands, special chars, and dictionary references
482std::string ParseTextDataByte(uint8_t value);
483
484// Parses a single message from ROM data starting at current_pos
485// Updates current_pos to point after the message terminator
486// Returns error if message is malformed (e.g., missing terminator)
487absl::StatusOr<MessageData> ParseSingleMessage(
488 const std::vector<uint8_t>& rom_data, int* current_pos);
489
490// Converts MessageData objects into human-readable strings with [command]
491// tokens This is the main function for displaying messages in the editor
492// Properly handles commands with arguments to avoid parsing errors
493std::vector<std::string> ParseMessageData(
494 std::vector<MessageData>& message_data,
495 const std::vector<DictionaryEntry>& dictionary_entries);
496
497constexpr int kTextData2 = 0x75F40;
498constexpr int kTextData2End = 0x773FF;
499
500// Reads all text data from the ROM and returns a vector of MessageData objects.
501// When max_pos > 0, the parser stops if pos exceeds max_pos (safety bound).
502// Set allow_bank_switch=false for contiguous banks such as expanded messages.
503std::vector<MessageData> ReadAllTextData(uint8_t* rom, int pos = kTextData,
504 int max_pos = -1,
505 bool allow_bank_switch = true);
506
507// Calls the file dialog and loads expanded messages from a BIN file.
508absl::Status LoadExpandedMessages(std::string& expanded_message_path,
509 std::vector<std::string>& parsed_messages,
510 std::vector<MessageData>& expanded_messages,
511 std::vector<DictionaryEntry>& dictionary);
512
513// Serializes a vector of MessageData to a JSON object.
514nlohmann::json SerializeMessagesToJson(
515 const std::vector<MessageData>& messages);
516
517// Exports messages to a JSON file at the specified path.
518absl::Status ExportMessagesToJson(const std::string& path,
519 const std::vector<MessageData>& messages);
520
521// Serializes message bundles (vanilla + expanded) to JSON.
522nlohmann::json SerializeMessageBundle(const std::vector<MessageData>& vanilla,
523 const std::vector<MessageData>& expanded);
524
525// Exports message bundle to JSON file.
526absl::Status ExportMessageBundleToJson(
527 const std::string& path, const std::vector<MessageData>& vanilla,
528 const std::vector<MessageData>& expanded);
529
530// Parses message bundle JSON into entries.
531absl::StatusOr<std::vector<MessageBundleEntry>> ParseMessageBundleJson(
532 const nlohmann::json& json);
533
534// Loads message bundle entries from a JSON file.
535absl::StatusOr<std::vector<MessageBundleEntry>> LoadMessageBundleFromJson(
536 const std::string& path);
537
538// ===========================================================================
539// Line Width Validation
540// ===========================================================================
541
542constexpr int kMaxLineWidth = 32; // Maximum visible characters per line
543
544// Validates that no line in a message exceeds kMaxLineWidth visible characters.
545// Splits on line break tokens: [1], [2], [3], [V], [K]
546// Returns a vector of warning strings (empty if all lines are within bounds).
547// Command tokens like [W:02], [SFX:2D] etc. are not counted as visible chars.
548std::vector<std::string> ValidateMessageLineWidths(const std::string& message);
549
550// ===========================================================================
551// Org Format (.org) Import/Export
552// ===========================================================================
553
554// Parses an org-mode header line like "** 0F - Skeleton Guard"
555// Returns {message_id, label} pair, or nullopt if not a valid header.
556std::optional<std::pair<int, std::string>> ParseOrgHeader(
557 const std::string& line);
558
559// Parses the full content of a .org file into message entries.
560// Returns a vector of {message_id, body_text} pairs.
561std::vector<std::pair<int, std::string>> ParseOrgContent(
562 const std::string& content);
563
564// Exports messages to .org format string.
565// messages: vector of {message_id, body_text} pairs
566// labels: parallel vector of human-readable labels for each message
567std::string ExportToOrgFormat(
568 const std::vector<std::pair<int, std::string>>& messages,
569 const std::vector<std::string>& labels);
570
571// ===========================================================================
572// Expanded Message Bank (Oracle of Secrets: $2F8000)
573// ===========================================================================
574
575// PC address of SNES $2F8000 (expanded message region start)
576constexpr int kExpandedTextDataDefault = 0x178000;
577// PC address of SNES $2FFFFF (expanded message region end)
578constexpr int kExpandedTextDataEndDefault = 0x17FFFF;
579
582
583// Reads expanded messages from a ROM buffer at the given PC address.
584// Messages are 0x7F-terminated, region is 0xFF-terminated.
585// Uses the same parsing as ReadAllTextData but for the expanded bank.
586std::vector<MessageData> ReadExpandedTextData(uint8_t* rom, int pos);
587
588// Bounded expanded-message reader. `end` is the inclusive final PC address;
589// parsing stops there even when the 0xFF region terminator is missing.
590std::vector<MessageData> ReadExpandedTextData(uint8_t* rom, int pos, int end);
591
592// Writes encoded messages to the expanded region of a ROM buffer.
593// Each message text is encoded via ParseMessageToData, terminated with 0x7F.
594// The region is terminated with 0xFF.
595// Returns error if total size exceeds (end - start + 1).
596//
597// Prefer the Rom* overload when possible:
598// - updates Rom dirty state
599// - is write-fence aware (ROM safety guardrails)
600absl::Status WriteExpandedTextData(Rom* rom, int start, int end,
601 const std::vector<std::string>& messages);
602
603// Legacy buffer overload. This bypasses Rom write fences and does not mark the
604// ROM dirty. Prefer WriteExpandedTextData(Rom*, ...) for new code.
605absl::Status WriteExpandedTextData(uint8_t* rom, int start, int end,
606 const std::vector<std::string>& messages);
607
608// Writes all vanilla message data back to the ROM with bank switching.
609absl::Status WriteAllTextData(Rom* rom,
610 const std::vector<MessageData>& messages);
611
612} // namespace editor
613} // namespace yaze
614
615#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< MessageData > ReadAllTextData(uint8_t *rom, int pos, int max_pos, bool allow_bank_switch)
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
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