diff options
author | Freya Murphy <freya@freyacat.org> | 2023-12-17 11:10:04 -0500 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2023-12-17 11:10:04 -0500 |
commit | e0eacfa9773c83850ed5169d1e889ff845180581 (patch) | |
tree | 9f8bb433404ce7e4dda1b86ca4dd5d5a2fba10e9 /nbt | |
parent | snbt (diff) | |
download | nbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.tar.gz nbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.tar.bz2 nbtvis-e0eacfa9773c83850ed5169d1e889ff845180581.zip |
Diffstat (limited to 'nbt')
-rw-r--r-- | nbt/json/print.c | 177 | ||||
-rw-r--r-- | nbt/json/read.c | 632 | ||||
-rw-r--r-- | nbt/nbt.c | 65 | ||||
-rw-r--r-- | nbt/nbt.h | 84 | ||||
-rw-r--r-- | nbt/nbt/print.c | 127 | ||||
-rw-r--r-- | nbt/nbt/read.c | 207 | ||||
-rw-r--r-- | nbt/snbt/print.c | 211 | ||||
-rw-r--r-- | nbt/snbt/read.c | 740 |
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); +} |