From 42d1c82a0bfa615b832f5ecc2652edc290bf6e9c Mon Sep 17 00:00:00 2001 From: Freya Murphy Date: Fri, 15 Dec 2023 23:02:45 -0500 Subject: [PATCH] add json support and other things --- src/flags.c | 155 ++++++++ src/flags.h | 17 + src/json/json.h | 7 + src/json/print.c | 173 +++++++++ src/json/read.c | 634 +++++++++++++++++++++++++++++++++ src/main.c | 54 ++- src/nbt/nbt.h | 7 + src/nbt/print.c | 125 +++++++ src/{tag_read.c => nbt/read.c} | 52 +-- src/stream.c | 173 +++++---- src/stream.h | 21 +- src/tag.c | 66 ++++ src/tag.h | 14 +- src/tag_print.c | 101 ------ 14 files changed, 1388 insertions(+), 211 deletions(-) create mode 100644 src/flags.c create mode 100644 src/flags.h create mode 100644 src/json/json.h create mode 100644 src/json/print.c create mode 100644 src/json/read.c create mode 100644 src/nbt/nbt.h create mode 100644 src/nbt/print.c rename src/{tag_read.c => nbt/read.c} (74%) create mode 100644 src/tag.c delete mode 100644 src/tag_print.c diff --git a/src/flags.c b/src/flags.c new file mode 100644 index 0000000..58311ac --- /dev/null +++ b/src/flags.c @@ -0,0 +1,155 @@ +#include "flags.h" +#include "lib.h" +#include "stream.h" + +#include + +static format_t get_file_extension(char *path) { + char *filename = strrchr(path, '/'); + if (filename == NULL) + return NBT; + char *extension = strrchr(filename, '.'); + if (extension == NULL) + return NBT; + else if (strcasecmp(extension, "json") == 0) + return JSON; + else if (strcasecmp(extension, "snbt") == 0) + return SNBT; + return NBT; +} + +void parse_long_arg(flags_t *flags, char *arg) { + + char *key = arg + 2; + char *value = strchr(key, '='); + if (value != NULL) { + *value = '\0'; + value++; + } + + if (strcmp(key, "help") == 0) { + flags->help = true; + return; + } + + else if (strcmp(key, "version") == 0) { + flags->version = true; + return; + } + + else if (strcmp(key, "in") == 0) { + if (value == NULL) + error_and_die("--in requires a value\n"); + flags->in = stream_open(value, "r"); + if (flags->__set_fin == false) + flags->fin = get_file_extension(value); + } + + else if (strcmp(key, "out") == 0) { + if (value == NULL) + error_and_die("--out requires a value\n"); + flags->out = stream_open(value, "w"); + if (flags->__set_fout == false) + flags->fout = get_file_extension(value); + } + + else { + error_and_die("invalid argument '--%s'\n", key); + } + +} + +void parse_short_args(flags_t *flags, char *arg, int len) { + for (int i = 1; i < len; i++) { + char c = arg[i]; + switch(c) { + case 'j': + flags->fin = JSON; + flags->__set_fin = true; + break; + case 'J': + flags->fout = JSON; + flags->__set_fout = true; + break; + case 's': + flags->fin = SNBT; + break; + case 'S': + flags->fout = SNBT; + break; + case 'n': + flags->fin = NBT; + break; + case 'N': + flags->fout = NBT; + break; + case 'h': + flags->help = true; + break; + case 'v': + flags->version = true; + break; + default: + error_and_die("invalid argument: '-%c'\n", c); + } + } +} + +void parse_arg(flags_t *flags, char *arg, int len) { + if (len < 2) { + error_and_die("invalid argument: '%s'\n", arg); + } else if (arg[1] == '-') { + parse_long_arg(flags, arg); + } else { + parse_short_args(flags, arg, len); + } +} + +void parse_flags(flags_t *flags, int argc, char **argv) { + + flags->__set_fin = false; + flags->__set_fout = false; + flags->help = false; + flags->version = false; + flags->in.__file = stdin; + flags->in.__alloc = false; + flags->out.__file = stdout; + flags->out.__alloc = false; + flags->fin = NBT; + flags->fout = NBT; + + int i; + + for (i = 1; i < argc; i++) { + char *arg = argv[i]; + int len = strlen(arg); + + if (len == 2 && arg[0] == '-' && arg[1] == '-') { + break; + } else if (arg[0] != '-') { + i--; + break; + } else { + parse_arg(flags, arg, len); + } + } + + if (i + 1 < argc) { + char *arg = argv[i + 1]; + flags->in = stream_open(arg, "r"); + if (flags->__set_fin == false) + flags->fin = get_file_extension(arg); + } + + if (i + 2 < argc) { + char *arg = argv[i + 2]; + flags->out = stream_open(arg, "w"); + if (flags->__set_fout == false) + flags->fout = get_file_extension(arg); + } + + if (i + 3 < argc) { + error_and_die("too many arguments passed\n"); + } + +} diff --git a/src/flags.h b/src/flags.h new file mode 100644 index 0000000..a14580b --- /dev/null +++ b/src/flags.h @@ -0,0 +1,17 @@ +#pragma once + +#include "tag.h" +#include "stream.h" + +typedef struct { + stream_t in; + stream_t out; + format_t fin; + format_t fout; + bool help; + bool version; + bool __set_fin; + bool __set_fout; +} flags_t; + +void parse_flags(flags_t *flags, int argc, char **argv); diff --git a/src/json/json.h b/src/json/json.h new file mode 100644 index 0000000..73b4f52 --- /dev/null +++ b/src/json/json.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../tag.h" +#include "../stream.h" + +bool json_read(tag_t *tag, const stream_t *stream); +bool json_print(const tag_t *tag, const stream_t *stream); diff --git a/src/json/print.c b/src/json/print.c new file mode 100644 index 0000000..e8b6996 --- /dev/null +++ b/src/json/print.c @@ -0,0 +1,173 @@ +#include "json.h" + +#include +#include + +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 > 1) { + 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; + for (int32_t i = 0; i < data->compound.size; i++) { + if (i != 0 && printi(stream, 0, ",\n") == false) + return false; + if (json_print_impl(&data->compound.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_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, "%f", tag->data.f); + break; + case TAG_DOUBLE: + ok = printi(stream, 0, "%lf", 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\":\t", 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/src/json/read.c b/src/json/read.c new file mode 100644 index 0000000..9b75597 --- /dev/null +++ b/src/json/read.c @@ -0,0 +1,634 @@ +#include "json.h" +#include "../lib.h" + +#include +#include +#include +#include +#include + +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_parse_ident(token_t *token, const stream_t *stream, char first) { + + char buf[4]; + buf[3] = '\0'; + + if (stream_read(stream, buf, 3) == false) + return false; + + if (first == 't' && strcmp(buf, "rue") == 0) { + token->type = TOK_BOOL; + token->data.b = true; + } else if (first == 'f' && strcmp(buf, "als") == 0) { + if (stream_read(stream, buf, 1) == false) + return false; + if (*buf != 'e') + return false; + token->type = TOK_BOOL; + token->data.b = false; + } else if (first == 'n' && strcmp(buf, "ull") == 0) { + 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) { + + token_t next = {0}; + if (json_next_token(&next, stream) == false) { + json_token_free(&next); + return false; + } + + if (next.type == TOK_RBRACE) { + data->compound.tags = NULL; + data->compound.size = 0; + return true; + } + + int capacity = 8; + int len = 0; + tag_t *tags = xalloc(capacity * sizeof(tag_t)); + + while (1) { + + if (next.type != TOK_STRING) { + free(tags); + json_token_free(&next); + return false; + } + + char *name = next.data.string.data; + int name_len = next.data.string.len; + + if (json_next_token(&next, stream) == false || next.type != TOK_COLON) { + free(tags); + free(name); + return false; + } + + tag_t value; + if (json_read_value(&value, stream, NULL) == false) { + free(tags); + free(name); + return false; + } + + value.name = name; + value.name_len = name_len; + + if (len == capacity) { + capacity *= 2; + tags = xrealloc(tags, capacity * sizeof(tag_t)); + } + + tags[len++] = value; + + if (json_next_token(&next, stream) == false) { + free(tags); + free(value.name); + json_token_free(&next); + return false; + } + + if (next.type == TOK_COMMA) { + continue; + } else if (next.type == TOK_RBRACE) { + break; + } else { + free(tags); + free(value.name); + json_token_free(&next); + return false; + } + + } + + data->compound.tags = xalloc(len * sizeof(tag_t)); + data->compound.size = len; + memcpy(data->compound.tags, tags, len * sizeof(tag_t)); + free(tags); + + 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/src/main.c b/src/main.c index fec6d06..f4419eb 100644 --- a/src/main.c +++ b/src/main.c @@ -1,24 +1,54 @@ -#include "tag.h" #include "lib.h" +#include "tag.h" +#include "flags.h" + #include +#include -int main (int argc, char** argv) { +__attribute__((__noreturn__)) +void version() { + fprintf(stderr, "nbtvis v0.0.1\n"); + fprintf(stderr, "Copyright (C) 2023 Freya Murphy\n"); + exit(0); +} - if (argc != 2) { - printf("usage: nbtvis file.nbt\n"); - return 0; - } +__attribute__((__noreturn__)) +void help() { + fprintf(stderr, "Usage: nbtvis [OPTION]... [INFILE] [OUTFILE]\n\n"); + fprintf(stderr, "\t-j\tinput data is JSON\n"); + fprintf(stderr, "\t-s\tinput data is SNBT\n"); + fprintf(stderr, "\t-n\tinput data is NBT\n"); + fprintf(stderr, "\t-J\toutput data is JSON\n"); + fprintf(stderr, "\t-S\toutput data is SNBT\n"); + fprintf(stderr, "\t-N\toutput data is NBT\n\n"); + fprintf(stderr, "\t-h --help\tprint the help message\n"); + fprintf(stderr, "\t-v --version\tprint the version\n"); + fprintf(stderr, "\t--in=\tset input file name\n"); + fprintf(stderr, "\t--out=\tset output file name\n"); + exit(0); +} - stream_t stream = stream_open(argv[1], "rb"); +int main(int argc, char **argv) { + + flags_t flags; + parse_flags(&flags, argc, argv); + + if (flags.help) + help(); + + if (flags.version) + version(); tag_t tag; - if (tag_read(&tag, &stream, true) == false) - error_and_die("failed to read tag\n"); + if (tag_read(&tag, &flags.in, flags.fin) == false) + error_and_die("error: failed to read tag\n"); if (tag.type != TAG_COMPOUND) - error_and_die("root tag is not of type compound\n"); + error_and_die("error: nbt tag not a valid compound tag\n"); - tag_print(&tag); + if (tag_print(&tag, &flags.out, flags.fout) == false) + error_and_die("error: failed to write tag\n"); + tag_free(&tag); - stream_close(&stream); + return 0; } diff --git a/src/nbt/nbt.h b/src/nbt/nbt.h new file mode 100644 index 0000000..13c0606 --- /dev/null +++ b/src/nbt/nbt.h @@ -0,0 +1,7 @@ +#pragma once + +#include "../tag.h" +#include "../stream.h" + +bool nbt_read(tag_t *tag, const stream_t *stream); +bool nbt_print(const tag_t *tag, const stream_t *stream); diff --git a/src/nbt/print.c b/src/nbt/print.c new file mode 100644 index 0000000..4eac31f --- /dev/null +++ b/src/nbt/print.c @@ -0,0 +1,125 @@ +#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 (int32_t i = 0; i < data->compound.size; i++) + if (nbt_print(&data->compound.tags[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/src/tag_read.c b/src/nbt/read.c similarity index 74% rename from src/tag_read.c rename to src/nbt/read.c index 5f43e7c..d5dd827 100644 --- a/src/tag_read.c +++ b/src/nbt/read.c @@ -1,10 +1,13 @@ -#include "tag.h" -#include "lib.h" +#include "nbt.h" +#include "../lib.h" + #include #include #include -bool tag_read_header(tag_t *tag, stream_t *stream, bool named) { +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) @@ -34,7 +37,7 @@ bool tag_read_header(tag_t *tag, stream_t *stream, bool named) { } -static bool tag_read_byte_array(tagdata_t *data, stream_t *stream) { +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) { @@ -48,7 +51,7 @@ static bool tag_read_byte_array(tagdata_t *data, stream_t *stream) { return true; } -static bool tag_read_int_array(tagdata_t *data, stream_t *stream) { +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) { @@ -62,7 +65,7 @@ static bool tag_read_int_array(tagdata_t *data, stream_t *stream) { return true; } -static bool tag_read_long_array(tagdata_t *data, stream_t *stream) { +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) { @@ -76,7 +79,7 @@ static bool tag_read_long_array(tagdata_t *data, stream_t *stream) { return true; } -static bool tag_read_string(tagdata_t *data, stream_t *stream) { +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) { @@ -89,7 +92,7 @@ static bool tag_read_string(tagdata_t *data, stream_t *stream) { return true; } -static bool tag_read_list(tagdata_t *data, stream_t *stream) { +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) @@ -107,14 +110,14 @@ static bool tag_read_list(tagdata_t *data, stream_t *stream) { tag.type = data->list.type; tag.name = ""; tag.name_len = 0; - if (tag_read_data(&tag, stream) == false) + if (nbt_read_data(&tag, stream) == false) return false; data->list.tags[i] = tag; } return true; } -static bool tag_read_compound(tagdata_t *data, stream_t *stream) { +static bool nbt_read_compound(tagdata_t *data, const stream_t *stream) { int32_t size = 0; int32_t capacity = 8; tag_t *tags = xalloc(capacity * sizeof(tag_t)); @@ -123,14 +126,18 @@ static bool tag_read_compound(tagdata_t *data, stream_t *stream) { tag_t tag; - if (tag_read_header(&tag, stream, true) == false) + if (nbt_read_header(&tag, stream, true) == false) { + free(tags); return false; + } if (tag.type == TAG_END) break; - if (tag_read_data(&tag, stream) == false) + if (nbt_read_data(&tag, stream) == false) { + free(tags); return false; + } if (size == capacity) { capacity *= 2; @@ -147,7 +154,8 @@ static bool tag_read_compound(tagdata_t *data, stream_t *stream) { return true; } -bool tag_read_data(tag_t *tag, stream_t *stream) { + +static bool nbt_read_data(tag_t *tag, const stream_t *stream) { bool ok = true; switch (tag->type) { @@ -169,33 +177,33 @@ bool tag_read_data(tag_t *tag, stream_t *stream) { ok = stream_read_i64(stream, &tag->data.l); break; case TAG_BYTE_ARRAY: - ok = tag_read_byte_array(&tag->data, stream); + ok = nbt_read_byte_array(&tag->data, stream); break; case TAG_STRING: - ok = tag_read_string(&tag->data, stream); + ok = nbt_read_string(&tag->data, stream); break; case TAG_LIST: - ok = tag_read_list(&tag->data, stream); + ok = nbt_read_list(&tag->data, stream); break; case TAG_COMPOUND: - ok = tag_read_compound(&tag->data, stream); + ok = nbt_read_compound(&tag->data, stream); break; case TAG_INT_ARRAY: - ok = tag_read_int_array(&tag->data, stream); + ok = nbt_read_int_array(&tag->data, stream); break; case TAG_LONG_ARRAY: - ok = tag_read_long_array(&tag->data, stream); + ok = nbt_read_long_array(&tag->data, stream); break; break; }; return ok; } -bool tag_read(tag_t *tag, stream_t *stream, bool named) { +bool nbt_read(tag_t *tag, const stream_t *stream) { memset(tag, 0, sizeof(tag_t)); - if (tag_read_header(tag, stream, named) == false) + if (nbt_read_header(tag, stream, true) == false) return false; - if (tag_read_data(tag, stream) == false) + if (nbt_read_data(tag, stream) == false) return false; return true; } diff --git a/src/stream.c b/src/stream.c index 622fafa..6780d02 100644 --- a/src/stream.c +++ b/src/stream.c @@ -7,64 +7,7 @@ #include #include -stream_t stream_open(char *path, char* mode) { - stream_t stream; - - if (strcmp("-", path) == 0) { - stream.__file = stdin; - stream.__alloc = false; - } - - stream.__file = fopen(path, mode); - stream.__alloc = true; - - if (stream.__file == NULL) { - perror_and_die("cannot read '%s'", path); - }; - - return stream; -} - -void stream_close(stream_t *stream) { - if (stream->__alloc) - fclose(stream->__file); -} - -bool stream_read(stream_t *stream, void *res, size_t amount) { - size_t read; - read = fread(res, 1, amount, stream->__file); - - if (read == 0) { - if (feof(stream->__file) || errno == 0) - return false; - else - perror_and_die("cannot read open stream"); - } - - return true; -} - -bool stream_read_i8(stream_t *stream, int8_t *res) { - if (stream_read(stream, res, 1) == false) - return false; - return true; -} - -bool stream_read_i16(stream_t *stream, int16_t *res) { - if (stream_read(stream, res, 2) == false) - return false; - *res = ntohs(*res); - return true; -} - -bool stream_read_i32(stream_t *stream, int32_t *res) { - if (stream_read(stream, res, 4) == false) - return false; - *res = ntohl(*res); - return true; -} - -static uint64_t ntohll(uint64_t ll) { +static uint64_t longswap(uint64_t ll) { if (htons(20) == 20) return ll; @@ -77,23 +20,125 @@ static uint64_t ntohll(uint64_t ll) { return out.ll; } -bool stream_read_i64(stream_t *stream, int64_t *res) { - if (stream_read(stream, res, 8) == false) - return false; - *res = ntohll(*res); + +stream_t stream_open(const char *path, const char* mode) { + stream_t stream; + + if (strcmp("-", path) == 0) { + if (*mode == 'r') + stream.__file = stdin; + else + stream.__file = stdout; + stream.__alloc = false; + return stream; + } + + stream.__file = fopen(path, mode); + stream.__alloc = true; + + if (stream.__file == NULL) { + perror_and_die("cannot open '%s'", path); + }; + + return stream; +} + +void stream_close(stream_t *stream) { + if (stream->__alloc) + fclose(stream->__file); +} + +bool stream_read(const stream_t *stream, void *res, size_t amount) { + size_t read; + read = fread(res, 1, amount, stream->__file); + + if (read == 0) { + if (feof(stream->__file) || errno == 0) + return false; + else + perror_and_die("cannot read from stream"); + } + return true; } -bool stream_read_u16(stream_t *stream, uint16_t *res) { +bool stream_read_i8(const stream_t *stream, int8_t *res) { + if (stream_read(stream, res, 1) == false) + return false; + return true; +} + +bool stream_read_i16(const stream_t *stream, int16_t *res) { if (stream_read(stream, res, 2) == false) return false; *res = ntohs(*res); return true; } -bool stream_read_u32(stream_t *stream, uint32_t *res) { +bool stream_read_i32(const stream_t *stream, int32_t *res) { if (stream_read(stream, res, 4) == false) return false; *res = ntohl(*res); return true; } + +bool stream_read_i64(const stream_t *stream, int64_t *res) { + if (stream_read(stream, res, 8) == false) + return false; + *res = longswap(*res); + return true; +} + +bool stream_read_u16(const stream_t *stream, uint16_t *res) { + if (stream_read(stream, res, 2) == false) + return false; + *res = ntohs(*res); + return true; +} + +bool stream_write(const stream_t *stream, const void *buf, size_t amount) { + size_t wrote; + wrote = fwrite(buf, 1, amount, stream->__file); + + if (wrote == 0) + perror_and_die("cannot write to stream"); + + if (wrote < amount) + return false; + + return true; +} + +bool stream_write_i8(const stream_t *stream, int8_t b) { + if (stream_write(stream, &b, 1) == false) + return false; + return true; +} + +bool stream_write_i16(const stream_t *stream, int16_t s) { + s = htons(s); + if (stream_write(stream, &s, 2) == false) + return false; + return true; +} + +bool stream_write_i32(const stream_t *stream, int32_t i) { + i = htonl(i); + if (stream_write(stream, &i, 4) == false) + return false; + return true; +} + +bool stream_write_i64(const stream_t *stream, int64_t l) { + l = longswap(l); + if (stream_write(stream, &l, 8) == false) + return false; + return true; +} + +bool stream_write_u16(const stream_t *stream, uint16_t s) { + s = htons(s); + if (stream_write(stream, &s, 2) == false) + return false; + return true; +} diff --git a/src/stream.h b/src/stream.h index e9fd920..d9a05ce 100644 --- a/src/stream.h +++ b/src/stream.h @@ -9,14 +9,19 @@ typedef struct { bool __alloc; } stream_t; -stream_t stream_open(char *path, char* mode); +stream_t stream_open(const char *path, const char* mode); void stream_close(stream_t *stream); -bool stream_read(stream_t *stream, void *res, size_t amount); -bool stream_read_i8(stream_t *stream, int8_t *res); -bool stream_read_i16(stream_t *stream, int16_t *res); -bool stream_read_i32(stream_t *stream, int32_t *res); -bool stream_read_i64(stream_t *stream, int64_t *res); +bool stream_read(const stream_t *stream, void *res, size_t amount); +bool stream_read_i8(const stream_t *stream, int8_t *res); +bool stream_read_i16(const stream_t *stream, int16_t *res); +bool stream_read_i32(const stream_t *stream, int32_t *res); +bool stream_read_i64(const stream_t *stream, int64_t *res); +bool stream_read_u16(const stream_t *stream, uint16_t *res); -bool stream_read_u16(stream_t *stream, uint16_t *res); -bool stream_read_u32(stream_t *stream, uint32_t *res); +bool stream_write(const stream_t *stream, const void *buf, size_t amount); +bool stream_write_i8(const stream_t *stream, int8_t b); +bool stream_write_i16(const stream_t *stream, int16_t s); +bool stream_write_i32(const stream_t *stream, int32_t i); +bool stream_write_i64(const stream_t *stream, int64_t l); +bool stream_write_u16(const stream_t *stream, uint16_t s); diff --git a/src/tag.c b/src/tag.c new file mode 100644 index 0000000..25d9d32 --- /dev/null +++ b/src/tag.c @@ -0,0 +1,66 @@ +#include "tag.h" +#include "nbt/nbt.h" +#include "json/json.h" + +#include + +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: + for (int32_t i = 0; i < tag->data.compound.size; i++) + tag_free(&tag->data.compound.tags[i]); + free(tag->data.compound.tags); + 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: + 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: + default: + return false; + } +} diff --git a/src/tag.h b/src/tag.h index 93eff64..d0033a5 100644 --- a/src/tag.h +++ b/src/tag.h @@ -67,7 +67,13 @@ typedef struct tag_t { char *name; } tag_t; -bool tag_read_header(tag_t *tag, stream_t *stream, bool named); -bool tag_read_data(tag_t *tag, stream_t *stream); -bool tag_read(tag_t *tag, stream_t *stream, bool named); -void tag_print(const tag_t *tag); +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); + diff --git a/src/tag_print.c b/src/tag_print.c deleted file mode 100644 index b6a8e1a..0000000 --- a/src/tag_print.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "tag.h" -#include -#include - -__attribute__((format(printf, 2, 3))) -static void printi(int depth, const char *format, ...) { - for (int i = 0; i < depth; i++) - printf("\t"); - va_list list; - va_start(list, format); - vprintf(format, list); -} - -static void tag_print_impl(const tag_t *tag, int depth); - -static void tag_print_data(const tag_t *tag, int depth) { - switch (tag->type) { - case TAG_BYTE: - printf("%hhd", tag->data.b); - break; - case TAG_SHORT: - printf("%hd", tag->data.s); - break; - case TAG_INT: - printf("%d", tag->data.i); - break; - case TAG_LONG: - printf("%ld", tag->data.l); - break; - case TAG_FLOAT: - printf("%f", tag->data.f); - break; - case TAG_DOUBLE: - printf("%lf", tag->data.d); - break; - case TAG_BYTE_ARRAY: - printf("["); - for (int32_t i = 0; i < tag->data.b_arr.size; i++) { - if (i != 0) printf(","); - printf("%hhd", tag->data.b_arr.data[i]); - } - printf("]"); - break; - case TAG_STRING: - if (tag->data.string.size > 1) - printf("\"%.*s\"", tag->data.string.size, tag->data.string.data); - else - printf("\"\""); - break; - case TAG_LIST: - printf("[\n"); - for (int32_t i = 0; i < tag->data.list.size; i++) { - if (i != 0) printf(",\n"); - tag_print_impl(&tag->data.list.tags[i], depth + 1); - } - printf("\n"); - printi(depth, "]"); - break; - case TAG_COMPOUND: - printf("{\n"); - for (int32_t i = 0; i < tag->data.compound.size; i++) { - if (i != 0) printf(",\n"); - tag_print_impl(&tag->data.compound.tags[i], depth + 1); - } - printf("\n"); - printi(depth, "}"); - break; - case TAG_INT_ARRAY: - printi(depth, "["); - for (int32_t i = 0; i < tag->data.i_arr.size; i++) { - if (i != 0) printf(","); - printf("%d", tag->data.i_arr.data[i]); - } - printf("]"); - break; - case TAG_LONG_ARRAY: - printf("["); - for (int32_t i = 0; i < tag->data.l_arr.size; i++) { - if (i != 0) printf(","); - printf("%ld", tag->data.l_arr.data[i]); - } - printf("]"); - break; - case TAG_END: - break; - } -} - -static void tag_print_impl(const tag_t *tag, int depth) { - if (tag->name_len > 0) { - printi(depth, "\"%.*s\":\t", tag->name_len, tag->name); - } else { - for (int i = 0; i < depth; i++) printf("\t"); - } - tag_print_data(tag, depth); -} - -void tag_print(const tag_t *tag) { - tag_print_impl(tag, 0); - printf("\n"); -}