1#include "Library/Yaml/ByamlHeader.h"
2
3#include <byteswap.h>
4#include <prim/seadEndian.h>
5#include <stream/seadStream.h>
6
7#include "Library/Yaml/ByamlData.h"
8
9#define BYAML_LE_TAG 0x5942 // 'YB'
10#define BYAML_BE_TAG 0x4259 // 'BY'
11
12namespace al {
13u16 ByamlHeader::getTag() const {
14 return isInvertOrder() ? bswap_16(mTag) : mTag;
15}
16
17bool ByamlHeader::isInvertOrder() const {
18 return mTag == BYAML_LE_TAG;
19}
20
21u16 ByamlHeader::getVersion() const {
22 if ((_0 & 0xFFFF) == BYAML_LE_TAG) // isInvertOrder()
23 return bswap_16(_0 >> 16);
24 return _0 >> 16;
25}
26
27u32 ByamlHeader::getHashKeyTableOffset() const {
28 return isInvertOrder() ? __bswap_32(x: mHashKeyOffset) : mHashKeyOffset;
29}
30
31u32 ByamlHeader::getStringTableOffset() const {
32 return isInvertOrder() ? __bswap_32(x: mStringTableOffset) : mStringTableOffset;
33}
34
35u32 ByamlHeader::getDataOffset() const {
36 return isInvertOrder() ? __bswap_32(x: mDataOffset) : mDataOffset;
37}
38
39ByamlStringTableIter::ByamlStringTableIter() = default;
40
41ByamlStringTableIter::ByamlStringTableIter(const u8* data, bool isRev)
42 : mData(data), mIsRev(isRev) {}
43
44s32 ByamlStringTableIter::getSize() const {
45 u32 type_and_size = *reinterpret_cast<const u32*>(mData);
46 return mIsRev ? bswap_24(type_and_size >> 8) : type_and_size >> 8;
47}
48
49const u32* ByamlStringTableIter::getAddressTable() const {
50 // mData is an integer pointer, so getting to the table is just increasing the pointer by 1
51 // (which is + 4)
52 return reinterpret_cast<const u32*>(mData + 4);
53}
54
55u32 ByamlStringTableIter::getStringAddress(s32 idx) const {
56 if (mIsRev)
57 return bswap_32(getAddressTable()[idx]);
58
59 return getAddressTable()[idx];
60}
61
62// NON_MATCHING: regalloc
63u32 ByamlStringTableIter::getEndAddress() const {
64 u32 val = getAddressTable()[getSize()];
65 return mIsRev ? bswap_32(val) : val;
66}
67
68const char* ByamlStringTableIter::getString(s32 index) const {
69 return reinterpret_cast<const char*>(&mData[getStringAddress(idx: index)]);
70}
71
72s32 ByamlStringTableIter::getStringSize(s32 index) const {
73 return getStringAddress(idx: index + 1) - getStringAddress(idx: index) - 1;
74}
75
76s32 ByamlStringTableIter::findStringIndex(const char* str) const {
77 s32 lowerBound = 0;
78 s32 upperBound = getSize();
79 while (lowerBound < upperBound) {
80 s32 avg = (lowerBound + upperBound) / 2;
81 s32 result = strcmp(str, getString(index: avg));
82 if (result == 0)
83 return avg;
84
85 if (result > 0)
86 lowerBound = avg + 1;
87 else
88 upperBound = avg;
89 }
90 return -1;
91}
92
93bool ByamlStringTableIter::isValidate() const {
94 return mData != nullptr;
95}
96
97} // namespace al
98
99namespace alByamlLocalUtil {
100
101const char* getDataTypeString(s32 type) {
102 switch (type) {
103 case al::ByamlDataType::TYPE_INVALID:
104 return "None";
105 case al::ByamlDataType::TYPE_STRING:
106 return "String";
107 case al::ByamlDataType::TYPE_ARRAY:
108 return "Array";
109 case al::ByamlDataType::TYPE_HASH:
110 return "Hash";
111 case al::ByamlDataType::TYPE_STRING_TABLE:
112 return "StringTable";
113 case al::ByamlDataType::TYPE_BOOL:
114 return "Bool";
115 case al::ByamlDataType::TYPE_INT:
116 return "Int";
117 case al::ByamlDataType::TYPE_FLOAT:
118 return "Float";
119 case al::ByamlDataType::TYPE_UINT:
120 return "UInt";
121 case al::ByamlDataType::TYPE_LONG:
122 return "Int64";
123 case al::ByamlDataType::TYPE_ULONG:
124 return "UInt64";
125 case al::ByamlDataType::TYPE_DOUBLE:
126 return "Double";
127 case al::ByamlDataType::TYPE_NULL:
128 return "NULL";
129 case al::ByamlDataType::TYPE_BINARY:
130 default:
131 return "Unknown";
132 };
133}
134
135al::ByamlStringTableIter getHashKeyTable(const u8* data) {
136 const al::ByamlHeader* header = reinterpret_cast<const al::ByamlHeader*>(data);
137 s32 off = header->getHashKeyTableOffset();
138 if (off == 0)
139 return {};
140 return {&data[off], header->isInvertOrder()};
141}
142
143al::ByamlStringTableIter getStringTable(const u8* data) {
144 const al::ByamlHeader* header = reinterpret_cast<const al::ByamlHeader*>(data);
145 s32 off = header->getStringTableOffset();
146 if (off == 0)
147 return {};
148 return {&data[off], header->isInvertOrder()};
149}
150
151u64 getData64Bit(const u8* data, u32 off, bool isRev) {
152 u64 val = *reinterpret_cast<const u64*>(&data[off]);
153 return isRev ? bswap_32_64(val) : val;
154}
155
156void writeU24(sead::WriteStream* stream, s32 val) {
157 if (sead::Endian::getHostEndian() == sead::Endian::cBig) {
158 stream->writeU8(val >> 16);
159 stream->writeU8(val >> 8);
160 stream->writeU8(val);
161 } else {
162 stream->writeU8(val);
163 stream->writeU8(val >> 8);
164 stream->writeU8(val >> 16);
165 }
166}
167
168// NON_MATCHING: inlined verifiHeader, splitted loads for unswappedAfterOffset and diff in final
169// logic
170bool verifiByaml(const u8* data) {
171 if (!verifiByamlHeader(data))
172 return false;
173
174 bool isRev = *((const u16*)data) == BYAML_LE_TAG;
175 const u32* biggerData = (const u32*)data;
176
177 u32 afterHashOffset = 0;
178 u32 hashOffset = isRev ? bswap_32(biggerData[1]) : biggerData[1];
179 if (hashOffset) {
180 const u8* hashData = &data[hashOffset];
181 if (!verifiByamlStringTable(data: hashData, isRev))
182 return false;
183 u32 type_and_size = *reinterpret_cast<const u32*>(hashData);
184 s32 hash_size = isRev ? bswap_24(type_and_size >> 8) : type_and_size >> 8;
185 s32 unswappedAfterOffset = *(s32*)&hashData[(hash_size * 4) + 4];
186 u32 swappedAfterOffset = bswap_32(unswappedAfterOffset);
187 afterHashOffset = isRev ? swappedAfterOffset : unswappedAfterOffset;
188 }
189
190 u32 afterStringOffset = 0;
191 u32 stringOffset = isRev ? bswap_32(biggerData[2]) : biggerData[2];
192 if (stringOffset) {
193 const u8* stringData = &data[stringOffset];
194 if (!verifiByamlStringTable(data: stringData, isRev))
195 return false;
196 u32 type_and_size = *reinterpret_cast<const u32*>(stringData);
197 s32 string_size = isRev ? bswap_24(type_and_size >> 8) : type_and_size >> 8;
198 s32 unswappedAfterOffset = *(s32*)&stringData[4 * string_size + 4];
199 u32 swappedAfterOffset = bswap_32(unswappedAfterOffset);
200 afterStringOffset = isRev ? swappedAfterOffset : unswappedAfterOffset;
201 }
202
203 u32 rootOffset = isRev ? bswap_32(biggerData[3]) : biggerData[3];
204
205 return (((!hashOffset && !stringOffset) || rootOffset) &&
206 (!hashOffset || ((!stringOffset || afterHashOffset <= stringOffset) &&
207 (!rootOffset || afterHashOffset <= rootOffset)))) &&
208 (afterStringOffset <= rootOffset || !stringOffset || !rootOffset);
209}
210
211// NON_MATCHING: missing & 0xFFFF
212bool verifiByamlHeader(const u8* data) {
213 const al::ByamlHeader* header = reinterpret_cast<const al::ByamlHeader*>(data);
214 return header->getTag() == BYAML_BE_TAG && (u32)(header->getVersion() - 1) < 3;
215}
216
217bool verifiByamlStringTable(const u8* data, bool isRev) {
218 const s32* address_table = reinterpret_cast<const s32*>(data + 4);
219
220 u32 type_and_size = *reinterpret_cast<const u32*>(data);
221 if ((type_and_size & 0xff) != al::ByamlDataType::TYPE_STRING_TABLE)
222 return false;
223 s32 size = isRev ? bswap_24(type_and_size >> 8) : type_and_size >> 8;
224 if (size < 1)
225 return false;
226
227 // check that strings are null-terminated
228 for (s32 i = 1; i <= size; i++) {
229 s32 off = isRev ? bswap_32(address_table[i]) : address_table[i];
230 if (data[off - 1])
231 return false;
232 }
233
234 for (s32 i = 0; i < size; i++) {
235 s32 val1 = isRev ? bswap_32(address_table[i]) : address_table[i];
236 s32 val2 = isRev ? bswap_32(address_table[i + 1]) : address_table[i + 1];
237 if (val1 >= val2)
238 return false;
239 }
240
241 // check for correct length of address table
242 s32 calcFirstStringPos = 4 * size + 8;
243 s32 dataFirstStringPos = isRev ? bswap_32(address_table[0]) : address_table[0];
244 if (dataFirstStringPos != calcFirstStringPos)
245 return false;
246
247 // TODO: improve this. Matching, but ugly
248 if (isRev) {
249 for (s32 i = 0; i < size - 1; i++) {
250 const char* c1 =
251 (const char*)&data[isRev ? bswap_32(address_table[i]) : address_table[i]];
252 const char* c2 =
253 (const char*)&data[isRev ? bswap_32(address_table[i + 1]) : address_table[i + 1]];
254 if (strcmp(c1, c2) > 0)
255 return false;
256 }
257 } else {
258 for (s32 i = 0; i < size - 1; i++) {
259 const char* c1 =
260 (const char*)&data[isRev ? bswap_32(address_table[i]) : address_table[i]];
261 const char* c2 =
262 (const char*)&data[false ? bswap_32(address_table[i + 1]) : address_table[i + 1]];
263 if (strcmp(c1, c2) > 0)
264 return false;
265 }
266 }
267 return true;
268}
269
270} // namespace alByamlLocalUtil
271