Mercurial > hg > batmud > maputils
view src/liblocfile.c @ 2470:d0aad04c3e61
th-libs now uses stdbool.h if possible, so we need to rename all
BOOL/TRUE/FALSE to bool/true/false.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 07 Dec 2022 13:23:46 +0200 |
parents | eba783bef2ee |
children | f207279a5f89 |
line wrap: on
line source
/* * liblocfile - Location file format handling * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2006-2022 Tecnic Software productions (TNSP) */ #include "liblocfile.h" // Internal parsing context structure typedef struct { LocFileInfo *file; char *filename; FILE *fp; unsigned int line; bool versionSet; int ch, parseMode, prevMode, nextMode, field, subField, sep; char *fieldSep; } LocFileParseContext; const char * locGetAreaNameType(const int flags, const bool type) { const char *pfx; const char *desc; switch (flags) { case NAME_ORIG : pfx = "@"; desc = "original"; break; default : pfx = "" ; desc = NULL; break; } return type ? desc : pfx; } static void locAddAreaName(LocName *dst, int *ndst, const LocName *src, const int nsrc) { if (src != NULL && src[nsrc].name != NULL) { int flags = 0; int n = 0; switch (src[nsrc].name[0]) { case '@': n++; flags |= NAME_ORIG; break; } dst[*ndst].name = th_strdup(&(src[nsrc].name[n])); dst[*ndst].flags = flags; (*ndst)++; } } const char * locGetAreaCreatorRole(const int flags, const bool type) { const char *pfx; const char *desc; switch (flags) { case CREATOR_ORIG : pfx = "@"; desc = "original"; break; case CREATOR_RECODER : pfx = "!"; desc = "recoder"; break; case CREATOR_MAINTAINER : pfx = "%"; desc = "maintainer"; break; case CREATOR_EXPANDER : pfx = "&"; desc = "expander"; break; default : pfx = "" ; desc = "undefined"; break; } return type ? desc : pfx; } static void locAddAreaCreator(LocName *dst, int *ndst, const LocName *src, const int nsrc) { if (src != NULL && src[nsrc].name != NULL) { int flags = 0; int n = 0; switch (src[nsrc].name[0]) { case '@': n++; flags |= CREATOR_ORIG; break; case '!': n++; flags |= CREATOR_RECODER; break; case '%': n++; flags |= CREATOR_MAINTAINER; break; case '&': n++; flags |= CREATOR_EXPANDER; break; } dst[*ndst].name = th_strdup(&(src[nsrc].name[n])); dst[*ndst].flags = flags; (*ndst)++; } } const char *locGetTypePrefix(const int flags) { switch (flags & LOCF_M_MASK) { case LOCF_M_CITY: return "CITY"; case LOCF_M_PCITY: return "PCITY"; default: switch (flags & LOCF_T_MASK) { case LOCF_T_SHRINE: return "SHRINE"; case LOCF_T_GUILD: return "GUILD"; case LOCF_T_SS: return "SS"; case LOCF_T_MONSTER: return "MOB"; case LOCF_T_TRAINER: return "TRAINER"; case LOCF_T_FORT: return "FORT"; } break; } return NULL; } const char *locGetTypeName(const int flags) { switch (flags & LOCF_M_MASK) { case LOCF_M_CITY: return "city"; case LOCF_M_PCITY: return "pcity"; default: switch (flags & LOCF_T_MASK) { case LOCF_T_SHRINE: return "shrine"; case LOCF_T_GUILD: return "guild"; case LOCF_T_SS: return "ss"; case LOCF_T_MONSTER: return "monster"; case LOCF_T_TRAINER: return "trainer"; case LOCF_T_FORT: return "fort"; } break; } return "default"; } LocMarker * locCopyLocMarker(const LocMarker *src) { LocMarker *res = th_malloc0(sizeof(LocMarker)); if (res == NULL) return NULL; // Just copy the data, as most of it is "static" // and then replace the pointers etc. as necessary. memcpy(res, src, sizeof(LocMarker)); res->file = NULL; res->uri = th_strdup(src->uri); res->freeform = th_strdup(src->freeform); for (int i = 0; i < res->nnames; i++) res->names[i].name = th_strdup(src->names[i].name); for (int i = 0; i < res->ncreators; i++) res->creators[i].name = th_strdup(src->creators[i].name); return res; } void locCopyLocations(MapLocations *dst, const MapLocations *src) { if (dst == NULL || src == NULL) return; dst->nlocations = src->nlocations; dst->locations = (LocMarker **) th_malloc(dst->nlocations * sizeof(LocMarker *)); for (int i = 0; i < dst->nlocations; i++) dst->locations[i] = locCopyLocMarker(src->locations[i]); } bool locAddNew(MapLocations *l, int xc, int yc, int dir, int flags, LocName *names, LocName *creators, LocDateStruct *added, bool valid, const char *uri, const char *freeform, LocFileInfo *file) { LocMarker *tmp; int i; // Allocate location struct if ((tmp = th_malloc0(sizeof(LocMarker))) == NULL) return false; tmp->xc = tmp->ox = xc; tmp->yc = tmp->oy = yc; tmp->align = dir; tmp->flags = flags; tmp->file = file; for (i = 0; i < LOC_MAX_NAMES; i++) { locAddAreaName(tmp->names, &tmp->nnames, names, i); locAddAreaCreator(tmp->creators, &tmp->ncreators, creators, i); } if (added != NULL) { memcpy(&(tmp->added), added, sizeof(tmp->added)); tmp->valid = valid; } else { time_t stamp = time(NULL); struct tm *tmpTime = localtime(&stamp); tmp->added.day = tmpTime->tm_mday; tmp->added.month = tmpTime->tm_mon + 1; tmp->added.year = tmpTime->tm_year + 1900; tmp->valid = true; } tmp->uri = th_strdup(uri); tmp->freeform = th_strdup(freeform); // Add new location l->locations = (LocMarker **) th_realloc(l->locations, sizeof(LocMarker*) * (l->nlocations+1)); if (l->locations == NULL) return false; l->locations[l->nlocations] = tmp; l->nlocations++; return true; } int locFindByCoords(const MapLocations *l, const int xc, const int yc, const bool locTrue) { for (int i = 0; i < l->nlocations; i++) { LocMarker *tmp = l->locations[i]; if (locTrue) { if (tmp->ox == xc && tmp->oy == yc) return i; } else { if (tmp->xc == xc && tmp->yc == yc) return i; } } return -1; } void locFreeMarkerData(LocMarker *marker) { for (int i = 0; i < LOC_MAX_NAMES; i++) { th_free_r(&marker->names[i].name); th_free_r(&marker->creators[i].name); } th_free_r(&marker->uri); th_free_r(&marker->freeform); } void locFreeMapLocations(MapLocations *loc) { if (loc->locations != NULL) { for (int i = 0; i < loc->nlocations; i++) if (loc->locations[i] != NULL) { locFreeMarkerData(loc->locations[i]); th_free(loc->locations[i]); } th_free(loc->locations); } } enum { PM_IDLE = 0, PM_FIELD, PM_FIELD_SEP, PM_COMMENT, PM_NEXT, PM_EOF, PM_ERROR }; static int locFGetc(LocFileParseContext *f) { return fgetc(f->fp); } static void locPMSet(LocFileParseContext *f, int parseMode, int nextMode) { f->prevMode = f->parseMode; if (parseMode != -1) f->parseMode = parseMode; if (nextMode != -1) f->nextMode = nextMode; } static void locPMErr(LocFileParseContext *ctx, const char *fmt, ...) { va_list ap; fprintf(stderr, "[%s:%d @ %d]: ", ctx->file->filename, ctx->line, ctx->field); ctx->parseMode = PM_ERROR; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } static bool locCheckFlag(int flags, int mask, int flag) { if (mask) return (flags & mask) == flag; else return (flags & flag); } static bool locCheckMutex(LocFileParseContext *f, int *flags, int mask, int flag) { if (!locCheckFlag(*flags, mask, 0) && !locCheckFlag(*flags, mask, flag)) { locPMErr(f, "Invalid flags setting.\n"); return false; } else { *flags |= flag; return true; } } static bool parseFieldInt(LocFileParseContext *f, int *val) { int res = 0; if (!isdigit(f->ch)) return false; while (isdigit(f->ch)) { res *= 10; res += f->ch - '0'; f->ch = locFGetc(f); } *val = res; return true; } static char * parseFieldString(LocFileParseContext *f, const char *endch) { char res[4096]; bool end = false; size_t pos = 0; int i; if (strchr(endch, f->ch)) return NULL; while (!end && !strchr(endch, f->ch) && pos < sizeof(res) - 1) { switch (f->ch) { case '\n': case '\r': locPMErr(f, "Unexpected EOL inside text field.\n"); return NULL; case EOF: locPMErr(f, "Unexpected EOF inside text field.\n"); return NULL; case '\\': // Enable continuation via '\' at EOL i = locFGetc(f); if (i == EOF) { locPMErr(f, "Unexpected EOF inside text field.\n"); return NULL; } else if (i == '\n' || i == '\r') { f->ch = locFGetc(f); if (i == '\r' && f->ch == '\n') f->ch = locFGetc(f); } else { res[pos++] = i; f->ch = locFGetc(f); } break; default: res[pos++] = f->ch; f->ch = locFGetc(f); break; } } while (pos > 0 && isspace(res[pos - 1])) res[--pos] = 0; res[pos] = 0; return (pos > 0) ? th_strdup(res) : NULL; } static bool locParseFlags(LocFileParseContext *ctx, int *flags) { bool endFlags; *flags = LOCF_NONE; endFlags = false; while (!endFlags) { switch (ctx->ch) { // Scenic marker flags case '?': if (!locCheckMutex(ctx, flags, LOCF_M_MASK, LOCF_M_SCENIC1)) return false; break; case '%': if (!locCheckMutex(ctx, flags, LOCF_M_MASK, LOCF_M_SCENIC2)) return false; break; case 'C': if (!locCheckMutex(ctx, flags, LOCF_M_MASK, LOCF_M_PCITY)) return false; break; case 'c': if (!locCheckMutex(ctx, flags, LOCF_M_MASK, LOCF_M_CITY)) return false; break; // Marker type flags case 'S': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_SHRINE)) return false; break; case 'G': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_GUILD)) return false; break; case 'P': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_SS)) return false; break; case 'M': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_MONSTER)) return false; break; case 'T': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_TRAINER)) return false; break; case 'F': if (!locCheckMutex(ctx, flags, LOCF_T_MASK, LOCF_T_FORT)) return false; break; // Additional flags case '-': *flags |= LOCF_INVIS; break; case '!': *flags |= LOCF_CLOSED; break; case 'I': *flags |= LOCF_INSTANCED; break; default: endFlags = true; break; } ctx->ch = locFGetc(ctx); } return true; } static void locParseMultiField(LocFileParseContext *ctx, char *fieldsep, char sep, const char *desc, LocName *data) { if (ctx->subField < 0) { ctx->subField = 0; ctx->fieldSep = fieldsep; ctx->sep = sep; } if (ctx->sep == sep) { if (ctx->subField < LOC_MAX_NAMES) { th_free(data[ctx->subField].name); data[ctx->subField++].name = parseFieldString(ctx, ctx->fieldSep); locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); if (!strchr(ctx->fieldSep, ctx->ch)) locPMErr(ctx, "Expected field separator '%s' after %s.\n", ctx->fieldSep, desc); } else locPMErr(ctx, "Too many %s (max %d).\n", desc, LOC_MAX_NAMES); } else { ctx->fieldSep = ";"; ctx->subField = -1; ctx->field++; locPMSet(ctx, PM_FIELD, -1); } } static void locParseLocField(LocFileParseContext *ctx, MapLocations *l, LocMarker *marker) { bool res = false; char *tmpStr; int i; if ((ctx->ch == '\n' || ctx->ch == '\r') && ctx->field < 8) { locPMErr(ctx, "Unexpected end of line.\n"); return; } switch (ctx->field) { case 1: // X-coordinate res = parseFieldInt(ctx, &marker->xc); ctx->fieldSep = ";"; if (res) { ctx->field++; locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); } else locPMErr(ctx, "Error parsing X-coordinate.\n"); break; case 2: // Y-coordinate res = parseFieldInt(ctx, &marker->yc); if (res) { ctx->field++; locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); } else locPMErr(ctx, "Error parsing Y-coordinate.\n"); break; case 3: // Label orientation and flags res = parseFieldInt(ctx, &marker->align); if (res) res = locParseFlags(ctx, &marker->flags); if (res) { ctx->field++; locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); } else locPMErr(ctx, "Error parsing orientation and flags field.\n"); break; case 4: // Location name(s) locParseMultiField(ctx, "|;", '|', "location names", marker->names); break; case 5: // Creators locParseMultiField(ctx, ",;", ',', "creator names", marker->creators); break; case 6: // Date marker->valid = false; tmpStr = parseFieldString(ctx, ctx->fieldSep); if (tmpStr && tmpStr[0]) { if (sscanf(tmpStr, LOC_TIMEFMT, &marker->added.day, &marker->added.month, &marker->added.year) == 3) marker->valid = true; else { locPMErr(ctx, "Warning, invalid timestamp '%s' in '%s'.\n", tmpStr, marker->names[0].name); } } th_free(tmpStr); ctx->field++; locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); break; case 7: // URI th_free(marker->uri); marker->uri = parseFieldString(ctx, ctx->fieldSep); ctx->field++; locPMSet(ctx, PM_NEXT, PM_FIELD_SEP); break; case 8: // Freeform tmpStr = parseFieldString(ctx, "\r\n"); // Check coordinates if (marker->xc < 1 || marker->yc < 1) { locPMErr(ctx, "Invalid X or Y coordinate (%d, %d), for location '%s'. Must be > 0.\n", marker->xc, marker->yc, marker->names[0].name); } // Check if location already exists marker->xc = marker->xc + marker->ox - 1; marker->yc = marker->yc + marker->oy - 1; i = locFindByCoords(l, marker->xc, marker->yc, true); if (i >= 0) { LocMarker *tloc = l->locations[i]; locPMErr(ctx, "Warning, location already in list! (%d,%d) '%s' <-> (%d,%d) '%s'\n", tloc->xc + 1, tloc->yc + 1, tloc->names[0].name, marker->xc + 1, marker->yc + 1, marker->names[0].name); } else { // Add new location to our list locAddNew(l, marker->xc, marker->yc, marker->align, marker->flags, marker->names, marker->creators, &marker->added, marker->valid, marker->uri, tmpStr, ctx->file); locPMSet(ctx, PM_IDLE, -1); } locFreeMarkerData(marker); th_free(tmpStr); break; default: locPMErr(ctx, "FATAL ERROR! Invalid state=%d!\n", ctx->parseMode); } } bool locParseLocStream(FILE *fp, LocFileInfo *file, MapLocations *l, const int offX, const int offY) { LocFileParseContext ctx; LocMarker marker; int i; memset(&ctx, 0, sizeof(ctx)); ctx.fp = fp; ctx.line = 1; ctx.ch = -1; ctx.file = file; memset(&marker, 0, sizeof(marker)); marker.ox = offX; marker.oy = offY; ctx.parseMode = PM_IDLE; ctx.nextMode = ctx.prevMode = PM_ERROR; ctx.field = ctx.subField = ctx.sep = -1; ctx.ch = locFGetc(&ctx); do { switch (ctx.parseMode) { case PM_IDLE: if (ctx.ch == EOF) locPMSet(&ctx, PM_EOF, -1); else if (ctx.ch == '\r') { ctx.line++; ctx.ch = locFGetc(&ctx); if (ctx.ch == '\n') ctx.ch = locFGetc(&ctx); } else if (ctx.ch == '\n') { ctx.line++; ctx.ch = locFGetc(&ctx); } else if (ctx.ch == '#') { locPMSet(&ctx, PM_COMMENT, PM_IDLE); } else if (isdigit(ctx.ch)) { // Start of a record locPMSet(&ctx, PM_FIELD, -1); ctx.field = 1; } else if (isspace(ctx.ch)) { ctx.ch = locFGetc(&ctx); } else { // Syntax error locPMErr(&ctx, "Syntax error in '%s' line #%d.\n", ctx.filename, ctx.line); } break; case PM_COMMENT: switch (ctx.ch) { case '\r': ctx.ch = locFGetc(&ctx); if (ctx.ch == '\n') ctx.ch = locFGetc(&ctx); ctx.line++; ctx.prevMode = ctx.parseMode; ctx.parseMode = ctx.nextMode; break; case '\n': ctx.ch = locFGetc(&ctx); ctx.line++; ctx.prevMode = ctx.parseMode; ctx.parseMode = ctx.nextMode; break; case EOF: ctx.parseMode = PM_EOF; break; default: ctx.ch = locFGetc(&ctx); /* Because loc file identification should be the first * comment line, we check it here. */ if (ctx.versionSet || !isalpha(ctx.ch)) break; char *tmp = parseFieldString(&ctx, "(\n\r"); if (tmp != NULL && !strcmp(tmp, LOC_MAGIC)) { // ID found, check version char *verStr = parseFieldString(&ctx, ")\n\r"); int verMajor, verMinor; if (verStr != NULL && sscanf(verStr, "(version %d.%d", &verMajor, &verMinor) == 2) { if (verMajor != LOC_VERSION_MAJOR) { // Major version mismatch, bail out with informative message THERR( "LOC file format version %d.%d detected, internal version is %d.%d. " "Refusing to read due to potential incompatibilities. If you neverthless " "wish to proceed, change the loc file's version to match internal version.\n", verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); ctx.parseMode = PM_ERROR; } else if (verMinor != LOC_VERSION_MINOR) { // Minor version mismatch, just inform about it THERR("LOC file format version %d.%d detected, internal version is %d.%d, proceeding.\n", verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); } } else { THERR("Invalid or malformed LOC file, version not found (%s).\n", verStr); ctx.parseMode = PM_ERROR; } th_free(verStr); } else { THERR("Invalid LOC file, the file ID is missing ('# %s (version %d.%d)' should be the first line.)\n", LOC_MAGIC, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); ctx.parseMode = PM_ERROR; } th_free(tmp); ctx.versionSet = true; break; } break; case PM_NEXT: switch (ctx.ch) { case EOF: locPMErr(&ctx, "Unexpected end of file.\n"); break; case 32: case 9: ctx.ch = locFGetc(&ctx); break; case '\\': // Enable continuation via '\' at EOL i = locFGetc(&ctx); if (i != '\n' && i != '\r') { locPMErr(&ctx, "Expected end of line.\n"); } else { ctx.line++; ctx.ch = locFGetc(&ctx); if (i == '\r' && ctx.ch == '\n') ctx.ch = locFGetc(&ctx); } break; default: ctx.prevMode = ctx.parseMode; ctx.parseMode = ctx.nextMode; break; } break; case PM_FIELD_SEP: if (strchr(ctx.fieldSep, ctx.ch) != NULL) { ctx.sep = ctx.ch; ctx.ch = locFGetc(&ctx); locPMSet(&ctx, PM_NEXT, PM_FIELD); } else { locPMErr(&ctx, "Expected field separator '%s', got '%c' (%d).\n", ctx.fieldSep, ctx.ch, ctx.ch); } break; case PM_FIELD: locParseLocField(&ctx, l, &marker); break; default: locPMErr(&ctx, "Invalid state in loc-file parser - mode=%d, prev=%d, next=%d.\n", ctx.parseMode, ctx.prevMode, ctx.nextMode); break; } } while (ctx.parseMode != PM_ERROR && ctx.parseMode != PM_EOF); locFreeMarkerData(&marker); return (ctx.parseMode == PM_EOF); } void locSetFileInfo(LocFileInfo *file, const char *filename, const char *continent) { file->filename = th_strdup(filename); file->continent = th_strdup(continent); file->xoffs = file->yoffs = 0; } void locFreeFileInfo(LocFileInfo *file) { th_free(file->filename); th_free(file->continent); }