FOSS-VG/src/lib/nbt.cpp

1863 lines
78 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Copyright 2022, FOSS-VG Developers and Contributers
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, version 3.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// version 3 along with this program.
// If not, see https://www.gnu.org/licenses/agpl-3.0.en.html
#include <bit>
#include <cstdint>
#include <vector>
#include <tinyutf8/tinyutf8.h>
#include "nbt.hpp"
#include "error.hpp"
#include "javacompat.hpp"
#include "../../.endianness"
#ifdef FOSSVG_ENDIAN_BIG_WORD
#error "Honeywell-316-style endianness is not supported. If you feel like it should, feel free to participate in the project to maintain it."
#endif
#ifdef FOSSVG_ENDIAN_LITTLE_WORD
#error "PDP-11-style endianness is not supported. If you feel like it should, feel free to participate in the project to maintain it."
#endif
#ifdef FOSSVG_ENDIAN_UNKNOWN
#error "The endianness of your system could not be determined. Please set it manually. FOSS-VG is currently implemented using some endian-specific functions."
#endif
namespace NBT {
namespace Helper {
ErrorOr<int8_t> readInt8(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int8_t>(true, ErrorCodes::OUT_OF_RANGE);
return ErrorOr<int8_t>((int8_t) data[currentPosition]);
}
ErrorOr<int16_t> readInt16(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int16_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+2) return ErrorOr<int16_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int16_t>((int16_t) ((static_cast<int16_t>(data[currentPosition]) << 8) | static_cast<int16_t>(data[currentPosition+1])));
}
ErrorOr<int32_t> readInt32(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+4) return ErrorOr<int32_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int32_t>((int32_t) (
(static_cast<int32_t>(data[currentPosition ]) << 24) |
(static_cast<int32_t>(data[currentPosition+1]) << 16) |
(static_cast<int32_t>(data[currentPosition+2]) << 8) |
static_cast<int32_t>(data[currentPosition+3])
));
}
ErrorOr<int64_t> readInt64(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
if (currentPosition>=dataSize) return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+8) return ErrorOr<int64_t>(true, ErrorCodes::OVERRUN);
return ErrorOr<int64_t>((int64_t) (
(static_cast<int64_t>(data[currentPosition ]) << 56) |
(static_cast<int64_t>(data[currentPosition+1]) << 48) |
(static_cast<int64_t>(data[currentPosition+2]) << 40) |
(static_cast<int64_t>(data[currentPosition+3]) << 32) |
(static_cast<int64_t>(data[currentPosition+4]) << 24) |
(static_cast<int64_t>(data[currentPosition+5]) << 16) |
(static_cast<int64_t>(data[currentPosition+6]) << 8) |
static_cast<int64_t>(data[currentPosition+7])
));
}
//FIXME: endian-dependent implementations
ErrorOr<float> readFloat(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
float* value = new float;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
if (dataSize<=currentPosition) return ErrorOr<float>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+4) return ErrorOr<float>(true, ErrorCodes::OVERRUN);
#ifdef FOSSVG_BIG_ENDIAN
*valueAsBytes = data[currentPosition];
*(valueAsBytes+1) = data[currentPosition+1];
*(valueAsBytes+2) = data[currentPosition+2];
*(valueAsBytes+3) = data[currentPosition+3];
#else
#ifdef FOSSVG_LITTLE_ENDIAN
*valueAsBytes = data[currentPosition+3];
*(valueAsBytes+1) = data[currentPosition+2];
*(valueAsBytes+2) = data[currentPosition+1];
*(valueAsBytes+3) = data[currentPosition];
#else
#error "NBT::Helper::readFloat: An implementation for your endianness is unavailable."
#endif
#endif
float dereferencedValue = *value;
delete value;
return ErrorOr<float>(dereferencedValue);
}
//FIXME: endian-dependent implementations
ErrorOr<double> readDouble(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
double* value = new double;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
if (dataSize<=currentPosition) return ErrorOr<double>(true, ErrorCodes::OUT_OF_RANGE);
if (dataSize<currentPosition+8) return ErrorOr<double>(true, ErrorCodes::OVERRUN);
#ifdef FOSSVG_BIG_ENDIAN
*valueAsBytes = data[currentPosition];
*(valueAsBytes+1) = data[currentPosition+1];
*(valueAsBytes+2) = data[currentPosition+2];
*(valueAsBytes+3) = data[currentPosition+3];
*(valueAsBytes+4) = data[currentPosition+4];
*(valueAsBytes+5) = data[currentPosition+5];
*(valueAsBytes+6) = data[currentPosition+6];
*(valueAsBytes+7) = data[currentPosition+7];
#else
#ifdef FOSSVG_LITTLE_ENDIAN
*valueAsBytes = data[currentPosition+7];
*(valueAsBytes+1) = data[currentPosition+6];
*(valueAsBytes+2) = data[currentPosition+5];
*(valueAsBytes+3) = data[currentPosition+4];
*(valueAsBytes+4) = data[currentPosition+3];
*(valueAsBytes+5) = data[currentPosition+2];
*(valueAsBytes+6) = data[currentPosition+1];
*(valueAsBytes+7) = data[currentPosition];
#else
#error "NBT::Helper::readDouble: An implementation for your endianness is unavailable."
#endif
#endif
double dereferencedValue = *value;
delete value;
return ErrorOr<double>(dereferencedValue);
}
ErrorOr<std::vector<int8_t>> readInt8Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int8_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+size.value > dataSize) return ErrorOr<std::vector<int8_t>>(true, ErrorCodes::OVERRUN);
std::vector<int8_t> result = std::vector<int8_t>();
for (int i=0; i<size.value; i++) {
result.push_back(data[currentPosition+4+i]);
}
return ErrorOr<std::vector<int8_t>>(result);
}
ErrorOr<tiny_utf8::string> readString(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int16_t> stringSize = readInt16(data, dataSize, currentPosition);
if (stringSize.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<tiny_utf8::string>(true, stringSize.errorCode);
}
if (currentPosition + (uint64_t) stringSize.value + 2 > dataSize) {
return ErrorOr<tiny_utf8::string>(true, ErrorCodes::OVERRUN);
}
ErrorOr<tiny_utf8::string> output = JavaCompat::importJavaString(data+currentPosition, stringSize.value);
if(output.isError){
return ErrorOr<tiny_utf8::string>(true, output.errorCode);
}
return output;
}
ErrorOr<std::vector<int32_t>> readInt32Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int32_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+(size.value*4) > dataSize) return ErrorOr<std::vector<int32_t>>(true, ErrorCodes::OVERRUN);
std::vector<int32_t> result = std::vector<int32_t>();
for (int i=0; i<size.value; i++) {
ErrorOr<int32_t> nextInt32 = readInt32(data, dataSize, currentPosition+4+(i*4));
if (nextInt32.isError) return ErrorOr<std::vector<int32_t>>(true, nextInt32.errorCode);
result.push_back(nextInt32.value);
}
return ErrorOr<std::vector<int32_t>>(result);
}
ErrorOr<std::vector<int64_t>> readInt64Array(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
if (size.isError) {
// It is okay to pass up the error code here without further
// processing because both OVERRUN and OUT_OF_RANGE errors will
// still apply.
return ErrorOr<std::vector<int64_t>>(true, size.errorCode);
}
// get content
if (currentPosition+4+(size.value*8) > dataSize) return ErrorOr<std::vector<int64_t>>(true, ErrorCodes::OVERRUN);
std::vector<int64_t> result = std::vector<int64_t>();
for (int i=0; i<size.value; i++) {
ErrorOr<int64_t> nextInt64 = readInt64(data, dataSize, currentPosition+4+(i*8));
if (nextInt64.isError) return ErrorOr<std::vector<int64_t>>(true, nextInt64.errorCode);
result.push_back(nextInt64.value);
}
return ErrorOr<std::vector<int64_t>>(result);
}
void writeInt8(std::vector<uint8_t>* destination, int8_t data) {
destination->push_back((uint8_t) data);
}
//FIXME: endian dependent implementation
void writeInt16(std::vector<uint8_t>* destination, int16_t data) {
int16_t* value = new int16_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
}
//FIXME: endian dependent implementation
void writeInt32(std::vector<uint8_t>* destination, int32_t data) {
int32_t* value = new int32_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
}
//FIXME: endian dependent implementation
void writeInt64(std::vector<uint8_t>* destination, int64_t data) {
int64_t* value = new int64_t;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+7));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+7));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
}
//FIXME: endian-specific implementations
void writeFloat(std::vector<uint8_t>* destination, float data) {
float* value = new float;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
}
//FIXME: endian-specific implementations
void writeDouble(std::vector<uint8_t>* destination, double data) {
double* value = new double;
uint8_t* valueAsBytes = reinterpret_cast<uint8_t*>(value);
*value = data;
#ifdef FOSSVG_BIG_ENDIAN
destination->push_back(*valueAsBytes);
destination->push_back(*(valueAsBytes+1));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+7));
#else
#ifdef FOSSVG_LITTLE_ENDIAN
destination->push_back(*(valueAsBytes+7));
destination->push_back(*(valueAsBytes+6));
destination->push_back(*(valueAsBytes+5));
destination->push_back(*(valueAsBytes+4));
destination->push_back(*(valueAsBytes+3));
destination->push_back(*(valueAsBytes+2));
destination->push_back(*(valueAsBytes+1));
destination->push_back(*valueAsBytes);
#else
#error "NBT::Helper::writeInt16: An implementation for your endianness is unavailable."
#endif
#endif
delete value;
}
void writeInt8Array(std::vector<uint8_t>* destination, std::vector<int8_t> data) {
writeInt32(destination, data.size());
for(int8_t datum: data){
destination->push_back(datum);
}
}
void writeInt8Array(std::vector<uint8_t>* destination, int8_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i=0; i < dataSize; i++){
destination->push_back(data[i]);
}
}
ErrorOrVoid writeString(std::vector<uint8_t>* destination, tiny_utf8::string data) {
ErrorOr<std::vector<uint8_t>> exportedString = JavaCompat::exportJavaString(data);
if(exportedString.isError){
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
destination->insert(destination->end(), exportedString.value.begin(), exportedString.value.end());
return ErrorOrVoid();
}
void writeInt32Array(std::vector<uint8_t>* destination, std::vector<int32_t> data) {
writeInt32(destination, data.size());
for(int32_t element: data){
writeInt32(destination, element);
}
}
void writeInt32Array(std::vector<uint8_t>* destination, int32_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i = 0; i<dataSize; i++){
writeInt32(destination, data[i]);
}
}
void writeInt64Array(std::vector<uint8_t>* destination, std::vector<int64_t> data) {
writeInt32(destination, data.size());
for(int64_t element: data){
writeInt64(destination, element);
}
}
void writeInt64Array(std::vector<uint8_t>* destination, int64_t data[], uint32_t dataSize) {
writeInt32(destination, dataSize);
for(uint32_t i = 0; i<dataSize; i++){
writeInt64(destination, data[i]);
}
}
//FIXME: instead of blindly passing the error code upwards, choose
// one that is applicable to the situation (for example replace
// OUT_OF_RANGE with OVERRUN where appropriate)
//
// The total size in bytes
//
// Does not work for compound tags and lists. This is an intended
// feature as compound tags and lists need to be dealt with
// separately to avoid unnecessarily long and complex code.
//
// Regarding lists specifically: The size of some lists can can
// be determined easily by looking at the contained data type and
// size information but cases like string lists or compound lists
// are significantly more difficult to deal with. Parsing their
// contents requires special attention anyway due to the tag headers
// of contained tags being absent so they may as well get treated
// separately for this as well.
ErrorOr<uint64_t> totalTagSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) {
uint8_t nextTag;
if (dataSize <= currentPosition) {
return ErrorOr<uint64_t>(true, ErrorCodes::OVERRUN);
} else {
nextTag = data[currentPosition];
}
// deal with compound tags and lists separately
if (nextTag == TagType::COMPOUND || nextTag == TagType::LIST) return ErrorOr<uint64_t>(true, ErrorCodes::NOT_YET_KNOWN);
// deal with end tag before trying to access the name
if (nextTag == TagType::END) return ErrorOr<uint64_t>(1);
ErrorOr<int16_t> nameSize = Helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<uint64_t>(true, nameSize.errorCode);
}
// add type byte and name size bytes
uint64_t prefixSize = (uint64_t) nameSize.value + 3;
switch (nextTag) {
case TagType::INT8:
return ErrorOr<uint64_t>(prefixSize+1);
case TagType::INT16:
return ErrorOr<uint64_t>(prefixSize+2);
case TagType::INT32:
return ErrorOr<uint64_t>(prefixSize+4);
case TagType::INT64:
return ErrorOr<uint64_t>(prefixSize+8);
case TagType::FLOAT:
return ErrorOr<uint64_t>(prefixSize+4);
case TagType::DOUBLE:
return ErrorOr<uint64_t>(prefixSize+8);
case TagType::INT8_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value + prefixSize + 4);
}
case TagType::STRING: {
ErrorOr<int16_t> stringSize = Helper::readInt16(data, dataSize, currentPosition+prefixSize);
if (stringSize.isError) {
return ErrorOr<uint64_t>(true, stringSize.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) stringSize.value + prefixSize + 2);
}
case TagType::INT32_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value*4 + prefixSize + 4);
}
case TagType::INT64_ARRAY: {
ErrorOr<int32_t> arrayLength = Helper::readInt32(data, dataSize, currentPosition+prefixSize);
if (arrayLength.isError) {
return ErrorOr<uint64_t>(true, arrayLength.errorCode);
}
return ErrorOr<uint64_t>((uint64_t) arrayLength.value*8 + prefixSize + 4);
}
// unknown tag or parsing error
default:
return ErrorOr<uint64_t>(true, ErrorCodes::UNKNOWN);
}
}
//FIXME: instead of blindly passing the error code upwards, choose
// one that is applicable to the situation (for example replace
// OUT_OF_RANGE with OVERRUN where appropriate)
//
// Length is the number of stored elements, not to be confused with size
// which is the size in bytes.
ErrorOr<int32_t> containedDataLength(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){
uint8_t nextTag;
if (dataSize <= currentPosition) {
return ErrorOr<int32_t>(true, ErrorCodes::OVERRUN);
} else {
nextTag = data[currentPosition];
}
// deal with compound tags separately
if (nextTag == TagType::COMPOUND) {
return ErrorOr<int32_t>(true, ErrorCodes::NOT_YET_KNOWN);
}
// deal with end tag before trying to access the name
if (nextTag == TagType::END) {
return ErrorOr<int32_t>(0);
}
// tags that only ever hold one value
if (nextTag == TagType::INT8 || nextTag == TagType::INT16 || nextTag == TagType::INT32 || nextTag == TagType::INT64 || nextTag == TagType::FLOAT || nextTag == TagType::DOUBLE) {
return ErrorOr<int32_t>(1);
}
ErrorOr<int16_t> nameSize = Helper::readInt16(data, dataSize, currentPosition+1);
if (nameSize.isError) {
return ErrorOr<int32_t>(true, nameSize.errorCode);
}
// add type byte and name size bytes
uint64_t prefixSize = (uint64_t) nameSize.value + 3;
switch (nextTag) {
case TagType::INT8_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
case TagType::STRING: {
ErrorOr<int16_t> stringSize = Helper::readInt16(data, dataSize, currentPosition+prefixSize);
if (stringSize.isError) {
return ErrorOr<int32_t>(true, stringSize.errorCode);
}
return ErrorOr<int32_t>((int32_t) stringSize.value);
}
case TagType::LIST: {
// add an additional byte for the contained data type
return Helper::readInt32(data, dataSize, currentPosition+prefixSize+1);
}
case TagType::INT32_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
case TagType::INT64_ARRAY: {
return Helper::readInt32(data, dataSize, currentPosition+prefixSize);
}
default:
// unknown tag or parsing error
return ErrorOr<int32_t>(true, ErrorCodes::UNKNOWN);
}
}
}
namespace Tag {
Generic::Generic() {
this->type = TagType::INVALID;
}
Generic::~Generic() {}
ErrorOrVoid Generic::serializeWithoutHeader([[maybe_unused]] std::vector<uint8_t>* rawData) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
ErrorOrVoid Generic::serialize(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
if (Helper::writeString(rawData, this->name).isError) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
return this->serializeWithoutHeader(rawData);
}
uint8_t Generic::getTagType(){
return this->type;
}
End::End() {
this->type = TagType::END;
}
ErrorOrVoid End::serializeWithoutHeader([[maybe_unused]] std::vector<uint8_t>* rawData) {
return ErrorOrVoid();
}
ErrorOrVoid End::serialize(std::vector<uint8_t>* rawData) {
rawData->push_back(this->type);
return ErrorOrVoid();
}
Int8::Int8() {
this->type = TagType::INT8;
}
Int8::Int8(tiny_utf8::string name, int8_t value) {
this->type = TagType::INT8;
this->name = name;
this->value = value;
}
ErrorOrVoid Int8::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt8(rawData, this->value);
return ErrorOrVoid();
}
int8_t Int8::getValue() {
this->mutex.lock();
int8_t value = this->value;
this->mutex.unlock();
return value;
}
void Int8::setValue(int8_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int16::Int16() {
this->type = TagType::INT16;
}
Int16::Int16(tiny_utf8::string name, int16_t value) {
this->type = TagType::INT16;
this->name = name;
this->value = value;
}
ErrorOrVoid Int16::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt16(rawData, this->value);
return ErrorOrVoid();
}
int16_t Int16::getValue() {
this->mutex.lock();
int16_t value = this->value;
this->mutex.unlock();
return value;
}
void Int16::setValue(int16_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int32::Int32() {
this->type = TagType::INT32;
}
Int32::Int32(tiny_utf8::string name, int32_t value) {
this->type = TagType::INT32;
this->name = name;
this->value = value;
}
ErrorOrVoid Int32::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt32(rawData, this->value);
return ErrorOrVoid();
}
int32_t Int32::getValue() {
this->mutex.lock();
int32_t value = this->value;
this->mutex.unlock();
return value;
}
void Int32::setValue(int32_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int64::Int64() {
this->type = TagType::INT64;
}
Int64::Int64(tiny_utf8::string name, int64_t value) {
this->type = TagType::INT64;
this->name = name;
this->value = value;
}
ErrorOrVoid Int64::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt64(rawData, this->value);
return ErrorOrVoid();
}
int64_t Int64::getValue() {
this->mutex.lock();
int64_t value = this->value;
this->mutex.unlock();
return value;
}
void Int64::setValue(int64_t value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Float::Float() {
this->type = TagType::FLOAT;
}
Float::Float(tiny_utf8::string name, float value) {
this->type = TagType::FLOAT;
this->name = name;
this->value = value;
}
ErrorOrVoid Float::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeFloat(rawData, this->value);
return ErrorOrVoid();
}
float Float::getValue() {
this->mutex.lock();
float value = this->value;
this->mutex.unlock();
return value;
}
void Float::setValue(float value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Double::Double() {
this->type = TagType::DOUBLE;
}
Double::Double(tiny_utf8::string name, double value) {
this->type = TagType::DOUBLE;
this->name = name;
this->value = value;
}
ErrorOrVoid Double::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeDouble(rawData, this->value);
return ErrorOrVoid();
}
double Double::getValue() {
this->mutex.lock();
double value = this->value;
this->mutex.unlock();
return value;
}
void Double::setValue(double value) {
this->mutex.lock();
this->value = value;
this->mutex.unlock();
}
Int8Array::Int8Array() {
this->type = TagType::INT8_ARRAY;
}
Int8Array::Int8Array(tiny_utf8::string name, std::vector<int8_t> data) {
this->type = TagType::INT8_ARRAY;
this->name = name;
this->data = data;
}
Int8Array::Int8Array(tiny_utf8::string name, uint64_t length, int8_t data[]){
this->type = TagType::INT8_ARRAY;
this->name = name;
this->data = std::vector<int8_t>(data, data+length);
}
ErrorOrVoid Int8Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt8Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int8_t> Int8Array::getData() {
return this->data;
}
ErrorOr<int8_t> Int8Array::getValue(uint64_t position) {
if (this->data.size() <= position) {
return ErrorOr<int8_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int8_t>(this->data.at(position));
}
void Int8Array::setData(std::vector<int8_t> newData) {
this->data = newData;
}
ErrorOrVoid Int8Array::setValue(uint64_t position, int8_t value) {
if (this->data.size() <= position) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int8Array::length() {
return this->data.size();
}
void Int8Array::addElement(int8_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int8Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
String::String() {
this->type = TagType::STRING;
}
String::String(tiny_utf8::string name, tiny_utf8::string value) {
this->type = TagType::STRING;
this->name = name;
this->value = value;
}
ErrorOrVoid String::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
return Helper::writeString(rawData, this->value);
}
tiny_utf8::string String::getValue() {
return this->value;
}
void String::setValue(tiny_utf8::string value) {
this->value = value;
}
List::List() {
this->type = TagType::LIST;
this->containedType = TagType::INVALID;
}
List::List(tiny_utf8::string name, uint8_t type) {
this->type = TagType::LIST;
this->name = name;
this->containedType = type;
}
// WARNING: The pointers inside the vector are automatically cleaned
// up upon deletion of the List object. Do not retain a copy of them
// elsewhere and especially do not delete them externally.
List::List(tiny_utf8::string name, std::vector<Generic*> data) {
this->type = TagType::LIST;
this->name = name;
this->tags = data;
if (data.size() == 0) {
this->containedType = TagType::END;
} else {
this->containedType = data.at(0)->getTagType();
}
}
ErrorOr<List*> List::constructWithData(tiny_utf8::string name, std::vector<Generic*> data) {
if (data.size() > 0xFFFFFFFF) {
return ErrorOr<List*>(true, ErrorCodes::OVERRUN, nullptr);
}
if (data.size() > 0) {
uint8_t dataType = data[0]->getTagType();
for (uint32_t i=1; i<data.size(); i++) {
if (data[i]->getTagType() != dataType) {
return ErrorOr<List*>(true, ErrorCodes::MIXED_TYPES, nullptr);
}
}
}
return ErrorOr<List*>(new List(name, data));
}
List::~List() {
for (uint64_t i=0; i<this->tags.size(); i++) {
delete this->tags.at(i);
}
}
uint8_t List::getContainedType() {
return this->containedType;
}
ErrorOrVoid List::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
if (this->containedType == TagType::INVALID) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
rawData->push_back(this->containedType);
// 32 bit signed integer max
if (this->tags.size() > 0x7FFFFFFF) {
return ErrorOrVoid(true, ErrorCodes::OVERRUN);
}
Helper::writeInt32(rawData, this->tags.size());
// unsigned integer bc of compiler warning (shouldn't matter)
for (uint32_t i=0; i<this->tags.size(); i++) {
ErrorOrVoid result = this->tags.at(i)->serializeWithoutHeader(rawData);
if (result.isError) {
return result;
}
}
return ErrorOrVoid();
}
ErrorOr<Generic*> List::getElementPointer(uint64_t position) {
if (this->tags.size() <= position) {
return ErrorOr<Generic*>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<Generic*>(this->tags.at(position));
}
ErrorOrVoid List::setElementPointerAt(uint64_t position, Generic* pointer) {
if (this->tags.size() <= position) {
delete pointer;
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
if (pointer->getTagType() != this->containedType) {
delete pointer;
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
delete this->tags[position];
this->tags[position] = pointer;
return ErrorOrVoid();
}
ErrorOrVoid List::appendPointer(Generic* pointer) {
if (pointer->getTagType() != this->containedType) {
return ErrorOrVoid(true, ErrorCodes::INVALID_TYPE);
}
this->tags.push_back(pointer);
return ErrorOrVoid();
}
ErrorOrVoid List::deleteElement(uint64_t position) {
if (position >= this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags.erase(this->tags.begin()+position);
return ErrorOrVoid();
}
uint64_t List::length() {
return this->tags.size();
}
Compound::Compound() {
this->type = TagType::COMPOUND;
this->endPointer = new End();
}
Compound::Compound(tiny_utf8::string name) {
this->type = TagType::COMPOUND;
this->name = name;
this->endPointer = new End();
}
Compound::Compound(tiny_utf8::string name, std::vector<Generic*> data) {
this->type = TagType::COMPOUND;
this->name = name;
this->tags = data;
this->endPointer = new End();
}
ErrorOr<Compound*> Compound::constructWithData(tiny_utf8::string name, std::vector<Generic*> data) {
if (data.size() > 0) {
for (uint64_t i=0; i<data.size(); i++) {
if (data[i]->getTagType() == TagType::END && i != data.size()-1) {
return ErrorOr<Compound*>(true, ErrorCodes::NOT_ALLOWED, nullptr);
}
}
if (data[data.size()-1]->getTagType() == TagType::END) {
return ErrorOr<Compound*>(new Compound(name, std::vector(data.begin(), data.end()-1)));
}
}
return ErrorOr<Compound*>(new Compound(name, data));
}
Compound::~Compound() {
for (uint64_t i=0; i<this->tags.size(); i++) {
delete this->tags.at(i);
}
delete this->endPointer;
}
ErrorOrVoid Compound::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
for (uint64_t i=0; i<this->tags.size(); i++) {
ErrorOrVoid result = this->tags.at(i)->serialize(rawData);
if (result.isError) {
return result;
}
}
this->endPointer->serialize(rawData);
return ErrorOrVoid();
}
ErrorOr<Generic*> Compound::getElementPointer(uint64_t position) {
if (position > this->tags.size()) {
return ErrorOr<Generic*>(true, ErrorCodes::OUT_OF_RANGE);
}
if (position == this->tags.size()) {
return this->endPointer;
}
return ErrorOr<Generic*>(this->tags.at(position));
}
ErrorOrVoid Compound::setElementPointerAt(uint64_t position, Generic* pointer) {
if (position == this->tags.size() || pointer->getTagType() == TagType::END) {
if (position == this->tags.size() && pointer->getTagType() == TagType::END) {
delete pointer;
// do nothing, already have one of those
return ErrorOrVoid();
} else {
delete pointer;
// End tags may only go at the end and
// the end may only hold an end tag.
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
}
if (position > this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags[position] = pointer;
return ErrorOrVoid();
}
ErrorOrVoid Compound::appendPointer(Generic* pointer) {
if (pointer->getTagType() == TagType::END) {
delete pointer;
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
this->tags.push_back(pointer);
return ErrorOrVoid();
}
ErrorOrVoid Compound::deleteElement(uint64_t position) {
// built-in end tag
if (position == this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::NOT_ALLOWED);
}
if (position > this->tags.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
delete this->tags[position];
this->tags.erase(this->tags.begin()+position);
return ErrorOrVoid();
}
uint64_t Compound::length() {
// account for built-in end tag
return this->tags.size()+1;
}
Int32Array::Int32Array() {
this->type = TagType::INT32_ARRAY;
}
Int32Array::Int32Array(tiny_utf8::string name, std::vector<int32_t> data) {
this->type = TagType::INT32_ARRAY;
this->name = name;
this->data = data;
}
Int32Array::Int32Array(tiny_utf8::string name, uint64_t length, int32_t data[]) {
this->type = TagType::INT32_ARRAY;
this->name = name;
this->data = std::vector(data, data+length);
}
ErrorOrVoid Int32Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt32Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int32_t> Int32Array::getData() {
return this->data;
}
ErrorOr<int32_t> Int32Array::getValue(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int32_t>(this->data.at(position));
}
void Int32Array::setData(std::vector<int32_t> newData) {
this->data = newData;
}
ErrorOrVoid Int32Array::setValue(uint64_t position, int32_t value) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int32Array::length() {
return this->data.size();
}
void Int32Array::addElement(int32_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int32Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
Int64Array::Int64Array() {
this->type = TagType::INT64_ARRAY;
}
Int64Array::Int64Array(tiny_utf8::string name, std::vector<int64_t> data) {
this->type = TagType::INT64_ARRAY;
this->name = name;
this->data = data;
}
Int64Array::Int64Array(tiny_utf8::string name, uint64_t length, int64_t data[]) {
this->type = TagType::INT64_ARRAY;
this->name = name;
this->data = std::vector(data, data+length);
}
ErrorOrVoid Int64Array::serializeWithoutHeader(std::vector<uint8_t>* rawData) {
Helper::writeInt64Array(rawData, this->data);
return ErrorOrVoid();
}
std::vector<int64_t> Int64Array::getData() {
return this->data;
}
ErrorOr<int64_t> Int64Array::getValue(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
}
return ErrorOr<int64_t>(this->data[position]);
}
void Int64Array::setData(std::vector<int64_t> newData) {
this->data = newData;
}
ErrorOrVoid Int64Array::setValue(uint64_t position, int64_t value) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data[position] = value;
return ErrorOrVoid();
}
uint64_t Int64Array::length() {
return this->data.size();
}
void Int64Array::addElement(int64_t element) {
this->data.push_back(element);
}
ErrorOrVoid Int64Array::removeElement(uint64_t position) {
if (position >= this->data.size()) {
return ErrorOrVoid(true, ErrorCodes::OUT_OF_RANGE);
}
this->data.erase(this->data.begin()+position);
return ErrorOrVoid();
}
}
// the same comment about blindly passing up error codes applies to this function
// FIXME: memory leak when returning errors
ErrorOr<std::vector<Tag::Generic*>> deserializeRawListContents(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize) {
std::vector<Tag::Generic*> contents;
ErrorOr<std::vector<Tag::Generic*>> returnValue;
// get contained data length by reading it manually because
// the function that does it normally can't deal with
// headerless tags
//
// add one byte to position to skip the type byte
ErrorOr<int32_t> elementCount = Helper::readInt32(data, dataSize, initialPosition+1);
if (elementCount.isError) {
// this is before the creation of any pointers so we can just return
// without using the returnError label at the end of this function
return ErrorOr<std::vector<Tag::Generic*>>(true, elementCount.errorCode);
}
uint8_t contentType = data[initialPosition];
// contained type byte + 4 length bytes = 5
*processedDataSize = 5;
switch (contentType) {
case TagType::END: {
// everything except content has been touched at this point
// and a list of end tags has no content that could be read
for (int32_t i=0; i<elementCount.value; i++) {
contents.push_back(new Tag::End());
}
break;
}
case TagType::INT8: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int8_t> nextInt = Helper::readInt8(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int8("", nextInt.value));
*processedDataSize += 1;
}
break;
}
case TagType::INT16: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int16_t> nextInt = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int16("", nextInt.value));
*processedDataSize += 2;
}
break;
}
case TagType::INT32: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int32_t> nextInt = Helper::readInt32(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int32("", nextInt.value));
*processedDataSize += 4;
}
break;
}
case TagType::FLOAT: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<float> nextFloat = Helper::readFloat(data, dataSize, initialPosition+*processedDataSize);
if (nextFloat.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextFloat.errorCode);
goto returnError;
}
contents.push_back(new Tag::Float("", nextFloat.value));
*processedDataSize += 4;
}
break;
}
case TagType::INT64: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<int64_t> nextInt = Helper::readInt64(data, dataSize, initialPosition+*processedDataSize);
if (nextInt.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextInt.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int64("", nextInt.value));
*processedDataSize += 8;
}
break;
}
case TagType::DOUBLE: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<double> nextDouble = Helper::readDouble(data, dataSize, initialPosition+*processedDataSize);
if (nextDouble.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextDouble.errorCode);
goto returnError;
}
contents.push_back(new Tag::Double("", nextDouble.value));
*processedDataSize += 8;
}
break;
}
case TagType::INT8_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int8_t>> nextArray = Helper::readInt8Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int8Array("", nextArray.value));
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() + 4;
}
break;
}
case TagType::STRING: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<tiny_utf8::string> nextString = Helper::readString(data, dataSize, initialPosition+*processedDataSize);
if (nextString.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextString.errorCode);
goto returnError;
}
contents.push_back(new Tag::String("", nextString.value));
// this cannot be an error because it just got read
int16_t nextStringSize = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize).value;
*processedDataSize += (uint64_t) nextStringSize + 2;
}
break;
}
case TagType::LIST: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
ErrorOr<std::vector<Tag::Generic*>> nextListContents = deserializeRawListContents(data, dataSize, initialPosition+*processedDataSize, containedDataSize);
if (nextListContents.isError) {
delete containedDataSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextListContents.errorCode);
goto returnError;
}
contents.push_back(Tag::List::constructWithData("", nextListContents.value).value);
*processedDataSize += *containedDataSize;
}
delete containedDataSize;
break;
}
case TagType::COMPOUND: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
ErrorOr<std::vector<Tag::Generic*>> nextCompoundData = deserialize(data, dataSize, initialPosition+*processedDataSize, containedDataSize);
if (nextCompoundData.isError) {
delete containedDataSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextCompoundData.errorCode);
goto returnError;
}
contents.push_back(reinterpret_cast<Tag::Generic*>(Tag::Compound::constructWithData("", nextCompoundData.value).value));
*processedDataSize += *containedDataSize;
}
delete containedDataSize;
break;
}
case TagType::INT32_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int32_t>> nextArray = Helper::readInt32Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int32Array("", nextArray.value));
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() * 4 + 4;
}
break;
}
case TagType::INT64_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int64_t>> nextArray = Helper::readInt64Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextArray.errorCode);
goto returnError;
}
contents.push_back(new Tag::Int64Array("", nextArray.value));
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() * 8 + 4;
}
break;
}
default:
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::INVALID_TYPE);
goto returnError;
}
return ErrorOr<std::vector<Tag::Generic*>>(contents);
returnError:
for (uint64_t i=0; i<contents.size(); i++) {
delete contents.at(i);
}
return returnValue;
}
// comment about blindly passing up error codes applies here
//
// The return value of this function is a vector of tags
// instead of a compound tag due to a spec extension that allows
// for any bare tag to be valid NBT data without a containing
// compound tag. This also just makes the implementation easier.
ErrorOr<std::vector<Tag::Generic*>> deserialize(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize){
if (initialPosition >= dataSize) {
if (processedDataSize!=nullptr) *processedDataSize=0;
return ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::OUT_OF_RANGE);
// An interesting question at this point is whether we should
// consider empty input valid or invalid NBT data.
//
// The original spec says that the top-most tag is always a
// compound (or in more recent times, the Microsoft-commercialized
// in-game-purchase-enabling version also allows list tags)
// which automatically means that no data is invalid data...
// I don't see a reason why having a different tag as the top-most
// tag shouldn't be valid NBT in which case we have to face the
// question whether no data is invalid or just empty NBT data.
//
// This seems like a reasonable extension to the spec to me and
// it should be backwards compatible AFAIK.
//
// - BodgeMaster
}
std::vector<Tag::Generic*> tags = std::vector<Tag::Generic*>();
ErrorOr<std::vector<Tag::Generic*>> returnValue;
uint64_t currentPosition = initialPosition;
while (currentPosition<dataSize) {
ErrorOr<uint64_t> nextTagSize = Helper::totalTagSize(data, dataSize, currentPosition);
if (nextTagSize.isError) {
if (nextTagSize.errorCode == ErrorCodes::NOT_YET_KNOWN) {
ErrorOr<tiny_utf8::string> tagName = Helper::readString(data, dataSize, currentPosition+1);
if (tagName.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, tagName.errorCode);
goto returnNow;
}
// used seek to the start of the list's/compounds contents
//
// there is no way this is an error bc it gets
// checked while trying to parse the string above
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
uint64_t* processedTagSize = new uint64_t;
*processedTagSize = 0;
if (data[currentPosition]==TagType::LIST) {
// type byte + two name size bytes = 3
ErrorOr<std::vector<Tag::Generic*>> listData = deserializeRawListContents(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize);
if (listData.isError) {
delete processedTagSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, listData.errorCode);
goto returnNow;
}
tags.push_back(Tag::List::constructWithData(tagName.value, listData.value).value);
*processedTagSize += (uint64_t) nameSize + 3;
}
if (data[currentPosition]==TagType::COMPOUND) {
// type byte + two name size bytes = 3
ErrorOr<std::vector<Tag::Generic*>> compoundData = deserialize(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize);
if (compoundData.isError) {
delete processedTagSize;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, compoundData.errorCode);
goto returnNow;
}
tags.push_back(reinterpret_cast<Tag::Generic*>(Tag::Compound::constructWithData(tagName.value, compoundData.value).value));
*processedTagSize += (uint64_t) nameSize + 3;
}
currentPosition += *processedTagSize;
delete processedTagSize;
continue;
}
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, nextTagSize.errorCode);
goto returnNow;
}
if (currentPosition + nextTagSize.value > dataSize) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::OVERRUN);
goto returnNow;
}
// recursion abort condition
if (data[currentPosition]==TagType::END) {
// not appending an end tag as it is built into
// the compound anyway
currentPosition++;
returnValue = ErrorOr<std::vector<Tag::Generic*>>(tags);
goto returnNow;
}
// nameSize cannot be an error here bc it got checked in
// nextTagSize() already
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
ErrorOr<tiny_utf8::string> name = Helper::readString(data, dataSize, currentPosition+1);
if (name.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, name.errorCode);
goto returnNow;
}
// Overrun / out of range errors have already been ruled out by
// checking the tag size against the total amount of data.
switch (data[currentPosition]) {
case TagType::INT8: {
int8_t content = Helper::readInt8(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int8(name.value, content));
break;
}
case TagType::INT16: {
int16_t content = Helper::readInt16(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int16(name.value, content));
break;
}
case TagType::INT32: {
int32_t content = Helper::readInt32(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int32(name.value, content));
break;
}
case TagType::INT64: {
int64_t content = Helper::readInt64(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int64(name.value, content));
break;
}
case TagType::FLOAT: {
float content = Helper::readFloat(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Float(name.value, content));
break;
}
case TagType::DOUBLE: {
double content = Helper::readDouble(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Double(name.value, content));
break;
}
case TagType::INT8_ARRAY: {
std::vector<int8_t> content = Helper::readInt8Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int8Array(name.value, content));
break;
}
case TagType::STRING: {
ErrorOr<tiny_utf8::string> content = Helper::readString(data, dataSize, currentPosition+nameSize+3);
if (content.isError) {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, content.errorCode);
goto returnNow;
}
tags.push_back(new Tag::String(name.value, content.value));
break;
}
case TagType::INT32_ARRAY: {
std::vector<int32_t> content = Helper::readInt32Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int32Array(name.value, content));
break;
}
case TagType::INT64_ARRAY: {
std::vector<int64_t> content = Helper::readInt64Array(data, dataSize, currentPosition+nameSize+3).value;
tags.push_back(new Tag::Int64Array(name.value, content));
break;
}
default: {
returnValue = ErrorOr<std::vector<Tag::Generic*>>(true, ErrorCodes::UNKNOWN);
goto returnNow;
}
}
currentPosition += nextTagSize.value;
}
returnValue = ErrorOr<std::vector<Tag::Generic*>>(tags);
goto returnNow;
returnNow:
if (processedDataSize!=nullptr) {
*processedDataSize = currentPosition-initialPosition;
}
if (returnValue.isError) {
for (uint64_t i=0; i<tags.size(); i++) {
delete tags[i];
}
}
return returnValue;
}
bool validateRawListContents(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize) {
// get contained data length by reading it manually because
// the function that does it normally can't deal with
// headerless tags
//
// add one byte to position to skip the type byte
ErrorOr<int32_t> elementCount = Helper::readInt32(data, dataSize, initialPosition+1);
if (elementCount.isError) {
return false;
}
uint8_t contentType = data[initialPosition];
// contained type byte + 4 length bytes = 5
*processedDataSize = 5;
switch (contentType) {
case TagType::END:
// everything except content has been touched at this point
// and a list of end tags has no content
return true;
case TagType::INT8: {
*processedDataSize += (uint64_t) elementCount.value;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT16: {
*processedDataSize += (uint64_t) elementCount.value * 2;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT32:
case TagType::FLOAT: {
*processedDataSize += (uint64_t) elementCount.value * 4;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT64:
case TagType::DOUBLE: {
*processedDataSize += (uint64_t) elementCount.value * 8;
return initialPosition + *processedDataSize < dataSize;
}
case TagType::INT8_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int8_t>> nextArray = Helper::readInt8Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() + 4;
}
return true;
}
case TagType::STRING: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<tiny_utf8::string> nextString = Helper::readString(data, dataSize, initialPosition+*processedDataSize);
if (nextString.isError) {
return false;
}
// this cannot be an error because it just got checked
int16_t nextStringSize = Helper::readInt16(data, dataSize, initialPosition+*processedDataSize).value;
*processedDataSize += (uint64_t) nextStringSize + 2;
}
return true;
}
case TagType::LIST: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
if (validateRawListContents(data, dataSize, initialPosition+*processedDataSize, containedDataSize)) {
*processedDataSize += *containedDataSize;
} else {
delete containedDataSize;
return false;
}
}
delete containedDataSize;
return true;
}
case TagType::COMPOUND: {
uint64_t* containedDataSize = new uint64_t;
for (int32_t i=0; i<elementCount.value; i++) {
*containedDataSize = 0;
if (validateRawNBTData(data, dataSize, initialPosition+*processedDataSize, containedDataSize)) {
*processedDataSize += *containedDataSize;
} else {
delete containedDataSize;
return false;
}
}
delete containedDataSize;
return true;
}
case TagType::INT32_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int32_t>> nextArray = Helper::readInt32Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() * 4 + 4;
}
return true;
}
case TagType::INT64_ARRAY: {
for (int32_t i=0; i<elementCount.value; i++) {
ErrorOr<std::vector<int64_t>> nextArray = Helper::readInt64Array(data, dataSize, initialPosition+*processedDataSize);
if (nextArray.isError) {
return false;
}
// +4 for the header of the array
*processedDataSize += (uint64_t) nextArray.value.size() * 8 + 4;
}
return true;
}
default:
return false;
}
}
bool validateRawNBTData(uint8_t data[], uint64_t dataSize, uint64_t initialPosition, uint64_t* processedDataSize){
if (initialPosition >= dataSize) {
// Yes, this *could* return an instance of ErrorOr with
// ErrorCodes::OVERRUN but we only care to know if what is
// at that position is valid NBT which it clearly isn't according
// to the original spec.
if (processedDataSize!=nullptr) *processedDataSize=0;
return false;
// An interesting question at this point is whether we should
// consider empty input valid or invalid NBT data.
//
// The original spec says that the top-most tag is always a
// compound (or in more recent times, the Microsoft-commercialized
// in-game-purchase-enabling version also allows list tags)
// which automatically means that no data is invalid data...
// I don't see a reason why having a different tag as the top-most
// tag shouldn't be valid NBT in which case we have to face the
// question whether no data is invalid or just empty NBT data.
//
// This seems like a reasonable extension to the spec to me and
// it should be backwards compatible AFAIK.
//
// - BodgeMaster
}
bool returnValue;
uint64_t currentPosition = initialPosition;
while (currentPosition<dataSize) {
ErrorOr<uint64_t> nextTagSize = Helper::totalTagSize(data, dataSize, currentPosition);
if (nextTagSize.isError) {
if (nextTagSize.errorCode == ErrorCodes::NOT_YET_KNOWN) {
// attempt parsing the name
ErrorOr<tiny_utf8::string> tagName = Helper::readString(data, dataSize, currentPosition+1);
if (tagName.isError) {
returnValue = false;
goto returnNow;
}
// used seek to the start of the list's/compounds contents
//
// there is no way this is an error bc it gets
// checked while trying to parse the string above
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
uint64_t* processedTagSize = new uint64_t;
*processedTagSize = 0;
if (data[currentPosition]==TagType::LIST) {
// type byte + two name size bytes = 3
if (!validateRawListContents(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize)) {
delete processedTagSize;
returnValue = false;
goto returnNow;
}
*processedTagSize += (uint64_t) nameSize + 3;
}
if (data[currentPosition]==TagType::COMPOUND) {
// type byte + two name size bytes = 3
if (!validateRawNBTData(data, dataSize, currentPosition + (uint64_t) nameSize + 3, processedTagSize)) {
delete processedTagSize;
returnValue = false;
goto returnNow;
}
*processedTagSize += (uint64_t) nameSize + 3;
}
currentPosition += *processedTagSize;
delete processedTagSize;
continue;
}
returnValue = false;
goto returnNow;
}
if (currentPosition + nextTagSize.value > dataSize) {
returnValue = false;
goto returnNow;
}
// recursion abort condition
if (data[currentPosition]==TagType::END) {
currentPosition++;
returnValue = true;
goto returnNow;
}
// nameSize cannot be an error here bc it got checked in
// nextTagSize() already
int16_t nameSize = Helper::readInt16(data, dataSize, currentPosition+1).value;
// attempt parsing the name
//
// This shouldn't matter too much here as the only error condition
// the parser function deals with rn is an overrun which is already
// being guarded against with
// if (currentPosition + nextTagSize.value > dataSize) {
// returnValue = false;
// goto returnNow;
// }
// It might, however, turn out to be a useful check in the future.
ErrorOr<tiny_utf8::string> name = Helper::readString(data, dataSize, currentPosition+1);
if (name.isError) {
returnValue = false;
goto returnNow;
}
switch (data[currentPosition]) {
case TagType::INT8:
case TagType::INT16:
case TagType::INT32:
case TagType::INT64:
case TagType::FLOAT:
case TagType::DOUBLE:
case TagType::INT8_ARRAY:
break;
case TagType::STRING: {
// attempt parsing the content
//
// This shouldn't matter too much here as the only
// error condition the parser function deals with rn is
// an overrun which is already being guarded against with
// if (currentPosition + nextTagSize.value > dataSize) {
// returnValue = false;
// goto returnNow;
// }
// It might, however, turn out to be a useful check
// in the future.
//
// type byte + two name size bytes = 3
ErrorOr<tiny_utf8::string> content = Helper::readString(data, dataSize, currentPosition+nameSize+3);
if (content.isError) {
returnValue = false;
goto returnNow;
}
break;
}
case TagType::INT32_ARRAY:
case TagType::INT64_ARRAY:
break;
default:
returnValue = false;
goto returnNow;
}
currentPosition += nextTagSize.value;
}
returnValue = true;
goto returnNow;
returnNow:
if (processedDataSize!=nullptr) {
*processedDataSize = currentPosition-initialPosition;
}
return returnValue;
}
}