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:
parent
5b37dbc457
commit
877895e373
11
Makefile
11
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 $@
|
||||
|
|
2
README
2
README
|
@ -40,3 +40,5 @@ libraries
|
|||
dependencies
|
||||
= = = = = = = = = = = = = = = = = = = = = = = = =
|
||||
- pthread
|
||||
- openssl
|
||||
- zlib
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
16
src/nbt.c
16
src/nbt.c
|
@ -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)
|
||||
{
|
||||
}
|
18
src/util.c
18
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue