From 877895e3738364bb7ae453b112c8513d4cf2d0db Mon Sep 17 00:00:00 2001 From: stilbruch Date: Thu, 15 Aug 2019 12:00:21 -0500 Subject: [PATCH] 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. --- Makefile | 11 +- README | 2 + include/cNBT/buffer.c | 94 +++++ include/cNBT/buffer.h | 58 +++ include/cNBT/check.c | 128 ++++++ include/cNBT/list.h | 112 ++++++ include/cNBT/nbt.h | 325 ++++++++++++++++ include/cNBT/nbt_loading.c | 295 ++++++++++++++ include/cNBT/nbt_parsing.c | 770 +++++++++++++++++++++++++++++++++++++ include/cNBT/nbt_treeops.c | 528 +++++++++++++++++++++++++ include/cNBT/nbt_util.c | 145 +++++++ include/cobble/nbt.h | 69 ---- include/cobble/util.h | 5 +- level.dat | Bin 0 -> 824 bytes src/main.c | 6 + src/nbt.c | 16 - src/util.c | 18 +- 17 files changed, 2488 insertions(+), 94 deletions(-) create mode 100644 include/cNBT/buffer.c create mode 100644 include/cNBT/buffer.h create mode 100644 include/cNBT/check.c create mode 100644 include/cNBT/list.h create mode 100644 include/cNBT/nbt.h create mode 100644 include/cNBT/nbt_loading.c create mode 100644 include/cNBT/nbt_parsing.c create mode 100644 include/cNBT/nbt_treeops.c create mode 100644 include/cNBT/nbt_util.c delete mode 100644 include/cobble/nbt.h create mode 100644 level.dat delete mode 100644 src/nbt.c diff --git a/Makefile b/Makefile index 90fbc2f..59a3ff9 100644 --- a/Makefile +++ b/Makefile @@ -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 $@ diff --git a/README b/README index d85b548..3e84263 100644 --- a/README +++ b/README @@ -40,3 +40,5 @@ libraries dependencies = = = = = = = = = = = = = = = = = = = = = = = = = - pthread +- openssl +- zlib diff --git a/include/cNBT/buffer.c b/include/cNBT/buffer.c new file mode 100644 index 0000000..c5d5d87 --- /dev/null +++ b/include/cNBT/buffer.c @@ -0,0 +1,94 @@ +/* +* ----------------------------------------------------------------------------- +* "THE BEER-WARE LICENSE" (Revision 42): +* Lukas Niederbremer and Clark Gaebel +* 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 +#include +#include +#include + +#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; +} + diff --git a/include/cNBT/buffer.h b/include/cNBT/buffer.h new file mode 100644 index 0000000..478d737 --- /dev/null +++ b/include/cNBT/buffer.h @@ -0,0 +1,58 @@ +/* + * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 + +/* + * 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 diff --git a/include/cNBT/check.c b/include/cNBT/check.c new file mode 100644 index 0000000..54d7309 --- /dev/null +++ b/include/cNBT/check.c @@ -0,0 +1,128 @@ +#include "nbt.h" + +#include +#include +#include +#include +#include + +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; +} diff --git a/include/cNBT/list.h b/include/cNBT/list.h new file mode 100644 index 0000000..3c8d234 --- /dev/null +++ b/include/cNBT/list.h @@ -0,0 +1,112 @@ +#ifndef LIST_H +#define LIST_H + +#include + +/* + * 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 diff --git a/include/cNBT/nbt.h b/include/cNBT/nbt.h new file mode 100644 index 0000000..362b6bc --- /dev/null +++ b/include/cNBT/nbt.h @@ -0,0 +1,325 @@ +/* * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 +#include /* for size_t */ +#include +#include /* 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 + diff --git a/include/cNBT/nbt_loading.c b/include/cNBT/nbt_loading.c new file mode 100644 index 0000000..4ee0597 --- /dev/null +++ b/include/cNBT/nbt_loading.c @@ -0,0 +1,295 @@ +/* + * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 +#include +#include +#include +#include + +/* + * 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; +} diff --git a/include/cNBT/nbt_parsing.c b/include/cNBT/nbt_parsing.c new file mode 100644 index 0000000..322b1a7 --- /dev/null +++ b/include/cNBT/nbt_parsing.c @@ -0,0 +1,770 @@ +/* + * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 +#include +#include +#include +#include +#include +#include + +/* 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 : "") + +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; +} diff --git a/include/cNBT/nbt_treeops.c b/include/cNBT/nbt_treeops.c new file mode 100644 index 0000000..f25c5ae --- /dev/null +++ b/include/cNBT/nbt_treeops.c @@ -0,0 +1,528 @@ +/* + * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 +#include +#include +#include + +/* 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; +} diff --git a/include/cNBT/nbt_util.c b/include/cNBT/nbt_util.c new file mode 100644 index 0000000..57ebde4 --- /dev/null +++ b/include/cNBT/nbt_util.c @@ -0,0 +1,145 @@ +/* + * ----------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * Lukas Niederbremer and Clark Gaebel + * 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 + +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; + } +} + diff --git a/include/cobble/nbt.h b/include/cobble/nbt.h deleted file mode 100644 index 3bb34d0..0000000 --- a/include/cobble/nbt.h +++ /dev/null @@ -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 diff --git a/include/cobble/util.h b/include/cobble/util.h index 5c2fada..d668637 100644 --- a/include/cobble/util.h +++ b/include/cobble/util.h @@ -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 diff --git a/level.dat b/level.dat new file mode 100644 index 0000000000000000000000000000000000000000..869d79dc1a56d805520195890100e7ff29aabd34 GIT binary patch literal 824 zcmV-81IPRyiwFP!0000009{neZrer_y|g5Xrl7=*U7+o%K-Mv0rw+Pv94Rs!$Z;SU zd3e!c#JM5|B4>!1p&VtMWp`cm^#fhx5BdT5rEc6i;=^{h5p3++!L$P+6XS-h|H_(}m z(H{T$*H6wJLiY`7hu4E(Dp_t4WuXNdryQVj6hM1Tm%qQaf3SBz^K^%7Rgbjs`S0Io z2AbUpmQsE0dC+HRs;&kq%jgvhWrVKtxf}|!s3!_x)A2AVQZxbdxVjK24zh#Kqe4cOAcOZPfNwJPq3SkB=pDu=W1B$d>EYp%r^hFI>AaBu42{xdoB!{rT8LUS&h*<{jYTFb@_}Y^>!ek8?PY3q zq6N-x8}IA2;vHMloMaE)lK>t>B`a$^oEJ7P8lXLsvnx4aToeX2_8+ft&@fYHc7~An!?OFKOl6c9xPI zeQH6^5+K@#wZ(h*v5vmHzP|p4mLOr8M@o|qwr))>}3{H6t41c&Krpn5Cl zbW|-IUli0_o@&!vObfcp`rpfceYjUGDVyV^l2~tVa4Y*fcM3J&R%*{!N zVRHD{hwVF8z@d4zS6A5_;}X;LDtE4wPPq@^%~`0A0g>abRs*wh1n@sr<-)L81pokk C_mm0% literal 0 HcmV?d00001 diff --git a/src/main.c b/src/main.c index 176fe39..0f1f51b 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include #include +#include "cNBT/nbt.h" #include #include @@ -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(); diff --git a/src/nbt.c b/src/nbt.c deleted file mode 100644 index c875639..0000000 --- a/src/nbt.c +++ /dev/null @@ -1,16 +0,0 @@ - -#include -#include -#include - -#include - -bool -nbt_parse_file(NBTNode *node, FILE *fp) -{ -} - -bool -nbt_parse_bytes(NBTNode *node, uint8_t *data, size_t data_len) -{ -} diff --git a/src/util.c b/src/util.c index 73ea982..6149b75 100644 --- a/src/util.c +++ b/src/util.c @@ -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); + } +}