summaryrefslogtreecommitdiff
path: root/nbt
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2023-12-17 11:10:04 -0500
committerFreya Murphy <freya@freyacat.org>2023-12-17 11:10:04 -0500
commite0eacfa9773c83850ed5169d1e889ff845180581 (patch)
tree9f8bb433404ce7e4dda1b86ca4dd5d5a2fba10e9 /nbt
parentsnbt (diff)
downloadnbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.tar.gz
nbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.tar.bz2
nbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.zip
refactorHEADmain
Diffstat (limited to 'nbt')
-rw-r--r--nbt/json/print.c177
-rw-r--r--nbt/json/read.c632
-rw-r--r--nbt/nbt.c65
-rw-r--r--nbt/nbt.h84
-rw-r--r--nbt/nbt/print.c127
-rw-r--r--nbt/nbt/read.c207
-rw-r--r--nbt/snbt/print.c211
-rw-r--r--nbt/snbt/read.c740
8 files changed, 2243 insertions, 0 deletions
diff --git a/nbt/json/print.c b/nbt/json/print.c
new file mode 100644
index 0000000..3b7cdb3
--- /dev/null
+++ b/nbt/json/print.c
@@ -0,0 +1,177 @@
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "nbt.h"
+
+static char buf[1024];
+
+__attribute__((format(printf, 3, 4)))
+static bool printi(const stream_t *stream, int depth, const char *format, ...) {
+ for (int i = 0; i < depth; i++)
+ if (stream_write(stream, "\t", 1) == false)
+ return false;
+ va_list list;
+ va_start(list, format);
+ int len;
+ if ((len = vsnprintf(buf, 1024, format, list)) < 0)
+ return false;
+ if (stream_write(stream, buf, len) == false)
+ return false;
+ return true;
+}
+
+static bool json_print_impl(const tag_t *tag, const stream_t *stream, int depth);
+
+static bool json_print_byte_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[") == false)
+ return false;
+ for (int32_t i = 0; i < data->b_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%hhd", data->b_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool json_print_int_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[") == false)
+ return false;
+ for (int32_t i = 0; i < data->i_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%d", data->i_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool json_print_long_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[") == false)
+ return false;
+ for (int32_t i = 0; i < data->l_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%ld", data->l_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool json_print_string(const tagdata_t *data, const stream_t *stream) {
+ if (data->string.size > 0) {
+ if (printi(stream, 0, "\"%.*s\"", data->string.size, data->string.data) == false)
+ return false;
+ } else {
+ if (printi(stream, 0, "\"\"") == false)
+ return false;
+ }
+ return true;
+}
+
+static bool json_print_compound(const tagdata_t *data, const stream_t *stream, int depth) {
+ if (printi(stream, 0, "{\n") == false)
+ return false;
+ bool first = true;
+ for (uint32_t i = 0; i < data->compound.capacity; i++) {
+ if (data->compound.entries[i].name == NULL)
+ continue;
+ if (!first && printi(stream, 0, ",\n") == false)
+ return false;
+ first = false;
+ if (json_print_impl(&data->compound.entries[i], stream, depth + 1) == false)
+ return false;
+ }
+ if (printi(stream, 0, "\n") == false || printi(stream, depth, "}") == false)
+ return false;
+ return true;
+}
+
+static bool json_print_list(const tagdata_t *data, const stream_t *stream, int depth) {
+ if (printi(stream, 0, "[\n") == false)
+ return false;
+ for (int32_t i = 0; i < data->list.size; i++) {
+ if (i != 0 && printi(stream, 0, ",\n") == false)
+ return false;
+ if (json_print_impl(&data->list.tags[i], stream, depth + 1) == false)
+ return false;
+ }
+ if (printi(stream, 0, "\n") == false || printi(stream, depth, "]") == false)
+ return false;
+ return true;
+}
+
+static bool json_print_data(const tag_t *tag, const stream_t *stream, int depth) {
+
+ bool ok = true;
+
+ switch (tag->type) {
+ case TAG_BYTE:
+ ok = printi(stream, 0, "%hhd", tag->data.b);
+ break;
+ case TAG_SHORT:
+ ok = printi(stream, 0, "%hd", tag->data.s);
+ break;
+ case TAG_INT:
+ ok = printi(stream, 0, "%d", tag->data.i);
+ break;
+ case TAG_LONG:
+ ok = printi(stream, 0, "%ld", tag->data.l);
+ break;
+ case TAG_FLOAT:
+ ok = printi(stream, 0, "%.9g", tag->data.f);
+ break;
+ case TAG_DOUBLE:
+ ok = printi(stream, 0, "%.17g", tag->data.d);
+ break;
+ case TAG_BYTE_ARRAY:
+ ok = json_print_byte_array(&tag->data, stream);
+ break;
+ case TAG_STRING:
+ ok = json_print_string(&tag->data, stream);
+ break;
+ case TAG_LIST:
+ ok = json_print_list(&tag->data, stream, depth);
+ break;
+ case TAG_COMPOUND:
+ ok = json_print_compound(&tag->data, stream, depth);
+ break;
+ case TAG_INT_ARRAY:
+ ok = json_print_int_array(&tag->data, stream);
+ break;
+ case TAG_LONG_ARRAY:
+ ok = json_print_long_array(&tag->data, stream);
+ break;
+ case TAG_END:
+ break;
+ }
+
+ return ok;
+}
+
+static bool json_print_impl(const tag_t *tag, const stream_t *stream, int depth) {
+ if (tag->name_len > 0) {
+ printi(stream, depth, "\"%.*s\": ", tag->name_len, tag->name);
+ } else {
+ for (int i = 0; i < depth; i++)
+ printi(stream, 0, "\t");
+ }
+ return json_print_data(tag, stream, depth);
+}
+
+bool json_print(const tag_t *tag, const stream_t *stream) {
+ if (json_print_impl(tag, stream, 0) == false)
+ return false;
+ if (stream_write(stream, "\n", 1) == false)
+ return false;
+ return true;
+}
diff --git a/nbt/json/read.c b/nbt/json/read.c
new file mode 100644
index 0000000..3316b55
--- /dev/null
+++ b/nbt/json/read.c
@@ -0,0 +1,632 @@
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nbt.h"
+#include "lib.h"
+
+static char ret = '\0';
+
+typedef enum {
+ TOK_LBRACE,
+ TOK_RBRACE,
+ TOK_LBRACK,
+ TOK_RBRACK,
+ TOK_COLON,
+ TOK_COMMA,
+ TOK_STRING,
+ TOK_NUMBER,
+ TOK_DOUBLE,
+ TOK_BOOL,
+ TOK_NULL
+} tokentype_t ;
+
+typedef union {
+ bool b;
+ int64_t number;
+ double decimal;
+ struct {
+ uint16_t len;
+ char *data;
+ } string;
+} tokendata_t;
+
+typedef struct {
+ tokentype_t type;
+ tokendata_t data;
+} token_t;
+
+static void json_token_free(token_t *token) {
+ if (token->type == TOK_STRING)
+ free(token->data.string.data);
+}
+
+static bool json_parse_unicode(char buf[4], int *read, const stream_t *stream) {
+ char temp[5];
+ temp[4] = '\0';
+
+ if (stream_read(stream, temp, 4) == false)
+ return false;
+
+ uint16_t code_point;
+ char *end = NULL;
+
+ code_point = strtol(temp, &end, 16);
+
+ if (end != NULL)
+ return false;
+
+ int lead1 = 0b00000000;
+ int lead2 = 0b11000000;
+ int lead3 = 0b11100000;
+ int cont = 0b10000000;
+ int contmask = 0b00111111;
+
+ if (code_point < 0x0080) {
+ buf[0] = ((code_point >> 0)) | lead1;
+ *read = 1;
+ } else if (code_point < 0x0800) {
+ buf[0] = ((code_point >> 6)) | lead2;
+ buf[1] = ((code_point >> 0) & contmask) | cont;
+ *read = 2;
+ } else {
+ buf[0] = ((code_point >> 12)) | lead3;
+ buf[1] = ((code_point >> 6) & contmask) | cont;
+ buf[2] = ((code_point >> 0) & contmask) | cont;
+ *read = 3;
+ }
+
+ return true;
+}
+
+static bool json_parse_escape(char buf[4], int *read, const stream_t *stream) {
+ char n;
+
+ char *c = &buf[0];
+ *read = 1;
+
+ if (stream_read(stream, &n, 1) == false)
+ return false;
+
+ switch (n) {
+ case '"':
+ case '\\':
+ case '/':
+ *c = n;
+ return true;
+ case 'b':
+ *c = '\b';
+ return true;
+ case 'f':
+ *c = '\f';
+ return true;
+ case 'n':
+ *c = '\n';
+ return true;
+ case 'r':
+ *c = '\r';
+ return true;
+ case 't':
+ *c = '\t';
+ return true;
+ case 'u': {
+ return json_parse_unicode(buf, read, stream);
+ default:
+ // invalid escape
+ return false;
+ };
+ }
+}
+
+static bool json_parse_string(tokendata_t *token, const stream_t *stream) {
+
+ int capacity = 8;
+ int len = 0;
+ char *buf = xalloc(capacity * sizeof(char));
+
+ while (1) {
+
+ char tmp[4];
+ int read = 1;
+
+ if (stream_read(stream, tmp, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ uint8_t c = tmp[0];
+
+ if (c == '"')
+ break;
+
+ // non printable ascii character
+ if (c < 32 || c > 127) {
+ free(buf);
+ return false;
+ }
+ // an escape, dont push to buffer, get next char
+ if (c == '\\' && json_parse_escape(tmp, &read, stream) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (len + read >= capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity);
+ }
+
+ memcpy(buf + len, tmp, read);
+ len += read;
+
+ }
+
+ token->string.data = xalloc(len * sizeof(char));
+ token->string.len = len;
+ memcpy(token->string.data, buf, len);
+ free(buf);
+
+ return true;
+}
+
+static bool json_ident_assert(const stream_t *stream, const char *rest) {
+ char c;
+ if (stream_read(stream, &c, 1) == false)
+ return false;
+ if (c != *rest)
+ return false;
+ rest += 1;
+ if (*rest == '\0')
+ return true;
+ else
+ return json_ident_assert(stream, rest);
+}
+
+static bool json_parse_ident(token_t *token, const stream_t *stream, char first) {
+ if (first == 't' && json_ident_assert(stream, "true")) {
+ token->type = TOK_BOOL;
+ token->data.b = true;
+ } else if (first == 'f' && json_ident_assert(stream, "alse")) {
+ token->type = TOK_BOOL;
+ token->data.b = false;
+ } else if (first == 'n' && json_ident_assert(stream, "ull")) {
+ token->type = TOK_NULL;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static void push_char(char **buf, int *len, int *cap, char c) {
+ if (*len == *cap) {
+ *cap *= *cap * 2;
+ *buf = xrealloc(*buf, *cap * sizeof(char));
+ }
+ (*buf)[(*len)++] = c;
+}
+
+static bool json_parse_number(token_t *token, const stream_t *stream, char first) {
+
+ int capacity = 8;
+ int len = 0;
+ char *buf = xalloc(capacity * sizeof(char));
+ bool isdec = false;
+ bool isneg = false;
+
+ char c = first;
+
+ // PARSE DIGITS AND NEGATIVITY
+
+ while (1) {
+ if (c == '\0' && stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (c == '-' && isneg) {
+ // cannot negate twice
+ free(buf);
+ return false;
+ } else if (c == '-') {
+ isneg = true;
+ c = '\0';
+ } else if (c == '0' && len == 0) {
+ // string starting with 0 cannot not have other digits
+ push_char(&buf, &len, &capacity, c);
+ c = '\0';
+ break;
+ } else if (c >= '0' && c <= '9') {
+ push_char(&buf, &len, &capacity, c);
+ c = '\0';
+ } else if (len == 0) {
+ // invalid start of digits
+ free(buf);
+ return false;
+ } else {
+ // end of starting digits
+ break;
+ }
+ }
+
+ // SET NEXT CHAR C IF NOT READ YET
+
+ if (c == '\0' && stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ // THERE IS A DECIMAL
+ // READ STREAM OF DIGITS
+
+ if (c == '.') {
+ isdec = true;
+ push_char(&buf, &len, &capacity, c);
+ int declen = 0;
+ while (1) {
+ if (stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+ if (c >= '0' && c <= '9') {
+ push_char(&buf, &len, &capacity, c);
+ declen++;
+ } else if (declen == 0) {
+ // invalid decimal
+ free(buf);
+ return false;
+ } else {
+ // end of decimal
+ break;
+ }
+ }
+ }
+
+ // PARSE EXPONENT
+ if (c == 'e' || c == 'E') {
+ isdec = true;
+ push_char(&buf, &len, &capacity, 'E');
+
+ int explen = 0; // the exponent len
+
+ if (stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (c == '+' || c == '-') {
+ push_char(&buf, &len, &capacity, c);
+ c = '\0';
+ }
+
+ while (1) {
+ if (c == '\0' && stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (c >= '0' && c <= '9') {
+ push_char(&buf, &len, &capacity, c);
+ explen++;
+ c = '\0';
+ } else if (explen == 0) {
+ // invalid exponent
+ free(buf);
+ return false;
+ } else {
+ break;
+ }
+ }
+
+ }
+
+ char *end = NULL;
+ push_char(&buf, &len, &capacity, '\0');
+
+ if (isdec) {
+ token->type = TOK_DOUBLE;
+ token->data.decimal = strtod(buf, &end);
+ } else {
+ token->type = TOK_NUMBER;
+ token->data.number = strtol(buf, &end, 10);
+ }
+
+ if (end != NULL && *end != 0)
+ return false;
+
+ free(buf);
+
+ ret = c;
+ return true;
+}
+
+static bool json_next_token(token_t *token, const stream_t *stream) {
+
+ memset(token, 0, sizeof(token_t));
+
+ char c;
+
+retry:
+
+ if (ret != '\0') {
+ c = ret;
+ ret = '\0';
+ } else if (stream_read(stream, &c, 1) == false) {
+ return false;
+ }
+
+ bool ok = true;
+
+ switch (c) {
+ case '{':
+ token->type = TOK_LBRACE;
+ break;
+ case '}':
+ token->type = TOK_RBRACE;
+ break;
+ case '[':
+ token->type = TOK_LBRACK;
+ break;
+ case ']':
+ token->type = TOK_RBRACK;
+ break;
+ case ':':
+ token->type = TOK_COLON;
+ break;
+ case ',':
+ token->type = TOK_COMMA;
+ break;
+ case '"':
+ token->type = TOK_STRING;
+ ok = json_parse_string(&token->data, stream);
+ break;
+ case 't':
+ case 'f':
+ case 'n':
+ // parse null or bool
+ ok = json_parse_ident(token, stream, c);
+ break;
+ case ' ':
+ case '\n':
+ case '\t':
+ case '\r':
+ goto retry;
+ default:
+ if (isdigit(c) || c == '-') {
+ // parse number
+ ok = json_parse_number(token, stream, c);
+ } else {
+ // disallowed symbol
+ ok = false;
+ }
+ break;
+ }
+
+ return ok;
+}
+
+static bool json_get_list_type(tagtype_t *type, const tag_t *tags, int len) {
+ if (len < 1) {
+ *type = TAG_END;
+ return true;
+ }
+
+ *type = tags[0].type;
+
+ for (int i = 0; i < len; i++)
+ if (tags[i].type != *type)
+ return false;
+
+ return true;
+}
+
+static bool json_read_value(tag_t *tag, const stream_t *stream, token_t *first);
+
+static bool json_read_list(tagdata_t *data, const stream_t *stream) {
+
+ token_t next = {0};
+ if (json_next_token(&next, stream) == false) {
+ json_token_free(&next);
+ return false;
+ }
+
+ token_t *ret = &next;
+
+ if (next.type == TOK_RBRACK) {
+ data->list.tags = NULL;
+ data->list.size = 0;
+ data->list.type = TAG_END;
+ return true;
+ }
+
+ int capacity = 8;
+ int len = 0;
+ tag_t *tags = xalloc(capacity * sizeof(tag_t));
+
+ while (1) {
+
+ tag_t value;
+ value.name = "";
+ value.name_len = 0;
+
+ if (json_read_value(&value, stream, ret) == false) {
+ free(tags);
+ return false;
+ }
+
+ ret = NULL;
+
+ if (len == capacity) {
+ capacity *= 2;
+ tags = xrealloc(tags, capacity * sizeof(tag_t));
+ }
+
+ tags[len++] = value;
+
+ if (json_next_token(&next, stream) == false) {
+ free(tags);
+ json_token_free(&next);
+ return false;
+ }
+
+ if (next.type == TOK_COMMA) {
+ continue;
+ } else if (next.type == TOK_RBRACK) {
+ break;
+ } else {
+ free(tags);
+ json_token_free(&next);
+ return false;
+ }
+
+ }
+
+ tagtype_t type;
+ if (json_get_list_type(&type, tags, len) == false) {
+ free(tags);
+ return false;
+ }
+
+ data->list.type = type;
+ data->list.size = len;
+ data->list.tags = xalloc(len * sizeof(tag_t));
+ memcpy(data->list.tags, tags, len * sizeof(tag_t));
+ free(tags);
+
+ return true;
+
+}
+
+static bool json_read_compound(tagdata_t *data, const stream_t *stream) {
+
+ map_t map;
+ map_init(&map);
+
+ token_t next = {0};
+ if (json_next_token(&next, stream) == false) {
+ json_token_free(&next);
+ return false;
+ }
+
+ if (next.type == TOK_RBRACE) {
+ data->compound = map;
+ return true;
+ }
+
+ while (1) {
+
+ if (next.type != TOK_STRING) {
+ map_free(&map);
+ json_token_free(&next);
+ return false;
+ }
+
+ char *name = next.data.string.data;
+ int name_len = next.data.string.len;
+
+ if (name_len < 1) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ if (json_next_token(&next, stream) == false || next.type != TOK_COLON) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ tag_t value;
+ if (json_read_value(&value, stream, NULL) == false) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ value.name = name;
+ value.name_len = name_len;
+
+ map_put(&map, &value);
+
+ if (json_next_token(&next, stream) == false) {
+ map_free(&map);
+ json_token_free(&next);
+ return false;
+ }
+
+ if (next.type == TOK_COMMA) {
+ if (json_next_token(&next, stream) == false) {
+ map_free(&map);
+ return false;
+ }
+ continue;
+ } else if (next.type == TOK_RBRACE) {
+ break;
+ } else {
+ map_free(&map);
+ json_token_free(&next);
+ return false;
+ }
+
+ }
+
+ data->compound = map;
+
+ return true;
+}
+
+static bool json_read_value(tag_t *tag, const stream_t *stream, token_t *first) {
+
+ token_t token;
+
+ if (first != NULL)
+ token = *first;
+ else if (json_next_token(&token, stream) == false)
+ return false;
+
+ tag->name = "";
+ tag->name_len = 0;
+
+ bool ok = true;
+
+ switch (token.type) {
+ case TOK_RBRACK:
+ case TOK_RBRACE:
+ case TOK_COLON:
+ case TOK_COMMA:
+ case TOK_NULL:
+ ok = false;
+ break;
+ case TOK_LBRACK:
+ tag->type = TAG_LIST;
+ ok = json_read_list(&tag->data, stream);
+ break;
+ case TOK_LBRACE:
+ tag->type = TAG_COMPOUND;
+ ok = json_read_compound(&tag->data, stream);
+ break;
+ case TOK_STRING:
+ tag->type = TAG_STRING;
+ tag->data.string.data = token.data.string.data;
+ tag->data.string.size = token.data.string.len;
+ break;
+ case TOK_NUMBER:
+ tag->type = TAG_LONG;
+ tag->data.l = token.data.number;
+ break;
+ case TOK_DOUBLE:
+ tag->type = TAG_DOUBLE;
+ tag->data.d = token.data.decimal;
+ break;
+ case TOK_BOOL:
+ tag->type = TAG_BYTE;
+ tag->data.b = token.data.b ? 1 : 0;
+ break;
+ }
+
+ return ok;
+}
+
+bool json_read(tag_t *tag, const stream_t *stream) {
+ return json_read_value(tag, stream, NULL);
+}
diff --git a/nbt/nbt.c b/nbt/nbt.c
new file mode 100644
index 0000000..a2a21d5
--- /dev/null
+++ b/nbt/nbt.c
@@ -0,0 +1,65 @@
+#include "nbt.h"
+#include "map.h"
+
+#include <stdlib.h>
+
+void tag_free(tag_t *tag) {
+ if (tag->name_len > 0 && tag->name != NULL)
+ free(tag->name);
+ switch(tag->type) {
+ case TAG_END:
+ case TAG_BYTE:
+ case TAG_SHORT:
+ case TAG_INT:
+ case TAG_LONG:
+ case TAG_FLOAT:
+ case TAG_DOUBLE:
+ break;
+ case TAG_BYTE_ARRAY:
+ free(tag->data.b_arr.data);
+ break;
+ case TAG_STRING:
+ free(tag->data.string.data);
+ break;
+ case TAG_LIST:
+ for (int32_t i = 0; i < tag->data.list.size; i++)
+ tag_free(&tag->data.list.tags[i]);
+ free(tag->data.list.tags);
+ break;
+ case TAG_COMPOUND:
+ map_free(&tag->data.compound);
+ break;
+ case TAG_INT_ARRAY:
+ free(tag->data.i_arr.data);
+ break;
+ case TAG_LONG_ARRAY:
+ free(tag->data.l_arr.data);
+ break;
+ }
+}
+
+bool tag_read(tag_t *tag, const stream_t *stream, format_t format) {
+ switch (format) {
+ case JSON:
+ return json_read(tag, stream);
+ case NBT:
+ return nbt_read(tag, stream);
+ case SNBT:
+ return snbt_read(tag, stream);
+ default:
+ return false;
+ }
+}
+
+bool tag_print(tag_t *tag, const stream_t *stream, format_t format) {
+ switch (format) {
+ case JSON:
+ return json_print(tag, stream);
+ case NBT:
+ return nbt_print(tag, stream);
+ case SNBT:
+ return snbt_print(tag, stream);
+ default:
+ return false;
+ }
+}
diff --git a/nbt/nbt.h b/nbt/nbt.h
new file mode 100644
index 0000000..97c4d3b
--- /dev/null
+++ b/nbt/nbt.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "map.h"
+#include "stream.h"
+
+typedef enum: int8_t {
+ TAG_END = 0,
+ TAG_BYTE = 1,
+ TAG_SHORT = 2,
+ TAG_INT = 3,
+ TAG_LONG = 4,
+ TAG_FLOAT = 5,
+ TAG_DOUBLE = 6,
+ TAG_BYTE_ARRAY = 7,
+ TAG_STRING = 8,
+ TAG_LIST = 9,
+ TAG_COMPOUND = 10,
+ TAG_INT_ARRAY = 11,
+ TAG_LONG_ARRAY = 12
+} tagtype_t ;
+
+typedef union {
+ int8_t b;
+ int16_t s;
+ int32_t i;
+ int64_t l;
+ float f;
+ double d;
+ struct {
+ int32_t size;
+ int8_t *data;
+ } b_arr;
+ struct {
+ uint16_t size;
+ char *data;
+ } string;
+ struct {
+ tagtype_t type;
+ int32_t size;
+ struct tag_t *tags;
+ } list;
+ map_t compound;
+ struct {
+ int32_t size;
+ int32_t *data;
+ } i_arr;
+ struct {
+ int32_t size;
+ int64_t *data;
+ } l_arr;
+} tagdata_t;
+
+typedef struct tag_t {
+ tagtype_t type;
+ tagdata_t data;
+ uint16_t name_len;
+ char *name;
+} tag_t;
+
+typedef enum {
+ JSON,
+ NBT,
+ SNBT
+} format_t;
+
+void tag_free(tag_t *tag);
+
+bool tag_read(tag_t *tag, const stream_t *stream, format_t format);
+bool tag_print(tag_t *tag, const stream_t *stream, format_t format);
+
+bool json_read(tag_t *tag, const stream_t *stream);
+bool json_print(const tag_t *tag, const stream_t *stream);
+
+bool nbt_read(tag_t *tag, const stream_t *stream);
+bool nbt_print(const tag_t *tag, const stream_t *stream);
+
+bool snbt_read(tag_t *tag, const stream_t *stream);
+bool snbt_print(const tag_t *tag, const stream_t *stream);
+bool snbt_allowed_ident(char c);
diff --git a/nbt/nbt/print.c b/nbt/nbt/print.c
new file mode 100644
index 0000000..3f687dc
--- /dev/null
+++ b/nbt/nbt/print.c
@@ -0,0 +1,127 @@
+#include "nbt.h"
+
+static bool nbt_print_header(const tag_t *tag, const stream_t *stream, bool named) {
+ if (stream_write_i8(stream, tag->type) == false)
+ return false;
+ if (!named)
+ return true;
+ if (stream_write_u16(stream, tag->name_len) == false)
+ return false;
+ if (tag->name_len > 0)
+ if (stream_write(stream, tag->name, tag->name_len) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_byte_array(const tagdata_t *data, const stream_t *stream) {
+ if (stream_write_i32(stream, data->b_arr.size) == false)
+ return false;
+ for (int32_t i = 0; i < data->b_arr.size; i++)
+ if (stream_write_i8(stream, data->b_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_int_array(const tagdata_t *data, const stream_t *stream) {
+ if (stream_write_i32(stream, data->i_arr.size) == false)
+ return false;
+ for (int32_t i = 0; i < data->i_arr.size; i++)
+ if (stream_write_i32(stream, data->i_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_long_array(const tagdata_t *data, const stream_t *stream) {
+ if (stream_write_i32(stream, data->l_arr.size) == false)
+ return false;
+ for (int32_t i = 0; i < data->l_arr.size; i++)
+ if (stream_write_i64(stream, data->l_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_string(const tagdata_t *data, const stream_t *stream) {
+ if (stream_write_u16(stream, data->string.size) == false)
+ return false;
+ if (data->string.size < 1)
+ return true;
+ if (stream_write(stream, data->string.data, data->string.size) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_data(const tag_t *tag, const stream_t *stream);
+
+static bool nbt_print_list(const tagdata_t *data, const stream_t *stream) {
+ if (stream_write_i8(stream, data->list.type) == false)
+ return false;
+ if (stream_write_i32(stream, data->list.size) == false)
+ return false;
+ for (int32_t i = 0; i < data->list.size; i++)
+ if (nbt_print_data(&data->list.tags[i], stream) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_compound(const tagdata_t *data, const stream_t *stream) {
+ for (uint32_t i = 0; i < data->compound.capacity; i++) {
+ if (data->compound.entries[i].name == NULL) continue;
+ if (nbt_print(&data->compound.entries[i], stream) == false)
+ return false;
+ }
+ if (stream_write_i8(stream, TAG_END) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_print_data(const tag_t *tag, const stream_t *stream) {
+ bool ok = true;
+
+ switch (tag->type) {
+ case TAG_END:
+ // tag end has no data
+ break;
+ case TAG_BYTE:
+ ok = stream_write_i8(stream, tag->data.b);
+ break;
+ case TAG_SHORT:
+ ok = stream_write_i16(stream, tag->data.s);
+ break;
+ case TAG_INT:
+ case TAG_FLOAT:
+ ok = stream_write_i32(stream, tag->data.i);
+ break;
+ case TAG_LONG:
+ case TAG_DOUBLE:
+ ok = stream_write_i64(stream, tag->data.l);
+ break;
+ case TAG_BYTE_ARRAY:
+ ok = nbt_print_byte_array(&tag->data, stream);
+ break;
+ case TAG_STRING:
+ ok = nbt_print_string(&tag->data, stream);
+ break;
+ case TAG_LIST:
+ ok = nbt_print_list(&tag->data, stream);
+ break;
+ case TAG_COMPOUND:
+ ok = nbt_print_compound(&tag->data, stream);
+ break;
+ case TAG_INT_ARRAY:
+ ok = nbt_print_int_array(&tag->data, stream);
+ break;
+ case TAG_LONG_ARRAY:
+ ok = nbt_print_long_array(&tag->data, stream);
+ break;
+ }
+
+ return ok;
+}
+
+bool nbt_print(const tag_t *tag, const stream_t *stream) {
+ if (nbt_print_header(tag, stream, true) == false)
+ return false;
+ if (nbt_print_data(tag, stream) == false)
+ return false;
+ return true;
+}
diff --git a/nbt/nbt/read.c b/nbt/nbt/read.c
new file mode 100644
index 0000000..9ed4f08
--- /dev/null
+++ b/nbt/nbt/read.c
@@ -0,0 +1,207 @@
+#include "nbt.h"
+#include "lib.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool nbt_read_data(tag_t *tag, const stream_t *stream);
+
+static bool nbt_read_header(tag_t *tag, const stream_t *stream, bool named) {
+ bool ok = true;
+
+ if (stream_read_i8(stream, &tag->type) == false)
+ return false;
+
+ if (tag->type == TAG_END)
+ named = false;
+
+ if (named) {
+ if (stream_read_u16(stream, &tag->name_len) == false)
+ return false;
+ } else {
+ tag->name_len = 0;
+ }
+
+ if (tag->name_len < 1) {
+ tag->name = "";
+ } else {
+ tag->name = xalloc(tag->name_len);
+ ok = stream_read(stream, tag->name, tag->name_len);
+ }
+
+ if (!ok)
+ return false;
+
+ return true;
+}
+
+
+static bool nbt_read_byte_array(tagdata_t *data, const stream_t *stream) {
+ if (stream_read_i32(stream, &data->b_arr.size) == false)
+ return false;
+ if (data->b_arr.size == 0) {
+ data->b_arr.data = NULL;
+ return true;
+ }
+ data->b_arr.data = xalloc(data->b_arr.size * sizeof(int8_t));
+ for (int32_t i = 0; i < data->b_arr.size; i++)
+ if (stream_read_i8(stream, &data->b_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_read_int_array(tagdata_t *data, const stream_t *stream) {
+ if (stream_read_i32(stream, &data->i_arr.size) == false)
+ return false;
+ if (data->i_arr.size == 0) {
+ data->i_arr.data = NULL;
+ return true;
+ }
+ data->i_arr.data = xalloc(data->i_arr.size * sizeof(int32_t));
+ for (int32_t i = 0; i < data->i_arr.size; i++)
+ if (stream_read_i32(stream, &data->i_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_read_long_array(tagdata_t *data, const stream_t *stream) {
+ if (stream_read_i32(stream, &data->l_arr.size) == false)
+ return false;
+ if (data->l_arr.size == 0) {
+ data->l_arr.data = NULL;
+ return true;
+ }
+ data->l_arr.data = xalloc(data->l_arr.size * sizeof(int64_t));
+ for (int32_t i = 0; i < data->l_arr.size; i++)
+ if (stream_read_i64(stream, &data->l_arr.data[i]) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_read_string(tagdata_t *data, const stream_t *stream) {
+ if (stream_read_u16(stream, &data->string.size) == false)
+ return false;
+ if (data->string.size < 1) {
+ data->string.data = NULL;
+ return true;
+ }
+ data->string.data = xalloc(data->string.size);
+ if (stream_read(stream, data->string.data, data->string.size) == false)
+ return false;
+ return true;
+}
+
+static bool nbt_read_list(tagdata_t *data, const stream_t *stream) {
+ if (stream_read_i8(stream, &data->list.type) == false)
+ return false;
+ if (stream_read_i32(stream, &data->list.size) == false)
+ return false;
+ if (data->list.size <= 0) {
+ data->list.tags = NULL;
+ return true;
+ } else if (data->list.type == 0) {
+ // tag end is not allowed to be used with non empty list
+ return false;
+ }
+ data->list.tags = xalloc(data->list.size * sizeof(tag_t));
+ for (int32_t i = 0; i < data->list.size; i++) {
+ tag_t tag;
+ tag.type = data->list.type;
+ tag.name = "";
+ tag.name_len = 0;
+ if (nbt_read_data(&tag, stream) == false)
+ return false;
+ data->list.tags[i] = tag;
+ }
+ return true;
+}
+
+static bool nbt_read_compound(tagdata_t *data, const stream_t *stream) {
+
+ map_t map;
+ map_init(&map);
+
+ while (1) {
+
+ tag_t tag;
+
+ if (nbt_read_header(&tag, stream, true) == false) {
+ map_free(&map);
+ return false;
+ }
+
+ if (tag.type == TAG_END)
+ break;
+
+ if (tag.name_len < 1) {
+ map_free(&map);
+ return false;
+ }
+
+ if (nbt_read_data(&tag, stream) == false) {
+ map_free(&map);
+ return false;
+ }
+
+ map_put(&map, &tag);
+ }
+
+ data->compound = map;
+
+ return true;
+}
+
+static bool nbt_read_data(tag_t *tag, const stream_t *stream) {
+ bool ok = true;
+
+ switch (tag->type) {
+ case TAG_END:
+ // tag has no data
+ break;
+ case TAG_BYTE:
+ ok = stream_read_i8(stream, &tag->data.b);
+ break;
+ case TAG_SHORT:
+ ok = stream_read_i16(stream, &tag->data.s);
+ break;
+ case TAG_FLOAT:
+ case TAG_INT:
+ ok = stream_read_i32(stream, &tag->data.i);
+ break;
+ case TAG_DOUBLE:
+ case TAG_LONG:
+ ok = stream_read_i64(stream, &tag->data.l);
+ break;
+ case TAG_BYTE_ARRAY:
+ ok = nbt_read_byte_array(&tag->data, stream);
+ break;
+ case TAG_STRING:
+ ok = nbt_read_string(&tag->data, stream);
+ break;
+ case TAG_LIST:
+ ok = nbt_read_list(&tag->data, stream);
+ break;
+ case TAG_COMPOUND:
+ ok = nbt_read_compound(&tag->data, stream);
+ break;
+ case TAG_INT_ARRAY:
+ ok = nbt_read_int_array(&tag->data, stream);
+ break;
+ case TAG_LONG_ARRAY:
+ ok = nbt_read_long_array(&tag->data, stream);
+ break;
+ break;
+ };
+ return ok;
+}
+
+bool nbt_read(tag_t *tag, const stream_t *stream) {
+ memset(tag, 0, sizeof(tag_t));
+ if (nbt_read_header(tag, stream, true) == false)
+ return false;
+ if (nbt_read_data(tag, stream) == false)
+ return false;
+ return true;
+}
+
diff --git a/nbt/snbt/print.c b/nbt/snbt/print.c
new file mode 100644
index 0000000..c62cc0a
--- /dev/null
+++ b/nbt/snbt/print.c
@@ -0,0 +1,211 @@
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "nbt.h"
+
+static char buf[1024];
+
+__attribute__((format(printf, 3, 4)))
+static bool printi(const stream_t *stream, int depth, const char *format, ...) {
+ for (int i = 0; i < depth; i++)
+ if (stream_write(stream, "\t", 1) == false)
+ return false;
+ va_list list;
+ va_start(list, format);
+ int len;
+ if ((len = vsnprintf(buf, 1024, format, list)) < 0)
+ return false;
+ if (stream_write(stream, buf, len) == false)
+ return false;
+ return true;
+}
+
+static bool snbt_print_impl(const tag_t *tag, const stream_t *stream, int depth);
+
+static bool snbt_print_byte_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[B;") == false)
+ return false;
+ for (int32_t i = 0; i < data->b_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%hhdb", data->b_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool snbt_print_int_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[I;") == false)
+ return false;
+ for (int32_t i = 0; i < data->i_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%d", data->i_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool snbt_print_long_array(const tagdata_t *data, const stream_t *stream) {
+ if (printi(stream, 0, "[L;") == false)
+ return false;
+ for (int32_t i = 0; i < data->l_arr.size; i++) {
+ if (i != 0)
+ if (printi(stream, 0, ",") == false)
+ return false;
+ if (printi(stream, 0, "%ldL", data->l_arr.data[i]) == false)
+ return false;
+ }
+ if (printi(stream, 0, "]") == false)
+ return false;
+ return true;
+}
+
+static bool snbt_string_unquoted(const char *text, uint16_t len) {
+ for (uint16_t i = 0; i < len; i++) {
+ char c = text[i];
+ if (snbt_allowed_ident(c))
+ continue;
+ return false;
+ }
+ return true;
+}
+
+static bool snbt_print_string_impl(const stream_t *stream, int depth, char *text, uint16_t len) {
+ if (len > 0 && snbt_string_unquoted(text, len)) {
+ if (printi(stream, depth, "%.*s", len, text) == false)
+ return false;
+ } else if (len > 0) {
+ if (printi(stream, depth, "\"") == false)
+ return false;
+ for (uint16_t i = 0; i < len; i++) {
+ char c = text[i];
+ if (c == '\\' || c == '"') {
+ if (printi(stream, 0, "\\%c", c) == false)
+ return false;
+ } else {
+ if (printi(stream, 0, "%c", c) == false)
+ return false;
+ }
+ }
+ if (printi(stream, 0, "\"") == false)
+ return false;
+ } else {
+ if (printi(stream, depth, "''") == false)
+ return false;
+ }
+ return true;
+}
+
+static bool snbt_print_string(const tagdata_t *data, const stream_t *stream) {
+ char *text = data->string.data;
+ uint16_t len = data->string.size;
+ return snbt_print_string_impl(stream, 0, text, len);
+}
+
+static bool snbt_print_compound(const tagdata_t *data, const stream_t *stream, int depth) {
+ if (printi(stream, 0, "{\n") == false)
+ return false;
+ bool first = true;
+ for (uint32_t i = 0; i < data->compound.capacity; i++) {
+ if (data->compound.entries[i].name == NULL)
+ continue;
+ if (!first && printi(stream, 0, ",\n") == false)
+ return false;
+ first = false;
+ if (snbt_print_impl(&data->compound.entries[i], stream, depth + 1) == false)
+ return false;
+ }
+ if (printi(stream, 0, "\n") == false || printi(stream, depth, "}") == false)
+ return false;
+ return true;
+}
+
+static bool snbt_print_list(const tagdata_t *data, const stream_t *stream, int depth) {
+ if (printi(stream, 0, "[\n") == false)
+ return false;
+ for (int32_t i = 0; i < data->list.size; i++) {
+ if (i != 0 && printi(stream, 0, ",\n") == false)
+ return false;
+ if (snbt_print_impl(&data->list.tags[i], stream, depth + 1) == false)
+ return false;
+ }
+ if (printi(stream, 0, "\n") == false || printi(stream, depth, "]") == false)
+ return false;
+ return true;
+}
+
+static bool snbt_print_data(const tag_t *tag, const stream_t *stream, int depth) {
+
+ bool ok = true;
+
+ switch (tag->type) {
+ case TAG_BYTE:
+ ok = printi(stream, 0, "%hhdb", tag->data.b);
+ break;
+ case TAG_SHORT:
+ ok = printi(stream, 0, "%hds", tag->data.s);
+ break;
+ case TAG_INT:
+ ok = printi(stream, 0, "%d", tag->data.i);
+ break;
+ case TAG_LONG:
+ ok = printi(stream, 0, "%ldL", tag->data.l);
+ break;
+ case TAG_FLOAT:
+ ok = printi(stream, 0, "%.9gf", tag->data.f);
+ break;
+ case TAG_DOUBLE:
+ ok = printi(stream, 0, "%.17g", tag->data.d);
+ break;
+ case TAG_BYTE_ARRAY:
+ ok = snbt_print_byte_array(&tag->data, stream);
+ break;
+ case TAG_STRING:
+ ok = snbt_print_string(&tag->data, stream);
+ break;
+ case TAG_LIST:
+ ok = snbt_print_list(&tag->data, stream, depth);
+ break;
+ case TAG_COMPOUND:
+ ok = snbt_print_compound(&tag->data, stream, depth);
+ break;
+ case TAG_INT_ARRAY:
+ ok = snbt_print_int_array(&tag->data, stream);
+ break;
+ case TAG_LONG_ARRAY:
+ ok = snbt_print_long_array(&tag->data, stream);
+ break;
+ case TAG_END:
+ break;
+ }
+
+ return ok;
+}
+
+static bool snbt_print_impl(const tag_t *tag, const stream_t *stream, int depth) {
+ if (tag->name_len > 0) {
+ if (snbt_print_string_impl(stream, depth, tag->name, tag->name_len) == false)
+ return false;
+ if (printi(stream, 0, ": ") == false)
+ return false;
+ } else {
+ for (int i = 0; i < depth; i++)
+ printi(stream, 0, "\t");
+ }
+ return snbt_print_data(tag, stream, depth);
+}
+
+bool snbt_print(const tag_t *tag, const stream_t *stream) {
+ if (snbt_print_impl(tag, stream, 0) == false)
+ return false;
+ if (stream_write(stream, "\n", 1) == false)
+ return false;
+ return true;
+}
diff --git a/nbt/snbt/read.c b/nbt/snbt/read.c
new file mode 100644
index 0000000..0ee89d2
--- /dev/null
+++ b/nbt/snbt/read.c
@@ -0,0 +1,740 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nbt.h"
+#include "lib.h"
+
+typedef enum {
+ TOK_RBRACE,
+ TOK_LBRACE,
+ TOK_RBRACK,
+ TOK_LBRACK,
+ TOK_COMMA,
+ TOK_COLON,
+ TOK_SEMI_COLON,
+ TOK_STRING,
+ TOK_IDENT,
+} tokentype_t;
+
+typedef union {
+ struct {
+ char *data;
+ uint16_t len;
+ } text;
+ double d;
+ int64_t l;
+} tokendata_t;
+
+typedef struct {
+ tokentype_t type;
+ tokendata_t data;
+} token_t;
+
+static char ret = '\0';
+
+static void snbt_free_token(token_t *token) {
+ if (token->type == TOK_IDENT || token->type == TOK_STRING) {
+ free(token->data.text.data);
+ }
+}
+
+static bool snbt_parse_string(token_t *token, const stream_t *stream, char delimiter) {
+ int len = 0;
+ int capacity = 8;
+ char *buf = xalloc(capacity * sizeof(char));
+
+ while (1) {
+ char c;
+ if (stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (c == delimiter)
+ break;
+
+ if (c == '\\') {
+ if (stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (c != '\\' && c != delimiter) {
+ free(buf);
+ return false;
+ }
+ }
+
+ if (len == capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity);
+ }
+
+ buf[len++] = c;
+ }
+
+ token->data.text.data = xalloc(len * sizeof(char));
+ token->data.text.len = len;
+ memcpy(token->data.text.data, buf, len * sizeof(char));
+ free(buf);
+
+ return true;
+}
+
+bool snbt_allowed_ident(char c) {
+ if (c >= '0' && c <= '9')
+ return true;
+ if (c >= 'a' && c <= 'z')
+ return true;
+ if (c >= 'A' && c <= 'Z')
+ return true;
+ if (c == '_' || c == '-' || c == '.' || c == '+')
+ return true;
+ return false;
+}
+
+static bool snbt_parse_ident(token_t *token, const stream_t *stream, char first) {
+ int len = 0;
+ int capacity = 8;
+ char *buf = xalloc(capacity * sizeof(char));
+
+ buf[len++] = first;
+
+ while (1) {
+ char c;
+ if (stream_read(stream, &c, 1) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (snbt_allowed_ident(c) == false) {
+ if (len == 0) {
+ free(buf);
+ return false;
+ } else {
+ ret = c;
+ break;
+ }
+ }
+
+ if (len == capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity);
+ }
+
+ buf[len++] = c;
+ }
+
+ token->data.text.data = xalloc(len * sizeof(char));
+ token->data.text.len = len;
+ memcpy(token->data.text.data, buf, len * sizeof(char));
+ free(buf);
+
+ return true;
+}
+
+static bool snbt_next_token(token_t *token, const stream_t *stream) {
+
+ memset(token, 0, sizeof(token_t));
+
+ char c;
+ bool ok = true;
+
+retry:
+
+ c = ret;
+ ret = '\0';
+
+ if (c == '\0' && stream_read(stream, &c, 1) == false)
+ return false;
+
+ switch (c) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ goto retry;
+ case '{':
+ token->type = TOK_LBRACE;
+ break;
+ case '}':
+ token->type = TOK_RBRACE;
+ break;
+ case '[':
+ token->type = TOK_LBRACK;
+ break;
+ case ']':
+ token->type = TOK_RBRACK;
+ break;
+ case ':':
+ token->type = TOK_COLON;
+ break;
+ case ';':
+ token->type = TOK_SEMI_COLON;
+ break;
+ case ',':
+ token->type = TOK_COMMA;
+ break;
+ case '"':
+ case '\'':
+ token->type = TOK_STRING;
+ ok = snbt_parse_string(token, stream, c);
+ break;
+ default:
+ token->type = TOK_IDENT;
+ ok = snbt_parse_ident(token, stream, c);
+ break;
+ }
+
+ return ok;
+}
+
+static bool snbt_read_value(tag_t *tag, const stream_t *stream, token_t *first);
+static bool snbt_convert_ident(tag_t *tag, token_t token);
+
+static bool snbt_read_byte_array(tag_t *tag, const stream_t *stream) {
+ int capacity = 8;
+ int len = 0;
+ int8_t *buf = xalloc(capacity * sizeof(int8_t));
+
+ while(1) {
+ tag_t value;
+ if (snbt_read_value(&value, stream, NULL) == false) {
+ free(buf);
+ return false;
+ }
+
+ if (value.type != TAG_BYTE) {
+ tag_free(&value);
+ free(buf);
+ return false;
+ }
+ if (len == capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity * sizeof(int8_t));
+ }
+ buf[len++] = value.data.b;
+ token_t token;
+ if (snbt_next_token(&token, stream) == false) {
+ free(buf);
+ return false;
+ }
+ if (token.type == TOK_COMMA)
+ continue;
+ if (token.type == TOK_RBRACK)
+ break;
+ free(buf);
+ return false;
+ }
+ tag->type = TAG_BYTE_ARRAY;
+ tag->data.b_arr.data = xalloc(len * sizeof(int8_t));
+ tag->data.b_arr.size = len;
+ memcpy(tag->data.b_arr.data, buf, len * sizeof(int8_t));
+ free(buf);
+
+ return true;
+}
+
+static bool snbt_read_int_array(tag_t *tag, const stream_t *stream) {
+ int capacity = 8;
+ int len = 0;
+ int32_t *buf = xalloc(capacity * sizeof(int32_t));
+
+ while(1) {
+ tag_t value;
+ if (snbt_read_value(&value, stream, NULL) == false) {
+ free(buf);
+ return false;
+ }
+ if (value.type != TAG_INT) {
+ tag_free(&value);
+ free(buf);
+ return false;
+ }
+ if (len == capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity * sizeof(int32_t));
+ }
+ buf[len++] = value.data.i;
+ token_t token;
+ if (snbt_next_token(&token, stream) == false) {
+ free(buf);
+ return false;
+ }
+ if (token.type == TOK_COMMA)
+ continue;
+ if (token.type == TOK_RBRACK)
+ break;
+ free(buf);
+ return false;
+ }
+ tag->type = TAG_INT_ARRAY;
+ tag->data.i_arr.data = xalloc(len * sizeof(int32_t));
+ tag->data.i_arr.size = len;
+ memcpy(tag->data.i_arr.data, buf, len * sizeof(int32_t));
+ free(buf);
+
+ return true;
+}
+
+static bool snbt_read_long_array(tag_t *tag, const stream_t *stream) {
+ int capacity = 8;
+ int len = 0;
+ int64_t *buf = xalloc(capacity * sizeof(int64_t));
+
+ while(1) {
+ tag_t value;
+ if (snbt_read_value(&value, stream, NULL) == false) {
+ free(buf);
+ return false;
+ }
+ if (value.type != TAG_LONG) {
+ tag_free(&value);
+ free(buf);
+ return false;
+ }
+ if (len == capacity) {
+ capacity *= 2;
+ buf = xrealloc(buf, capacity * sizeof(int64_t));
+ }
+ buf[len++] = value.data.l;
+ token_t token;
+ if (snbt_next_token(&token, stream) == false) {
+ free(buf);
+ return false;
+ }
+ if (token.type == TOK_COMMA)
+ continue;
+ if (token.type == TOK_RBRACK)
+ break;
+ free(buf);
+ return false;
+ }
+ tag->type = TAG_LONG_ARRAY;
+ tag->data.l_arr.data = xalloc(len * sizeof(int64_t));
+ tag->data.l_arr.size = len;
+ memcpy(tag->data.l_arr.data, buf, len * sizeof(int64_t));
+ free(buf);
+
+ return true;
+}
+
+static bool snbt_read_array(tag_t *tag, tagtype_t type, const stream_t *stream) {
+ switch(type) {
+ case TAG_BYTE:
+ return snbt_read_byte_array(tag, stream);
+ case TAG_INT:
+ return snbt_read_int_array(tag, stream);
+ case TAG_LONG:
+ return snbt_read_long_array(tag, stream);
+ default:
+ return false;
+ }
+}
+
+static bool snbt_read_list(tag_t *tag, tag_t *first, token_t *tok_sav, const stream_t *stream) {
+ int capacity = 8;
+ int len = 0;
+ tag_t *tags = xalloc(sizeof(tag_t) * capacity);
+ tagtype_t type = TAG_END;
+
+ if (first != NULL) {
+ first->name_len = 0;
+ first->name = "";
+ tags[len++] = *first;
+ type = first->type;
+ goto endcheck;
+ }
+
+ if (tok_sav == NULL) {
+ if (snbt_next_token(tok_sav, stream) == false) {
+ free(tags);
+ return false;
+ }
+ if (tok_sav->type == TOK_RBRACK) {
+ goto end;
+ }
+ }
+
+ while(1) {
+
+ tag_t value;
+ token_t check;
+
+ if (snbt_read_value(&value, stream, tok_sav) == false) {
+ snbt_free_token(tok_sav);
+ free(tags);
+ return false;
+ }
+
+ tok_sav = NULL;
+
+ if (type != TAG_END && value.type != type) {
+ free(tags);
+ return false;
+ }
+
+ if (len == capacity) {
+ capacity *= 2;
+ tags = xrealloc(tags, capacity * sizeof(tag_t));
+ }
+
+ tags[len++] = value;
+
+endcheck:
+
+ if (snbt_next_token(&check, stream) == false) {
+ free(tags);
+ return false;
+ }
+
+ if (check.type == TOK_COMMA)
+ continue;
+
+ if (check.type == TOK_RBRACK)
+ break;
+
+ free(tags);
+ return false;
+ }
+end:
+ tag->type = TAG_LIST;
+ tag->data.list.type = type;
+ tag->data.list.size = len;
+ tag->data.list.tags = xalloc(len * sizeof(tag_t));
+ memcpy(tag->data.list.tags, tags, len *sizeof(tag_t));
+ free(tags);
+
+ return true;
+}
+
+static bool snbt_read_collection(tag_t *tag, const stream_t *stream) {
+
+ token_t first;
+
+ if (snbt_next_token(&first, stream) == false)
+ return false;
+
+ if (first.type == TOK_RBRACK) {
+ tag->type = TAG_LIST;
+ tag->data.list.type = TAG_END;
+ tag->data.list.size = 0;
+ tag->data.list.tags = NULL;
+ return true;
+ }
+
+ if (first.type == TOK_IDENT && first.data.text.len == 1) {
+ tagtype_t type;
+ char c = first.data.text.data[0];
+ switch (c) {
+ case 'B':
+ type = TAG_BYTE;
+ break;
+ case 'I':
+ type = TAG_INT;
+ break;
+ case 'L':
+ type = TAG_LONG;
+ break;
+ default: {
+ tag_t new;
+ if (snbt_convert_ident(&new, first) == false) {
+ snbt_free_token(&first);
+ return false;
+ }
+ return snbt_read_list(tag, &new, NULL, stream);
+ }
+ }
+
+ token_t second;
+ if (snbt_next_token(&second, stream) == false) {
+ snbt_free_token(&first);
+ return false;
+ }
+
+ if (second.type == TOK_COMMA) {
+ tag_t new;
+ new.type = TAG_STRING;
+ new.data.string.data = first.data.text.data;
+ new.data.string.size = first.data.text.len;
+ return snbt_read_list(tag, &new, NULL, stream);
+ }
+
+ snbt_free_token(&first);
+
+ if (second.type == TOK_SEMI_COLON) {
+ return snbt_read_array(tag, type, stream);
+ }
+
+ snbt_free_token(&second);
+ return false;
+
+ }
+
+ return snbt_read_list(tag, NULL, &first, stream);
+}
+
+static bool snbt_read_compound(tagdata_t *data, const stream_t *stream) {
+ map_t map;
+ map_init(&map);
+
+ token_t next = {0};
+ if (snbt_next_token(&next, stream) == false) {
+ snbt_free_token(&next);
+ return false;
+ }
+
+ if (next.type == TOK_RBRACE) {
+ data->compound = map;
+ return true;
+ }
+
+ while (1) {
+
+ if (next.type != TOK_STRING && next.type != TOK_IDENT) {
+ map_free(&map);
+ snbt_free_token(&next);
+ return false;
+ }
+
+ char *name = next.data.text.data;
+ int name_len = next.data.text.len;
+
+ if (name_len < 1) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ if (snbt_next_token(&next, stream) == false || next.type != TOK_COLON) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ tag_t value;
+ if (snbt_read_value(&value, stream, NULL) == false) {
+ map_free(&map);
+ free(name);
+ return false;
+ }
+
+ value.name = name;
+ value.name_len = name_len;
+
+ map_put(&map, &value);
+
+ if (snbt_next_token(&next, stream) == false) {
+ map_free(&map);
+ snbt_free_token(&next);
+ return false;
+ }
+
+ if (next.type == TOK_COMMA) {
+ if (snbt_next_token(&next, stream) == false) {
+ map_free(&map);
+ return false;
+ }
+ continue;
+ } else if (next.type == TOK_RBRACE) {
+ break;
+ } else {
+ map_free(&map);
+ snbt_free_token(&next);
+ return false;
+ }
+
+ }
+
+ data->compound = map;
+
+ return true;
+}
+
+static bool snbt_convert_decimal(tag_t *tag, char *text) {
+ uint16_t len = strlen(text);
+
+ char *end = NULL;
+
+ double d = strtod(text, &end);
+
+ char c = *end;
+ bool check1 = end == &text[len - 1] && (
+ c == 'f' ||
+ c == 'F' ||
+ c == 'd' ||
+ c == 'D'
+ );
+ bool check2 = end == &text[len] && c == '\0';
+ if (!check1 && !check2) {
+ return false;
+ }
+
+ if (tag->type == TAG_FLOAT) {
+ tag->data.f = (float)d;
+ } else {
+ tag->data.d = d;
+ }
+
+ return true;
+}
+
+static bool snbt_convert_int(tag_t *tag, char *text) {
+ uint16_t len = strlen(text);
+
+ char *end = NULL;
+
+ uint64_t i = strtol(text, &end, 10);
+
+ char c = *end;
+ bool check1 = end == &text[len - 1] && (
+ c == 'b' ||
+ c == 'B' ||
+ c == 's' ||
+ c == 'S' ||
+ c == 'l' ||
+ c == 'L'
+ );
+ bool check2 = end == &text[len] && c == '\0';
+ if (!check1 && !check2) {
+ return false;
+ }
+
+ if (tag->type == TAG_BYTE) {
+ tag->data.b = (uint8_t)i;
+ } else if (tag->type == TAG_SHORT) {
+ tag->data.s = (uint16_t)i;
+ } else if (tag->type == TAG_INT) {
+ tag->data.i = (uint32_t)i;
+ } else {
+ tag->data.l = i;
+ }
+
+ return true;
+}
+
+static bool snbt_convert_ident(tag_t *tag, token_t token) {
+ uint16_t len = token.data.text.len + 1;
+ char *text = xalloc(len * sizeof(char));
+ memcpy(text, token.data.text.data, len - 1);
+ text[len - 1] = '\0';
+
+ if (len == 4 && memcmp(text, "true", 4) == 0) {
+ tag->type = TAG_BYTE;
+ tag->data.b = 1;
+ } else if (len == 5 && memcmp(text, "false", 5) == 0) {
+ tag->type = TAG_BYTE;
+ tag->data.b = 0;
+ }
+
+ // try to parse as a number lol he he ha ha
+ char end = text[len - 2];
+
+ bool ok = false;
+
+ switch (end) {
+ case 'f':
+ case 'F':
+ tag->type = TAG_FLOAT;
+ ok = snbt_convert_decimal(tag, text);
+ break;
+ case 'd':
+ case 'D':
+ tag->type = TAG_DOUBLE;
+ ok = snbt_convert_decimal(tag, text);
+ break;
+ case 'b':
+ case 'B':
+ tag->type = TAG_BYTE;
+ ok = snbt_convert_int(tag, text);
+ break;
+ case 's':
+ case 'S':
+ tag->type = TAG_SHORT;
+ ok = snbt_convert_int(tag, text);
+ break;
+ case 'l':
+ case 'L':
+ tag->type = TAG_LONG;
+ ok = snbt_convert_int(tag, text);
+ break;
+ default:
+ break;
+ }
+
+ if (ok) {
+ free(text);
+ snbt_free_token(&token);
+ return true;
+ }
+
+ // unkown type try long >> double >> string (fallback)
+
+ tag->type = TAG_INT;
+ if (snbt_convert_int(tag, text) == true) {
+ free(text);
+ snbt_free_token(&token);
+ return true;
+ }
+
+ tag->type = TAG_DOUBLE;
+ if (snbt_convert_decimal(tag, text) == true) {
+ free(text);
+ snbt_free_token(&token);
+ return true;
+ }
+
+ tag->type = TAG_STRING;
+ tag->data.string.data = token.data.text.data;
+ tag->data.string.size = token.data.text.len;
+ free(text);
+ return true;
+}
+
+static bool snbt_read_value(tag_t *tag, const stream_t *stream, token_t *first) {
+
+ token_t token;
+
+ if (first != NULL)
+ token = *first;
+ else if (snbt_next_token(&token, stream) == false)
+ return false;
+
+ tag->name = "";
+ tag->name_len = 0;
+
+ bool ok = true;
+
+ switch (token.type) {
+ case TOK_RBRACK:
+ case TOK_RBRACE:
+ case TOK_COLON:
+ case TOK_SEMI_COLON:
+ case TOK_COMMA:
+ ok = false;
+ break;
+ case TOK_LBRACK:
+ tag->type = TAG_LIST;
+ ok = snbt_read_collection(tag, stream);
+ break;
+ case TOK_LBRACE:
+ tag->type = TAG_COMPOUND;
+ ok = snbt_read_compound(&tag->data, stream);
+ break;
+ case TOK_STRING:
+ tag->type = TAG_STRING;
+ tag->data.string.data = token.data.text.data;
+ tag->data.string.size = token.data.text.len;
+ break;
+ case TOK_IDENT:
+ ok = snbt_convert_ident(tag, token);
+ break;
+ }
+
+ return ok;
+}
+
+bool snbt_read(tag_t *tag, const stream_t *stream) {
+ return snbt_read_value(tag, stream, NULL);
+}