summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/nbt.h154
-rw-r--r--meson.build4
-rw-r--r--src/htgen.h4
-rw-r--r--src/libmain.c3
-rw-r--r--src/meson.build2
-rw-r--r--src/nbt.internal.h78
-rw-r--r--src/nbt/list.c173
-rw-r--r--src/nbt/nbt.c207
-rw-r--r--src/nbt/nbtht.c2
-rw-r--r--src/nbt/nbtht.internal.h44
10 files changed, 668 insertions, 3 deletions
diff --git a/include/nbt.h b/include/nbt.h
new file mode 100644
index 0000000..ba7e0a6
--- /dev/null
+++ b/include/nbt.h
@@ -0,0 +1,154 @@
+#ifndef LIBWORLD_NBT_H_INCLUDED
+#define LIBWORLD_NBT_H_INCLUDED
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef size_t nbt_size_t;
+
+/* NBT primitives */
+typedef int8_t nbt_byte_t;
+typedef int16_t nbt_short_t;
+typedef int32_t nbt_int_t;
+typedef int64_t nbt_long_t;
+
+typedef float nbt_float_t;
+typedef double nbt_double_t;
+
+/* NBT tag types */
+typedef enum {
+ NBT_TAG_END = 0,
+ NBT_TAG_BYTE,
+ NBT_TAG_SHORT,
+ NBT_TAG_INT,
+ NBT_TAG_LONG,
+
+ NBT_TAG_FLOAT,
+ NBT_TAG_DOUBLE,
+
+ NBT_TAG_BYTE_ARRAY,
+
+ NBT_TAG_STRING,
+ NBT_TAG_LIST,
+ NBT_TAG_COMPOUND,
+
+ NBT_TAG_INT_ARRAY,
+ NBT_TAG_LONG_ARRAY
+} nbt_type_t;
+
+typedef struct nbt__tag_tag nbt_tag_t;
+
+/* API inspired by jansson's API (it is nice to use) */
+
+/* constructors and management functions */
+nbt_tag_t *nbt_byte(nbt_byte_t b);
+nbt_tag_t *nbt_short(nbt_short_t s);
+nbt_tag_t *nbt_int(nbt_int_t i);
+nbt_tag_t *nbt_long(nbt_long_t l);
+
+nbt_tag_t *nbt_float(nbt_float_t f);
+nbt_tag_t *nbt_double(nbt_double_t d);
+
+nbt_tag_t *nbt_byte_array(const nbt_byte_t *bytes, nbt_size_t length);
+
+nbt_tag_t *nbt_string(const char *c_str);
+nbt_tag_t *nbt_stringn(const char *c_str, nbt_size_t length);
+
+nbt_tag_t *nbt_list(void);
+nbt_tag_t *nbt_listn(nbt_size_t cap);
+
+nbt_tag_t *nbt_compound(void);
+nbt_tag_t *nbt_compoundn(nbt_size_t cap);
+
+nbt_tag_t *nbt_int_array(const nbt_int_t *ints, nbt_size_t length);
+nbt_tag_t *nbt_long_array(const nbt_long_t *longs, nbt_size_t length);
+
+void nbt_decref(nbt_tag_t *tag);
+nbt_tag_t *nbt_incref(nbt_tag_t *tag);
+
+/* primitive functions */
+
+nbt_byte_t nbt_byte_get(nbt_tag_t *tag);
+nbt_short_t nbt_short_get(nbt_tag_t *tag);
+nbt_int_t nbt_int_get(nbt_tag_t *tag);
+nbt_long_t nbt_long_get(nbt_tag_t *tag);
+
+nbt_float_t nbt_float_get(nbt_tag_t *tag);
+nbt_double_t nbt_double_get(nbt_tag_t *tag);
+
+/* array functions */
+
+const nbt_byte_t *nbt_byte_array_data(nbt_tag_t *tag, nbt_size_t *length);
+nbt_size_t nbt_byte_array_length(nbt_tag_t *tag);
+
+const char *nbt_string_data(nbt_tag_t *tag, nbt_size_t *length);
+nbt_size_t nbt_string_length(nbt_tag_t *tag);
+
+const nbt_int_t *nbt_int_array_data(nbt_tag_t *tag, nbt_size_t *length);
+nbt_size_t nbt_int_array_length(nbt_tag_t *tag);
+
+const nbt_long_t *nbt_long_array_data(nbt_tag_t *tag, nbt_size_t *length);
+nbt_size_t nbt_long_array_length(nbt_tag_t *tag);
+
+/* list functions */
+
+nbt_type_t nbt_list_type(nbt_tag_t *list);
+nbt_size_t nbt_list_length(nbt_tag_t *list);
+
+nbt_tag_t *nbt_list_get(nbt_tag_t *list, nbt_size_t idx);
+
+int nbt_list_append(nbt_tag_t *list, nbt_tag_t *tag);
+int nbt_list_append_move(nbt_tag_t *list, nbt_tag_t *tag);
+
+int nbt_list_insert(nbt_tag_t *list, nbt_tag_t *tag, size_t at);
+int nbt_list_insert_move(nbt_tag_t *list, nbt_tag_t *tag, size_t at);
+
+int nbt_list_remove(nbt_tag_t *list, nbt_size_t idx);
+nbt_tag_t *nbt_list_pop(nbt_tag_t *list, nbt_size_t idx);
+
+int nbt_list_remove_back(nbt_tag_t *list);
+nbt_tag_t *nbt_list_pop_back(nbt_tag_t *list);
+
+void nbt_list_clear(nbt_tag_t *list);
+
+/* list advanced functions */
+nbt_size_t nbt_list_capacity(nbt_tag_t *list);
+
+/* realloc internal array down to size */
+int nbt_list_shrink(nbt_tag_t *list);
+
+/* realloc internal array to at least this size */
+int nbt_list_reserve(nbt_tag_t *list, nbt_size_t newsz);
+
+/* realloc internal array to at least the list size plus moresz
+ * (basically equivalent to `nbt_list_reserve(list, nbt_list_length(list) + moresz)'.) */
+int nbt_list_reserve_more(nbt_tag_t *list, nbt_size_t moresz);
+
+/* compound functions */
+
+nbt_size_t nbt_compound_length(nbt_tag_t *compound);
+
+nbt_tag_t *nbt_compound_find(const char *key);
+nbt_tag_t *nbt_compound_findn(const char *key, nbt_size_t keylen);
+
+/* returns NBT_TAG_END if there is no value present */
+nbt_type_t nbt_compound_type(const char *key);
+nbt_type_t nbt_compound_typen(const char *key, nbt_size_t keylen);
+
+int nbt_compound_put(nbt_tag_t *compound, const char *key, nbt_tag_t *value);
+int nbt_compound_putn(nbt_tag_t *compound, const char *key, nbt_size_t sz, nbt_tag_t *value);
+
+nbt_tag_t *nbt_compound_swap(nbt_tag_t *compound, const char *key, nbt_tag_t *value);
+nbt_tag_t *nbt_compound_swapn(nbt_tag_t *compound, const char *key, nbt_size_t sz, nbt_tag_t *value);
+
+int nbt_compound_remove(nbt_tag_t *compound, const char *key);
+int nbt_compound_removen(nbt_tag_t *compound, const char *key, nbt_size_t sz);
+
+nbt_tag_t *nbt_compound_pop(nbt_tag_t *compound, const char *key);
+nbt_tag_t *nbt_compound_popn(nbt_tag_t *compound, const char *key, nbt_size_t sz);
+
+void nbt_compound_clear(nbt_tag_t *compound);
+
+int nbt_compound_merge(nbt_tag_t *target, nbt_tag_t *srccomp);
+
+#endif
diff --git a/meson.build b/meson.build
index 11da057..eaae487 100644
--- a/meson.build
+++ b/meson.build
@@ -2,5 +2,7 @@ project('world', 'c')
xxhash_dep = dependency('libxxhash', required : true, static : true)
+include_folder = include_directories('include')
+
subdir('src')
-executable('world', libworld_sources, dependencies : [xxhash_dep], win_subsystem : 'console')
+executable('world', libworld_sources, include_directories : [include_folder], dependencies : [xxhash_dep], win_subsystem : 'console')
diff --git a/src/htgen.h b/src/htgen.h
index 86bf7e9..3e22329 100644
--- a/src/htgen.h
+++ b/src/htgen.h
@@ -4,6 +4,7 @@
#include <stdlib.h>
#include <string.h>
+#ifdef HTGEN__TEST_CONFIG
#if 1
#define HT_PREFIX shash_
#define HT_VALTYPE char *
@@ -76,6 +77,7 @@ XXH64_hash_t ihash_hash_int(int i)
#define HT_VAL_FMT "%d"
#endif
+#endif
#define HT_MALLOC(_sz) malloc(_sz)
#define HT_CALLOC(_num, _sz) calloc(_num, _sz)
@@ -128,7 +130,7 @@ HT_VALTYPE HT__NS(popn)(HT__TYPE *ht, HT_KEYTYPE_CREF key, size_t keylen, bool *
/* implementations */
-#if defined(HT_IMPLEMENTATIONS) || 1
+#if defined(HT_IMPLEMENTATIONS)
#define HT__NODE HT__NS(_node)
diff --git a/src/libmain.c b/src/libmain.c
index 25a340d..e82635d 100644
--- a/src/libmain.c
+++ b/src/libmain.c
@@ -5,8 +5,11 @@
#include <stdio.h>
#define HT_IMPLEMENTATIONS
+#define HTGEN__TEST_CONFIG
#include "htgen.h"
+#include "nbt.h"
+
#if 1
int main(int argc, char **argv) {
shash_t *hash = shash_create(4, 0.75f);
diff --git a/src/meson.build b/src/meson.build
index a2cf6d0..92fbc23 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1 +1 @@
-libworld_sources = files('libmain.c')
+libworld_sources = files('libmain.c', 'nbt/nbt.c', 'nbt/nbtht.c', 'nbt/list.c')
diff --git a/src/nbt.internal.h b/src/nbt.internal.h
new file mode 100644
index 0000000..88f636b
--- /dev/null
+++ b/src/nbt.internal.h
@@ -0,0 +1,78 @@
+#ifndef LIBWORLD_NBT_INTERNAL_H_INCLUDED
+#define LIBWORLD_NBT_INTERNAL_H_INCLUDED
+
+#include "nbt.h"
+#include "nbt/nbtht.internal.h"
+
+typedef struct {
+ size_t len;
+ nbt_byte_t *data;
+} nbt__byte_array_t;
+
+typedef struct {
+ size_t len;
+ nbt_int_t *data;
+} nbt__int_array_t;
+
+typedef struct {
+ size_t len;
+ nbt_long_t *data;
+} nbt__long_array_t;
+
+typedef struct {
+ size_t len;
+ char *data;
+} nbt__string_t;
+
+typedef struct nbt__compound_tag {
+ nbt__ht_t *hash;
+} nbt__compound_t;
+
+typedef struct nbt__list_tag {
+ size_t cap, len;
+ nbt_tag_t **ptags;
+} nbt__list_t;
+
+typedef union {
+ nbt_byte_t nbt_byte;
+ nbt_short_t nbt_short;
+ nbt_int_t nbt_int;
+ nbt_long_t nbt_long;
+
+ nbt_float_t nbt_float;
+ nbt_double_t nbt_double;
+
+ nbt__byte_array_t nbt_byte_array;
+
+ nbt__string_t nbt_string;
+
+ nbt__list_t nbt_list;
+ nbt__compound_t nbt_compound;
+
+ nbt__int_array_t nbt_int_array;
+ nbt__long_array_t nbt_long_array;
+} nbt__any_t;
+
+struct nbt__tag_tag {
+ size_t ref;
+ nbt_type_t type;
+ nbt__any_t value;
+};
+
+/* a named tag */
+typedef struct {
+ nbt_tag_t tag;
+
+ size_t name_length;
+ char *name; /* nullable if there is no name (will be written as if the name is empty) */
+} nbt__ntag_t;
+
+#define NBT__CHECK_TYPE(_tag, _t, _r, ...) do { \
+ if (!(_tag) || (_tag)->type != _t) { _r(__VA_ARGS__); } \
+} while (0)
+
+/* functions */
+
+void nbt__tag_free(nbt_tag_t *tag);
+
+#endif /* include guard */
diff --git a/src/nbt/list.c b/src/nbt/list.c
new file mode 100644
index 0000000..df12844
--- /dev/null
+++ b/src/nbt/list.c
@@ -0,0 +1,173 @@
+#include "../nbt.internal.h"
+#include "nbt.h"
+
+#define NBT__RETURN1(_r) return _r
+#define NBT__RETURN0() return
+#define NBT__CHECK(_tag, _ret) NBT__CHECK_TYPE(_tag, NBT_TAG_LIST, NBT__RETURN1, _ret)
+#define NBT__CHECK0(_tag) NBT__CHECK_TYPE(_tag, NBT_TAG_LIST, NBT__RETURN0)
+
+nbt_type_t nbt_list_type(nbt_tag_t *list)
+{
+ NBT__CHECK(list, -1);
+
+ if (list->value.nbt_list.len) {
+ return (*list->value.nbt_list.ptags)->type;
+ }
+
+ return NBT_TAG_END;
+}
+
+nbt_size_t nbt_list_length(nbt_tag_t *list)
+{
+ NBT__CHECK(list, (size_t)-1);
+ return list->value.nbt_list.len;
+}
+
+nbt_tag_t *nbt_list_get(nbt_tag_t *list, nbt_size_t idx)
+{
+ NBT__CHECK(list, NULL);
+
+ if (list->value.nbt_list.len <= idx) return NULL;
+ return list->value.nbt_list.ptags[idx];
+}
+
+#define NBT__APPEND_BODY(_list, _tag, _ex) \
+{ \
+ NBT__CHECK(list, -1); \
+ if (!tag) return -1; \
+ \
+ int ret = nbt_list_reserve_more(list, 1); \
+ if (ret < 0) return ret; \
+ \
+ list->value.nbt_list.ptags[list->value.nbt_list.len++] = _ex(tag); \
+ return 0; \
+}
+
+#define NBT__LEAVE_ALONE(_t) _t
+
+int nbt_list_append(nbt_tag_t *list, nbt_tag_t *tag)
+ NBT__APPEND_BODY(list, tag, nbt_incref)
+
+int nbt_list_append_move(nbt_tag_t *list, nbt_tag_t *tag)
+ NBT__APPEND_BODY(list, tag, NBT__LEAVE_ALONE)
+
+#define NBT__INSERT_BODY(_list, _tag, _at, _ex) \
+{ \
+ NBT__CHECK(list, -1); \
+ if (!tag) return -1; \
+ \
+ int ret = nbt_list_reserve_more(list, 1); \
+ if (ret < 0) return ret; \
+ \
+ memmove(list->value.nbt_list.ptags + _at + 1, \
+ list->value.nbt_list.ptags + _at, \
+ list->value.nbt_list.len - _at); \
+ \
+ list->value.nbt_list.ptags[at] = _ex(tag); \
+ \
+ ++list->value.nbt_list.len; \
+ return 0; \
+}
+
+int nbt_list_insert(nbt_tag_t *list, nbt_tag_t *tag, size_t at)
+ NBT__INSERT_BODY(list, tag, at, nbt_incref)
+
+int nbt_list_insert_move(nbt_tag_t *list, nbt_tag_t *tag, size_t at)
+ NBT__INSERT_BODY(list, tag, at, NBT__LEAVE_ALONE)
+
+int nbt_list_remove(nbt_tag_t *list, nbt_size_t idx)
+{
+ nbt_tag_t *ret = nbt_list_pop(list, idx);
+ if (!ret) return -1;
+
+ nbt_decref(ret);
+ return 0;
+}
+
+nbt_tag_t *nbt_list_pop(nbt_tag_t *list, nbt_size_t idx)
+{
+ NBT__CHECK(list, NULL);
+ if (list->value.nbt_list.len <= idx) return NULL;
+
+ nbt_tag_t *ret = list->value.nbt_list.ptags[idx];
+
+ --list->value.nbt_list.len;
+ memmove(list->value.nbt_list.ptags + idx, list->value.nbt_list.ptags + idx + 1, list->value.nbt_list.len - idx);
+
+ return ret;
+}
+
+int nbt_list_remove_back(nbt_tag_t *list)
+{
+ NBT__CHECK(list, -1);
+ return nbt_list_remove(list, nbt_list_length(list));
+}
+
+nbt_tag_t *nbt_list_pop_back(nbt_tag_t *list)
+{
+ NBT__CHECK(list, NULL);
+ return nbt_list_pop(list, nbt_list_length(list));
+}
+
+void nbt_list_clear(nbt_tag_t *list)
+{
+ NBT__CHECK0(list);
+ for (size_t n = 0; n < list->value.nbt_list.len; ++n) {
+ nbt_decref(list->value.nbt_list.ptags[n]);
+ }
+ list->value.nbt_list.len = 0;
+}
+
+nbt_size_t nbt_list_capacity(nbt_tag_t *list)
+{
+ NBT__CHECK(list, (nbt_size_t)-1);
+ return list->value.nbt_list.cap;
+}
+
+nbt_size_t nbt__list_next_po2(nbt_size_t in) {
+ nbt_size_t n = 1;
+ while (n && n < in) n <<= 1;
+ return n;
+}
+
+int nbt_list_shrink(nbt_tag_t *list)
+{
+ NBT__CHECK(list, -1);
+
+ if (list->value.nbt_list.len == 0) {
+ free(list->value.nbt_list.ptags);
+ list->value.nbt_list.ptags = NULL;
+ /* a future realloc will allocate a new one */
+ return 0;
+ } else if (list->value.nbt_list.len == list->value.nbt_list.cap) return 0;
+
+ nbt_tag_t **ntags = realloc(list->value.nbt_list.ptags, list->value.nbt_list.len);
+ if (!ntags) return -1;
+ list->value.nbt_list.ptags = ntags;
+ list->value.nbt_list.cap = list->value.nbt_list.len;
+
+ return 0;
+}
+
+int nbt_list_reserve(nbt_tag_t *list, nbt_size_t newsz)
+{
+ NBT__CHECK(list, -1);
+ if (newsz <= nbt_list_length(list)) return 0;
+
+ nbt_size_t next = nbt__list_next_po2(newsz);
+ if (!next) return -1;
+ if (list->value.nbt_list.cap >= next) return 0;
+
+ nbt_tag_t **ntags = realloc(list->value.nbt_list.ptags, next);
+ if (!ntags) return -1;
+ list->value.nbt_list.ptags = ntags;
+ list->value.nbt_list.cap = next;
+
+ return 0;
+}
+
+int nbt_list_reserve_more(nbt_tag_t *list, nbt_size_t moresz)
+{
+ NBT__CHECK(list, -1);
+ return nbt_list_reserve(list, nbt_list_length(list) + moresz);
+}
diff --git a/src/nbt/nbt.c b/src/nbt/nbt.c
new file mode 100644
index 0000000..5948bce
--- /dev/null
+++ b/src/nbt/nbt.c
@@ -0,0 +1,207 @@
+#include "../nbt.internal.h"
+#include "nbt.h"
+#include "src/nbt.internal.h"
+
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+nbt_tag_t *nbt__new_val(nbt_type_t type)
+{
+ nbt_tag_t *tag = calloc(1, sizeof(nbt_tag_t));
+ if (!tag) return NULL;
+
+ tag->ref = 1;
+ tag->type = type;
+ return tag;
+}
+
+nbt_tag_t *nbt_byte(nbt_byte_t b)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_BYTE);
+ if (!tag) return NULL;
+
+ tag->value.nbt_byte = b;
+ return tag;
+}
+
+nbt_tag_t *nbt_short(nbt_short_t s)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_SHORT);
+ if (!tag) return NULL;
+
+ tag->value.nbt_short = s;
+ return tag;
+}
+
+nbt_tag_t *nbt_int(nbt_int_t i)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_INT);
+ if (!tag) return NULL;
+
+ tag->value.nbt_int = i;
+ return tag;
+}
+
+nbt_tag_t *nbt_long(nbt_long_t l)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_LONG);
+ if (!tag) return NULL;
+
+ tag->value.nbt_long = l;
+ return tag;
+}
+
+nbt_tag_t *nbt_float(nbt_float_t f)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_FLOAT);
+ if (!tag) return NULL;
+
+ tag->value.nbt_float = f;
+ return tag;
+}
+
+nbt_tag_t *nbt_double(nbt_double_t d)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_DOUBLE);
+ if (!tag) return NULL;
+
+ tag->value.nbt_byte = d;
+ return tag;
+}
+
+#define NBT__BUFCOPY_BODY(_tl, _tu, _te, _inbuf, _inlen) \
+{ \
+ static nbt_tag_t nbt__single_empty_ ## _tl = { \
+ .ref = SIZE_MAX, \
+ .type = NBT_TAG_ ## _tu, \
+ .value = { .nbt_ ## _tl = { .len = 0, .data = NULL } } \
+ }; \
+ \
+ if (!(_inlen)) return &nbt__single_empty_ ## _tl; \
+ \
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_ ## _tu); \
+ if (!tag) return NULL; \
+ \
+ _te *buf = calloc(_inlen, sizeof(_te)); \
+ if (!buf) goto cleanup; \
+ \
+ memcpy(buf, _inbuf, (_inlen) * sizeof(_te)); \
+ tag->value.nbt_ ## _tl.len = _inlen; \
+ tag->value.nbt_ ## _tl.data = buf; \
+ \
+ return tag; \
+ \
+cleanup: \
+ free(tag); \
+ return NULL; \
+}
+
+nbt_tag_t *nbt_byte_array(const nbt_byte_t *bytes, nbt_size_t length)
+ NBT__BUFCOPY_BODY(byte_array, BYTE_ARRAY, nbt_byte_t, bytes, length)
+
+nbt_tag_t *nbt_string(const char *c_str)
+{
+ return nbt_stringn(c_str, strlen(c_str));
+}
+
+nbt_tag_t *nbt_stringn(const char *c_str, nbt_size_t length)
+ NBT__BUFCOPY_BODY(string, STRING, char, c_str, length)
+
+nbt_tag_t *nbt_list(void)
+{
+ return nbt_listn(0);
+}
+
+nbt_tag_t *nbt_listn(nbt_size_t cap)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_LIST);
+ if (!tag) return NULL;
+ if (!cap) cap = 16;
+
+ tag->value.nbt_list.len = 0;
+ tag->value.nbt_list.cap = cap;
+
+ nbt_tag_t **tags = calloc(cap, sizeof(nbt_tag_t *));
+ if (!tags) goto cleanup;
+
+ tag->value.nbt_list.ptags = tags;
+
+ return tag;
+
+cleanup:
+ free(tag);
+ return NULL;
+}
+
+nbt_tag_t *nbt_compound(void)
+{
+ return nbt_compoundn(0);
+}
+
+nbt_tag_t *nbt_compoundn(nbt_size_t cap)
+{
+ nbt_tag_t *tag = nbt__new_val(NBT_TAG_COMPOUND);
+ if (!tag) return NULL;
+
+ tag->value.nbt_compound.hash = nbt__ht_create(cap);
+ if (!tag->value.nbt_compound.hash) goto cleanup;
+
+ return tag;
+
+cleanup:
+ free(tag);
+ return NULL;
+}
+
+nbt_tag_t *nbt_int_array(const nbt_int_t *ints, nbt_size_t length)
+ NBT__BUFCOPY_BODY(int_array, INT_ARRAY, nbt_int_t, ints, length)
+
+nbt_tag_t *nbt_long_array(const nbt_long_t *longs, nbt_size_t length)
+ NBT__BUFCOPY_BODY(long_array, LONG_ARRAY, nbt_long_t, longs, length);
+
+void nbt_decref(nbt_tag_t *tag)
+{
+ if (!tag) return;
+ if (tag->ref == SIZE_MAX) return;
+
+ --tag->ref;
+ if (!tag->ref) nbt__tag_free(tag);
+}
+
+nbt_tag_t *nbt_incref(nbt_tag_t *tag)
+{
+ if (tag->ref != SIZE_MAX) ++tag->ref;
+ return tag;
+}
+
+void nbt__tag_free(nbt_tag_t *tag)
+{
+ if (!tag) return;
+ switch (tag->type) {
+ case NBT_TAG_BYTE_ARRAY:
+ free(tag->value.nbt_byte_array.data);
+ break;
+ case NBT_TAG_STRING:
+ free(tag->value.nbt_string.data);
+ break;
+ case NBT_TAG_INT_ARRAY:
+ free(tag->value.nbt_int_array.data);
+ break;
+ case NBT_TAG_LONG_ARRAY:
+ free(tag->value.nbt_long_array.data);
+ break;
+ case NBT_TAG_LIST:
+ nbt_list_clear(tag);
+ free(tag->value.nbt_list.ptags);
+ break;
+ case NBT_TAG_COMPOUND:
+ nbt_compound_clear(tag);
+ nbt__ht_free(tag->value.nbt_compound.hash);
+ break;
+ default:
+ break;
+ }
+
+ free(tag);
+}
diff --git a/src/nbt/nbtht.c b/src/nbt/nbtht.c
new file mode 100644
index 0000000..437a603
--- /dev/null
+++ b/src/nbt/nbtht.c
@@ -0,0 +1,2 @@
+#define HT_IMPLEMENTATIONS
+#include "nbtht.internal.h"
diff --git a/src/nbt/nbtht.internal.h b/src/nbt/nbtht.internal.h
new file mode 100644
index 0000000..f3bc14b
--- /dev/null
+++ b/src/nbt/nbtht.internal.h
@@ -0,0 +1,44 @@
+#ifndef LIBWORLD_NBTHT_INTERNAL_H_INCLUDED
+#define LIBWORLD_NBTHT_INTERNAL_H_INCLUDED
+
+#include "nbt.h"
+#include <string.h>
+#include <stdlib.h>
+
+#include <xxh3.h>
+
+#define HT_PREFIX nbt__ht_
+
+#define HT_VALTYPE nbt_tag_t *
+#define HT_VALTYPE_CREF nbt_tag_t *
+
+#define HT_VAL_SENTINEL NULL
+#define HT_VAL_COPY(_v, _sz) nbt_incref(_v)
+#define HT_VAL_FREE(_v) nbt_decref(_v)
+#define HT_VAL_STATIC_LEN sizeof(nbt_tag_t *)
+
+inline void *nbt__ht_memdup(const void *in, size_t sz)
+{
+ void *ret = malloc(sz);
+ if (!ret) return NULL;
+ memcpy(ret, in, sz);
+ return ret;
+}
+
+#define HT_KEYTYPE char *
+#define HT_KEYTYPE_CREF const char *
+#define HT_KEY_EQ(_val1, _len1, _val2, _len2) (((_len1) == (_len2)) && !memcmp(_val1, _val2, _len1))
+#define HT_KEY_GUESS_LEN(_k) strlen(_k)
+#define HT_KEY_FREE(_k) free(_k)
+#define HT_KEY_COPY(_k, _klen) nbt__ht_memdup(_k, _klen)
+
+#define HT_KEY_FMT "%s"
+#define HT_VAL_FMT "%p"
+
+#define HT_HASHTYPE XXH64_hash_t
+#define HT_KEY_HASH(_val, _sz) XXH3_64bits(_val, _sz)
+#define HT_LOADFACTOR 0.75f
+
+#include "../htgen.h"
+
+#endif /* include guard */