Torch
Loading...
Searching...
No Matches
BKDialogShared.h
1#pragma once
2
3#include "BKEmitText.h"
4#include "DialogFactory.h"
5
6#include "spdlog/spdlog.h"
7#include <binarytools/BinaryReader.h>
8#include <binarytools/BinaryWriter.h>
9#include <factories/BaseFactory.h>
10
11#include <optional>
12#include <ostream>
13#include <string>
14#include <vector>
15
16namespace BK64 {
17
18// Both question types end their entry list with this many answer options.
19constexpr int kQuestionOptionCount = 3;
20
21// One string's bytes as C source: printable characters quoted, control codes as hex.
22inline void WriteEscapedChars(std::ostream& write, const std::string& str) {
23 for (auto& c : str) {
24 if (c < ' ') {
25 write << ", 0x" << FORMAT_HEX((uint32_t)c, 2);
26 } else if (c == '\'') {
27 write << ", \'\\" << c << "\'";
28 } else {
29 write << ", \'" << c << "\'";
30 }
31 }
32}
33
34// A counted run of entries as C source: the count, then `cmd, length, bytes...` per entry.
35inline void WriteDialogStringArray(std::ostream& write, const std::vector<DialogString>& entries) {
36 write << fourSpaceTab << entries.size() << ",\n";
37 for (const auto& [cmd, str] : entries) {
38 write << fourSpaceTab << "0x" << FORMAT_HEX((uint32_t)cmd, 2) << ", " << str.length();
39 WriteEscapedChars(write, str);
40 write << ",\n";
41 }
42}
43
44// The same run in the binary resource: u32 count, then cmd + u32 length + raw bytes.
45inline void WriteDialogStrings(LUS::BinaryWriter& writer, const std::vector<DialogString>& entries) {
46 writer.Write((uint32_t)entries.size());
47 for (const auto& entry : entries) {
48 writer.Write(entry.cmd);
49 writer.Write((uint32_t)entry.str.length());
50 // [port] Write(string) would prefix the length twice
51 writer.Write((char*)entry.str.data(), entry.str.size());
52 }
53}
54
55// The same run as a modding-yaml sequence of [cmd, "text"] pairs.
56inline void EmitDialogStringSeq(YAML::Emitter& out, const std::vector<DialogString>& entries) {
57 out << YAML::BeginSeq;
58 for (const auto& [cmd, str] : entries) {
59 out << YAML::Flow;
60 out << YAML::BeginSeq;
61 out << YAML_HEX((uint32_t)cmd);
62 EmitText(out, str);
63 out << YAML::EndSeq;
64 }
65 out << YAML::EndSeq;
66}
67
68// Does the blob still hold `count` more bytes?
69inline bool HasBytes(LUS::BinaryReader& reader, size_t count) {
70 return reader.GetBaseAddress() + count <= reader.GetLength();
71}
72
73inline std::vector<DialogString> ReadDialogStrings(LUS::BinaryReader& reader, int count, const std::string& symbol,
74 const char* kind) {
75 std::vector<DialogString> entries;
76 for (int i = 0; i < count; i++) {
77 if (!HasBytes(reader, 2)) {
78 SPDLOG_WARN("[BK64] {} {}: entry {} of {} runs past the end of the blob; dropping the rest.", kind, symbol,
79 i + 1, count);
80 break;
81 }
82
83 DialogString entry;
84 entry.cmd = reader.ReadUByte();
85 const auto strLen = reader.ReadUByte();
86
87 if (!HasBytes(reader, strLen)) {
88 SPDLOG_WARN("[BK64] {} {}: entry {} of {} claims {} bytes the blob doesn't hold; dropping the rest.", kind,
89 symbol, i + 1, count, strLen);
90 break;
91 }
92
93 entry.str = reader.ReadString(strLen);
94 entries.push_back(entry);
95 }
96 return entries;
97}
98
99// Read back a [cmd, "text"] sequence from modding yaml. Strings are re-terminated
100// because the game reads them as C strings.
101inline std::vector<DialogString> ReadModdingDialogSeq(const YAML::Node& seq) {
102 std::vector<DialogString> entries;
103 for (YAML::const_iterator it = seq.begin(); it != seq.end(); ++it) {
104 DialogString entry;
105 entry.cmd = (*it)[0].as<uint32_t>();
106 entry.str = DecodeText((*it)[1].as<std::string>());
107 entry.str += '\0';
108 entries.push_back(entry);
109 }
110 return entries;
111}
112
113// Parse a modding-yaml buffer and return the asset's value node (the map under its symbol).
114inline std::optional<YAML::Node> LoadModdingRoot(const std::vector<uint8_t>& buffer) {
115 try {
116 std::string text((char*)buffer.data(), buffer.size());
117 YAML::Node assetNode = YAML::Load(text.c_str());
118 return assetNode.begin()->second;
119 } catch (YAML::ParserException& e) {
120 SPDLOG_ERROR("Failed to parse message data: {}", e.what());
121 SPDLOG_ERROR("{}", (char*)buffer.data());
122 return std::nullopt;
123 }
124}
125
126// Both question types share a container. Header byte 0 is the language count, followed
127// by one LE u16 start offset per language. US has a single language, so its offset table
128// is the lone `05 00` and the question begins right after it; PAL carries three, with
129// English first at byte 9. Either way the reader ends up on the English block, and the
130// returned vector holds the start offsets of the languages after it (empty on US/JP).
131// Returns nullopt (and logs) when neither header matches.
132inline std::optional<std::vector<uint16_t>> SeekQuestionBlock(LUS::BinaryReader& reader, const std::string& symbol,
133 const char* kind, const int8_t (&usHeader)[3],
134 const int8_t (&palHeader)[3]) {
135 const auto header1 = reader.ReadInt8();
136 const auto header2 = reader.ReadInt8();
137 const auto header3 = reader.ReadInt8();
138
139 if (header1 == usHeader[0] && header2 == usHeader[1] && header3 == usHeader[2]) {
140 reader.ReadInt8(); // header4 (0x05)
141 reader.ReadInt8(); // header5 (0x00)
142 return std::vector<uint16_t>{};
143 }
144
145 if (header1 == palHeader[0] && header2 == palHeader[1] && header3 == palHeader[2]) {
146 std::vector<uint16_t> extraOffsets;
147 for (int i = 0; i < header1; i++) {
148 const uint16_t offset = reader.ReadUByte() | (reader.ReadUByte() << 8);
149 if (i > 0) {
150 extraOffsets.push_back(offset); // index 0 is English, where the reader now sits
151 }
152 }
153 return extraOffsets;
154 }
155
156 SPDLOG_ERROR("Invalid Header For BK64 {} {}: {:02X} {:02X} {:02X}", kind, symbol, header1, header2, header3);
157 return std::nullopt;
158}
159
160} // namespace BK64
Definition DialogFactory.h:8