/* * Copyright (C) 2013, 2021 P. J. McDermott * * This file is part of Dodge Balls * * Dodge Balls 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. * * Dodge Balls 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 Dodge Balls. If not, see . */ #include #include #include #include #include #include #include "game.h" #include "level.h" #include "locale.h" #include "output.h" #include "xml.h" struct db_game { char *id; int best_name_match; int best_desc_match; char *name; char *desc; struct db_level *level_head; struct db_level *level_tail; enum { DB_GAME_XML_PARSING_NAME, DB_GAME_XML_PARSING_DESCRIPTION, } xml_parsing; }; static int _db_game_is_dir(const struct dirent *entry) { return (entry->d_type == DT_DIR && entry->d_name[0] != '.'); } static void XMLCALL _db_game_xml_invalid_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (invalid)", name); p = (XML_Parser) pv; db_xml_unexpected_end_tag(p, name, ""); } static void XMLCALL _db_game_xml_invalid_start(void *pv, const char *name, const char **attr __attribute__((__unused__))) { XML_Parser p; db_dbg(" <%s> (invalid)", name); p = (XML_Parser) pv; db_xml_node_push(p, NULL, _db_game_xml_invalid_start, _db_game_xml_invalid_end, NULL); } static void XMLCALL _db_game_xml_cdata(void *pv, const char *s, int len) { XML_Parser p; struct db_game *game; char **data; char *s_z; char *s_z_trimmed; char *s_z_trimmed_end; char *old_data; p = (XML_Parser) pv; game = db_xml_node_peek(p); if (game->xml_parsing == DB_GAME_XML_PARSING_NAME) { data = &game->name; } else { data = &game->desc; } s_z = s_z_trimmed = strndup(s, len); while(isspace(*s_z_trimmed)) { ++s_z_trimmed; --len; } if (*s_z_trimmed == '\0') { db_dbg(" (null)"); free(s_z); return; } s_z_trimmed_end = s_z_trimmed + len - 1; while (s_z_trimmed_end > s_z_trimmed && isspace(*s_z_trimmed_end)) { --s_z_trimmed_end; } *(s_z_trimmed_end + 1) = '\0'; db_dbg(" %s", s_z_trimmed); if (*data != NULL && **data != '\0') { old_data = *data; sprintf(*data, "%s %s", *data, strdup(s_z_trimmed)); free(old_data); } else { *data = strdup(s_z_trimmed); } free(s_z); } static void XMLCALL _db_game_xml_name_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (name)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "name")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "name"); } } static void XMLCALL _db_game_xml_description_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (description)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "description")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "description"); } } static void XMLCALL _db_game_xml_level_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (level)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "level")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "level"); } } static void XMLCALL _db_game_xml_levels_el_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_game *game; char *id; db_dbg(" <%s> (levels child)", name); p = (XML_Parser) pv; game = db_xml_node_peek(p); if (db_xml_check_tag(name, "level")) { id = NULL; db_xml_get_string_attr(p, attr, "id", &id, 1); if (id == NULL) { return; } db_dbg(" id=\"%s\"", id); game->level_tail = db_level_new(game->id, id, game->level_tail); if (game->level_head == NULL) { game->level_head = game->level_tail; } free(id); db_xml_node_push(p, game, _db_game_xml_invalid_start, _db_game_xml_level_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "level"); } } static void XMLCALL _db_game_xml_levels_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (levels)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "levels")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "levels"); } } static void XMLCALL _db_game_xml_game_el_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_game *game; char *lang; int match; db_dbg(" <%s> (game child)", name); p = (XML_Parser) pv; game = db_xml_node_peek(p); if (db_xml_check_tag(name, "name")) { lang = NULL; db_xml_get_string_attr(p, attr, "lang", &lang, 1); if (lang == NULL) { return; } db_dbg(" lang=\"%s\"", lang); match = db_locale_match(lang); if (match > game->best_name_match) { game->best_name_match = match; goto name_ok; } db_xml_node_push(p, game, _db_game_xml_invalid_start, _db_game_xml_name_end, NULL); goto name_err; name_ok: if (game->name != NULL) { free(game->name); game->name = NULL; } game->xml_parsing = DB_GAME_XML_PARSING_NAME; db_xml_node_push(p, game, _db_game_xml_invalid_start, _db_game_xml_name_end, _db_game_xml_cdata); name_err: free(lang); } else if (db_xml_check_tag(name, "description")) { lang = NULL; db_xml_get_string_attr(p, attr, "lang", &lang, 1); if (lang == NULL) { return; } db_dbg(" lang=\"%s\"", lang); match = db_locale_match(lang); if (match > game->best_desc_match) { game->best_name_match = match; goto desc_ok; } db_xml_node_push(p, game, _db_game_xml_invalid_start, _db_game_xml_description_end, NULL); goto desc_err; desc_ok: if (game->desc != NULL) { free(game->desc); game->desc = NULL; } game->xml_parsing = DB_GAME_XML_PARSING_DESCRIPTION; db_xml_node_push(p, game, _db_game_xml_invalid_start, _db_game_xml_description_end, _db_game_xml_cdata); desc_err: free(lang); } else if (db_xml_check_tag(name, "levels")) { db_xml_node_push(p, game, _db_game_xml_levels_el_start, _db_game_xml_levels_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "name, description, or levels"); } } static void XMLCALL _db_game_xml_game_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (game)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "game")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "game"); } } static void XMLCALL _db_game_xml_game_start(void *pv, const char *name, const char **attr __attribute__((__unused__))) { XML_Parser p; struct db_game *game; db_dbg(" <%s> (game)", name); p = (XML_Parser) pv; game = db_xml_node_peek(p); if (db_xml_check_tag(name, "game")) { db_xml_node_push(p, game, _db_game_xml_game_el_start, _db_game_xml_game_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "game"); } } static int _db_game_read_info(const char *games_dir, const char *name, struct db_game *game) { XML_Parser p; char *path; FILE *fp; void *buf; size_t len; enum XML_Status status; game->id = strdup(name); if (game->id == NULL) { return -1; } p = XML_ParserCreate(NULL); if (p == NULL) { return -1; } XML_UseParserAsHandlerArg(p); db_xml_node_push(p, game, _db_game_xml_game_start, _db_game_xml_invalid_end, NULL); path = calloc(strlen(games_dir) + 1 + strlen(name) + strlen("/game.xml") + 1, sizeof(*path)); sprintf(path, "%s/%s/game.xml", games_dir, name); if (path == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); return -1; } fp = fopen(path, "rb"); if (fp == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); return -1; } buf = XML_GetBuffer(p, 8192); if (buf == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); fclose(fp); return -1; } db_dbg("Parsing <%s>:", path); free(path); while (!feof(fp)) { len = fread(buf, 1, 8192, fp); status = XML_ParseBuffer(p, len, feof(fp)); if (status == XML_STATUS_OK) { continue; } db_err("Failed to parse game information (%s)", XML_ErrorString(XML_GetErrorCode(p))); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); return -1; } db_dbg("Parsing done"); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); return 0; } int db_games_find(const char *games_dir, struct db_game ***games) { struct dirent **entries; int n; int i; n = scandir(games_dir, &entries, &_db_game_is_dir, alphasort); if (n < 0) { db_err("Failed to scan games directory"); return -1; } else if (n == 0) { return 0; } *games = calloc(n, sizeof(**games)); if (*games == NULL) { db_err("Failed to allocate memory"); return -1; } for (i = 0; i < n; ++i) { (*games)[i] = calloc(1, sizeof(***games)); if ((*games)[i] == NULL || _db_game_read_info(games_dir, entries[i]->d_name, (*games)[i]) < 0) { db_err("Failed to get game information"); while (--i >= 0) { db_game_free((*games)[i]); } free(*games); return -1; } free(entries[i]); } free(entries); return n; } const char * db_game_get_name(struct db_game *game) { return game->name; } const char * db_game_get_desc(struct db_game *game) { return game->desc; } int db_game_play(SDL_Renderer *renderer, struct db_game *game) { struct db_level *level; for (level = game->level_head; level != NULL; level = db_level_next(level)) { if (db_level_play(renderer, level) != 0) { return -1; } } return 0; } void db_game_free(struct db_game *game) { if (game->id) { free(game->id); } if (game->name) { free(game->name); } if (game->desc) { free(game->desc); } free(game); }