2023-01-27 10:45:18 -05:00
|
|
|
/*
|
|
|
|
* Created by Brett on 27/01/23.
|
|
|
|
* Licensed under GNU General Public License V3.0
|
|
|
|
* See LICENSE file for license detail
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef BLT_TESTS_NBT_H
|
|
|
|
#define BLT_TESTS_NBT_H
|
|
|
|
|
2023-07-24 03:40:09 -04:00
|
|
|
#include <utility>
|
2023-07-26 03:48:09 -04:00
|
|
|
#include <bit>
|
|
|
|
#include <cstring>
|
|
|
|
#include <type_traits>
|
2023-07-26 13:52:51 -04:00
|
|
|
#include <unordered_map>
|
2023-07-24 03:40:09 -04:00
|
|
|
|
2023-01-29 15:24:33 -05:00
|
|
|
#include "blt/std/format.h"
|
2023-01-29 22:27:48 -05:00
|
|
|
#include "blt/std/filesystem.h"
|
2023-01-29 15:24:33 -05:00
|
|
|
|
|
|
|
namespace blt::nbt {
|
2023-07-26 13:52:51 -04:00
|
|
|
#ifndef HASHMAP
|
|
|
|
template<typename K, typename V>
|
|
|
|
using HASHMAP = std::unordered_map<K, V>;
|
|
|
|
#endif
|
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
void writeUTF8String(blt::fs::block_writer& stream, const std::string& str);
|
2023-01-29 15:24:33 -05:00
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
std::string readUTF8String(blt::fs::block_reader& stream);
|
2023-01-29 15:24:33 -05:00
|
|
|
|
2023-07-25 14:06:04 -04:00
|
|
|
// Used to grab the byte-data of any T element. Defaults to Big Endian, however can be configured to use little endian
|
|
|
|
template <typename T>
|
2023-07-26 03:48:09 -04:00
|
|
|
inline static int toBytes(const T& in, char* out) {
|
|
|
|
std::memcpy(out, (void*) &in, sizeof(T));
|
|
|
|
|
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
|
|
|
for (size_t i = 0; i < sizeof(T) / 2; i++)
|
|
|
|
std::swap(out[i], out[sizeof(T) - 1 - i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-07-25 14:06:04 -04:00
|
|
|
|
|
|
|
// Used to cast the binary data of any T object, into a T object.
|
|
|
|
template <typename T>
|
2023-07-26 03:48:09 -04:00
|
|
|
inline static int fromBytes(const char* in, T* out) {
|
|
|
|
memcpy(out, in, sizeof(T));
|
|
|
|
|
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
|
|
|
for (size_t i = 0; i < sizeof(T) / 2; i++)
|
|
|
|
std::swap(((char*) (out))[i], ((char*) (out))[sizeof(T) - 1 - i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
inline static void writeData(blt::fs::block_writer& out, const T& d){
|
|
|
|
char data[sizeof(T)];
|
|
|
|
toBytes(d, data);
|
|
|
|
out.write(data, sizeof(T));
|
|
|
|
}
|
2023-07-26 13:52:51 -04:00
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
template<typename T>
|
|
|
|
inline static void readData(blt::fs::block_reader& in, T& d) {
|
|
|
|
char data[sizeof(T)];
|
|
|
|
in.read(data, sizeof(T));
|
|
|
|
fromBytes(data, &d);
|
|
|
|
}
|
2023-07-25 14:06:04 -04:00
|
|
|
|
2023-07-24 03:30:23 -04:00
|
|
|
enum class nbt_tag : char {
|
|
|
|
END = 0,
|
|
|
|
BYTE = 1,
|
|
|
|
SHORT = 2,
|
|
|
|
INT = 3,
|
|
|
|
LONG = 4,
|
|
|
|
FLOAT = 5,
|
|
|
|
DOUBLE = 6,
|
|
|
|
BYTE_ARRAY = 7,
|
|
|
|
STRING = 8,
|
|
|
|
LIST = 9,
|
|
|
|
COMPOUND = 10,
|
|
|
|
INT_ARRAY = 11,
|
|
|
|
LONG_ARRAY = 12
|
2023-01-29 23:36:42 -05:00
|
|
|
};
|
|
|
|
|
2023-07-25 14:06:04 -04:00
|
|
|
class tag_t {
|
|
|
|
protected:
|
|
|
|
nbt_tag type;
|
|
|
|
std::string name;
|
|
|
|
public:
|
|
|
|
explicit tag_t(nbt_tag type): type(type) {};
|
|
|
|
explicit tag_t(nbt_tag type, std::string name): type(type), name(std::move(name)) {}
|
2023-07-26 03:48:09 -04:00
|
|
|
virtual void writePayload(blt::fs::block_writer& out) = 0;
|
|
|
|
virtual void readPayload(blt::fs::block_reader& in) = 0;
|
|
|
|
void writeName(blt::fs::block_writer& out) {
|
|
|
|
writeUTF8String(out, name);
|
|
|
|
}
|
|
|
|
void readName(blt::fs::block_reader& in) {
|
|
|
|
name = readUTF8String(in);
|
|
|
|
}
|
|
|
|
[[nodiscard]] inline nbt_tag getType() const {
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
[[nodiscard]] inline const std::string& getName() const {
|
|
|
|
return name;
|
|
|
|
}
|
2023-07-26 13:09:41 -04:00
|
|
|
virtual ~tag_t() = default;
|
2023-07-25 14:06:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
template<typename T>
|
|
|
|
class tag : public tag_t {
|
|
|
|
protected:
|
|
|
|
T t;
|
|
|
|
public:
|
2023-07-26 03:48:09 -04:00
|
|
|
explicit tag(nbt_tag type): tag_t(type) {}
|
|
|
|
tag(nbt_tag type, std::string name, T t): tag_t(type, std::move(name)), t(std::move(t)) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) override {
|
|
|
|
if constexpr(std::is_arithmetic<T>::value)
|
|
|
|
writeData(out, t);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) override {
|
|
|
|
if constexpr(std::is_arithmetic<T>::value)
|
|
|
|
readData(in, t);
|
|
|
|
}
|
2023-07-25 14:06:04 -04:00
|
|
|
[[nodiscard]] inline const T& get() const {return t;}
|
|
|
|
inline T& get() {return t;}
|
2023-07-26 13:09:41 -04:00
|
|
|
~tag() override = default;
|
2023-07-25 14:06:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
class tag_end : public tag<char> {
|
|
|
|
public:
|
2023-07-26 03:48:09 -04:00
|
|
|
void writePayload(blt::fs::block_writer&) final {}
|
2023-07-25 14:06:04 -04:00
|
|
|
// nothing to read
|
2023-07-26 03:48:09 -04:00
|
|
|
void readPayload(blt::fs::block_reader&) final {}
|
2023-07-25 14:06:04 -04:00
|
|
|
};
|
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
class tag_byte : public tag<int8_t> {
|
2023-07-25 14:06:04 -04:00
|
|
|
public:
|
2023-07-26 03:48:09 -04:00
|
|
|
tag_byte(): tag(nbt_tag::BYTE) {}
|
|
|
|
tag_byte(const std::string& name, int8_t b): tag(nbt_tag::BYTE, name, b) {}
|
2023-07-25 14:06:04 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
class tag_short : public tag<int16_t> {
|
|
|
|
public:
|
2023-07-26 03:48:09 -04:00
|
|
|
tag_short(): tag(nbt_tag::SHORT) {}
|
|
|
|
tag_short(const std::string& name, int16_t s): tag(nbt_tag::SHORT, name, s) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_int : public tag<int32_t> {
|
|
|
|
public:
|
|
|
|
tag_int(): tag(nbt_tag::INT) {}
|
|
|
|
tag_int(const std::string& name, int32_t i): tag(nbt_tag::INT, name, i) {}
|
2023-07-25 14:06:04 -04:00
|
|
|
};
|
2023-07-24 03:40:09 -04:00
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
class tag_long : public tag<int64_t> {
|
|
|
|
public:
|
|
|
|
tag_long(): tag(nbt_tag::LONG) {}
|
|
|
|
tag_long(const std::string& name, int64_t l): tag(nbt_tag::LONG, name, l) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_float : public tag<float> {
|
|
|
|
public:
|
|
|
|
tag_float(): tag(nbt_tag::FLOAT) {}
|
|
|
|
tag_float(const std::string& name, float f): tag(nbt_tag::FLOAT, name, f) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_double : public tag<double> {
|
|
|
|
public:
|
|
|
|
tag_double(): tag(nbt_tag::DOUBLE) {}
|
|
|
|
tag_double(const std::string& name, double d): tag(nbt_tag::DOUBLE, name, d) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_byte_array : public tag<std::vector<int8_t>> {
|
|
|
|
public:
|
|
|
|
tag_byte_array(): tag(nbt_tag::BYTE_ARRAY) {}
|
|
|
|
tag_byte_array(const std::string& name, const std::vector<int8_t>& v): tag(nbt_tag::BYTE_ARRAY, name, v) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
auto length = (int32_t) t.size();
|
|
|
|
writeData(out, length);
|
|
|
|
// TODO on the writer (remove need for cast + more std::fstream functions)
|
|
|
|
out.write(reinterpret_cast<char*>(t.data()), length);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
int32_t length;
|
|
|
|
readData(in, length);
|
|
|
|
t.reserve(length);
|
|
|
|
in.read(reinterpret_cast<char*>(t.data()), length);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_string : public tag<std::string> {
|
|
|
|
public:
|
|
|
|
tag_string(): tag(nbt_tag::STRING) {}
|
|
|
|
tag_string(const std::string& name, const std::string& s): tag(nbt_tag::BYTE_ARRAY, name, s) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
writeUTF8String(out, t);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
t = readUTF8String(in);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_int_array : public tag<std::vector<int32_t>> {
|
|
|
|
public:
|
|
|
|
tag_int_array(): tag(nbt_tag::INT_ARRAY) {}
|
|
|
|
tag_int_array(const std::string& name, const std::vector<int32_t>& v): tag(nbt_tag::INT, name, v) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
auto length = (int32_t) t.size();
|
|
|
|
writeData(out, length);
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
writeData(out, t[i]);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
int32_t length;
|
|
|
|
readData(in, length);
|
|
|
|
t.reserve(length);
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
readData(in, t[i]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class tag_long_array : public tag<std::vector<int64_t>> {
|
|
|
|
public:
|
|
|
|
tag_long_array(): tag(nbt_tag::LONG_ARRAY) {}
|
|
|
|
tag_long_array(const std::string& name, const std::vector<int64_t>& v): tag(nbt_tag::LONG_ARRAY, name, v) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
auto length = (int32_t) t.size();
|
|
|
|
writeData(out, length);
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
writeData(out, t[i]);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
int32_t length;
|
|
|
|
readData(in, length);
|
|
|
|
t.reserve(length);
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
readData(in, t[i]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#define BLT_NBT_POPULATE_VEC(type, vec, length) for (int i = 0; i < length; i++) vec.push_back(type);
|
|
|
|
|
2023-07-26 13:09:41 -04:00
|
|
|
namespace _internal_ {
|
|
|
|
// EVIL HACK
|
|
|
|
static tag_t* newCompound();
|
|
|
|
static tag_t* newList();
|
|
|
|
static tag_t* toType(char id){
|
|
|
|
switch ((nbt_tag) id) {
|
|
|
|
case nbt_tag::END:
|
|
|
|
return nullptr;
|
|
|
|
break;
|
|
|
|
case nbt_tag::BYTE:
|
|
|
|
return new blt::nbt::tag_byte;
|
|
|
|
case nbt_tag::SHORT:
|
|
|
|
return new blt::nbt::tag_short;
|
|
|
|
case nbt_tag::INT:
|
|
|
|
return new blt::nbt::tag_int;
|
|
|
|
case nbt_tag::LONG:
|
|
|
|
return new blt::nbt::tag_long;
|
|
|
|
case nbt_tag::FLOAT:
|
|
|
|
return new blt::nbt::tag_float;
|
|
|
|
case nbt_tag::DOUBLE:
|
|
|
|
return new blt::nbt::tag_double;
|
|
|
|
case nbt_tag::BYTE_ARRAY:
|
|
|
|
return new blt::nbt::tag_byte_array;
|
|
|
|
case nbt_tag::STRING:
|
|
|
|
return new blt::nbt::tag_string;
|
|
|
|
case nbt_tag::LIST:
|
|
|
|
return _internal_::newList();
|
|
|
|
case nbt_tag::COMPOUND:
|
|
|
|
return _internal_::newCompound();
|
|
|
|
case nbt_tag::INT_ARRAY:
|
|
|
|
return new blt::nbt::tag_int_array;
|
|
|
|
case nbt_tag::LONG_ARRAY:
|
|
|
|
return new blt::nbt::tag_long_array;
|
|
|
|
}
|
|
|
|
}
|
2023-07-26 13:52:51 -04:00
|
|
|
static HASHMAP<std::string, tag_t*> toHashmap(const std::vector<tag_t*>& v){
|
|
|
|
HASHMAP<std::string, tag_t*> tags;
|
|
|
|
for (const auto& t : v)
|
|
|
|
tags[t->getName()] = t;
|
|
|
|
return tags;
|
|
|
|
}
|
2023-07-26 13:09:41 -04:00
|
|
|
}
|
|
|
|
|
2023-07-26 03:48:09 -04:00
|
|
|
class tag_list : public tag<std::vector<tag_t*>> {
|
|
|
|
public:
|
|
|
|
tag_list(): tag(nbt_tag::LIST) {}
|
|
|
|
tag_list(const std::string& name, const std::vector<tag_t*>& v): tag(nbt_tag::LIST, name, v) {}
|
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
if (t.empty())
|
|
|
|
writeData(out, (char)nbt_tag::END);
|
|
|
|
else
|
|
|
|
writeData(out, (char)t[0]->getType());
|
|
|
|
auto length = (int32_t) t.size();
|
|
|
|
writeData(out, length);
|
|
|
|
for (const auto& v : t)
|
|
|
|
v->writePayload(out);
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
char id;
|
|
|
|
int32_t length;
|
|
|
|
readData(in, id);
|
|
|
|
readData(in, length);
|
|
|
|
if (length == 0 || id == 0)
|
|
|
|
return;
|
2023-07-26 13:09:41 -04:00
|
|
|
t.reserve(length);
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
t[i] = _internal_::toType(id);
|
2023-07-26 03:48:09 -04:00
|
|
|
t[i]->readPayload(in);
|
2023-07-26 13:09:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
~tag_list() override {
|
|
|
|
for (auto* p : t)
|
|
|
|
delete p;
|
2023-07-26 03:48:09 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-07-26 13:52:51 -04:00
|
|
|
class tag_compound : public tag<HASHMAP<std::string, tag_t*>> {
|
2023-07-26 13:09:41 -04:00
|
|
|
public:
|
|
|
|
tag_compound(): tag(nbt_tag::COMPOUND) {}
|
2023-07-26 13:52:51 -04:00
|
|
|
tag_compound(const std::string& name, const std::vector<tag_t*>& v): tag(nbt_tag::COMPOUND, name, _internal_::toHashmap(v)) {}
|
|
|
|
tag_compound(const std::string& name, const HASHMAP<std::string, tag_t*>& v): tag(nbt_tag::COMPOUND, name, v) {}
|
2023-07-26 13:09:41 -04:00
|
|
|
void writePayload(blt::fs::block_writer& out) final {
|
|
|
|
for (auto*& v : t){
|
|
|
|
out.put((char) v->getType());
|
|
|
|
v->writeName(out);
|
|
|
|
v->writePayload(out);
|
|
|
|
}
|
|
|
|
out.put('\0');
|
|
|
|
}
|
|
|
|
void readPayload(blt::fs::block_reader& in) final {
|
|
|
|
char type;
|
|
|
|
while ((type = in.get()) != (char)nbt_tag::END){
|
|
|
|
auto* v = _internal_::toType(type);
|
|
|
|
v->readName(in);
|
|
|
|
v->readPayload(in);
|
|
|
|
t.push_back(v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2023-07-26 03:48:09 -04:00
|
|
|
|
2023-07-26 13:09:41 -04:00
|
|
|
static tag_t* _internal_::newCompound(){
|
|
|
|
return new blt::nbt::tag_compound;
|
|
|
|
}
|
2023-07-26 03:48:09 -04:00
|
|
|
|
2023-07-26 13:09:41 -04:00
|
|
|
static tag_t* _internal_::newList() {
|
|
|
|
return new blt::nbt::tag_list;
|
2023-07-26 03:48:09 -04:00
|
|
|
}
|
|
|
|
|
2023-07-26 13:52:51 -04:00
|
|
|
class NBTReader {
|
2023-01-29 15:24:33 -05:00
|
|
|
private:
|
2023-07-26 13:52:51 -04:00
|
|
|
blt::fs::block_reader& reader;
|
2023-01-29 15:24:33 -05:00
|
|
|
public:
|
2023-07-26 13:52:51 -04:00
|
|
|
explicit NBTReader(blt::fs::block_reader& reader): reader(reader) {}
|
|
|
|
|
2023-01-29 15:24:33 -05:00
|
|
|
};
|
|
|
|
|
2023-07-26 13:52:51 -04:00
|
|
|
class NBTWriter {
|
2023-01-29 15:24:33 -05:00
|
|
|
private:
|
2023-07-26 13:52:51 -04:00
|
|
|
blt::fs::block_writer& writer;
|
|
|
|
tag_compound* root = nullptr;
|
2023-01-29 15:24:33 -05:00
|
|
|
public:
|
2023-07-26 13:52:51 -04:00
|
|
|
explicit NBTWriter(blt::fs::block_writer& writer): writer(writer) {}
|
|
|
|
void readFully(){
|
|
|
|
|
|
|
|
}
|
2023-01-29 15:24:33 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
2023-01-27 10:45:18 -05:00
|
|
|
|
|
|
|
#endif //BLT_TESTS_NBT_H
|