blob: 5dffb67d93760d2ff40ee42f86dcadf44cb6e4b3 [file] [log] [blame] [raw]
/*
* -----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Lukas Niederbremer <webmaster@flippeh.de> and Clark Gaebel <cg.wowus.cg@gmail.com>
* wrote this file. As long as you retain this notice you can do whatever you
* want with this stuff. If we meet some day, and you think this stuff is worth
* it, you can buy us a beer in return.
* -----------------------------------------------------------------------------
*/
#include "nbt.h"
#include "buffer.h"
#include "list.h"
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#define INDENT_SIZE 2
struct output_target {
struct buffer *buffer;
FILE *file;
};
/* are we running on a little-endian system? */
static int little_endian()
{
uint16_t t = 0x0001;
char c[2];
memcpy(c, &t, sizeof t);
return c[0];
}
static void* swap_bytes(void* s, size_t len)
{
for(char* b = s,
* e = b + len - 1;
b < e;
b++, e--)
{
char t = *b;
*b = *e;
*e = t;
}
return s;
}
/* big endian to native endian. works in-place */
static void* be2ne(void* s, size_t len)
{
return little_endian() ? swap_bytes(s, len) : s;
}
/* native endian to big endian. works the exact same as its inverse */
#define ne2be be2ne
/* A special form of memcpy which copies `n' bytes into `dest', then returns
* `src' + n.
*/
static const void* memscan(void* dest, const void* src, size_t n)
{
memcpy(dest, src, n);
return (const char*)src + n;
}
/* Does a memscan, then goes from big endian to native endian on the
* destination.
*/
static const void* swapped_memscan(void* dest, const void* src, size_t n)
{
const void* ret = memscan(dest, src, n);
return be2ne(dest, n), ret;
}
#define CHECKED_MALLOC(var, n, on_error) do { \
if((var = malloc(n)) == NULL) \
{ \
errno = NBT_EMEM; \
on_error; \
} \
} while(0)
#define CHECKED_APPEND(b, ptr, len) do { \
if(buffer_append((b), (ptr), (len))) \
return NBT_EMEM; \
} while(0)
/* Parses a tag, given a name (may be NULL) and a type. Fills in the payload. */
static nbt_node* parse_unnamed_tag(nbt_type type, char* name, const char** memory, size_t* length);
/*
* Reads some bytes from the memory stream. This macro will read `n'
* bytes into `dest', call either memscan or swapped_memscan depending on
* `scanner', then fix the length. If anything funky goes down, `on_failure'
* will be executed.
*/
#define READ_GENERIC(dest, n, scanner, on_failure) do { \
if(*length < (n)) { on_failure; } \
*memory = scanner((dest), *memory, (n)); \
*length -= (n); \
} while(0)
/* printfs into the end of a buffer. Note: no null-termination! */
static void bprintf(struct output_target *target, const char* restrict format, ...)
{
va_list args;
int siz;
if(target->file) {
va_start(args, format);
vfprintf(target->file, format, args);
va_end(args);
return;
}
struct buffer *b = target->buffer;
va_start(args, format);
siz = vsnprintf(NULL, 0, format, args);
va_end(args);
buffer_reserve(b, b->len + siz + 1);
va_start(args, format);
vsnprintf((char*)(b->data + b->len), siz + 1, format, args);
va_end(args);
b->len += siz; // remember - no null terminator!
}
/*
* Reads a string from memory, moving the pointer and updating the length
* appropriately. Returns NULL on failure.
*/
static char* read_string(const char** memory, size_t* length)
{
int16_t string_length;
char* ret = NULL;
READ_GENERIC(&string_length, sizeof string_length, swapped_memscan, goto parse_error);
if(string_length < 0) goto parse_error;
if(*length < (size_t)string_length) goto parse_error;
CHECKED_MALLOC(ret, string_length + 1, goto parse_error);
READ_GENERIC(ret, (size_t)string_length, memscan, goto parse_error);
ret[string_length] = '\0'; /* don't forget to NULL-terminate ;) */
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(ret);
return NULL;
}
static nbt_node* parse_named_tag(const char** memory, size_t* length)
{
char* name = NULL;
uint8_t type;
READ_GENERIC(&type, sizeof type, memscan, goto parse_error);
name = read_string(memory, length);
nbt_node* ret = parse_unnamed_tag((nbt_type)type, name, memory, length);
if(ret == NULL) goto parse_error;
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(name);
return NULL;
}
static struct nbt_byte_array read_byte_array(const char** memory, size_t* length)
{
struct nbt_byte_array ret;
ret.data = NULL;
READ_GENERIC(&ret.length, sizeof ret.length, swapped_memscan, goto parse_error);
if(ret.length < 0) goto parse_error;
CHECKED_MALLOC(ret.data, ret.length, goto parse_error);
READ_GENERIC(ret.data, (size_t)ret.length, memscan, goto parse_error);
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(ret.data);
ret.data = NULL;
return ret;
}
static struct nbt_int_array read_int_array(const char** memory, size_t* length)
{
struct nbt_int_array ret;
ret.data = NULL;
READ_GENERIC(&ret.length, sizeof ret.length, swapped_memscan, goto parse_error);
if(ret.length < 0) goto parse_error;
CHECKED_MALLOC(ret.data, ret.length * sizeof(int32_t), goto parse_error);
READ_GENERIC(ret.data, (size_t)ret.length * sizeof(int32_t), memscan, goto parse_error);
// Byteswap the whole array.
for(int32_t i = 0; i < ret.length; i++)
be2ne(ret.data + i, sizeof(int32_t));
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(ret.data);
ret.data = NULL;
return ret;
}
static struct nbt_long_array read_long_array(const char** memory, size_t* length)
{
struct nbt_long_array ret;
ret.data = NULL;
READ_GENERIC(&ret.length, sizeof ret.length, swapped_memscan, goto parse_error);
if(ret.length < 0) goto parse_error;
CHECKED_MALLOC(ret.data, ret.length * sizeof(int64_t), goto parse_error);
READ_GENERIC(ret.data, (size_t)ret.length * sizeof(int64_t), memscan, goto parse_error);
// Byteswap the whole array.
for(int32_t i = 0; i < ret.length; i++)
be2ne(ret.data + i, sizeof(int64_t));
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(ret.data);
ret.data = NULL;
return ret;
}
/*
* Is the list all one type? If yes, return the type. Otherwise, return
* TAG_INVALID
*/
static nbt_type list_is_homogenous(const struct nbt_list* list)
{
nbt_type type = TAG_INVALID;
const struct list_head* pos;
list_for_each(pos, &list->entry)
{
const struct nbt_list* cur = list_entry(pos, const struct nbt_list, entry);
assert(cur->data);
assert(cur->data->type != TAG_INVALID);
if(cur->data->type == TAG_INVALID)
return TAG_INVALID;
/* if we're the first type, just set it to our current type */
if(type == TAG_INVALID) type = cur->data->type;
if(type != cur->data->type)
return TAG_INVALID;
}
/* if the list was empty, use the sentinel type */
if(type == TAG_INVALID && list->data != NULL)
type = list->data->type;
return type;
}
static struct nbt_list* read_list(const char** memory, size_t* length)
{
uint8_t type;
int32_t elems;
struct nbt_list* ret;
CHECKED_MALLOC(ret, sizeof *ret, goto parse_error);
/* we allocate the data pointer to store the type of the list in the first
* sentinel element */
CHECKED_MALLOC(ret->data, sizeof *ret->data, goto parse_error);
INIT_LIST_HEAD(&ret->entry);
READ_GENERIC(&type, sizeof type, swapped_memscan, goto parse_error);
READ_GENERIC(&elems, sizeof elems, swapped_memscan, goto parse_error);
ret->data->type = type == TAG_INVALID ? TAG_COMPOUND : (nbt_type)type;
for(int32_t i = 0; i < elems; i++)
{
struct nbt_list* new;
CHECKED_MALLOC(new, sizeof *new, goto parse_error);
new->data = parse_unnamed_tag((nbt_type)type, NULL, memory, length);
if(new->data == NULL)
{
free(new);
goto parse_error;
}
list_add_tail(&new->entry, &ret->entry);
}
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
nbt_free_list(ret);
return NULL;
}
static struct nbt_list* read_compound(const char** memory, size_t* length)
{
struct nbt_list* ret;
CHECKED_MALLOC(ret, sizeof *ret, goto parse_error);
ret->data = NULL;
INIT_LIST_HEAD(&ret->entry);
for(;;)
{
uint8_t type;
char* name = NULL;
struct nbt_list* new_entry;
READ_GENERIC(&type, sizeof type, swapped_memscan, goto parse_error);
if(type == 0) break; /* TAG_END == 0. We've hit the end of the list when type == TAG_END. */
name = read_string(memory, length);
if(name == NULL) goto parse_error;
CHECKED_MALLOC(new_entry, sizeof *new_entry,
free(name);
goto parse_error;
);
new_entry->data = parse_unnamed_tag((nbt_type)type, name, memory, length);
if(new_entry->data == NULL)
{
free(new_entry);
free(name);
goto parse_error;
}
list_add_tail(&new_entry->entry, &ret->entry);
}
return ret;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
nbt_free_list(ret);
return NULL;
}
/*
* Parses a tag, given a name (may be NULL) and a type. Fills in the payload.
*/
static nbt_node* parse_unnamed_tag(nbt_type type, char* name, const char** memory, size_t* length)
{
nbt_node* node;
CHECKED_MALLOC(node, sizeof *node, goto parse_error);
node->type = type;
node->name = name;
#define COPY_INTO_PAYLOAD(payload_name) \
READ_GENERIC(&node->payload.payload_name, sizeof node->payload.payload_name, swapped_memscan, goto parse_error);
switch(type)
{
case TAG_BYTE:
COPY_INTO_PAYLOAD(tag_byte);
break;
case TAG_SHORT:
COPY_INTO_PAYLOAD(tag_short);
break;
case TAG_INT:
COPY_INTO_PAYLOAD(tag_int);
break;
case TAG_LONG:
COPY_INTO_PAYLOAD(tag_long);
break;
case TAG_FLOAT:
COPY_INTO_PAYLOAD(tag_float);
break;
case TAG_DOUBLE:
COPY_INTO_PAYLOAD(tag_double);
break;
case TAG_BYTE_ARRAY:
node->payload.tag_byte_array = read_byte_array(memory, length);
break;
case TAG_INT_ARRAY:
node->payload.tag_int_array = read_int_array(memory, length);
break;
case TAG_LONG_ARRAY:
node->payload.tag_long_array = read_long_array(memory, length);
break;
case TAG_STRING:
node->payload.tag_string = read_string(memory, length);
break;
case TAG_LIST:
node->payload.tag_list = read_list(memory, length);
break;
case TAG_COMPOUND:
node->payload.tag_compound = read_compound(memory, length);
break;
default:
goto parse_error; /* Unknown node or TAG_END. Either way, we shouldn't be parsing this. */
}
#undef COPY_INTO_PAYLOAD
if(errno != NBT_OK) goto parse_error;
return node;
parse_error:
if(errno == NBT_OK)
errno = NBT_ERR;
free(node);
return NULL;
}
nbt_node* nbt_parse(const void* mem, size_t len)
{
errno = NBT_OK;
const char** memory = (const char**)&mem;
size_t* length = &len;
return parse_named_tag(memory, length);
}
/* spaces, not tabs ;) */
static void indent(struct output_target *target, size_t amount)
{
size_t size = amount * INDENT_SIZE;
char temp[size + 1];
memset(temp, ' ', size);
temp[size] = '\0';
bprintf(target, "%s", temp);
}
static nbt_status __nbt_dump_ascii(const nbt_node *, struct output_target *, size_t);
static void dump_byte_array(const struct nbt_byte_array ba, struct output_target *target)
{
assert(ba.length >= 0);
bprintf(target, "[ ");
for(int32_t i = 0; i < ba.length; ++i) {
bprintf(target, "%hhu ", (unsigned char)+ba.data[i]);
}
bprintf(target, "]");
}
static void dump_int_array(const struct nbt_int_array ia, struct output_target *target)
{
assert(ia.length >= 0);
bprintf(target, "[ ");
for(int32_t i = 0; i < ia.length; ++i) {
bprintf(target, "%u ", (unsigned int)+ia.data[i]);
}
bprintf(target, "]");
}
static void dump_long_array(const struct nbt_long_array la, struct output_target *target)
{
assert(la.length >= 0);
bprintf(target, "[ ");
for(int32_t i = 0; i < la.length; ++i) {
bprintf(target, "%llu ", (unsigned long long int)+la.data[i]);
}
bprintf(target, "]");
}
static nbt_status dump_list_contents_ascii(const struct nbt_list* list, struct output_target *target, size_t ident)
{
const struct list_head* pos;
list_for_each(pos, &list->entry)
{
const struct nbt_list* entry = list_entry(pos, const struct nbt_list, entry);
nbt_status status = __nbt_dump_ascii(entry->data, target, ident);
if(status != NBT_OK) return status;
}
return NBT_OK;
}
static const char *quoted_string(const char *s) {
static char buffer[8192] = { '"' };
if(!s) return "null";
unsigned int i = 1;
while(*s) {
if(i >= sizeof buffer - 7) {
memcpy(buffer + i, "...", 3);
break;
}
switch(*s) {
case '\n':
buffer[i++] = '\\';
buffer[i++] = 'n';
s++;
break;
case '"':
buffer[i++] = '\\';
// Fallthrough
default:
buffer[i++] = *s++;
}
}
buffer[i++] = '"';
buffer[i] = 0;
return buffer;
}
static nbt_status __nbt_dump_ascii(const nbt_node* tree, struct output_target *target, size_t ident)
{
if(tree == NULL) return NBT_OK;
indent(target, ident);
switch(tree->type) {
nbt_status status;
case TAG_BYTE:
bprintf(target, "TAG_Byte(%s) %i\n",
quoted_string(tree->name), (int)tree->payload.tag_byte);
break;
case TAG_SHORT:
bprintf(target, "TAG_Short(%s) %i\n",
quoted_string(tree->name), (int)tree->payload.tag_short);
break;
case TAG_INT:
bprintf(target, "TAG_Int(%s) %i\n",
quoted_string(tree->name), (int)tree->payload.tag_int);
break;
case TAG_LONG:
bprintf(target, "TAG_Long(%s) %" PRIi64 "\n",
quoted_string(tree->name), tree->payload.tag_long);
break;
case TAG_FLOAT:
bprintf(target, "TAG_Float(%s) %f\n",
quoted_string(tree->name), (double)tree->payload.tag_float);
break;
case TAG_DOUBLE:
bprintf(target, "TAG_Double(%s) %f\n",
quoted_string(tree->name), tree->payload.tag_double);
break;
case TAG_BYTE_ARRAY:
bprintf(target, "TAG_Byte_Array(%s) ", quoted_string(tree->name));
dump_byte_array(tree->payload.tag_byte_array, target);
bprintf(target, "\n");
break;
case TAG_INT_ARRAY:
bprintf(target, "Tag_Int_Array(%s) ", quoted_string(tree->name));
dump_int_array(tree->payload.tag_int_array, target);
bprintf(target, "\n");
break;
case TAG_LONG_ARRAY:
bprintf(target, "Tag_Long_Array(%s) ", quoted_string(tree->name));
dump_long_array(tree->payload.tag_long_array, target);
bprintf(target, "\n");
break;
case TAG_STRING:
if(tree->payload.tag_string == NULL) return NBT_ERR;
bprintf(target, "TAG_String(%s) ", quoted_string(tree->name));
bprintf(target, "%s\n", quoted_string(tree->payload.tag_string));
break;
case TAG_LIST:
bprintf(target, "TAG_List(%s) [%s] {\n",
quoted_string(tree->name), nbt_type_to_string(tree->payload.tag_list->data->type));
status = dump_list_contents_ascii(tree->payload.tag_list, target, ident + 1);
indent(target, ident);
bprintf(target, "}\n");
if(status != NBT_OK) return status;
break;
case TAG_COMPOUND:
bprintf(target, "TAG_Compound(%s) {\n", quoted_string(tree->name));
status = dump_list_contents_ascii(tree->payload.tag_compound, target, ident + 1);
indent(target, ident);
bprintf(target, "}\n");
if(status != NBT_OK) return status;
break;
default:
return NBT_ERR;
}
return NBT_OK;
}
char* nbt_dump_ascii(const nbt_node* tree)
{
errno = NBT_OK;
assert(tree);
struct buffer b = BUFFER_INIT;
struct output_target target = { .buffer = &b };
if((errno = __nbt_dump_ascii(tree, &target, 0)) != NBT_OK) goto OOM;
if(buffer_reserve(&b, b.len + 1)) goto OOM;
b.data[b.len] = '\0'; /* null-terminate that biatch, since bprintf doesn't
do that for us. */
return (char*)b.data;
OOM:
if(errno != NBT_OK)
errno = NBT_EMEM;
buffer_free(&b);
return NULL;
}
nbt_status nbt_dump_ascii_file(const nbt_node* tree, FILE *file) {
if(!file) return NBT_ERR;
struct output_target target = { .file = file };
return __nbt_dump_ascii(tree, &target, 0);
}
static nbt_status dump_byte_array_binary(const struct nbt_byte_array ba, struct buffer* b)
{
int32_t dumped_length = ba.length;
ne2be(&dumped_length, sizeof dumped_length);
CHECKED_APPEND(b, &dumped_length, sizeof dumped_length);
if(ba.length) assert(ba.data);
CHECKED_APPEND(b, ba.data, ba.length);
return NBT_OK;
}
static nbt_status dump_int_array_binary(const struct nbt_int_array ia, struct buffer* b)
{
int32_t dumped_length = ia.length;
ne2be(&dumped_length, sizeof dumped_length);
CHECKED_APPEND(b, &dumped_length, sizeof dumped_length);
if(ia.length) assert(ia.data);
for(int32_t i = 0; i < ia.length; i++)
{
int32_t swappedElem = ia.data[i];
ne2be(&swappedElem, sizeof(swappedElem));
CHECKED_APPEND(b, &swappedElem, sizeof(swappedElem));
}
return NBT_OK;
}
static nbt_status dump_long_array_binary(const struct nbt_long_array la, struct buffer* b)
{
int32_t dumped_length = la.length;
ne2be(&dumped_length, sizeof dumped_length);
CHECKED_APPEND(b, &dumped_length, sizeof dumped_length);
if(la.length) assert(la.data);
for(int32_t i = 0; i < la.length; i++)
{
int64_t swappedElem = la.data[i];
ne2be(&swappedElem, sizeof(swappedElem));
CHECKED_APPEND(b, &swappedElem, sizeof(swappedElem));
}
return NBT_OK;
}
static nbt_status dump_string_binary(const char* name, struct buffer* b)
{
assert(name);
size_t len = strlen(name);
if(len > 32767 /* SHORT_MAX */)
return NBT_ERR;
{ /* dump the length */
int16_t dumped_len = (int16_t)len;
ne2be(&dumped_len, sizeof dumped_len);
CHECKED_APPEND(b, &dumped_len, sizeof dumped_len);
}
CHECKED_APPEND(b, name, len);
return NBT_OK;
}
static nbt_status __dump_binary(const nbt_node*, bool, struct buffer*);
static nbt_status dump_list_binary(const struct nbt_list* list, struct buffer* b)
{
nbt_type type = list_is_homogenous(list);
size_t len = list_length(&list->entry);
if(len > 2147483647 /* INT_MAX */)
return NBT_ERR;
if(type == TAG_INVALID)
return NBT_ERR;
{
int8_t _type = (int8_t)type;
ne2be(&_type, sizeof _type); /* unnecessary, but left in to keep similar code looking similar */
CHECKED_APPEND(b, &_type, sizeof _type);
}
{
int32_t dumped_len = (int32_t)len;
ne2be(&dumped_len, sizeof dumped_len);
CHECKED_APPEND(b, &dumped_len, sizeof dumped_len);
}
const struct list_head* pos;
list_for_each(pos, &list->entry)
{
const struct nbt_list* entry = list_entry(pos, const struct nbt_list, entry);
nbt_status ret;
if((ret = __dump_binary(entry->data, false, b)) != NBT_OK)
return ret;
}
return NBT_OK;
}
static nbt_status dump_compound_binary(const struct nbt_list* list, struct buffer* b)
{
const struct list_head* pos;
list_for_each(pos, &list->entry)
{
const struct nbt_list* entry = list_entry(pos, const struct nbt_list, entry);
nbt_status ret;
if((ret = __dump_binary(entry->data, true, b)) != NBT_OK)
return ret;
}
/* write out TAG_End */
uint8_t zero = 0;
CHECKED_APPEND(b, &zero, sizeof zero);
return NBT_OK;
}
/*
* @param dump_type Should we dump the type, or just skip it? We need to skip
* when dumping lists, because the list header already says
* the type.
*/
static nbt_status __dump_binary(const nbt_node* tree, bool dump_type, struct buffer* b)
{
if(dump_type)
{ /* write out the type */
int8_t type = (int8_t)tree->type;
CHECKED_APPEND(b, &type, sizeof type);
}
if(tree->name)
{
nbt_status err;
if((err = dump_string_binary(tree->name, b)) != NBT_OK)
return err;
}
#define DUMP_NUM(type, x) do { \
type temp = x; \
ne2be(&temp, sizeof temp); \
CHECKED_APPEND(b, &temp, sizeof temp); \
} while(0)
if(tree->type == TAG_BYTE)
DUMP_NUM(int8_t, tree->payload.tag_byte);
else if(tree->type == TAG_SHORT)
DUMP_NUM(int16_t, tree->payload.tag_short);
else if(tree->type == TAG_INT)
DUMP_NUM(int32_t, tree->payload.tag_int);
else if(tree->type == TAG_LONG)
DUMP_NUM(int64_t, tree->payload.tag_long);
else if(tree->type == TAG_FLOAT)
DUMP_NUM(float, tree->payload.tag_float);
else if(tree->type == TAG_DOUBLE)
DUMP_NUM(double, tree->payload.tag_double);
else if(tree->type == TAG_BYTE_ARRAY)
return dump_byte_array_binary(tree->payload.tag_byte_array, b);
else if(tree->type == TAG_INT_ARRAY)
return dump_int_array_binary(tree->payload.tag_int_array, b);
else if(tree->type == TAG_LONG_ARRAY)
return dump_long_array_binary(tree->payload.tag_long_array, b);
else if(tree->type == TAG_STRING)
return dump_string_binary(tree->payload.tag_string, b);
else if(tree->type == TAG_LIST)
return dump_list_binary(tree->payload.tag_list, b);
else if(tree->type == TAG_COMPOUND)
return dump_compound_binary(tree->payload.tag_compound, b);
else
return NBT_ERR;
return NBT_OK;
#undef DUMP_NUM
}
struct buffer nbt_dump_binary(const nbt_node* tree)
{
struct buffer ret = BUFFER_INIT;
if(tree == NULL) return ret;
nbt_status status = __dump_binary(tree, true, &ret);
if(status != NBT_OK) {
buffer_free(&ret);
errno = status;
}
return ret;
}