yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
message_data.cc
Go to the documentation of this file.
1#include "message_data.h"
2
3#include <optional>
4#include <string>
5
6#include "rom/snes.h"
7#include "util/hex.h"
8#include "util/log.h"
9
10namespace yaze {
11namespace editor {
12
13uint8_t FindMatchingCharacter(char value) {
14 for (const auto [key, char_value] : CharEncoder) {
15 if (value == char_value) {
16 return key;
17 }
18 }
19 return 0xFF;
20}
21
22int8_t FindDictionaryEntry(uint8_t value) {
23 if (value < DICTOFF || value == 0xFF) {
24 return -1;
25 }
26 return value - DICTOFF;
27}
28
29std::optional<TextElement> FindMatchingCommand(uint8_t b) {
30 for (const auto& text_element : TextCommands) {
31 if (text_element.ID == b) {
32 return text_element;
33 }
34 }
35 return std::nullopt;
36}
37
38std::optional<TextElement> FindMatchingSpecial(uint8_t value) {
39 auto it = std::ranges::find_if(SpecialChars,
40 [value](const TextElement& text_element) {
41 return text_element.ID == value;
42 });
43 if (it != SpecialChars.end()) {
44 return *it;
45 }
46 return std::nullopt;
47}
48
49ParsedElement FindMatchingElement(const std::string& str) {
50 std::smatch match;
51 std::vector<TextElement> commands_and_chars = TextCommands;
52 commands_and_chars.insert(commands_and_chars.end(), SpecialChars.begin(),
53 SpecialChars.end());
54 for (auto& text_element : commands_and_chars) {
55 match = text_element.MatchMe(str);
56 if (match.size() > 0) {
57 if (text_element.HasArgument) {
58 std::string arg = match[1].str().substr(1);
59 try {
60 return ParsedElement(text_element, std::stoi(arg, nullptr, 16));
61 } catch (const std::invalid_argument& e) {
62 util::logf("Error parsing argument for %s: %s", text_element.GenericToken.c_str(), arg.c_str());
63 return ParsedElement(text_element, 0);
64 } catch (const std::out_of_range& e) {
65 util::logf("Argument out of range for %s: %s", text_element.GenericToken.c_str(), arg.c_str());
66 return ParsedElement(text_element, 0);
67 }
68 } else {
69 return ParsedElement(text_element, 0);
70 }
71 }
72 }
73
74 const auto dictionary_element =
75 TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
76
77 match = dictionary_element.MatchMe(str);
78 if (match.size() > 0) {
79 try {
80 return ParsedElement(dictionary_element,
81 DICTOFF + std::stoi(match[1].str(), nullptr, 16));
82 } catch (const std::exception& e) {
83 util::logf("Error parsing dictionary token: %s", match[1].str().c_str());
84 return ParsedElement();
85 }
86 }
87 return ParsedElement();
88}
89
90std::string ParseTextDataByte(uint8_t value) {
91 if (CharEncoder.contains(value)) {
92 char c = CharEncoder.at(value);
93 std::string str = "";
94 str.push_back(c);
95 return str;
96 }
97
98 // Check for command.
99 if (auto text_element = FindMatchingCommand(value);
100 text_element != std::nullopt) {
101 return text_element->GenericToken;
102 }
103
104 // Check for special characters.
105 if (auto special_element = FindMatchingSpecial(value);
106 special_element != std::nullopt) {
107 return special_element->GenericToken;
108 }
109
110 // Check for dictionary.
111 int8_t dictionary = FindDictionaryEntry(value);
112 if (dictionary >= 0) {
113 return absl::StrFormat("[%s:%02X]", DICTIONARYTOKEN,
114 static_cast<unsigned char>(dictionary));
115 }
116
117 return "";
118}
119
120std::vector<uint8_t> ParseMessageToData(std::string str) {
121 std::vector<uint8_t> bytes;
122 std::string temp_string = std::move(str);
123 int pos = 0;
124 while (pos < temp_string.size()) {
125 // Get next text fragment.
126 if (temp_string[pos] == '[') {
127 int next = temp_string.find(']', pos);
128 if (next == -1) {
129 break;
130 }
131
132 ParsedElement parsedElement =
133 FindMatchingElement(temp_string.substr(pos, next - pos + 1));
134
135 const auto dictionary_element =
136 TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");
137
138 if (!parsedElement.Active) {
139 util::logf("Error parsing message: %s", temp_string);
140 break;
141 } else if (parsedElement.Parent == dictionary_element) {
142 bytes.push_back(parsedElement.Value);
143 } else {
144 bytes.push_back(parsedElement.Parent.ID);
145
146 if (parsedElement.Parent.HasArgument) {
147 bytes.push_back(parsedElement.Value);
148 }
149 }
150
151 pos = next + 1;
152 continue;
153 } else {
154 uint8_t bb = FindMatchingCharacter(temp_string[pos++]);
155
156 if (bb != 0xFF) {
157 bytes.push_back(bb);
158 }
159 }
160 }
161
162 return bytes;
163}
164
165std::vector<DictionaryEntry> BuildDictionaryEntries(Rom* rom) {
166 std::vector<DictionaryEntry> AllDictionaries;
167 for (int i = 0; i < kNumDictionaryEntries; i++) {
168 std::vector<uint8_t> bytes;
169 std::stringstream stringBuilder;
170
171 int address = SnesToPc(
172 kTextData + (rom->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
173 rom->data()[kPointersDictionaries + (i * 2)]);
174
175 int temppush_backress =
177 (rom->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
178 rom->data()[kPointersDictionaries + ((i + 1) * 2)]);
179
180 while (address < temppush_backress) {
181 uint8_t uint8_tDictionary = rom->data()[address++];
182 bytes.push_back(uint8_tDictionary);
183 stringBuilder << ParseTextDataByte(uint8_tDictionary);
184 }
185
186 AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
187 }
188
189 std::ranges::sort(AllDictionaries,
190 [](const DictionaryEntry& a, const DictionaryEntry& b) {
191 return a.Contents.size() > b.Contents.size();
192 });
193
194 return AllDictionaries;
195}
196
198 std::string str, const std::vector<DictionaryEntry>& dictionary) {
199 std::string temp = std::move(str);
200 for (const auto& entry : dictionary) {
201 if (entry.ContainedInString(temp)) {
202 temp = entry.ReplaceInstancesOfIn(temp);
203 }
204 }
205 return temp;
206}
207
209 uint8_t value, const std::vector<DictionaryEntry>& dictionary) {
210 for (const auto& entry : dictionary) {
211 if (entry.ID + DICTOFF == value) {
212 return entry;
213 }
214 }
215 return DictionaryEntry();
216}
217
218absl::StatusOr<MessageData> ParseSingleMessage(
219 const std::vector<uint8_t>& rom_data, int* current_pos) {
220 MessageData message_data;
221 int pos = *current_pos;
222 uint8_t current_byte;
223 std::vector<uint8_t> temp_bytes_raw;
224 std::vector<uint8_t> temp_bytes_parsed;
225 std::string current_message_raw;
226 std::string current_message_parsed;
227
228 // Read the message data
229 while (true) {
230 current_byte = rom_data[pos++];
231
232 if (current_byte == kMessageTerminator) {
233 message_data.ID = message_data.ID + 1;
234 message_data.Address = pos;
235 message_data.RawString = current_message_raw;
236 message_data.Data = temp_bytes_raw;
237 message_data.DataParsed = temp_bytes_parsed;
238 message_data.ContentsParsed = current_message_parsed;
239
240 temp_bytes_raw.clear();
241 temp_bytes_parsed.clear();
242 current_message_raw.clear();
243 current_message_parsed.clear();
244
245 break;
246 } else if (current_byte == 0xFF) {
247 break;
248 }
249
250 temp_bytes_raw.push_back(current_byte);
251
252 // Check for command.
253 auto text_element = FindMatchingCommand(current_byte);
254 if (text_element != std::nullopt) {
255 current_message_raw.append(text_element->GetParamToken());
256 current_message_parsed.append(text_element->GetParamToken());
257 temp_bytes_parsed.push_back(current_byte);
258 continue;
259 }
260
261 // Check for dictionary.
262 int8_t dictionary = FindDictionaryEntry(current_byte);
263 if (dictionary >= 0) {
264 current_message_raw.append("[");
265 current_message_raw.append(DICTIONARYTOKEN);
266 current_message_raw.append(":");
267 current_message_raw.append(
268 util::HexWord(static_cast<unsigned char>(dictionary)));
269 current_message_raw.append("]");
270
271 auto mutable_rom_data = const_cast<uint8_t*>(rom_data.data());
272 uint32_t address = Get24LocalFromPC(
273 mutable_rom_data, kPointersDictionaries + (dictionary * 2));
274 uint32_t address_end = Get24LocalFromPC(
275 mutable_rom_data, kPointersDictionaries + ((dictionary + 1) * 2));
276
277 for (uint32_t i = address; i < address_end; i++) {
278 temp_bytes_parsed.push_back(rom_data[i]);
279 current_message_parsed.append(ParseTextDataByte(rom_data[i]));
280 }
281
282 continue;
283 }
284
285 // Everything else.
286 if (CharEncoder.contains(current_byte)) {
287 std::string str = "";
288 str.push_back(CharEncoder.at(current_byte));
289 current_message_raw.append(str);
290 current_message_parsed.append(str);
291 temp_bytes_parsed.push_back(current_byte);
292 }
293 }
294
295 *current_pos = pos;
296 return message_data;
297}
298
299std::vector<std::string> ParseMessageData(
300 std::vector<MessageData>& message_data,
301 const std::vector<DictionaryEntry>& dictionary_entries) {
302 std::vector<std::string> parsed_messages;
303
304 for (auto& message : message_data) {
305 std::string parsed_message = "";
306 // Use index-based loop to properly skip argument bytes
307 for (size_t pos = 0; pos < message.Data.size(); ++pos) {
308 uint8_t byte = message.Data[pos];
309
310 // Check for text commands first (they may have arguments to skip)
311 auto text_element = FindMatchingCommand(byte);
312 if (text_element != std::nullopt) {
313 // Add newline for certain commands
314 if (text_element->ID == kScrollVertical || text_element->ID == kLine2 ||
315 text_element->ID == kLine3) {
316 parsed_message.append("\n");
317 }
318 // If command has an argument, get it from next byte and skip it
319 if (text_element->HasArgument && pos + 1 < message.Data.size()) {
320 uint8_t arg_byte = message.Data[pos + 1];
321 parsed_message.append(text_element->GetParamToken(arg_byte));
322 pos++; // Skip the argument byte
323 } else {
324 parsed_message.append(text_element->GetParamToken());
325 }
326 continue; // Move to next byte
327 }
328
329 // Check for special characters
330 auto special_element = FindMatchingSpecial(byte);
331 if (special_element != std::nullopt) {
332 parsed_message.append(special_element->GetParamToken());
333 continue;
334 }
335
336 // Check for dictionary entries
337 if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
338 DictionaryEntry dic_entry;
339 for (const auto& entry : dictionary_entries) {
340 if (entry.ID == byte - DICTOFF) {
341 dic_entry = entry;
342 break;
343 }
344 }
345 parsed_message.append(dic_entry.Contents);
346 continue;
347 }
348
349 // Finally check for regular characters
350 if (CharEncoder.contains(byte)) {
351 parsed_message.push_back(CharEncoder.at(byte));
352 }
353 }
354 parsed_messages.push_back(parsed_message);
355 }
356
357 return parsed_messages;
358}
359
360std::vector<MessageData> ReadAllTextData(uint8_t* rom, int pos) {
361 std::vector<MessageData> list_of_texts;
362 int message_id = 0;
363
364 std::vector<uint8_t> raw_message;
365 std::vector<uint8_t> parsed_message;
366 std::string current_raw_message;
367 std::string current_parsed_message;
368
369 uint8_t current_byte = 0;
370 while (current_byte != 0xFF) {
371 current_byte = rom[pos++];
372 if (current_byte == kMessageTerminator) {
373 list_of_texts.push_back(
374 MessageData(message_id++, pos, current_raw_message, raw_message,
375 current_parsed_message, parsed_message));
376 raw_message.clear();
377 parsed_message.clear();
378 current_raw_message.clear();
379 current_parsed_message.clear();
380 continue;
381 } else if (current_byte == 0xFF) {
382 break;
383 }
384
385 raw_message.push_back(current_byte);
386
387 auto text_element = FindMatchingCommand(current_byte);
388 if (text_element != std::nullopt) {
389 parsed_message.push_back(current_byte);
390 if (text_element->HasArgument) {
391 current_byte = rom[pos++];
392 raw_message.push_back(current_byte);
393 parsed_message.push_back(current_byte);
394 }
395
396 current_raw_message.append(text_element->GetParamToken(current_byte));
397 current_parsed_message.append(text_element->GetParamToken(current_byte));
398
399 if (text_element->Token == kBankToken) {
400 pos = kTextData2;
401 }
402
403 continue;
404 }
405
406 // Check for special characters.
407 auto special_element = FindMatchingSpecial(current_byte);
408 if (special_element != std::nullopt) {
409 current_raw_message.append(special_element->GetParamToken());
410 current_parsed_message.append(special_element->GetParamToken());
411 parsed_message.push_back(current_byte);
412 continue;
413 }
414
415 // Check for dictionary.
416 int8_t dictionary = FindDictionaryEntry(current_byte);
417 if (dictionary >= 0) {
418 current_raw_message.append(absl::StrFormat(
419 "[%s:%s]", DICTIONARYTOKEN,
420 util::HexByte(static_cast<unsigned char>(dictionary))));
421
422 uint32_t address =
423 Get24LocalFromPC(rom, kPointersDictionaries + (dictionary * 2));
424 uint32_t address_end =
425 Get24LocalFromPC(rom, kPointersDictionaries + ((dictionary + 1) * 2));
426
427 for (uint32_t i = address; i < address_end; i++) {
428 parsed_message.push_back(rom[i]);
429 current_parsed_message.append(ParseTextDataByte(rom[i]));
430 }
431
432 continue;
433 }
434
435 // Everything else.
436 if (CharEncoder.contains(current_byte)) {
437 std::string str = "";
438 str.push_back(CharEncoder.at(current_byte));
439 current_raw_message.append(str);
440 current_parsed_message.append(str);
441 parsed_message.push_back(current_byte);
442 }
443 }
444
445 return list_of_texts;
446}
447
448absl::Status LoadExpandedMessages(std::string& expanded_message_path,
449 std::vector<std::string>& parsed_messages,
450 std::vector<MessageData>& expanded_messages,
451 std::vector<DictionaryEntry>& dictionary) {
452 static Rom expanded_message_rom;
453 if (!expanded_message_rom.LoadFromFile(expanded_message_path).ok()) {
454 return absl::InternalError("Failed to load expanded message ROM");
455 }
456 expanded_messages = ReadAllTextData(expanded_message_rom.mutable_data(), 0);
457 auto parsed_expanded_messages =
458 ParseMessageData(expanded_messages, dictionary);
459 // Insert into parsed_messages
460 for (const auto& expanded_message : expanded_messages) {
461 parsed_messages.push_back(parsed_expanded_messages[expanded_message.ID]);
462 }
463 return absl::OkStatus();
464}
465
466nlohmann::json SerializeMessagesToJson(const std::vector<MessageData>& messages) {
467 nlohmann::json j = nlohmann::json::array();
468 for (const auto& msg : messages) {
469 j.push_back({
470 {"id", msg.ID},
471 {"address", msg.Address},
472 {"raw_string", msg.RawString},
473 {"parsed_string", msg.ContentsParsed}
474 });
475 }
476 return j;
477}
478
479absl::Status ExportMessagesToJson(const std::string& path,
480 const std::vector<MessageData>& messages) {
481 try {
482 nlohmann::json j = SerializeMessagesToJson(messages);
483 std::ofstream file(path);
484 if (!file.is_open()) {
485 return absl::InternalError(absl::StrFormat("Failed to open file for writing: %s", path));
486 }
487 file << j.dump(2); // Pretty print with 2-space indent
488 return absl::OkStatus();
489 } catch (const std::exception& e) {
490 return absl::InternalError(absl::StrFormat("JSON export failed: %s", e.what()));
491 }
492}
493
494} // namespace editor
495} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:24
absl::Status LoadFromFile(const std::string &filename, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:74
auto mutable_data()
Definition rom.h:136
auto data() const
Definition rom.h:135
std::vector< MessageData > ReadAllTextData(uint8_t *rom, int pos)
uint8_t FindMatchingCharacter(char value)
const std::string kBankToken
nlohmann::json SerializeMessagesToJson(const std::vector< MessageData > &messages)
DictionaryEntry FindRealDictionaryEntry(uint8_t value, const std::vector< DictionaryEntry > &dictionary)
const std::string DICTIONARYTOKEN
constexpr uint8_t kScrollVertical
std::string ParseTextDataByte(uint8_t value)
absl::Status LoadExpandedMessages(std::string &expanded_message_path, std::vector< std::string > &parsed_messages, std::vector< MessageData > &expanded_messages, std::vector< DictionaryEntry > &dictionary)
constexpr int kTextData
constexpr int kTextData2
std::string ReplaceAllDictionaryWords(std::string str, const std::vector< DictionaryEntry > &dictionary)
constexpr uint8_t kLine2
constexpr int kPointersDictionaries
constexpr int kNumDictionaryEntries
absl::StatusOr< MessageData > ParseSingleMessage(const std::vector< uint8_t > &rom_data, int *current_pos)
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< DictionaryEntry > BuildDictionaryEntries(Rom *rom)
std::vector< uint8_t > ParseMessageToData(std::string str)
absl::Status ExportMessagesToJson(const std::string &path, const std::vector< MessageData > &messages)
constexpr uint8_t DICTOFF
std::optional< TextElement > FindMatchingCommand(uint8_t b)
ParsedElement FindMatchingElement(const std::string &str)
constexpr uint8_t kLine3
int8_t FindDictionaryEntry(uint8_t value)
std::string HexWord(uint16_t word, HexStringParams params)
Definition hex.cc:41
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
void logf(const absl::FormatSpec< Args... > &format, Args &&... args)
Definition log.h:115
uint32_t Get24LocalFromPC(uint8_t *data, int addr, bool pc=true)
Definition snes.h:30
uint32_t SnesToPc(uint32_t addr) noexcept
Definition snes.h:8
std::vector< uint8_t > Data
std::vector< uint8_t > DataParsed