Cuberite
A lightweight, fast and extensible game server for Minecraft
FastNBT.cpp
Go to the documentation of this file.
1 
2 // FastNBT.cpp
3 
4 // Implements the fast NBT parser and writer
5 
6 #include "Globals.h"
7 #include "FastNBT.h"
8 
9 
10 
11 
12 
13 // The number of NBT tags that are reserved when an NBT parsing is started.
14 // You can override this by using a cmdline define
15 #ifndef NBT_RESERVE_SIZE
16  #define NBT_RESERVE_SIZE 200
17 #endif // NBT_RESERVE_SIZE
18 
19 #ifdef _MSC_VER
20  // Dodge a C4127 (conditional expression is constant) for this specific macro usage
21  #define PROPAGATE_ERROR(X) do { auto Err = (X); if (Err != eNBTParseError::npSuccess) return Err; } while ((false, false))
22 #else
23  #define PROPAGATE_ERROR(X) do { auto Err = (X); if (Err != eNBTParseError::npSuccess) return Err; } while (false)
24 #endif
25 
26 
27 
29 // cNBTParseErrorCategory:
30 
31 namespace
32 {
33 
34 class cNBTParseErrorCategory final :
35  public std::error_category
36 {
37  cNBTParseErrorCategory() = default;
38 public:
40  virtual const char * name() const noexcept override
41  {
42  return "NBT parse error";
43  }
44 
46  virtual AString message(int a_Condition) const override;
47 
49  static const cNBTParseErrorCategory & Get() noexcept
50  {
51  static cNBTParseErrorCategory Category;
52  return Category;
53  }
54 };
55 
56 
57 
58 
59 
60 AString cNBTParseErrorCategory::message(int a_Condition) const
61 {
62  switch (static_cast<eNBTParseError>(a_Condition))
63  {
65  {
66  return "Parsing succeded";
67  }
69  {
70  return "Expected more data";
71  }
73  {
74  return "No top level compound tag";
75  }
77  {
78  return "Expected a string length but had insufficient data";
79  }
81  {
82  return "String length invalid";
83  }
85  {
86  return "Compound tag was unmatched at end of file";
87  }
89  {
90  return "Expected a list type but had insuffiecient data";
91  }
93  {
94  return "Expected a list length but had insufficient data";
95  }
97  {
98  return "List length invalid";
99  }
101  {
102  return "Expected a numeric type but had insufficient data";
103  }
105  {
106  return "Expected an array length but had insufficient data";
107  }
109  {
110  return "Array length invalid";
111  }
113  {
114  return "Unknown tag";
115  }
116  }
117  UNREACHABLE("Unsupported nbt parse error");
118 }
119 
120 } // namespace (anonymous)
121 
122 
123 
124 
125 
126 std::error_code make_error_code(eNBTParseError a_Err) noexcept
127 {
128  return { static_cast<int>(a_Err), cNBTParseErrorCategory::Get() };
129 }
130 
131 
132 
133 
134 
136 // cParsedNBT:
137 
138 #define NEEDBYTES(N, ERR) \
139  do { \
140  if (m_Data.size() - m_Pos < static_cast<size_t>(N)) \
141  { \
142  return ERR; \
143  } \
144  } while (false)
145 
146 
147 
148 
149 
151  m_Data(a_Data),
152  m_Pos(0)
153 {
154  m_Error = Parse();
155 }
156 
157 
158 
159 
160 
162 {
163  if (m_Data.size() < 3)
164  {
165  // Data too short
167  }
168  if (m_Data[0] != std::byte(TAG_Compound))
169  {
170  // The top-level tag must be a Compound
172  }
173 
174  m_Tags.reserve(NBT_RESERVE_SIZE);
175 
176  m_Tags.emplace_back(TAG_Compound, -1);
177 
178  m_Pos = 1;
179 
180  PROPAGATE_ERROR(ReadString(m_Tags.back().m_NameStart, m_Tags.back().m_NameLength));
181  return ReadCompound();
182 }
183 
184 
185 
186 
187 
188 eNBTParseError cParsedNBT::ReadString(size_t & a_StringStart, size_t & a_StringLen)
189 {
191  a_StringStart = m_Pos + 2;
192  a_StringLen = static_cast<size_t>(GetBEShort(m_Data.data() + m_Pos));
194  m_Pos += 2 + a_StringLen;
196 }
197 
198 
199 
200 
201 
203 {
204  ASSERT(m_Tags.size() > 0);
205 
206  // Reads the latest tag as a compound
207  size_t ParentIdx = m_Tags.size() - 1;
208  int PrevSibling = -1;
209  for (;;)
210  {
212  const auto TagTypeNum = m_Data[m_Pos];
213  if ((TagTypeNum < std::byte(TAG_Min)) || (TagTypeNum > std::byte(TAG_Max)))
214  {
216  }
217  eTagType TagType = static_cast<eTagType>(TagTypeNum);
218  m_Pos++;
219  if (TagType == TAG_End)
220  {
221  break;
222  }
223  m_Tags.emplace_back(TagType, static_cast<int>(ParentIdx), PrevSibling);
224  if (PrevSibling >= 0)
225  {
226  m_Tags[static_cast<size_t>(PrevSibling)].m_NextSibling = static_cast<int>(m_Tags.size()) - 1;
227  }
228  else
229  {
230  m_Tags[ParentIdx].m_FirstChild = static_cast<int>(m_Tags.size()) - 1;
231  }
232  PrevSibling = static_cast<int>(m_Tags.size()) - 1;
233  PROPAGATE_ERROR(ReadString(m_Tags.back().m_NameStart, m_Tags.back().m_NameLength));
235  } // while (true)
236  m_Tags[ParentIdx].m_LastChild = PrevSibling;
238 }
239 
240 
241 
242 
243 
245 {
246  // Reads the latest tag as a list of items of type a_ChildrenType
247 
248  // Read the count:
250  int Count = GetBEInt(m_Data.data() + m_Pos);
251  m_Pos += 4;
252  auto MinChildSize = GetMinTagSize(a_ChildrenType);
253  if ((Count < 0) || (Count > static_cast<int>((m_Data.size() - m_Pos) / MinChildSize)))
254  {
256  }
257 
258  // Read items:
259  ASSERT(m_Tags.size() > 0);
260  size_t ParentIdx = m_Tags.size() - 1;
261  int PrevSibling = -1;
262  for (int i = 0; i < Count; i++)
263  {
264  m_Tags.emplace_back(a_ChildrenType, static_cast<int>(ParentIdx), PrevSibling);
265  if (PrevSibling >= 0)
266  {
267  m_Tags[static_cast<size_t>(PrevSibling)].m_NextSibling = static_cast<int>(m_Tags.size()) - 1;
268  }
269  else
270  {
271  m_Tags[ParentIdx].m_FirstChild = static_cast<int>(m_Tags.size()) - 1;
272  }
273  PrevSibling = static_cast<int>(m_Tags.size()) - 1;
275  } // for (i)
276  m_Tags[ParentIdx].m_LastChild = PrevSibling;
278 }
279 
280 
281 
282 
283 
284 #define CASE_SIMPLE_TAG(TAGTYPE, LEN) \
285  case TAG_##TAGTYPE: \
286  { \
287  NEEDBYTES(LEN, eNBTParseError::npSimpleMissing); \
288  Tag.m_DataStart = m_Pos; \
289  Tag.m_DataLength = LEN; \
290  m_Pos += LEN; \
291  return eNBTParseError::npSuccess; \
292  }
293 
295 {
296  cFastNBTTag & Tag = m_Tags.back();
297  switch (Tag.m_Type)
298  {
301  CASE_SIMPLE_TAG(Int, 4)
302  CASE_SIMPLE_TAG(Long, 8)
303  CASE_SIMPLE_TAG(Float, 4)
304  CASE_SIMPLE_TAG(Double, 8)
305 
306  case TAG_String:
307  {
308  return ReadString(Tag.m_DataStart, Tag.m_DataLength);
309  }
310 
311  case TAG_ByteArray:
312  {
314  int len = GetBEInt(m_Data.data() + m_Pos);
315  m_Pos += 4;
316  if (len < 0)
317  {
318  // Invalid length
320  }
322  Tag.m_DataLength = static_cast<size_t>(len);
323  Tag.m_DataStart = m_Pos;
324  m_Pos += static_cast<size_t>(len);
326  }
327 
328  case TAG_List:
329  {
331  eTagType ItemType = static_cast<eTagType>(m_Data[m_Pos]);
332  m_Pos++;
333  PROPAGATE_ERROR(ReadList(ItemType));
335  }
336 
337  case TAG_Compound:
338  {
341  }
342 
343  case TAG_IntArray:
344  {
346  int len = GetBEInt(m_Data.data() + m_Pos);
347  m_Pos += 4;
348  if (len < 0)
349  {
350  // Invalid length
352  }
353  len *= 4;
355  Tag.m_DataLength = static_cast<size_t>(len);
356  Tag.m_DataStart = m_Pos;
357  m_Pos += static_cast<size_t>(len);
359  }
360 
361  case TAG_Min:
362  {
364  }
365  } // switch (iType)
366  UNREACHABLE("Unsupported nbt tag type");
367 }
368 
369 #undef CASE_SIMPLE_TAG
370 
371 
372 
373 
374 
375 int cParsedNBT::FindChildByName(int a_Tag, const char * a_Name, size_t a_NameLength) const
376 {
377  if (a_Tag < 0)
378  {
379  return -1;
380  }
381  if (m_Tags[static_cast<size_t>(a_Tag)].m_Type != TAG_Compound)
382  {
383  return -1;
384  }
385 
386  if (a_NameLength == 0)
387  {
388  a_NameLength = strlen(a_Name);
389  }
390  for (int Child = m_Tags[static_cast<size_t>(a_Tag)].m_FirstChild; Child != -1; Child = m_Tags[static_cast<size_t>(Child)].m_NextSibling)
391  {
392  if (
393  (m_Tags[static_cast<size_t>(Child)].m_NameLength == a_NameLength) &&
394  (memcmp(m_Data.data() + m_Tags[static_cast<size_t>(Child)].m_NameStart, a_Name, a_NameLength) == 0)
395  )
396  {
397  return Child;
398  }
399  } // for Child - children of a_Tag
400  return -1;
401 }
402 
403 
404 
405 
406 
407 int cParsedNBT::FindTagByPath(int a_Tag, const AString & a_Path) const
408 {
409  if (a_Tag < 0)
410  {
411  return -1;
412  }
413  size_t Begin = 0;
414  size_t Length = a_Path.length();
415  int Tag = a_Tag;
416  for (size_t i = 0; i < Length; i++)
417  {
418  if (a_Path[i] != '\\')
419  {
420  continue;
421  }
422  Tag = FindChildByName(Tag, a_Path.c_str() + Begin, i - Begin);
423  if (Tag < 0)
424  {
425  return -1;
426  }
427  Begin = i + 1;
428  } // for i - a_Path[]
429 
430  if (Begin < Length)
431  {
432  Tag = FindChildByName(Tag, a_Path.c_str() + Begin, Length - Begin);
433  }
434  return Tag;
435 }
436 
437 
438 
439 
440 
442 {
443  switch (a_TagType)
444  {
445  case TAG_End: return 1;
446  case TAG_Byte: return 1;
447  case TAG_Short: return 2;
448  case TAG_Int: return 4;
449  case TAG_Long: return 8;
450  case TAG_Float: return 4;
451  case TAG_Double: return 8;
452  case TAG_String: return 2; // 2 bytes for the string length
453  case TAG_ByteArray: return 4; // 4 bytes for the count
454  case TAG_List: return 5; // 1 byte list type + 4 bytes count
455  case TAG_Compound: return 1; // Single TAG_End byte
456  case TAG_IntArray: return 4; // 4 bytes for the count
457  }
458  UNREACHABLE("Unsupported nbt tag type");
459 }
460 
461 
462 
463 
464 
466 // cFastNBTWriter:
467 
468 cFastNBTWriter::cFastNBTWriter(const AString & a_RootTagName) :
469  m_CurrentStack(0)
470 {
472  m_Result.reserve(100 KiB);
473  m_Result.push_back(std::byte(TAG_Compound));
474  WriteString(a_RootTagName);
475 }
476 
477 
478 
479 
480 
482 {
483  if (m_CurrentStack >= MAX_STACK - 1)
484  {
485  ASSERT(!"Stack overflow");
486  return;
487  }
488 
489  TagCommon(a_Name, TAG_Compound);
490 
491  ++m_CurrentStack;
493 }
494 
495 
496 
497 
498 
500 {
501  ASSERT(m_CurrentStack > 0);
503 
504  m_Result.push_back(std::byte(TAG_End));
505  --m_CurrentStack;
506 }
507 
508 
509 
510 
511 
512 void cFastNBTWriter::BeginList(const AString & a_Name, eTagType a_ChildrenType)
513 {
514  if (m_CurrentStack >= MAX_STACK - 1)
515  {
516  ASSERT(!"Stack overflow");
517  return;
518  }
519 
520  TagCommon(a_Name, TAG_List);
521 
522  m_Result.push_back(std::byte(a_ChildrenType));
523  m_Result.append(4, std::byte(0));
524 
525  ++m_CurrentStack;
527  m_Stack[m_CurrentStack].m_Pos = static_cast<int>(m_Result.size()) - 4;
529  m_Stack[m_CurrentStack].m_ItemType = a_ChildrenType;
530 }
531 
532 
533 
534 
535 
537 {
538  ASSERT(m_CurrentStack > 0);
540 
541  // Update the list count:
543 
544  --m_CurrentStack;
545 }
546 
547 
548 
549 
550 
551 void cFastNBTWriter::AddByte(const AString & a_Name, unsigned char a_Value)
552 {
553  TagCommon(a_Name, TAG_Byte);
554  m_Result.push_back(std::byte(a_Value));
555 }
556 
557 
558 
559 
560 
561 void cFastNBTWriter::AddShort(const AString & a_Name, Int16 a_Value)
562 {
563  TagCommon(a_Name, TAG_Short);
564  UInt16 Value = htons(static_cast<UInt16>(a_Value));
565  m_Result.append(reinterpret_cast<const std::byte *>(&Value), 2);
566 }
567 
568 
569 
570 
571 
572 void cFastNBTWriter::AddInt(const AString & a_Name, Int32 a_Value)
573 {
574  TagCommon(a_Name, TAG_Int);
575  UInt32 Value = htonl(static_cast<UInt32>(a_Value));
576  m_Result.append(reinterpret_cast<const std::byte *>(&Value), 4);
577 }
578 
579 
580 
581 
582 
583 void cFastNBTWriter::AddLong(const AString & a_Name, Int64 a_Value)
584 {
585  TagCommon(a_Name, TAG_Long);
586  UInt64 Value = HostToNetwork8(&a_Value);
587  m_Result.append(reinterpret_cast<const std::byte *>(&Value), 8);
588 }
589 
590 
591 
592 
593 
594 void cFastNBTWriter::AddFloat(const AString & a_Name, float a_Value)
595 {
596  TagCommon(a_Name, TAG_Float);
597  UInt32 Value = HostToNetwork4(&a_Value);
598  m_Result.append(reinterpret_cast<const std::byte *>(&Value), 4);
599 }
600 
601 
602 
603 
604 
605 void cFastNBTWriter::AddDouble(const AString & a_Name, double a_Value)
606 {
607  TagCommon(a_Name, TAG_Double);
608  UInt64 Value = HostToNetwork8(&a_Value);
609  m_Result.append(reinterpret_cast<const std::byte *>(&Value), 8);
610 }
611 
612 
613 
614 
615 
616 void cFastNBTWriter::AddString(const AString & a_Name, const std::string_view a_Value)
617 {
618  TagCommon(a_Name, TAG_String);
619  const UInt16 Length = htons(static_cast<UInt16>(a_Value.size()));
620  m_Result.append(reinterpret_cast<const std::byte *>(&Length), sizeof(Length));
621  m_Result.append({ reinterpret_cast<const std::byte *>(a_Value.data()), a_Value.size() });
622 }
623 
624 
625 
626 
627 
628 void cFastNBTWriter::AddByteArray(const AString & a_Name, const char * a_Value, size_t a_NumElements)
629 {
630  TagCommon(a_Name, TAG_ByteArray);
631  UInt32 len = htonl(static_cast<UInt32>(a_NumElements));
632  m_Result.append(reinterpret_cast<const std::byte *>(&len), 4);
633  m_Result.append(reinterpret_cast<const std::byte *>(a_Value), a_NumElements);
634 }
635 
636 
637 
638 
639 
640 void cFastNBTWriter::AddByteArray(const AString & a_Name, size_t a_NumElements, unsigned char a_Value)
641 {
642  TagCommon(a_Name, TAG_ByteArray);
643  UInt32 len = htonl(static_cast<UInt32>(a_NumElements));
644  m_Result.append(reinterpret_cast<const std::byte *>(&len), 4);
645  m_Result.append(a_NumElements, std::byte(a_Value));
646 }
647 
648 
649 
650 
651 
652 void cFastNBTWriter::AddIntArray(const AString & a_Name, const Int32 * a_Value, size_t a_NumElements)
653 {
654  TagCommon(a_Name, TAG_IntArray);
655  UInt32 len = htonl(static_cast<UInt32>(a_NumElements));
656  size_t cap = m_Result.capacity();
657  size_t size = m_Result.length();
658  if ((cap - size) < (4 + a_NumElements * 4))
659  {
660  m_Result.reserve(size + 4 + (a_NumElements * 4));
661  }
662  m_Result.append(reinterpret_cast<const std::byte *>(&len), sizeof(len));
663  for (size_t i = 0; i < a_NumElements; i++)
664  {
665  UInt32 Element = htonl(static_cast<UInt32>(a_Value[i]));
666  m_Result.append(reinterpret_cast<const std::byte *>(&Element), sizeof(Element));
667  }
668 }
669 
670 
671 
672 
673 
675 {
676  ASSERT(m_CurrentStack == 0);
677  m_Result.push_back(std::byte(TAG_End));
678 }
679 
680 
681 
682 
683 
684 void cFastNBTWriter::WriteString(const std::string_view a_Data)
685 {
686  // TODO check size <= short max
687  UInt16 Len = htons(static_cast<unsigned short>(a_Data.size()));
688  m_Result.append(reinterpret_cast<const std::byte *>(&Len), sizeof(Len));
689  m_Result.append(reinterpret_cast<const std::byte *>(a_Data.data()), a_Data.size());
690 }
UInt32 HostToNetwork4(const void *a_Value)
Definition: Endianness.h:24
UInt64 HostToNetwork8(const void *a_Value)
Definition: Endianness.h:12
#define UNREACHABLE(x)
Definition: Globals.h:288
unsigned int UInt32
Definition: Globals.h:157
signed long long Int64
Definition: Globals.h:151
signed short Int16
Definition: Globals.h:153
std::basic_string_view< std::byte > ContiguousByteBufferView
Definition: Globals.h:376
signed int Int32
Definition: Globals.h:152
unsigned long long UInt64
Definition: Globals.h:156
#define KiB
Allows arithmetic expressions like "32 KiB" (but consider using parenthesis around it,...
Definition: Globals.h:234
#define ASSERT(x)
Definition: Globals.h:276
unsigned short UInt16
Definition: Globals.h:158
unsigned char Byte
Definition: Globals.h:161
eMonsterType m_Type
Definition: Monster.cpp:35
short GetBEShort(const std::byte *const a_Mem)
Reads two bytes from the specified memory location and interprets them as BigEndian short.
int GetBEInt(const std::byte *const a_Mem)
Reads four bytes from the specified memory location and interprets them as BigEndian int.
void SetBEInt(std::byte *a_Mem, Int32 a_Value)
Writes four bytes to the specified memory location so that they interpret as BigEndian int.
std::string AString
Definition: StringUtils.h:11
#define PROPAGATE_ERROR(X)
Definition: FastNBT.cpp:23
#define CASE_SIMPLE_TAG(TAGTYPE, LEN)
Definition: FastNBT.cpp:284
#define NEEDBYTES(N, ERR)
Definition: FastNBT.cpp:138
std::error_code make_error_code(eNBTParseError a_Err) noexcept
Definition: FastNBT.cpp:126
#define NBT_RESERVE_SIZE
Definition: FastNBT.cpp:16
eTagType
Definition: FastNBT.h:30
@ TAG_Long
Definition: FastNBT.h:36
@ TAG_IntArray
Definition: FastNBT.h:43
@ TAG_ByteArray
Definition: FastNBT.h:39
@ TAG_Max
Definition: FastNBT.h:44
@ TAG_End
Definition: FastNBT.h:32
@ TAG_List
Definition: FastNBT.h:41
@ TAG_Float
Definition: FastNBT.h:37
@ TAG_String
Definition: FastNBT.h:40
@ TAG_Short
Definition: FastNBT.h:34
@ TAG_Byte
Definition: FastNBT.h:33
@ TAG_Compound
Definition: FastNBT.h:42
@ TAG_Int
Definition: FastNBT.h:35
@ TAG_Double
Definition: FastNBT.h:38
@ TAG_Min
Definition: FastNBT.h:31
eNBTParseError
Definition: FastNBT.h:112
bool Short(const BlockState Block)
This structure is used for all NBT tags.
Definition: FastNBT.h:58
eTagType m_Type
Definition: FastNBT.h:61
size_t m_DataStart
Definition: FastNBT.h:67
size_t m_DataLength
Definition: FastNBT.h:68
size_t m_Pos
Definition: FastNBT.h:307
eNBTParseError Parse(void)
Definition: FastNBT.cpp:161
std::vector< cFastNBTTag > m_Tags
Definition: FastNBT.h:303
eNBTParseError ReadList(eTagType a_ChildrenType)
Definition: FastNBT.cpp:244
eNBTParseError ReadString(size_t &a_StringStart, size_t &a_StringLen)
Definition: FastNBT.cpp:188
cParsedNBT(ContiguousByteBufferView a_Data)
Definition: FastNBT.cpp:150
int FindChildByName(int a_Tag, const AString &a_Name) const
Returns the direct child tag of the specified name, or -1 if no such tag.
Definition: FastNBT.h:199
int FindTagByPath(int a_Tag, const AString &a_Path) const
Returns the child tag of the specified path (Name1 / Name2 / Name3...), or -1 if no such tag.
Definition: FastNBT.cpp:407
eNBTParseError ReadTag(void)
Definition: FastNBT.cpp:294
eNBTParseError m_Error
Definition: FastNBT.h:304
ContiguousByteBufferView m_Data
Definition: FastNBT.h:302
static size_t GetMinTagSize(eTagType a_TagType)
Returns the minimum size, in bytes, of the specified tag type.
Definition: FastNBT.cpp:441
eNBTParseError ReadCompound(void)
Definition: FastNBT.cpp:202
void AddByteArray(const AString &a_Name, const char *a_Value, size_t a_NumElements)
Definition: FastNBT.cpp:628
void AddByte(const AString &a_Name, unsigned char a_Value)
Definition: FastNBT.cpp:551
void AddDouble(const AString &a_Name, double a_Value)
Definition: FastNBT.cpp:605
void AddShort(const AString &a_Name, Int16 a_Value)
Definition: FastNBT.cpp:561
void AddInt(const AString &a_Name, Int32 a_Value)
Definition: FastNBT.cpp:572
void Finish(void)
Definition: FastNBT.cpp:674
void AddString(const AString &a_Name, std::string_view a_Value)
Definition: FastNBT.cpp:616
void EndList(void)
Definition: FastNBT.cpp:536
ContiguousByteBuffer m_Result
Definition: FastNBT.h:371
void BeginList(const AString &a_Name, eTagType a_ChildrenType)
Definition: FastNBT.cpp:512
void AddFloat(const AString &a_Name, float a_Value)
Definition: FastNBT.cpp:594
void AddIntArray(const AString &a_Name, const Int32 *a_Value, size_t a_NumElements)
Definition: FastNBT.cpp:652
cFastNBTWriter(const AString &a_RootTagName="")
Definition: FastNBT.cpp:468
void EndCompound(void)
Definition: FastNBT.cpp:499
bool IsStackTopCompound(void) const
Definition: FastNBT.h:373
void BeginCompound(const AString &a_Name)
Definition: FastNBT.cpp:481
static const int MAX_STACK
Definition: FastNBT.h:365
void WriteString(std::string_view a_Data)
Definition: FastNBT.cpp:684
int m_CurrentStack
Definition: FastNBT.h:369
void AddLong(const AString &a_Name, Int64 a_Value)
Definition: FastNBT.cpp:583
void TagCommon(const AString &a_Name, eTagType a_Type)
Definition: FastNBT.h:377
sParent m_Stack[MAX_STACK]
Definition: FastNBT.h:368