From f5d85da98c587ef62cc3ead22cd2eefab9be3907 Mon Sep 17 00:00:00 2001 From: BodgeMaster <> Date: Thu, 11 Aug 2022 07:43:54 +0200 Subject: [PATCH] lib/nbt: Move the functions for getting tag sizes into the helper namespace, give up on handling lists the same as all other tags I tried dealing with lists in the same way as with other more basic tags but came to the conclusion that this is most likely not feasible in the same way that it is not feasible for compounds. It would require a mini-parser that can deal with all sorts of tags (including nested lists and compounds). Instead, an approach more similar to the recursion for compound tags will be used (using its own function to deal with the missing tag headers ofc). --- src/lib/nbt.cpp | 292 +++++++++++++++++++++--------------------------- src/lib/nbt.hpp | 6 +- 2 files changed, 129 insertions(+), 169 deletions(-) diff --git a/src/lib/nbt.cpp b/src/lib/nbt.cpp index 41c1e02..1d53efa 100644 --- a/src/lib/nbt.cpp +++ b/src/lib/nbt.cpp @@ -370,6 +370,132 @@ namespace NBT { 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) + // + // 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 complex code. + // + // Regardinng 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 the tag headers + // of contained tags being absent so they may as well get their + // own function for this as well. + ErrorOr nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) { + uint8_t nextTag; + if (dataSize <= currentPosition) { + return ErrorOr(true, ErrorCodes::OVERRUN); + } else { + nextTag = data[currentPosition]; + } + // deal with compound tags separately + if (nextTag == TagType::COMPOUND || nextTag == TagType::LIST) return ErrorOr(false, ErrorCodes::NOT_YET_KNOWN); + // deal with end tag before trying to access the name + if (nextTag == TagType::END) return ErrorOr(1); + // get name size + ErrorOr nameSize = helper::readInt16(data, dataSize, currentPosition+1); + if (nameSize.isError) { + return ErrorOr(true, nameSize.errorCode); + } + switch (nextTag) { + case TagType::INT8: + // type byte + name size + data byte -> 4 bytes + return ErrorOr((uint64_t) nameSize.value+4); + case TagType::INT16: + // type byte + name size + 2 data bytes -> 5 bytes + return ErrorOr((uint64_t) nameSize.value+5); + case TagType::INT32: + // type byte + name size + 4 data bytes -> 7 bytes + return ErrorOr((uint64_t) nameSize.value+7); + case TagType::INT64: + // type byte + name size + 8 data bytes -> 11 bytes + return ErrorOr((uint64_t) nameSize.value+11); + case TagType::FLOAT: + // type byte + name size + 4 data bytes -> 7 bytes + return ErrorOr((uint64_t) nameSize.value+7); + case TagType::DOUBLE: + // type byte + name size + 8 data bytes -> 11 bytes + return ErrorOr((uint64_t) nameSize.value+11); + case TagType::INT8_ARRAY: { + // type byte + name size + 4 size bytes -> 7 bytes + uint64_t totalSize = (uint64_t) nameSize.value+7; + + // add size of actual data (1 byte per entry) + ErrorOr arraySize = helper::readInt32(data, dataSize, currentPosition+totalSize); + if (arraySize.isError) { + return ErrorOr(true, arraySize.errorCode); + } + totalSize += (uint64_t) arraySize.value; + + return ErrorOr(totalSize); + } + case TagType::STRING: { + // type byte + name size + 2 size bytes -> 5 bytes + uint64_t totalSize = (uint64_t) nameSize.value+5; + + // add size of actual data + ErrorOr stringSize = helper::readInt16(data, dataSize, currentPosition+totalSize); + if (stringSize.isError) { + return ErrorOr(true, stringSize.errorCode); + } + totalSize += (uint64_t) stringSize.value; + + return ErrorOr(totalSize); + } + case TagType::INT32_ARRAY: { + // type byte + name size + 4 size bytes -> 7 bytes + uint64_t totalSize = (uint64_t) nameSize.value+7; + + // add size of actual data (4 bytes per entry) + ErrorOr arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize); + if (arraySize.isError) { + return ErrorOr(true, arraySize.errorCode); + } + totalSize += (uint64_t) arraySize.value*4; + + return ErrorOr(totalSize); + } + case TagType::INT64_ARRAY: { + // type byte + name size + 4 size bytes -> 7 bytes + uint64_t totalSize = (uint64_t) nameSize.value+7; + + // add size of actual data (8 bytes per entry) + ErrorOr arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize); + if (arraySize.isError) { + return ErrorOr(true, arraySize.errorCode); + } + totalSize += (uint64_t) arraySize.value*8; + + return ErrorOr(totalSize); + } + // unknown tag or parsing error + default: + return ErrorOr(true, ErrorCodes::UNKNOWN); + } + } + + ErrorOr nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){ + + uint8_t nextTag; + if (dataSize <= currentPosition) { + return ErrorOr(true, ErrorCodes::OVERRUN); + } else { + nextTag = data[currentPosition]; + } + + // deal with compound tags separately + if (nextTag == TagType::COMPOUND) return ErrorOr(true, ErrorCodes::NOT_YET_KNOWN); + // deal with end tag before trying to access the name + if (nextTag == TagType::END) return 0; + //TODO: implement for all the remaining types + // unknown tag or parsing error + return ErrorOr(true, ErrorCodes::UNKNOWN); + } } //Tag constructors @@ -424,172 +550,6 @@ namespace NBT { } } - - //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) - ErrorOr nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition) { - uint8_t nextTag; - if (dataSize <= currentPosition) { - return ErrorOr(true, ErrorCodes::OVERRUN); - } else { - nextTag = data[currentPosition]; - } - // deal with compound tags separately - if (nextTag == TagType::COMPOUND) return ErrorOr(false, ErrorCodes::NOT_YET_KNOWN); - // deal with end tag before trying to access the name - if (nextTag == TagType::END) return ErrorOr(1); - // get name size - ErrorOr nameSize = helper::readInt16(data, dataSize, currentPosition+1); - if (nameSize.isError) { - return ErrorOr(true, nameSize.errorCode); - } - switch (nextTag) { - case TagType::INT8: - // type byte + name size + data byte -> 4 bytes - return ErrorOr((uint64_t) nameSize.value+4); - case TagType::INT16: - // type byte + name size + 2 data bytes -> 5 bytes - return ErrorOr((uint64_t) nameSize.value+5); - case TagType::INT32: - // type byte + name size + 4 data bytes -> 7 bytes - return ErrorOr((uint64_t) nameSize.value+7); - case TagType::INT64: - // type byte + name size + 8 data bytes -> 11 bytes - return ErrorOr((uint64_t) nameSize.value+11); - case TagType::FLOAT: - // type byte + name size + 4 data bytes -> 7 bytes - return ErrorOr((uint64_t) nameSize.value+7); - case TagType::DOUBLE: - // type byte + name size + 8 data bytes -> 11 bytes - return ErrorOr((uint64_t) nameSize.value+11); - case TagType::INT8_ARRAY: { - // type byte + name size + 4 size bytes -> 7 bytes - uint64_t totalSize = (uint64_t) nameSize.value+7; - - // add size of actual data (1 byte per entry) - ErrorOr arraySize = helper::readInt32(data, dataSize, currentPosition+totalSize); - if (arraySize.isError) { - return ErrorOr(true, arraySize.errorCode); - } - totalSize += (uint64_t) arraySize.value; - - return ErrorOr(totalSize); - } - case TagType::STRING: { - // type byte + name size + 2 size bytes -> 5 bytes - uint64_t totalSize = (uint64_t) nameSize.value+5; - - // add size of actual data - ErrorOr stringSize = helper::readInt16(data, dataSize, currentPosition+totalSize); - if (stringSize.isError) { - return ErrorOr(true, stringSize.errorCode); - } - totalSize += (uint64_t) stringSize.value; - - return ErrorOr(totalSize); - } - case TagType::LIST: { - // type byte + name size + type prefix + 4 size bytes -> 8 bytes - uint64_t totalSize = (uint64_t) nameSize.value+8; - - // determine size of actual data - ErrorOr containedType = nextTagType(data, dataSize, currentPosition+totalSize-1); - if (containedType.isError) { - return ErrorOr(true, containedType.errorCode); - } - ErrorOr listSize = helper::readInt16(data, dataSize, currentPosition+totalSize); - if (listSize.isError) { - return ErrorOr(true, listSize.errorCode); - } - // Can we just multiply list size with data type size? - if (containedType.value == TagType::END || containedType.value == TagType::INT8 || containedType.value == TagType::INT16 || containedType.value == TagType::INT32 || containedType.value == TagType::INT64 || containedType.value == TagType::FLOAT || containedType.value == TagType::DOUBLE) { - uint8_t factor; - switch (containedType.value) { - case TagType::END: - factor = 1; - break; - case TagType::INT8: - factor = 1; - break; - case TagType::INT16: - factor = 2; - break; - case TagType::INT32: - factor = 4; - break; - case TagType::INT64: - factor = 8; - break; - case TagType::FLOAT: - factor = 4; - break; - case TagType::DOUBLE: - factor = 8; - break; - default: - // How would you even get here? - return ErrorOr(true, ErrorCodes::UNKNOWN); - } - totalSize += listSize.value*factor; - return ErrorOr(totalSize); - } else { - if (containedType.value == TagType::COMPOUND || containedType.value == TagType::LIST) return ErrorOr(false, ErrorCodes::NOT_YET_KNOWN); - //TODO: INT8_ARRAY, STRING, INT32_ARRAY, INT64_ARRAY - } - - return ErrorOr(true, ErrorCodes::UNKNOWN); - } - case TagType::INT32_ARRAY: { - // type byte + name size + 4 size bytes -> 7 bytes - uint64_t totalSize = (uint64_t) nameSize.value+7; - - // add size of actual data (4 bytes per entry) - ErrorOr arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize); - if (arraySize.isError) { - return ErrorOr(true, arraySize.errorCode); - } - totalSize += (uint64_t) arraySize.value*4; - - return ErrorOr(totalSize); - } - case TagType::INT64_ARRAY: { - // type byte + name size + 4 size bytes -> 7 bytes - uint64_t totalSize = (uint64_t) nameSize.value+7; - - // add size of actual data (8 bytes per entry) - ErrorOr arraySize = helper::readInt16(data, dataSize, currentPosition+totalSize); - if (arraySize.isError) { - return ErrorOr(true, arraySize.errorCode); - } - totalSize += (uint64_t) arraySize.value*8; - - return ErrorOr(totalSize); - } - // fall-through in case of unknown tag or parsing error - default: - return ErrorOr(true, ErrorCodes::UNKNOWN); - } - } - - ErrorOr nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition){ - - uint8_t nextTag; - if (dataSize <= currentPosition) { - return ErrorOr(true, ErrorCodes::OVERRUN); - } else { - nextTag = data[currentPosition]; - } - - // deal with compound tags separately - if (nextTag == TagType::COMPOUND) return ErrorOr(true, ErrorCodes::NOT_YET_KNOWN); - // deal with end tag before trying to access the name - if (nextTag == TagType::END) return 0; - //TODO: implement for all the remaining types - // fall-through in case of unknown tag or parsing error - return ErrorOr(true, ErrorCodes::UNKNOWN); - } - bool validateRawNBTData(uint8_t data[], uint64_t dataSize, uint64_t initialPosition){ //TODO: find out the size of the next tag //TODO: consume tag diff --git a/src/lib/nbt.hpp b/src/lib/nbt.hpp index 899c9c2..578a1f2 100644 --- a/src/lib/nbt.hpp +++ b/src/lib/nbt.hpp @@ -66,6 +66,9 @@ namespace NBT { void writeInt32Array(std::vector* destination, int32_t data[], uint32_t dataSize); void writeInt64Array(std::vector* destination, std::vector data); void writeInt64Array(std::vector* destination, int64_t data[], uint32_t dataSize); + + ErrorOr nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition); + ErrorOr nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition); } namespace TagType { @@ -110,8 +113,5 @@ namespace NBT { bool validate(uint8_t data[]); }; - ErrorOr nextTagType(uint8_t data[], uint64_t dataSize, uint64_t currentPosition); - ErrorOr nextTagTotalSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition); - ErrorOr nextTagDataSize(uint8_t data[], uint64_t dataSize, uint64_t currentPosition); bool validateRawNBTData(uint8_t data[], int length, uint64_t initialPosition=0); }