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