433 lines
19 KiB
C++
433 lines
19 KiB
C++
// 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 <iostream>
|
|
|
|
#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 (dataSize<currentPosition+1) 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 (dataSize<currentPosition+2) return ErrorOr<int16_t>(true, ErrorCodes::OUT_OF_RANGE);
|
|
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 (dataSize<currentPosition+4) return ErrorOr<int32_t>(true, ErrorCodes::OUT_OF_RANGE);
|
|
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 (dataSize<currentPosition+8) return ErrorOr<int64_t>(true, ErrorCodes::OUT_OF_RANGE);
|
|
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) {
|
|
// get size prefix
|
|
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
|
|
if (size.isError) 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) {
|
|
if(dataSize > 0xFFFF){
|
|
return ErrorOr<tiny_utf8::string>(true, ErrorCodes::OVERRUN);
|
|
}
|
|
|
|
ErrorOr<tiny_utf8::string> output = JavaCompat::importJavaString(data+currentPosition, (uint16_t) dataSize);
|
|
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) {
|
|
// get size prefix
|
|
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
|
|
if (size.isError) 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) {
|
|
// get size prefix
|
|
ErrorOr<int32_t> size = readInt32(data, dataSize, currentPosition);
|
|
if (size.isError) 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]);
|
|
}
|
|
}
|
|
|
|
void writeString(std::vector<uint8_t>* destination, tiny_utf8::string data) {
|
|
ErrorOr<std::vector<uint8_t>> exportedString = JavaCompat::exportJavaString(data);
|
|
if(exportedString.isError){
|
|
std::cerr << "NBT::helpers::writeString encountered an error: " << (int) exportedString.errorCode << std::endl;
|
|
std::abort();
|
|
}
|
|
*destination = exportedString.value;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Tag constructors
|
|
|
|
template <typename T>
|
|
Tag<T>::Tag(uint8_t tagType, tiny_utf8::string name, uint16_t nameSize, T content, uint32_t size)
|
|
: tagType(tagType), name(name), nameSize(nameSize), content(content) ,size(size)
|
|
{}
|
|
|
|
End::End() : Tag::Tag(0, "", 0, 0, 0) {}
|
|
|
|
Byte::Byte(tiny_utf8::string name, uint16_t nameSize, int8_t content)
|
|
: Tag::Tag(1, name, nameSize, content, 1)
|
|
{}
|
|
|
|
Byte::Byte(uint8_t data[]){
|
|
if(validate(data)){
|
|
this->tagType = 1;
|
|
|
|
uint8_t nameSizeSlice[] = {data[1], data[2]};
|
|
|
|
ErrorOr<int16_t> readIntResult = helper::readInt16(nameSizeSlice, 2, 0);
|
|
if(!readIntResult.isError){
|
|
this->nameSize = readIntResult.value;
|
|
}else{
|
|
throw readIntResult.errorCode;
|
|
}
|
|
|
|
uint8_t nameSlice[this->nameSize+2];
|
|
for(int i=0; i<this->nameSize+2; i++){
|
|
nameSlice[i] = data[i+1];
|
|
}
|
|
|
|
ErrorOr<tiny_utf8::string> readStringResult = helper::readString(nameSlice, this->nameSize, 0);
|
|
if(!readStringResult.isError){
|
|
this->name = readStringResult.value;
|
|
}else{
|
|
throw readStringResult.errorCode;
|
|
}
|
|
|
|
//int8 needs only one byte
|
|
this->content = data[this->nameSize+4];
|
|
}
|
|
}
|
|
|
|
//more conditions will be added
|
|
bool Byte::validate(uint8_t data[]){
|
|
if(data[0] == 0x01){
|
|
return true;
|
|
}else{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool validateRawNBTData(uint8_t data[], uint64_t dataSize){
|
|
//state machine?
|
|
//TODO: implement
|
|
return false;
|
|
}
|
|
}
|