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