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