From 337247d56e3300d17301daa7fa3fbfb6084cd540 Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Tue, 20 Dec 2016 09:35:09 +0100 Subject: Initial commit. --- .gitignore | 3 + Makefile | 39 +++++ src/common.c | 100 +++++++++++++ src/common.h | 86 +++++++++++ src/elist.h | 113 ++++++++++++++ src/lexer.c | 253 +++++++++++++++++++++++++++++++ src/main.c | 112 ++++++++++++++ src/mapcat.c | 483 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1189 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/common.c create mode 100644 src/common.h create mode 100644 src/elist.h create mode 100644 src/lexer.c create mode 100644 src/main.c create mode 100644 src/mapcat.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4c2254 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +obj/ +mapcat +test/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..441c008 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CC = gcc +CFLAGS += -g -Wall +CPPFLAGS += -MMD +LDFLAGS += + +PP_BOLD := $(shell tput bold) +PP_RESET := $(shell tput sgr0) +PP_CC := $(PP_BOLD)$(shell tput setf 6)CC$(PP_RESET) +PP_LD := $(PP_BOLD)$(shell tput setf 2)LD$(PP_RESET) +PP_RM := $(PP_BOLD)$(shell tput setf 4)RM$(PP_RESET) + +SRC := src/common.c \ + src/lexer.c \ + src/main.c \ + src/mapcat.c +OBJ := $(SRC:src/%.c=obj/%.o) +OUT := mapcat + +all: $(OUT) + +-include $(OBJ:.o=.d) + +obj/%.o : src/%.c + @echo "$(PP_CC) src/$*.c" + @mkdir -p $(@D) + @$(CC) $(CFLAGS) $(CPPFLAGS) -c src/$*.c -o obj/$*.o + +$(OUT): $(OBJ) + @echo "$(PP_LD) $(OUT)" + @$(CC) $(OBJ) -o $(OUT) $(LDFLAGS) + +clean: + @echo "${PP_RM} obj" + @rm -rf obj + @echo "${PP_RM} ${OUT}" + @rm -rf ${OUT} + +.PHONY: clean + diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..1c057a2 --- /dev/null +++ b/src/common.c @@ -0,0 +1,100 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "common.h" + +void vstr_init(vstr_t *vstr) +{ + memset(vstr, 0, sizeof(*vstr)); +} + +void vstr_free(vstr_t *vstr) +{ + free(vstr->data); +} + +void vstr_clear(vstr_t *vstr) +{ + vstr->size = 0; +} + +static int vstr_enlarge(vstr_t *vstr) +{ + size_t new_alloc; + + new_alloc = (vstr->alloc + 4) * 3 / 2; + debug("%zu -> %zu\n", vstr->alloc, new_alloc); + + vstr->data = realloc(vstr->data, new_alloc); + if (!vstr->data) + return 1; + + vstr->alloc = new_alloc; + return 0; +} + +int vstr_putc(vstr_t *vstr, char ch) +{ + // note: keep at least one character free at all times for vstr_termz + if (vstr->size + 2 > vstr->alloc) + if (vstr_enlarge(vstr)) + return -ENOMEM; + + vstr->data[vstr->size] = ch; + vstr->size++; + return 0; +} + +int vstr_cmp(vstr_t *vstr, const char *str) +{ + size_t len; + + len = strlen(str); + if (vstr->size < len) + len = vstr->size; + + return memcmp(vstr->data, str, len); +} + +char *vstr_strdup(vstr_t *vstr) +{ + char *str; + + str = malloc(vstr->size + 1); + if (!str) + return NULL; + + memcpy(str, vstr->data, vstr->size); + str[vstr->size] = 0; + + return str; +} + +void vstr_termz(vstr_t *vstr) +{ + vstr->data[vstr->size] = 0; +} + +float vstr_atof(vstr_t *vstr) +{ + if (!vstr->size) + return 0; + + vstr_termz(vstr); + return atof(vstr->data); +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..8c6f43f --- /dev/null +++ b/src/common.h @@ -0,0 +1,86 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include +#include +#include +#include +#include "elist.h" + +#ifdef DEBUG +#define debug(fmt, ...) fprintf(stderr, "%s: " fmt, __func__, ##__VA_ARGS__) +#else +#define debug(fmt, ...) +#endif + +#define PROGRAM_NAME "mapcat" +#define PROGRAM_VERSION "0.1.0" + +// common.c + +typedef struct { + char *data; + size_t size, alloc; +} vstr_t; + +void vstr_init(vstr_t *vstr); +void vstr_free(vstr_t *vstr); +void vstr_clear(vstr_t *vstr); +int vstr_putc(vstr_t *vstr, char ch); +int vstr_cmp(vstr_t *vstr, const char *str); +char *vstr_strdup(vstr_t *vstr); +void vstr_termz(vstr_t *vstr); +float vstr_atof(vstr_t *vstr); + +// lexer.c + +#define LEXER_BUFFER 1024 + +typedef struct { + int error; + const char *path; + FILE *fp; + bool eof; + + vstr_t *token; + char buf[LEXER_BUFFER]; + char *buf_c, *buf_e; + + size_t cc, lc, Cc; // character, line, and column counters + char last; + + bool in_token; + bool in_quote; + bool in_comment; +} lexer_state_t; + +int lexer_open(lexer_state_t *ls, const char *path, vstr_t *token); +int lexer_get_token(lexer_state_t *ls); +int lexer_assert(lexer_state_t *ls, const char *match, const char *desc); +int lexer_assert_or_eof(lexer_state_t *ls, const char *match, const char *desc); +void lexer_perror(lexer_state_t *ls, const char *fmt, ...); +void lexer_perror_eg(lexer_state_t *ls, const char *expected); +int lexer_get_floats(lexer_state_t *ls, float *out, size_t count); + +// mapcat.c + +int mapcat_load(const char *path); +int mapcat_save(const char *path); +void mapcat_free(void); diff --git a/src/elist.h b/src/elist.h new file mode 100644 index 0000000..da6d172 --- /dev/null +++ b/src/elist.h @@ -0,0 +1,113 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#define offsetin(V, M) ((size_t)&(V)->M - (size_t)(V)) + +typedef struct { + void *prev, *next; +} elist_header_t; + +static inline elist_header_t *elist_header(void *entry, size_t offs) +{ + return (elist_header_t*)((char*)entry + offs); +} + +static inline void elist_append_real(void **head, void *entry, size_t offs) +{ + elist_header_t *head_header, *entry_header; + + entry_header = elist_header(entry, offs); + + if (!*head) { + *head = entry; + entry_header->prev = entry; + entry_header->next = NULL; + return; + } + + head_header = elist_header(*head, offs); + + entry_header->prev = head_header->prev; + if (head_header->prev) + elist_header(head_header->prev, offs)->next = entry; + head_header->prev = entry; + + entry_header->next = NULL; +} + +#define elist_append(head, entry, member) \ +elist_append_real((void**)(head), (entry), offsetin((entry), member)) + +#define elist_next(entry, member) \ +(elist_header((entry), offsetin((entry), member))->next) + +#define elist_for(iter, head, member) \ +for ((iter) = (head); (iter); (iter) = elist_next((iter), member)) + +static inline void elist_unlink_real(void **head, void *entry, size_t offs) +{ + elist_header_t *entry_header; + + entry_header = elist_header(entry, offs); + + if (entry_header->prev && *head != entry) { + elist_header_t *prev_header; + prev_header = elist_header(entry_header->prev, offs); + prev_header->next = entry_header->next; + } + + if (entry_header->next) { + elist_header_t *next_header; + next_header = elist_header(entry_header->next, offs); + next_header->prev = entry_header->prev; + } else { + elist_header_t *head_header; + head_header = elist_header(*head, offs); + head_header->prev = entry_header->prev; + } + + if (*head == entry) + *head = entry_header->next; + +} + +#define elist_unlink(head, entry, member) \ +elist_unlink_real((void**)(head), (entry), offsetin((entry), member)) + +static inline void elist_append_list_real(void **head1, void *head2, size_t offs) +{ + elist_header_t *head1_header, *head2_header, *last1_header; + + if (!*head1) { + *head1 = head2; + return; + } + + if (!head2) + return; + + head1_header = elist_header(head1, offs); + head2_header = elist_header(head2, offs); + last1_header = elist_header(head1_header->prev, offs); + head1_header->prev = head2_header->prev; + last1_header->next = head2; + head2_header->prev = head1_header->prev; +} + +#define elist_append_list(head1, head2, member) \ +elist_append_list_real((void**)(head1), (head2), offsetin((head2), member)) diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..41d6784 --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,253 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "common.h" +#include + +int lexer_open(lexer_state_t *ls, const char *path, vstr_t *token) +{ + ls->error = 0; + ls->path = path; + + ls->fp = fopen(path, "r"); + if (!ls->fp) + return -errno; + + ls->eof = false; + + ls->token = token; + ls->buf_e = ls->buf_c = ls->buf; + ls->cc = ls->lc = ls->Cc = 0; + + ls->in_token = false; + ls->in_quote = false; + ls->in_comment = false; + + return 0; +} + +//RETURN VALUES +// <0 on error +// 0 on success +// note: sets ls->eof to true if there's no more data left +static int fill_buffer(lexer_state_t *ls) +{ + size_t read; + + read = fread(ls->buf, 1, sizeof(ls->buf), ls->fp); + debug("read = %zu\n", read); + if (read < sizeof(ls->buf)) { + if (ferror(ls->fp)) { + fclose(ls->fp); + return -errno; + } + + ls->eof = true; + fclose(ls->fp); + debug("no data left, ls->fp closed\n"); + } + + ls->buf_c = ls->buf; + ls->buf_e = ls->buf + read; + return 0; +} + +//RETURN VALUES +// -ENOMEM +// -EAGAIN when the buffer runs out +// 0 on success +// 1 when no data is left +static int read_buffer(lexer_state_t *ls) +{ + while (ls->buf_c < ls->buf_e) { + bool ret_token = false; + + debug("*ls->buf_c = %c, ls->last = %c\n", *ls->buf_c, ls->last); + + if (*ls->buf_c == '\n') { + ls->lc++; + ls->Cc = 0; + } + + if (ls->in_comment) { + if (*ls->buf_c == '\n') + ls->in_comment = false; + } else if (isspace(*ls->buf_c) && !ls->in_quote) { + if (ls->in_token) { + ls->in_token = false; + ret_token = true; + } + } else if (*ls->buf_c == '/' && ls->last == '/') { + ls->in_comment = true; + ls->in_token = false; + + ls->token->size--; // remove the first slash + if (ls->token->size) + ret_token = true; + } else if (*ls->buf_c == '\"' && + (ls->cc && ls->last != '\\')) { + ls->in_quote = !ls->in_quote; + + if (!ls->in_quote) { + ls->in_token = false; + ret_token = true; + } + } else { + if (!ls->in_token) { + ls->in_token = true; + } + } + + if (ls->in_token) + if (vstr_putc(ls->token, *ls->buf_c)) { + ls->error = ENOMEM; + return -ENOMEM; + } + + ls->last = *ls->buf_c; + ls->buf_c++; + ls->cc++; + ls->Cc++; + + if (ret_token) + return 0; + } + + if (ls->eof) { + if (ls->token->size > 0) + return 0; + return 1; + } + + return -EAGAIN; +} + +//RETURN VALUES +// <0 on error +// 0 on success +// 1 when no data is left +int lexer_get_token(lexer_state_t *ls) +{ + int ret; + + vstr_clear(ls->token); + + while (1) { + ret = read_buffer(ls); + debug("read_buffer = %i\n", ret); + if (ret != -EAGAIN) + return ret; + + ret = fill_buffer(ls); + debug("fill_buffer = %i\n", ret); + if (ret < 0) + return ret; + } +} + +void lexer_perror(lexer_state_t *ls, const char *fmt, ...) +{ + va_list vl; + + fprintf(stderr, "%s:%zu:%zu: ", ls->path, ls->lc + 1, ls->Cc + 1); + + if (ls->error) { + perror(NULL); + } else { + va_start(vl, fmt); + vfprintf(stderr, fmt, vl); + va_end(vl); + } +} + +void lexer_perror_eg(lexer_state_t *ls, const char *expected) +{ + if (ls->eof && ls->buf_c == ls->buf_e) + lexer_perror(ls, "expected %s, got EOF\n", expected); + else { + vstr_termz(ls->token); + lexer_perror(ls, "expected %s, got \"%s\"\n", expected, + ls->token->data); + } +} + +int lexer_assert(lexer_state_t *ls, const char *match, const char *desc) +{ + int ret; + + ret = lexer_get_token(ls); + if (ret) { + lexer_perror(ls, "expected %s%s\"%s\", got EOF\n", + (desc ? desc : ""), (desc ? " " : ""), match); + return 1; + } + + if (vstr_cmp(ls->token, match)) { + vstr_termz(ls->token); + lexer_perror(ls, "expected %s%s\"%s\", got \"%s\"\n", + (desc ? desc : ""), (desc ? " " : ""), match, + ls->token->data); + return 1; + } + + return 0; +} + +//RETURN VALUE +// -1 on eof (also success) +// 0 on success +// 1 on error +int lexer_assert_or_eof(lexer_state_t *ls, const char *match, const char *desc) +{ + int ret; + + ret = lexer_get_token(ls); + if (ret < 0) { + perror("lexer"); + return 1; + } + + if (ret == 1) { + return -1; + } + + if (vstr_cmp(ls->token, match)) { + lexer_perror(ls, "expected %s%s\"%s\" or EOF, got \"%.*s\"\n", + (desc ? desc : ""), (desc ? " " : ""), match, + (int)ls->token->size, ls->token->data); + return 1; + } + + return 0; +} + +int lexer_get_floats(lexer_state_t *ls, float *out, size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) { + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "a number"); + return 1; + } + + out[i] = vstr_atof(ls->token); + } + + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..d34f00e --- /dev/null +++ b/src/main.c @@ -0,0 +1,112 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "common.h" +#include + +#define error(fmt, ...) fprintf(stderr, PROGRAM_NAME ": " fmt, ##__VA_ARGS__) + +void print_version(void) +{ + puts(PROGRAM_NAME " " PROGRAM_VERSION ", Copyright (C) 2016 Paweł Redman\n" + PROGRAM_NAME " comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it\n" + "under certain conditions. See the file COPYING."); +} + +void print_usage(void) +{ + puts(PROGRAM_NAME " " PROGRAM_VERSION "\n" + "usage: " PROGRAM_NAME " -o outfile infile...\n" + " or " PROGRAM_NAME " -v\n" + " or " PROGRAM_NAME " -h"); +} + +typedef struct { + char *path; + elist_header_t list; +} input_file_t; + +int main(int argc, char **argv) +{ + int rv = 1, i; + input_file_t *inputs = NULL, *input, *next; + char *output = NULL; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + print_version(); + rv = 0; + goto out; + } else if (!strcmp(argv[i], "-h")) { + print_usage(); + rv = 0; + goto out; + } else if (!strcmp(argv[i], "-o")) { + if (i + 1 >= argc) { + error("-o needs an argument\n"); + goto out; + } + + if (output) { + error("-o can be specified only once\n"); + goto out; + } + + output = argv[i + 1]; + i++; + } else { + input = malloc(sizeof(input_file_t)); + if (!input) { + error("out of memory\n"); + goto out; + } + + input->path = argv[i]; + elist_append(&inputs, input, list); + } + } + + if (!inputs) { + error("no input files specified, try '" PROGRAM_NAME " -h'\n"); + goto out; + } + + if (!output) { + error("no output file specified, try '" PROGRAM_NAME " -h'\n"); + goto out; + } + + elist_for (input, inputs, list) + if (mapcat_load(input->path)) + goto out; + + if (mapcat_save(output)) + goto out; + + rv = 0; +out: + mapcat_free(); + + for (input = inputs; input; input = next) { + next = elist_next(input, list); + free(input); + } + + return rv; +} diff --git a/src/mapcat.c b/src/mapcat.c new file mode 100644 index 0000000..6420cf5 --- /dev/null +++ b/src/mapcat.c @@ -0,0 +1,483 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 3 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#define DEBUG +#include "common.h" + +typedef struct { + float def[9]; + char *shader; + float texmap[8]; + elist_header_t list; +} brush_face_t; + +typedef struct { + brush_face_t *faces; + elist_header_t list; +} brush_t; + +typedef struct { + char *key; + char *value; + elist_header_t list; +} entity_key_t; + +typedef struct { + char *classname; + brush_t *brushes; + entity_key_t *keys; + + elist_header_t list; +} entity_t; + +entity_t *worldspawn; +entity_t *entities; + +static int read_entity_key(lexer_state_t *ls, entity_t *entity) +{ + entity_key_t *key; + + // classnames are stored separately for easier access later + if (!vstr_cmp(ls->token, "classname")) { + if (entity->classname) { + lexer_perror(ls, "warning: duplicate classname\n"); + free(entity->classname); + entity->classname = NULL; + } + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the classname"); + return 1; + } + + entity->classname = vstr_strdup(ls->token); + if (!entity->classname) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + return 0; + } + + key = malloc(sizeof(entity_key_t)); + if (!key) + goto error_oom; + + memset(key, 0, sizeof(*key)); + elist_append(&entity->keys, key, list); + + key->key = vstr_strdup(ls->token); + if (!key->key) + goto error_oom; + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the key value"); + return 1; + } + + key->value = vstr_strdup(ls->token); + if (!key->value) + goto error_oom; + + return 0; +error_oom: + lexer_perror(ls, "out of memory\n"); + return 1; +} + +static int read_brush_face(lexer_state_t *ls, brush_t *brush) +{ + brush_face_t *face; + + face = malloc(sizeof(brush_face_t)); + if (!face) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + memset(face, 0, sizeof(*face)); + elist_append(&brush->faces, face, list); + + if (lexer_get_floats(ls, face->def, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 1st vector")) + return 1; + + if (lexer_assert(ls, "(", "the beginning of this face's 2nd vector")) + return 1; + + if (lexer_get_floats(ls, face->def + 3, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 2nd vector")) + return 1; + + if (lexer_assert(ls, "(", "the beginning of this face's 3rd vector")) + return 1; + + if (lexer_get_floats(ls, face->def + 6, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 3rd vector")) + return 1; + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "a shader name"); + return 1; + } + + face->shader = vstr_strdup(ls->token); + if (!face->shader) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + if (lexer_get_floats(ls, face->texmap, 8)) + return 1; + + return 0; +} + +static int read_brush_faces(lexer_state_t *ls, brush_t *brush) +{ + while (1) { + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the beginning of a brush face " + " ('(') or the end of this brush" + " ('}')"); + return 1; + } + + if (!vstr_cmp(ls->token, "(")) { + if (read_brush_face(ls, brush)) + return 1; + } else if (!vstr_cmp(ls->token, "}")) + break; + } + + return 0; +} + +static void free_entity_keys(entity_t *entity) +{ + entity_key_t *key, *next; + + for (key = entity->keys; key; key = next) { + next = elist_next(key, list); + + free(key->key); + free(key->value); + free(key); + } +} + +static void free_brush(brush_t *brush) +{ + brush_face_t *face, *face_next; + + for (face = brush->faces; face; face = face_next) { + face_next = elist_next(face, list); + + free(face->shader); + free(face); + } + + free(brush); +} + +// this frees an entity_t and its children +// note: this function does NOT unlink the entity from the global list +static void free_entity(entity_t *entity) +{ + brush_t *brush, *next; + + free_entity_keys(entity); + free(entity->classname); + + for (brush = entity->brushes; brush; brush = next) { + next = elist_next(brush, list); + free_brush(brush); + } + + free(entity); +} + +static void merge_into_worldspawn(entity_t *entity) +{ + // the first worldspawn becomes the output's worldspawn. + // other worldspawn's brushes are appended to it + if (!worldspawn) { + worldspawn = entity; + return; + } + + // keep only the original key-value pairs + free_entity_keys(entity); + + elist_append_list(worldspawn->brushes, entity->brushes, list); + + free(entity); +} + +static bool brush_discard(brush_t *brush) +{ + brush_face_t *face; + + elist_for(face, brush->faces, list) + if (!strcmp(face->shader, "mapcat_discard")) + return true; + + return false; +} + +static int read_entity(lexer_state_t *ls) +{ + entity_t *entity; + + entity = malloc(sizeof(entity_t)); + if (!entity) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + memset(entity, 0, sizeof(*entity)); + + // read keys and values + while (1) { + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "a key or the beginning of a brush" + " \"{\" or the end of this entity" + " \"}\""); + goto error; + } + + if (!vstr_cmp(ls->token, "{")) + break; + + if (!vstr_cmp(ls->token, "}")) + goto no_brushes; + + if (read_entity_key(ls, entity)) + goto error; + } + + // the opening brace of the first brush in this entity was already + // read in the loop above, so it has to be skipped in the loop below. + // the problem is solved by jumping in the middle of the loop + goto skip_first_brace; + + while (1) { + brush_t *brush; + + if (lexer_get_token(ls)) { + L1: + lexer_perror_eg(ls, "the beginning of a brush \"{\"" + " or the end of this entity \"}\""); + goto error; + } + + if (!vstr_cmp(ls->token, "}")) + break; + else if (vstr_cmp(ls->token, "{")) + goto L1; + + skip_first_brace: + brush = malloc(sizeof(brush_t)); + if (!brush) { + lexer_perror(ls, "out of memory\n"); + goto error; + } + + memset(brush, 0, sizeof(*brush)); + + if (read_brush_faces(ls, brush)) { + free_brush(brush); + goto error; + } + + if (brush_discard(brush)) + free_brush(brush); + else + elist_append(&entity->brushes, brush, list); + } + +no_brushes: + + if (entity->classname && !strcmp(entity->classname, "worldspawn")) + merge_into_worldspawn(entity); + else + elist_append(&entities, entity, list); + + return 0; +error: + free_entity(entity); + return 1; +} + + +int mapcat_load(const char *path) +{ + int rv = 1; + lexer_state_t lexer; + vstr_t token; + + vstr_init(&token); + + if (lexer_open(&lexer, path, &token)) { + perror(path); + return 1; + } + + while (1) { + int ret; + + ret = lexer_assert_or_eof(&lexer, "{", + "the beginning of an entity"); + if (ret == -1) + break; + if (ret > 0) + goto out; + + if (read_entity(&lexer)) + goto out; + } + + rv = 0; +out: + vstr_free(&token); + return rv; +} + +static int write_brush(FILE *fp, brush_t *brush) +{ + brush_face_t *face; + + elist_for(face, brush->faces, list) { + size_t i; + + for (i = 0; i < 9; i += 3) { + if (i) + fprintf(fp, " "); + + fprintf(fp, "( %f %f %f )", face->def[i], + face->def[i + 1], face->def[i + 2]); + } + + fprintf(fp, " %s", face->shader); + + for (i = 0; i < 5; i++) + fprintf(fp, " %f", face->texmap[i]); + + // the last three values are integers + for (i = 5; i < 8; i++) + fprintf(fp, " %.0f", face->texmap[i]); + + fprintf(fp, "\n"); + } + + if (ferror(fp)) + return -errno; + + return 0; +} + +static int write_entity(FILE *fp, entity_t *entity) +{ + entity_key_t *key; + brush_t *brush; + size_t brush_counter = 0; + + fprintf(fp, "\"classname\" \"%s\"\n", entity->classname); + + elist_for(key, entity->keys, list) + fprintf(fp, "\"%s\" \"%s\"\n", key->key, key->value); + + elist_for(brush, entity->brushes, list) { + fprintf(fp, "// brush %zu\n{\n", brush_counter); + write_brush(fp, brush); + fprintf(fp, "}\n"); + brush_counter++; + } + + if (ferror(fp)) + return -errno; + + return 0; +} + +int mapcat_save(const char *path) +{ + int rv = 1; + FILE *fp; + entity_t *entity; + size_t entity_counter = 1; // worldspawn is #0 + + fp = fopen(path, "w"); + if (!fp) { + perror(path); + goto out; + } + + if (!worldspawn) { + fprintf(stderr, "error: worldspawn is missing\n"); + goto out; + } + + fprintf(fp, "// entity 0\n{\n"); + write_entity(fp, worldspawn); + fprintf(fp, "}\n"); + + elist_for(entity, entities, list) { + fprintf(fp, "// entity %zu\n{\n", entity_counter); + + if (write_entity(fp, entity)) { + perror(path); + goto out; + } + + fprintf(fp, "}\n"); + entity_counter++; + } + + if (ferror(fp) || fclose(fp)) { + perror(path); + goto out; + } + + fp = NULL; + rv = 0; + +out: + if (fp) + fclose(fp); + return rv; +} + +void mapcat_free(void) +{ + entity_t *entity, *next; + + if (worldspawn) + free_entity(worldspawn); + + for (entity = entities; entity; entity = next) { + next = elist_next(entity, list); + free_entity(entity); + } +} -- cgit