adding some stuff i've been working on for the last few days so i can work on it from another computer. this commit is very broken don't try and use it.

This commit is contained in:
hayden 2019-08-15 12:00:21 -05:00
parent 5b37dbc457
commit 877895e373
17 changed files with 2488 additions and 94 deletions

View File

@ -4,17 +4,18 @@ OBJ_DIR = obj
SRC = $(wildcard $(SRC_DIR)/*.c)
OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CFLAGS += -Wall -Iinclude/ -DLOG_USE_COLOR
LDFLAGS += -Llib
LDFLAGS += -lpthread -lcrypto
LDFLAGS += -lpthread -lcrypto -lz
CC = gcc
.PHONY: all clean
.PHONY: all clean debug
all: $(EXE)
debug: CFLAGS += -DDEBUG -g
debug: $(EXE)
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
strip -s $(EXE)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $(EXE)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@

2
README
View File

@ -40,3 +40,5 @@ libraries
dependencies
= = = = = = = = = = = = = = = = = = = = = = = = =
- pthread
- openssl
- zlib

94
include/cNBT/buffer.c Normal file
View File

@ -0,0 +1,94 @@
/*
* -----------------------------------------------------------------------------
* "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 "buffer.h"
#include <assert.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#ifdef __GNUC__
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect( (x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
static int lazy_init(struct buffer* b)
{
assert(b->data == NULL);
size_t cap = 1024;
*b = (struct buffer) {
.data = malloc(cap),
.len = 0,
.cap = cap
};
if(unlikely(b->data == NULL))
return 1;
return 0;
}
void buffer_free(struct buffer* b)
{
assert(b);
free(b->data);
b->data = NULL;
b->len = 0;
b->cap = 0;
}
int buffer_reserve(struct buffer* b, size_t reserved_amount)
{
assert(b);
if(unlikely(b->data == NULL) &&
unlikely(lazy_init(b)))
return 1;
if(likely(b->cap >= reserved_amount))
return 0;
while(b->cap < reserved_amount)
b->cap *= 2;
unsigned char* temp = realloc(b->data, b->cap);
if(unlikely(temp == NULL))
return buffer_free(b), 1;
b->data = temp;
return 0;
}
int buffer_append(struct buffer* b, const void* data, size_t n)
{
assert(b);
if(unlikely(b->data == NULL) &&
unlikely(lazy_init(b)))
return 1;
if(unlikely(buffer_reserve(b, b->len + n)))
return 1;
memcpy(b->data + b->len, data, n);
b->len += n;
return 0;
}

58
include/cNBT/buffer.h Normal file
View File

@ -0,0 +1,58 @@
/*
* -----------------------------------------------------------------------------
* "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.
* -----------------------------------------------------------------------------
*/
#ifndef NBT_BUFFER_H
#define NBT_BUFFER_H
#include <stddef.h>
/*
* A buffer is 'unlimited' storage for raw data. As long as buffer_append is
* used to add data, it will automatically resize to make room. To read the
* data, just access `data' directly.
*/
struct buffer {
unsigned char* data; /* You can access the buffer's raw bytes through this pointer */
size_t len; /* Only accesses in the interval [data, data + len) are defined */
size_t cap; /* Internal use. The allocated size of the buffer. */
};
/*
* Initialize a buffer with this macro.
*
* Usage:
* struct buffer b = BUFFER_INIT;
* OR
* struct buffer b;
* b = BUFFER_INIT;
*/
#define BUFFER_INIT (struct buffer) { NULL, 0, 0 }
/*
* Frees all memory associated with the buffer. The same buffer may be freed
* multiple times without consequence.
*/
void buffer_free(struct buffer* b);
/*
* Ensures there's enough room in the buffer for at least `reserved_amount'
* bytes. Returns non-zero on failure. If such a failure occurs, the buffer
* is deallocated and set to one which can be passed to buffer_free. Any other
* usage is undefined.
*/
int buffer_reserve(struct buffer* b, size_t reserved_amount);
/*
* Copies `n' bytes from `data' into the buffer. Returns non-zero if an
* out-of-memory failure occured. If such a failure occurs, further usage of the
* buffer results in undefined behavior.
*/
int buffer_append(struct buffer* b, const void* data, size_t n);
#endif

128
include/cNBT/check.c Normal file
View File

@ -0,0 +1,128 @@
#include "nbt.h"
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void die(const char* message)
{
fprintf(stderr, "%s\n", message);
exit(1);
}
static void die_with_err(int err)
{
fprintf(stderr, "Error %i: %s\n", err, nbt_error_to_string(err));
exit(1);
}
static nbt_node* get_tree(const char* filename)
{
FILE* fp = fopen(filename, "rb");
if(fp == NULL) die("Could not open the file for reading.");
nbt_node* ret = nbt_parse_file(fp);
if(ret == NULL) die_with_err(errno);
fclose(fp);
return ret;
}
static bool check_size(nbt_node* n, void* aux)
{
(void)n;
int* size = aux;
*size += 1;
return true;
}
int main(int argc, char** argv)
{
if(argc == 1 || strcmp(argv[1], "--help") == 0)
{
printf("Usage: %s [nbt file]\n", argv[0]);
return 0;
}
printf("Getting tree from %s... ", argv[1]);
nbt_node* tree = get_tree(argv[1]);
printf("OK.\n");
/* Use this to refer to the tree in gdb. */
char* the_tree = nbt_dump_ascii(tree);
if(the_tree == NULL)
die_with_err(errno);
{
printf("Checking nbt_map and nbt_size...");
size_t mapped_size = 0;
bool ret = nbt_map(tree, check_size, &mapped_size);
size_t actual_size = nbt_size(tree);
if(!ret)
die("FAILED. nbt_map was terminated by a visitor, even though the visitor wants to do no such thing.");
if(mapped_size != actual_size)
die("FAILED. nbt_map and nbt_size are not playing nice.");
printf("OK.\n");
}
{
printf("Checking nbt_clone... ");
nbt_node* clone = nbt_clone(tree);
if(!nbt_eq(tree, clone))
die("FAILED. Clones not equal.");
nbt_free(tree); /* swap the tree out for its clone */
tree = clone;
printf("OK.\n");
}
FILE* temp = fopen("delete_me.nbt", "wb");
if(temp == NULL) die("Could not open a temporary file.");
nbt_status err;
printf("Dumping binary... ");
if((err = nbt_dump_file(tree, temp, STRAT_GZIP)) != NBT_OK)
die_with_err(err);
printf("OK.\n");
fclose(temp);
temp = fopen("delete_me.nbt", "rb");
if(temp == NULL) die("Could not re-open a temporary file.");
printf("Reparsing... ");
nbt_node* tree_copy = nbt_parse_file(temp);
if(tree_copy == NULL) die_with_err(errno);
printf("OK.\n");
printf("Checking trees... ");
if(!nbt_eq(tree, tree_copy))
{
printf("Original tree:\n%s\n", the_tree);
char* copy = nbt_dump_ascii(tree_copy);
if(copy == NULL) die_with_err(err);
printf("Reparsed tree:\n%s\n", copy);
die("Trees not equal.");
}
printf("OK.\n");
printf("Freeing resources... ");
fclose(temp);
if(remove("delete_me.nbt") == -1)
die("Could not delete delete_me.nbt. Race condition?");
nbt_free(tree);
nbt_free(tree_copy);
printf("OK.\n");
free(the_tree);
return 0;
}

112
include/cNBT/list.h Normal file
View File

@ -0,0 +1,112 @@
#ifndef LIST_H
#define LIST_H
#include <stddef.h>
/*
* Represents a single entry in the list. This must be embedded in your linked
* structure.
*/
struct list_head {
struct list_head *blink, /* back link */
*flink; /* front link */
};
/* The first element is a sentinel. Don't access it. */
#define INIT_LIST_HEAD(head) (head)->flink = (head)->blink = (head)
/* Adds a new element to the beginning of a list. Returns the head of the list
* so that calls may be chained. */
static inline struct list_head* list_add_head(struct list_head* restrict new_element,
struct list_head* restrict head)
{
new_element->flink = head->flink;
new_element->blink = head;
new_element->flink->blink = new_element;
new_element->blink->flink = new_element;
return head;
}
/* Adds a new element to the end of a list. Returns the head of the list so that
* calls may be chained. */
static inline struct list_head* list_add_tail(struct list_head* restrict new_element,
struct list_head* restrict head)
{
new_element->flink = head;
new_element->blink = head->blink;
new_element->flink->blink = new_element;
new_element->blink->flink = new_element;
return head;
}
/* Deletes an element from a list. NOTE: This does not free any memory. */
static inline void list_del(struct list_head* loc)
{
loc->flink->blink = loc->blink;
loc->blink->flink = loc->flink;
loc->flink = NULL;
loc->blink = NULL;
}
/* Tests if the list is empty */
#define list_empty(head) ((head)->flink == (head))
/* Gets a pointer to the overall structure from the list member */
#define list_entry(ptr, type, member) \
((type*)((char*)(ptr) - offsetof(type, member)))
/*
* Iterates over all the elements forward. If you modify the list (such as by
* deleting an element), you should use list_for_each_safe instead.
*/
#define list_for_each(pos, head) \
for((pos) = (head)->flink; \
(pos) != (head); \
(pos) = (pos)->flink)
/* The same as list_for_each, except it traverses the list backwards. */
#define list_for_each_reverse(pos, head) \
for((pos) = (head)->blink; \
(pos) != (head); \
(pos) = (pos)->blink)
/*
* Iterates over a list, where `pos' represents the current element, `n'
* represents temporary storage for the next element, and `head' is the start of
* the list.
*
* As opposed to list_for_each, it is safe to remove `pos' from the list.
*/
#define list_for_each_safe(pos, n, head) \
for((pos) = (head)->flink, (n) = (pos)->flink; \
(pos) != (head); \
(pos) = (n), (n) = (pos)->flink)
/* The same as list_for_each_safe, except it traverses the list backwards. */
#define list_for_each_reverse_safe(pos, p, head) \
for((pos) = (head)->blink, (p) = (pos)->blink; \
(pos) != (head); \
(pos) = (p), (p) = (pos)->blink)
/*
* Returns the length of a list. WARNING: Unlike every other function, this runs
* in O(n). Avoid using it as much as possible, as you will have to walk the
* whole list.
*/
static inline size_t list_length(const struct list_head* head)
{
const struct list_head* cursor;
size_t accum = 0;
list_for_each(cursor, head)
accum++;
return accum;
}
#endif

325
include/cNBT/nbt.h Normal file
View File

@ -0,0 +1,325 @@
/* * -----------------------------------------------------------------------------
* "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.
* -----------------------------------------------------------------------------
*/
#ifndef NBT_H
#define NBT_H
#ifdef __cplusplus
#define restrict __restrict__
extern "C" {
#endif
#include <stdbool.h>
#include <stddef.h> /* for size_t */
#include <stdint.h>
#include <stdio.h> /* for FILE* */
#include "buffer.h" /* for struct buffer */
#include "list.h" /* For struct list_entry etc. */
typedef enum {
NBT_OK = 0, /* No error. */
NBT_ERR = -1, /* Generic error, most likely of the parsing variety. */
NBT_EMEM = -2, /* Out of memory. */
NBT_EIO = -3, /* IO error. */
NBT_EZ = -4 /* Zlib compression/decompression error. */
} nbt_status;
typedef enum {
TAG_INVALID = 0, /* tag_end, but we don't use it in the in-memory representation. */
TAG_BYTE = 1, /* char, 8 bits, signed */
TAG_SHORT = 2, /* short, 16 bits, signed */
TAG_INT = 3, /* long, 32 bits, signed */
TAG_LONG = 4, /* long long, 64 bits, signed */
TAG_FLOAT = 5, /* float, 32 bits, signed */
TAG_DOUBLE = 6, /* double, 64 bits, signed */
TAG_BYTE_ARRAY = 7, /* char *, 8 bits, unsigned, TAG_INT length */
TAG_STRING = 8, /* char *, 8 bits, signed, TAG_SHORT length */
TAG_LIST = 9, /* X *, X bits, TAG_INT length, no names inside */
TAG_COMPOUND = 10, /* nbt_tag * */
TAG_INT_ARRAY = 11
} nbt_type;
typedef enum {
STRAT_GZIP, /* Use a gzip header. Use this if you want your data to be
compressed like level.dat */
STRAT_INFLATE /* Use a zlib header. Use this if you want your data to be
compressed like a chunk. */
} nbt_compression_strategy;
struct nbt_node;
/*
* Represents a single node in the tree. You should switch on `type' and ONLY
* access the union member it signifies. tag_compound and tag_list contain
* recursive nbt_node entries, so those will have to be switched on too. I
* recommended being VERY comfortable with recursion before traversing this
* beast, or at least sticking to the library routines provided.
*/
typedef struct nbt_node {
nbt_type type;
char* name; /* This may be NULL. Check your damn pointers. */
union { /* payload */
/* tag_end has no payload */
int8_t tag_byte;
int16_t tag_short;
int32_t tag_int;
int64_t tag_long;
float tag_float;
double tag_double;
struct nbt_byte_array {
unsigned char* data;
int32_t length;
} tag_byte_array;
struct nbt_int_array {
int32_t* data;
int32_t length;
} tag_int_array;
char* tag_string; /* TODO: technically, this should be a UTF-8 string */
/*
* Design addendum: we make tag_list a linked list instead of an array
* so that nbt_node can be a true recursive data structure. If we used
* an array, it would be incorrect to call free() on any element except
* the first one. By using a linked list, the context of the node is
* irrelevant. One tradeoff of this design is that we don't get tight
* list packing when memory is a concern and huge lists are created.
*
* For more information on using the linked list, see `list.h'. The API
* is well documented.
*/
struct nbt_list {
struct nbt_node* data; /* A single node's data. */
struct list_head entry;
} * tag_list,
* tag_compound;
/*
* The primary difference between a tag_list and a tag_compound is the
* use of the first (sentinel) node.
*
* In an nbt_list, the sentinel node contains a valid data pointer with
* only the type filled in. This is to deal with empty lists which
* still posess types. Therefore, the sentinel's data pointer must be
* deallocated.
*
* In the tag_compound, the only use of the sentinel is to get the
* beginning and end of the doubly linked list. The data pointer is
* unused and set to NULL.
*/
} payload;
} nbt_node;
/***** High Level Loading/Saving Functions *****/
/*
* Loads a NBT tree from a compressed file. The file must have been opened with
* a mode of "rb". If an error occurs, NULL will be returned and errno will be
* set to the appropriate nbt_status. Check your danm pointers.
*/
nbt_node* nbt_parse_file(FILE* fp);
/*
* The same as nbt_parse_file, but opens and closes the file for you.
*/
nbt_node* nbt_parse_path(const char* filename);
/*
* Loads a NBT tree from a compressed block of memory (such as a chunk or a
* pre-loaded level.dat). If an error occurs, NULL will be returned and errno
* will be set to the appropriate nbt_status. Check your damn pointers.
*
* PROTIP: Memory map each individual region file, then call
* nbt_parse_compressed for chunks as needed.
*/
nbt_node* nbt_parse_compressed(const void* chunk_start, size_t length);
/*
* Dumps a tree into a file. Check your damn error codes. This function should
* return NBT_OK.
*
* @see nbt_compression_strategy
*/
nbt_status nbt_dump_file(const nbt_node* tree,
FILE* fp, nbt_compression_strategy);
/*
* Dumps a tree into a block of memory. If an error occurs, a buffer with a NULL
* `data' pointer will be returned, and errno will be set.
*
* 1) Check your damn pointers.
* 2) Don't forget to free buf->data. Memory leaks are bad, mkay?
*
* @see nbt_compression_strategy
*/
struct buffer nbt_dump_compressed(const nbt_node* tree,
nbt_compression_strategy);
/***** Low Level Loading/Saving Functions *****/
/*
* Loads a NBT tree from memory. The tree MUST NOT be compressed. If an error
* occurs, NULL will be returned, and errno will be set to the appropriate
* nbt_status. Please check your damn pointers.
*/
nbt_node* nbt_parse(const void* memory, size_t length);
/*
* Returns a NULL-terminated string as the ascii representation of the tree. If
* an error occurs, NULL will be returned and errno will be set.
*
* 1) Check your damn pointers.
* 2) Don't forget to free the returned pointer. Memory leaks are bad, mkay?
*/
char* nbt_dump_ascii(const nbt_node* tree);
/*
* Returns a buffer representing the uncompressed tree in Notch's official
* binary format. Trees dumped with this function can be regenerated with
* nbt_parse. If an error occurs, a buffer with a NULL `data' pointer will be
* returned, and errno will be set.
*
* 1) Check your damn pointers.
* 2) Don't forget to free buf->data. Memory leaks are bad, mkay?
*/
struct buffer nbt_dump_binary(const nbt_node* tree);
/***** Tree Manipulation Functions *****/
/*
* Clones an existing tree. Returns NULL on memory errors.
*/
nbt_node* nbt_clone(nbt_node*);
/*
* Recursively deallocates a node and all its children. If this is used on a an
* entire tree, no memory will be leaked.
*/
void nbt_free(nbt_node*);
/*
* Recursively frees all the elements of a list, and then frees the list itself.
*/
void nbt_free_list(struct nbt_list*);
/*
* A visitor function to traverse the tree. Return true to keep going, false to
* stop. `aux' is an optional parameter which will be passed to your visitor
* from the parent function.
*/
typedef bool (*nbt_visitor_t)(nbt_node* node, void* aux);
/*
* A function which directs the overall algorithm with its return type.
* `aux' is an optional parameter which will be passed to your predicate from
* the parent function.
*/
typedef bool (*nbt_predicate_t)(const nbt_node* node, void* aux);
/*
* Traverses the tree until a visitor says stop or all elements are exhausted.
* Returns false if it was terminated by a visitor, true otherwise. In most
* cases this can be ignored.
*
* TODO: Is there a way to do this without expensive function pointers? Maybe
* something like list_for_each?
*/
bool nbt_map(nbt_node* tree, nbt_visitor_t, void* aux);
/*
* Returns a new tree, consisting of a copy of all the nodes the predicate
* returned `true' for. If the new tree is empty, this function will return
* NULL. If an out of memory error occured, errno will be set to NBT_EMEM.
*
* TODO: What if I want to keep a tree and all of its children? Do I need to
* augment nbt_node with parent pointers?
*/
nbt_node* nbt_filter(const nbt_node* tree, nbt_predicate_t, void* aux);
/*
* The exact same as nbt_filter, except instead of returning a new tree, the
* existing tree is modified in place, and then returned for convenience.
*/
nbt_node* nbt_filter_inplace(nbt_node* tree, nbt_predicate_t, void* aux);
/*
* Returns the first node which causes the predicate to return true. If all
* nodes are rejected, NULL is returned. If you want to find every instance of
* something, consider using nbt_map with a visitor that keeps track.
*
* Since const-ing `tree' would require me const-ing the return value, you'll
* just have to take my word for it that nbt_find DOES NOT modify the tree.
* Feel free to cast as necessary.
*/
nbt_node* nbt_find(nbt_node* tree, nbt_predicate_t, void* aux);
/*
* Returns the first node with the name `name'. If no node with that name is in
* the tree, returns NULL.
*
* If `name' is NULL, this function will find the first unnamed node.
*
* Since const-ing `tree' would require me const-ing the return value, you'll
* just have to take my word for it that nbt_find DOES NOT modify the tree.
* Feel free to cast as necessary.
*/
nbt_node* nbt_find_by_name(nbt_node* tree, const char* name);
/*
* Returns the first node with the "path" in the tree of `path'. If no such node
* exists, returns NULL. If an element has no name, something like:
*
* root.subelement..data == "root" -> "subelement" -> "" -> "data"
*
* Remember, if multiple elements exist in a sublist which share the same name
* (including ""), the first one will be chosen.
*/
nbt_node* nbt_find_by_path(nbt_node* tree, const char* path);
/* Returns the number of nodes in the tree. */
size_t nbt_size(const nbt_node* tree);
/*
* Returns the Nth item of a list
* Don't use this to iterate through a list, it would be very inefficient
*/
nbt_node* nbt_list_item(nbt_node* list, int n);
/* TODO: More utilities as requests are made and patches contributed. */
/***** Utility Functions *****/
/* Returns true if the trees are identical. */
bool nbt_eq(const nbt_node* restrict a, const nbt_node* restrict b);
/*
* Converts a type to a print-friendly string. The string is statically
* allocated, and therefore does not have to be freed by the user.
*/
const char* nbt_type_to_string(nbt_type);
/*
* Converts an error code into a print-friendly string. The string is statically
* allocated, and therefore does not have to be freed by the user.
*/
const char* nbt_error_to_string(nbt_status);
#ifdef __cplusplus
}
#endif
#endif

295
include/cNBT/nbt_loading.c Normal file
View File

@ -0,0 +1,295 @@
/*
* -----------------------------------------------------------------------------
* "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 <stdio.h>
#include <stdlib.h>
#include <zlib.h>
/*
* zlib resources:
*
* http://zlib.net/manual.html
* http://zlib.net/zlib_how.html
* http://www.gzip.org/zlib/zlib_faq.html
*/
/* The number of bytes to process at a time */
#define CHUNK_SIZE 4096
/*
* Reads a whole file into a buffer. Returns a NULL buffer and sets errno on
* error.
*/
static struct buffer read_file(FILE* fp)
{
struct buffer ret = BUFFER_INIT;
size_t bytes_read;
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
return (errno = NBT_EMEM), buffer_free(&ret), BUFFER_INIT;
bytes_read = fread(ret.data + ret.len, 1, CHUNK_SIZE, fp);
ret.len += bytes_read;
if(ferror(fp))
return (errno = NBT_EIO), buffer_free(&ret), BUFFER_INIT;
} while(!feof(fp));
return ret;
}
static nbt_status write_file(FILE* fp, const void* data, size_t len)
{
const char* cdata = data;
size_t bytes_left = len;
size_t bytes_written;
do {
bytes_written = fwrite(cdata, 1, bytes_left, fp);
if(ferror(fp)) return NBT_EIO;
bytes_left -= bytes_written;
cdata += bytes_written;
} while(bytes_left > 0);
return NBT_OK;
}
/*
* Reads in uncompressed data and returns a buffer with the $(strat)-compressed
* data within. Returns a NULL buffer on failure, and sets errno appropriately.
*/
static struct buffer __compress(const void* mem,
size_t len,
nbt_compression_strategy strat)
{
struct buffer ret = BUFFER_INIT;
errno = NBT_OK;
z_stream stream = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.next_in = (void*)mem,
.avail_in = len
};
/* "The default value is 15"... */
int windowbits = 15;
/* ..."Add 16 to windowBits to write a simple gzip header and trailer around
* the compressed data instead of a zlib wrapper." */
if(strat == STRAT_GZIP)
windowbits += 16;
if(deflateInit2(&stream,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
windowbits,
8,
Z_DEFAULT_STRATEGY
) != Z_OK)
{
errno = NBT_EZ;
return BUFFER_INIT;
}
assert(stream.avail_in == len); /* I'm not sure if zlib will clobber this */
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
{
errno = NBT_EMEM;
goto compression_error;
}
stream.next_out = ret.data + ret.len;
stream.avail_out = CHUNK_SIZE;
if(deflate(&stream, Z_FINISH) == Z_STREAM_ERROR)
goto compression_error;
ret.len += CHUNK_SIZE - stream.avail_out;
} while(stream.avail_out == 0);
(void)deflateEnd(&stream);
return ret;
compression_error:
if(errno == NBT_OK)
errno = NBT_EZ;
(void)deflateEnd(&stream);
buffer_free(&ret);
return BUFFER_INIT;
}
/*
* Reads in zlib-compressed data, and returns a buffer with the decompressed
* data within. Returns a NULL buffer on failure, and sets errno appropriately.
*/
static struct buffer __decompress(const void* mem, size_t len)
{
struct buffer ret = BUFFER_INIT;
errno = NBT_OK;
z_stream stream = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.next_in = (void*)mem,
.avail_in = len
};
/* "Add 32 to windowBits to enable zlib and gzip decoding with automatic
* header detection" */
if(inflateInit2(&stream, 15 + 32) != Z_OK)
{
errno = NBT_EZ;
return BUFFER_INIT;
}
int zlib_ret;
do {
if(buffer_reserve(&ret, ret.len + CHUNK_SIZE))
{
errno = NBT_EMEM;
goto decompression_error;
}
stream.avail_out = CHUNK_SIZE;
stream.next_out = (unsigned char*)ret.data + ret.len;
switch((zlib_ret = inflate(&stream, Z_NO_FLUSH)))
{
case Z_MEM_ERROR:
errno = NBT_EMEM;
/* fall through */
case Z_DATA_ERROR: case Z_NEED_DICT:
goto decompression_error;
default:
/* update our buffer length to reflect the new data */
ret.len += CHUNK_SIZE - stream.avail_out;
}
} while(stream.avail_out == 0);
/*
* If we're at the end of the input data, we'd sure as hell be at the end
* of the zlib stream.
*/
if(zlib_ret != Z_STREAM_END) goto decompression_error;
(void)inflateEnd(&stream);
return ret;
decompression_error:
if(errno == NBT_OK)
errno = NBT_EZ;
(void)inflateEnd(&stream);
buffer_free(&ret);
return BUFFER_INIT;
}
/*
* No incremental parsing goes on. We just dump the whole compressed file into
* memory then pass the job off to nbt_parse_chunk.
*/
nbt_node* nbt_parse_file(FILE* fp)
{
errno = NBT_OK;
struct buffer compressed = read_file(fp);
if(compressed.data == NULL)
return NULL;
nbt_node* ret = nbt_parse_compressed(compressed.data, compressed.len);
buffer_free(&compressed);
return ret;
}
nbt_node* nbt_parse_path(const char* filename)
{
FILE* fp = fopen(filename, "rb");
if(fp == NULL)
{
errno = NBT_EIO;
return NULL;
}
nbt_node* r = nbt_parse_file(fp);
fclose(fp);
return r;
}
nbt_node* nbt_parse_compressed(const void* chunk_start, size_t length)
{
struct buffer decompressed = __decompress(chunk_start, length);
if(decompressed.data == NULL)
return NULL;
nbt_node* ret = nbt_parse(decompressed.data, decompressed.len);
buffer_free(&decompressed);
return ret;
}
/*
* Once again, all we're doing is handing the actual compression off to
* nbt_dump_compressed, then dumping it into the file.
*/
nbt_status nbt_dump_file(const nbt_node* tree, FILE* fp, nbt_compression_strategy strat)
{
struct buffer compressed = nbt_dump_compressed(tree, strat);
if(compressed.data == NULL)
return (nbt_status)errno;
nbt_status ret = write_file(fp, compressed.data, compressed.len);
buffer_free(&compressed);
return ret;
}
struct buffer nbt_dump_compressed(const nbt_node* tree, nbt_compression_strategy strat)
{
struct buffer uncompressed = nbt_dump_binary(tree);
if(uncompressed.data == NULL)
return BUFFER_INIT;
struct buffer compressed = __compress(uncompressed.data, uncompressed.len, strat);
buffer_free(&uncompressed);
return compressed;
}

770
include/cNBT/nbt_parsing.c Normal file
View File

@ -0,0 +1,770 @@
/*
* -----------------------------------------------------------------------------
* "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>
/* 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 buffer* b, const char* restrict format, ...)
{
va_list args;
int siz;
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;
}
/*
* 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_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 buffer* b, size_t amount)
{
size_t spaces = amount * 4; /* 4 spaces per indent */
char temp[spaces + 1];
for(size_t i = 0; i < spaces; ++i)
temp[i] = ' ';
temp[spaces] = '\0';
bprintf(b, "%s", temp);
}
static nbt_status __nbt_dump_ascii(const nbt_node*, struct buffer*, size_t ident);
/* prints the node's name, or (null) if it has none. */
#define SAFE_NAME(node) ((node)->name ? (node)->name : "<null>")
static void dump_byte_array(const struct nbt_byte_array ba, struct buffer* b)
{
assert(ba.length >= 0);
bprintf(b, "[ ");
for(int32_t i = 0; i < ba.length; ++i)
bprintf(b, "%u ", +ba.data[i]);
bprintf(b, "]");
}
static void dump_int_array(const struct nbt_int_array ia, struct buffer* b)
{
assert(ia.length >= 0);
bprintf(b, "[ ");
for(int32_t i = 0; i < ia.length; ++i)
bprintf(b, "%u ", +ia.data[i]);
bprintf(b, "]");
}
static nbt_status dump_list_contents_ascii(const struct nbt_list* list, struct buffer* b, 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 err;
if((err = __nbt_dump_ascii(entry->data, b, ident)) != NBT_OK)
return err;
}
return NBT_OK;
}
static nbt_status __nbt_dump_ascii(const nbt_node* tree, struct buffer* b, size_t ident)
{
if(tree == NULL) return NBT_OK;
indent(b, ident);
if(tree->type == TAG_BYTE)
bprintf(b, "TAG_Byte(\"%s\"): %i\n", SAFE_NAME(tree), (int)tree->payload.tag_byte);
else if(tree->type == TAG_SHORT)
bprintf(b, "TAG_Short(\"%s\"): %i\n", SAFE_NAME(tree), (int)tree->payload.tag_short);
else if(tree->type == TAG_INT)
bprintf(b, "TAG_Int(\"%s\"): %i\n", SAFE_NAME(tree), (int)tree->payload.tag_int);
else if(tree->type == TAG_LONG)
bprintf(b, "TAG_Long(\"%s\"): %" PRIi64 "\n", SAFE_NAME(tree), tree->payload.tag_long);
else if(tree->type == TAG_FLOAT)
bprintf(b, "TAG_Float(\"%s\"): %f\n", SAFE_NAME(tree), (double)tree->payload.tag_float);
else if(tree->type == TAG_DOUBLE)
bprintf(b, "TAG_Double(\"%s\"): %f\n", SAFE_NAME(tree), tree->payload.tag_double);
else if(tree->type == TAG_BYTE_ARRAY)
{
bprintf(b, "TAG_Byte_Array(\"%s\"): ", SAFE_NAME(tree));
dump_byte_array(tree->payload.tag_byte_array, b);
bprintf(b, "\n");
}
else if(tree->type == TAG_INT_ARRAY)
{
bprintf(b, "Tag_Int_Array(\"%s\"): ", SAFE_NAME(tree));
dump_int_array(tree->payload.tag_int_array, b);
bprintf(b, "\n");
}
else if(tree->type == TAG_STRING)
{
if(tree->payload.tag_string == NULL)
return NBT_ERR;
bprintf(b, "TAG_String(\"%s\"): %s\n", SAFE_NAME(tree), tree->payload.tag_string);
}
else if(tree->type == TAG_LIST)
{
bprintf(b, "TAG_List(\"%s\") [%s]\n", SAFE_NAME(tree), nbt_type_to_string(tree->payload.tag_list->data->type));
indent(b, ident);
bprintf(b, "{\n");
nbt_status err = dump_list_contents_ascii(tree->payload.tag_list, b, ident + 1);
indent(b, ident);
bprintf(b, "}\n");
if(err != NBT_OK)
return err;
}
else if(tree->type == TAG_COMPOUND)
{
bprintf(b, "TAG_Compound(\"%s\")\n", SAFE_NAME(tree));
indent(b, ident);
bprintf(b, "{\n");
nbt_status err = dump_list_contents_ascii(tree->payload.tag_compound, b, ident + 1);
indent(b, ident);
bprintf(b, "}\n");
if(err != NBT_OK)
return err;
}
else
return NBT_ERR;
return NBT_OK;
}
char* nbt_dump_ascii(const nbt_node* tree)
{
errno = NBT_OK;
assert(tree);
struct buffer b = BUFFER_INIT;
if((errno = __nbt_dump_ascii(tree, &b, 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;
}
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_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;
assert(type != TAG_INVALID);
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_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)
{
errno = NBT_OK;
if(tree == NULL) return BUFFER_INIT;
struct buffer ret = BUFFER_INIT;
errno = __dump_binary(tree, true, &ret);
return ret;
}

528
include/cNBT/nbt_treeops.c Normal file
View File

@ -0,0 +1,528 @@
/*
* -----------------------------------------------------------------------------
* "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 <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
/* strdup isn't standard. GNU extension. */
static inline char* _nbt_strdup(const char* s)
{
char* r = malloc(strlen(s) + 1);
if(r == NULL) return NULL;
strcpy(r, s);
return r;
}
#define CHECKED_MALLOC(var, n, on_error) do { \
if((var = malloc(n)) == NULL) \
{ \
errno = NBT_EMEM; \
on_error; \
} \
} while(0)
void nbt_free_list(struct nbt_list* list)
{
if (!list)
return;
struct list_head* current;
struct list_head* temp;
list_for_each_safe(current, temp, &list->entry)
{
struct nbt_list* entry = list_entry(current, struct nbt_list, entry);
nbt_free(entry->data);
free(entry);
}
free(list->data);
free(list);
}
void nbt_free(nbt_node* tree)
{
if(tree == NULL) return;
if(tree->type == TAG_LIST)
nbt_free_list(tree->payload.tag_list);
else if (tree->type == TAG_COMPOUND)
nbt_free_list(tree->payload.tag_compound);
else if(tree->type == TAG_BYTE_ARRAY)
free(tree->payload.tag_byte_array.data);
else if(tree->type == TAG_INT_ARRAY)
free(tree->payload.tag_int_array.data);
else if(tree->type == TAG_STRING)
free(tree->payload.tag_string);
free(tree->name);
free(tree);
}
static struct nbt_list* clone_list(struct nbt_list* list)
{
/* even empty lists are valid pointers! */
assert(list);
struct nbt_list* ret;
CHECKED_MALLOC(ret, sizeof *ret, goto clone_error);
INIT_LIST_HEAD(&ret->entry);
ret->data = NULL;
if(list->data != NULL)
{
CHECKED_MALLOC(ret->data, sizeof *ret->data, goto clone_error);
ret->data->type = list->data->type;
}
struct list_head* pos;
list_for_each(pos, &list->entry)
{
struct nbt_list* current = list_entry(pos, struct nbt_list, entry);
struct nbt_list* new;
CHECKED_MALLOC(new, sizeof *new, goto clone_error);
new->data = nbt_clone(current->data);
if(new->data == NULL)
{
free(new);
goto clone_error;
}
list_add_tail(&new->entry, &ret->entry);
}
return ret;
clone_error:
nbt_free_list(ret);
return NULL;
}
/* same as strdup, but handles NULL gracefully */
static inline char* safe_strdup(const char* s)
{
return s ? _nbt_strdup(s) : NULL;
}
nbt_node* nbt_clone(nbt_node* tree)
{
if(tree == NULL) return NULL;
assert(tree->type != TAG_INVALID);
nbt_node* ret = NULL;
CHECKED_MALLOC(ret, sizeof *ret, return NULL);
ret->type = tree->type;
ret->name = safe_strdup(tree->name);
if(tree->name && ret->name == NULL) goto clone_error;
if(tree->type == TAG_STRING)
{
ret->payload.tag_string = _nbt_strdup(tree->payload.tag_string);
if(ret->payload.tag_string == NULL) goto clone_error;
}
else if(tree->type == TAG_BYTE_ARRAY)
{
unsigned char* newbuf;
CHECKED_MALLOC(newbuf, tree->payload.tag_byte_array.length, goto clone_error);
memcpy(newbuf,
tree->payload.tag_byte_array.data,
tree->payload.tag_byte_array.length);
ret->payload.tag_byte_array.data = newbuf;
ret->payload.tag_byte_array.length = tree->payload.tag_byte_array.length;
}
else if(tree->type == TAG_INT_ARRAY)
{
int32_t* newbuf;
CHECKED_MALLOC(newbuf, tree->payload.tag_int_array.length * sizeof(int32_t), goto clone_error);
memcpy(newbuf,
tree->payload.tag_int_array.data,
tree->payload.tag_int_array.length);
ret->payload.tag_int_array.data = newbuf;
ret->payload.tag_int_array.length = tree->payload.tag_int_array.length;
}
else if(tree->type == TAG_LIST)
{
ret->payload.tag_list = clone_list(tree->payload.tag_list);
if(ret->payload.tag_list == NULL) goto clone_error;
}
else if(tree->type == TAG_COMPOUND)
{
ret->payload.tag_compound = clone_list(tree->payload.tag_compound);
if(ret->payload.tag_compound == NULL) goto clone_error;
}
else
{
ret->payload = tree->payload;
}
return ret;
clone_error:
if(ret) free(ret->name);
free(ret);
return NULL;
}
bool nbt_map(nbt_node* tree, nbt_visitor_t v, void* aux)
{
assert(v);
if(tree == NULL) return true;
if(!v(tree, aux)) return false;
/* And if the item is a list or compound, recurse through each of their elements. */
if(tree->type == TAG_COMPOUND)
{
struct list_head* pos;
list_for_each(pos, &tree->payload.tag_compound->entry)
if(!nbt_map(list_entry(pos, struct nbt_list, entry)->data, v, aux))
return false;
}
if(tree->type == TAG_LIST)
{
struct list_head* pos;
list_for_each(pos, &tree->payload.tag_list->entry)
if(!nbt_map(list_entry(pos, struct nbt_list, entry)->data, v, aux))
return false;
}
return true;
}
/* Only returns NULL on error. An empty list is still a valid pointer */
static struct nbt_list* filter_list(const struct nbt_list* list, nbt_predicate_t predicate, void* aux)
{
assert(list);
struct nbt_list* ret = NULL;
CHECKED_MALLOC(ret, sizeof *ret, goto filter_error);
ret->data = NULL;
INIT_LIST_HEAD(&ret->entry);
const struct list_head* pos;
list_for_each(pos, &list->entry)
{
const struct nbt_list* p = list_entry(pos, struct nbt_list, entry);
nbt_node* new_node = nbt_filter(p->data, predicate, aux);
if(errno != NBT_OK) goto filter_error;
if(new_node == NULL) continue;
struct nbt_list* new_entry;
CHECKED_MALLOC(new_entry, sizeof *new_entry, goto filter_error);
new_entry->data = new_node;
list_add_tail(&new_entry->entry, &ret->entry);
}
return ret;
filter_error:
if(errno == NBT_OK)
errno = NBT_EMEM;
nbt_free_list(ret);
return NULL;
}
nbt_node* nbt_filter(const nbt_node* tree, nbt_predicate_t filter, void* aux)
{
assert(filter);
errno = NBT_OK;
if(tree == NULL) return NULL;
if(!filter(tree, aux)) return NULL;
nbt_node* ret = NULL;
CHECKED_MALLOC(ret, sizeof *ret, goto filter_error);
ret->type = tree->type;
ret->name = safe_strdup(tree->name);
if(tree->name && ret->name == NULL) goto filter_error;
if(tree->type == TAG_STRING)
{
ret->payload.tag_string = _nbt_strdup(tree->payload.tag_string);
if(ret->payload.tag_string == NULL) goto filter_error;
}
else if(tree->type == TAG_BYTE_ARRAY)
{
CHECKED_MALLOC(ret->payload.tag_byte_array.data,
tree->payload.tag_byte_array.length,
goto filter_error);
memcpy(ret->payload.tag_byte_array.data,
tree->payload.tag_byte_array.data,
tree->payload.tag_byte_array.length);
ret->payload.tag_byte_array.length = tree->payload.tag_byte_array.length;
}
else if(tree->type == TAG_INT_ARRAY)
{
CHECKED_MALLOC(ret->payload.tag_int_array.data,
tree->payload.tag_int_array.length * sizeof(int32_t),
goto filter_error);
memcpy(ret->payload.tag_int_array.data,
tree->payload.tag_int_array.data,
tree->payload.tag_int_array.length);
ret->payload.tag_int_array.length = tree->payload.tag_int_array.length;
}
/* Okay, we want to keep this node, but keep traversing the tree! */
else if(tree->type == TAG_LIST)
{
ret->payload.tag_list = filter_list(tree->payload.tag_list, filter, aux);
if(ret->payload.tag_list == NULL) goto filter_error;
}
else if(tree->type == TAG_COMPOUND)
{
ret->payload.tag_compound = filter_list(tree->payload.tag_compound, filter, aux);
if(ret->payload.tag_compound == NULL) goto filter_error;
}
else
{
ret->payload = tree->payload;
}
return ret;
filter_error:
if(errno == NBT_OK)
errno = NBT_EMEM;
if(ret) free(ret->name);
free(ret);
return NULL;
}
nbt_node* nbt_filter_inplace(nbt_node* tree, nbt_predicate_t filter, void* aux)
{
assert(filter);
if(tree == NULL) return NULL;
if(!filter(tree, aux)) return nbt_free(tree), NULL;
if(tree->type != TAG_LIST &&
tree->type != TAG_COMPOUND) return tree;
struct list_head* pos;
struct list_head* n;
struct nbt_list* list = tree->type == TAG_LIST ? tree->payload.tag_list : tree->payload.tag_compound;
list_for_each_safe(pos, n, &list->entry)
{
struct nbt_list* cur = list_entry(pos, struct nbt_list, entry);
cur->data = nbt_filter_inplace(cur->data, filter, aux);
if(cur->data == NULL)
{
list_del(pos);
free(cur);
}
}
return tree;
}
nbt_node* nbt_find(nbt_node* tree, nbt_predicate_t predicate, void* aux)
{
if(tree == NULL) return NULL;
if(predicate(tree, aux)) return tree;
if(tree->type != TAG_LIST &&
tree->type != TAG_COMPOUND) return NULL;
struct list_head* pos;
struct nbt_list* list = tree->type == TAG_LIST ? tree->payload.tag_list : tree->payload.tag_compound;
list_for_each(pos, &list->entry)
{
struct nbt_list* p = list_entry(pos, struct nbt_list, entry);
struct nbt_node* found;
if((found = nbt_find(p->data, predicate, aux)))
return found;
}
return NULL;
}
static bool names_are_equal(const nbt_node* node, void* vname)
{
const char* name = vname;
assert(node);
if(name == NULL && node->name == NULL)
return true;
if(name == NULL || node->name == NULL)
return false;
return strcmp(node->name, name) == 0;
}
nbt_node* nbt_find_by_name(nbt_node* tree, const char* name)
{
return nbt_find(tree, &names_are_equal, (void*)name);
}
/*
* Returns the index of the first occurence of `c' in `s', or the index of the
* NULL-terminator. Whichever comes first.
*/
static size_t index_of(const char* s, char c)
{
const char* p = s;
for(; *p; p++)
if(*p == c)
return p - s;
return p - s;
}
/*
* Pretends that s1 ends after `len' bytes, and does a strcmp.
*/
static int partial_strcmp(const char* s1, size_t len, const char* s2)
{
assert(s1);
if(s2 == NULL) return len != 0;
int r;
if((r = strncmp(s1, s2, len)) != 0)
return r;
/* at this point, the first `len' characters match. Check for NULL. */
return s2[len] != '\0';
}
/*
* Format:
* current_name.[other shit]
* OR
* current_name'\0'
*
* where current_name can be empty.
*/
nbt_node* nbt_find_by_path(nbt_node* tree, const char* path)
{
assert(tree);
assert(path);
/* The end of the "current_name" piece. */
size_t e = index_of(path, '.');
bool names_match = partial_strcmp(path, e, tree->name) == 0;
/* Names don't match. These aren't the droids you're looking for. */
if(!names_match) return NULL;
/* We're a leaf node, and the names match. Wooo found it. */
if(path[e] == '\0') return tree;
/*
* Initial names match, but the string isn't at the end. We're expecting a
* list, but haven't hit one.
*/
if(tree->type != TAG_LIST && tree->type != TAG_COMPOUND) return NULL;
/* At this point, the inital names match, and we're not at a leaf node. */
struct list_head* pos;
struct nbt_list* list = tree->type == TAG_LIST ? tree->payload.tag_list : tree->payload.tag_compound;
list_for_each(pos, &list->entry)
{
struct nbt_list* elem = list_entry(pos, struct nbt_list, entry);
nbt_node* r;
if((r = nbt_find_by_path(elem->data, path + e + 1)) != NULL)
return r;
}
/* Wasn't found in the list (or the current node isn't a list). Give up. */
return NULL;
}
/* Gets the length of the list, plus the length of all its children. */
static inline size_t nbt_full_list_length(struct nbt_list* list)
{
size_t accum = 0;
struct list_head* pos;
list_for_each(pos, &list->entry)
accum += nbt_size(list_entry(pos, const struct nbt_list, entry)->data);
return accum;
}
size_t nbt_size(const nbt_node* tree)
{
if(tree == NULL)
return 0;
if(tree->type == TAG_LIST)
return nbt_full_list_length(tree->payload.tag_list) + 1;
if(tree->type == TAG_COMPOUND)
return nbt_full_list_length(tree->payload.tag_compound) + 1;
return 1;
}
nbt_node* nbt_list_item(nbt_node* list, int n) {
if (list == NULL || (list->type != TAG_LIST && list->type != TAG_COMPOUND))
return NULL;
int i = 0;
const struct list_head* pos;
list_for_each(pos, &list->payload.tag_list->entry) {
if (i++ == n)
return list_entry(pos, struct nbt_list, entry)->data;
}
return NULL;
}

145
include/cNBT/nbt_util.c Normal file
View File

@ -0,0 +1,145 @@
/*
* -----------------------------------------------------------------------------
* "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 <string.h>
const char* nbt_type_to_string(nbt_type t)
{
#define DEF_CASE(name) case name: return #name;
switch(t)
{
case 0: return "TAG_END";
DEF_CASE(TAG_BYTE);
DEF_CASE(TAG_SHORT);
DEF_CASE(TAG_INT);
DEF_CASE(TAG_LONG);
DEF_CASE(TAG_FLOAT);
DEF_CASE(TAG_DOUBLE);
DEF_CASE(TAG_BYTE_ARRAY);
DEF_CASE(TAG_STRING);
DEF_CASE(TAG_LIST);
DEF_CASE(TAG_COMPOUND);
DEF_CASE(TAG_INT_ARRAY);
default:
return "TAG_UNKNOWN";
}
#undef DEF_CASE
}
const char* nbt_error_to_string(nbt_status s)
{
switch(s)
{
case NBT_OK:
return "No error.";
case NBT_ERR:
return "NBT tree is corrupt.";
case NBT_EMEM:
return "Out of memory. You should buy some RAM.";
case NBT_EIO:
return "IO Error. Nonexistant/corrupt file?";
case NBT_EZ:
return "Fatal zlib error. Corrupt file?";
default:
return "Unknown error.";
}
}
/* Returns 1 if one is null and the other isn't. */
static int safe_strcmp(const char* a, const char* b)
{
if(a == NULL)
return b != NULL; /* a is NULL, b is not */
if(b == NULL) /* b is NULL, a is not */
return 1;
return strcmp(a, b);
}
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
static inline bool floats_are_close(double a, double b)
{
double epsilon = 0.000001;
return (min(a, b) + epsilon) >= max(a, b);
}
bool nbt_eq(const nbt_node* restrict a, const nbt_node* restrict b)
{
if(a->type != b->type)
return false;
if(safe_strcmp(a->name, b->name) != 0)
return false;
switch(a->type)
{
case TAG_BYTE:
return a->payload.tag_byte == b->payload.tag_byte;
case TAG_SHORT:
return a->payload.tag_short == b->payload.tag_short;
case TAG_INT:
return a->payload.tag_int == b->payload.tag_int;
case TAG_LONG:
return a->payload.tag_long == b->payload.tag_long;
case TAG_FLOAT:
return floats_are_close((double)a->payload.tag_float, (double)b->payload.tag_float);
case TAG_DOUBLE:
return floats_are_close(a->payload.tag_double, b->payload.tag_double);
case TAG_BYTE_ARRAY:
if(a->payload.tag_byte_array.length != b->payload.tag_byte_array.length) return false;
return memcmp(a->payload.tag_byte_array.data,
b->payload.tag_byte_array.data,
a->payload.tag_byte_array.length) == 0;
case TAG_INT_ARRAY:
if(a->payload.tag_int_array.length != b->payload.tag_int_array.length) return false;
return memcmp(a->payload.tag_int_array.data,
b->payload.tag_int_array.data,
a->payload.tag_int_array.length) == 0;
case TAG_STRING:
return strcmp(a->payload.tag_string, b->payload.tag_string) == 0;
case TAG_LIST:
case TAG_COMPOUND:
{
struct list_head *ai, *bi;
struct nbt_list* alist = a->type == TAG_LIST ? a->payload.tag_list : a->payload.tag_compound;
struct nbt_list* blist = b->type == TAG_LIST ? b->payload.tag_list : b->payload.tag_compound;
for(ai = alist->entry.flink, bi = blist->entry.flink;
ai != &alist->entry && bi != &blist->entry;
ai = ai->flink, bi = bi->flink)
{
struct nbt_list* ae = list_entry(ai, struct nbt_list, entry);
struct nbt_list* be = list_entry(bi, struct nbt_list, entry);
if(!nbt_eq(ae->data, be->data))
return false;
}
/* if there are still elements left in either list... */
if(ai != &alist->entry || bi != &blist->entry)
return false;
return true;
}
default: /* wtf invalid type */
return false;
}
}

View File

@ -1,69 +0,0 @@
/* see LICENSE file for copyright and license details. */
#ifndef COBBLE_NBT_H
#define COBBLE_NBT_H
struct nbt_node_s;
typedef enum {
TAG_END,
TAG_BYTE,
TAG_SHORT,
TAG_INT,
TAG_LONG,
TAG_FLOAT,
TAG_DOUBLE,
TAG_BYTE_ARRAY,
TAG_STRING,
TAG_LIST,
TAG_COMPOUND,
TAG_INT_ARRAY,
TAG_LONG_ARRAY
} NBTType;
typedef struct {
int32_t length;
int8_t *data;
} NBTByteArray;
typedef struct {
NBTType type;
int32_t length;
void *data;
} NBTTagList;
typedef struct {
int32_t size;
int32_t *data;
} NBTIntArray;
typedef struct {
int32_t size;
int64_t *data;
} NBTLongArray;
typedef struct {
int32_t name_length;
char *name;
struct nbt_node_s *children;
} NBTCompound;
typedef struct nbt_node_s {
NBTType type;
char *name;
union {
int8_t tag_byte;
int16_t tag_short;
int32_t tag_int;
int64_t tag_long;
float tag_float;
double tag_double;
NBTByteArray tag_byte_array;
char *tag_string;
NBTTagList tag_list;
NBTCompound tag_compound;
} payload;
} NBTNode;
bool nbt_parse_file(NBTNode *node, FILE *fp);
bool nbt_parse_bytes(NBTNode *node, uint8_t *data, size_t data_len);
#endif

View File

@ -4,12 +4,13 @@
#define SAFE_MALLOC(n) smalloc(n, __LINE__)
#define SAFE_CALLOC(n) scalloc(n, __LINE__)
#define SAFE_REALLOC(ptr, n) srealloc(ptr, n, __LINE__)
/* malloc that throws an error and exits if we run out of memory */
void* smalloc(size_t size, unsigned long line);
/* same as above but with calloc. block size is 1 */
void* scalloc(size_t size, unsigned long line);
char* string_dup(char* from);
/* same as above but with realloc */
void srealloc(void *ptr, size_t size, unsigned long line);
#endif

BIN
level.dat Normal file

Binary file not shown.

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <time.h>
#include "cNBT/nbt.h"
#include <dyad/dyad.c>
#include <log/log.h>
@ -37,6 +38,11 @@ main(int argc, char** argv)
// create and setup the server struct
Server *server = SAFE_MALLOC(sizeof(Server));
log_info("Loading world");
FILE* f = fopen("level.dat", "rb");
nbt_node* root = nbt_parse_file(f);
fclose(f);
log_info("Starting network thread");
dyad_init();
server->dyad_stream = dyad_newStream();

View File

@ -1,16 +0,0 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <cobble/nbt.h>
bool
nbt_parse_file(NBTNode *node, FILE *fp)
{
}
bool
nbt_parse_bytes(NBTNode *node, uint8_t *data, size_t data_len)
{
}

View File

@ -8,7 +8,9 @@
void*
smalloc(size_t size, unsigned long line)
{
void* p = malloc(size);
void *p;
p = malloc(size);
if (!p) {
fprintf(stderr, "ran out of memory in [%s:%lu]", __FILE__, line);
exit(EXIT_FAILURE);
@ -19,10 +21,22 @@ smalloc(size_t size, unsigned long line)
void*
scalloc(size_t size, unsigned long line)
{
void* p = calloc(size, 1);
void *p;
p = calloc(size, 1);
if (!p) {
fprintf(stderr, "ran out of memory in [%s:%lu]", __FILE__, line);
exit(EXIT_FAILURE);
}
return p;
}
void
srealloc(void *ptr, size_t size, unsigned long line)
{
realloc(ptr, (unsigned long)size);
if (!ptr) {
fprintf(stderr, "ran out of memory in [%s:%lu]", __FILE__, line);
exit(EXIT_FAILURE);
}
}