Mercurial > hg > batmud > maputils
changeset 2346:2dd99055f6d8
Move C source code under src/ subdirectory.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Tue, 27 Jul 2021 10:07:03 +0300 |
parents | 30cfdadef0aa |
children | 4b282f4e5a35 |
files | Makefile.gen colormap.c combine.c diffmap.c liblocfile.c liblocfile.h libmaputils.c libmaputils.h libutil.h map2ppm.c mapsearch.c mapstats.c mkcitymap.c mkloc.c patchmap.c src/colormap.c src/combine.c src/diffmap.c src/liblocfile.c src/liblocfile.h src/libmaputils.c src/libmaputils.h src/libutil.h src/map2ppm.c src/mapsearch.c src/mapstats.c src/mkcitymap.c src/mkloc.c src/patchmap.c src/stitchmap.c stitchmap.c |
diffstat | 31 files changed, 8820 insertions(+), 8820 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.gen Tue Jul 27 09:48:54 2021 +0300 +++ b/Makefile.gen Tue Jul 27 10:07:03 2021 +0300 @@ -55,21 +55,21 @@ ### ### Binary target rules ### -$(OBJPATH)%.o: %.c $(THLIBS_A) - $(COMPILE_C_BIN) -I$(THLIBS) +$(OBJPATH)%.o: src/%.c $(THLIBS_A) + $(COMPILE_C_OBJ) -I$(THLIBS) $(BINPATH)%$(BINEXT): $(OBJPATH)%.o $(LIBMAPUTILS_OBJ) $(LIBLOCFILE_OBJ) $(THLIBS_A) $(THLIBS_DEP) $(LINK_C_BIN) -$(OBJPATH)map2ppm.o: map2ppm.c $(THLIBS_A) +$(OBJPATH)map2ppm.o: src/map2ppm.c $(THLIBS_A) $(COMPILE_C_OBJ) $(MAP2PPM_CFLAGS) $(MAP2PPM_BIN): $(OBJPATH)map2ppm.o $(LIBMAPUTILS_OBJ) $(THLIBS_A) $(THLIBS_DEP) $(LINK_C_BIN) $(MAP2PPM_LDFLAGS) $(THLIBS_A) -$(OBJPATH)mapsearch.o: mapsearch.c $(THLIBS_A) +$(OBJPATH)mapsearch.o: src/mapsearch.c $(THLIBS_A) $(COMPILE_C_OBJ) $(MAPSEARCH_CFLAGS) $(MAPSEARCH_BIN): $(OBJPATH)mapsearch.o $(LIBMAPUTILS_OBJ) $(LIBLOCFILE_OBJ) $(THLIBS_A) $(THLIBS_DEP)
--- a/colormap.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,845 +0,0 @@ -/* - * Convert BatMUD ASCII map to different formats - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - -#define SET_MAX_COLORS (256) -#define SET_MAX_STRLEN (1024) - - -typedef struct -{ - char *fmtName; - char *fmtDescription; - BOOL supBackColor; - void (*putTagMarker) (FILE *, const char *, const int color, const int marker); - void (*putTagLocation) (FILE *, const char *, const int color); - void (*putTagLocationEnd) (FILE *, const char *); - void (*putTagStart) (FILE *, const int color); - void (*putTagEnd) (FILE *); - BOOL (*putTagEOL) (FILE *); - void (*putFileStart) (FILE *); - void (*putFileEnd) (FILE *); - int (*putString) (const char *str, FILE *); -} CMapOutFormat; - - -char *inFilename = NULL, - *outFilename = NULL; - -char *optXHTMLTagName = NULL, - *optMapTitle = NULL, - *optUrchinFile = NULL; - -BOOL optPerformAnalysis = FALSE, - optUseOldFormat = FALSE, - optCheatMode = FALSE, - optNoHeaders = FALSE, - optPosGlue = FALSE, - optCityFormat = FALSE; - -int optOutputFormat = 0, - optBackColor = 0; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'q', "quiet", "Be quiet", OPT_NONE }, - { 3, 'T', "html-tag", "XHTML tag used for map", OPT_ARGREQ }, - { 4, 'a', "analysis", "Perform statistical analysis", OPT_NONE }, - { 6, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, - { 7, 'o', "output", "Output filename", OPT_ARGREQ }, - { 8, 'f', "format", "Specify output format", OPT_ARGREQ }, - { 9, 't', "title", "Map title", OPT_ARGREQ }, - { 10,'C', "cheat-mode", "Use cheating in HTML", OPT_NONE }, - { 11,'n', "no-headers", "Do not output headers/footers", OPT_NONE }, - { 12,'P', "pos-glue", "Generate JavaScript, etc. for posglue", OPT_NONE }, - { 13,'c', "city-format", "Input is a city map", OPT_NONE }, - { 14,'u', "urchin-file", "Specify urchin file", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -/* - * ANSI output format functions - */ -void putTagStartANSI(FILE *outFile, const int c) -{ - fputc(0x1b, outFile); - - if (c < 0) - { - fprintf(outFile, "[0;%d;30m", - 10 + mapColors[-c-1].ansi); - } - else - { - fprintf(outFile, "[0;%d;%dm", - mapColors[c].ansiAttr, - mapColors[c].ansi); - } -} - -void putTagEndANSI(FILE *outFile) -{ - (void) outFile; - /* - fputc(0x1b, outFile); - fprintf(outFile, "[0m"); - */ -} - -BOOL putEOLANSI(FILE *outFile) -{ - fprintf(outFile, "\n"); - return TRUE; -} - -void putFileStartANSI(FILE *outFile) -{ - fputc(0x1b, outFile); - fprintf(outFile, "[0m"); - - if (optMapTitle) - { - fprintf(outFile, "%s\n", optMapTitle); - } -} - - -void putFileEndANSI(FILE *outFile) -{ - fputc(0x1b, outFile); - fprintf(outFile, "[0m"); -} - - -/* - * ASCII output format functions - */ -void putTagStartText(FILE *outFile, const int c) -{ - (void) outFile; (void) c; -// fprintf(outFile, "§"); -} - -void putTagEndText(FILE *outFile) -{ - (void) outFile; -// fprintf(outFile, "$"); -} - -BOOL putEOLText(FILE *outFile) -{ - fprintf(outFile, "\n"); - return FALSE; -} - -void putFileStartText(FILE *outFile) -{ - if (optMapTitle) - { - fprintf(outFile, "%s\n", optMapTitle); - } -} - - -/* - * XHTML+CSS output format functions - */ -#define XHTML_LOCLABEL "label" -#define XHTML_LOCMARKER "marker" - -void putColorClassXHTML(FILE *outFile, const int color) -{ - int q = 'a' + color; - - if (optCheatMode) - fprintf(outFile, "class=%c>", q); - else - fprintf(outFile, "class=\"%c\">", q); -} - -void putTagMarkerXHTML(FILE *outFile, const char *locID, const int color, const int marker) -{ - fprintf(outFile, - "<i class=\"" XHTML_LOCMARKER " %c\" id=\"%s\">%c", - 'a' + color, locID, marker); -} - -void putTagLocationXHTML(FILE *outFile, const char *locID, const int color) -{ - fprintf(outFile, - "<i class=\"" XHTML_LOCLABEL "\"><a id=\"%s\" ", - locID); - putColorClassXHTML(outFile, color); -} - -void putTagLocationEndXHTML(FILE *outFile, const char *locID) -{ - (void) locID; - fprintf(outFile, "</a>"); -} - -void putTagStartXHTML(FILE *outFile, const int c) -{ - fprintf(outFile, "<i "); - putColorClassXHTML(outFile, c); -} - -void putTagEndXHTML(FILE *outFile) -{ - fprintf(outFile, "</i>"); -} - -BOOL putEOLXHTML(FILE *outFile) -{ - fprintf(outFile, "\n"); - return FALSE; -} - -void putFileGenericHTML(FILE *outFile) -{ - char buf[32]; - - fprintf(outFile, - " <script type=\"text/javascript\" src=\"util.js\"></script>\n" - " <style type=\"text/css\">\n" - " <!--\n" - " %s." XHTML_LOCLABEL " { background: white; color: black; %s}\n" - " body { background: black; color: %s; font-family: Arial, Verdana, sans-serif; }\n" - " h2 { color: white; font-size: 14pt; }\n" - " pre { font-size: 10pt; }\n" - " %s { text-decoration: none; font-style: normal; }\n", - optXHTMLTagName, (optPosGlue) ? "position: relative; " : "", - muColorToCSSColor(buf, sizeof(buf), optBackColor), - optXHTMLTagName - ); - - muPrintHTMLcolors(outFile, optXHTMLTagName, NULL, NULL); - muPrintHTMLcolors(outFile, "a", "background", " color: black;"); - - if (optPosGlue) - { - fprintf(outFile, - " div.sgbox {\n" - " background: #bbb;\n" - " color: black;\n" - " position: fixed;\n" - " top: 0.5em;\n" - " right: 0.5em;\n" - " width: 25em;\n" - " height: 6em;\n" - " margin: 4px;\n" - " padding: 4px;\n" - " z-index: 10;\n" - " border: 2px solid gray;\n" - " font-size: 10pt;\n" - " }\n" - " i." XHTML_LOCLABEL " a {\n" - " position: absolute;\n" - " top: 0px;\n" - " left: 0px;\n" - " z-index: 2;\n" - " border: 2px solid black;\n" - " border-radius: 0.35em;\n" - " padding: 0.15em;\n" - " box-shadow: 2px 2px 4px black;\n" - " }\n" - " div.sbtitle {\n" - " font-size: 1.5em;\n" - " font-weight: bold;\n" - " }\n" - " i.label a.nactive {\n" - " border: 2px solid rgba(100,0,0, 0.5);\n" - " background: rgba(255,0,0, 0.7);\n" - " color: rgba(255,255,255, 0.8);\n" - " text-shadow: 1px 1px 1px #000;\n" - " font-weight: bold;\n" - " transform: scale(1.1);\n" - " }\n" - " i.marker.nactive {\n" - " animation: pulse 1s ease infinite;\n" - " }\n" - " @keyframes pulse {\n" - " 0%% { background: #000; }\n" - " 50%% { background: #f00; }\n" - " 100%% { background: #000; }\n" - " }\n" - ); - } - - fprintf(outFile, - " -->\n" - " </style>\n" - ); - - if (optUrchinFile) - { - if (muCopyFileToStream(outFile, optUrchinFile) < 0) - { - THERR("Error copying urchin file '%s' to output!\n", optUrchinFile); - exit(17); - } - } - - fprintf(outFile, - "</head>\n" - "<body onLoad=\"mapOnLoad();\">\n"); - - if (!optPosGlue) - { - if (optMapTitle) - { - fprintf(outFile, "<h2>"); - fputse(optMapTitle, outFile); - fprintf(outFile, "</h2>\n"); - } - } - else - { - fprintf(outFile, - "<div id=\"sbox\" class=\"sgbox\">\n" - " <div class=\"sbtitle\">"); - fputse(optMapTitle, outFile); - fprintf(outFile, - "</div>\n" - " <form>\n" - " <select id=\"slocation\" onChange=\"mapGotoPos();\" autofocus=\"autofocus\">\n" - "@LOCATIONS@\n" - " </select>\n" - " <br />\n" - " <input id=\"shide\" onClick=\"mapToggleLabels();\" type=\"checkbox\"%s><label for=\"shide\">Labels</label>\n" - " <input id=\"sscroll\" type=\"checkbox\"%s><label for=\"sscroll\">Smooth scroll</label>\n" - " </form>\n" - "</div>\n", - (1 ? " checked=\"checked\"" : ""), - (0 ? " checked=\"checked\"" : "") - ); - } - - fprintf(outFile, - "<pre>\n" - ); -} - -void putFileStartXHTML(FILE *outFile) -{ - muPrintHTMLhead(outFile, optMapTitle, FALSE); - putFileGenericHTML(outFile); -} - -void putFileEndXHTML(FILE *outFile) -{ - // XHTML document end tags - fprintf(outFile, - "</pre>\n" - "</body>\n" - "</html>\n" - ); -} - - -/* - * XHTML+CSS output format functions - */ -void putFileStartHTML5(FILE *outFile) -{ - muPrintHTMLhead(outFile, optMapTitle, TRUE); - putFileGenericHTML(outFile); -} - - - -/* Process a normal format input - */ -void checkEndTag(FILE *outFile, const CMapOutFormat *fmt, const int prevColor) -{ - if (prevColor != -1 && (!fmt->supBackColor || prevColor != optBackColor)) - fmt->putTagEnd(outFile); -} - - -BOOL getTagStr(FILE *inFile, char *tmpStr, const size_t len, const int endch) -{ - size_t i; - int mch = EOF; - - for (i = 0; i + 1 < len && (mch = fgetc(inFile)) != EOF;) - { - if (mch == endch) - break; - else - tmpStr[i++] = mch; - } - - tmpStr[i] = 0; - - if (mch == EOF) - { - THERR("Unexpected end of file.\n"); - return FALSE; - } - else - if (mch != endch) - { - THERR("No end tag 0x%02x found.\n", endch); - return FALSE; - } - - return TRUE; -} - - -BOOL processData(FILE *inFile, FILE *outFile, const CMapOutFormat *fmt) -{ - int k, currColor, prevColor; - - currColor = prevColor = -1; - while ((k = fgetc(inFile)) != EOF) - { - if (k == '\n') - { - if (fmt->putTagEOL(outFile)) - currColor = -1; - } - else - if (k == 0xfb) - { - char tmpStr[SET_MAX_STRLEN]; - int mch; - - // Location marker mode - checkEndTag(outFile, fmt, prevColor); - - // Get location marker tag - if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) - return FALSE; - - // Get marker character - mch = fgetc(inFile); - k = fgetc(inFile); - - if (k != 0xfe) - { - THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); - return FALSE; - } - - currColor = muGetMapPieceColor(mch, optUseOldFormat, optCityFormat); - if (fmt->putTagMarker != NULL) - fmt->putTagMarker(outFile, tmpStr, currColor, mch); - else - { - if (fmt->putTagStart) - fmt->putTagStart(outFile, currColor); - - fprintf(outFile, "%c", mch); - } - - currColor = prevColor = -2; - } - else - if (k == 0xff) - { - char tmpStr[SET_MAX_STRLEN], tmpStr2[SET_MAX_STRLEN]; - int mcol; - - // Location title mode - checkEndTag(outFile, fmt, prevColor); - - // Get location marker tag - if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) - return FALSE; - - // Get color - mcol = fgetc(inFile); - if (mcol == EOF) - { - THERR("Unexpected end of file (location tag colour).\n"); - return FALSE; - } - - // Get location name - if (!getTagStr(inFile, tmpStr2, SET_MAX_STRLEN, 0xfe)) - { - THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); - return FALSE; - } - - if (!fmt->putTagLocation && fmt->putTagStart) - fmt->putTagStart(outFile, -mcol - 1); - - if (fmt->putTagLocation) - fmt->putTagLocation(outFile, tmpStr, mcol); - - if (fmt->putString) - fmt->putString(tmpStr2, outFile); - else - fputs(tmpStr2, outFile); - - if (fmt->putTagLocationEnd) - fmt->putTagLocationEnd(outFile, tmpStr); - - currColor = prevColor = -2; - } - else - { - currColor = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); - if (currColor != prevColor) - { - checkEndTag(outFile, fmt, prevColor); - - if ((!fmt->supBackColor || currColor != optBackColor) && fmt->putTagStart) - fmt->putTagStart(outFile, currColor); - - fprintf(outFile, "%c", k); - } - else - { - fprintf(outFile, "%c", k); - } - } - - prevColor = currColor; - } - - checkEndTag(outFile, fmt, prevColor); - - return TRUE; -} - - -/* Get a symbol - */ -char getSymbol(int i, BOOL useOld) -{ - if (useOld) - return mapPieces[i].oldSymbol; - else - return mapPieces[i].symbol; -} - - -/* List of output formats - */ -CMapOutFormat outputFormats[] = -{ - { "xhtml", "XHTML+CSS", TRUE, - putTagMarkerXHTML, - putTagLocationXHTML, putTagLocationEndXHTML, - putTagStartXHTML, putTagEndXHTML, putEOLXHTML, - putFileStartXHTML, putFileEndXHTML, fputse - }, - - { "html5", "HTML5+CSS", TRUE, - putTagMarkerXHTML, - putTagLocationXHTML, putTagLocationEndXHTML, - putTagStartXHTML, putTagEndXHTML, putEOLXHTML, - putFileStartHTML5, putFileEndXHTML, fputse - }, - - { "ansi", "ANSI text", FALSE, - NULL, NULL, NULL, - putTagStartANSI, putTagEndANSI, putEOLANSI, - putFileStartANSI, putFileEndANSI, NULL - }, - - { "text", "Plain ASCII text", FALSE, - NULL, NULL, NULL, - putTagStartText, putTagEndText, putEOLText, - putFileStartText, NULL, NULL - }, -}; - -const int noutputFormats = sizeof(outputFormats) / sizeof(outputFormats[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <input mapfile>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); - - fprintf(stdout, "\nAvailable OUTPUT formats:\n"); - for (int i = 0; i < noutputFormats; i++) - { - fprintf(stdout, " %-8s - %s %s\n", - outputFormats[i].fmtName, - outputFormats[i].fmtDescription, - (i == optOutputFormat) ? "(default)" : "" - ); - } - - fprintf(stdout, "\n"); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - th_verbosity = -1; - break; - - case 3: - { - char *tmp = th_strdup_trim(optArg, TH_TRIM_BOTH); - if (tmp == NULL) - { - THERR("Can't set HTML tag to empty string.\n"); - return FALSE; - } - - th_free(optXHTMLTagName); - optXHTMLTagName = tmp; - THMSG(2, "HTML tag set to '%s'.\n", optXHTMLTagName); - } - break; - - case 4: - optPerformAnalysis = TRUE; - THMSG(2, "Analysis enabled.\n"); - break; - - case 6: - optUseOldFormat = TRUE; - THMSG(2, "Input is using old map symbols/colors.\n"); - break; - - case 7: - outFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", outFilename); - break; - - case 8: - { - int i, n; - // Match format from the list - for (i = 0, n = -1; i < noutputFormats && n < 0; i++) - if (th_strcasecmp(optArg, outputFormats[i].fmtName) == 0) - n = i; - - if (n < 0) - { - THERR("Invalid output format '%s'.\n", optArg); - return FALSE; - } - - optOutputFormat = n; - THMSG(2, "Output format set to '%s'.\n", optArg); - } - break; - - case 9: - optMapTitle = optArg; - THMSG(2, "Map title string set to '%s'.\n", optMapTitle); - break; - - case 10: - optCheatMode = TRUE; - THMSG(2, "HTML cheats mode enabled!\n"); - break; - - case 11: - optNoHeaders = TRUE; - THMSG(2, "Not outputting headers/footers\n"); - break; - - case 12: - optPosGlue = TRUE; - THMSG(2, "Generating JavaScript junk for positioning glue\n"); - break; - - case 13: - optCityFormat = TRUE; - THMSG(2, "Input is handled as a city map\n"); - break; - - case 14: - optUrchinFile = optArg; - THMSG(2, "Urchin filename set to '%s'.\n", optUrchinFile); - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (!inFilename) - inFilename = currArg; - else - { - THERR("Too many input map files specified!\n"); - return FALSE; - } - - return TRUE; -} - - -/* Main program - */ -int main(int argc, char *argv[]) -{ - FILE *inFile = NULL, *outFile = NULL; - CMapOutFormat *fmt = NULL; - - // Initialize - th_init("colormap", "ASCII map colorizer", "0.5", NULL, NULL); - th_verbosity = 0; - optXHTMLTagName = th_strdup("i"); - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - exit(1); - - if (inFilename == NULL) - { - THERR("Nothing to do. (try --help)\n"); - exit(0); - } - - // Do statistical analysis, if needed - if (optPerformAnalysis) - { - int k, c, p, i, tMax, fMax, tn, fn; - int colChangesTo[SET_MAX_COLORS]; - int colChangesFrom[SET_MAX_COLORS]; - - THMSG(1, "Performing statistical frequency analysis ...\n"); - - THMSG(2, "Reading '%s' for analysis data ...\n", - inFilename); - - if ((inFile = fopen(inFilename, "rb")) == NULL) - { - THERR("Error opening input file '%s'!\n", - inFilename); - exit(1); - } - - // Initialize counters - memset(colChangesTo, 0, sizeof(colChangesTo)); - memset(colChangesFrom, 0, sizeof(colChangesFrom)); - - // Read data, keeping statistics of colour change frequencies - c = p = -1; - while ((k = fgetc(inFile)) != EOF) - { - if (k != '\n' && k != ' ' && k < 0xfb) - { - c = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); - if (c != p && c >= 0 && c < SET_MAX_COLORS) - { - colChangesTo[c]++; - - if (p >= 0 && p < SET_MAX_COLORS) - colChangesFrom[p]++; - } - } - p = c; - } - - fclose(inFile); - - // Find highest frequency - THMSG(2, "Computing results.\n"); - - tMax = fMax = tn = fn = -1; - for (i = 0; i < SET_MAX_COLORS; i++) - { - if (colChangesTo[i] > tMax) - { - tMax = colChangesTo[i]; - tn = i; - } - if (colChangesFrom[i] > fMax) - { - fMax = colChangesFrom[i]; - fn = i; - } - } - - THMSG(2, "tMax=%d, tn=%d -- fMax=%d, fn=%d\n", - tMax, tn, fMax, fn); - - if (tMax > fMax) - optBackColor = tn; - else - optBackColor = fn; - - THMSG(1, "Using colour %d as 'background' color.\n", - optBackColor); - } - - // Open input file - if ((inFile = fopen(inFilename, "rb")) == NULL) - { - THERR("Error opening input file '%s'!\n", - inFilename); - goto out; - } - - // Open output file - if (outFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(outFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - outFilename); - goto out; - } - - // Okay, let's process the shit - fmt = &outputFormats[optOutputFormat]; - THMSG(1, "Converting to '%s' ...\n", fmt->fmtName); - - if (!optNoHeaders && fmt->putFileStart) - fmt->putFileStart(outFile); - - processData(inFile, outFile, fmt); - - if (!optNoHeaders && fmt->putFileEnd) - fmt->putFileEnd(outFile); - -out: - if (outFile != NULL) - fclose(outFile); - - if (inFile != NULL) - fclose(inFile); - - THMSG(1, "Done.\n"); - - exit(0); - return 0; -}
--- a/combine.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,379 +0,0 @@ -/* - * Combine several maps into one bigger - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - - -#define SET_MAX_FILES (256) - - -typedef struct -{ - char *filename; - int xc, yc; - MapBlock *blk; -} MapFile; - - -/* Variables - */ -int nsrcFiles = 0; -MapFile srcFiles[SET_MAX_FILES]; -char *dstFile = NULL; -int optFillChar = -1; -BOOL optInputIsDiff = FALSE; -int optWorldMinW = 0, - optWorldMinH = 0; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'o', "output", "Specify output file", OPT_ARGREQ }, - { 3, 'q', "quiet", "Be quiet", OPT_NONE }, - { 5, 'd', "diff", "Map files are in 'diff' format", OPT_NONE }, - { 4, 'f', "fill", "Fill character", OPT_ARGREQ }, - { 6, 'w', "width", "Minimum width", OPT_ARGREQ }, - { 7, 'h', "height", "Minimum height", OPT_ARGREQ }, -}; - -const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp() -{ - th_print_banner(stdout, th_prog_name, - "[options] <mapfile1:x-offset:y-offset> [<mapfile2:x:y> ...]"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - dstFile = optArg; - THMSG(1, "Output file '%s'\n", dstFile); - break; - - case 3: - th_verbosity = -1; - break; - - case 4: - if (optArg[1] != 0) - { - THERR("Fill character is not a string, dumbass!\n"); - return FALSE; - } - - optFillChar = optArg[0]; - THMSG(1, "Using fill character '%c'\n", optFillChar); - break; - - case 5: - THMSG(1, "Assuming all input files are diffs.\n"); - optInputIsDiff = TRUE; - break; - - case 6: - case 7: - { - char *s; - int v = atoi(optArg); - if (optN == 6) - { - s = "width"; - optWorldMinW = v; - } - else - { - s = "height"; - optWorldMinH = v; - } - - if (v < 0 || v > 128 * 1024) - { - THERR("Invalid %s setting %d!\n", s, v); - return FALSE; - } - - THMSG(1, "Using world minimum %s %d\n", s, v); - } - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - break; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - char *tmpArg = NULL; - - if (nsrcFiles < SET_MAX_FILES) - { - char *q, *c; - MapFile *file = &srcFiles[nsrcFiles++]; - - if ((tmpArg = th_strdup(currArg)) == NULL) - { - THERR("Could not allocate temp buffer!\n"); - goto err; - } - - // Get filename component - for (q = c = tmpArg; *c && (*c != ':'); c++); - if (*c != ':') - { - THERR("Expected ':' after filename in '%s'.\n", - currArg); - goto err; - } - - *(c++) = 0; - file->filename = th_strdup(q); - - // Get X offset - if (!th_isdigit(*c) && *c != '-') - { - THERR("Expected decimal X offset value in '%s'.\n", currArg); - goto err; - } - - q = c; if (*c == '-') c++; - while (*c && th_isdigit(*c)) c++; - if (*c != ':') - { - THERR("Expected ':' after X offset value in '%s'.\n", currArg); - goto err; - } - - *(c++) = 0; - file->xc = atoi(q); - - // Get Y offset - if (!th_isdigit(*c) && *c != '-') - { - THERR("Expected decimal Y offset value in '%s'.\n", currArg); - goto err; - } - - q = c; - if (*c == '-') c++; - - while (*c && th_isdigit(*c)) c++; - if (*c != 0) - { - THERR("Invalid Y offset value in '%s'.\n", currArg); - goto err; - } - - file->yc = atoi(q); - } - else - { - THERR("Too many input files specified (%d max)!\n", SET_MAX_FILES); - goto err; - } - - th_free(tmpArg); - return TRUE; - -err: - th_free(tmpArg); - return FALSE; -} - - -void findWorldSize(MapBlock *tmp, - int *worldX0, int *worldY0, - int *worldX1, int *worldY1) -{ - if (tmp->xc < *worldX0) - *worldX0 = tmp->xc; - if (tmp->xc + tmp->width > *worldX1) - *worldX1 = tmp->xc + tmp->width; - - if (tmp->yc < *worldY0) - *worldY0 = tmp->yc; - if (tmp->yc + tmp->height > *worldY1) - *worldY1 = tmp->yc + tmp->height; -} - - -int main(int argc, char *argv[]) -{ - int i, worldX0, worldY0, worldX1, worldY1, worldW, worldH; - MapBlock *worldMap = NULL; - FILE *tmpFile = NULL; - - th_init("combine", "Combine several maps into one", "0.1", NULL, NULL); - th_verbosity = 0; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - goto exit; - - if (nsrcFiles < 1) - { - THERR("Nothing to do. (try --help)\n"); - goto exit; - } - - - /* Read map files - */ - for (i = 0; i < nsrcFiles; i++) - { - MapFile *file = &srcFiles[i]; - if ((file->blk = mapBlockParseFile(file->filename, optInputIsDiff)) == NULL) - { - THERR("Error reading input file '%s'!\n", - file->filename); - goto exit; - } - - mapBlockClean(file->blk, (unsigned char *) " ", 1); - file->blk->xc = file->xc; - file->blk->yc = file->yc; - } - - THMSG(2, "Total of %d maps read.\n", nsrcFiles); - - if (nsrcFiles <= 0) - { - THERR("No maps, nothing to do.\n"); - goto exit; - } - - - // Get world dimensions - worldX0 = worldY0 = worldX1 = worldY1 = 0; - for (i = 0; i < nsrcFiles; i++) - findWorldSize(srcFiles[i].blk, &worldX0, &worldY0, &worldX1, &worldY1); - - worldW = worldX1 - worldX0 + 1; - worldH = worldY1 - worldY0 + 1; - - THMSG(2, "Initial dimensions <%d, %d> - <%d, %d> (%d x %d)\n", - worldX0, worldY0, worldX1, worldY1, worldW, worldH); - - // Adjust to origo <0, 0> - worldX0 = -worldX0; - worldY0 = -worldY0; - worldX1 = worldX0 + worldW - 1; - worldY1 = worldY0 + worldH - 1; - - THMSG(2, "Adjusted dimensions <%d, %d> - <%d, %d> (%d x %d)\n", - worldX0, worldY0, worldX1, worldY1, worldW, worldH); - - // Adjust for requested minimum dimensions - if (worldW < optWorldMinW) - { - int tmp = (optWorldMinW - worldW) / 2; - worldX0 += tmp; - worldX1 += tmp; - worldW = optWorldMinW; - } - if (worldH < optWorldMinH) - { - int tmp = (optWorldMinH - worldH) / 2; - worldY0 += tmp; - worldY1 += tmp; - worldH = optWorldMinH; - } - - // Allocate world map - THMSG(1, "Initializing world map of <%d, %d> - <%d, %d> (%d x %d)\n", - worldX0, worldY0, worldX1, worldY1, - worldW, worldH); - - if ((worldMap = mapBlockAlloc(worldW, worldH)) == NULL) - { - THERR("Error allocating world map!\n"); - goto exit; - } - - - /* Clear with some character, if requested - */ - if (optFillChar > 0) - { - memset(worldMap->data, optFillChar, worldMap->size); - } - - - /* Blit map blocks - */ - for (i = 0; i < nsrcFiles; i++) - { - MapFile *file = &srcFiles[i]; - if (mapBlockPutDo(worldMap, file->blk, worldX0 + file->blk->xc, worldY0 + file->blk->yc) != 0) - { - THERR("Mapblock #%d ['%s' @ %d,%d] placement failed!\n", - i, file->filename, file->blk->xc, file->blk->yc); - goto exit; - } - } - - - /* Output generated map - */ - if (worldMap != NULL) - { - - THMSG(1, "Outputting generated map of (%d x %d) ...\n", - worldMap->width, worldMap->height); - - if (dstFile == NULL) - tmpFile = stdout; - else - if ((tmpFile = fopen(dstFile, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", dstFile); - goto exit; - } - - if (optInputIsDiff) - mapBlockPrintRaw(tmpFile, worldMap); - else - mapBlockPrint(tmpFile, worldMap); - } - else - { - THERR("No map generated?\n"); - } - -exit: - mapBlockFree(worldMap); - - if (tmpFile != NULL) - fclose(tmpFile); - - return 0; -}
--- a/diffmap.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,267 +0,0 @@ -/* - * Compute 'diff' between two maps - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_util.h" -#include "th_args.h" -#include "th_string.h" - - -#define SET_DEF_FILTER "C?%" - - -/* Variables - */ -char *srcFilename1 = NULL, - *srcFilename2 = NULL, - *destFilename = NULL; -BOOL optUseOldFormat = FALSE, - optAlwaysDiff = FALSE; -char *optFilter1 = "", - *optFilter2 = SET_DEF_FILTER; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'q', "quiet", "Be quiet", OPT_NONE }, - { 3, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, - { 4, 'o', "output", "Output filename", OPT_ARGREQ }, - { 5, 'F', NULL, "Filter chars (none) for mapfile #1", OPT_ARGREQ }, - { 6, 'f', NULL, "Filter chars (" SET_DEF_FILTER ") for mapfile #2", OPT_ARGREQ }, - { 7, 'a', "always", "Always output diff file even when no changes", OPT_NONE }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <mapfile #1> <mapfile #2>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - th_verbosity = -1; - break; - - case 3: - optUseOldFormat = TRUE; - THMSG(2, "Input is using old map symbols/colors.\n"); - break; - - case 4: - destFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", optArg); - break; - - case 5: - optFilter1 = optArg; - THMSG(2, "Filter characters for mapfile #1 set to'%s'.\n", optArg); - break; - - case 6: - optFilter2 = optArg; - THMSG(2, "Filter characters for mapfile #2 set to'%s'.\n", optArg); - break; - - case 7: - optAlwaysDiff = TRUE; - THMSG(2, "Outputting diff file even with no changes.\n"); - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (srcFilename1 == NULL) - srcFilename1 = currArg; - else - if (srcFilename2 == NULL) - srcFilename2 = currArg; - else - { - THERR("Too many input map files specified!\n"); - return FALSE; - } - - return TRUE; -} - - -MapBlock * diffBlocks(const MapBlock *map1, const MapBlock *map2, - const BOOL useOld, const char *filter1, const char *filter2, - size_t *count) -{ - MapBlock *res; - size_t - nfilter1 = strlen(filter1), - nfilter2 = strlen(filter2); - - if (map1->width != map2->width || map1->height != map2->height) - { - THERR("Mapblock size mismatch (%d, %d) vs (%d, %d)\n", - map1->width, map1->height, - map2->width, map2->height); - return NULL; - } - - if ((res = mapBlockAlloc(map1->width, map1->height)) == NULL) - { - THERR("Could not allocate mapblock (%d, %d)\n", - map1->width, map1->height); - return NULL; - } - - *count = 0; - for (int y = 0; y < map1->height; y++) - { - unsigned char - *p1 = map1->data + (map1->scansize * y), - *p2 = map2->data + (map2->scansize * y), - *pd = res->data + (res->scansize * y); - - for (int x = 0; x < map1->width; x++) - { - int c; - - if (*p1 != *p2 && - !muStrChr((unsigned char *) filter1, nfilter1, *p1) && - !muStrChr((unsigned char *) filter2, nfilter2, *p2)) - { - c = muGetMapPieceIndex(*p2, useOld, FALSE); - *pd = (c < 0) ? 0xfd : (c | 64); - THMSG(2, "[%d,%d]: %d -> %d\n", x+1, y+1, *p1, *p2); - (*count)++; - } - else - { - c = muGetMapPieceIndex(*p1, useOld, FALSE); - *pd = (c < 0) ? 0xfd : c; - } - - p1++; - p2++; - pd++; - } - } - - return res; -} - - -int main(int argc, char *argv[]) -{ - MapBlock - *srcMap1 = NULL, - *srcMap2 = NULL, - *resMap = NULL; - FILE *outFile = NULL; - size_t count; - int res = 0; - - // Initialize - th_init("diffmap", "Create a diff between two ASCII mapfiles", "0.5", NULL, NULL); - th_verbosity = 1; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, TRUE)) - { - res = 1; - goto exit; - } - - if (srcFilename1 == NULL || srcFilename2 == NULL) - { - THERR("Nothing to do. (try --help)\n"); - res = 1; - goto exit; - } - - // Read mapfiles - if ((srcMap1 = mapBlockParseFile(srcFilename1, FALSE)) == NULL || - (srcMap2 = mapBlockParseFile(srcFilename2, FALSE)) == NULL) - { - res = 2; - goto exit; - } - - // Compute and output data - count = 0; - if ((resMap = diffBlocks(srcMap1, srcMap2, - optUseOldFormat, optFilter1, optFilter2, &count)) == NULL) - { - THERR("Could not create diff between inputs!\n"); - res = 3; - goto exit; - } - THMSG(1, "%" PRIu_SIZE_T " differences.\n", count); - - // Open output file - res = count > 0 ? 0 : 4; - if (optAlwaysDiff || count > 0) - { - THMSG(2, "Outputting map diff %d x %d...\n", - resMap->width, resMap->height); - - if (destFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(destFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - destFilename); - res = 6; - goto exit; - } - - fprintf(outFile, DIFF_MAGIC "%d\n", DIFF_VERSION); - mapBlockPrintRaw(outFile, resMap); - - } - else - { - THMSG(2, "Not outputting diff as no changes were found.\n"); - goto exit; - } - - -exit: - if (outFile != NULL) - fclose(outFile); - - mapBlockFree(srcMap1); - mapBlockFree(srcMap2); - mapBlockFree(resMap); - - return res; -}
--- a/liblocfile.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,832 +0,0 @@ -/* - * liblocfile - Location file format handling - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 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; - - -static void locAddName(LocName *dst, int *ndst, 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; - case '!': n++; flags |= NAME_RECODER; break; - case '%': n++; flags |= NAME_MAINTAINER; break; - case '&': n++; flags |= NAME_EXPANDER; break; - } - dst[*ndst].name = th_strdup(&(src[nsrc].name[n])); - dst[*ndst].flags = flags; - (*ndst)++; - } -} - - -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->ncoders; i++) - res->coders[i].name = th_strdup(src->coders[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 *coders, 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++) - { - locAddName(tmp->names, &tmp->nnames, names, i); - locAddName(tmp->coders, &tmp->ncoders, coders, 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->coders[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: // Coders - locParseMultiField(ctx, ",;", ',', "coder names", marker->coders); - 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->coders, &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); -} - - -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"; -} - - -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); -}
--- a/liblocfile.h Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -/* - * liblocfile - Location file format handling - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#ifndef LIBLOCFILE_H -#define LIBLOCFILE_H - -#include "libutil.h" -#include <time.h> -#include <stdio.h> - - -/* Version string - */ -#define LOC_MAGIC "MapUtils LOC file" -#define LOC_VERSION_MAJOR (4) -#define LOC_VERSION_MINOR (0) - - -/* LOCD_* is an enum describing the preferred orientation of the - * location marker _label_ in relation to the marker spot. - */ -enum { - LOCD_NONE = 0, - LOCD_LEFT, - LOCD_LEFTDOWN, - LOCD_DOWN -}; - - -/* Timestamp used in loc-files, in sscanf() format. - */ -#define LOC_TIMEFMT "%02d.%02d.%04d" - - -/* These flags are used to describe the marker/location type and label - * related information: [LOCF_M_*] Draw marker of specified type (by default, - * no marker is drawn). LOCF_M_CITY and LOCF_M_PCITY are special, they also - * work as aliases to LOCF_T_CITY/PCITY and thus have similar functionality. - * - * [LOCF_T_*] specify the type of the location/marker. Currently the effect - * of this settings is addition of "GUILD", "SHRINE"-type strings to labels, - * but this information can be used in other ways too. - */ -#define LOCF_NONE (0x00000) - -// Marker types -#define LOCF_M_SCENIC1 (0x000001) // '?' Scenic marker -#define LOCF_M_SCENIC2 (0x000002) // '%' Shrine marker/etc -#define LOCF_M_PCITY (0x000004) // 'C' Player city -#define LOCF_M_CITY (0x000008) // 'c' City -#define LOCF_M_MASK (0x00000F) - -// Location types -#define LOCF_T_SHRINE (0x000010) // 'S' Raceshrine -#define LOCF_T_GUILD (0x000020) // 'G' Guild -#define LOCF_T_SS (0x000040) // 'P' Player guild/Secret Society -#define LOCF_T_MONSTER (0x000080) // 'M' Special monster -#define LOCF_T_TRAINER (0x000100) // 'T' Guild trainer -#define LOCF_T_FORT (0x000200) // 'F' Regions fort -#define LOCF_T_MASK (0x00FFF0) -#define LOCF_MASK (LOCF_M_MASK | LOCF_T_MASK) - -// Extra flags -#define LOCF_INVIS (0x010000) // '-' Invisible marker / Don't show label -#define LOCF_CLOSED (0x020000) // '!' Area is CLOSED -#define LOCF_INSTANCED (0x040000) // 'I' Location is "instanced" for each player -#define LOCF_INVALID (0x400000) // Possibly invalid location -#define LOCF_NOMARKER (0x800000) // Location has no marker in mapdata or explicitly defined -#define LOCF_Q_MASK (0xFF0000) - - -/* Misc constants - */ -#define LOC_MAX_NAMES (64) // Probably more than enough? -#define LOC_MARKERS "?%C" -#define LOC_MAX_FILES (64) - - -#define NAME_ORIG (0x00001) // '@' Original area name or coder -#define NAME_RECODER (0x00002) // '!' Converter or recoder of area -#define NAME_MAINTAINER (0x00004) // '%' Maintainer -#define NAME_EXPANDER (0x00008) // '&' Expander, adding new things - - -/* Structures - */ -typedef struct -{ - int day, month, year; -} LocDateStruct; - - -typedef struct -{ - char *filename; - char *continent; - int xoffs, yoffs; -} LocFileInfo; - - -typedef struct -{ - char *name; - int flags; -} LocName; - - -typedef struct -{ - LocFileInfo *file; // Reference to file/continent data - - int xc, yc, ox, oy; // Location coordinates - int align; // Label alignment value - int flags; // Flags (see LOCF_*) - - LocDateStruct added; // Date / time information - BOOL valid; - - int nnames, ncoders; - LocName names[LOC_MAX_NAMES], coders[LOC_MAX_NAMES]; - - char *uri, *freeform; - - union - { - int v_int; - float v_float; - } vsort; -} LocMarker; - - -typedef struct -{ - int nlocations; - LocMarker **locations; -} MapLocations; - - -/* Location file parsing and data handling - */ -BOOL locAddNew(MapLocations *l, int xc, int yc, int dir, int flags, - LocName *names, LocName *coders, LocDateStruct *added, BOOL valid, - const char *uri, const char *freeform, LocFileInfo *file); - -BOOL locParseLocStream(FILE *fp, LocFileInfo *file, MapLocations *l, const int offX, const int offY); -void locFreeMarkerData(LocMarker *marker); -void locFreeMapLocations(MapLocations *loc); - -int locFindByCoords(const MapLocations *l, const int x, const int y, const BOOL locTrue); - -const char * locGetTypePrefix(const int flags); -const char * locGetTypeName(const int flags); - -LocMarker *locCopyLocMarker(const LocMarker *); -void locCopyLocations(MapLocations *dst, const MapLocations *src); - -void locSetFileInfo(LocFileInfo *file, const char *filename, const char *continent); -void locFreeFileInfo(LocFileInfo *file); - - -#endif
--- a/libmaputils.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,824 +0,0 @@ -/* - * maputils - Generic functions/tables for maputils package - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" - - -const MapColor mapColors[] = -{ - { 0x00, 0x00, 0x00, 30, ANSI_OFF, FALSE }, // col_black - { 0x00, 0x00, 0xaa, 34, ANSI_OFF, FALSE }, // col_blue - { 0xaa, 0x00, 0x00, 31, ANSI_OFF, FALSE }, // col_red - { 0xaa, 0xaa, 0x00, 33, ANSI_OFF, FALSE }, // col_yellow - { 0x00, 0xaa, 0x00, 32, ANSI_OFF, FALSE }, // col_green - { 0xaa, 0xaa, 0xaa, 37, ANSI_OFF, FALSE }, // col_white - { 0x00, 0xff, 0xff, 36, ANSI_OFF, FALSE }, // col_cyan - { 0x77, 0x00, 0x77, 35, ANSI_OFF, FALSE }, // col_magenta - - { 0x77, 0x77, 0x77, 30, ANSI_BOLD, FALSE }, // col_light_black - { 0x00, 0x00, 0xff, 34, ANSI_BOLD, FALSE }, // col_light_blue - { 0xee, 0x00, 0x00, 31, ANSI_BOLD, FALSE }, // col_light_red - { 0xff, 0xff, 0x00, 33, ANSI_BOLD, FALSE }, // col_light_yellow - { 0x00, 0xff, 0x00, 32, ANSI_BOLD, FALSE }, // col_light_green - { 0xff, 0xff, 0xff, 37, ANSI_BOLD, FALSE }, // col_light_white - { 0x00, 0xff, 0xff, 36, ANSI_BOLD, FALSE }, // col_light_cyan - { 0xff, 0x00, 0xff, 35, ANSI_BOLD, FALSE }, // col_light_magenta - - // extra colors, not in ANSI - { 0xff, 0x00, 0x00, 31, ANSI_BOLD, TRUE }, // col_very_light_red -}; - -const int nmapColors = sizeof(mapColors) / sizeof(mapColors[0]); - - -const MapPiece mapPieces[] = -{ - { '!', "Mountain Peak", 0xcc,0xff,0xff, col_white, -1, }, - { '#', "Ruins", 0x88,0x88,0x88, col_light_black, -1, }, - { '%', "Special Location", 0xff,0xff,0xff, col_light_white, -1, }, - { '+', "Crossing", 0x33,0x33,0x33, col_light_black, -1, }, - { '-', "Road", 0x33,0x33,0x33, col_light_black, -1, }, - { '|', "Road", 0x33,0x33,0x33, col_light_black, -1, }, - { '/', "Road", 0x33,0x33,0x33, col_light_black, -1, }, - { '\\',"Road", 0x33,0x33,0x33, col_light_black, -1, }, - { '.', "Plains", 0x55,0x92,0x00, col_green, -1, }, - { '=', "Bridge", 0x33,0x33,0x33, col_light_black, -1, }, - { '?', "Scenic Location", 0xff,0xff,0xff, col_light_white, -1, }, - { '@', "Flowing Lava", 0xff,0x99,0x3f, col_very_light_red, -1, }, - { 'C', "Player City", 0x88,0x88,0x88, col_light_black, -1, }, - { 'F', "Deep Forest", 0x00,0x88,0x00, col_green, -1, }, - { 'H', "Highlands", 0x66,0x3f,0x00, col_magenta, -1, }, - { 'L', "Lava Lake", 0xff,0x50,0x00, col_very_light_red, -1, }, - { 'R', "Deep River", 0x33,0x66,0xff, col_blue, -1, }, - { 'V', "Volcano", 0xff,0x33,0x00, col_red, -1, }, - { '^', "Mountain", 0x71,0x82,0x92, col_light_magenta, -1, }, - { 'b', "Beach", 0xcf,0xc4,0xa5, col_yellow, -1, }, - { 'c', "City", 0x88,0x88,0x88, col_light_black, -1, }, - { 'd', "Desert", 0xee,0xaa,0x22, col_yellow, -1, }, - { 'f', "Forest", 0x00,0xb6,0x00, col_light_green, -1, }, - { 'h', "Hills", 0x99,0x66,0x00, col_magenta, -1, }, - { 'i', "Ice", 0xee,0xee,0xff, col_light_blue, -1, }, - { 'j', "Jungle", 0x13,0x96,0x36, col_green, -1, }, - { 'l', "Lake", 0x21,0x33,0xcc, col_light_blue, -1, }, - { 'r', "River", 0x66,0x99,0xff, col_light_blue, -1, }, - { 's', "Swamp", 0x9d,0xa8,0x0a, col_light_red, -1, }, - { 't', "Tundra", 0x61,0xc3,0xa2, col_white, -1, }, - { 'v', "Valley", 0x22,0xdd,0x22, col_light_green, -1, }, - { 'w', "Waterfall", 0x77,0xaa,0xff, col_light_cyan, -1, }, - { 'x', "Badlands", 0x8a,0x83,0x60, col_light_red, -1, }, - { 'y', "Fields", 0xa7,0xcc,0x14, col_yellow, -1, }, - { 'z', "Shore", 0xa7,0xcc,0x14, col_light_yellow, -1, }, - { ',', "Muddy Trail", 0x8c,0x57,0x38, col_light_yellow, -1, }, - { '&', "Monster", 0xff,0x00,0x00, col_light_red, -1, }, -#ifndef SECRET_MAP_DATA_FORMAT - { 'S', "Shallows", 0x44,0xcc,0xcc, col_light_cyan, -1, }, - { '~', "Sea", 0x11,0x88,0xdd, col_blue, -1, }, -#else - { '~', "Sea 1", 0x00,0x11,0x88, col_blue, -1, }, - { '"', "Sea 2", 0x11,0x22,0x99, col_blue, -1, }, - { '\'',"Sea 3", 0x11,0x33,0xaa, col_blue, -1, }, - { '`', "Shallows?", 0x11,0x66,0xdd, col_blue, -1, }, - { 'p', "Plains?", 0x55,0x92,0x00, col_green, -1, }, - { 'S', "Swamp?", 0x77,0x77,0x33, col_yellow, -1, }, -#endif - { -1 , "Road", 0x33,0x33,0x33, col_light_black, '.', }, - { -1 , "Plains", 0x00,0xff,0x00, col_light_green, 'p', }, - { -1 , "Highlands", 0x77,0x00,0x77, col_magenta, 'i', }, -}; - -const int nmapPieces = sizeof(mapPieces) / sizeof(mapPieces[0]); - - -const MapPiece mapCityPieces[] = -{ - { '.', "Street", 0xaa,0xaa,0x00, col_yellow, -1, }, - { '-', "Door/Gate", 0xff,0xff,0x00, col_light_yellow, -1, }, - { '|', "Door/Gate", 0xff,0xff,0x00, col_light_yellow, -1, }, - { '=', "Bridge/Gate", 0x33,0x33,0x33, col_yellow, -1, }, - { '*', "Fountain", 0x00,0x00,0xff, col_light_blue, -1, }, - { '@', "???", 0xff,0xff,0x00, col_light_yellow, -1, }, - { '$', "Tree", 0x00,0xaa,0x00, col_green, -1, }, - { '#', "Wall", 0x33,0x33,0x33, col_light_black, -1, }, - { '"', "Grass", 0x00,0x88,0x00, col_green, -1, }, - { ':', "???", 0x00,0x00,0x00, col_light_white, -1, }, - { 'z', "Shore", 0xa7,0xcc,0x14, col_yellow, -1, }, - { ',', "Lawn", 0x00,0xcc,0x00, col_green, -1, }, - - // Old versions - { -1, "Street", 0x00,0x00,0x00, col_white, '.', }, - { -1, "Door/Gate", 0x00,0x00,0x00, col_yellow, '-', }, - { -1, "Door/Gate", 0x00,0x00,0x00, col_yellow, '|', }, - { -1, "Fountain", 0x00,0x00,0x00, col_light_white, '*', }, -}; - -const int nmapCityPieces = sizeof(mapCityPieces) / sizeof(mapCityPieces[0]); - - -typedef struct -{ - unsigned char c; - char *ent; -} HTMLEntity; - - -static const HTMLEntity HTMLEntities[] = -{ - { '&', "amp" }, - { '<', "lt" }, - { '>', "gt" }, - { '"', "quot" }, - - { 228, "#228" }, - { 246, "#246" }, - { 196, "#196" }, - { 214, "#214" }, -}; - -static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]); - - -int tmpl_fprintve(int (*mputs)(const char *, FILE *), FILE *outFile, const char *fmt, va_list ap) -{ - int n, bufsize = strlen(fmt) * 2; - char *buf, *tmp; - - if ((buf = th_malloc(bufsize)) == NULL) - return -1; - - while (1) - { - va_list tap; - va_copy(tap, ap); - n = vsnprintf(buf, bufsize, fmt, tap); - va_end(tap); - if (n > -1 && n < bufsize) - { - // String fit the buffer, print it out and return - int ret = mputs(buf, outFile); - if (ret < 0) - return ret; - - th_free(buf); - return n; - } - - // Didn't fit, try reallocating some more space - if (n > -1) - bufsize = n + 1; - else - bufsize *= 2; - - if ((tmp = th_realloc(buf, bufsize)) == NULL) - { - th_free(buf); - return -2; - } - else - buf = tmp; - } - - return 0; -} - - -int fputse(const char *str, FILE *outFile) -{ - const char *s = str; - if (str == NULL) - return EOF; - - while (*s) - { - BOOL found = FALSE; - - for (int i = 0; i < numHTMLEntities; i++) - if (HTMLEntities[i].c == *s) - { - fprintf(outFile, "&%s;", HTMLEntities[i].ent); - found = TRUE; - break; - } - - if (!found) - fputc(*s, outFile); - s++; - } - - return 0; -} - - -int fprintve(FILE *outFile, const char *fmt, va_list ap) -{ - return tmpl_fprintve(fputse, outFile, fmt, ap); -} - - -int fprintfe(FILE *outFile, const char *fmt, ...) -{ - int ret; - va_list ap; - va_start(ap, fmt); - ret = fprintve(outFile, fmt, ap); - va_end(ap); - return ret; -} - - -int fputsesc1(const char *str, FILE *f) -{ - const char *p = str; - if (str == NULL) - return -1; - - while (*p) - { - int ret; - switch (*p) - { - case '\\': ret = fputs("\\", f); break; - case '"': ret = fputs("\\\"", f); break; - default: ret = fputc(*p, f); break; - } - p++; - if (ret < 0) - return ret; - } - - return 0; -} - - -int fprintvesc1(FILE *outFile, const char *fmt, va_list ap) -{ - return tmpl_fprintve(fputsesc1, outFile, fmt, ap); -} - - -int fprintfesc1(FILE *outFile, const char *fmt, ...) -{ - int ret; - va_list ap; - va_start(ap, fmt); - ret = fprintvesc1(outFile, fmt, ap); - va_end(ap); - return ret; -} - - -int fputsesc3(const char *str, FILE *f) -{ - const char *p = str; - if (str == NULL) - return -1; - - while (*p) - { - int ret; - switch (*p) - { - case ';': ret = fputs("\\;", f); break; - case '\\': ret = fputs("\\", f); break; - case '\'': ret = fputs("\\'", f); break; - case '"': ret = fputs("\\\"", f); break; - default: ret = fputc(*p, f); break; - } - p++; - if (ret < 0) - return ret; - } - - return 0; -} - - -int fputsesc2(const char *str, FILE *f) -{ - const char *p = str; - if (str == NULL) - return -1; - - while (*p) - { - int ret; - switch (*p) - { - case ';': ret = fputs("\\;", f); break; - case '\\': ret = fputs("\\", f); break; - default: ret = fputc(*p, f); break; - } - p++; - if (ret < 0) - return ret; - } - - return 0; -} - - -char *muColorToCSSColor(char *buf, const size_t len, const int c) -{ - const MapColor *col = &mapColors[c]; - - snprintf(buf, len, "#%02x%02x%02x", - col->cr, col->cg, col->cb); - - return buf; -} - - -static int muGetPieceFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld) -{ - for (int i = 0; i < npieces; i++) - { - if ((getOld && pieces[i].oldSymbol == symbol) || - (!getOld && pieces[i].symbol == symbol)) - return i; - } - - for (int i = 0; i < npieces; i++) - { - if (getOld && pieces[i].symbol == symbol) - return i; - } - - return -1; -} - - -int muGetMapPieceIndex(int symbol, BOOL getOld, BOOL getCity) -{ - int n; - - if (getCity && (n = muGetPieceFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0) - return n; - - return muGetPieceFromList(mapPieces, nmapPieces, symbol, getOld); -} - - -static int muGetColorFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld) -{ - if (getOld) - { - for (int i = 0; i < npieces; i++) - if (pieces[i].oldSymbol == symbol) - return pieces[i].color; - } - - for (int i = 0; i < npieces; i++) - if (pieces[i].symbol == symbol) - return pieces[i].color; - - return -1; -} - - -int muGetMapPieceColor(int symbol, BOOL getOld, BOOL getCity) -{ - int n; - - if (getCity && (n = muGetColorFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0) - return n; - - return ((n = muGetColorFromList(mapPieces, nmapPieces, symbol, getOld)) >= 0) ? n : 0; -} - - -void muPrintHTMLhead(FILE *outFile, const char *title, BOOL html5) -{ - static const char *strCharSet = "utf-8"; - assert(outFile != NULL); - - if (html5) - { - fprintf(outFile, - "<!DOCTYPE html>\n" - "<html lang=\"en\">\n" - "<head>\n" - " <meta charset=\"%s\">\n", - strCharSet); - } - else - { - fprintf(outFile, - "<?xml version=\"1.0\" encoding=\"%s\"?>\n" - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " - "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" - "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n" - "<head>\n" - " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n", - strCharSet, - strCharSet); - } - - if (title) - { - fprintf(outFile, " <title>"); - fputse(title, outFile); - fprintf(outFile, "</title>\n"); - } -} - - -void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra) -{ - assert(outFile != NULL); - - for (int n = 0; n < nmapColors; n++) - { - const MapColor *col = &mapColors[n]; - - fprintf(outFile, - " %s.%c { %s: #%02x%02x%02x;%s%s }\n", - tagName, 'a'+n, propName ? propName : "color", - col->cr, col->cg, col->cb, - col->bold ? " font-weight: bold;" : "", - extra ? extra : ""); - } -} - - -int muCopyFileToStream(FILE *outFile, const char *filename) -{ - FILE *inFile; - - if ((inFile = fopen(filename, "rb")) != NULL) - { - int c; - while ((c = fgetc(inFile)) != EOF) - { - if (fputc(c, outFile) == EOF) - { - fclose(inFile); - return -2; - } - } - fclose(inFile); - return 0; - } - else - return -1; -} - - -BOOL muStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch) -{ - for (size_t n = 0; n < nsymbols; n++) - if (symbols[n] == ch) - return TRUE; - - return FALSE; -} - - -/* Map block handling - */ -MapBlock * mapBlockAlloc(const int width, const int height) -{ - MapBlock *res; - - // Check arguments - if (width <= 0 || height <= 0) - return NULL; - - // Allocate struct and data - res = (MapBlock *) th_malloc0(sizeof(MapBlock)); - if (!res) return NULL; - - res->width = width; - res->height = height; - res->scansize = ((width / BLOCK_SCAN_ALIGN) + 1) * BLOCK_SCAN_ALIGN; - res->size = res->height * res->scansize * sizeof(char); - - res->data = (unsigned char *) th_malloc0(res->size); - if (!res->data) - { - th_free(res); - return NULL; - } - - return res; -} - - -void mapBlockFree(MapBlock *block) -{ - if (block) - { - th_free_r(&block->data); - th_free(block); - } -} - - -MapBlock * mapBlockCopy(const MapBlock *block) -{ - MapBlock *res; - - if (block == NULL) - return NULL; - - res = (MapBlock *) th_malloc0(sizeof(MapBlock)); - if (!res) return NULL; - - res->width = block->width; - res->height = block->height; - res->scansize = block->scansize; - res->size = block->size; - - res->data = (unsigned char *) th_malloc(res->size); - if (!res->data) - { - th_free(res); - return NULL; - } - - memcpy(res->data, block->data, res->size); - - return res; -} - - -/* Parse single arbitrary sized block from given mapfile - */ -MapBlock * mapBlockParseFile(const char *filename, BOOL isDiff) -{ - MapBlock *block; - FILE *inFile; - - if ((inFile = fopen(filename, "rb")) == NULL) - { - THERR("Could not open mapfile '%s' for reading.\n", filename); - return NULL; - } - - block = mapBlockParseStream(filename, inFile, isDiff); - - fclose(inFile); - - return block; -} - - -MapBlock * mapBlockParseStream(const char *filename, FILE *inFile, BOOL isDiff) -{ - MapBlock *res; - unsigned char *o; - long pos = 0L; - BOOL flag; - int x, y, resW, resH, c; - assert(filename != NULL); - - if (isDiff) - { - char buf[128]; - int ver; - if (fgets(buf, sizeof(buf), inFile) == NULL) - { - THERR("Failed to read file header from '%s': %s\n", - filename, th_error_str(th_get_error())); - return NULL; - } - if (sscanf(buf, DIFF_MAGIC "%d\n", &ver) != 1) - { - THERR("Not a DIFF format file '%s'.\n", filename); - return NULL; - } - if (ver != DIFF_VERSION) - { - THERR("Not a correct DIFF format version (%d != %d) '%s'.\n", - ver, DIFF_VERSION, filename); - return NULL; - } - pos = ftell(inFile); - } - - // Probe map width - resH = 1; - resW = -1; - x = 0; - flag = FALSE; - while ((c = fgetc(inFile)) != EOF) - { - if ((!isDiff && c == '\n') || (isDiff && c == 0xff)) - { - if (x > resW) - resW = x; - flag = TRUE; - } - else - { - if (flag) - { - x = 0; - resH++; - flag = FALSE; - } - x++; - } - } - - // Seek back - if (fseek(inFile, pos, SEEK_SET) == -1) - { - THERR("Could not rewind file '%s'.\n", - filename); - return NULL; - } - - // Allocate block - if ((res = mapBlockAlloc(resW, resH)) == NULL) - { - THERR("Could not allocate mapblock (%d, %d) for '%s'.\n", - resW, resH, filename); - return NULL; - } - - // Read data - o = res->data; - y = 0; - x = 0; - flag = FALSE; - while ((c = fgetc(inFile)) != EOF && (y < res->height)) - { - if ((!isDiff && c == '\n') || (isDiff && c == 0xff)) - { - if (x != res->width) - { - THERR("Broken block in '%s', line #%d width %d < %d!\n", - filename, y, x, res->width); - mapBlockFree(res); - return NULL; - } - flag = TRUE; - } - else - { - if (flag) - { - x = 0; - y++; - o = res->data + (y * res->scansize); - flag = FALSE; - } - o[x++] = c; - - if (x > res->scansize) - { - THERR("Broken block in '%s', line #%d width %d > scansize %d!\n", - filename, y, x, res->scansize); - mapBlockFree(res); - return NULL; - } - } - } - - // Close file - if (y >= res->height) - { - THERR("Broken block in '%s', height %d >= %d\n", filename, y, res->height); - mapBlockFree(res); - return NULL; - } - - return res; -} - - -/* Blit a block into another, assume that memory has been allocated - * in sufficient way and other preparations are done. - */ -int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy) -{ - assert(map != NULL); - assert(src != NULL); - - for (int y = 0; y < src->height; y++) - for (int x = 0; x < src->width; x++) - { - const int dx = ox + x; - const int dy = oy + y; - - if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) - { - char c = src->data[(y * src->scansize) + x]; - - if (c != 0) - map->data[(dy * map->scansize) + dx] = c; - } - else - return -1; - } - - return 0; -} - - -int mapBlockPut(MapBlock **pmap, const MapBlock *src, int ox, int oy) -{ - MapBlock *tmp; - int x0, y0, x1, y1, mx, my; - - assert(pmap != NULL); - assert(*pmap != NULL); - assert(src != NULL); - - // Determine new block size - x0 = mx = y0 = my = 0; - x1 = (*pmap)->width - 1; - y1 = (*pmap)->height - 1; - - if (ox < 0) { x0 = ox; mx = -ox; ox = 0; } - if (oy < 0) { y0 = oy; my = -oy; oy = 0; } - - if ((x0 + ox + src->width - 1) > x1) - x1 = (x0 + ox + src->width - 1); - - if ((y0 + oy + src->height - 1) > y1) - y1 = (y0 + oy + src->height - 1); - - // Allocate new block - if ((tmp = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL) - return -1; - - // Copy data - if (mapBlockPutDo(tmp, *pmap, mx, my) < 0) - { - mapBlockFree(tmp); - return -2; - } - - if (mapBlockPutDo(tmp, src, ox, oy) < 0) - { - mapBlockFree(tmp); - return -3; - } - - tmp->xc = -mx; - tmp->yc = -my; - - // Out with the old, in with the new - mapBlockFree(*pmap); - *pmap = tmp; - - return 0; -} - - -/* Clean given block from position markers and whitespaces - */ -void mapBlockClean(MapBlock *map, const unsigned char *symbols, const size_t nsymbols) -{ - assert(map != NULL); - assert(symbols != NULL); - - for (int y = 0; y < map->height; y++) - { - unsigned char *dp = map->data + (y * map->scansize); - for (int x = 0; x < map->width; x++) - { - if (muStrChr(symbols, nsymbols, *dp)) - *dp = 0; - dp++; - } - } -} - - -/* Print block to given output stream - */ -void mapBlockPrint(FILE *fh, const MapBlock *map) -{ - assert(fh != NULL); - assert(map != NULL); - - for (int y = 0; y < map->height; y++) - { - unsigned char *c = map->data + (y * map->scansize); - for (int x = 0; x < map->width; x++) - { - fputc(*c >= 32 && *c <= 126 ? *c : ' ', fh); - c++; - } - fprintf(fh, "\n"); - } -} - - -void mapBlockPrintRaw(FILE *fh, const MapBlock *map) -{ - assert(fh != NULL); - assert(map != NULL); - - for (int y = 0; y < map->height; y++) - { - unsigned char *dp = map->data + (y * map->scansize); - for (int x = 0; x < map->width; x++) - fputc(*dp++, fh); - - fputc(0xff, fh); - } -}
--- a/libmaputils.h Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/* - * libmaputils - Generic functions/tables for maputils package - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#ifndef LIBMAPUTILS_H -#define LIBMAPUTILS_H - -#include "libutil.h" -#include <stdio.h> -#include <stdarg.h> - - -/* Map diff format header and version - */ -#define DIFF_MAGIC "MAPDIFF" -#define DIFF_VERSION 2 - -#define BLOCK_SCAN_ALIGN 8 - - -/* Typedefs - */ -typedef struct -{ - char symbol; - char *desc; - int cr, cg, cb; - int color; - char oldSymbol; -} MapPiece; - - -typedef struct -{ - unsigned char *data; - int width, height, scansize, size; - int xc, yc; - BOOL mark; -} MapBlock; - - -typedef struct -{ - int cr, cg, cb; - int ansi; - int ansiAttr; - BOOL bold; -} MapColor; - - -enum -{ - ANSI_OFF = 0, - ANSI_BOLD = 1, - ANSI_UNDERSCORE = 4, - ANSI_BLINK = 5, - ANSI_REVERSE = 7, - ANSI_CONCEALED = 8 -}; - - -enum -{ - col_black = 0, - col_blue, - col_red, - col_yellow, - col_green, - col_white, - col_cyan, - col_magenta, - - col_light_black, - col_light_blue, - col_light_red, - col_light_yellow, - col_light_green, - col_light_white, - col_light_cyan, - col_light_magenta, - - col_very_light_red, -}; - - -/* Some global tables - */ -extern const MapColor mapColors[]; -extern const int nmapColors; - -extern const MapPiece mapPieces[]; -extern const int nmapPieces; - - -/* Misc functions - */ -int fputse(const char *str, FILE *outFile); -int fprintve(FILE *outFile, const char *fmt, va_list ap); -int fprintfe(FILE *outFile, const char *fmt, ...); - -int fprintvesc1(FILE *outFile, const char *fmt, va_list ap); -int fprintfesc1(FILE *outFile, const char *fmt, ...); -int fputsesc1(const char *str, FILE *f); -int fputsesc2(const char *str, FILE *f); -int fputsesc3(const char *str, FILE *f); - -int muGetMapPieceIndex(int symbol, BOOL getOld, BOOL getCity); -int muGetMapPieceColor(int symbol, BOOL getOld, BOOL getCity); -void muPrintHTMLhead(FILE *outFile, const char *title, BOOL html5); -void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra); -char * muColorToCSSColor(char *buf, const size_t len, const int c); -int muCopyFileToStream(FILE *outFile, const char *filename); -BOOL muStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch); - - -/* Mapblock handling - */ -MapBlock * mapBlockAlloc(int width, int height); -MapBlock * mapBlockCopy(const MapBlock *block); -void mapBlockFree(MapBlock *block); -MapBlock * mapBlockParseStream(const char *filename, FILE *fh, BOOL isDiff); -MapBlock * mapBlockParseFile(const char *filename, BOOL isDiff); -void mapBlockPrint(FILE *fh, const MapBlock *block); -int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy); -int mapBlockPut(MapBlock **pmap, const MapBlock *src, const int ox, const int oy); -void mapBlockClean(MapBlock *block, const unsigned char *symbols, const size_t nsymbols); -void mapBlockPrintRaw(FILE *fh, const MapBlock *block); - - -#endif
--- a/libutil.h Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -#ifndef LIBUTIL_H -#define LIBUTIL_H - -#include "th_util.h" -#include "th_string.h" - - -#endif
--- a/map2ppm.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,413 +0,0 @@ -/* - * Convert BatMUD ASCII map to PPM or PNG image file - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - -#ifdef HAVE_LIBPNG -#include <png.h> -#endif - -char *srcFilename = NULL, - *dstFilename = NULL; - -BOOL optUseOldFormat = FALSE, - optInputIsDiff = FALSE, - optUseANSI = FALSE, - optCityFormat = FALSE; -int optScale = 1; -#ifdef HAVE_LIBPNG -int optPNGLevel = -1; -#endif - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'q', "quiet", "Be quiet", OPT_NONE }, - { 3, 'd', "input-diff", "Input is a diff", OPT_NONE }, - { 4, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, - { 5, 'o', "output", "Output filename", OPT_ARGREQ }, - { 6, 'A', "ansi-colors", "Use ANSI colors", OPT_NONE }, - { 7, 'c', "city-format", "Input is a city map", OPT_NONE }, - { 8, 's', "scale", "Scale value (integer)", OPT_ARGREQ }, - -#ifdef HAVE_LIBPNG - { 9, 'P', "png", "PNG format output (compression level 0-9)", OPT_ARGREQ }, -#endif -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <input mapfile>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - th_verbosity = -1; - break; - - case 3: - optInputIsDiff = TRUE; - THMSG(2, "Input is a 'diff', handling it as such.\n"); - break; - - case 4: - optUseOldFormat = TRUE; - THMSG(2, "Input is using old map symbols/colors.\n"); - break; - - case 5: - dstFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", dstFilename); - break; - - case 6: - optUseANSI = TRUE; - THMSG(2, "Using ANSI colors.\n"); - break; - - case 7: - optCityFormat = TRUE; - THMSG(2, "Input is handled as a city map\n"); - break; - - case 8: - optScale = atoi(optArg); - if (optScale < 1 || optScale > 50) - { - THERR("Invalid scale value %d, must be 1 < x < 50.\n", optScale); - return FALSE; - } - THMSG(2, "Output scaling set to %d.\n", optScale); - break; - - -#ifdef HAVE_LIBPNG - case 9: - optPNGLevel = atoi(optArg); - if (optPNGLevel < 0 || optPNGLevel > 9) - { - THERR("Invalid PNG compression factor %d, must be 0 < x < 9.\n", optPNGLevel); - return FALSE; - } - THMSG(2, "Output format set to PNG, compression level %d.\n", optPNGLevel); - break; -#endif - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (!srcFilename) - srcFilename = currArg; - else - { - THERR("Too many input map files specified!\n"); - return FALSE; - } - - return TRUE; -} - - -int writeImageData(const MapBlock *map, void *cbdata, - BOOL (*writeRowCB)(void *, const uint8_t *, const size_t), - const int scale) -{ - int res = 0; - uint8_t *row = NULL; - - // Allocate memory for row buffer - if ((row = th_malloc(map->width * 3 * scale + 16)) == NULL) - { - res = -16; - goto done; - } - - for (int y = 0; y < map->height; y++) - { - uint8_t *ptr = row; - - for (int x = 0; x < map->width; x++) - { - int qr, qg, qb, c; - qr = 255; qg = qb = 0; - c = (uint8_t) map->data[(y * map->scansize) + x]; - - if (optInputIsDiff) - { - if (c < nmapPieces) - { - if (optUseANSI) - { - qr = qg = qb = 255; - } - else - { - c = c & 63; - qr = mapPieces[c].cr; - qg = mapPieces[c].cg; - qb = mapPieces[c].cb; - } - } - } - else - if (optUseANSI) - { - if ((c = muGetMapPieceColor(c, optUseOldFormat, optCityFormat)) >= 0) - { - qr = mapColors[c].cr; - qg = mapColors[c].cg; - qb = mapColors[c].cb; - } - } - else - { - if ((c = muGetMapPieceIndex(c, optUseOldFormat, optCityFormat)) >= 0) - { - qr = mapPieces[c].cr; - qg = mapPieces[c].cg; - qb = mapPieces[c].cb; - } - } - - for (int xscale = 0; xscale < scale; xscale++) - { - *ptr++ = qr; - *ptr++ = qg; - *ptr++ = qb; - } - } - - for (int yscale = 0; yscale < scale; yscale++) - { - if (!writeRowCB(cbdata, row, map->width * 3 * scale)) - { - res = -32; - goto done; - } - } - } - -done: - th_free(row); - return res; -} - - -BOOL writePPMRow(void *cbdata, const uint8_t *row, const size_t len) -{ - return fwrite(row, sizeof(uint8_t), len, (FILE *) cbdata) == len; -} - - -int writePPMFile(FILE *outFile, const MapBlock *map, const int scale) -{ - // Write header for 24-bit PPM - fprintf(outFile, - "P6\n%d %d\n255\n", - map->width * scale, map->height * scale); - - // Write image data - return writeImageData(map, (void *) outFile, writePPMRow, scale); -} - - -#ifdef HAVE_LIBPNG -BOOL writePNGRow(void *cbdata, const uint8_t *row, const size_t len) -{ - png_structp png_ptr = cbdata; - (void) len; - - if (setjmp(png_jmpbuf(png_ptr))) - return FALSE; - - png_write_row(png_ptr, row); - - return TRUE; -} - - -int writePNGFile(FILE *outFile, const MapBlock *map, const int scale) -{ - int width, height; - png_structp png_ptr; - png_infop info_ptr; - - width = map->width * scale; - height = map->height * scale; - - // Create PNG structures - png_ptr = png_create_write_struct( - PNG_LIBPNG_VER_STRING, - NULL, NULL, NULL); - - if (png_ptr == NULL) - { - THERR("PNG: png_create_write_struct() failed.\n"); - return -1; - } - - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) - { - THERR("PNG: png_create_info_struct(%p) failed.\n", (void *) png_ptr); - return -2; - } - - if (setjmp(png_jmpbuf(png_ptr))) - { - THERR("PNG: Error during PNG init_io().\n"); - return -3; - } - - png_init_io(png_ptr, outFile); - - // Write PNG header info - if (setjmp(png_jmpbuf(png_ptr))) - { - THERR("PNG: Error during writing header.\n"); - return -4; - } - - png_set_IHDR(png_ptr, info_ptr, - width, - height, - 8, // bits per component - PNG_COLOR_TYPE_RGB, // 3 components, RGB - PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, - PNG_FILTER_TYPE_DEFAULT); - -// png_set_gAMA(png_ptr, info_ptr, 2.2); - - png_write_info(png_ptr, info_ptr); - - - // Write compressed image data - if (setjmp(png_jmpbuf(png_ptr))) - { - THERR("PNG: Error during writing image data.\n"); - return -5; - } - - writeImageData(map, (void *) png_ptr, writePNGRow, scale); - - - // Write footer - if (setjmp(png_jmpbuf(png_ptr))) - { - THERR("PNG: Error during writing image footer.\n"); - return -6; - } - - png_write_end(png_ptr, NULL); - - // Dellocate shit - if (png_ptr && info_ptr) - png_destroy_write_struct(&png_ptr, &info_ptr); - - return 0; -} -#endif - - -/* Main program - */ -int main(int argc, char *argv[]) -{ - FILE *outFile = NULL; - MapBlock *map = NULL; - int ret = 0; - - // Initialize - th_init("map2ppm", "ASCII map to PPM/PNG image converter", "0.5", NULL, NULL); - th_verbosity = 0; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - exit(1); - - if (srcFilename == NULL) - { - THERR("Nothing to do. (try --help)\n"); - exit(0); - } - - // Read input file - THMSG(1, "Reading map file '%s'\n", srcFilename); - - if ((map = mapBlockParseFile(srcFilename, optInputIsDiff)) == NULL) - { - THERR("Error reading map file '%s'!\n", - srcFilename); - goto out; - } - - // Open output file - if (dstFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(dstFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - dstFilename); - goto out; - } - - THMSG(1, "Outputting image of %dx%d ...\n", - map->width * optScale, map->height * optScale); - -#ifdef HAVE_LIBPNG - if (optPNGLevel >= 0) - ret = writePNGFile(outFile, map, optScale); - else -#endif - ret = writePPMFile(outFile, map, optScale); - - if (ret != 0) - THERR("Image write failed, code=%d\n", ret); - else - THMSG(1, "Done.\n"); - -out: - if (outFile != NULL) - fclose(outFile); - - mapBlockFree(map); - return ret; -}
--- a/mapsearch.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1990 +0,0 @@ -/* - * PupuMaps Search WebSockets server - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2018-2021 Tecnic Software productions (TNSP) - */ -#include "th_args.h" -#include "th_datastruct.h" -#include "liblocfile.h" -#include "libmaputils.h" -#include <stdarg.h> -#include <libwebsockets.h> -#include <sys/types.h> -#include <pwd.h> -#include <grp.h> -#include <math.h> - - -/* Default settings etc. constants - */ -#define SET_MAX_MAPS 16 // Maximum number of maps allowed to be loaded -#define SET_MAX_LISTEN 4 // Maximum number of interfaces to listen -#define SET_MAX_MATCHES 64 // Maximum number of match results per query - -// Define the static lws_write() buffer size -#define SET_LWS_BUF_SIZE (256 * 1024) // 256kB probably enough for our purposes(tm) -#define SET_LWS_BUF_PAD (((LWS_PRE / 16) + 1) * 16) - - -// List of default SSL/TLS ciphers to use/allowed -#define SET_DEF_CIPHERS \ - "ECDHE-ECDSA-AES256-GCM-SHA384:" \ - "ECDHE-RSA-AES256-GCM-SHA384:" \ - "DHE-RSA-AES256-GCM-SHA384:" \ - "ECDHE-RSA-AES256-SHA384:" \ - "HIGH:!aNULL:!eNULL:!EXPORT:" \ - "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" \ - "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" \ - "!DHE-RSA-AES128-SHA256:" \ - "!AES128-GCM-SHA256:" \ - "!AES128-SHA256:" \ - "!DHE-RSA-AES256-SHA256:" \ - "!AES256-GCM-SHA384:" \ - "!AES256-SHA256" - - -/* Structure that holds information about one listen interface - */ -typedef struct -{ - char *interface; // Listen interface (* = listen all) - char *vhostname; // Vhost name - int port; // Port number - - int ipvMode; // Enable/disable IPv4/6 support for this listener - - BOOL useSSL; // Use SSL/TLS? - char *sslCertFile, // Certificate file - *sslKeyFile, // Key file - *sslCAFile; // CA file - - struct lws_vhost *vhost; // LWS vhost info -} MAPListenerCtx; - - -/* Structure for holding information about one map and its locations - */ -typedef struct -{ - char *mapFilename; // Filename of the map data - LocFileInfo locFile; // Location file info struct - MapBlock *map; // Map data, when loaded - MapLocations loc; // Map locations, when loaded -} MAPInfoCtx; - - -typedef struct -{ - char *map; - int mx, my, wx, wy; - LocMarker *marker; - int nname; -} MAPMatch; - - -LocMarker **optMapLocations = NULL; -int optNMapLocations = 0; - -/* Options - */ -MAPInfoCtx optMaps[SET_MAX_MAPS]; -int optNMaps = 0; -MAPListenerCtx *optListenTo[SET_MAX_LISTEN]; -int optNListenTo = 0; -char *optSSLCipherList = SET_DEF_CIPHERS; -struct lws_context *setLWSContext = NULL; -int optWorldXC = 0, optWorldYC = 0; -char *optTest = NULL; -int optUID = -1, optGID = -1; -char *optLogFilename = NULL; -FILE *setLogFH = NULL; -int optBenchmark = -1; - -unsigned char *setLWSBuffer = NULL; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'l', "listen", "Listen to interface (see below)", OPT_ARGREQ }, - { 3, 0, "ssl-ciphers", "Specify list of SSL/TLS ciphers", OPT_ARGREQ }, - { 5, 'w', "world-origin", "Specify the world origin <x:y> coordinates " - "to which the map offsets are relative to", OPT_ARGREQ }, - { 6, 'T', "test", "Test search with given file input", OPT_ARGREQ }, - { 4, 'B', "benchmark", "Run a benchmark on test input (-T option) for specified number cycles", OPT_ARGREQ }, - { 7, 'U', "uid", "Run as UID", OPT_ARGREQ }, - { 8, 'G', "gid", "Run as GID", OPT_ARGREQ }, - { 9, 'L', "log-file", "Log to specified file", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, "[options] <map spec>"); - th_args_help(stdout, optList, optListN, 0, 80 - 2); - - fprintf(stdout, - "\n" - "Listening interface(s) are specified with following syntax:\n" - "-l \"<interface/IP/host>:<port>[:no-ipv(4|6)][=<SSL/TLS spec>]\"\n" - "\n" - "IPv6 addresses should be specified with the square bracket notation [].\n" - "To listen to all interfaces, you can specify an asterisk (*) as host:\n" - "\n" - "-l *:3491 -l *:3492=<vhostname for SNI>:<ssl_cert_file.crt>:<ssl_key_file.key>:<ca_file.crt>\n" - "\n" - "This would listen for normal WebSocket (ws://) connections on port 3491 and for\n" - "secure SSL/TLS WebSocket (wss://) connections on port 3492 of all interfaces.\n" - "\n" - "To disable listening on IPv4/6 addresses, specify :no-ipv4 or :no-ipv6\n" - "\n" - "Maps and location files for each map are specified as follows:\n" - "<filename.map>:<locfilename.loc>:<map/continent name>[:<world x-offset>:<world y-offset>]\n" - "World offsets are optional and default to 0, 0 if not specified.\n" - "\n" - "All the map offsets are relative to world origin coordinates, which are 0,0 by default.\n" - "-w 8192:8192\n" - ); -} - - -void mapMSG_V(const int level, const char *fmt, va_list ap) -{ - // Quick way out - if (setLogFH != NULL || (level < 0 || th_verbosity >= level)) - { - char *vtmp = th_strdup_vprintf(fmt, ap); - char vstr[64] = ""; - time_t stamp = time(NULL); - struct tm *stamp_tm; - - // Format timestamp - if ((stamp_tm = localtime(&stamp)) != NULL) - strftime(vstr, sizeof(vstr), "%c", stamp_tm); - - // Sanitize the printed string - for (size_t i = 0; vtmp[i]; i++) - { - if (vtmp[i] != '\n' && (vtmp[i] < 32 || vtmp[i] > 126)) - vtmp[i] = ' '; - } - - if (setLogFH != NULL) - { - fprintf(setLogFH, "[%s] %s", vstr, vtmp); - fflush(setLogFH); - } - else - { - fprintf(stdout, "[%s] %s", vstr, vtmp); - } - - th_free(vtmp); - } -} - - -void mapMSG(const int level, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - mapMSG_V(level, fmt, ap); - va_end(ap); -} - - -void mapERR(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - mapMSG_V(-1, fmt, ap); - va_end(ap); -} - - -void mapLogWS(int level, const char *line) -{ - if (level <= (1 << th_verbosity)) - mapMSG(-1, "%s", line); -} - - -BOOL mapParseCoordPair(const char *str, int *xc, int *yc) -{ - char *piece, *tmp, *fmt = th_strdup(str); - BOOL ret = FALSE; - - if ((piece = strchr(fmt, ':')) == NULL) - goto err; - *piece++ = 0; - - tmp = th_strdup_trim(fmt, TH_TRIM_BOTH); - *xc = atoi(tmp); - th_free(tmp); - - tmp = th_strdup_trim(piece, TH_TRIM_BOTH); - *yc = atoi(tmp); - th_free(tmp); - - ret = TRUE; - -err: - th_free(fmt); - return ret; -} - - -BOOL mapParseMapSpec(const char *str, MAPInfoCtx *info) -{ - char *piece, *start, *fmt = th_strdup(str); - BOOL ret = FALSE; - - memset(info, 0, sizeof(MAPInfoCtx)); - - // Check for map filename end - if ((piece = strchr(fmt, ':')) == NULL) - goto err; - *piece++ = 0; - - info->mapFilename = th_strdup_trim(fmt, TH_TRIM_BOTH); - start = piece; - - // Check for loc filename end - if ((piece = strchr(start, ':')) == NULL) - goto err; - *piece++ = 0; - - info->locFile.filename = th_strdup_trim(start, TH_TRIM_BOTH); - start = piece; - - // Check for world x-offset separator - if ((piece = strchr(start, ':')) != NULL) - *piece++ = 0; - - info->locFile.continent = th_strdup_trim(start, TH_TRIM_BOTH); - - // Get world X/Y offsets, if any - if (piece != NULL && - !mapParseCoordPair(piece, &info->locFile.xoffs, &info->locFile.yoffs)) - goto err; - - ret = TRUE; - -err: - th_free(fmt); - return ret; -} - - -MAPListenerCtx *mapNewListenCtx(void) -{ - return th_malloc0(sizeof(MAPListenerCtx)); -} - - -void mapFreeListenCtxR(MAPListenerCtx *ctx) -{ - if (ctx != NULL) - { - th_free(ctx->vhostname); - th_free(ctx->interface); - th_free(ctx->sslCertFile); - th_free(ctx->sslKeyFile); - th_free(ctx->sslCAFile); - } -} - - -void mapFreeListenCtx(MAPListenerCtx *ctx) -{ - mapFreeListenCtxR(ctx); - th_free(ctx); -} - - -MAPListenerCtx *mapParseListenerSpec(const char *cfmt) -{ - char *start, *end, *flags, *port = NULL, - *interface, *fmt = th_strdup(cfmt); - BOOL ret = FALSE; - MAPListenerCtx *ctx; - - if ((ctx = mapNewListenCtx()) == NULL) - goto out; - - interface = fmt; - if (*interface == '[') - { - // IPv6 IP address is handled in a special case - interface++; - if ((end = strchr(interface, ']')) == NULL) - { - mapERR("Invalid IPv6 IP address '%s'.\n", cfmt); - goto out; - } - *end++ = 0; - - for (size_t n = 0; interface[n]; n++) - if (!isxdigit(interface[n]) && interface[n] != ':') - { - mapERR("Invalid IPv6 IP address '%s'.\n", interface); - goto out; - } - } - else - { - end = strchr(interface, ':'); - } - - // Find port number separator - if (end == NULL || *end != ':') - { - mapERR("Missing listening port in '%s'.\n", cfmt); - goto out; - } - *end++ = 0; - start = end; - - // Check for '=<SSL/TLS spec>' at the end - if ((flags = strchr(start, '=')) != NULL) - *flags++ = 0; - - // Check for ':no-ipv4' or ':no-ipv6' flag - if ((end = strstr(start, ":no-ipv")) != NULL && - (end[7] == '4' || end[7] == '6')) - { - *end = 0; - - if (end[7] == '4') - ctx->ipvMode = 6; - else - ctx->ipvMode = 4; - } - - // Get the interface name - ctx->interface = th_strdup_trim(interface, TH_TRIM_BOTH); - if (strcmp(ctx->interface, "*") == 0) - { - th_free(ctx->interface); - ctx->interface = NULL; - } - - // Get port number - if ((port = th_strdup_trim(start, TH_TRIM_BOTH)) == NULL) - { - mapERR("Missing listening port in '%s'.\n", cfmt); - goto out; - } - if ((ctx->port = atoi(port)) < 1) - { - mapERR("Invalid listening port %d in '%s'.\n", ctx->port, cfmt); - goto out; - } - - // Parse the SSL/TLS spec, if any - if (flags != NULL) - { - char *cstart, *cend; - - // Check for separator - cstart = flags; - if ((cend = strchr(cstart, ':')) == NULL) - { - mapERR("Invalid SSL/TLS spec '%s'\n", flags); - goto out; - } - *cend++ = 0; - - // Get the vhost name - ctx->vhostname = th_strdup_trim(cstart, TH_TRIM_BOTH); - if (strlen(ctx->vhostname) == 0) - { - th_free(ctx->vhostname); - ctx->vhostname = NULL; - } - - // Check for separator - cstart = cend; - if ((cend = strchr(cend, ':')) == NULL) - { - mapERR("Invalid SSL/TLS spec, missing certificate file.\n"); - goto out; - } - *cend++ = 0; - - // Get certificate file path - ctx->sslCertFile = th_strdup_trim(cstart, TH_TRIM_BOTH); - - // Check for separator - cstart = cend; - if ((cend = strchr(cend, ':')) == NULL) - { - mapERR("Invalid SSL/TLS spec, missing key file.\n"); - goto out; - } - *cend++ = 0; - - // Get the rest - ctx->sslKeyFile = th_strdup_trim(cstart, TH_TRIM_BOTH); - ctx->sslCAFile = th_strdup_trim(cend, TH_TRIM_BOTH); - ctx->useSSL = TRUE; - } - - // Check for duplicates - for (int n = 0; n < optNListenTo; n++) - { - MAPListenerCtx *chk = optListenTo[n]; - if (( - (ctx->interface == NULL && chk->interface == NULL) || - (ctx->interface != NULL && chk->interface != NULL && strcmp(ctx->interface, chk->interface) == 0)) && - chk->port == ctx->port) - { - mapERR("Duplicate listener spec (%s:%d)\n", - ctx->interface != NULL ? ctx->interface : "*", ctx->port); - goto out; - } - } - ret = TRUE; - -out: - th_free(fmt); - th_free(port); - - if (ret) - return ctx; - - mapFreeListenCtx(ctx); - return NULL; -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - if (optNListenTo < SET_MAX_LISTEN) - { - MAPListenerCtx *ctx; - if ((ctx = mapParseListenerSpec(optArg)) != NULL) - optListenTo[optNListenTo++] = ctx; - else - return FALSE; - } - else - { - mapERR("Maximum number of listener specs already specified.\n"); - return FALSE; - } - break; - - case 3: - optSSLCipherList = optArg; - break; - - case 5: - if (!mapParseCoordPair(optArg, &optWorldXC, &optWorldYC)) - { - mapERR("Invalid world origin coordinates '%s'.\n", optArg); - return FALSE; - } - break; - - case 6: - optTest = optArg; - break; - - case 4: - { - int tmp = atoi(optArg); - if (tmp < 10) - { - mapERR("Invalid bechmark cycle count %d.\n", optBenchmark); - return FALSE; - } - optBenchmark = tmp; - } - break; - - case 7: - if (sscanf(optArg, "%d", &optUID) != 1) - { - struct passwd *info = getpwnam(optArg); - if (info != NULL) - optUID = info->pw_uid; - else - { - mapERR("Invalid UID '%s'.\n", optArg); - return FALSE; - } - } - break; - - case 8: - if (sscanf(optArg, "%d", &optGID) != 1) - { - struct group *info = getgrnam(optArg); - if (info != NULL) - optUID = info->gr_gid; - else - { - mapERR("Invalid GID '%s'.\n", optArg); - return FALSE; - } - } - break; - - case 9: - optLogFilename = optArg; - break; - - default: - mapERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (optNMaps < SET_MAX_MAPS) - { - if (!mapParseMapSpec(currArg, &optMaps[optNMaps])) - { - mapERR("Invalid map spec '%s'.\n", currArg); - return FALSE; - } - - optNMaps++; - return TRUE; - } - else - { - mapERR("Maximum number of map specs already specified.\n"); - return FALSE; - } -} - - -// Find the actual data boundaries of the block -// ignoring any null characters around it. -void mapBlockFindBoundaries(const MapBlock *map, int *x0, int *y0, int *x1, int *y1, const BOOL precrop) -{ - if (!precrop) - { - *x0 = map->width - 1; - *y0 = map->height - 1; - *x1 = 0; - *y1 = 0; - } - - for (int yc1 = 0, yc2 = map->height - 1; yc1 < map->height; yc1++, yc2--) - { - const unsigned char - *sp0 = map->data + (yc1 * map->scansize), - *sp1 = map->data + (yc2 * map->scansize); - - int minX = -1, - maxX = -1; - - for (int xc1 = 0, xc2 = map->width - 1; xc1 < map->width; xc1++, xc2--) - { - if (minX == -1 && sp0[xc1] != 0) - minX = xc1; - - if (maxX == -1 && sp0[xc2] != 0) - maxX = xc2; - - if (sp0[xc1] != 0 && yc1 < *y0) - *y0 = yc1; - - if (sp1[xc1] != 0 && yc2 > *y1) - *y1 = yc2; - } - - if (minX != -1 && minX < *x0) - *x0 = minX; - - if (maxX != -1 && maxX > *x1) - *x1 = maxX; - } -} - - -BOOL mapBlockCrop(MapBlock **pdst, const MapBlock *src, const int x0, const int y0, const int x1, const int y1) -{ - // Check dimensions - if (x1 - x0 < 0 || y1 - y0 < 0) - return FALSE; - - // Allocate new block and copy the cropped data - if ((*pdst = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL) - return FALSE; - - for (int yc = 0; yc < (*pdst)->height; yc++) - { - const unsigned char *sp = src->data + ((yc + y0) * src->scansize) + x0; - unsigned char *dp = (*pdst)->data + (yc * (*pdst)->scansize); - - for (int xc = 0; xc < (*pdst)->width; xc++) - *dp++ = *sp++; - } - - return TRUE; -} - - -BOOL mapBlockAutoCrop(MapBlock **pdst, const MapBlock *src, - const unsigned char *symbols, const size_t nsymbols, - int x0, int y0, int x1, int y1, const BOOL precrop) -{ - MapBlock *clean; - - // Step #1: Detect crop boundaries - if ((clean = mapBlockCopy(src)) == NULL) - return FALSE; - - mapBlockClean(clean, symbols, nsymbols); - mapBlockFindBoundaries(clean, &x0, &y0, &x1, &y1, precrop); - mapBlockFree(clean); - - // Step #2: Check if the boundaries are any smaller than - // the current block size - if (x0 == 0 && y0 == 0 && - x1 == src->width - 1 && y1 == src->height - 1) - return FALSE; - - // Step #3: Crop it - if (!mapBlockCrop(pdst, src, x0, y0, x1, y1)) - return FALSE; - - return TRUE; -} - - -void mapBlockParseDimensions(const unsigned char *data, const size_t len, int *width, int *height) -{ - size_t offs = 0; - int x1 = 0, x2 = 0; - - *width = *height = 0; - - while (offs < len) - { - const unsigned char ch = data[offs++]; - if (ch == '\n') - { - if (x1 > *width) - *width = x1; - - (*height)++; - x1 = x2 = 0; - } - else - { - x2++; - if (ch != ' ') - x1 = x2; - } - } - - if (x1 > *width) - *width = x1; - - if (x1 > 0) - (*height)++; -} - - -BOOL mapBlockParse(const unsigned char *data, const size_t len, MapBlock *res) -{ - size_t offs = 0; - - for (int yc = 0; yc < res->height; yc++) - { - unsigned char *dp = res->data + (yc * res->scansize); - - if (offs < len && data[offs] != '\n') - for (int xc = 0; xc < res->width; xc++) - { - if (offs < len && data[offs] != '\n') - dp[xc] = data[offs++]; - else - break; - } - - while (offs < len && data[offs] != '\n') - offs++; - - if (offs < len && data[offs] == '\n') - offs++; - } - - return offs == len; -} - - -// Find "center" coordinates (which may not be actual center) -// for given map block based on list of center symbols. -// Returns TRUE if center marker matching one of the symbols found. -BOOL mapBlockFindCenter(const MapBlock *block, - const unsigned char *symbols, const size_t nsymbols, - int *cx, int *cy, const int tolerance) -{ - const int - x0 = (block->width * tolerance) / 100, - x1 = (block->width * (100 - tolerance)) / 100, - y0 = (block->height * tolerance) / 100, - y1 = (block->height * (100 - tolerance)) / 100; - - *cx = *cy = 0; - for (int yc = 0; yc < block->height; yc++) - { - const unsigned char *dp = block->data + (yc * block->scansize); - for (int xc = 0; xc < block->width; xc++) - { - if (xc >= x0 && xc <= x1 && - yc >= y0 && yc <= y1 && - muStrChr(symbols, nsymbols, dp[xc])) - { - *cx = xc; - *cy = yc; - return TRUE; - } - } - } - - return FALSE; -} - - -// Calculate entropy value for the given map block, excluding -// the specified characters. TODO: This function is not very good. -// It does not take into account spatial entropy. -int mapBlockGetEntropy(const MapBlock *map, const char *exclude, const int nexclude) -{ - unsigned char *list; - int num, i; - - // Allocate memory for entropy array - if ((list = th_malloc0(256)) == NULL) - return -1; - - // Collect sums into entropy array - for (int yc = 0; yc < map->height; yc++) - { - unsigned char *sp = map->data + (yc * map->scansize); - for (int xc = 0; xc < map->width; xc++) - list[sp[xc]]++; - } - - // Handle exclusions - if (exclude != NULL && nexclude > 0) - { - for (i = 0; i < nexclude; i++) - list[(int) exclude[i]] = 0; - } - - // Calculate simple entropy - for (num = 0, i = 0; i < 256; i++) - if (list[i]) num++; - - th_free(list); - return num; -} - - -// Match given "pattern" block against given "map" at -// specified offset coordinates (ox, oy). Return TRUE if -// there is a match, FALSE otherwise. -BOOL mapMatchBlock(const MapBlock *map, const MapBlock *pattern, const int ox, const int oy) -{ - const unsigned char - *sp = map->data + (oy * map->scansize) + ox, - *dp = pattern->data; - - int yc = pattern->height; - while (yc--) - { - for (int xc = 0; xc < pattern->width; xc++) - { - if (dp[xc] != 0 && dp[xc] != sp[xc]) - return FALSE; - } - - sp += map->scansize; - dp += pattern->scansize; - } - - return TRUE; -} - - -// Simple implementation of atoi() without support for other than base 10 -int mapAtoI(const char *str, const size_t len) -{ - int value = 0; - size_t i = 0; - BOOL neg = FALSE; - - while (th_isspace(str[i])) i++; - - if (str[i] == '-') - { - neg = TRUE; - i++; - } - - for (; i < len; i++) - { - if (str[i] >= '0' && str[i] <= '9') - { - value *= 10; - value += str[i] - '0'; - } - else - break; - } - - return neg ? -value : value; -} - - -// -// This wrapper function for lws_write exists because of the -// libwebsockets' requirement for pre-pad of the data buffer -// for header information. -// -int mapLWSWrite(struct lws *wsi, const unsigned char *data, const size_t len) -{ - if (wsi == NULL) - return 0; - - if (len >= SET_LWS_BUF_SIZE - SET_LWS_BUF_PAD) - return -1; - - // Costs us an extra copy - memcpy(setLWSBuffer + SET_LWS_BUF_PAD, data, len); - - return lws_write(wsi, setLWSBuffer + SET_LWS_BUF_PAD, len, LWS_WRITE_TEXT); -} - - -// Creates a JSON format results string from the list of matches, -// with additional information in the first array. -void mapCreateResultStr(char **buf, size_t *bufLen, const MAPMatch *matches, - const int nmatches, const int nlimit, const BOOL type, const BOOL centered, - const int centerX, const int centerY, const MapBlock *pattern) -{ - size_t bufSize = 0; - char *vstr; - *bufLen = 0; - *buf = NULL; - - if (type) - { - vstr = th_strdup_printf( - "RESULT:[[%d,%d,%d,%d,%d,%d,%d]", - nmatches, nlimit, - centered, centerX, centerY, - pattern != NULL ? pattern->width : -1, - pattern != NULL ? pattern->height : -1); - } - else - { - vstr = th_strdup_printf( - "RESULT:[[%d,%d]", - nmatches, nlimit); - } - - th_strbuf_puts(buf, &bufSize, bufLen, vstr); - th_free(vstr); - - for (int n = 0; n < nmatches; n++) - { - const MAPMatch *match = &matches[n]; - const char *vstart = (n == 0) ? "," : ""; - const char *vend = (n < nmatches - 1) ? "," : ""; - - if (type) - { - vstr = th_strdup_printf( - "%s[\"%s\",%d,%d,%d,%d]%s", - vstart, - match->map, - match->mx, match->my, - match->wx, match->wy, - vend); - - th_strbuf_puts(buf, &bufSize, bufLen, vstr); - th_free(vstr); - } - else - { - vstr = th_strdup_printf( - "%s[\"%s\",%d,%d,%d,%d,%d,%d,%1.3f,[", - vstart, - match->map, - match->mx, match->my, - match->wx, match->wy, - match->marker->flags, - match->nname, - match->marker->vsort.v_float); - - th_strbuf_puts(buf, &bufSize, bufLen, vstr); - th_free(vstr); - - for (int i = 0; i < match->marker->nnames; i++) - { - vstr = th_strdup_printf("\"%s\"%s", - match->marker->names[i].name, - (i < match->marker->nnames - 1) ? "," : ""); - - th_strbuf_puts(buf, &bufSize, bufLen, vstr); - th_free(vstr); - } - - th_strbuf_puts(buf, &bufSize, bufLen, "]]"); - th_strbuf_puts(buf, &bufSize, bufLen, vend); - } - } - - th_strbuf_puts(buf, &bufSize, bufLen, "]"); -} - - -BOOL mapParseIntValue(const unsigned char *data, const size_t len, size_t *offs, int *val) -{ - size_t start = *offs; - - for (; *offs < len && data[*offs] != ':';) - (*offs)++; - - if (data[*offs] != ':' || *offs >= len) - return FALSE; - - *val = mapAtoI((char *) data + start, *offs); - return TRUE; -} - - -void mapPerformSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) -{ - static const char *cleanChars = " *@?%CX"; - size_t ncleanChars = strlen(cleanChars); - MapBlock *pattern = NULL, *ptmp = NULL; - BOOL mapList[SET_MAX_MAPS]; - MAPMatch matches[SET_MAX_MATCHES]; - int width, height, centerX = 0, centerY = 0, - nmatches = 0, nmapList, reqMatches, - maxMatches = SET_MAX_MATCHES; - BOOL centered = FALSE; - size_t offs = 0; - - // Get requested number of matches - if (!mapParseIntValue(data, len, &offs, &reqMatches)) - { - *verr = "Invalid search query."; - goto out; - } - - reqMatches = maxMatches; - if (maxMatches < 1 || maxMatches > SET_MAX_MATCHES) - maxMatches = SET_MAX_MATCHES; - - mapMSG(2, "Requested %d matches, limiting to %d.\n", - reqMatches, maxMatches); - - // Get active map list - nmapList = 0; - memset(&mapList, 0, sizeof(mapList)); - while (offs < len) - { - size_t offs2; - BOOL found = FALSE; - - // Find next separator or end \n - for (offs2 = offs; offs2 < len && data[offs2] != ':' && data[offs2] != '\n';) - offs2++; - - // Check against map list - if (offs2 > offs) - { - for (int nmap = 0; nmap < optNMaps && !found; nmap++) - { - const char *name = optMaps[nmap].locFile.continent; - const size_t slen = strlen(name); - - // If the map name matches requested, enable it - if (offs2 >= offs + slen && - memcmp(data + offs, name, slen) == 0) - { - mapList[nmap] = TRUE; - nmapList++; - found = TRUE; - } - } - - if (!found) - { - char *tmps = th_strndup_no0((const char *) data + offs, offs2 - offs); - - mapMSG(-1, "Unknown map spec '%s'.\n", tmps); - *verr = "Unknown map spec."; - - th_free(tmps); - goto out; - } - } - - // Check for separator or end - if (data[offs2] != ':') - { - offs = offs2; - break; - } - else - offs = offs2 + 1; - } - - // Check for remaining data - if (offs + 1 >= len || data[offs] != '\n') - { - *verr = "No map pattern data!"; - goto out; - } - offs++; - - // Parse pattern block dimensions - mapBlockParseDimensions(data + offs, len - offs, &width, &height); - if (width <= 0 || height <= 0) - { - *verr = "Could not parse map block dimensions."; - goto out; - } - - mapMSG(2, "Parsed block size %d x %d\n", width, height); - - // Do basic checks for sanity - if (width * height < 3) - { - *verr = "Search block pattern too small."; - goto out; - } - - if (width * height > 30 * 30) - { - *verr = "Search block pattern too large."; - goto out; - } - - // Allocate and attempt to parse the block - if ((pattern = mapBlockAlloc(width, height)) == NULL) - goto out; - - if (!mapBlockParse(data + offs, len - offs, pattern)) - { - *verr = "Error parsing map block data."; - goto out; - } - - // Crop the pattern block - if (mapBlockAutoCrop(&ptmp, pattern, - (unsigned char *) cleanChars, ncleanChars, - 0, 0, 0, 0, FALSE)) - { - mapBlockFree(pattern); - pattern = ptmp; - mapMSG(2, "Cropped block size: %d x %d\n", - pattern->width, pattern->height); - } - - // Sanity checks against the cropped block - if (pattern->width * pattern->height < 3) - { - *verr = "Search block pattern too small."; - goto out; - } - - // Print the cropped block - if (th_verbosity >= 2) - { - FILE *tmp = (setLogFH != NULL) ? setLogFH : stdout; - fprintf(tmp, "----------------------------\n"); - mapBlockPrint(tmp, pattern); - fprintf(tmp, "----------------------------\n"); - } - - // Entropy check - int entropy = mapBlockGetEntropy(pattern, cleanChars, ncleanChars); - mapMSG(2, "Block entropy %d\n", entropy); - - if ((entropy < 2 && width < 4 && height < 4) || - (entropy < 3 && width * height < 4)) - { - *verr = "Search block entropy insufficient."; - goto out; - } - - // Find pattern center marker, if any - centered = mapBlockFindCenter(pattern, - (unsigned char*) "*@X", 3, - ¢erX, ¢erY, 10); - - if (centered) - mapMSG(2, "Center at %d, %d\n", centerX, centerY); - - // Clean the pattern from characters we do not want to match - mapBlockClean(pattern, (unsigned char *) cleanChars, ncleanChars); - - // - // Search the maps .. enabled or if none specified, all of them - // - for (int nmap = 0; nmap < optNMaps; nmap++) - if (mapList[nmap] || nmapList == 0) - { - MAPInfoCtx *info = &optMaps[nmap]; - - for (int oy = 0; oy < info->map->height - pattern->height; oy++) - for (int ox = 0; ox < info->map->width - pattern->width; ox++) - { - // Check for match - if (mapMatchBlock(info->map, pattern, ox, oy)) - { - // Okay, add the match to our list - MAPMatch *match = &matches[nmatches++]; - - match->marker = NULL; - match->map = info->locFile.continent; - match->mx = ox + 1 + centerX; - match->my = oy + 1 + centerY; - match->wx = optWorldXC + info->locFile.xoffs + ox + centerX; - match->wy = optWorldYC + info->locFile.yoffs + oy + centerY; - - // Check for max matches - if (nmatches >= maxMatches) - goto out; - } - } - } - -out: - // If an error occured, bail out now - if (*verr != NULL) - { - mapBlockFree(pattern); - return; - } - - // We got some matches, output them as a JSON array - char *buf; - size_t bufLen; - - mapCreateResultStr(&buf, &bufLen, - matches, nmatches, maxMatches, - TRUE, centered, centerX, centerY, pattern); - - mapBlockFree(pattern); - - mapMSG(2, "%s\n", buf); - mapLWSWrite(wsi, (unsigned char *) buf, bufLen); - th_free(buf); -} - - -int mapCompareDistance(const void *pa, const void *pb) -{ - const LocMarker *va = *(const LocMarker **) pa, - *vb = *(const LocMarker **) pb; - - return va->vsort.v_float - vb->vsort.v_float; -} - - -int mapNearbyLocationSearch(LocMarker ***pnearest, - const int xc, const int yc, const int maxDist, char **verr) -{ - int nnearest = 0; - LocMarker **nearest; - - // Allocate memory for results - if ((*pnearest = nearest = th_malloc(sizeof(LocMarker *) * optNMapLocations)) == NULL) - { - *verr = "Could not allocate memory for temporary sorting buffer."; - goto out; - } - - // Calculate distances and filter by maxDist - for (int nloc = 0; nloc < optNMapLocations; nloc++) - { - LocMarker *marker = optMapLocations[nloc]; - int dx = xc - (optWorldXC + marker->xc + marker->file->xoffs), - dy = yc - (optWorldYC + marker->yc + marker->file->yoffs); - float dist = sqrt(dx * dx + dy * dy); - - if (maxDist < 0 || dist <= maxDist) - { - marker->vsort.v_float = dist; - nearest[nnearest++] = marker; - } - } - - // Sort the locations based on distance - if (nnearest > 0) - qsort(nearest, nnearest, sizeof(LocMarker *), mapCompareDistance); - -out: - - return nnearest; -} - - -void mapLocationSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) -{ - MAPMatch matches[SET_MAX_MATCHES]; - int nmatches = 0; - char *pattern = NULL; - size_t offs1, offs2, slen; - - if (len == 0) - { - *verr = "Search pattern too short."; - goto out; - } - - // Check search pattern length - for (offs1 = 0; offs1 < len && th_isspace(data[offs1]); ) offs1++; - for (offs2 = len - 1; offs2 > offs1 && (data[offs2] == 0 || th_isspace(data[offs2])); ) offs2--; - - slen = offs2 - offs1 + 1; - if (slen < 2) - { - *verr = "Search pattern too short."; - goto out; - } - - if (slen > 25) - { - *verr = "Search pattern too long."; - goto out; - } - - if ((pattern = th_strndup((char *) data + offs1, slen)) == NULL) - { - *verr = "Could not allocate search pattern memory."; - goto out; - } - - mapMSG(2, "Search pattern: '%s'\n", pattern); - - // Search the locations - for (int nmap = 0; nmap < optNMaps; nmap++) - { - MAPInfoCtx *info = &optMaps[nmap]; - for (int nloc = 0; nloc < info->loc.nlocations; nloc++) - { - LocMarker *marker = info->loc.locations[nloc]; - for (int nname = 0; nname < marker->nnames; nname++) - if ((marker->flags & LOCF_INVIS) == 0 && - th_strcasematch(marker->names[nname].name, pattern)) - { - // Okay, add the match to our list - MAPMatch *match = &matches[nmatches++]; - - match->marker = marker; - match->nname = nname; - match->map = info->locFile.continent; - match->mx = marker->xc + 1; - match->my = marker->yc + 1; - match->wx = optWorldXC + info->locFile.xoffs + marker->xc; - match->wy = optWorldYC + info->locFile.yoffs + marker->yc; - - // Check for max matches - if (nmatches >= SET_MAX_MATCHES) - goto out; - - // Bail out from this location, in order not to have dupes for it - break; - } - } - } - -out: - th_free(pattern); - - // If an error occured, bail out now - if (*verr != NULL) - return; - - // We got some matches, output them as a JSON array - char *buf; - size_t bufLen; - - mapCreateResultStr(&buf, &bufLen, - matches, nmatches, SET_MAX_MATCHES, - FALSE, FALSE, 0, 0, NULL); - - mapMSG(2, "%s\n", buf); - mapLWSWrite(wsi, (unsigned char *) buf, bufLen); - th_free(buf); -} - - -void mapNearLocationSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) -{ - MAPMatch matches[SET_MAX_MATCHES]; - LocMarker **nearest = NULL; - int findXC, findYC, maxDist, nnearest = 0, maxMatches = SET_MAX_MATCHES; - size_t offs = 0; - - // Get coordinates - if (!mapParseIntValue(data, len, &offs, &findXC)) - { - *verr = "Invalid search query, no global X coordinate specified."; - goto out; - } - - offs++; - - if (!mapParseIntValue(data, len, &offs, &findYC)) - { - *verr = "Invalid search query, no global Y coordinate specified."; - goto out; - } - - offs++; - - // Get max distance, default to some value - if (!mapParseIntValue(data, len, &offs, &maxDist)) - maxDist = 50; - else - { - offs++; - - // Get max matches amount - if (!mapParseIntValue(data, len, &offs, &maxMatches)) - maxMatches = SET_MAX_MATCHES; - } - - // Check values - if (findXC < 0 || findYC < 0) - { - *verr = "Invalid search coordinates."; - goto out; - } - - if (maxDist < 1 || maxDist > 1000) - { - *verr = "Invalid maximum search distance."; - goto out; - } - - if (maxMatches < 1) - maxMatches = 1; - else - if (maxMatches > SET_MAX_MATCHES) - maxMatches = SET_MAX_MATCHES; - - // Find nearest locations - nnearest = mapNearbyLocationSearch(&nearest, findXC, findYC, maxDist, verr); - if (*verr != NULL) - goto out; - - if (nnearest >= maxMatches) - nnearest = maxMatches; - - for (int nloc = 0; nloc < nnearest; nloc++) - { - LocMarker *marker = nearest[nloc]; - MAPMatch *match = &matches[nloc]; - - match->map = marker->file->continent; - match->marker = marker; - match->nname = 0; - match->mx = marker->xc + 1; - match->my = marker->yc + 1; - match->wx = optWorldXC + marker->file->xoffs + marker->xc; - match->wy = optWorldYC + marker->file->yoffs + marker->yc; - } - -out: - th_free(nearest); - - // If an error occured, bail out now - if (*verr != NULL) - return; - - // We got some matches, output them as a JSON array - char *buf; - size_t bufLen; - - mapCreateResultStr(&buf, &bufLen, - matches, nnearest, SET_MAX_MATCHES, - FALSE, FALSE, 0, 0, NULL); - - mapMSG(2, "%s\n", buf); - mapLWSWrite(wsi, (unsigned char *) buf, bufLen); - th_free(buf); -} - - -void mapHandleRequest(struct lws *wsi, char *data, const size_t len, char **verr) -{ - unsigned char *udata = (unsigned char *) data; - - // Check what the request is about? - if (len >= 10 + 2 && strncmp(data, "MAPSEARCH:", 10) == 0) - { - mapPerformSearch(wsi, udata + 10, len - 10, verr); - } - else - if (len >= 7 && strncmp(data, "GETMAPS", 7) == 0) - { - // Client wants a list of available maps - char *buf = NULL; - size_t bufLen = 0, bufSize = 0; - - mapMSG(1, "[%p] Sending map information.\n", wsi); - - th_strbuf_puts(&buf, &bufSize, &bufLen, "MAPS:["); - - for (int n = 0; n < optNMaps; n++) - { - MAPInfoCtx *info = &optMaps[n]; - char *vstr = th_strdup_printf( - "[\"%s\",%d,%d]%s", - info->locFile.continent, - info->locFile.xoffs + optWorldXC, - info->locFile.yoffs + optWorldYC, - (n < optNMaps - 1) ? "," : ""); - - th_strbuf_puts(&buf, &bufSize, &bufLen, vstr); - th_free(vstr); - } - - th_strbuf_puts(&buf, &bufSize, &bufLen, "]"); - mapLWSWrite(wsi, (unsigned char *) buf, bufLen); - th_free(buf); - } - else - if (len >= 10 + 1 && strncmp(data, "LOCSEARCH:", 10) == 0) - { - mapLocationSearch(wsi, udata + 10, len - 10, verr); - } - else - if (len >= 8 + 3 && strncmp(data, "LOCNEAR:", 8) == 0) - { - mapNearLocationSearch(wsi, udata + 8, len - 8, verr); - } - else - { - // Unknown or invalid query - *verr = "Invalid command, and/or not enough data."; - } -} - - -int mapLWSCallback(struct lws *wsi, - enum lws_callback_reasons reason, - void *user, void *in, size_t len) -{ - (void) user; - - switch (reason) - { - case LWS_CALLBACK_ESTABLISHED: - { - char strName[256], strIP[64]; - int fd = lws_get_socket_fd(wsi); - lws_get_peer_addresses(wsi, fd, strName, sizeof(strName), strIP, sizeof(strIP)); - mapMSG(2, "[%p] Client connection from %s [%s]\n", wsi, strIP, strName); - } - break; - - case LWS_CALLBACK_RECEIVE: - { - char *verr = NULL; - - mapHandleRequest(wsi, (char *) in, len, &verr); - - // Check for errors .. - if (verr != NULL) - { - char *vstr = th_strdup_printf("ERROR:%s", verr); - mapERR("[%p] %s\n", wsi, verr); - mapLWSWrite(wsi, (unsigned char *) vstr, strlen(vstr)); - th_free(vstr); - } - - // End communication - lws_close_reason(wsi, LWS_CLOSE_STATUS_NOSTATUS, NULL, 0); - } - break; - - default: - break; - } - - return 0; -} - - -static const struct lws_extension mapLWSExtensions[] = -{ - { - "permessage-deflate", - lws_extension_callback_pm_deflate, - "permessage-deflate" - }, - { - "deflate-frame", - lws_extension_callback_pm_deflate, - "deflate_frame" - }, - { NULL, NULL, NULL } -}; - - -static const struct lws_protocols mapLWSProtocols[] = -{ - { "default", &mapLWSCallback, 0, 0, 0, NULL -#ifdef HAVE_LIBWEBSOCKETS22 - , 0 -#endif - }, - - { NULL, NULL, 0, 0, 0, NULL -#ifdef HAVE_LIBWEBSOCKETS22 - , 0 -#endif - }, -}; - - -int mapReadFile(const char *filename, uint8_t **pbuf, size_t *pbufSize, - const size_t bufInit, const size_t bufGrow) -{ - size_t readSize, dataSize, dataPos; - FILE *fh = NULL; - int res = THERR_OK; - - if ((fh = fopen(filename, "rb")) == NULL) - { - res = THERR_FOPEN; - goto out; - } - - // Allocate initial data buffer - readSize = dataSize = bufInit; - if ((*pbuf = th_malloc(dataSize)) == NULL) - { - res = THERR_MALLOC; - goto out; - } - - dataPos = 0; - *pbufSize = 0; - while (!feof(fh) && !ferror(fh)) - { - size_t read = fread((*pbuf) + dataPos, 1, readSize, fh); - dataPos += read; - (*pbufSize) += read; - - if (*pbufSize >= dataSize) - { - readSize = bufGrow; - dataSize += bufGrow; - if ((*pbuf = th_realloc(*pbuf, dataSize)) == NULL) - { - res = THERR_MALLOC; - goto out; - } - } - else - break; - } - -out: - if (fh != NULL) - fclose(fh); - - return res; -} - - -BOOL mapLoadMaps(void) -{ - mapMSG(1, "Trying to load %d map specs. World origin at [%d, %d].\n", - optNMaps, optWorldXC, optWorldYC); - - for (int nmap = 0; nmap < optNMaps; nmap++) - { - MAPInfoCtx *info = &optMaps[nmap]; - FILE *fh; - - mapMSG(1, "Map ID '%s', data '%s', locations '%s' at [%d, %d]\n", - info->locFile.continent, - info->mapFilename, - info->locFile.filename, - info->locFile.xoffs, info->locFile.yoffs); - - if ((info->map = mapBlockParseFile(info->mapFilename, FALSE)) == NULL) - { - mapERR("Could not read map data file '%s'.\n", info->mapFilename); - return FALSE; - } - - if ((fh = fopen(info->locFile.filename, "rb")) == NULL) - { - mapERR("Could not open location file '%s' for reading.\n", - info->locFile.filename); - return FALSE; - } - - if (!locParseLocStream(fh, &info->locFile, &(info->loc), 0, 0)) - { - fclose(fh); - return FALSE; - } - - fclose(fh); - } - - // Get total number of non-invis locations - optNMapLocations = 0; - for (int nmap = 0; nmap < optNMaps; nmap++) - { - MAPInfoCtx *info = &optMaps[nmap]; - for (int nloc = 0; nloc < info->loc.nlocations; nloc++) - { - LocMarker *marker = info->loc.locations[nloc]; - if ((marker->flags & LOCF_INVIS) == 0) - optNMapLocations++; - } - } - - // Create large array of location pointers for sorting purposes - if ((optMapLocations = th_malloc(sizeof(LocMarker *) * optNMapLocations)) == NULL) - { - mapERR("Could not allocate memory for location sorting buffer of %d entries.\n", - optNMapLocations); - return FALSE; - } - - for (int nmap = 0, index = 0; nmap < optNMaps; nmap++) - { - MAPInfoCtx *info = &optMaps[nmap]; - for (int nloc = 0; nloc < info->loc.nlocations; nloc++) - { - LocMarker *marker = info->loc.locations[nloc]; - if ((marker->flags & LOCF_INVIS) == 0) - optMapLocations[index++] = marker; - } - } - - return TRUE; -} - - -void mapFreeMaps(void) -{ - for (int n = 0; n < optNMaps; n++) - { - MAPInfoCtx *info = &optMaps[n]; - - mapBlockFree(info->map); - locFreeMapLocations(&info->loc); - } - - th_free_r(&optMapLocations); -} - - -#ifdef HAVE_LIBWEBSOCKETS32 -void mapSigHandler(void *handle, int signum) -#else -void mapSigHandler(uv_signal_t *handle, int signum) -#endif -{ - (void) handle; - - switch (signum) - { - case SIGTERM: - case SIGINT: - mapERR("Signal %d caught, exiting...\n", signum); -#ifdef HAVE_LIBWEBSOCKETS32 - lws_context_destroy(setLWSContext); -#else - lws_libuv_stop(setLWSContext); -#endif - break; - - default: - mapERR("Signal %d caught, aborting...\n", signum); - signal(SIGABRT, SIG_DFL); - abort(); - break; - } -} - - -int main(int argc, char *argv[]) -{ - // Initialize - th_init("MapSearch", "Map Search WebSockets server", "0.8", NULL, NULL); - th_verbosity = 0; - - memset(&optMaps, 0, sizeof(optMaps)); - memset(&optListenTo, 0, sizeof(optListenTo)); - - // Parse command line arguments - BOOL argsOK = th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, 0); - - if (!argsOK) - return -2; - - if (optNMaps == 0) - { - mapERR("No maps specified.\n"); - goto exit; - } - - if (optNListenTo == 0 && optTest == NULL) - { - mapERR("No listeners specified.\n"); - goto exit; - } - - // Initialize logging - if (optLogFilename != NULL && optTest == NULL) - { - if ((setLogFH = fopen(optLogFilename, "a")) == NULL) - { - int err = th_get_error(); - mapERR("Could not open log file '%s' for writing (%d): %s\n", - err, th_error_str(err)); - goto exit; - } - } - - // Load maps - if (!mapLoadMaps()) - goto exit; - - // Check for test mode - if (optTest != NULL) - { - mapMSG(1, "Running in test mode, input '%s'.\n", optTest); - uint8_t *buf = NULL; - size_t bufSize; - - if (mapReadFile(optTest, &buf, &bufSize, 512, 512) == THERR_OK) - { - char *verr = NULL; - if (optBenchmark > 0) - { - int save = th_verbosity; - mapMSG(0, "Benchmarking for %d cycles ..\n", optBenchmark); - th_verbosity = -1; - for (int n = 0; n < optBenchmark; n++) - { - printf("."); - fflush(stdout); - mapHandleRequest(NULL, (char *) buf, bufSize, &verr); - if (verr != NULL) - { - mapERR("%s\n", verr); - break; - } - } - th_verbosity = save; - printf("\n"); - mapMSG(0, "Finished.\n"); - } - else - { - mapHandleRequest(NULL, (char *) buf, bufSize, &verr); - if (verr != NULL) - mapERR("%s\n", verr); - } - } - else - mapERR("Could not read test file.\n"); - - th_free(buf); - goto exit; - } - - // Initialize libwebsockets and create context - mapMSG(1, "Creating libwebsockets context.\n"); - - if ((setLWSBuffer = th_malloc(SET_LWS_BUF_SIZE)) == NULL) - { - mapERR("Could not allocate %d bytes of memory for LWS buffer.\n", - SET_LWS_BUF_SIZE); - - goto exit; - } - - // Setup log handler - lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE, mapLogWS); - - // Create the LWS context(s) - MAPListenerCtx *ctx = optListenTo[0]; - struct lws_context_creation_info info; - int info_options; - memset(&info, 0, sizeof(info)); - - info.gid = optGID; - info.uid = optUID; - info.max_http_header_pool = 16; - info_options = info.options = - LWS_SERVER_OPTION_EXPLICIT_VHOSTS | - LWS_SERVER_OPTION_VALIDATE_UTF8 | - LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT -#if defined(LWS_WITH_LIBUV) || defined(LWS_USE_LIBUV) || !defined(HAVE_LIBWEBSOCKETS32) - | LWS_SERVER_OPTION_LIBUV -#endif - ; - - info.protocols = mapLWSProtocols; - info.extensions = mapLWSExtensions; - info.ssl_cipher_list = optSSLCipherList; - info.timeout_secs = 5; -#ifdef HAVE_LIBWEBSOCKETS32 - info.signal_cb = mapSigHandler; -#endif - - if ((setLWSContext = lws_create_context(&info)) == NULL) - { - mapERR("libwebsocket initialization failed.\n"); - goto exit; - } - - // Create listener LWS vhosts .. - for (int n = 0; n < optNListenTo; n++) - { - char *ipvMode = ""; - ctx = optListenTo[n]; - - info.port = ctx->port; - info.iface = ctx->interface; - info.options = info_options; - - switch (ctx->ipvMode) - { - case 0: - ipvMode = "IPv4 + IPv6"; - break; - - case 4: - ipvMode = "IPv4 only"; - info.options |= LWS_SERVER_OPTION_DISABLE_IPV6; - break; - - case 6: - ipvMode = "IPv6 only"; - info.options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE; - break; - } - - if (ctx->useSSL) - { - mapMSG(1, "Listen to %s:%d (wss) [vhost='%s', cert='%s', key='%s', ca='%s'] [%s]\n", - ctx->interface != NULL ? ctx->interface : "*", - ctx->port, - ctx->vhostname, - ctx->sslCertFile, ctx->sslKeyFile, - (ctx->sslCAFile != NULL && ctx->sslCAFile[0]) ? ctx->sslCAFile : "NONE", - ipvMode); - - info.vhost_name = ctx->vhostname; - info.ssl_cert_filepath = ctx->sslCertFile; - info.ssl_private_key_filepath = ctx->sslKeyFile; - info.ssl_ca_filepath = (ctx->sslCAFile != NULL && ctx->sslCAFile[0]) ? ctx->sslCAFile : NULL; - } - else - { - mapMSG(1, "Listen to %s:%d (ws) [%s]\n", - ctx->interface != NULL ? ctx->interface : "*", - ctx->port, - ipvMode); - - info.vhost_name = NULL; - info.ssl_cert_filepath = NULL; - info.ssl_private_key_filepath = NULL; - info.ssl_ca_filepath = NULL; - } - - if ((ctx->vhost = lws_create_vhost(setLWSContext, &info)) == NULL) - { - mapERR("LWS vhost creation failed!\n"); - goto exit; - } - } - - // Drop privileges - lws_finalize_startup(setLWSContext); - -#ifndef HAVE_LIBWEBSOCKETS32 - // Set up signal handlers - mapMSG(1, "Setting up signal handlers.\n"); - lws_uv_sigint_cfg(setLWSContext, 1, mapSigHandler); - - // Set up libuv event loop - if (lws_uv_initloop(setLWSContext, NULL, 0)) - { - mapERR("lws_uv_initloop() failed.\n"); - goto exit; - } -#endif - - // Start running .. - mapMSG(1, "Waiting for connections...\n"); - lws_service(setLWSContext, 0); - -exit: - // Shutdown sequence - mapMSG(1, "Server shutting down.\n"); - - if (setLWSContext != NULL) - lws_context_destroy(setLWSContext); - - for (int n = 0; n < optNListenTo; n++) - mapFreeListenCtx(optListenTo[n]); - - mapFreeMaps(); - - for (int n = 0; n < optNMaps; n++) - { - MAPInfoCtx *info = &optMaps[n]; - - th_free(info->mapFilename); - th_free(info->locFile.filename); - th_free(info->locFile.continent); - } - - th_free(setLWSBuffer); - - if (setLogFH) - fclose(setLogFH); - - return 0; -}
--- a/mapstats.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,322 +0,0 @@ -/* - * Calculate terrain type (and other) statistics from ASCII map - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2007-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - - -typedef int (*compareFunc)(const void *, const void *); - - -enum -{ - SORT_NONE, - SORT_NAME, - SORT_SYMBOL, - SORT_AMOUNT -}; - -#define SET_MAX_FILES (512) - -char *srcFilenames[SET_MAX_FILES], - *dstFilename = NULL; -int nsrcFilenames = 0; - -BOOL optUseOldFormat = FALSE, - optInputIsDiff = FALSE, - optSortBy = SORT_NONE, - optCityFormat = FALSE; - - -typedef struct -{ - unsigned int n; - int piece; -} MapPieceStat; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'q', "quiet", "Be quiet", OPT_NONE }, - { 3, 'd', "input-diff", "Input is a diff", OPT_NONE }, - { 4, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, - { 5, 'o', "output", "Output filename", OPT_ARGREQ }, - { 6, 's', "sort", "Sort (by name,symbol,amount)", OPT_ARGREQ }, - { 7, 'c', "city-format","Input is a city map", OPT_NONE }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <input mapfile>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - th_verbosity = -1; - break; - - case 3: - optInputIsDiff = TRUE; - THMSG(2, "Input is a 'diff', handling it as such.\n"); - break; - - case 4: - optUseOldFormat = TRUE; - THMSG(2, "Input is using old map symbols/colors.\n"); - break; - - case 5: - dstFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", dstFilename); - break; - - case 6: - if (!strncmp(optArg, "n", 1)) - optSortBy = SORT_NAME; - else - if (!strncmp(optArg, "s", 1)) - optSortBy = SORT_SYMBOL; - else - if (!strncmp(optArg, "a", 1)) - optSortBy = SORT_AMOUNT; - else - { - THERR("Unknown sorting method name '%s'!\n", optArg); - exit(1); - } - - THMSG(2, "Sorting via method %d\n", optSortBy); - break; - - case 7: - optCityFormat = TRUE; - THMSG(2, "Input is handled as a city map\n"); - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (nsrcFilenames < SET_MAX_FILES) - { - srcFilenames[nsrcFilenames++] = currArg; - } - else - { - THERR("Too many input map files specified!\n"); - return FALSE; - } - - return TRUE; -} - - -int compareByName(const void *p1, const void *p2) -{ - MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; - const MapPiece *mp1 = &mapPieces[vp1->piece], *mp2 = &mapPieces[vp2->piece]; - - if (optUseOldFormat) - { - if (mp1->oldSymbol >= 0 && mp2->oldSymbol >= 0) - return strcmp(mp1->desc, mp2->desc); - else - return -1; - } - else - { - if (mp1->symbol >= 0 && mp2->symbol >= 0) - return strcmp(mp1->desc, mp2->desc); - else - return -1; - } -} - - -int compareBySymbol(const void *p1, const void *p2) -{ - MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; - const MapPiece *mp1 = &mapPieces[vp1->piece], *mp2 = &mapPieces[vp2->piece]; - - if (optUseOldFormat) - { - if (mp1->oldSymbol >= 0 && mp2->oldSymbol >= 0) - return mp1->oldSymbol - mp2->oldSymbol; - else - return -1; - } - else - { - if (mp1->symbol >= 0 && mp2->symbol >= 0) - return mp1->symbol - mp2->symbol; - else - return -1; - } -} - - -int compareByAmount(const void *p1, const void *p2) -{ - MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; - - if (vp1->n < vp2->n) - return -1; - else - if (vp1->n > vp2->n) - return 1; - else - return 0; -} - - -/* Main program - */ -int main(int argc, char *argv[]) -{ - FILE *outFile; - MapBlock *map; - unsigned int statTotal, statUnknown; - MapPieceStat statPieces[nmapPieces]; - - // Initialize - th_init("mapstats", "ASCII map statistics generator", "0.2", NULL, NULL); - th_verbosity = 1; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - exit(1); - - if (nsrcFilenames == 0) - { - THERR("Nothing to do. (try --help)\n"); - exit(0); - } - - // Read input file - statTotal = statUnknown = 0; - for (int i = 0; i < nmapPieces; i++) - { - statPieces[i].n = 0; - statPieces[i].piece = i; - } - - for (int n = 0; n < nsrcFilenames; n++) - { - THMSG(1, "Reading map file '%s'\n", srcFilenames[n]); - - if ((map = mapBlockParseFile(srcFilenames[n], optInputIsDiff)) == NULL) - { - THERR("Error reading map file '%s'!\n", - srcFilenames[n]); - exit(1); - } - - THMSG(1, "Analyzing %dx%d area ...\n", - map->width, map->height); - - for (int y = 0; y < map->height; y++) - { - unsigned char *d = map->data + (map->scansize * y); - for (int x = 0; x < map->width; x++) - { - int c; - if ((c = muGetMapPieceIndex(*d, optUseOldFormat, optCityFormat)) >= 0) - statPieces[c].n++; - else - statUnknown++; - - statTotal++; - d++; - } - } - mapBlockFree(map); - } - - // Sort results, if needed - if (optSortBy != SORT_NONE) - { - compareFunc tmpFunc; - - THMSG(2, "Sorting results ...\n"); - switch (optSortBy) - { - case SORT_NAME: tmpFunc = compareByName; break; - case SORT_SYMBOL: tmpFunc = compareBySymbol; break; - case SORT_AMOUNT: tmpFunc = compareByAmount; break; - default: - THERR("Internal error, no sort function for sort type %d.\n", optSortBy); - exit(2); - break; - } - qsort(statPieces, nmapPieces, sizeof(MapPieceStat), tmpFunc); - } - - // Open output file - if (dstFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(dstFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - dstFilename); - exit(1); - } - - for (int i = 0; i < nmapPieces; i++) - { - const int p = statPieces[i].piece; - int symbol = optUseOldFormat ? mapPieces[p].oldSymbol : mapPieces[p].symbol; - if (symbol >= 0) - { - fprintf(outFile, "%-20s [%c]: %6d (%1.3f%%)\n", - mapPieces[p].desc, - symbol, - statPieces[i].n, - (statPieces[i].n * 100.0f / statTotal)); - } - } - - fprintf(outFile, " %d total, %d unknown.\n", - statTotal, statUnknown); - - fclose(outFile); - - THMSG(1, "Done.\n"); - - exit(0); - return 0; -}
--- a/mkcitymap.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,444 +0,0 @@ -/* - * Create interactive XHTML+CSS+JS map from input - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "liblocfile.h" -#include "th_args.h" -#include "th_string.h" - - -char *optDestFilename = NULL, - *optMapFilename = NULL, - *optLocFilename = NULL, - *optHeadFilename = NULL, - *optMapTitle = NULL; -BOOL optUseOldFormat = FALSE; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'o', "output", "Output filename", OPT_ARGREQ }, - { 3, 't', "title", "Map title", OPT_ARGREQ }, - { 4, 'h', "header-file", "Specify extra <head> data in file", OPT_ARGREQ }, - { 5, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, -}; - -const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <mapfile> <locfile>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - optDestFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", optDestFilename); - break; - - case 3: - optMapTitle = optArg; - THMSG(2, "Map title string set to '%s'.\n", optMapTitle); - break; - - case 4: - optHeadFilename = optArg; - THMSG(2, "Head filename set to '%s'.\n", optHeadFilename); - break; - - case 5: - optUseOldFormat = TRUE; - THMSG(2, "Input is using old map symbols/colors.\n"); - break; - - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (!optMapFilename) - optMapFilename = currArg; - else - if (!optLocFilename) - optLocFilename = currArg; - else - { - THERR("Too many filenames specified!\n"); - return FALSE; - } - - return TRUE; -} - - -void outputHTMLHeader(FILE *outFile, const MapLocations *locs) -{ - (void) locs; - - muPrintHTMLhead(outFile, optMapTitle, TRUE); - - if (optHeadFilename != NULL && - muCopyFileToStream(outFile, optHeadFilename) < 0) - { - THERR("Error copying urchin file '%s' to output.\n", optHeadFilename); - } - - fprintf(outFile, " <style type=\"text/css\">\n"); - muPrintHTMLcolors(outFile, "span", NULL, NULL); - fprintf(outFile, " </style>\n"); - - - fprintf(outFile, - "</head>\n" - "<body onload=\"mapOnLoad();\">\n" - ); - - if (optMapTitle) - { - fprintf(outFile, "<h1>"); - fputse(optMapTitle, outFile); - fprintf(outFile, "</h1>\n"); - } - - fprintf(outFile, - "<div class=\"map %s\">" - "<pre class=\"map\">", - optUseOldFormat ? "old" : "new" - ); -} - - -const char *getCityLocationType(const LocMarker *marker) -{ - switch (marker->flags & LOCF_M_MASK) - { -// case LOCF_M_CITY: return "special"; - case LOCF_M_PCITY: return "gov"; - default: - switch (marker->flags & LOCF_T_MASK) - { - case LOCF_T_SHRINE: return "shop"; - 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"; -} - - -static char *getExtraNameData(const LocMarker *marker) -{ - return th_strdup_printf( - "%s", - (marker->flags & LOCF_CLOSED) ? " <b>[CLOSED]</b>" : "" - ); -} - - -static int compareLocation(const void *pp1, const void *pp2) -{ - const LocMarker - *vp1 = *(const LocMarker **) pp1, - *vp2 = *(const LocMarker **) pp2; - - if (vp1->flags == vp2->flags) - return th_strcasecmp(vp1->names[0].name, vp2->names[0].name); - else - return (vp1->flags & LOCF_MASK) - (vp2->flags & LOCF_MASK); -} - - -void outputHTMLFooter(FILE *outFile, MapLocations *locs) -{ - char *tmps; - - fprintf(outFile, - "</pre>" - "</div>\n" - "<div class=\"loctab\">\n" - ); - - qsort(locs->locations, locs->nlocations, sizeof(LocMarker *), compareLocation); - - for (int n = 0; n < locs->nlocations; n++) - { - const LocMarker *marker = locs->locations[n]; - if (marker->flags & LOCF_INVIS) - continue; - - if ((tmps = getExtraNameData(marker)) == NULL) - return; - - for (int i = 0; i < marker->nnames; i++) - { - - fprintf(outFile, - "<a class=\"loc %s%s lt%s lt%d\" id=\"listloc%d_%d\" href=\"?%d_%d\" " - "onmouseover=\"%s('%d_%d');\" onmouseout=\"qn();\">", - i == 0 ? "ltprimary" : "ltsecondary", - i == 0 && marker->nnames > 1 ? " ltsubitems" : "", - getCityLocationType(marker), - marker->align, - marker->xc, marker->yc, - marker->xc, marker->yc, - (marker->freeform /* be less spammy in the list || marker->nnames > 1 */) ? "sttq" : "qh", - marker->xc, marker->yc); - - fputse(marker->names[i].name, outFile); - - fprintf(outFile, "%s</a>\n", tmps); - } - - th_free(tmps); - } - - fprintf(outFile, - "</div>\n" - ); - - for (int n = 0; n < locs->nlocations; n++) - { - const LocMarker *marker = locs->locations[n]; - if (marker->flags & LOCF_INVIS) - continue; - - if ((tmps = getExtraNameData(marker)) == NULL) - return; - - fprintf(outFile, - "<div class=\"tooltip holder\" id=\"tt%d_%d\">" - "<h1>%s%s</h1>", - marker->xc, marker->yc, - marker->names[0].name, - tmps); - - if (marker->nnames > 1) - { - for (int i = 1; i < marker->nnames; i++) - { - fprintf(outFile, "%s%s", - marker->names[i].name, - i + 1 < marker->nnames ? " ; " : ""); - } - } - - if (marker->freeform) - { - fprintf(outFile, "<br />%s", marker->freeform); - } - - fprintf(outFile, "</div>\n"); - - th_free(tmps); - } - - fprintf(outFile, - "</body>\n" - "</html>\n" - ); -} - - -void outputMapHTML(FILE *outFile, const MapBlock *map, const MapLocations *locs) -{ - int n, p = -1; - BOOL span = FALSE; - - for (int yc = 0; yc < map->height; yc++) - { - const unsigned char *dp = map->data + (map->scansize * yc); - for (int xc = 0; xc < map->width; xc++) - { - if ((n = locFindByCoords(locs, xc, yc, TRUE)) >= 0) - { - LocMarker *marker = locs->locations[n]; - unsigned char chr; - int color; - - if (span) - fprintf(outFile, "</span>"); - - if (marker->uri != NULL) - { - // If URI/URL is set, parse piece color from it - char *endptr = NULL; - color = strtol(marker->uri, &endptr, 10); - if (color < 0 || color >= nmapColors) - color = 0; - - // If there is extra character after color number, - // use it as the map patch - if (endptr != NULL && *endptr != 0) - chr = *endptr; - else - chr = *dp; - } - else - { - // If old format, use map piece color - if (optUseOldFormat) - color = muGetMapPieceColor(*dp, optUseOldFormat, TRUE); - else - // Otherwise use "link" color - color = col_light_red; - - chr = *dp; - } - - if (marker->flags & LOCF_INVIS) - { - fprintf(outFile, - "<span class=\"%c\">%c</span>", - 'a' + color, chr); - } - else - { - fprintf(outFile, - "<span class=\"%c\">" - "<a onmouseover=\"sttq('%d_%d');\" " - "onmouseout=\"qn();\" " - "class=\"loc\" id=\"maploc%d_%d\" " - "href=\"?%d_%d\">%c</a></span>", - 'a' + color, - marker->xc, marker->yc, - marker->xc, marker->yc, - marker->xc, marker->yc, - chr); - } - span = FALSE; - p = -1; - } - else - { - if (p != *dp) - { - int color = muGetMapPieceColor(*dp, optUseOldFormat, TRUE); - if (span) fprintf(outFile, "</span>"); - fprintf(outFile, "<span class=\"%c\">", - 'a' + color); - span = TRUE; - } - fputc(*dp, outFile); - p = *dp; - } - dp++; - } - if (span) fprintf(outFile, "</span>\n"); - p = -1; - span = FALSE; - } -} - - -int main(int argc, char *argv[]) -{ - MapBlock *map = NULL; - FILE *outFile = NULL, *inFile = NULL; - LocFileInfo info; - MapLocations locations; - - memset(&info, 0, sizeof(info)); - memset(&locations, 0, sizeof(locations)); - - th_init("mkcitymap", "ASCII citymap converter", "0.5", NULL, NULL); - th_verbosity = 0; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - exit(1); - - if (optMapFilename == NULL || optLocFilename == NULL) - { - THERR("You need to specify at least map and loc filenames. (try --help)\n"); - goto exit; - } - - // Parse location file - locSetFileInfo(&info, optLocFilename, NULL); - if ((inFile = fopen(info.filename, "rb")) == NULL) - { - THERR("Could not open location file '%s' for reading.\n", - info.filename); - goto exit; - } - - if (!locParseLocStream(inFile, &info, &locations, info.xoffs, info.yoffs)) - goto exit; - - - // Open mapfile - if ((map = mapBlockParseFile(optMapFilename, FALSE)) == NULL) - { - THERR("Error parsing/opening mapfile '%s'.\n", - optMapFilename); - goto exit; - } - - if (optDestFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(optDestFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - optDestFilename); - goto exit; - } - - // Output data - outputHTMLHeader(outFile, &locations); - outputMapHTML(outFile, map, &locations); - outputHTMLFooter(outFile, &locations); - - -exit: - // Close input and output files - if (outFile != NULL && outFile != stdout) - fclose(outFile); - - if (inFile != NULL) - fclose(inFile); - - mapBlockFree(map); - locFreeMapLocations(&locations); - locFreeFileInfo(&info); - - return 0; -}
--- a/mkloc.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1260 +0,0 @@ -/* - * Manipulate and convert BatMUD location data files - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "liblocfile.h" -#include "th_args.h" -#include "th_string.h" -#include "th_datastruct.h" - -enum -{ - OUTFMT_MAP = 0, - OUTFMT_LOCFILE, - OUTFMT_SCRIPT, - OUTFMT_MAPLOC, - OUTFMT_GMAPS -}; - -enum -{ - GMAPS_XML = 0, - GMAPS_JSON, - GMAPS_LAST -}; - -// These must be in lower case -static const char *gmapsModes[GMAPS_LAST] = -{ - "xml", - "json", -}; - -/* Variables - */ -char *optInFilename = NULL, - *optOutFilename = NULL; - -int noptLocFiles = -1; -LocFileInfo optLocFiles[LOC_MAX_FILES]; - -char *optLocMarkers = LOC_MARKERS; -BOOL optGetUpdateLoc = FALSE, - optOutput = OUTFMT_MAP, - optNoLabels = FALSE, - optNoAdjust = FALSE, - optLabelType = FALSE; -int optGMapsMode = -1; -float optScale = -1, - optUnitSize = 1.0f, - optFontScale = 1.0f; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 3, 'q', "quiet", "Be quiet", OPT_NONE }, - { 1, 'o', "output", "Output file (default stdout)", OPT_ARGREQ }, - { 5, 'm', "map", "Input map file", OPT_ARGREQ }, - { 6, 'l', "locinfo", "Input location info file", OPT_ARGREQ }, - { 4, 'g', "getloc", "Generate/update location info", OPT_NONE }, - { 7, 'x', "offset-x", "Location X offset", OPT_ARGREQ }, - { 8, 'y', "offset-y", "Location Y offset", OPT_ARGREQ }, - { 9, 's', "scale", "Scale coordinates by", OPT_ARGREQ }, - { 12,'f', "font-scale", "(-S) Font scale factor", OPT_ARGREQ }, - { 13,'u', "unit-size", "(-S) Unit size", OPT_ARGREQ }, - { 10,'S', "out-script", "Output script for ImageMagick", OPT_NONE }, - { 11,'L', "out-locinfo", "Output location info file", OPT_NONE }, - { 17,'M', "out-maploc", "Output MapLoc HTML", OPT_NONE }, - { 20,'G', "out-gmaps", "Output GMaps data (xml, overlay, labels)", OPT_ARGREQ }, - { 21,'c', "continent", "Continent for GMaps XML output", OPT_ARGREQ }, - { 15,'n', "no-labels", "No labels, only markers", OPT_NONE }, - { 16,'N', "no-adjust", "No label adjustment", OPT_NONE }, - { 18,'t', "type-prefix", "Prepend labels with type prefix", OPT_NONE }, - { 19,'X', "markers", "Location markers ('" LOC_MARKERS "')", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp() -{ - th_print_banner(stdout, th_prog_name, - "[options]"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - LocFileInfo *f; - - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 2: - th_verbosity++; - break; - - case 3: - th_verbosity = -1; - break; - - case 1: - optOutFilename = optArg; - THMSG(2, "Output file '%s'\n", optOutFilename); - break; - - case 5: - optInFilename = optArg; - THMSG(2, "Map file '%s'\n", optInFilename); - break; - - case 6: - noptLocFiles++; - if (noptLocFiles < LOC_MAX_FILES) - { - locSetFileInfo(&optLocFiles[noptLocFiles], optArg, NULL); - THMSG(2, "Added location data file #%d '%s'\n", noptLocFiles, optArg); - } - else - { - THERR("Too many location data files specified, maximum is %d.\n", LOC_MAX_FILES); - return FALSE; - } - break; - - case 4: - THMSG(2, "Location updating/generation mode\n"); - optGetUpdateLoc = TRUE; - break; - - case 7: - case 8: - case 21: - if (noptLocFiles >= 0) - { - f = &optLocFiles[noptLocFiles]; - switch (optN) - { - case 7: - f->xoffs = atoi(optArg); - THMSG(2, "Location file #%d X offset = %d\n", noptLocFiles, f->xoffs); - break; - - case 8: - f->yoffs = atoi(optArg); - THMSG(2, "Location file #%d Y offset = %d\n", noptLocFiles, f->yoffs); - break; - - case 21: - th_free(f->continent); - f->continent = th_strdup(optArg); - THMSG(2, "Using continent name '%s' for location file #%d\n", optArg, noptLocFiles); - break; - } - } - else - { - THERR("No location files specified, but location option '-%s %s' given.\n", currArg, optArg); - return FALSE; - } - break; - - - case 9: - optScale = atof(optArg); - THMSG(2, "Location scale factor = %1.3f\n", optScale); - break; - - case 10: - THMSG(2, "Script output mode\n"); - optOutput = OUTFMT_SCRIPT; - break; - - case 11: - THMSG(2, "Location file output mode\n"); - optOutput = OUTFMT_LOCFILE; - break; - - case 17: - THMSG(2, "MapLoc HTML output mode\n"); - optOutput = OUTFMT_MAPLOC; - break; - - case 12: - optFontScale = atof(optArg); - THMSG(2, "Font scale factor = %1.3f\n", optFontScale); - break; - - case 13: - optUnitSize = atof(optArg); - THMSG(2, "Unit size = %1.3f\n", optUnitSize); - break; - - case 15: - THMSG(2, "Not adding labels to locations\n"); - optNoLabels = TRUE; - break; - - case 16: - THMSG(2, "Not adjusting data under labels\n"); - optNoAdjust = TRUE; - break; - - case 18: - THMSG(2, "Adding label type prefixes\n"); - optLabelType = TRUE; - break; - - case 19: - optLocMarkers = optArg; - THMSG(2, "Using location markers = '%s'\n", optLocMarkers); - break; - - case 20: - optOutput = OUTFMT_GMAPS; - optGMapsMode = -1; - for (int i = 0; i < GMAPS_LAST; i++) - if (tolower(optArg[0]) == gmapsModes[i][0]) - { - optGMapsMode = i; - break; - } - - if (optGMapsMode < 0) - { - THERR("Invalid GMaps mode '%s'.\n", optArg); - return FALSE; - } - THMSG(2, "GMaps output mode '%s' selected\n", gmapsModes[optGMapsMode]); - break; - - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -int locPrintType(FILE *outFile, LocMarker *loc, BOOL adjust, int (*func)(const char *, FILE *), BOOL label) -{ - const char *type = locGetTypePrefix(loc->flags); - int len = 0; - - if (type != NULL && label) - { - len += strlen(type) + 1; - if (outFile != NULL && func != NULL) - fprintf(outFile, "%s ", type); - } - - if (func != NULL) - { - char *name = (loc->names[0].name != NULL) ? loc->names[0].name : "UNKNOWN"; - if (outFile != NULL) - func(name, outFile); - len += strlen(name); - - if (label && (loc->flags & LOCF_CLOSED)) - { - static const char *strClosed = " (CLOSED)"; - if (outFile != NULL) - func(strClosed, outFile); - len += strlen(strClosed); - } - } - - return adjust ? len : 0; -} - - -/* Adjust location label coordinates for the ASCII-CTRL map - */ -void adjustLocInfoCoords(MapBlock *map, MapLocations *l) -{ - for (int i = 0; i < l->nlocations; i++) - { - int yc, x0, x1, len; - LocMarker *tmp = l->locations[i]; - - len = strlen(tmp->names[0].name); - if (optLabelType) - len += locPrintType(NULL, tmp, TRUE, NULL, FALSE); - - // Compute text location - switch (tmp->align) - { - case LOCD_LEFTDOWN: - yc = tmp->yc + 1; - x0 = tmp->xc - len; - break; - - case LOCD_LEFT: - yc = tmp->yc - 1; - x0 = tmp->xc - len; - break; - - case LOCD_DOWN: - yc = tmp->yc + 1; - x0 = tmp->xc + 1; - break; - - case LOCD_NONE: - default: - yc = tmp->yc - 1; - x0 = tmp->xc + 1; - break; - } - - x1 = x0 + len + 1; - - if (x1 > map->width) - x0 -= (x1 - map->width - 1); - if (x0 < 0) - x0 = 0; - - if (yc < 0) - yc += 2; - if (yc > map->height) - yc -= (yc - map->height - 1); - - // Update location info - tmp->xc = x0; - tmp->yc = yc; - } -} - - -/* Check for adjacent markers - */ -int checkForAdjacent(MapLocations *world, int cx, int cy, int mask) -{ - int x, y, n; - - for (y = -1; y <= 1; y++) - for (x = -1; x <= 1; x++) - { - if (!(y == 0 && x == 0) && - (n = locFindByCoords(world, cx + x, cy + y, TRUE)) >= 0) - { - LocMarker *loc = world->locations[n]; - if ((loc->flags & mask) == mask) - return n; - } - } - - return -1; -} - - -/* Scan given map and update location list with new locations, - * if any are found. - */ -void updateLocations(MapBlock *worldMap, MapLocations *worldLoc) -{ - int n, numNewLoc = 0, numInvLoc = 0, numNoMarker = 0; - size_t noptLocMarkers = strlen(optLocMarkers); - LocMarker *tmpl; - - THMSG(2, "Updating location information ..\n"); - - // Find new, unknown locations - for (int y = 0; y < worldMap->height; y++) - { - unsigned char *dp = worldMap->data + (worldMap->scansize * y); - - for (int x = 0; x < worldMap->width; x++) - { - if (muStrChr((unsigned char *) optLocMarkers, noptLocMarkers, dp[x])) - { - LocName tmpNames[LOC_MAX_NAMES]; - char tmpDesc[512]; - int tmpFlags; - - // Check for new locations - if (locFindByCoords(worldLoc, x, y, TRUE) >= 0) - continue; - - - if (dp[x] == 'C') - { - // In case of player cities, check for existing adjacent blocks - // so we can match with an existing pcity .. - n = checkForAdjacent(worldLoc, x, y, LOCF_M_PCITY); - if (n >= 0) - { - // Found, use its name for this block - tmpl = worldLoc->locations[n]; - tmpFlags = LOCF_M_PCITY | LOCF_INVIS; - snprintf(tmpDesc, sizeof(tmpDesc), - "%s", tmpl->names[0].name); - - } - else - { - // Nope, it is a "new" pcity - numNewLoc++; - tmpFlags = LOCF_M_PCITY; - snprintf(tmpDesc, sizeof(tmpDesc), - "%.3s-PCITY #%d", - optInFilename, numNewLoc); - } - } - else - { - // Non-pcities are handled here - numNewLoc++; - tmpFlags = LOCF_NONE; - snprintf(tmpDesc, sizeof(tmpDesc), - "%.3s-UNK #%d", - optInFilename, numNewLoc); - } - - memset(tmpNames, 0, sizeof(tmpNames)); - tmpNames[0].name = tmpDesc; - locAddNew(worldLoc, x, y, LOCD_NONE, tmpFlags, - tmpNames, NULL, NULL, FALSE, NULL, NULL, NULL); - } - else - { - // Check for misplaced locations - if ((n = locFindByCoords(worldLoc, x, y, TRUE)) >= 0) - { - tmpl = worldLoc->locations[n]; - - if (tmpl->flags == LOCF_NONE) - { - // Mark as possibly invalid - tmpl->flags |= LOCF_INVALID; - numInvLoc++; - } - - if ((tmpl->flags & LOCF_M_MASK) == 0) - { - // No apparent marker - tmpl->flags |= LOCF_NOMARKER; - numNoMarker++; - } - } - } - } - } - - THMSG(1, "%d new locations, %d invalid, %d missing marker.\n", - numNewLoc, numInvLoc, numNoMarker); -} - - -/* Quicksort comparision function for location names - */ -int maplocCompare(const void *pp1, const void *pp2) -{ - const LocMarker - *vp1 = *(const LocMarker **) pp1, - *vp2 = *(const LocMarker **) pp2; - - if (vp1->vsort.v_int == vp2->vsort.v_int) - return strcmp(vp1->names[0].name, vp2->names[0].name); - else - return vp1->vsort.v_int - vp2->vsort.v_int; -} - - -/* Sort locations by name - */ -void maplocSort(MapLocations *l) -{ - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *loc = l->locations[i]; - loc->vsort.v_int = 2; - - switch (loc->flags & LOCF_M_MASK) - { - case LOCF_M_CITY: loc->vsort.v_int = 1; break; - case LOCF_M_PCITY: loc->vsort.v_int = 10; break; - default: - switch (loc->flags & LOCF_T_MASK) - { - case LOCF_T_GUILD: loc->vsort.v_int = 3; break; - case LOCF_T_TRAINER: loc->vsort.v_int = 4; break; - case LOCF_T_SHRINE: loc->vsort.v_int = 5; break; - case LOCF_T_SS: loc->vsort.v_int = 6; break; - case LOCF_T_MONSTER: loc->vsort.v_int = 7; break; - case LOCF_T_FORT: loc->vsort.v_int = 100; break; - } - } - } - - qsort(l->locations, l->nlocations, sizeof(LocMarker *), maplocCompare); -} - - -/* Output the map with labels and location markers, etc. in - * special ASCII-CTRL format understood by colormap utility. - */ -void outputMapCTRL(FILE *outFile, MapBlock *map, MapLocations *l) -{ - for (int y = 0; y < map->height; y++) - { - unsigned char *dp = map->data + (map->scansize * y); - - for (int x = 0; x < map->width; x++) - { - int n; - if ((n = locFindByCoords(l, x, y, TRUE)) >= 0) - { - LocMarker *tmp = l->locations[n]; - char chm = dp[x]; - - switch (tmp->flags & LOCF_M_MASK) - { - case LOCF_M_SCENIC1: chm = '?'; break; - case LOCF_M_SCENIC2: chm = '%'; break; - case LOCF_M_PCITY: chm = 'C'; break; - case LOCF_M_CITY: chm = 'c'; break; - - default: - if (tmp->flags & LOCF_INVALID) - chm = '$'; - break; - } - - fputc(0xfb, outFile); - fprintf(outFile, "mloc%d_%d", tmp->ox + 1, tmp->oy + 1); - fputc(0xfc, outFile); - fputc(chm, outFile); - fputc(0xfe, outFile); - } - else - if (!optNoLabels && (n = locFindByCoords(l, x, y, FALSE)) >= 0) - { - LocMarker *tmp = l->locations[n]; - - if ((tmp->flags & LOCF_INVIS) == 0) - { - int col = col_light_white; - - fputc(0xff, outFile); - fprintf(outFile, "loc%d_%d", tmp->ox + 1, tmp->oy + 1); - fputc(0xfc, outFile); - - switch (tmp->flags & LOCF_M_MASK) - { - case LOCF_M_PCITY: col = col_light_green; break; - case LOCF_M_CITY: col = col_light_red; break; - - default: - switch (tmp->flags & LOCF_T_MASK) - { - case LOCF_T_SHRINE: col = col_light_yellow; break; - case LOCF_T_GUILD: col = col_light_magenta; break; - case LOCF_T_MONSTER: col = col_light_cyan; break; - case LOCF_T_TRAINER: col = col_light_magenta; break; - case LOCF_T_FORT: col = col_light_cyan; break; - default: col = col_light_white; break; - } - break; - } - - if (tmp->flags & LOCF_CLOSED) - col = col_light_red; - - fputc(col, outFile); - - if (optLabelType) - { - x += locPrintType(outFile, tmp, !optNoAdjust, NULL, FALSE); - } - - fputs(tmp->names[0].name, outFile); - fputc(0xfe, outFile); - - if (!optNoAdjust) - x += strlen(tmp->names[0].name) - 1; - else - fputc(dp[x], outFile); - } - else - fputc(dp[x], outFile); - } - else - fputc(dp[x], outFile); - } - - fprintf(outFile, "\n"); - } -} - - -/* Print out location list as HTML option list. - */ -void outputMapLocHTML(FILE *outFile, MapLocations *l) -{ - assert(l != NULL); - - fprintf(outFile, "<option value=\"\">-</option>\n"); - - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *tmp = l->locations[i]; - - if (tmp->flags & LOCF_INVIS) - continue; - - fprintf(outFile, "<option value=\"loc%d_%d\">", tmp->ox + 1, tmp->oy + 1); - locPrintType(outFile, tmp, FALSE, fputse, TRUE); - fprintf(outFile, "</option>\n"); - } -} - - -/* Output generated locations into given file stream - */ -void printLocNameEsc(FILE *outFile, LocName *name) -{ - char *prefix = ""; - switch (name->flags) - { - case NAME_ORIG: prefix = "@"; break; - case NAME_RECODER: prefix = "!"; break; - case NAME_MAINTAINER: prefix = "%"; break; - case NAME_EXPANDER: prefix = "&"; break; - } - fputs(prefix, outFile); - fputsesc2(name->name, outFile); -} - - -void outputLocationFile(FILE *outFile, MapLocations *l) -{ - // Output header - fprintf(outFile, - "# " LOC_MAGIC " (version %d.%d)\n", - LOC_VERSION_MAJOR, LOC_VERSION_MINOR - ); - - fprintf(outFile, - "# Refer to README.loc for more information.\n" - "#\n" - ); - - // Output each location entry - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *tmp = l->locations[i]; - - // Add comment in few cases - if (tmp->flags & LOCF_Q_MASK) - { - char *s = NULL; - if (tmp->flags & LOCF_NOMARKER) - s = "Location missing marker"; - else - if (tmp->flags & LOCF_INVALID) - s = "Possibly invalid location"; - - if (s) - { - fprintf(outFile, "\n# %s #%d: %s\n", - s, i, tmp->names[0].name); - } - } - - fprintf(outFile, "%d\t; %d\t; %d", - tmp->ox + 1, tmp->oy + 1, tmp->align); - - switch (tmp->flags & LOCF_M_MASK) - { - case LOCF_M_SCENIC1: fputc('?', outFile); break; - case LOCF_M_SCENIC2: fputc('%', outFile); break; - case LOCF_M_PCITY: fputc('C', outFile); break; - case LOCF_M_CITY: fputc('c', outFile); break; - } - - switch (tmp->flags & LOCF_T_MASK) - { - case LOCF_T_SHRINE: fputc('S', outFile); break; - case LOCF_T_GUILD: fputc('G', outFile); break; - case LOCF_T_SS: fputc('P', outFile); break; - case LOCF_T_MONSTER: fputc('M', outFile); break; - case LOCF_T_TRAINER: fputc('T', outFile); break; - case LOCF_T_FORT: fputc('F', outFile); break; - } - - if (tmp->flags & LOCF_CLOSED) - fputc('!', outFile); - - if (tmp->flags & LOCF_INSTANCED) - fputc('I', outFile); - - if (tmp->flags & LOCF_INVIS) - fputc('-', outFile); - - fprintf(outFile, "\t;"); - printLocNameEsc(outFile, &tmp->names[0]); - for (int n = 1; n < tmp->nnames; n++) - { - fprintf(outFile, "|"); - printLocNameEsc(outFile, &tmp->names[n]); - } - fprintf(outFile, ";"); - - if (tmp->ncoders > 0) - { - printLocNameEsc(outFile, &tmp->coders[0]); - for (int n = 1; n < tmp->ncoders; n++) - { - fprintf(outFile, ","); - printLocNameEsc(outFile, &tmp->coders[n]); - } - } - - fprintf(outFile, ";"); - - if (tmp->valid) - { - fprintf(outFile, LOC_TIMEFMT, - tmp->added.day, tmp->added.month, tmp->added.year); - } - - fprintf(outFile, ";"); - - if (tmp->uri) - fputsesc2(tmp->uri, outFile); - - fprintf(outFile, ";"); - - if (tmp->freeform) - fputsesc2(tmp->freeform, outFile); - - fprintf(outFile, "\n"); - } -} - - -/* Generate a shell-script for running ImageMagick to add - * location and label information to an map image. - */ -void outputMagickScript(FILE *outFile, MapLocations *l) -{ - int numCity, numLoc, numTotal; - - // Output script start - fprintf(outFile, - "#!/bin/sh\n" - "convert \"$1\" @OPTS_START@ \\\n"); - - numCity = numLoc = numTotal = 0; - - // Output instructions for each visible location - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *tmp = l->locations[i]; - int xc, yc, leftMove; - char *cs; - - // Is location visible? - if (tmp->flags & LOCF_INVIS) - continue; - - leftMove = ((float) strlen(tmp->names[0].name)) * optFontScale; - - switch (tmp->align) - { - case LOCD_LEFTDOWN: - yc = tmp->yc + optUnitSize*3.0f; - xc = tmp->xc - leftMove; - break; - - case LOCD_LEFT: - yc = tmp->yc - optUnitSize; - xc = tmp->xc - leftMove; - break; - - case LOCD_DOWN: - yc = tmp->yc + optUnitSize*3.0f; - xc = tmp->xc + optUnitSize; - break; - - case LOCD_NONE: - default: - yc = tmp->yc - optUnitSize; - xc = tmp->xc + optUnitSize; - break; - } - - // Determine colour - switch (tmp->flags & LOCF_M_MASK) - { - case LOCF_M_CITY: - cs = "'#880000'"; - numLoc++; - break; - case LOCF_M_PCITY: - cs = "'#00ff00'"; - numCity++; - break; - default: - switch (tmp->flags & LOCF_T_MASK) - { - case LOCF_T_SHRINE: cs = "yellow"; break; - case LOCF_T_GUILD: cs = "magenta"; break; - case LOCF_T_MONSTER: cs = "cyan"; break; - case LOCF_T_TRAINER: cs = "magenta"; break; - case LOCF_T_FORT: cs = "'#00ffff'"; break; - default: cs = "white"; break; - } - numLoc++; - break; - } - - if (tmp->flags & LOCF_CLOSED) - cs = "'#ff0000'"; - - numTotal++; - - // Location marker - fprintf(outFile, - "\t-fill black -draw \"circle %d,%d %d,%d\" ", - tmp->xc, tmp->yc, - (int) (tmp->xc + optUnitSize), (int) (tmp->yc + optUnitSize)); - - fprintf(outFile, - "-fill %s -draw \"circle %d,%d %d,%d\" ", - cs, tmp->xc, tmp->yc, - (int) (tmp->xc + optUnitSize * 0.90f), - (int) (tmp->yc + optUnitSize * 0.90f)); - - - // Location description text - if (!optNoLabels) - { - fprintf(outFile, - "-fill %s -box '#00000080' -draw \"text %d,%d '", - cs, xc, yc); - - fputsesc3(tmp->names[0].name, outFile); - fprintf(outFile, "'\" "); - } - - fprintf(outFile, " \\\n"); - } - - fprintf(outFile, - "@OPTS_END@" - "'\" \\\n"); - - fprintf(outFile, "\t\"$2\"\n"); -} - - -/* Output locations in GMaps XML format. Character set conversion - * is not performed, caller must take care of it via iconv or similar. - */ -static char *getQuestLink(const char *name, const char *desc) -{ - char *str, *tmp = th_strdup(name); - - for (size_t i = 0; i < strlen(tmp); i++) - tmp[i] = th_isspace(tmp[i]) ? '+' : th_tolower(tmp[i]); - - str = th_strdup_printf("<a target=\"_blank\" href=\"http://www.bat.org/help/quests?str=%s\">%s</a>", tmp, desc); - th_free(tmp); - return str; -} - - -static char *addQuestLink(char **buf, size_t *bufSize, size_t *bufLen, char *ptr, char *start, char *end) -{ - if (start != NULL && end != NULL) - { - char *name = th_strndup(start + 1, end - start - 1); - char *desc = th_strndup(ptr, end - ptr + 1); - char *tmp = getQuestLink(name, desc); - th_strbuf_puts(buf, bufSize, bufLen, tmp); - th_free(name); - th_free(desc); - th_free(tmp); - return end + 1; - } - else - return ptr; -} - -void outputGMapsHTML(FILE *outFile, LocMarker *tmp, - int (*fpr)(FILE *, const char *fmt, ...), int (*fps)(const char *, FILE *)) -{ - if (tmp->uri != NULL) - { - fpr(outFile, "<b><a target=\"_blank\" href=\"%s\">", tmp->uri); - locPrintType(outFile, tmp, FALSE, fps, TRUE); - fpr(outFile, "</a></b><br>"); - } - else - { - fpr(outFile, "<b>"); - locPrintType(outFile, tmp, FALSE, fps, TRUE); - fpr(outFile, "</b><br>"); - } - - // Alternative names, if any - if (tmp->nnames > 1) - { - fpr(outFile, "Also known as <i>"); - for (int n = 1; n < tmp->nnames; n++) - { - fps(tmp->names[n].name, outFile); - if (tmp->names[n].flags & NAME_ORIG) - fprintf(outFile, " (*)"); - if (n < tmp->nnames - 1) - fprintf(outFile, " ; "); - } - fpr(outFile, "</i>.<br>"); - } - - // Added to game timestamp - if (tmp->valid) - { - fpr(outFile, "Added " LOC_TIMEFMT ".<br>", - tmp->added.day, tmp->added.month, tmp->added.year); - } - - // Coder names or societies - if (tmp->ncoders > 0) - { - if (tmp->flags & LOCF_M_PCITY) - { - fprintf(outFile, "Societies: "); - for (int n = 0; n < tmp->ncoders; n++) - { - fps(tmp->coders[n].name, outFile); - if (n < tmp->ncoders - 1) - fprintf(outFile, ", "); - } - } - else - { - fprintf(outFile, "Coders: "); - for (int n = 0; n < tmp->ncoders; n++) - { - char *info = "", *sinfo = ""; - switch (tmp->coders[n].flags) - { - case NAME_ORIG: info = " (O)"; sinfo = "Original coder"; break; - case NAME_RECODER: info = " (R)"; sinfo = "Re-coder"; break; - case NAME_MAINTAINER: info = " (M)"; sinfo = "Maintainer"; break; - case NAME_EXPANDER: info = " (E)"; sinfo = "Expander"; break; - } - //fpr(outFile, "<a target=\"_blank\" href=\"http://www.bat.org/char/%s\">%s%s</a>", - fpr(outFile, "<a target=\"_blank\" href=\"https://tnsp.org/maps/loc.php?a=%s\" title=\"%s\">%s%s</a>", - tmp->coders[n].name, sinfo, - tmp->coders[n].name, info); - - if (n < tmp->ncoders - 1) - fprintf(outFile, ", "); - } - } - fps(".<br>", outFile); - } - - // Print out freeform information field - if (tmp->freeform) - { - char *ptr = tmp->freeform; - char *buf = NULL; - size_t bufLen = 0, bufSize = 0; - - while (*ptr != 0) - { - if (ptr[0] == 'A' && ptr[1] == 'Q' && th_isspace(ptr[2])) - { - char *start = strchr(ptr + 3, '"'); - ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, strchr(start + 1, '"')); - } - else - if (ptr[0] == 'L' && ptr[1] == 'Q' && th_isdigit(ptr[2])) - { - char *start = strchr(ptr + 3, '"'); - ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, start ? strchr(start + 1, '"') : NULL); - } - - if (*ptr != 0) - th_strbuf_putch(&buf, &bufSize, &bufLen, *ptr++); - } - - th_strbuf_putch(&buf, &bufSize, &bufLen, 0); - - fpr(outFile, "<br>%s<br>", buf); - th_free(buf); - } -} - - -void outputGMapsXML(FILE *outFile, MapLocations *l) -{ - fprintf(outFile, - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" - "<markers>\n"); - - // Output each location entry - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *tmp = l->locations[i]; - - // Skip disabled / invisible locations - if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; - - // Print out coordinates etc. - fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\" name=\"", - tmp->ox, tmp->oy, tmp->align); - - // Location name - locPrintType(outFile, tmp, FALSE, fputse, FALSE); - - fprintf(outFile, "\" html=\""); - - outputGMapsHTML(outFile, tmp, fprintfe, fputse); - - // Flags - fprintf(outFile, "\" flags=\"%d\"", - tmp->flags); - - // Continent name - if (tmp->file != NULL && tmp->file->continent != NULL) - { - fprintf(outFile, " continent=\""); - fputse(tmp->file->continent, outFile); - fprintf(outFile, "\""); - } - - // Type of the marker - fprintf(outFile, " type=\"%s\"/>\n", - locGetTypeName(tmp->flags)); - } - - fprintf(outFile, "</markers>\n"); -} - - -void outputGMapsJSON(FILE *outFile, MapLocations *l) -{ - fprintf(outFile, "[\n"); - - // Output each location entry - for (int i = 0; i < l->nlocations; i++) - { - LocMarker *tmp = l->locations[i]; - - // Skip disabled / invisible locations - if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; - - // Print out coordinates etc. - fprintf(outFile, "{\"x\":%d,\"y\":%d,\"labeldir\":%d,\"name\":\"", - tmp->ox, tmp->oy, tmp->align); - - // Location name - locPrintType(outFile, tmp, FALSE, fputsesc1, FALSE); - fprintf(outFile, "\",\"html\":\""); - - outputGMapsHTML(outFile, tmp, fprintfesc1, fputsesc1); - - // Flags - fprintf(outFile, "\",\"flags\":%d", - tmp->flags); - - // Continent name - if (tmp->file != NULL && tmp->file->continent != NULL) - { - fprintf(outFile, ",\"continent\":\""); - fputsesc1(tmp->file->continent, outFile); - fprintf(outFile, "\""); - } - - fprintf(outFile, "}%s\n", - (i < l->nlocations - 1) ? "," : ""); - } - - fprintf(outFile, "]\n"); -} - - -int main(int argc, char *argv[]) -{ - FILE *outFile = NULL; - MapBlock *worldMap = NULL; - MapLocations worldLoc; - - memset(&worldLoc, 0, sizeof(worldLoc)); - memset(&optLocFiles, 0, sizeof(optLocFiles)); - - // Initialize - th_init("mkloc", "Manipulate and convert location files and ASCII map data", "1.6", NULL, NULL); - th_verbosity = 0; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, NULL, OPTH_BAILOUT)) - goto exit; - - // Check the mode and arguments - if (optInFilename == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) - { - THERR("Map file required for location update mode!\n"); - goto exit; - } - - if (optOutput == OUTFMT_LOCFILE && noptLocFiles < 0 && !optGetUpdateLoc) - { - THERR("Location file or location update mode required for location file output!\n"); - goto exit; - } - - if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && noptLocFiles < 0) - { - THERR("Location file required for script or MapLoc HTML output!\n"); - goto exit; - } - - if (optInFilename == NULL && optOutput == OUTFMT_MAP) - { - THERR("Mapfile required for map generation.\n"); - goto exit; - } - - // Read initial map - if (optInFilename != NULL) - { - THMSG(2, "Reading map '%s'\n", optInFilename); - worldMap = mapBlockParseFile(optInFilename, FALSE); - if (worldMap == NULL) - { - THERR("World map could not be loaded!\n"); - goto exit; - } - - THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->width, worldMap->height); - } - - // Read location info - for (int i = 0; i <= noptLocFiles; i++) - { - LocFileInfo *f = &optLocFiles[i]; - FILE *inFile; - - if (optOutput == OUTFMT_GMAPS && f->continent == NULL) - { - THERR("Required continent name not set for #%d '%s'.\n", - i, f->filename); - goto exit; - } - - THMSG(2, "Reading location info '%s'\n", f->filename); - if ((inFile = fopen(f->filename, "rb")) == NULL) - { - THERR("Could not open location file '%s' for reading.\n", - f->filename); - goto exit; - } - - if (!locParseLocStream(inFile, f, &worldLoc, f->xoffs, f->yoffs)) - goto exit; - - fclose(inFile); - } - - // Update locations - if (optGetUpdateLoc) - updateLocations(worldMap, &worldLoc); - - // Scale locations - if (optScale > 0) - { - THMSG(1, "Scaling locations ..\n"); - - for (int i = 0; i < worldLoc.nlocations; i++) - { - LocMarker *tmp = worldLoc.locations[i]; - - tmp->xc = ((float) tmp->xc) * optScale; - tmp->yc = ((float) tmp->yc) * optScale; - } - } - - // Open output file - if (optOutFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(optOutFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - optOutFilename); - goto exit; - } - - // Output results - switch (optOutput) - { - case OUTFMT_SCRIPT: - THMSG(1, "Generating ImageMagick script ...\n"); - outputMagickScript(outFile, &worldLoc); - break; - - case OUTFMT_LOCFILE: - THMSG(1, "Outputting generated location list ...\n"); - outputLocationFile(outFile, &worldLoc); - THMSG(2, "%d locations\n", worldLoc.nlocations); - break; - - case OUTFMT_MAPLOC: - maplocSort(&worldLoc); - THMSG(1, "Outputting MapLoc HTML ...\n"); - outputMapLocHTML(outFile, &worldLoc); - THMSG(2, "%d locations\n", worldLoc.nlocations); - break; - - case OUTFMT_GMAPS: - maplocSort(&worldLoc); - THMSG(1, "Outputting GMaps data (%s) ...\n", gmapsModes[optGMapsMode]); - switch (optGMapsMode) - { - case GMAPS_XML: outputGMapsXML(outFile, &worldLoc); break; - case GMAPS_JSON: outputGMapsJSON(outFile, &worldLoc); break; - } - THMSG(2, "%d locations\n", worldLoc.nlocations); - break; - - case OUTFMT_MAP: - THMSG(1, "Outputting generated map of (%d x %d) ...\n", - worldMap->width, worldMap->height); - - THMSG(2, "Adjusting location labels ..\n"); - adjustLocInfoCoords(worldMap, &worldLoc); - - outputMapCTRL(outFile, worldMap, &worldLoc); - break; - } - -exit: - if (outFile != NULL) - fclose(outFile); - - mapBlockFree(worldMap); - locFreeMapLocations(&worldLoc); - - return 0; -}
--- a/patchmap.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,198 +0,0 @@ -/* - * Patch a map with given diff file - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2008-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_util.h" -#include "th_args.h" -#include "th_string.h" - - -char *mapFilename = NULL, - *patchFilename = NULL, - *destFilename = NULL; -BOOL optCheck = FALSE; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 2, 'q', "quiet", "Be quiet", OPT_NONE }, - { 3, 'c', "check", "Check non-patched blocks", OPT_NONE }, - { 4, 'o', "output", "Output filename", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp(void) -{ - th_print_banner(stdout, th_prog_name, - "[options] <mapfile> <patchfile>"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - th_verbosity++; - break; - - case 2: - th_verbosity = -1; - break; - - case 3: - optCheck = TRUE; - THMSG(2, "Checking non-patched blocks for match.\n"); - break; - - case 4: - destFilename = optArg; - THMSG(2, "Output file set to '%s'.\n", destFilename); - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (mapFilename == NULL) - mapFilename = currArg; - else - if (patchFilename == NULL) - patchFilename = currArg; - else - { - THERR("Too many input map files specified!\n"); - return FALSE; - } - - return TRUE; -} - - -int main(int argc, char *argv[]) -{ - MapBlock *map = NULL, *patch = NULL; - FILE *outFile = NULL; - int res = 0; - - // Initialize - th_init("patchmap", "Patch a mapfile with a diff", "0.1", NULL, NULL); - th_verbosity = 1; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - { - res = 1; - goto exit; - } - - if (mapFilename == NULL || patchFilename == NULL) - { - THERR("Nothing to do. (try --help)\n"); - res = 1; - goto exit; - } - - // Read map and patch file - if ((map = mapBlockParseFile(mapFilename, FALSE)) == NULL || - (patch = mapBlockParseFile(patchFilename, TRUE)) == NULL) - { - res = 2; - goto exit; - } - - // Check map and diff sizes - if (optCheck && (map->width != patch->width || map->height != patch->height)) - { - THERR("Map and patch dimensions do not match (%d x %d [map] <-> %d x %d [patch])\n", - map->width, map->height, - patch->width, patch->height); - - res = 2; - goto exit; - } - - // Generate - for (int yc = 0; yc < map->height; yc++) - { - unsigned char - *dp = map->data + (map->scansize * yc), - *sp = patch->data + (patch->scansize * yc); - - for (int xc = 0; xc < map->width; xc++, sp++, dp++) - { - const int i = (*sp & 63); - unsigned char ch; - - if (*sp == 0xfd) - ch = ' '; - else - if (i < nmapPieces) - ch = mapPieces[i].symbol; - else - { - THERR("[%d,%d] invalid symbol index %d (%d) in patch\n", - xc+1, yc+1, *sp, i); - res = 7; - goto exit; - } - - if (*sp < 64) - { - if (optCheck && *sp != *dp) - { - THERR("[%d,%d] mismatch %d != %d\n", - xc+1, yc+1, *sp, *dp); - } - } - else - *dp = ch; - } - } - - // Open output file - if (destFilename == NULL) - outFile = stdout; - else - if ((outFile = fopen(destFilename, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - destFilename); - res = 6; - goto exit; - } - - mapBlockPrint(outFile, map); - -exit: - if (outFile != NULL) - fclose(outFile); - - mapBlockFree(map); - mapBlockFree(patch); - - return res; -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/colormap.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,845 @@ +/* + * Convert BatMUD ASCII map to different formats + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + +#define SET_MAX_COLORS (256) +#define SET_MAX_STRLEN (1024) + + +typedef struct +{ + char *fmtName; + char *fmtDescription; + BOOL supBackColor; + void (*putTagMarker) (FILE *, const char *, const int color, const int marker); + void (*putTagLocation) (FILE *, const char *, const int color); + void (*putTagLocationEnd) (FILE *, const char *); + void (*putTagStart) (FILE *, const int color); + void (*putTagEnd) (FILE *); + BOOL (*putTagEOL) (FILE *); + void (*putFileStart) (FILE *); + void (*putFileEnd) (FILE *); + int (*putString) (const char *str, FILE *); +} CMapOutFormat; + + +char *inFilename = NULL, + *outFilename = NULL; + +char *optXHTMLTagName = NULL, + *optMapTitle = NULL, + *optUrchinFile = NULL; + +BOOL optPerformAnalysis = FALSE, + optUseOldFormat = FALSE, + optCheatMode = FALSE, + optNoHeaders = FALSE, + optPosGlue = FALSE, + optCityFormat = FALSE; + +int optOutputFormat = 0, + optBackColor = 0; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'q', "quiet", "Be quiet", OPT_NONE }, + { 3, 'T', "html-tag", "XHTML tag used for map", OPT_ARGREQ }, + { 4, 'a', "analysis", "Perform statistical analysis", OPT_NONE }, + { 6, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, + { 7, 'o', "output", "Output filename", OPT_ARGREQ }, + { 8, 'f', "format", "Specify output format", OPT_ARGREQ }, + { 9, 't', "title", "Map title", OPT_ARGREQ }, + { 10,'C', "cheat-mode", "Use cheating in HTML", OPT_NONE }, + { 11,'n', "no-headers", "Do not output headers/footers", OPT_NONE }, + { 12,'P', "pos-glue", "Generate JavaScript, etc. for posglue", OPT_NONE }, + { 13,'c', "city-format", "Input is a city map", OPT_NONE }, + { 14,'u', "urchin-file", "Specify urchin file", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +/* + * ANSI output format functions + */ +void putTagStartANSI(FILE *outFile, const int c) +{ + fputc(0x1b, outFile); + + if (c < 0) + { + fprintf(outFile, "[0;%d;30m", + 10 + mapColors[-c-1].ansi); + } + else + { + fprintf(outFile, "[0;%d;%dm", + mapColors[c].ansiAttr, + mapColors[c].ansi); + } +} + +void putTagEndANSI(FILE *outFile) +{ + (void) outFile; + /* + fputc(0x1b, outFile); + fprintf(outFile, "[0m"); + */ +} + +BOOL putEOLANSI(FILE *outFile) +{ + fprintf(outFile, "\n"); + return TRUE; +} + +void putFileStartANSI(FILE *outFile) +{ + fputc(0x1b, outFile); + fprintf(outFile, "[0m"); + + if (optMapTitle) + { + fprintf(outFile, "%s\n", optMapTitle); + } +} + + +void putFileEndANSI(FILE *outFile) +{ + fputc(0x1b, outFile); + fprintf(outFile, "[0m"); +} + + +/* + * ASCII output format functions + */ +void putTagStartText(FILE *outFile, const int c) +{ + (void) outFile; (void) c; +// fprintf(outFile, "§"); +} + +void putTagEndText(FILE *outFile) +{ + (void) outFile; +// fprintf(outFile, "$"); +} + +BOOL putEOLText(FILE *outFile) +{ + fprintf(outFile, "\n"); + return FALSE; +} + +void putFileStartText(FILE *outFile) +{ + if (optMapTitle) + { + fprintf(outFile, "%s\n", optMapTitle); + } +} + + +/* + * XHTML+CSS output format functions + */ +#define XHTML_LOCLABEL "label" +#define XHTML_LOCMARKER "marker" + +void putColorClassXHTML(FILE *outFile, const int color) +{ + int q = 'a' + color; + + if (optCheatMode) + fprintf(outFile, "class=%c>", q); + else + fprintf(outFile, "class=\"%c\">", q); +} + +void putTagMarkerXHTML(FILE *outFile, const char *locID, const int color, const int marker) +{ + fprintf(outFile, + "<i class=\"" XHTML_LOCMARKER " %c\" id=\"%s\">%c", + 'a' + color, locID, marker); +} + +void putTagLocationXHTML(FILE *outFile, const char *locID, const int color) +{ + fprintf(outFile, + "<i class=\"" XHTML_LOCLABEL "\"><a id=\"%s\" ", + locID); + putColorClassXHTML(outFile, color); +} + +void putTagLocationEndXHTML(FILE *outFile, const char *locID) +{ + (void) locID; + fprintf(outFile, "</a>"); +} + +void putTagStartXHTML(FILE *outFile, const int c) +{ + fprintf(outFile, "<i "); + putColorClassXHTML(outFile, c); +} + +void putTagEndXHTML(FILE *outFile) +{ + fprintf(outFile, "</i>"); +} + +BOOL putEOLXHTML(FILE *outFile) +{ + fprintf(outFile, "\n"); + return FALSE; +} + +void putFileGenericHTML(FILE *outFile) +{ + char buf[32]; + + fprintf(outFile, + " <script type=\"text/javascript\" src=\"util.js\"></script>\n" + " <style type=\"text/css\">\n" + " <!--\n" + " %s." XHTML_LOCLABEL " { background: white; color: black; %s}\n" + " body { background: black; color: %s; font-family: Arial, Verdana, sans-serif; }\n" + " h2 { color: white; font-size: 14pt; }\n" + " pre { font-size: 10pt; }\n" + " %s { text-decoration: none; font-style: normal; }\n", + optXHTMLTagName, (optPosGlue) ? "position: relative; " : "", + muColorToCSSColor(buf, sizeof(buf), optBackColor), + optXHTMLTagName + ); + + muPrintHTMLcolors(outFile, optXHTMLTagName, NULL, NULL); + muPrintHTMLcolors(outFile, "a", "background", " color: black;"); + + if (optPosGlue) + { + fprintf(outFile, + " div.sgbox {\n" + " background: #bbb;\n" + " color: black;\n" + " position: fixed;\n" + " top: 0.5em;\n" + " right: 0.5em;\n" + " width: 25em;\n" + " height: 6em;\n" + " margin: 4px;\n" + " padding: 4px;\n" + " z-index: 10;\n" + " border: 2px solid gray;\n" + " font-size: 10pt;\n" + " }\n" + " i." XHTML_LOCLABEL " a {\n" + " position: absolute;\n" + " top: 0px;\n" + " left: 0px;\n" + " z-index: 2;\n" + " border: 2px solid black;\n" + " border-radius: 0.35em;\n" + " padding: 0.15em;\n" + " box-shadow: 2px 2px 4px black;\n" + " }\n" + " div.sbtitle {\n" + " font-size: 1.5em;\n" + " font-weight: bold;\n" + " }\n" + " i.label a.nactive {\n" + " border: 2px solid rgba(100,0,0, 0.5);\n" + " background: rgba(255,0,0, 0.7);\n" + " color: rgba(255,255,255, 0.8);\n" + " text-shadow: 1px 1px 1px #000;\n" + " font-weight: bold;\n" + " transform: scale(1.1);\n" + " }\n" + " i.marker.nactive {\n" + " animation: pulse 1s ease infinite;\n" + " }\n" + " @keyframes pulse {\n" + " 0%% { background: #000; }\n" + " 50%% { background: #f00; }\n" + " 100%% { background: #000; }\n" + " }\n" + ); + } + + fprintf(outFile, + " -->\n" + " </style>\n" + ); + + if (optUrchinFile) + { + if (muCopyFileToStream(outFile, optUrchinFile) < 0) + { + THERR("Error copying urchin file '%s' to output!\n", optUrchinFile); + exit(17); + } + } + + fprintf(outFile, + "</head>\n" + "<body onLoad=\"mapOnLoad();\">\n"); + + if (!optPosGlue) + { + if (optMapTitle) + { + fprintf(outFile, "<h2>"); + fputse(optMapTitle, outFile); + fprintf(outFile, "</h2>\n"); + } + } + else + { + fprintf(outFile, + "<div id=\"sbox\" class=\"sgbox\">\n" + " <div class=\"sbtitle\">"); + fputse(optMapTitle, outFile); + fprintf(outFile, + "</div>\n" + " <form>\n" + " <select id=\"slocation\" onChange=\"mapGotoPos();\" autofocus=\"autofocus\">\n" + "@LOCATIONS@\n" + " </select>\n" + " <br />\n" + " <input id=\"shide\" onClick=\"mapToggleLabels();\" type=\"checkbox\"%s><label for=\"shide\">Labels</label>\n" + " <input id=\"sscroll\" type=\"checkbox\"%s><label for=\"sscroll\">Smooth scroll</label>\n" + " </form>\n" + "</div>\n", + (1 ? " checked=\"checked\"" : ""), + (0 ? " checked=\"checked\"" : "") + ); + } + + fprintf(outFile, + "<pre>\n" + ); +} + +void putFileStartXHTML(FILE *outFile) +{ + muPrintHTMLhead(outFile, optMapTitle, FALSE); + putFileGenericHTML(outFile); +} + +void putFileEndXHTML(FILE *outFile) +{ + // XHTML document end tags + fprintf(outFile, + "</pre>\n" + "</body>\n" + "</html>\n" + ); +} + + +/* + * XHTML+CSS output format functions + */ +void putFileStartHTML5(FILE *outFile) +{ + muPrintHTMLhead(outFile, optMapTitle, TRUE); + putFileGenericHTML(outFile); +} + + + +/* Process a normal format input + */ +void checkEndTag(FILE *outFile, const CMapOutFormat *fmt, const int prevColor) +{ + if (prevColor != -1 && (!fmt->supBackColor || prevColor != optBackColor)) + fmt->putTagEnd(outFile); +} + + +BOOL getTagStr(FILE *inFile, char *tmpStr, const size_t len, const int endch) +{ + size_t i; + int mch = EOF; + + for (i = 0; i + 1 < len && (mch = fgetc(inFile)) != EOF;) + { + if (mch == endch) + break; + else + tmpStr[i++] = mch; + } + + tmpStr[i] = 0; + + if (mch == EOF) + { + THERR("Unexpected end of file.\n"); + return FALSE; + } + else + if (mch != endch) + { + THERR("No end tag 0x%02x found.\n", endch); + return FALSE; + } + + return TRUE; +} + + +BOOL processData(FILE *inFile, FILE *outFile, const CMapOutFormat *fmt) +{ + int k, currColor, prevColor; + + currColor = prevColor = -1; + while ((k = fgetc(inFile)) != EOF) + { + if (k == '\n') + { + if (fmt->putTagEOL(outFile)) + currColor = -1; + } + else + if (k == 0xfb) + { + char tmpStr[SET_MAX_STRLEN]; + int mch; + + // Location marker mode + checkEndTag(outFile, fmt, prevColor); + + // Get location marker tag + if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) + return FALSE; + + // Get marker character + mch = fgetc(inFile); + k = fgetc(inFile); + + if (k != 0xfe) + { + THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); + return FALSE; + } + + currColor = muGetMapPieceColor(mch, optUseOldFormat, optCityFormat); + if (fmt->putTagMarker != NULL) + fmt->putTagMarker(outFile, tmpStr, currColor, mch); + else + { + if (fmt->putTagStart) + fmt->putTagStart(outFile, currColor); + + fprintf(outFile, "%c", mch); + } + + currColor = prevColor = -2; + } + else + if (k == 0xff) + { + char tmpStr[SET_MAX_STRLEN], tmpStr2[SET_MAX_STRLEN]; + int mcol; + + // Location title mode + checkEndTag(outFile, fmt, prevColor); + + // Get location marker tag + if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) + return FALSE; + + // Get color + mcol = fgetc(inFile); + if (mcol == EOF) + { + THERR("Unexpected end of file (location tag colour).\n"); + return FALSE; + } + + // Get location name + if (!getTagStr(inFile, tmpStr2, SET_MAX_STRLEN, 0xfe)) + { + THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); + return FALSE; + } + + if (!fmt->putTagLocation && fmt->putTagStart) + fmt->putTagStart(outFile, -mcol - 1); + + if (fmt->putTagLocation) + fmt->putTagLocation(outFile, tmpStr, mcol); + + if (fmt->putString) + fmt->putString(tmpStr2, outFile); + else + fputs(tmpStr2, outFile); + + if (fmt->putTagLocationEnd) + fmt->putTagLocationEnd(outFile, tmpStr); + + currColor = prevColor = -2; + } + else + { + currColor = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); + if (currColor != prevColor) + { + checkEndTag(outFile, fmt, prevColor); + + if ((!fmt->supBackColor || currColor != optBackColor) && fmt->putTagStart) + fmt->putTagStart(outFile, currColor); + + fprintf(outFile, "%c", k); + } + else + { + fprintf(outFile, "%c", k); + } + } + + prevColor = currColor; + } + + checkEndTag(outFile, fmt, prevColor); + + return TRUE; +} + + +/* Get a symbol + */ +char getSymbol(int i, BOOL useOld) +{ + if (useOld) + return mapPieces[i].oldSymbol; + else + return mapPieces[i].symbol; +} + + +/* List of output formats + */ +CMapOutFormat outputFormats[] = +{ + { "xhtml", "XHTML+CSS", TRUE, + putTagMarkerXHTML, + putTagLocationXHTML, putTagLocationEndXHTML, + putTagStartXHTML, putTagEndXHTML, putEOLXHTML, + putFileStartXHTML, putFileEndXHTML, fputse + }, + + { "html5", "HTML5+CSS", TRUE, + putTagMarkerXHTML, + putTagLocationXHTML, putTagLocationEndXHTML, + putTagStartXHTML, putTagEndXHTML, putEOLXHTML, + putFileStartHTML5, putFileEndXHTML, fputse + }, + + { "ansi", "ANSI text", FALSE, + NULL, NULL, NULL, + putTagStartANSI, putTagEndANSI, putEOLANSI, + putFileStartANSI, putFileEndANSI, NULL + }, + + { "text", "Plain ASCII text", FALSE, + NULL, NULL, NULL, + putTagStartText, putTagEndText, putEOLText, + putFileStartText, NULL, NULL + }, +}; + +const int noutputFormats = sizeof(outputFormats) / sizeof(outputFormats[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <input mapfile>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); + + fprintf(stdout, "\nAvailable OUTPUT formats:\n"); + for (int i = 0; i < noutputFormats; i++) + { + fprintf(stdout, " %-8s - %s %s\n", + outputFormats[i].fmtName, + outputFormats[i].fmtDescription, + (i == optOutputFormat) ? "(default)" : "" + ); + } + + fprintf(stdout, "\n"); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + th_verbosity = -1; + break; + + case 3: + { + char *tmp = th_strdup_trim(optArg, TH_TRIM_BOTH); + if (tmp == NULL) + { + THERR("Can't set HTML tag to empty string.\n"); + return FALSE; + } + + th_free(optXHTMLTagName); + optXHTMLTagName = tmp; + THMSG(2, "HTML tag set to '%s'.\n", optXHTMLTagName); + } + break; + + case 4: + optPerformAnalysis = TRUE; + THMSG(2, "Analysis enabled.\n"); + break; + + case 6: + optUseOldFormat = TRUE; + THMSG(2, "Input is using old map symbols/colors.\n"); + break; + + case 7: + outFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", outFilename); + break; + + case 8: + { + int i, n; + // Match format from the list + for (i = 0, n = -1; i < noutputFormats && n < 0; i++) + if (th_strcasecmp(optArg, outputFormats[i].fmtName) == 0) + n = i; + + if (n < 0) + { + THERR("Invalid output format '%s'.\n", optArg); + return FALSE; + } + + optOutputFormat = n; + THMSG(2, "Output format set to '%s'.\n", optArg); + } + break; + + case 9: + optMapTitle = optArg; + THMSG(2, "Map title string set to '%s'.\n", optMapTitle); + break; + + case 10: + optCheatMode = TRUE; + THMSG(2, "HTML cheats mode enabled!\n"); + break; + + case 11: + optNoHeaders = TRUE; + THMSG(2, "Not outputting headers/footers\n"); + break; + + case 12: + optPosGlue = TRUE; + THMSG(2, "Generating JavaScript junk for positioning glue\n"); + break; + + case 13: + optCityFormat = TRUE; + THMSG(2, "Input is handled as a city map\n"); + break; + + case 14: + optUrchinFile = optArg; + THMSG(2, "Urchin filename set to '%s'.\n", optUrchinFile); + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (!inFilename) + inFilename = currArg; + else + { + THERR("Too many input map files specified!\n"); + return FALSE; + } + + return TRUE; +} + + +/* Main program + */ +int main(int argc, char *argv[]) +{ + FILE *inFile = NULL, *outFile = NULL; + CMapOutFormat *fmt = NULL; + + // Initialize + th_init("colormap", "ASCII map colorizer", "0.5", NULL, NULL); + th_verbosity = 0; + optXHTMLTagName = th_strdup("i"); + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + exit(1); + + if (inFilename == NULL) + { + THERR("Nothing to do. (try --help)\n"); + exit(0); + } + + // Do statistical analysis, if needed + if (optPerformAnalysis) + { + int k, c, p, i, tMax, fMax, tn, fn; + int colChangesTo[SET_MAX_COLORS]; + int colChangesFrom[SET_MAX_COLORS]; + + THMSG(1, "Performing statistical frequency analysis ...\n"); + + THMSG(2, "Reading '%s' for analysis data ...\n", + inFilename); + + if ((inFile = fopen(inFilename, "rb")) == NULL) + { + THERR("Error opening input file '%s'!\n", + inFilename); + exit(1); + } + + // Initialize counters + memset(colChangesTo, 0, sizeof(colChangesTo)); + memset(colChangesFrom, 0, sizeof(colChangesFrom)); + + // Read data, keeping statistics of colour change frequencies + c = p = -1; + while ((k = fgetc(inFile)) != EOF) + { + if (k != '\n' && k != ' ' && k < 0xfb) + { + c = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); + if (c != p && c >= 0 && c < SET_MAX_COLORS) + { + colChangesTo[c]++; + + if (p >= 0 && p < SET_MAX_COLORS) + colChangesFrom[p]++; + } + } + p = c; + } + + fclose(inFile); + + // Find highest frequency + THMSG(2, "Computing results.\n"); + + tMax = fMax = tn = fn = -1; + for (i = 0; i < SET_MAX_COLORS; i++) + { + if (colChangesTo[i] > tMax) + { + tMax = colChangesTo[i]; + tn = i; + } + if (colChangesFrom[i] > fMax) + { + fMax = colChangesFrom[i]; + fn = i; + } + } + + THMSG(2, "tMax=%d, tn=%d -- fMax=%d, fn=%d\n", + tMax, tn, fMax, fn); + + if (tMax > fMax) + optBackColor = tn; + else + optBackColor = fn; + + THMSG(1, "Using colour %d as 'background' color.\n", + optBackColor); + } + + // Open input file + if ((inFile = fopen(inFilename, "rb")) == NULL) + { + THERR("Error opening input file '%s'!\n", + inFilename); + goto out; + } + + // Open output file + if (outFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(outFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + outFilename); + goto out; + } + + // Okay, let's process the shit + fmt = &outputFormats[optOutputFormat]; + THMSG(1, "Converting to '%s' ...\n", fmt->fmtName); + + if (!optNoHeaders && fmt->putFileStart) + fmt->putFileStart(outFile); + + processData(inFile, outFile, fmt); + + if (!optNoHeaders && fmt->putFileEnd) + fmt->putFileEnd(outFile); + +out: + if (outFile != NULL) + fclose(outFile); + + if (inFile != NULL) + fclose(inFile); + + THMSG(1, "Done.\n"); + + exit(0); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/combine.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,379 @@ +/* + * Combine several maps into one bigger + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + + +#define SET_MAX_FILES (256) + + +typedef struct +{ + char *filename; + int xc, yc; + MapBlock *blk; +} MapFile; + + +/* Variables + */ +int nsrcFiles = 0; +MapFile srcFiles[SET_MAX_FILES]; +char *dstFile = NULL; +int optFillChar = -1; +BOOL optInputIsDiff = FALSE; +int optWorldMinW = 0, + optWorldMinH = 0; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'o', "output", "Specify output file", OPT_ARGREQ }, + { 3, 'q', "quiet", "Be quiet", OPT_NONE }, + { 5, 'd', "diff", "Map files are in 'diff' format", OPT_NONE }, + { 4, 'f', "fill", "Fill character", OPT_ARGREQ }, + { 6, 'w', "width", "Minimum width", OPT_ARGREQ }, + { 7, 'h', "height", "Minimum height", OPT_ARGREQ }, +}; + +const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp() +{ + th_print_banner(stdout, th_prog_name, + "[options] <mapfile1:x-offset:y-offset> [<mapfile2:x:y> ...]"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + dstFile = optArg; + THMSG(1, "Output file '%s'\n", dstFile); + break; + + case 3: + th_verbosity = -1; + break; + + case 4: + if (optArg[1] != 0) + { + THERR("Fill character is not a string, dumbass!\n"); + return FALSE; + } + + optFillChar = optArg[0]; + THMSG(1, "Using fill character '%c'\n", optFillChar); + break; + + case 5: + THMSG(1, "Assuming all input files are diffs.\n"); + optInputIsDiff = TRUE; + break; + + case 6: + case 7: + { + char *s; + int v = atoi(optArg); + if (optN == 6) + { + s = "width"; + optWorldMinW = v; + } + else + { + s = "height"; + optWorldMinH = v; + } + + if (v < 0 || v > 128 * 1024) + { + THERR("Invalid %s setting %d!\n", s, v); + return FALSE; + } + + THMSG(1, "Using world minimum %s %d\n", s, v); + } + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + break; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + char *tmpArg = NULL; + + if (nsrcFiles < SET_MAX_FILES) + { + char *q, *c; + MapFile *file = &srcFiles[nsrcFiles++]; + + if ((tmpArg = th_strdup(currArg)) == NULL) + { + THERR("Could not allocate temp buffer!\n"); + goto err; + } + + // Get filename component + for (q = c = tmpArg; *c && (*c != ':'); c++); + if (*c != ':') + { + THERR("Expected ':' after filename in '%s'.\n", + currArg); + goto err; + } + + *(c++) = 0; + file->filename = th_strdup(q); + + // Get X offset + if (!th_isdigit(*c) && *c != '-') + { + THERR("Expected decimal X offset value in '%s'.\n", currArg); + goto err; + } + + q = c; if (*c == '-') c++; + while (*c && th_isdigit(*c)) c++; + if (*c != ':') + { + THERR("Expected ':' after X offset value in '%s'.\n", currArg); + goto err; + } + + *(c++) = 0; + file->xc = atoi(q); + + // Get Y offset + if (!th_isdigit(*c) && *c != '-') + { + THERR("Expected decimal Y offset value in '%s'.\n", currArg); + goto err; + } + + q = c; + if (*c == '-') c++; + + while (*c && th_isdigit(*c)) c++; + if (*c != 0) + { + THERR("Invalid Y offset value in '%s'.\n", currArg); + goto err; + } + + file->yc = atoi(q); + } + else + { + THERR("Too many input files specified (%d max)!\n", SET_MAX_FILES); + goto err; + } + + th_free(tmpArg); + return TRUE; + +err: + th_free(tmpArg); + return FALSE; +} + + +void findWorldSize(MapBlock *tmp, + int *worldX0, int *worldY0, + int *worldX1, int *worldY1) +{ + if (tmp->xc < *worldX0) + *worldX0 = tmp->xc; + if (tmp->xc + tmp->width > *worldX1) + *worldX1 = tmp->xc + tmp->width; + + if (tmp->yc < *worldY0) + *worldY0 = tmp->yc; + if (tmp->yc + tmp->height > *worldY1) + *worldY1 = tmp->yc + tmp->height; +} + + +int main(int argc, char *argv[]) +{ + int i, worldX0, worldY0, worldX1, worldY1, worldW, worldH; + MapBlock *worldMap = NULL; + FILE *tmpFile = NULL; + + th_init("combine", "Combine several maps into one", "0.1", NULL, NULL); + th_verbosity = 0; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + goto exit; + + if (nsrcFiles < 1) + { + THERR("Nothing to do. (try --help)\n"); + goto exit; + } + + + /* Read map files + */ + for (i = 0; i < nsrcFiles; i++) + { + MapFile *file = &srcFiles[i]; + if ((file->blk = mapBlockParseFile(file->filename, optInputIsDiff)) == NULL) + { + THERR("Error reading input file '%s'!\n", + file->filename); + goto exit; + } + + mapBlockClean(file->blk, (unsigned char *) " ", 1); + file->blk->xc = file->xc; + file->blk->yc = file->yc; + } + + THMSG(2, "Total of %d maps read.\n", nsrcFiles); + + if (nsrcFiles <= 0) + { + THERR("No maps, nothing to do.\n"); + goto exit; + } + + + // Get world dimensions + worldX0 = worldY0 = worldX1 = worldY1 = 0; + for (i = 0; i < nsrcFiles; i++) + findWorldSize(srcFiles[i].blk, &worldX0, &worldY0, &worldX1, &worldY1); + + worldW = worldX1 - worldX0 + 1; + worldH = worldY1 - worldY0 + 1; + + THMSG(2, "Initial dimensions <%d, %d> - <%d, %d> (%d x %d)\n", + worldX0, worldY0, worldX1, worldY1, worldW, worldH); + + // Adjust to origo <0, 0> + worldX0 = -worldX0; + worldY0 = -worldY0; + worldX1 = worldX0 + worldW - 1; + worldY1 = worldY0 + worldH - 1; + + THMSG(2, "Adjusted dimensions <%d, %d> - <%d, %d> (%d x %d)\n", + worldX0, worldY0, worldX1, worldY1, worldW, worldH); + + // Adjust for requested minimum dimensions + if (worldW < optWorldMinW) + { + int tmp = (optWorldMinW - worldW) / 2; + worldX0 += tmp; + worldX1 += tmp; + worldW = optWorldMinW; + } + if (worldH < optWorldMinH) + { + int tmp = (optWorldMinH - worldH) / 2; + worldY0 += tmp; + worldY1 += tmp; + worldH = optWorldMinH; + } + + // Allocate world map + THMSG(1, "Initializing world map of <%d, %d> - <%d, %d> (%d x %d)\n", + worldX0, worldY0, worldX1, worldY1, + worldW, worldH); + + if ((worldMap = mapBlockAlloc(worldW, worldH)) == NULL) + { + THERR("Error allocating world map!\n"); + goto exit; + } + + + /* Clear with some character, if requested + */ + if (optFillChar > 0) + { + memset(worldMap->data, optFillChar, worldMap->size); + } + + + /* Blit map blocks + */ + for (i = 0; i < nsrcFiles; i++) + { + MapFile *file = &srcFiles[i]; + if (mapBlockPutDo(worldMap, file->blk, worldX0 + file->blk->xc, worldY0 + file->blk->yc) != 0) + { + THERR("Mapblock #%d ['%s' @ %d,%d] placement failed!\n", + i, file->filename, file->blk->xc, file->blk->yc); + goto exit; + } + } + + + /* Output generated map + */ + if (worldMap != NULL) + { + + THMSG(1, "Outputting generated map of (%d x %d) ...\n", + worldMap->width, worldMap->height); + + if (dstFile == NULL) + tmpFile = stdout; + else + if ((tmpFile = fopen(dstFile, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", dstFile); + goto exit; + } + + if (optInputIsDiff) + mapBlockPrintRaw(tmpFile, worldMap); + else + mapBlockPrint(tmpFile, worldMap); + } + else + { + THERR("No map generated?\n"); + } + +exit: + mapBlockFree(worldMap); + + if (tmpFile != NULL) + fclose(tmpFile); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/diffmap.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,267 @@ +/* + * Compute 'diff' between two maps + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_util.h" +#include "th_args.h" +#include "th_string.h" + + +#define SET_DEF_FILTER "C?%" + + +/* Variables + */ +char *srcFilename1 = NULL, + *srcFilename2 = NULL, + *destFilename = NULL; +BOOL optUseOldFormat = FALSE, + optAlwaysDiff = FALSE; +char *optFilter1 = "", + *optFilter2 = SET_DEF_FILTER; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'q', "quiet", "Be quiet", OPT_NONE }, + { 3, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, + { 4, 'o', "output", "Output filename", OPT_ARGREQ }, + { 5, 'F', NULL, "Filter chars (none) for mapfile #1", OPT_ARGREQ }, + { 6, 'f', NULL, "Filter chars (" SET_DEF_FILTER ") for mapfile #2", OPT_ARGREQ }, + { 7, 'a', "always", "Always output diff file even when no changes", OPT_NONE }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <mapfile #1> <mapfile #2>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + th_verbosity = -1; + break; + + case 3: + optUseOldFormat = TRUE; + THMSG(2, "Input is using old map symbols/colors.\n"); + break; + + case 4: + destFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", optArg); + break; + + case 5: + optFilter1 = optArg; + THMSG(2, "Filter characters for mapfile #1 set to'%s'.\n", optArg); + break; + + case 6: + optFilter2 = optArg; + THMSG(2, "Filter characters for mapfile #2 set to'%s'.\n", optArg); + break; + + case 7: + optAlwaysDiff = TRUE; + THMSG(2, "Outputting diff file even with no changes.\n"); + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (srcFilename1 == NULL) + srcFilename1 = currArg; + else + if (srcFilename2 == NULL) + srcFilename2 = currArg; + else + { + THERR("Too many input map files specified!\n"); + return FALSE; + } + + return TRUE; +} + + +MapBlock * diffBlocks(const MapBlock *map1, const MapBlock *map2, + const BOOL useOld, const char *filter1, const char *filter2, + size_t *count) +{ + MapBlock *res; + size_t + nfilter1 = strlen(filter1), + nfilter2 = strlen(filter2); + + if (map1->width != map2->width || map1->height != map2->height) + { + THERR("Mapblock size mismatch (%d, %d) vs (%d, %d)\n", + map1->width, map1->height, + map2->width, map2->height); + return NULL; + } + + if ((res = mapBlockAlloc(map1->width, map1->height)) == NULL) + { + THERR("Could not allocate mapblock (%d, %d)\n", + map1->width, map1->height); + return NULL; + } + + *count = 0; + for (int y = 0; y < map1->height; y++) + { + unsigned char + *p1 = map1->data + (map1->scansize * y), + *p2 = map2->data + (map2->scansize * y), + *pd = res->data + (res->scansize * y); + + for (int x = 0; x < map1->width; x++) + { + int c; + + if (*p1 != *p2 && + !muStrChr((unsigned char *) filter1, nfilter1, *p1) && + !muStrChr((unsigned char *) filter2, nfilter2, *p2)) + { + c = muGetMapPieceIndex(*p2, useOld, FALSE); + *pd = (c < 0) ? 0xfd : (c | 64); + THMSG(2, "[%d,%d]: %d -> %d\n", x+1, y+1, *p1, *p2); + (*count)++; + } + else + { + c = muGetMapPieceIndex(*p1, useOld, FALSE); + *pd = (c < 0) ? 0xfd : c; + } + + p1++; + p2++; + pd++; + } + } + + return res; +} + + +int main(int argc, char *argv[]) +{ + MapBlock + *srcMap1 = NULL, + *srcMap2 = NULL, + *resMap = NULL; + FILE *outFile = NULL; + size_t count; + int res = 0; + + // Initialize + th_init("diffmap", "Create a diff between two ASCII mapfiles", "0.5", NULL, NULL); + th_verbosity = 1; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, TRUE)) + { + res = 1; + goto exit; + } + + if (srcFilename1 == NULL || srcFilename2 == NULL) + { + THERR("Nothing to do. (try --help)\n"); + res = 1; + goto exit; + } + + // Read mapfiles + if ((srcMap1 = mapBlockParseFile(srcFilename1, FALSE)) == NULL || + (srcMap2 = mapBlockParseFile(srcFilename2, FALSE)) == NULL) + { + res = 2; + goto exit; + } + + // Compute and output data + count = 0; + if ((resMap = diffBlocks(srcMap1, srcMap2, + optUseOldFormat, optFilter1, optFilter2, &count)) == NULL) + { + THERR("Could not create diff between inputs!\n"); + res = 3; + goto exit; + } + THMSG(1, "%" PRIu_SIZE_T " differences.\n", count); + + // Open output file + res = count > 0 ? 0 : 4; + if (optAlwaysDiff || count > 0) + { + THMSG(2, "Outputting map diff %d x %d...\n", + resMap->width, resMap->height); + + if (destFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(destFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + destFilename); + res = 6; + goto exit; + } + + fprintf(outFile, DIFF_MAGIC "%d\n", DIFF_VERSION); + mapBlockPrintRaw(outFile, resMap); + + } + else + { + THMSG(2, "Not outputting diff as no changes were found.\n"); + goto exit; + } + + +exit: + if (outFile != NULL) + fclose(outFile); + + mapBlockFree(srcMap1); + mapBlockFree(srcMap2); + mapBlockFree(resMap); + + return res; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/liblocfile.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,832 @@ +/* + * liblocfile - Location file format handling + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 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; + + +static void locAddName(LocName *dst, int *ndst, 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; + case '!': n++; flags |= NAME_RECODER; break; + case '%': n++; flags |= NAME_MAINTAINER; break; + case '&': n++; flags |= NAME_EXPANDER; break; + } + dst[*ndst].name = th_strdup(&(src[nsrc].name[n])); + dst[*ndst].flags = flags; + (*ndst)++; + } +} + + +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->ncoders; i++) + res->coders[i].name = th_strdup(src->coders[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 *coders, 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++) + { + locAddName(tmp->names, &tmp->nnames, names, i); + locAddName(tmp->coders, &tmp->ncoders, coders, 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->coders[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: // Coders + locParseMultiField(ctx, ",;", ',', "coder names", marker->coders); + 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->coders, &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); +} + + +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"; +} + + +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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/liblocfile.h Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,163 @@ +/* + * liblocfile - Location file format handling + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#ifndef LIBLOCFILE_H +#define LIBLOCFILE_H + +#include "libutil.h" +#include <time.h> +#include <stdio.h> + + +/* Version string + */ +#define LOC_MAGIC "MapUtils LOC file" +#define LOC_VERSION_MAJOR (4) +#define LOC_VERSION_MINOR (0) + + +/* LOCD_* is an enum describing the preferred orientation of the + * location marker _label_ in relation to the marker spot. + */ +enum { + LOCD_NONE = 0, + LOCD_LEFT, + LOCD_LEFTDOWN, + LOCD_DOWN +}; + + +/* Timestamp used in loc-files, in sscanf() format. + */ +#define LOC_TIMEFMT "%02d.%02d.%04d" + + +/* These flags are used to describe the marker/location type and label + * related information: [LOCF_M_*] Draw marker of specified type (by default, + * no marker is drawn). LOCF_M_CITY and LOCF_M_PCITY are special, they also + * work as aliases to LOCF_T_CITY/PCITY and thus have similar functionality. + * + * [LOCF_T_*] specify the type of the location/marker. Currently the effect + * of this settings is addition of "GUILD", "SHRINE"-type strings to labels, + * but this information can be used in other ways too. + */ +#define LOCF_NONE (0x00000) + +// Marker types +#define LOCF_M_SCENIC1 (0x000001) // '?' Scenic marker +#define LOCF_M_SCENIC2 (0x000002) // '%' Shrine marker/etc +#define LOCF_M_PCITY (0x000004) // 'C' Player city +#define LOCF_M_CITY (0x000008) // 'c' City +#define LOCF_M_MASK (0x00000F) + +// Location types +#define LOCF_T_SHRINE (0x000010) // 'S' Raceshrine +#define LOCF_T_GUILD (0x000020) // 'G' Guild +#define LOCF_T_SS (0x000040) // 'P' Player guild/Secret Society +#define LOCF_T_MONSTER (0x000080) // 'M' Special monster +#define LOCF_T_TRAINER (0x000100) // 'T' Guild trainer +#define LOCF_T_FORT (0x000200) // 'F' Regions fort +#define LOCF_T_MASK (0x00FFF0) +#define LOCF_MASK (LOCF_M_MASK | LOCF_T_MASK) + +// Extra flags +#define LOCF_INVIS (0x010000) // '-' Invisible marker / Don't show label +#define LOCF_CLOSED (0x020000) // '!' Area is CLOSED +#define LOCF_INSTANCED (0x040000) // 'I' Location is "instanced" for each player +#define LOCF_INVALID (0x400000) // Possibly invalid location +#define LOCF_NOMARKER (0x800000) // Location has no marker in mapdata or explicitly defined +#define LOCF_Q_MASK (0xFF0000) + + +/* Misc constants + */ +#define LOC_MAX_NAMES (64) // Probably more than enough? +#define LOC_MARKERS "?%C" +#define LOC_MAX_FILES (64) + + +#define NAME_ORIG (0x00001) // '@' Original area name or coder +#define NAME_RECODER (0x00002) // '!' Converter or recoder of area +#define NAME_MAINTAINER (0x00004) // '%' Maintainer +#define NAME_EXPANDER (0x00008) // '&' Expander, adding new things + + +/* Structures + */ +typedef struct +{ + int day, month, year; +} LocDateStruct; + + +typedef struct +{ + char *filename; + char *continent; + int xoffs, yoffs; +} LocFileInfo; + + +typedef struct +{ + char *name; + int flags; +} LocName; + + +typedef struct +{ + LocFileInfo *file; // Reference to file/continent data + + int xc, yc, ox, oy; // Location coordinates + int align; // Label alignment value + int flags; // Flags (see LOCF_*) + + LocDateStruct added; // Date / time information + BOOL valid; + + int nnames, ncoders; + LocName names[LOC_MAX_NAMES], coders[LOC_MAX_NAMES]; + + char *uri, *freeform; + + union + { + int v_int; + float v_float; + } vsort; +} LocMarker; + + +typedef struct +{ + int nlocations; + LocMarker **locations; +} MapLocations; + + +/* Location file parsing and data handling + */ +BOOL locAddNew(MapLocations *l, int xc, int yc, int dir, int flags, + LocName *names, LocName *coders, LocDateStruct *added, BOOL valid, + const char *uri, const char *freeform, LocFileInfo *file); + +BOOL locParseLocStream(FILE *fp, LocFileInfo *file, MapLocations *l, const int offX, const int offY); +void locFreeMarkerData(LocMarker *marker); +void locFreeMapLocations(MapLocations *loc); + +int locFindByCoords(const MapLocations *l, const int x, const int y, const BOOL locTrue); + +const char * locGetTypePrefix(const int flags); +const char * locGetTypeName(const int flags); + +LocMarker *locCopyLocMarker(const LocMarker *); +void locCopyLocations(MapLocations *dst, const MapLocations *src); + +void locSetFileInfo(LocFileInfo *file, const char *filename, const char *continent); +void locFreeFileInfo(LocFileInfo *file); + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libmaputils.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,824 @@ +/* + * maputils - Generic functions/tables for maputils package + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" + + +const MapColor mapColors[] = +{ + { 0x00, 0x00, 0x00, 30, ANSI_OFF, FALSE }, // col_black + { 0x00, 0x00, 0xaa, 34, ANSI_OFF, FALSE }, // col_blue + { 0xaa, 0x00, 0x00, 31, ANSI_OFF, FALSE }, // col_red + { 0xaa, 0xaa, 0x00, 33, ANSI_OFF, FALSE }, // col_yellow + { 0x00, 0xaa, 0x00, 32, ANSI_OFF, FALSE }, // col_green + { 0xaa, 0xaa, 0xaa, 37, ANSI_OFF, FALSE }, // col_white + { 0x00, 0xff, 0xff, 36, ANSI_OFF, FALSE }, // col_cyan + { 0x77, 0x00, 0x77, 35, ANSI_OFF, FALSE }, // col_magenta + + { 0x77, 0x77, 0x77, 30, ANSI_BOLD, FALSE }, // col_light_black + { 0x00, 0x00, 0xff, 34, ANSI_BOLD, FALSE }, // col_light_blue + { 0xee, 0x00, 0x00, 31, ANSI_BOLD, FALSE }, // col_light_red + { 0xff, 0xff, 0x00, 33, ANSI_BOLD, FALSE }, // col_light_yellow + { 0x00, 0xff, 0x00, 32, ANSI_BOLD, FALSE }, // col_light_green + { 0xff, 0xff, 0xff, 37, ANSI_BOLD, FALSE }, // col_light_white + { 0x00, 0xff, 0xff, 36, ANSI_BOLD, FALSE }, // col_light_cyan + { 0xff, 0x00, 0xff, 35, ANSI_BOLD, FALSE }, // col_light_magenta + + // extra colors, not in ANSI + { 0xff, 0x00, 0x00, 31, ANSI_BOLD, TRUE }, // col_very_light_red +}; + +const int nmapColors = sizeof(mapColors) / sizeof(mapColors[0]); + + +const MapPiece mapPieces[] = +{ + { '!', "Mountain Peak", 0xcc,0xff,0xff, col_white, -1, }, + { '#', "Ruins", 0x88,0x88,0x88, col_light_black, -1, }, + { '%', "Special Location", 0xff,0xff,0xff, col_light_white, -1, }, + { '+', "Crossing", 0x33,0x33,0x33, col_light_black, -1, }, + { '-', "Road", 0x33,0x33,0x33, col_light_black, -1, }, + { '|', "Road", 0x33,0x33,0x33, col_light_black, -1, }, + { '/', "Road", 0x33,0x33,0x33, col_light_black, -1, }, + { '\\',"Road", 0x33,0x33,0x33, col_light_black, -1, }, + { '.', "Plains", 0x55,0x92,0x00, col_green, -1, }, + { '=', "Bridge", 0x33,0x33,0x33, col_light_black, -1, }, + { '?', "Scenic Location", 0xff,0xff,0xff, col_light_white, -1, }, + { '@', "Flowing Lava", 0xff,0x99,0x3f, col_very_light_red, -1, }, + { 'C', "Player City", 0x88,0x88,0x88, col_light_black, -1, }, + { 'F', "Deep Forest", 0x00,0x88,0x00, col_green, -1, }, + { 'H', "Highlands", 0x66,0x3f,0x00, col_magenta, -1, }, + { 'L', "Lava Lake", 0xff,0x50,0x00, col_very_light_red, -1, }, + { 'R', "Deep River", 0x33,0x66,0xff, col_blue, -1, }, + { 'V', "Volcano", 0xff,0x33,0x00, col_red, -1, }, + { '^', "Mountain", 0x71,0x82,0x92, col_light_magenta, -1, }, + { 'b', "Beach", 0xcf,0xc4,0xa5, col_yellow, -1, }, + { 'c', "City", 0x88,0x88,0x88, col_light_black, -1, }, + { 'd', "Desert", 0xee,0xaa,0x22, col_yellow, -1, }, + { 'f', "Forest", 0x00,0xb6,0x00, col_light_green, -1, }, + { 'h', "Hills", 0x99,0x66,0x00, col_magenta, -1, }, + { 'i', "Ice", 0xee,0xee,0xff, col_light_blue, -1, }, + { 'j', "Jungle", 0x13,0x96,0x36, col_green, -1, }, + { 'l', "Lake", 0x21,0x33,0xcc, col_light_blue, -1, }, + { 'r', "River", 0x66,0x99,0xff, col_light_blue, -1, }, + { 's', "Swamp", 0x9d,0xa8,0x0a, col_light_red, -1, }, + { 't', "Tundra", 0x61,0xc3,0xa2, col_white, -1, }, + { 'v', "Valley", 0x22,0xdd,0x22, col_light_green, -1, }, + { 'w', "Waterfall", 0x77,0xaa,0xff, col_light_cyan, -1, }, + { 'x', "Badlands", 0x8a,0x83,0x60, col_light_red, -1, }, + { 'y', "Fields", 0xa7,0xcc,0x14, col_yellow, -1, }, + { 'z', "Shore", 0xa7,0xcc,0x14, col_light_yellow, -1, }, + { ',', "Muddy Trail", 0x8c,0x57,0x38, col_light_yellow, -1, }, + { '&', "Monster", 0xff,0x00,0x00, col_light_red, -1, }, +#ifndef SECRET_MAP_DATA_FORMAT + { 'S', "Shallows", 0x44,0xcc,0xcc, col_light_cyan, -1, }, + { '~', "Sea", 0x11,0x88,0xdd, col_blue, -1, }, +#else + { '~', "Sea 1", 0x00,0x11,0x88, col_blue, -1, }, + { '"', "Sea 2", 0x11,0x22,0x99, col_blue, -1, }, + { '\'',"Sea 3", 0x11,0x33,0xaa, col_blue, -1, }, + { '`', "Shallows?", 0x11,0x66,0xdd, col_blue, -1, }, + { 'p', "Plains?", 0x55,0x92,0x00, col_green, -1, }, + { 'S', "Swamp?", 0x77,0x77,0x33, col_yellow, -1, }, +#endif + { -1 , "Road", 0x33,0x33,0x33, col_light_black, '.', }, + { -1 , "Plains", 0x00,0xff,0x00, col_light_green, 'p', }, + { -1 , "Highlands", 0x77,0x00,0x77, col_magenta, 'i', }, +}; + +const int nmapPieces = sizeof(mapPieces) / sizeof(mapPieces[0]); + + +const MapPiece mapCityPieces[] = +{ + { '.', "Street", 0xaa,0xaa,0x00, col_yellow, -1, }, + { '-', "Door/Gate", 0xff,0xff,0x00, col_light_yellow, -1, }, + { '|', "Door/Gate", 0xff,0xff,0x00, col_light_yellow, -1, }, + { '=', "Bridge/Gate", 0x33,0x33,0x33, col_yellow, -1, }, + { '*', "Fountain", 0x00,0x00,0xff, col_light_blue, -1, }, + { '@', "???", 0xff,0xff,0x00, col_light_yellow, -1, }, + { '$', "Tree", 0x00,0xaa,0x00, col_green, -1, }, + { '#', "Wall", 0x33,0x33,0x33, col_light_black, -1, }, + { '"', "Grass", 0x00,0x88,0x00, col_green, -1, }, + { ':', "???", 0x00,0x00,0x00, col_light_white, -1, }, + { 'z', "Shore", 0xa7,0xcc,0x14, col_yellow, -1, }, + { ',', "Lawn", 0x00,0xcc,0x00, col_green, -1, }, + + // Old versions + { -1, "Street", 0x00,0x00,0x00, col_white, '.', }, + { -1, "Door/Gate", 0x00,0x00,0x00, col_yellow, '-', }, + { -1, "Door/Gate", 0x00,0x00,0x00, col_yellow, '|', }, + { -1, "Fountain", 0x00,0x00,0x00, col_light_white, '*', }, +}; + +const int nmapCityPieces = sizeof(mapCityPieces) / sizeof(mapCityPieces[0]); + + +typedef struct +{ + unsigned char c; + char *ent; +} HTMLEntity; + + +static const HTMLEntity HTMLEntities[] = +{ + { '&', "amp" }, + { '<', "lt" }, + { '>', "gt" }, + { '"', "quot" }, + + { 228, "#228" }, + { 246, "#246" }, + { 196, "#196" }, + { 214, "#214" }, +}; + +static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]); + + +int tmpl_fprintve(int (*mputs)(const char *, FILE *), FILE *outFile, const char *fmt, va_list ap) +{ + int n, bufsize = strlen(fmt) * 2; + char *buf, *tmp; + + if ((buf = th_malloc(bufsize)) == NULL) + return -1; + + while (1) + { + va_list tap; + va_copy(tap, ap); + n = vsnprintf(buf, bufsize, fmt, tap); + va_end(tap); + if (n > -1 && n < bufsize) + { + // String fit the buffer, print it out and return + int ret = mputs(buf, outFile); + if (ret < 0) + return ret; + + th_free(buf); + return n; + } + + // Didn't fit, try reallocating some more space + if (n > -1) + bufsize = n + 1; + else + bufsize *= 2; + + if ((tmp = th_realloc(buf, bufsize)) == NULL) + { + th_free(buf); + return -2; + } + else + buf = tmp; + } + + return 0; +} + + +int fputse(const char *str, FILE *outFile) +{ + const char *s = str; + if (str == NULL) + return EOF; + + while (*s) + { + BOOL found = FALSE; + + for (int i = 0; i < numHTMLEntities; i++) + if (HTMLEntities[i].c == *s) + { + fprintf(outFile, "&%s;", HTMLEntities[i].ent); + found = TRUE; + break; + } + + if (!found) + fputc(*s, outFile); + s++; + } + + return 0; +} + + +int fprintve(FILE *outFile, const char *fmt, va_list ap) +{ + return tmpl_fprintve(fputse, outFile, fmt, ap); +} + + +int fprintfe(FILE *outFile, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = fprintve(outFile, fmt, ap); + va_end(ap); + return ret; +} + + +int fputsesc1(const char *str, FILE *f) +{ + const char *p = str; + if (str == NULL) + return -1; + + while (*p) + { + int ret; + switch (*p) + { + case '\\': ret = fputs("\\", f); break; + case '"': ret = fputs("\\\"", f); break; + default: ret = fputc(*p, f); break; + } + p++; + if (ret < 0) + return ret; + } + + return 0; +} + + +int fprintvesc1(FILE *outFile, const char *fmt, va_list ap) +{ + return tmpl_fprintve(fputsesc1, outFile, fmt, ap); +} + + +int fprintfesc1(FILE *outFile, const char *fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = fprintvesc1(outFile, fmt, ap); + va_end(ap); + return ret; +} + + +int fputsesc3(const char *str, FILE *f) +{ + const char *p = str; + if (str == NULL) + return -1; + + while (*p) + { + int ret; + switch (*p) + { + case ';': ret = fputs("\\;", f); break; + case '\\': ret = fputs("\\", f); break; + case '\'': ret = fputs("\\'", f); break; + case '"': ret = fputs("\\\"", f); break; + default: ret = fputc(*p, f); break; + } + p++; + if (ret < 0) + return ret; + } + + return 0; +} + + +int fputsesc2(const char *str, FILE *f) +{ + const char *p = str; + if (str == NULL) + return -1; + + while (*p) + { + int ret; + switch (*p) + { + case ';': ret = fputs("\\;", f); break; + case '\\': ret = fputs("\\", f); break; + default: ret = fputc(*p, f); break; + } + p++; + if (ret < 0) + return ret; + } + + return 0; +} + + +char *muColorToCSSColor(char *buf, const size_t len, const int c) +{ + const MapColor *col = &mapColors[c]; + + snprintf(buf, len, "#%02x%02x%02x", + col->cr, col->cg, col->cb); + + return buf; +} + + +static int muGetPieceFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld) +{ + for (int i = 0; i < npieces; i++) + { + if ((getOld && pieces[i].oldSymbol == symbol) || + (!getOld && pieces[i].symbol == symbol)) + return i; + } + + for (int i = 0; i < npieces; i++) + { + if (getOld && pieces[i].symbol == symbol) + return i; + } + + return -1; +} + + +int muGetMapPieceIndex(int symbol, BOOL getOld, BOOL getCity) +{ + int n; + + if (getCity && (n = muGetPieceFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0) + return n; + + return muGetPieceFromList(mapPieces, nmapPieces, symbol, getOld); +} + + +static int muGetColorFromList(const MapPiece pieces[], const int npieces, int symbol, BOOL getOld) +{ + if (getOld) + { + for (int i = 0; i < npieces; i++) + if (pieces[i].oldSymbol == symbol) + return pieces[i].color; + } + + for (int i = 0; i < npieces; i++) + if (pieces[i].symbol == symbol) + return pieces[i].color; + + return -1; +} + + +int muGetMapPieceColor(int symbol, BOOL getOld, BOOL getCity) +{ + int n; + + if (getCity && (n = muGetColorFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0) + return n; + + return ((n = muGetColorFromList(mapPieces, nmapPieces, symbol, getOld)) >= 0) ? n : 0; +} + + +void muPrintHTMLhead(FILE *outFile, const char *title, BOOL html5) +{ + static const char *strCharSet = "utf-8"; + assert(outFile != NULL); + + if (html5) + { + fprintf(outFile, + "<!DOCTYPE html>\n" + "<html lang=\"en\">\n" + "<head>\n" + " <meta charset=\"%s\">\n", + strCharSet); + } + else + { + fprintf(outFile, + "<?xml version=\"1.0\" encoding=\"%s\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n" + "<head>\n" + " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n", + strCharSet, + strCharSet); + } + + if (title) + { + fprintf(outFile, " <title>"); + fputse(title, outFile); + fprintf(outFile, "</title>\n"); + } +} + + +void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra) +{ + assert(outFile != NULL); + + for (int n = 0; n < nmapColors; n++) + { + const MapColor *col = &mapColors[n]; + + fprintf(outFile, + " %s.%c { %s: #%02x%02x%02x;%s%s }\n", + tagName, 'a'+n, propName ? propName : "color", + col->cr, col->cg, col->cb, + col->bold ? " font-weight: bold;" : "", + extra ? extra : ""); + } +} + + +int muCopyFileToStream(FILE *outFile, const char *filename) +{ + FILE *inFile; + + if ((inFile = fopen(filename, "rb")) != NULL) + { + int c; + while ((c = fgetc(inFile)) != EOF) + { + if (fputc(c, outFile) == EOF) + { + fclose(inFile); + return -2; + } + } + fclose(inFile); + return 0; + } + else + return -1; +} + + +BOOL muStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch) +{ + for (size_t n = 0; n < nsymbols; n++) + if (symbols[n] == ch) + return TRUE; + + return FALSE; +} + + +/* Map block handling + */ +MapBlock * mapBlockAlloc(const int width, const int height) +{ + MapBlock *res; + + // Check arguments + if (width <= 0 || height <= 0) + return NULL; + + // Allocate struct and data + res = (MapBlock *) th_malloc0(sizeof(MapBlock)); + if (!res) return NULL; + + res->width = width; + res->height = height; + res->scansize = ((width / BLOCK_SCAN_ALIGN) + 1) * BLOCK_SCAN_ALIGN; + res->size = res->height * res->scansize * sizeof(char); + + res->data = (unsigned char *) th_malloc0(res->size); + if (!res->data) + { + th_free(res); + return NULL; + } + + return res; +} + + +void mapBlockFree(MapBlock *block) +{ + if (block) + { + th_free_r(&block->data); + th_free(block); + } +} + + +MapBlock * mapBlockCopy(const MapBlock *block) +{ + MapBlock *res; + + if (block == NULL) + return NULL; + + res = (MapBlock *) th_malloc0(sizeof(MapBlock)); + if (!res) return NULL; + + res->width = block->width; + res->height = block->height; + res->scansize = block->scansize; + res->size = block->size; + + res->data = (unsigned char *) th_malloc(res->size); + if (!res->data) + { + th_free(res); + return NULL; + } + + memcpy(res->data, block->data, res->size); + + return res; +} + + +/* Parse single arbitrary sized block from given mapfile + */ +MapBlock * mapBlockParseFile(const char *filename, BOOL isDiff) +{ + MapBlock *block; + FILE *inFile; + + if ((inFile = fopen(filename, "rb")) == NULL) + { + THERR("Could not open mapfile '%s' for reading.\n", filename); + return NULL; + } + + block = mapBlockParseStream(filename, inFile, isDiff); + + fclose(inFile); + + return block; +} + + +MapBlock * mapBlockParseStream(const char *filename, FILE *inFile, BOOL isDiff) +{ + MapBlock *res; + unsigned char *o; + long pos = 0L; + BOOL flag; + int x, y, resW, resH, c; + assert(filename != NULL); + + if (isDiff) + { + char buf[128]; + int ver; + if (fgets(buf, sizeof(buf), inFile) == NULL) + { + THERR("Failed to read file header from '%s': %s\n", + filename, th_error_str(th_get_error())); + return NULL; + } + if (sscanf(buf, DIFF_MAGIC "%d\n", &ver) != 1) + { + THERR("Not a DIFF format file '%s'.\n", filename); + return NULL; + } + if (ver != DIFF_VERSION) + { + THERR("Not a correct DIFF format version (%d != %d) '%s'.\n", + ver, DIFF_VERSION, filename); + return NULL; + } + pos = ftell(inFile); + } + + // Probe map width + resH = 1; + resW = -1; + x = 0; + flag = FALSE; + while ((c = fgetc(inFile)) != EOF) + { + if ((!isDiff && c == '\n') || (isDiff && c == 0xff)) + { + if (x > resW) + resW = x; + flag = TRUE; + } + else + { + if (flag) + { + x = 0; + resH++; + flag = FALSE; + } + x++; + } + } + + // Seek back + if (fseek(inFile, pos, SEEK_SET) == -1) + { + THERR("Could not rewind file '%s'.\n", + filename); + return NULL; + } + + // Allocate block + if ((res = mapBlockAlloc(resW, resH)) == NULL) + { + THERR("Could not allocate mapblock (%d, %d) for '%s'.\n", + resW, resH, filename); + return NULL; + } + + // Read data + o = res->data; + y = 0; + x = 0; + flag = FALSE; + while ((c = fgetc(inFile)) != EOF && (y < res->height)) + { + if ((!isDiff && c == '\n') || (isDiff && c == 0xff)) + { + if (x != res->width) + { + THERR("Broken block in '%s', line #%d width %d < %d!\n", + filename, y, x, res->width); + mapBlockFree(res); + return NULL; + } + flag = TRUE; + } + else + { + if (flag) + { + x = 0; + y++; + o = res->data + (y * res->scansize); + flag = FALSE; + } + o[x++] = c; + + if (x > res->scansize) + { + THERR("Broken block in '%s', line #%d width %d > scansize %d!\n", + filename, y, x, res->scansize); + mapBlockFree(res); + return NULL; + } + } + } + + // Close file + if (y >= res->height) + { + THERR("Broken block in '%s', height %d >= %d\n", filename, y, res->height); + mapBlockFree(res); + return NULL; + } + + return res; +} + + +/* Blit a block into another, assume that memory has been allocated + * in sufficient way and other preparations are done. + */ +int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy) +{ + assert(map != NULL); + assert(src != NULL); + + for (int y = 0; y < src->height; y++) + for (int x = 0; x < src->width; x++) + { + const int dx = ox + x; + const int dy = oy + y; + + if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) + { + char c = src->data[(y * src->scansize) + x]; + + if (c != 0) + map->data[(dy * map->scansize) + dx] = c; + } + else + return -1; + } + + return 0; +} + + +int mapBlockPut(MapBlock **pmap, const MapBlock *src, int ox, int oy) +{ + MapBlock *tmp; + int x0, y0, x1, y1, mx, my; + + assert(pmap != NULL); + assert(*pmap != NULL); + assert(src != NULL); + + // Determine new block size + x0 = mx = y0 = my = 0; + x1 = (*pmap)->width - 1; + y1 = (*pmap)->height - 1; + + if (ox < 0) { x0 = ox; mx = -ox; ox = 0; } + if (oy < 0) { y0 = oy; my = -oy; oy = 0; } + + if ((x0 + ox + src->width - 1) > x1) + x1 = (x0 + ox + src->width - 1); + + if ((y0 + oy + src->height - 1) > y1) + y1 = (y0 + oy + src->height - 1); + + // Allocate new block + if ((tmp = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL) + return -1; + + // Copy data + if (mapBlockPutDo(tmp, *pmap, mx, my) < 0) + { + mapBlockFree(tmp); + return -2; + } + + if (mapBlockPutDo(tmp, src, ox, oy) < 0) + { + mapBlockFree(tmp); + return -3; + } + + tmp->xc = -mx; + tmp->yc = -my; + + // Out with the old, in with the new + mapBlockFree(*pmap); + *pmap = tmp; + + return 0; +} + + +/* Clean given block from position markers and whitespaces + */ +void mapBlockClean(MapBlock *map, const unsigned char *symbols, const size_t nsymbols) +{ + assert(map != NULL); + assert(symbols != NULL); + + for (int y = 0; y < map->height; y++) + { + unsigned char *dp = map->data + (y * map->scansize); + for (int x = 0; x < map->width; x++) + { + if (muStrChr(symbols, nsymbols, *dp)) + *dp = 0; + dp++; + } + } +} + + +/* Print block to given output stream + */ +void mapBlockPrint(FILE *fh, const MapBlock *map) +{ + assert(fh != NULL); + assert(map != NULL); + + for (int y = 0; y < map->height; y++) + { + unsigned char *c = map->data + (y * map->scansize); + for (int x = 0; x < map->width; x++) + { + fputc(*c >= 32 && *c <= 126 ? *c : ' ', fh); + c++; + } + fprintf(fh, "\n"); + } +} + + +void mapBlockPrintRaw(FILE *fh, const MapBlock *map) +{ + assert(fh != NULL); + assert(map != NULL); + + for (int y = 0; y < map->height; y++) + { + unsigned char *dp = map->data + (y * map->scansize); + for (int x = 0; x < map->width; x++) + fputc(*dp++, fh); + + fputc(0xff, fh); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libmaputils.h Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,131 @@ +/* + * libmaputils - Generic functions/tables for maputils package + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#ifndef LIBMAPUTILS_H +#define LIBMAPUTILS_H + +#include "libutil.h" +#include <stdio.h> +#include <stdarg.h> + + +/* Map diff format header and version + */ +#define DIFF_MAGIC "MAPDIFF" +#define DIFF_VERSION 2 + +#define BLOCK_SCAN_ALIGN 8 + + +/* Typedefs + */ +typedef struct +{ + char symbol; + char *desc; + int cr, cg, cb; + int color; + char oldSymbol; +} MapPiece; + + +typedef struct +{ + unsigned char *data; + int width, height, scansize, size; + int xc, yc; + BOOL mark; +} MapBlock; + + +typedef struct +{ + int cr, cg, cb; + int ansi; + int ansiAttr; + BOOL bold; +} MapColor; + + +enum +{ + ANSI_OFF = 0, + ANSI_BOLD = 1, + ANSI_UNDERSCORE = 4, + ANSI_BLINK = 5, + ANSI_REVERSE = 7, + ANSI_CONCEALED = 8 +}; + + +enum +{ + col_black = 0, + col_blue, + col_red, + col_yellow, + col_green, + col_white, + col_cyan, + col_magenta, + + col_light_black, + col_light_blue, + col_light_red, + col_light_yellow, + col_light_green, + col_light_white, + col_light_cyan, + col_light_magenta, + + col_very_light_red, +}; + + +/* Some global tables + */ +extern const MapColor mapColors[]; +extern const int nmapColors; + +extern const MapPiece mapPieces[]; +extern const int nmapPieces; + + +/* Misc functions + */ +int fputse(const char *str, FILE *outFile); +int fprintve(FILE *outFile, const char *fmt, va_list ap); +int fprintfe(FILE *outFile, const char *fmt, ...); + +int fprintvesc1(FILE *outFile, const char *fmt, va_list ap); +int fprintfesc1(FILE *outFile, const char *fmt, ...); +int fputsesc1(const char *str, FILE *f); +int fputsesc2(const char *str, FILE *f); +int fputsesc3(const char *str, FILE *f); + +int muGetMapPieceIndex(int symbol, BOOL getOld, BOOL getCity); +int muGetMapPieceColor(int symbol, BOOL getOld, BOOL getCity); +void muPrintHTMLhead(FILE *outFile, const char *title, BOOL html5); +void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra); +char * muColorToCSSColor(char *buf, const size_t len, const int c); +int muCopyFileToStream(FILE *outFile, const char *filename); +BOOL muStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch); + + +/* Mapblock handling + */ +MapBlock * mapBlockAlloc(int width, int height); +MapBlock * mapBlockCopy(const MapBlock *block); +void mapBlockFree(MapBlock *block); +MapBlock * mapBlockParseStream(const char *filename, FILE *fh, BOOL isDiff); +MapBlock * mapBlockParseFile(const char *filename, BOOL isDiff); +void mapBlockPrint(FILE *fh, const MapBlock *block); +int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy); +int mapBlockPut(MapBlock **pmap, const MapBlock *src, const int ox, const int oy); +void mapBlockClean(MapBlock *block, const unsigned char *symbols, const size_t nsymbols); +void mapBlockPrintRaw(FILE *fh, const MapBlock *block); + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libutil.h Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,8 @@ +#ifndef LIBUTIL_H +#define LIBUTIL_H + +#include "th_util.h" +#include "th_string.h" + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/map2ppm.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,413 @@ +/* + * Convert BatMUD ASCII map to PPM or PNG image file + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + +#ifdef HAVE_LIBPNG +#include <png.h> +#endif + +char *srcFilename = NULL, + *dstFilename = NULL; + +BOOL optUseOldFormat = FALSE, + optInputIsDiff = FALSE, + optUseANSI = FALSE, + optCityFormat = FALSE; +int optScale = 1; +#ifdef HAVE_LIBPNG +int optPNGLevel = -1; +#endif + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'q', "quiet", "Be quiet", OPT_NONE }, + { 3, 'd', "input-diff", "Input is a diff", OPT_NONE }, + { 4, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, + { 5, 'o', "output", "Output filename", OPT_ARGREQ }, + { 6, 'A', "ansi-colors", "Use ANSI colors", OPT_NONE }, + { 7, 'c', "city-format", "Input is a city map", OPT_NONE }, + { 8, 's', "scale", "Scale value (integer)", OPT_ARGREQ }, + +#ifdef HAVE_LIBPNG + { 9, 'P', "png", "PNG format output (compression level 0-9)", OPT_ARGREQ }, +#endif +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <input mapfile>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + th_verbosity = -1; + break; + + case 3: + optInputIsDiff = TRUE; + THMSG(2, "Input is a 'diff', handling it as such.\n"); + break; + + case 4: + optUseOldFormat = TRUE; + THMSG(2, "Input is using old map symbols/colors.\n"); + break; + + case 5: + dstFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", dstFilename); + break; + + case 6: + optUseANSI = TRUE; + THMSG(2, "Using ANSI colors.\n"); + break; + + case 7: + optCityFormat = TRUE; + THMSG(2, "Input is handled as a city map\n"); + break; + + case 8: + optScale = atoi(optArg); + if (optScale < 1 || optScale > 50) + { + THERR("Invalid scale value %d, must be 1 < x < 50.\n", optScale); + return FALSE; + } + THMSG(2, "Output scaling set to %d.\n", optScale); + break; + + +#ifdef HAVE_LIBPNG + case 9: + optPNGLevel = atoi(optArg); + if (optPNGLevel < 0 || optPNGLevel > 9) + { + THERR("Invalid PNG compression factor %d, must be 0 < x < 9.\n", optPNGLevel); + return FALSE; + } + THMSG(2, "Output format set to PNG, compression level %d.\n", optPNGLevel); + break; +#endif + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (!srcFilename) + srcFilename = currArg; + else + { + THERR("Too many input map files specified!\n"); + return FALSE; + } + + return TRUE; +} + + +int writeImageData(const MapBlock *map, void *cbdata, + BOOL (*writeRowCB)(void *, const uint8_t *, const size_t), + const int scale) +{ + int res = 0; + uint8_t *row = NULL; + + // Allocate memory for row buffer + if ((row = th_malloc(map->width * 3 * scale + 16)) == NULL) + { + res = -16; + goto done; + } + + for (int y = 0; y < map->height; y++) + { + uint8_t *ptr = row; + + for (int x = 0; x < map->width; x++) + { + int qr, qg, qb, c; + qr = 255; qg = qb = 0; + c = (uint8_t) map->data[(y * map->scansize) + x]; + + if (optInputIsDiff) + { + if (c < nmapPieces) + { + if (optUseANSI) + { + qr = qg = qb = 255; + } + else + { + c = c & 63; + qr = mapPieces[c].cr; + qg = mapPieces[c].cg; + qb = mapPieces[c].cb; + } + } + } + else + if (optUseANSI) + { + if ((c = muGetMapPieceColor(c, optUseOldFormat, optCityFormat)) >= 0) + { + qr = mapColors[c].cr; + qg = mapColors[c].cg; + qb = mapColors[c].cb; + } + } + else + { + if ((c = muGetMapPieceIndex(c, optUseOldFormat, optCityFormat)) >= 0) + { + qr = mapPieces[c].cr; + qg = mapPieces[c].cg; + qb = mapPieces[c].cb; + } + } + + for (int xscale = 0; xscale < scale; xscale++) + { + *ptr++ = qr; + *ptr++ = qg; + *ptr++ = qb; + } + } + + for (int yscale = 0; yscale < scale; yscale++) + { + if (!writeRowCB(cbdata, row, map->width * 3 * scale)) + { + res = -32; + goto done; + } + } + } + +done: + th_free(row); + return res; +} + + +BOOL writePPMRow(void *cbdata, const uint8_t *row, const size_t len) +{ + return fwrite(row, sizeof(uint8_t), len, (FILE *) cbdata) == len; +} + + +int writePPMFile(FILE *outFile, const MapBlock *map, const int scale) +{ + // Write header for 24-bit PPM + fprintf(outFile, + "P6\n%d %d\n255\n", + map->width * scale, map->height * scale); + + // Write image data + return writeImageData(map, (void *) outFile, writePPMRow, scale); +} + + +#ifdef HAVE_LIBPNG +BOOL writePNGRow(void *cbdata, const uint8_t *row, const size_t len) +{ + png_structp png_ptr = cbdata; + (void) len; + + if (setjmp(png_jmpbuf(png_ptr))) + return FALSE; + + png_write_row(png_ptr, row); + + return TRUE; +} + + +int writePNGFile(FILE *outFile, const MapBlock *map, const int scale) +{ + int width, height; + png_structp png_ptr; + png_infop info_ptr; + + width = map->width * scale; + height = map->height * scale; + + // Create PNG structures + png_ptr = png_create_write_struct( + PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + + if (png_ptr == NULL) + { + THERR("PNG: png_create_write_struct() failed.\n"); + return -1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) + { + THERR("PNG: png_create_info_struct(%p) failed.\n", (void *) png_ptr); + return -2; + } + + if (setjmp(png_jmpbuf(png_ptr))) + { + THERR("PNG: Error during PNG init_io().\n"); + return -3; + } + + png_init_io(png_ptr, outFile); + + // Write PNG header info + if (setjmp(png_jmpbuf(png_ptr))) + { + THERR("PNG: Error during writing header.\n"); + return -4; + } + + png_set_IHDR(png_ptr, info_ptr, + width, + height, + 8, // bits per component + PNG_COLOR_TYPE_RGB, // 3 components, RGB + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + +// png_set_gAMA(png_ptr, info_ptr, 2.2); + + png_write_info(png_ptr, info_ptr); + + + // Write compressed image data + if (setjmp(png_jmpbuf(png_ptr))) + { + THERR("PNG: Error during writing image data.\n"); + return -5; + } + + writeImageData(map, (void *) png_ptr, writePNGRow, scale); + + + // Write footer + if (setjmp(png_jmpbuf(png_ptr))) + { + THERR("PNG: Error during writing image footer.\n"); + return -6; + } + + png_write_end(png_ptr, NULL); + + // Dellocate shit + if (png_ptr && info_ptr) + png_destroy_write_struct(&png_ptr, &info_ptr); + + return 0; +} +#endif + + +/* Main program + */ +int main(int argc, char *argv[]) +{ + FILE *outFile = NULL; + MapBlock *map = NULL; + int ret = 0; + + // Initialize + th_init("map2ppm", "ASCII map to PPM/PNG image converter", "0.5", NULL, NULL); + th_verbosity = 0; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + exit(1); + + if (srcFilename == NULL) + { + THERR("Nothing to do. (try --help)\n"); + exit(0); + } + + // Read input file + THMSG(1, "Reading map file '%s'\n", srcFilename); + + if ((map = mapBlockParseFile(srcFilename, optInputIsDiff)) == NULL) + { + THERR("Error reading map file '%s'!\n", + srcFilename); + goto out; + } + + // Open output file + if (dstFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(dstFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + dstFilename); + goto out; + } + + THMSG(1, "Outputting image of %dx%d ...\n", + map->width * optScale, map->height * optScale); + +#ifdef HAVE_LIBPNG + if (optPNGLevel >= 0) + ret = writePNGFile(outFile, map, optScale); + else +#endif + ret = writePPMFile(outFile, map, optScale); + + if (ret != 0) + THERR("Image write failed, code=%d\n", ret); + else + THMSG(1, "Done.\n"); + +out: + if (outFile != NULL) + fclose(outFile); + + mapBlockFree(map); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mapsearch.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,1990 @@ +/* + * PupuMaps Search WebSockets server + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2018-2021 Tecnic Software productions (TNSP) + */ +#include "th_args.h" +#include "th_datastruct.h" +#include "liblocfile.h" +#include "libmaputils.h" +#include <stdarg.h> +#include <libwebsockets.h> +#include <sys/types.h> +#include <pwd.h> +#include <grp.h> +#include <math.h> + + +/* Default settings etc. constants + */ +#define SET_MAX_MAPS 16 // Maximum number of maps allowed to be loaded +#define SET_MAX_LISTEN 4 // Maximum number of interfaces to listen +#define SET_MAX_MATCHES 64 // Maximum number of match results per query + +// Define the static lws_write() buffer size +#define SET_LWS_BUF_SIZE (256 * 1024) // 256kB probably enough for our purposes(tm) +#define SET_LWS_BUF_PAD (((LWS_PRE / 16) + 1) * 16) + + +// List of default SSL/TLS ciphers to use/allowed +#define SET_DEF_CIPHERS \ + "ECDHE-ECDSA-AES256-GCM-SHA384:" \ + "ECDHE-RSA-AES256-GCM-SHA384:" \ + "DHE-RSA-AES256-GCM-SHA384:" \ + "ECDHE-RSA-AES256-SHA384:" \ + "HIGH:!aNULL:!eNULL:!EXPORT:" \ + "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" \ + "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" \ + "!DHE-RSA-AES128-SHA256:" \ + "!AES128-GCM-SHA256:" \ + "!AES128-SHA256:" \ + "!DHE-RSA-AES256-SHA256:" \ + "!AES256-GCM-SHA384:" \ + "!AES256-SHA256" + + +/* Structure that holds information about one listen interface + */ +typedef struct +{ + char *interface; // Listen interface (* = listen all) + char *vhostname; // Vhost name + int port; // Port number + + int ipvMode; // Enable/disable IPv4/6 support for this listener + + BOOL useSSL; // Use SSL/TLS? + char *sslCertFile, // Certificate file + *sslKeyFile, // Key file + *sslCAFile; // CA file + + struct lws_vhost *vhost; // LWS vhost info +} MAPListenerCtx; + + +/* Structure for holding information about one map and its locations + */ +typedef struct +{ + char *mapFilename; // Filename of the map data + LocFileInfo locFile; // Location file info struct + MapBlock *map; // Map data, when loaded + MapLocations loc; // Map locations, when loaded +} MAPInfoCtx; + + +typedef struct +{ + char *map; + int mx, my, wx, wy; + LocMarker *marker; + int nname; +} MAPMatch; + + +LocMarker **optMapLocations = NULL; +int optNMapLocations = 0; + +/* Options + */ +MAPInfoCtx optMaps[SET_MAX_MAPS]; +int optNMaps = 0; +MAPListenerCtx *optListenTo[SET_MAX_LISTEN]; +int optNListenTo = 0; +char *optSSLCipherList = SET_DEF_CIPHERS; +struct lws_context *setLWSContext = NULL; +int optWorldXC = 0, optWorldYC = 0; +char *optTest = NULL; +int optUID = -1, optGID = -1; +char *optLogFilename = NULL; +FILE *setLogFH = NULL; +int optBenchmark = -1; + +unsigned char *setLWSBuffer = NULL; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'l', "listen", "Listen to interface (see below)", OPT_ARGREQ }, + { 3, 0, "ssl-ciphers", "Specify list of SSL/TLS ciphers", OPT_ARGREQ }, + { 5, 'w', "world-origin", "Specify the world origin <x:y> coordinates " + "to which the map offsets are relative to", OPT_ARGREQ }, + { 6, 'T', "test", "Test search with given file input", OPT_ARGREQ }, + { 4, 'B', "benchmark", "Run a benchmark on test input (-T option) for specified number cycles", OPT_ARGREQ }, + { 7, 'U', "uid", "Run as UID", OPT_ARGREQ }, + { 8, 'G', "gid", "Run as GID", OPT_ARGREQ }, + { 9, 'L', "log-file", "Log to specified file", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, "[options] <map spec>"); + th_args_help(stdout, optList, optListN, 0, 80 - 2); + + fprintf(stdout, + "\n" + "Listening interface(s) are specified with following syntax:\n" + "-l \"<interface/IP/host>:<port>[:no-ipv(4|6)][=<SSL/TLS spec>]\"\n" + "\n" + "IPv6 addresses should be specified with the square bracket notation [].\n" + "To listen to all interfaces, you can specify an asterisk (*) as host:\n" + "\n" + "-l *:3491 -l *:3492=<vhostname for SNI>:<ssl_cert_file.crt>:<ssl_key_file.key>:<ca_file.crt>\n" + "\n" + "This would listen for normal WebSocket (ws://) connections on port 3491 and for\n" + "secure SSL/TLS WebSocket (wss://) connections on port 3492 of all interfaces.\n" + "\n" + "To disable listening on IPv4/6 addresses, specify :no-ipv4 or :no-ipv6\n" + "\n" + "Maps and location files for each map are specified as follows:\n" + "<filename.map>:<locfilename.loc>:<map/continent name>[:<world x-offset>:<world y-offset>]\n" + "World offsets are optional and default to 0, 0 if not specified.\n" + "\n" + "All the map offsets are relative to world origin coordinates, which are 0,0 by default.\n" + "-w 8192:8192\n" + ); +} + + +void mapMSG_V(const int level, const char *fmt, va_list ap) +{ + // Quick way out + if (setLogFH != NULL || (level < 0 || th_verbosity >= level)) + { + char *vtmp = th_strdup_vprintf(fmt, ap); + char vstr[64] = ""; + time_t stamp = time(NULL); + struct tm *stamp_tm; + + // Format timestamp + if ((stamp_tm = localtime(&stamp)) != NULL) + strftime(vstr, sizeof(vstr), "%c", stamp_tm); + + // Sanitize the printed string + for (size_t i = 0; vtmp[i]; i++) + { + if (vtmp[i] != '\n' && (vtmp[i] < 32 || vtmp[i] > 126)) + vtmp[i] = ' '; + } + + if (setLogFH != NULL) + { + fprintf(setLogFH, "[%s] %s", vstr, vtmp); + fflush(setLogFH); + } + else + { + fprintf(stdout, "[%s] %s", vstr, vtmp); + } + + th_free(vtmp); + } +} + + +void mapMSG(const int level, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + mapMSG_V(level, fmt, ap); + va_end(ap); +} + + +void mapERR(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + mapMSG_V(-1, fmt, ap); + va_end(ap); +} + + +void mapLogWS(int level, const char *line) +{ + if (level <= (1 << th_verbosity)) + mapMSG(-1, "%s", line); +} + + +BOOL mapParseCoordPair(const char *str, int *xc, int *yc) +{ + char *piece, *tmp, *fmt = th_strdup(str); + BOOL ret = FALSE; + + if ((piece = strchr(fmt, ':')) == NULL) + goto err; + *piece++ = 0; + + tmp = th_strdup_trim(fmt, TH_TRIM_BOTH); + *xc = atoi(tmp); + th_free(tmp); + + tmp = th_strdup_trim(piece, TH_TRIM_BOTH); + *yc = atoi(tmp); + th_free(tmp); + + ret = TRUE; + +err: + th_free(fmt); + return ret; +} + + +BOOL mapParseMapSpec(const char *str, MAPInfoCtx *info) +{ + char *piece, *start, *fmt = th_strdup(str); + BOOL ret = FALSE; + + memset(info, 0, sizeof(MAPInfoCtx)); + + // Check for map filename end + if ((piece = strchr(fmt, ':')) == NULL) + goto err; + *piece++ = 0; + + info->mapFilename = th_strdup_trim(fmt, TH_TRIM_BOTH); + start = piece; + + // Check for loc filename end + if ((piece = strchr(start, ':')) == NULL) + goto err; + *piece++ = 0; + + info->locFile.filename = th_strdup_trim(start, TH_TRIM_BOTH); + start = piece; + + // Check for world x-offset separator + if ((piece = strchr(start, ':')) != NULL) + *piece++ = 0; + + info->locFile.continent = th_strdup_trim(start, TH_TRIM_BOTH); + + // Get world X/Y offsets, if any + if (piece != NULL && + !mapParseCoordPair(piece, &info->locFile.xoffs, &info->locFile.yoffs)) + goto err; + + ret = TRUE; + +err: + th_free(fmt); + return ret; +} + + +MAPListenerCtx *mapNewListenCtx(void) +{ + return th_malloc0(sizeof(MAPListenerCtx)); +} + + +void mapFreeListenCtxR(MAPListenerCtx *ctx) +{ + if (ctx != NULL) + { + th_free(ctx->vhostname); + th_free(ctx->interface); + th_free(ctx->sslCertFile); + th_free(ctx->sslKeyFile); + th_free(ctx->sslCAFile); + } +} + + +void mapFreeListenCtx(MAPListenerCtx *ctx) +{ + mapFreeListenCtxR(ctx); + th_free(ctx); +} + + +MAPListenerCtx *mapParseListenerSpec(const char *cfmt) +{ + char *start, *end, *flags, *port = NULL, + *interface, *fmt = th_strdup(cfmt); + BOOL ret = FALSE; + MAPListenerCtx *ctx; + + if ((ctx = mapNewListenCtx()) == NULL) + goto out; + + interface = fmt; + if (*interface == '[') + { + // IPv6 IP address is handled in a special case + interface++; + if ((end = strchr(interface, ']')) == NULL) + { + mapERR("Invalid IPv6 IP address '%s'.\n", cfmt); + goto out; + } + *end++ = 0; + + for (size_t n = 0; interface[n]; n++) + if (!isxdigit(interface[n]) && interface[n] != ':') + { + mapERR("Invalid IPv6 IP address '%s'.\n", interface); + goto out; + } + } + else + { + end = strchr(interface, ':'); + } + + // Find port number separator + if (end == NULL || *end != ':') + { + mapERR("Missing listening port in '%s'.\n", cfmt); + goto out; + } + *end++ = 0; + start = end; + + // Check for '=<SSL/TLS spec>' at the end + if ((flags = strchr(start, '=')) != NULL) + *flags++ = 0; + + // Check for ':no-ipv4' or ':no-ipv6' flag + if ((end = strstr(start, ":no-ipv")) != NULL && + (end[7] == '4' || end[7] == '6')) + { + *end = 0; + + if (end[7] == '4') + ctx->ipvMode = 6; + else + ctx->ipvMode = 4; + } + + // Get the interface name + ctx->interface = th_strdup_trim(interface, TH_TRIM_BOTH); + if (strcmp(ctx->interface, "*") == 0) + { + th_free(ctx->interface); + ctx->interface = NULL; + } + + // Get port number + if ((port = th_strdup_trim(start, TH_TRIM_BOTH)) == NULL) + { + mapERR("Missing listening port in '%s'.\n", cfmt); + goto out; + } + if ((ctx->port = atoi(port)) < 1) + { + mapERR("Invalid listening port %d in '%s'.\n", ctx->port, cfmt); + goto out; + } + + // Parse the SSL/TLS spec, if any + if (flags != NULL) + { + char *cstart, *cend; + + // Check for separator + cstart = flags; + if ((cend = strchr(cstart, ':')) == NULL) + { + mapERR("Invalid SSL/TLS spec '%s'\n", flags); + goto out; + } + *cend++ = 0; + + // Get the vhost name + ctx->vhostname = th_strdup_trim(cstart, TH_TRIM_BOTH); + if (strlen(ctx->vhostname) == 0) + { + th_free(ctx->vhostname); + ctx->vhostname = NULL; + } + + // Check for separator + cstart = cend; + if ((cend = strchr(cend, ':')) == NULL) + { + mapERR("Invalid SSL/TLS spec, missing certificate file.\n"); + goto out; + } + *cend++ = 0; + + // Get certificate file path + ctx->sslCertFile = th_strdup_trim(cstart, TH_TRIM_BOTH); + + // Check for separator + cstart = cend; + if ((cend = strchr(cend, ':')) == NULL) + { + mapERR("Invalid SSL/TLS spec, missing key file.\n"); + goto out; + } + *cend++ = 0; + + // Get the rest + ctx->sslKeyFile = th_strdup_trim(cstart, TH_TRIM_BOTH); + ctx->sslCAFile = th_strdup_trim(cend, TH_TRIM_BOTH); + ctx->useSSL = TRUE; + } + + // Check for duplicates + for (int n = 0; n < optNListenTo; n++) + { + MAPListenerCtx *chk = optListenTo[n]; + if (( + (ctx->interface == NULL && chk->interface == NULL) || + (ctx->interface != NULL && chk->interface != NULL && strcmp(ctx->interface, chk->interface) == 0)) && + chk->port == ctx->port) + { + mapERR("Duplicate listener spec (%s:%d)\n", + ctx->interface != NULL ? ctx->interface : "*", ctx->port); + goto out; + } + } + ret = TRUE; + +out: + th_free(fmt); + th_free(port); + + if (ret) + return ctx; + + mapFreeListenCtx(ctx); + return NULL; +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + if (optNListenTo < SET_MAX_LISTEN) + { + MAPListenerCtx *ctx; + if ((ctx = mapParseListenerSpec(optArg)) != NULL) + optListenTo[optNListenTo++] = ctx; + else + return FALSE; + } + else + { + mapERR("Maximum number of listener specs already specified.\n"); + return FALSE; + } + break; + + case 3: + optSSLCipherList = optArg; + break; + + case 5: + if (!mapParseCoordPair(optArg, &optWorldXC, &optWorldYC)) + { + mapERR("Invalid world origin coordinates '%s'.\n", optArg); + return FALSE; + } + break; + + case 6: + optTest = optArg; + break; + + case 4: + { + int tmp = atoi(optArg); + if (tmp < 10) + { + mapERR("Invalid bechmark cycle count %d.\n", optBenchmark); + return FALSE; + } + optBenchmark = tmp; + } + break; + + case 7: + if (sscanf(optArg, "%d", &optUID) != 1) + { + struct passwd *info = getpwnam(optArg); + if (info != NULL) + optUID = info->pw_uid; + else + { + mapERR("Invalid UID '%s'.\n", optArg); + return FALSE; + } + } + break; + + case 8: + if (sscanf(optArg, "%d", &optGID) != 1) + { + struct group *info = getgrnam(optArg); + if (info != NULL) + optUID = info->gr_gid; + else + { + mapERR("Invalid GID '%s'.\n", optArg); + return FALSE; + } + } + break; + + case 9: + optLogFilename = optArg; + break; + + default: + mapERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (optNMaps < SET_MAX_MAPS) + { + if (!mapParseMapSpec(currArg, &optMaps[optNMaps])) + { + mapERR("Invalid map spec '%s'.\n", currArg); + return FALSE; + } + + optNMaps++; + return TRUE; + } + else + { + mapERR("Maximum number of map specs already specified.\n"); + return FALSE; + } +} + + +// Find the actual data boundaries of the block +// ignoring any null characters around it. +void mapBlockFindBoundaries(const MapBlock *map, int *x0, int *y0, int *x1, int *y1, const BOOL precrop) +{ + if (!precrop) + { + *x0 = map->width - 1; + *y0 = map->height - 1; + *x1 = 0; + *y1 = 0; + } + + for (int yc1 = 0, yc2 = map->height - 1; yc1 < map->height; yc1++, yc2--) + { + const unsigned char + *sp0 = map->data + (yc1 * map->scansize), + *sp1 = map->data + (yc2 * map->scansize); + + int minX = -1, + maxX = -1; + + for (int xc1 = 0, xc2 = map->width - 1; xc1 < map->width; xc1++, xc2--) + { + if (minX == -1 && sp0[xc1] != 0) + minX = xc1; + + if (maxX == -1 && sp0[xc2] != 0) + maxX = xc2; + + if (sp0[xc1] != 0 && yc1 < *y0) + *y0 = yc1; + + if (sp1[xc1] != 0 && yc2 > *y1) + *y1 = yc2; + } + + if (minX != -1 && minX < *x0) + *x0 = minX; + + if (maxX != -1 && maxX > *x1) + *x1 = maxX; + } +} + + +BOOL mapBlockCrop(MapBlock **pdst, const MapBlock *src, const int x0, const int y0, const int x1, const int y1) +{ + // Check dimensions + if (x1 - x0 < 0 || y1 - y0 < 0) + return FALSE; + + // Allocate new block and copy the cropped data + if ((*pdst = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL) + return FALSE; + + for (int yc = 0; yc < (*pdst)->height; yc++) + { + const unsigned char *sp = src->data + ((yc + y0) * src->scansize) + x0; + unsigned char *dp = (*pdst)->data + (yc * (*pdst)->scansize); + + for (int xc = 0; xc < (*pdst)->width; xc++) + *dp++ = *sp++; + } + + return TRUE; +} + + +BOOL mapBlockAutoCrop(MapBlock **pdst, const MapBlock *src, + const unsigned char *symbols, const size_t nsymbols, + int x0, int y0, int x1, int y1, const BOOL precrop) +{ + MapBlock *clean; + + // Step #1: Detect crop boundaries + if ((clean = mapBlockCopy(src)) == NULL) + return FALSE; + + mapBlockClean(clean, symbols, nsymbols); + mapBlockFindBoundaries(clean, &x0, &y0, &x1, &y1, precrop); + mapBlockFree(clean); + + // Step #2: Check if the boundaries are any smaller than + // the current block size + if (x0 == 0 && y0 == 0 && + x1 == src->width - 1 && y1 == src->height - 1) + return FALSE; + + // Step #3: Crop it + if (!mapBlockCrop(pdst, src, x0, y0, x1, y1)) + return FALSE; + + return TRUE; +} + + +void mapBlockParseDimensions(const unsigned char *data, const size_t len, int *width, int *height) +{ + size_t offs = 0; + int x1 = 0, x2 = 0; + + *width = *height = 0; + + while (offs < len) + { + const unsigned char ch = data[offs++]; + if (ch == '\n') + { + if (x1 > *width) + *width = x1; + + (*height)++; + x1 = x2 = 0; + } + else + { + x2++; + if (ch != ' ') + x1 = x2; + } + } + + if (x1 > *width) + *width = x1; + + if (x1 > 0) + (*height)++; +} + + +BOOL mapBlockParse(const unsigned char *data, const size_t len, MapBlock *res) +{ + size_t offs = 0; + + for (int yc = 0; yc < res->height; yc++) + { + unsigned char *dp = res->data + (yc * res->scansize); + + if (offs < len && data[offs] != '\n') + for (int xc = 0; xc < res->width; xc++) + { + if (offs < len && data[offs] != '\n') + dp[xc] = data[offs++]; + else + break; + } + + while (offs < len && data[offs] != '\n') + offs++; + + if (offs < len && data[offs] == '\n') + offs++; + } + + return offs == len; +} + + +// Find "center" coordinates (which may not be actual center) +// for given map block based on list of center symbols. +// Returns TRUE if center marker matching one of the symbols found. +BOOL mapBlockFindCenter(const MapBlock *block, + const unsigned char *symbols, const size_t nsymbols, + int *cx, int *cy, const int tolerance) +{ + const int + x0 = (block->width * tolerance) / 100, + x1 = (block->width * (100 - tolerance)) / 100, + y0 = (block->height * tolerance) / 100, + y1 = (block->height * (100 - tolerance)) / 100; + + *cx = *cy = 0; + for (int yc = 0; yc < block->height; yc++) + { + const unsigned char *dp = block->data + (yc * block->scansize); + for (int xc = 0; xc < block->width; xc++) + { + if (xc >= x0 && xc <= x1 && + yc >= y0 && yc <= y1 && + muStrChr(symbols, nsymbols, dp[xc])) + { + *cx = xc; + *cy = yc; + return TRUE; + } + } + } + + return FALSE; +} + + +// Calculate entropy value for the given map block, excluding +// the specified characters. TODO: This function is not very good. +// It does not take into account spatial entropy. +int mapBlockGetEntropy(const MapBlock *map, const char *exclude, const int nexclude) +{ + unsigned char *list; + int num, i; + + // Allocate memory for entropy array + if ((list = th_malloc0(256)) == NULL) + return -1; + + // Collect sums into entropy array + for (int yc = 0; yc < map->height; yc++) + { + unsigned char *sp = map->data + (yc * map->scansize); + for (int xc = 0; xc < map->width; xc++) + list[sp[xc]]++; + } + + // Handle exclusions + if (exclude != NULL && nexclude > 0) + { + for (i = 0; i < nexclude; i++) + list[(int) exclude[i]] = 0; + } + + // Calculate simple entropy + for (num = 0, i = 0; i < 256; i++) + if (list[i]) num++; + + th_free(list); + return num; +} + + +// Match given "pattern" block against given "map" at +// specified offset coordinates (ox, oy). Return TRUE if +// there is a match, FALSE otherwise. +BOOL mapMatchBlock(const MapBlock *map, const MapBlock *pattern, const int ox, const int oy) +{ + const unsigned char + *sp = map->data + (oy * map->scansize) + ox, + *dp = pattern->data; + + int yc = pattern->height; + while (yc--) + { + for (int xc = 0; xc < pattern->width; xc++) + { + if (dp[xc] != 0 && dp[xc] != sp[xc]) + return FALSE; + } + + sp += map->scansize; + dp += pattern->scansize; + } + + return TRUE; +} + + +// Simple implementation of atoi() without support for other than base 10 +int mapAtoI(const char *str, const size_t len) +{ + int value = 0; + size_t i = 0; + BOOL neg = FALSE; + + while (th_isspace(str[i])) i++; + + if (str[i] == '-') + { + neg = TRUE; + i++; + } + + for (; i < len; i++) + { + if (str[i] >= '0' && str[i] <= '9') + { + value *= 10; + value += str[i] - '0'; + } + else + break; + } + + return neg ? -value : value; +} + + +// +// This wrapper function for lws_write exists because of the +// libwebsockets' requirement for pre-pad of the data buffer +// for header information. +// +int mapLWSWrite(struct lws *wsi, const unsigned char *data, const size_t len) +{ + if (wsi == NULL) + return 0; + + if (len >= SET_LWS_BUF_SIZE - SET_LWS_BUF_PAD) + return -1; + + // Costs us an extra copy + memcpy(setLWSBuffer + SET_LWS_BUF_PAD, data, len); + + return lws_write(wsi, setLWSBuffer + SET_LWS_BUF_PAD, len, LWS_WRITE_TEXT); +} + + +// Creates a JSON format results string from the list of matches, +// with additional information in the first array. +void mapCreateResultStr(char **buf, size_t *bufLen, const MAPMatch *matches, + const int nmatches, const int nlimit, const BOOL type, const BOOL centered, + const int centerX, const int centerY, const MapBlock *pattern) +{ + size_t bufSize = 0; + char *vstr; + *bufLen = 0; + *buf = NULL; + + if (type) + { + vstr = th_strdup_printf( + "RESULT:[[%d,%d,%d,%d,%d,%d,%d]", + nmatches, nlimit, + centered, centerX, centerY, + pattern != NULL ? pattern->width : -1, + pattern != NULL ? pattern->height : -1); + } + else + { + vstr = th_strdup_printf( + "RESULT:[[%d,%d]", + nmatches, nlimit); + } + + th_strbuf_puts(buf, &bufSize, bufLen, vstr); + th_free(vstr); + + for (int n = 0; n < nmatches; n++) + { + const MAPMatch *match = &matches[n]; + const char *vstart = (n == 0) ? "," : ""; + const char *vend = (n < nmatches - 1) ? "," : ""; + + if (type) + { + vstr = th_strdup_printf( + "%s[\"%s\",%d,%d,%d,%d]%s", + vstart, + match->map, + match->mx, match->my, + match->wx, match->wy, + vend); + + th_strbuf_puts(buf, &bufSize, bufLen, vstr); + th_free(vstr); + } + else + { + vstr = th_strdup_printf( + "%s[\"%s\",%d,%d,%d,%d,%d,%d,%1.3f,[", + vstart, + match->map, + match->mx, match->my, + match->wx, match->wy, + match->marker->flags, + match->nname, + match->marker->vsort.v_float); + + th_strbuf_puts(buf, &bufSize, bufLen, vstr); + th_free(vstr); + + for (int i = 0; i < match->marker->nnames; i++) + { + vstr = th_strdup_printf("\"%s\"%s", + match->marker->names[i].name, + (i < match->marker->nnames - 1) ? "," : ""); + + th_strbuf_puts(buf, &bufSize, bufLen, vstr); + th_free(vstr); + } + + th_strbuf_puts(buf, &bufSize, bufLen, "]]"); + th_strbuf_puts(buf, &bufSize, bufLen, vend); + } + } + + th_strbuf_puts(buf, &bufSize, bufLen, "]"); +} + + +BOOL mapParseIntValue(const unsigned char *data, const size_t len, size_t *offs, int *val) +{ + size_t start = *offs; + + for (; *offs < len && data[*offs] != ':';) + (*offs)++; + + if (data[*offs] != ':' || *offs >= len) + return FALSE; + + *val = mapAtoI((char *) data + start, *offs); + return TRUE; +} + + +void mapPerformSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) +{ + static const char *cleanChars = " *@?%CX"; + size_t ncleanChars = strlen(cleanChars); + MapBlock *pattern = NULL, *ptmp = NULL; + BOOL mapList[SET_MAX_MAPS]; + MAPMatch matches[SET_MAX_MATCHES]; + int width, height, centerX = 0, centerY = 0, + nmatches = 0, nmapList, reqMatches, + maxMatches = SET_MAX_MATCHES; + BOOL centered = FALSE; + size_t offs = 0; + + // Get requested number of matches + if (!mapParseIntValue(data, len, &offs, &reqMatches)) + { + *verr = "Invalid search query."; + goto out; + } + + reqMatches = maxMatches; + if (maxMatches < 1 || maxMatches > SET_MAX_MATCHES) + maxMatches = SET_MAX_MATCHES; + + mapMSG(2, "Requested %d matches, limiting to %d.\n", + reqMatches, maxMatches); + + // Get active map list + nmapList = 0; + memset(&mapList, 0, sizeof(mapList)); + while (offs < len) + { + size_t offs2; + BOOL found = FALSE; + + // Find next separator or end \n + for (offs2 = offs; offs2 < len && data[offs2] != ':' && data[offs2] != '\n';) + offs2++; + + // Check against map list + if (offs2 > offs) + { + for (int nmap = 0; nmap < optNMaps && !found; nmap++) + { + const char *name = optMaps[nmap].locFile.continent; + const size_t slen = strlen(name); + + // If the map name matches requested, enable it + if (offs2 >= offs + slen && + memcmp(data + offs, name, slen) == 0) + { + mapList[nmap] = TRUE; + nmapList++; + found = TRUE; + } + } + + if (!found) + { + char *tmps = th_strndup_no0((const char *) data + offs, offs2 - offs); + + mapMSG(-1, "Unknown map spec '%s'.\n", tmps); + *verr = "Unknown map spec."; + + th_free(tmps); + goto out; + } + } + + // Check for separator or end + if (data[offs2] != ':') + { + offs = offs2; + break; + } + else + offs = offs2 + 1; + } + + // Check for remaining data + if (offs + 1 >= len || data[offs] != '\n') + { + *verr = "No map pattern data!"; + goto out; + } + offs++; + + // Parse pattern block dimensions + mapBlockParseDimensions(data + offs, len - offs, &width, &height); + if (width <= 0 || height <= 0) + { + *verr = "Could not parse map block dimensions."; + goto out; + } + + mapMSG(2, "Parsed block size %d x %d\n", width, height); + + // Do basic checks for sanity + if (width * height < 3) + { + *verr = "Search block pattern too small."; + goto out; + } + + if (width * height > 30 * 30) + { + *verr = "Search block pattern too large."; + goto out; + } + + // Allocate and attempt to parse the block + if ((pattern = mapBlockAlloc(width, height)) == NULL) + goto out; + + if (!mapBlockParse(data + offs, len - offs, pattern)) + { + *verr = "Error parsing map block data."; + goto out; + } + + // Crop the pattern block + if (mapBlockAutoCrop(&ptmp, pattern, + (unsigned char *) cleanChars, ncleanChars, + 0, 0, 0, 0, FALSE)) + { + mapBlockFree(pattern); + pattern = ptmp; + mapMSG(2, "Cropped block size: %d x %d\n", + pattern->width, pattern->height); + } + + // Sanity checks against the cropped block + if (pattern->width * pattern->height < 3) + { + *verr = "Search block pattern too small."; + goto out; + } + + // Print the cropped block + if (th_verbosity >= 2) + { + FILE *tmp = (setLogFH != NULL) ? setLogFH : stdout; + fprintf(tmp, "----------------------------\n"); + mapBlockPrint(tmp, pattern); + fprintf(tmp, "----------------------------\n"); + } + + // Entropy check + int entropy = mapBlockGetEntropy(pattern, cleanChars, ncleanChars); + mapMSG(2, "Block entropy %d\n", entropy); + + if ((entropy < 2 && width < 4 && height < 4) || + (entropy < 3 && width * height < 4)) + { + *verr = "Search block entropy insufficient."; + goto out; + } + + // Find pattern center marker, if any + centered = mapBlockFindCenter(pattern, + (unsigned char*) "*@X", 3, + ¢erX, ¢erY, 10); + + if (centered) + mapMSG(2, "Center at %d, %d\n", centerX, centerY); + + // Clean the pattern from characters we do not want to match + mapBlockClean(pattern, (unsigned char *) cleanChars, ncleanChars); + + // + // Search the maps .. enabled or if none specified, all of them + // + for (int nmap = 0; nmap < optNMaps; nmap++) + if (mapList[nmap] || nmapList == 0) + { + MAPInfoCtx *info = &optMaps[nmap]; + + for (int oy = 0; oy < info->map->height - pattern->height; oy++) + for (int ox = 0; ox < info->map->width - pattern->width; ox++) + { + // Check for match + if (mapMatchBlock(info->map, pattern, ox, oy)) + { + // Okay, add the match to our list + MAPMatch *match = &matches[nmatches++]; + + match->marker = NULL; + match->map = info->locFile.continent; + match->mx = ox + 1 + centerX; + match->my = oy + 1 + centerY; + match->wx = optWorldXC + info->locFile.xoffs + ox + centerX; + match->wy = optWorldYC + info->locFile.yoffs + oy + centerY; + + // Check for max matches + if (nmatches >= maxMatches) + goto out; + } + } + } + +out: + // If an error occured, bail out now + if (*verr != NULL) + { + mapBlockFree(pattern); + return; + } + + // We got some matches, output them as a JSON array + char *buf; + size_t bufLen; + + mapCreateResultStr(&buf, &bufLen, + matches, nmatches, maxMatches, + TRUE, centered, centerX, centerY, pattern); + + mapBlockFree(pattern); + + mapMSG(2, "%s\n", buf); + mapLWSWrite(wsi, (unsigned char *) buf, bufLen); + th_free(buf); +} + + +int mapCompareDistance(const void *pa, const void *pb) +{ + const LocMarker *va = *(const LocMarker **) pa, + *vb = *(const LocMarker **) pb; + + return va->vsort.v_float - vb->vsort.v_float; +} + + +int mapNearbyLocationSearch(LocMarker ***pnearest, + const int xc, const int yc, const int maxDist, char **verr) +{ + int nnearest = 0; + LocMarker **nearest; + + // Allocate memory for results + if ((*pnearest = nearest = th_malloc(sizeof(LocMarker *) * optNMapLocations)) == NULL) + { + *verr = "Could not allocate memory for temporary sorting buffer."; + goto out; + } + + // Calculate distances and filter by maxDist + for (int nloc = 0; nloc < optNMapLocations; nloc++) + { + LocMarker *marker = optMapLocations[nloc]; + int dx = xc - (optWorldXC + marker->xc + marker->file->xoffs), + dy = yc - (optWorldYC + marker->yc + marker->file->yoffs); + float dist = sqrt(dx * dx + dy * dy); + + if (maxDist < 0 || dist <= maxDist) + { + marker->vsort.v_float = dist; + nearest[nnearest++] = marker; + } + } + + // Sort the locations based on distance + if (nnearest > 0) + qsort(nearest, nnearest, sizeof(LocMarker *), mapCompareDistance); + +out: + + return nnearest; +} + + +void mapLocationSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) +{ + MAPMatch matches[SET_MAX_MATCHES]; + int nmatches = 0; + char *pattern = NULL; + size_t offs1, offs2, slen; + + if (len == 0) + { + *verr = "Search pattern too short."; + goto out; + } + + // Check search pattern length + for (offs1 = 0; offs1 < len && th_isspace(data[offs1]); ) offs1++; + for (offs2 = len - 1; offs2 > offs1 && (data[offs2] == 0 || th_isspace(data[offs2])); ) offs2--; + + slen = offs2 - offs1 + 1; + if (slen < 2) + { + *verr = "Search pattern too short."; + goto out; + } + + if (slen > 25) + { + *verr = "Search pattern too long."; + goto out; + } + + if ((pattern = th_strndup((char *) data + offs1, slen)) == NULL) + { + *verr = "Could not allocate search pattern memory."; + goto out; + } + + mapMSG(2, "Search pattern: '%s'\n", pattern); + + // Search the locations + for (int nmap = 0; nmap < optNMaps; nmap++) + { + MAPInfoCtx *info = &optMaps[nmap]; + for (int nloc = 0; nloc < info->loc.nlocations; nloc++) + { + LocMarker *marker = info->loc.locations[nloc]; + for (int nname = 0; nname < marker->nnames; nname++) + if ((marker->flags & LOCF_INVIS) == 0 && + th_strcasematch(marker->names[nname].name, pattern)) + { + // Okay, add the match to our list + MAPMatch *match = &matches[nmatches++]; + + match->marker = marker; + match->nname = nname; + match->map = info->locFile.continent; + match->mx = marker->xc + 1; + match->my = marker->yc + 1; + match->wx = optWorldXC + info->locFile.xoffs + marker->xc; + match->wy = optWorldYC + info->locFile.yoffs + marker->yc; + + // Check for max matches + if (nmatches >= SET_MAX_MATCHES) + goto out; + + // Bail out from this location, in order not to have dupes for it + break; + } + } + } + +out: + th_free(pattern); + + // If an error occured, bail out now + if (*verr != NULL) + return; + + // We got some matches, output them as a JSON array + char *buf; + size_t bufLen; + + mapCreateResultStr(&buf, &bufLen, + matches, nmatches, SET_MAX_MATCHES, + FALSE, FALSE, 0, 0, NULL); + + mapMSG(2, "%s\n", buf); + mapLWSWrite(wsi, (unsigned char *) buf, bufLen); + th_free(buf); +} + + +void mapNearLocationSearch(struct lws *wsi, const unsigned char *data, const size_t len, char **verr) +{ + MAPMatch matches[SET_MAX_MATCHES]; + LocMarker **nearest = NULL; + int findXC, findYC, maxDist, nnearest = 0, maxMatches = SET_MAX_MATCHES; + size_t offs = 0; + + // Get coordinates + if (!mapParseIntValue(data, len, &offs, &findXC)) + { + *verr = "Invalid search query, no global X coordinate specified."; + goto out; + } + + offs++; + + if (!mapParseIntValue(data, len, &offs, &findYC)) + { + *verr = "Invalid search query, no global Y coordinate specified."; + goto out; + } + + offs++; + + // Get max distance, default to some value + if (!mapParseIntValue(data, len, &offs, &maxDist)) + maxDist = 50; + else + { + offs++; + + // Get max matches amount + if (!mapParseIntValue(data, len, &offs, &maxMatches)) + maxMatches = SET_MAX_MATCHES; + } + + // Check values + if (findXC < 0 || findYC < 0) + { + *verr = "Invalid search coordinates."; + goto out; + } + + if (maxDist < 1 || maxDist > 1000) + { + *verr = "Invalid maximum search distance."; + goto out; + } + + if (maxMatches < 1) + maxMatches = 1; + else + if (maxMatches > SET_MAX_MATCHES) + maxMatches = SET_MAX_MATCHES; + + // Find nearest locations + nnearest = mapNearbyLocationSearch(&nearest, findXC, findYC, maxDist, verr); + if (*verr != NULL) + goto out; + + if (nnearest >= maxMatches) + nnearest = maxMatches; + + for (int nloc = 0; nloc < nnearest; nloc++) + { + LocMarker *marker = nearest[nloc]; + MAPMatch *match = &matches[nloc]; + + match->map = marker->file->continent; + match->marker = marker; + match->nname = 0; + match->mx = marker->xc + 1; + match->my = marker->yc + 1; + match->wx = optWorldXC + marker->file->xoffs + marker->xc; + match->wy = optWorldYC + marker->file->yoffs + marker->yc; + } + +out: + th_free(nearest); + + // If an error occured, bail out now + if (*verr != NULL) + return; + + // We got some matches, output them as a JSON array + char *buf; + size_t bufLen; + + mapCreateResultStr(&buf, &bufLen, + matches, nnearest, SET_MAX_MATCHES, + FALSE, FALSE, 0, 0, NULL); + + mapMSG(2, "%s\n", buf); + mapLWSWrite(wsi, (unsigned char *) buf, bufLen); + th_free(buf); +} + + +void mapHandleRequest(struct lws *wsi, char *data, const size_t len, char **verr) +{ + unsigned char *udata = (unsigned char *) data; + + // Check what the request is about? + if (len >= 10 + 2 && strncmp(data, "MAPSEARCH:", 10) == 0) + { + mapPerformSearch(wsi, udata + 10, len - 10, verr); + } + else + if (len >= 7 && strncmp(data, "GETMAPS", 7) == 0) + { + // Client wants a list of available maps + char *buf = NULL; + size_t bufLen = 0, bufSize = 0; + + mapMSG(1, "[%p] Sending map information.\n", wsi); + + th_strbuf_puts(&buf, &bufSize, &bufLen, "MAPS:["); + + for (int n = 0; n < optNMaps; n++) + { + MAPInfoCtx *info = &optMaps[n]; + char *vstr = th_strdup_printf( + "[\"%s\",%d,%d]%s", + info->locFile.continent, + info->locFile.xoffs + optWorldXC, + info->locFile.yoffs + optWorldYC, + (n < optNMaps - 1) ? "," : ""); + + th_strbuf_puts(&buf, &bufSize, &bufLen, vstr); + th_free(vstr); + } + + th_strbuf_puts(&buf, &bufSize, &bufLen, "]"); + mapLWSWrite(wsi, (unsigned char *) buf, bufLen); + th_free(buf); + } + else + if (len >= 10 + 1 && strncmp(data, "LOCSEARCH:", 10) == 0) + { + mapLocationSearch(wsi, udata + 10, len - 10, verr); + } + else + if (len >= 8 + 3 && strncmp(data, "LOCNEAR:", 8) == 0) + { + mapNearLocationSearch(wsi, udata + 8, len - 8, verr); + } + else + { + // Unknown or invalid query + *verr = "Invalid command, and/or not enough data."; + } +} + + +int mapLWSCallback(struct lws *wsi, + enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + (void) user; + + switch (reason) + { + case LWS_CALLBACK_ESTABLISHED: + { + char strName[256], strIP[64]; + int fd = lws_get_socket_fd(wsi); + lws_get_peer_addresses(wsi, fd, strName, sizeof(strName), strIP, sizeof(strIP)); + mapMSG(2, "[%p] Client connection from %s [%s]\n", wsi, strIP, strName); + } + break; + + case LWS_CALLBACK_RECEIVE: + { + char *verr = NULL; + + mapHandleRequest(wsi, (char *) in, len, &verr); + + // Check for errors .. + if (verr != NULL) + { + char *vstr = th_strdup_printf("ERROR:%s", verr); + mapERR("[%p] %s\n", wsi, verr); + mapLWSWrite(wsi, (unsigned char *) vstr, strlen(vstr)); + th_free(vstr); + } + + // End communication + lws_close_reason(wsi, LWS_CLOSE_STATUS_NOSTATUS, NULL, 0); + } + break; + + default: + break; + } + + return 0; +} + + +static const struct lws_extension mapLWSExtensions[] = +{ + { + "permessage-deflate", + lws_extension_callback_pm_deflate, + "permessage-deflate" + }, + { + "deflate-frame", + lws_extension_callback_pm_deflate, + "deflate_frame" + }, + { NULL, NULL, NULL } +}; + + +static const struct lws_protocols mapLWSProtocols[] = +{ + { "default", &mapLWSCallback, 0, 0, 0, NULL +#ifdef HAVE_LIBWEBSOCKETS22 + , 0 +#endif + }, + + { NULL, NULL, 0, 0, 0, NULL +#ifdef HAVE_LIBWEBSOCKETS22 + , 0 +#endif + }, +}; + + +int mapReadFile(const char *filename, uint8_t **pbuf, size_t *pbufSize, + const size_t bufInit, const size_t bufGrow) +{ + size_t readSize, dataSize, dataPos; + FILE *fh = NULL; + int res = THERR_OK; + + if ((fh = fopen(filename, "rb")) == NULL) + { + res = THERR_FOPEN; + goto out; + } + + // Allocate initial data buffer + readSize = dataSize = bufInit; + if ((*pbuf = th_malloc(dataSize)) == NULL) + { + res = THERR_MALLOC; + goto out; + } + + dataPos = 0; + *pbufSize = 0; + while (!feof(fh) && !ferror(fh)) + { + size_t read = fread((*pbuf) + dataPos, 1, readSize, fh); + dataPos += read; + (*pbufSize) += read; + + if (*pbufSize >= dataSize) + { + readSize = bufGrow; + dataSize += bufGrow; + if ((*pbuf = th_realloc(*pbuf, dataSize)) == NULL) + { + res = THERR_MALLOC; + goto out; + } + } + else + break; + } + +out: + if (fh != NULL) + fclose(fh); + + return res; +} + + +BOOL mapLoadMaps(void) +{ + mapMSG(1, "Trying to load %d map specs. World origin at [%d, %d].\n", + optNMaps, optWorldXC, optWorldYC); + + for (int nmap = 0; nmap < optNMaps; nmap++) + { + MAPInfoCtx *info = &optMaps[nmap]; + FILE *fh; + + mapMSG(1, "Map ID '%s', data '%s', locations '%s' at [%d, %d]\n", + info->locFile.continent, + info->mapFilename, + info->locFile.filename, + info->locFile.xoffs, info->locFile.yoffs); + + if ((info->map = mapBlockParseFile(info->mapFilename, FALSE)) == NULL) + { + mapERR("Could not read map data file '%s'.\n", info->mapFilename); + return FALSE; + } + + if ((fh = fopen(info->locFile.filename, "rb")) == NULL) + { + mapERR("Could not open location file '%s' for reading.\n", + info->locFile.filename); + return FALSE; + } + + if (!locParseLocStream(fh, &info->locFile, &(info->loc), 0, 0)) + { + fclose(fh); + return FALSE; + } + + fclose(fh); + } + + // Get total number of non-invis locations + optNMapLocations = 0; + for (int nmap = 0; nmap < optNMaps; nmap++) + { + MAPInfoCtx *info = &optMaps[nmap]; + for (int nloc = 0; nloc < info->loc.nlocations; nloc++) + { + LocMarker *marker = info->loc.locations[nloc]; + if ((marker->flags & LOCF_INVIS) == 0) + optNMapLocations++; + } + } + + // Create large array of location pointers for sorting purposes + if ((optMapLocations = th_malloc(sizeof(LocMarker *) * optNMapLocations)) == NULL) + { + mapERR("Could not allocate memory for location sorting buffer of %d entries.\n", + optNMapLocations); + return FALSE; + } + + for (int nmap = 0, index = 0; nmap < optNMaps; nmap++) + { + MAPInfoCtx *info = &optMaps[nmap]; + for (int nloc = 0; nloc < info->loc.nlocations; nloc++) + { + LocMarker *marker = info->loc.locations[nloc]; + if ((marker->flags & LOCF_INVIS) == 0) + optMapLocations[index++] = marker; + } + } + + return TRUE; +} + + +void mapFreeMaps(void) +{ + for (int n = 0; n < optNMaps; n++) + { + MAPInfoCtx *info = &optMaps[n]; + + mapBlockFree(info->map); + locFreeMapLocations(&info->loc); + } + + th_free_r(&optMapLocations); +} + + +#ifdef HAVE_LIBWEBSOCKETS32 +void mapSigHandler(void *handle, int signum) +#else +void mapSigHandler(uv_signal_t *handle, int signum) +#endif +{ + (void) handle; + + switch (signum) + { + case SIGTERM: + case SIGINT: + mapERR("Signal %d caught, exiting...\n", signum); +#ifdef HAVE_LIBWEBSOCKETS32 + lws_context_destroy(setLWSContext); +#else + lws_libuv_stop(setLWSContext); +#endif + break; + + default: + mapERR("Signal %d caught, aborting...\n", signum); + signal(SIGABRT, SIG_DFL); + abort(); + break; + } +} + + +int main(int argc, char *argv[]) +{ + // Initialize + th_init("MapSearch", "Map Search WebSockets server", "0.8", NULL, NULL); + th_verbosity = 0; + + memset(&optMaps, 0, sizeof(optMaps)); + memset(&optListenTo, 0, sizeof(optListenTo)); + + // Parse command line arguments + BOOL argsOK = th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, 0); + + if (!argsOK) + return -2; + + if (optNMaps == 0) + { + mapERR("No maps specified.\n"); + goto exit; + } + + if (optNListenTo == 0 && optTest == NULL) + { + mapERR("No listeners specified.\n"); + goto exit; + } + + // Initialize logging + if (optLogFilename != NULL && optTest == NULL) + { + if ((setLogFH = fopen(optLogFilename, "a")) == NULL) + { + int err = th_get_error(); + mapERR("Could not open log file '%s' for writing (%d): %s\n", + err, th_error_str(err)); + goto exit; + } + } + + // Load maps + if (!mapLoadMaps()) + goto exit; + + // Check for test mode + if (optTest != NULL) + { + mapMSG(1, "Running in test mode, input '%s'.\n", optTest); + uint8_t *buf = NULL; + size_t bufSize; + + if (mapReadFile(optTest, &buf, &bufSize, 512, 512) == THERR_OK) + { + char *verr = NULL; + if (optBenchmark > 0) + { + int save = th_verbosity; + mapMSG(0, "Benchmarking for %d cycles ..\n", optBenchmark); + th_verbosity = -1; + for (int n = 0; n < optBenchmark; n++) + { + printf("."); + fflush(stdout); + mapHandleRequest(NULL, (char *) buf, bufSize, &verr); + if (verr != NULL) + { + mapERR("%s\n", verr); + break; + } + } + th_verbosity = save; + printf("\n"); + mapMSG(0, "Finished.\n"); + } + else + { + mapHandleRequest(NULL, (char *) buf, bufSize, &verr); + if (verr != NULL) + mapERR("%s\n", verr); + } + } + else + mapERR("Could not read test file.\n"); + + th_free(buf); + goto exit; + } + + // Initialize libwebsockets and create context + mapMSG(1, "Creating libwebsockets context.\n"); + + if ((setLWSBuffer = th_malloc(SET_LWS_BUF_SIZE)) == NULL) + { + mapERR("Could not allocate %d bytes of memory for LWS buffer.\n", + SET_LWS_BUF_SIZE); + + goto exit; + } + + // Setup log handler + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE, mapLogWS); + + // Create the LWS context(s) + MAPListenerCtx *ctx = optListenTo[0]; + struct lws_context_creation_info info; + int info_options; + memset(&info, 0, sizeof(info)); + + info.gid = optGID; + info.uid = optUID; + info.max_http_header_pool = 16; + info_options = info.options = + LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_VALIDATE_UTF8 | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT +#if defined(LWS_WITH_LIBUV) || defined(LWS_USE_LIBUV) || !defined(HAVE_LIBWEBSOCKETS32) + | LWS_SERVER_OPTION_LIBUV +#endif + ; + + info.protocols = mapLWSProtocols; + info.extensions = mapLWSExtensions; + info.ssl_cipher_list = optSSLCipherList; + info.timeout_secs = 5; +#ifdef HAVE_LIBWEBSOCKETS32 + info.signal_cb = mapSigHandler; +#endif + + if ((setLWSContext = lws_create_context(&info)) == NULL) + { + mapERR("libwebsocket initialization failed.\n"); + goto exit; + } + + // Create listener LWS vhosts .. + for (int n = 0; n < optNListenTo; n++) + { + char *ipvMode = ""; + ctx = optListenTo[n]; + + info.port = ctx->port; + info.iface = ctx->interface; + info.options = info_options; + + switch (ctx->ipvMode) + { + case 0: + ipvMode = "IPv4 + IPv6"; + break; + + case 4: + ipvMode = "IPv4 only"; + info.options |= LWS_SERVER_OPTION_DISABLE_IPV6; + break; + + case 6: + ipvMode = "IPv6 only"; + info.options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE; + break; + } + + if (ctx->useSSL) + { + mapMSG(1, "Listen to %s:%d (wss) [vhost='%s', cert='%s', key='%s', ca='%s'] [%s]\n", + ctx->interface != NULL ? ctx->interface : "*", + ctx->port, + ctx->vhostname, + ctx->sslCertFile, ctx->sslKeyFile, + (ctx->sslCAFile != NULL && ctx->sslCAFile[0]) ? ctx->sslCAFile : "NONE", + ipvMode); + + info.vhost_name = ctx->vhostname; + info.ssl_cert_filepath = ctx->sslCertFile; + info.ssl_private_key_filepath = ctx->sslKeyFile; + info.ssl_ca_filepath = (ctx->sslCAFile != NULL && ctx->sslCAFile[0]) ? ctx->sslCAFile : NULL; + } + else + { + mapMSG(1, "Listen to %s:%d (ws) [%s]\n", + ctx->interface != NULL ? ctx->interface : "*", + ctx->port, + ipvMode); + + info.vhost_name = NULL; + info.ssl_cert_filepath = NULL; + info.ssl_private_key_filepath = NULL; + info.ssl_ca_filepath = NULL; + } + + if ((ctx->vhost = lws_create_vhost(setLWSContext, &info)) == NULL) + { + mapERR("LWS vhost creation failed!\n"); + goto exit; + } + } + + // Drop privileges + lws_finalize_startup(setLWSContext); + +#ifndef HAVE_LIBWEBSOCKETS32 + // Set up signal handlers + mapMSG(1, "Setting up signal handlers.\n"); + lws_uv_sigint_cfg(setLWSContext, 1, mapSigHandler); + + // Set up libuv event loop + if (lws_uv_initloop(setLWSContext, NULL, 0)) + { + mapERR("lws_uv_initloop() failed.\n"); + goto exit; + } +#endif + + // Start running .. + mapMSG(1, "Waiting for connections...\n"); + lws_service(setLWSContext, 0); + +exit: + // Shutdown sequence + mapMSG(1, "Server shutting down.\n"); + + if (setLWSContext != NULL) + lws_context_destroy(setLWSContext); + + for (int n = 0; n < optNListenTo; n++) + mapFreeListenCtx(optListenTo[n]); + + mapFreeMaps(); + + for (int n = 0; n < optNMaps; n++) + { + MAPInfoCtx *info = &optMaps[n]; + + th_free(info->mapFilename); + th_free(info->locFile.filename); + th_free(info->locFile.continent); + } + + th_free(setLWSBuffer); + + if (setLogFH) + fclose(setLogFH); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mapstats.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,322 @@ +/* + * Calculate terrain type (and other) statistics from ASCII map + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2007-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + + +typedef int (*compareFunc)(const void *, const void *); + + +enum +{ + SORT_NONE, + SORT_NAME, + SORT_SYMBOL, + SORT_AMOUNT +}; + +#define SET_MAX_FILES (512) + +char *srcFilenames[SET_MAX_FILES], + *dstFilename = NULL; +int nsrcFilenames = 0; + +BOOL optUseOldFormat = FALSE, + optInputIsDiff = FALSE, + optSortBy = SORT_NONE, + optCityFormat = FALSE; + + +typedef struct +{ + unsigned int n; + int piece; +} MapPieceStat; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'q', "quiet", "Be quiet", OPT_NONE }, + { 3, 'd', "input-diff", "Input is a diff", OPT_NONE }, + { 4, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, + { 5, 'o', "output", "Output filename", OPT_ARGREQ }, + { 6, 's', "sort", "Sort (by name,symbol,amount)", OPT_ARGREQ }, + { 7, 'c', "city-format","Input is a city map", OPT_NONE }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <input mapfile>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + th_verbosity = -1; + break; + + case 3: + optInputIsDiff = TRUE; + THMSG(2, "Input is a 'diff', handling it as such.\n"); + break; + + case 4: + optUseOldFormat = TRUE; + THMSG(2, "Input is using old map symbols/colors.\n"); + break; + + case 5: + dstFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", dstFilename); + break; + + case 6: + if (!strncmp(optArg, "n", 1)) + optSortBy = SORT_NAME; + else + if (!strncmp(optArg, "s", 1)) + optSortBy = SORT_SYMBOL; + else + if (!strncmp(optArg, "a", 1)) + optSortBy = SORT_AMOUNT; + else + { + THERR("Unknown sorting method name '%s'!\n", optArg); + exit(1); + } + + THMSG(2, "Sorting via method %d\n", optSortBy); + break; + + case 7: + optCityFormat = TRUE; + THMSG(2, "Input is handled as a city map\n"); + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (nsrcFilenames < SET_MAX_FILES) + { + srcFilenames[nsrcFilenames++] = currArg; + } + else + { + THERR("Too many input map files specified!\n"); + return FALSE; + } + + return TRUE; +} + + +int compareByName(const void *p1, const void *p2) +{ + MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; + const MapPiece *mp1 = &mapPieces[vp1->piece], *mp2 = &mapPieces[vp2->piece]; + + if (optUseOldFormat) + { + if (mp1->oldSymbol >= 0 && mp2->oldSymbol >= 0) + return strcmp(mp1->desc, mp2->desc); + else + return -1; + } + else + { + if (mp1->symbol >= 0 && mp2->symbol >= 0) + return strcmp(mp1->desc, mp2->desc); + else + return -1; + } +} + + +int compareBySymbol(const void *p1, const void *p2) +{ + MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; + const MapPiece *mp1 = &mapPieces[vp1->piece], *mp2 = &mapPieces[vp2->piece]; + + if (optUseOldFormat) + { + if (mp1->oldSymbol >= 0 && mp2->oldSymbol >= 0) + return mp1->oldSymbol - mp2->oldSymbol; + else + return -1; + } + else + { + if (mp1->symbol >= 0 && mp2->symbol >= 0) + return mp1->symbol - mp2->symbol; + else + return -1; + } +} + + +int compareByAmount(const void *p1, const void *p2) +{ + MapPieceStat *vp1 = (MapPieceStat *) p1, *vp2 = (MapPieceStat *) p2; + + if (vp1->n < vp2->n) + return -1; + else + if (vp1->n > vp2->n) + return 1; + else + return 0; +} + + +/* Main program + */ +int main(int argc, char *argv[]) +{ + FILE *outFile; + MapBlock *map; + unsigned int statTotal, statUnknown; + MapPieceStat statPieces[nmapPieces]; + + // Initialize + th_init("mapstats", "ASCII map statistics generator", "0.2", NULL, NULL); + th_verbosity = 1; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + exit(1); + + if (nsrcFilenames == 0) + { + THERR("Nothing to do. (try --help)\n"); + exit(0); + } + + // Read input file + statTotal = statUnknown = 0; + for (int i = 0; i < nmapPieces; i++) + { + statPieces[i].n = 0; + statPieces[i].piece = i; + } + + for (int n = 0; n < nsrcFilenames; n++) + { + THMSG(1, "Reading map file '%s'\n", srcFilenames[n]); + + if ((map = mapBlockParseFile(srcFilenames[n], optInputIsDiff)) == NULL) + { + THERR("Error reading map file '%s'!\n", + srcFilenames[n]); + exit(1); + } + + THMSG(1, "Analyzing %dx%d area ...\n", + map->width, map->height); + + for (int y = 0; y < map->height; y++) + { + unsigned char *d = map->data + (map->scansize * y); + for (int x = 0; x < map->width; x++) + { + int c; + if ((c = muGetMapPieceIndex(*d, optUseOldFormat, optCityFormat)) >= 0) + statPieces[c].n++; + else + statUnknown++; + + statTotal++; + d++; + } + } + mapBlockFree(map); + } + + // Sort results, if needed + if (optSortBy != SORT_NONE) + { + compareFunc tmpFunc; + + THMSG(2, "Sorting results ...\n"); + switch (optSortBy) + { + case SORT_NAME: tmpFunc = compareByName; break; + case SORT_SYMBOL: tmpFunc = compareBySymbol; break; + case SORT_AMOUNT: tmpFunc = compareByAmount; break; + default: + THERR("Internal error, no sort function for sort type %d.\n", optSortBy); + exit(2); + break; + } + qsort(statPieces, nmapPieces, sizeof(MapPieceStat), tmpFunc); + } + + // Open output file + if (dstFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(dstFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + dstFilename); + exit(1); + } + + for (int i = 0; i < nmapPieces; i++) + { + const int p = statPieces[i].piece; + int symbol = optUseOldFormat ? mapPieces[p].oldSymbol : mapPieces[p].symbol; + if (symbol >= 0) + { + fprintf(outFile, "%-20s [%c]: %6d (%1.3f%%)\n", + mapPieces[p].desc, + symbol, + statPieces[i].n, + (statPieces[i].n * 100.0f / statTotal)); + } + } + + fprintf(outFile, " %d total, %d unknown.\n", + statTotal, statUnknown); + + fclose(outFile); + + THMSG(1, "Done.\n"); + + exit(0); + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mkcitymap.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,444 @@ +/* + * Create interactive XHTML+CSS+JS map from input + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "liblocfile.h" +#include "th_args.h" +#include "th_string.h" + + +char *optDestFilename = NULL, + *optMapFilename = NULL, + *optLocFilename = NULL, + *optHeadFilename = NULL, + *optMapTitle = NULL; +BOOL optUseOldFormat = FALSE; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'o', "output", "Output filename", OPT_ARGREQ }, + { 3, 't', "title", "Map title", OPT_ARGREQ }, + { 4, 'h', "header-file", "Specify extra <head> data in file", OPT_ARGREQ }, + { 5, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, +}; + +const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <mapfile> <locfile>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + optDestFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", optDestFilename); + break; + + case 3: + optMapTitle = optArg; + THMSG(2, "Map title string set to '%s'.\n", optMapTitle); + break; + + case 4: + optHeadFilename = optArg; + THMSG(2, "Head filename set to '%s'.\n", optHeadFilename); + break; + + case 5: + optUseOldFormat = TRUE; + THMSG(2, "Input is using old map symbols/colors.\n"); + break; + + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (!optMapFilename) + optMapFilename = currArg; + else + if (!optLocFilename) + optLocFilename = currArg; + else + { + THERR("Too many filenames specified!\n"); + return FALSE; + } + + return TRUE; +} + + +void outputHTMLHeader(FILE *outFile, const MapLocations *locs) +{ + (void) locs; + + muPrintHTMLhead(outFile, optMapTitle, TRUE); + + if (optHeadFilename != NULL && + muCopyFileToStream(outFile, optHeadFilename) < 0) + { + THERR("Error copying urchin file '%s' to output.\n", optHeadFilename); + } + + fprintf(outFile, " <style type=\"text/css\">\n"); + muPrintHTMLcolors(outFile, "span", NULL, NULL); + fprintf(outFile, " </style>\n"); + + + fprintf(outFile, + "</head>\n" + "<body onload=\"mapOnLoad();\">\n" + ); + + if (optMapTitle) + { + fprintf(outFile, "<h1>"); + fputse(optMapTitle, outFile); + fprintf(outFile, "</h1>\n"); + } + + fprintf(outFile, + "<div class=\"map %s\">" + "<pre class=\"map\">", + optUseOldFormat ? "old" : "new" + ); +} + + +const char *getCityLocationType(const LocMarker *marker) +{ + switch (marker->flags & LOCF_M_MASK) + { +// case LOCF_M_CITY: return "special"; + case LOCF_M_PCITY: return "gov"; + default: + switch (marker->flags & LOCF_T_MASK) + { + case LOCF_T_SHRINE: return "shop"; + 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"; +} + + +static char *getExtraNameData(const LocMarker *marker) +{ + return th_strdup_printf( + "%s", + (marker->flags & LOCF_CLOSED) ? " <b>[CLOSED]</b>" : "" + ); +} + + +static int compareLocation(const void *pp1, const void *pp2) +{ + const LocMarker + *vp1 = *(const LocMarker **) pp1, + *vp2 = *(const LocMarker **) pp2; + + if (vp1->flags == vp2->flags) + return th_strcasecmp(vp1->names[0].name, vp2->names[0].name); + else + return (vp1->flags & LOCF_MASK) - (vp2->flags & LOCF_MASK); +} + + +void outputHTMLFooter(FILE *outFile, MapLocations *locs) +{ + char *tmps; + + fprintf(outFile, + "</pre>" + "</div>\n" + "<div class=\"loctab\">\n" + ); + + qsort(locs->locations, locs->nlocations, sizeof(LocMarker *), compareLocation); + + for (int n = 0; n < locs->nlocations; n++) + { + const LocMarker *marker = locs->locations[n]; + if (marker->flags & LOCF_INVIS) + continue; + + if ((tmps = getExtraNameData(marker)) == NULL) + return; + + for (int i = 0; i < marker->nnames; i++) + { + + fprintf(outFile, + "<a class=\"loc %s%s lt%s lt%d\" id=\"listloc%d_%d\" href=\"?%d_%d\" " + "onmouseover=\"%s('%d_%d');\" onmouseout=\"qn();\">", + i == 0 ? "ltprimary" : "ltsecondary", + i == 0 && marker->nnames > 1 ? " ltsubitems" : "", + getCityLocationType(marker), + marker->align, + marker->xc, marker->yc, + marker->xc, marker->yc, + (marker->freeform /* be less spammy in the list || marker->nnames > 1 */) ? "sttq" : "qh", + marker->xc, marker->yc); + + fputse(marker->names[i].name, outFile); + + fprintf(outFile, "%s</a>\n", tmps); + } + + th_free(tmps); + } + + fprintf(outFile, + "</div>\n" + ); + + for (int n = 0; n < locs->nlocations; n++) + { + const LocMarker *marker = locs->locations[n]; + if (marker->flags & LOCF_INVIS) + continue; + + if ((tmps = getExtraNameData(marker)) == NULL) + return; + + fprintf(outFile, + "<div class=\"tooltip holder\" id=\"tt%d_%d\">" + "<h1>%s%s</h1>", + marker->xc, marker->yc, + marker->names[0].name, + tmps); + + if (marker->nnames > 1) + { + for (int i = 1; i < marker->nnames; i++) + { + fprintf(outFile, "%s%s", + marker->names[i].name, + i + 1 < marker->nnames ? " ; " : ""); + } + } + + if (marker->freeform) + { + fprintf(outFile, "<br />%s", marker->freeform); + } + + fprintf(outFile, "</div>\n"); + + th_free(tmps); + } + + fprintf(outFile, + "</body>\n" + "</html>\n" + ); +} + + +void outputMapHTML(FILE *outFile, const MapBlock *map, const MapLocations *locs) +{ + int n, p = -1; + BOOL span = FALSE; + + for (int yc = 0; yc < map->height; yc++) + { + const unsigned char *dp = map->data + (map->scansize * yc); + for (int xc = 0; xc < map->width; xc++) + { + if ((n = locFindByCoords(locs, xc, yc, TRUE)) >= 0) + { + LocMarker *marker = locs->locations[n]; + unsigned char chr; + int color; + + if (span) + fprintf(outFile, "</span>"); + + if (marker->uri != NULL) + { + // If URI/URL is set, parse piece color from it + char *endptr = NULL; + color = strtol(marker->uri, &endptr, 10); + if (color < 0 || color >= nmapColors) + color = 0; + + // If there is extra character after color number, + // use it as the map patch + if (endptr != NULL && *endptr != 0) + chr = *endptr; + else + chr = *dp; + } + else + { + // If old format, use map piece color + if (optUseOldFormat) + color = muGetMapPieceColor(*dp, optUseOldFormat, TRUE); + else + // Otherwise use "link" color + color = col_light_red; + + chr = *dp; + } + + if (marker->flags & LOCF_INVIS) + { + fprintf(outFile, + "<span class=\"%c\">%c</span>", + 'a' + color, chr); + } + else + { + fprintf(outFile, + "<span class=\"%c\">" + "<a onmouseover=\"sttq('%d_%d');\" " + "onmouseout=\"qn();\" " + "class=\"loc\" id=\"maploc%d_%d\" " + "href=\"?%d_%d\">%c</a></span>", + 'a' + color, + marker->xc, marker->yc, + marker->xc, marker->yc, + marker->xc, marker->yc, + chr); + } + span = FALSE; + p = -1; + } + else + { + if (p != *dp) + { + int color = muGetMapPieceColor(*dp, optUseOldFormat, TRUE); + if (span) fprintf(outFile, "</span>"); + fprintf(outFile, "<span class=\"%c\">", + 'a' + color); + span = TRUE; + } + fputc(*dp, outFile); + p = *dp; + } + dp++; + } + if (span) fprintf(outFile, "</span>\n"); + p = -1; + span = FALSE; + } +} + + +int main(int argc, char *argv[]) +{ + MapBlock *map = NULL; + FILE *outFile = NULL, *inFile = NULL; + LocFileInfo info; + MapLocations locations; + + memset(&info, 0, sizeof(info)); + memset(&locations, 0, sizeof(locations)); + + th_init("mkcitymap", "ASCII citymap converter", "0.5", NULL, NULL); + th_verbosity = 0; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + exit(1); + + if (optMapFilename == NULL || optLocFilename == NULL) + { + THERR("You need to specify at least map and loc filenames. (try --help)\n"); + goto exit; + } + + // Parse location file + locSetFileInfo(&info, optLocFilename, NULL); + if ((inFile = fopen(info.filename, "rb")) == NULL) + { + THERR("Could not open location file '%s' for reading.\n", + info.filename); + goto exit; + } + + if (!locParseLocStream(inFile, &info, &locations, info.xoffs, info.yoffs)) + goto exit; + + + // Open mapfile + if ((map = mapBlockParseFile(optMapFilename, FALSE)) == NULL) + { + THERR("Error parsing/opening mapfile '%s'.\n", + optMapFilename); + goto exit; + } + + if (optDestFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(optDestFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + optDestFilename); + goto exit; + } + + // Output data + outputHTMLHeader(outFile, &locations); + outputMapHTML(outFile, map, &locations); + outputHTMLFooter(outFile, &locations); + + +exit: + // Close input and output files + if (outFile != NULL && outFile != stdout) + fclose(outFile); + + if (inFile != NULL) + fclose(inFile); + + mapBlockFree(map); + locFreeMapLocations(&locations); + locFreeFileInfo(&info); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mkloc.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,1260 @@ +/* + * Manipulate and convert BatMUD location data files + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "liblocfile.h" +#include "th_args.h" +#include "th_string.h" +#include "th_datastruct.h" + +enum +{ + OUTFMT_MAP = 0, + OUTFMT_LOCFILE, + OUTFMT_SCRIPT, + OUTFMT_MAPLOC, + OUTFMT_GMAPS +}; + +enum +{ + GMAPS_XML = 0, + GMAPS_JSON, + GMAPS_LAST +}; + +// These must be in lower case +static const char *gmapsModes[GMAPS_LAST] = +{ + "xml", + "json", +}; + +/* Variables + */ +char *optInFilename = NULL, + *optOutFilename = NULL; + +int noptLocFiles = -1; +LocFileInfo optLocFiles[LOC_MAX_FILES]; + +char *optLocMarkers = LOC_MARKERS; +BOOL optGetUpdateLoc = FALSE, + optOutput = OUTFMT_MAP, + optNoLabels = FALSE, + optNoAdjust = FALSE, + optLabelType = FALSE; +int optGMapsMode = -1; +float optScale = -1, + optUnitSize = 1.0f, + optFontScale = 1.0f; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 3, 'q', "quiet", "Be quiet", OPT_NONE }, + { 1, 'o', "output", "Output file (default stdout)", OPT_ARGREQ }, + { 5, 'm', "map", "Input map file", OPT_ARGREQ }, + { 6, 'l', "locinfo", "Input location info file", OPT_ARGREQ }, + { 4, 'g', "getloc", "Generate/update location info", OPT_NONE }, + { 7, 'x', "offset-x", "Location X offset", OPT_ARGREQ }, + { 8, 'y', "offset-y", "Location Y offset", OPT_ARGREQ }, + { 9, 's', "scale", "Scale coordinates by", OPT_ARGREQ }, + { 12,'f', "font-scale", "(-S) Font scale factor", OPT_ARGREQ }, + { 13,'u', "unit-size", "(-S) Unit size", OPT_ARGREQ }, + { 10,'S', "out-script", "Output script for ImageMagick", OPT_NONE }, + { 11,'L', "out-locinfo", "Output location info file", OPT_NONE }, + { 17,'M', "out-maploc", "Output MapLoc HTML", OPT_NONE }, + { 20,'G', "out-gmaps", "Output GMaps data (xml, overlay, labels)", OPT_ARGREQ }, + { 21,'c', "continent", "Continent for GMaps XML output", OPT_ARGREQ }, + { 15,'n', "no-labels", "No labels, only markers", OPT_NONE }, + { 16,'N', "no-adjust", "No label adjustment", OPT_NONE }, + { 18,'t', "type-prefix", "Prepend labels with type prefix", OPT_NONE }, + { 19,'X', "markers", "Location markers ('" LOC_MARKERS "')", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp() +{ + th_print_banner(stdout, th_prog_name, + "[options]"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + LocFileInfo *f; + + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 2: + th_verbosity++; + break; + + case 3: + th_verbosity = -1; + break; + + case 1: + optOutFilename = optArg; + THMSG(2, "Output file '%s'\n", optOutFilename); + break; + + case 5: + optInFilename = optArg; + THMSG(2, "Map file '%s'\n", optInFilename); + break; + + case 6: + noptLocFiles++; + if (noptLocFiles < LOC_MAX_FILES) + { + locSetFileInfo(&optLocFiles[noptLocFiles], optArg, NULL); + THMSG(2, "Added location data file #%d '%s'\n", noptLocFiles, optArg); + } + else + { + THERR("Too many location data files specified, maximum is %d.\n", LOC_MAX_FILES); + return FALSE; + } + break; + + case 4: + THMSG(2, "Location updating/generation mode\n"); + optGetUpdateLoc = TRUE; + break; + + case 7: + case 8: + case 21: + if (noptLocFiles >= 0) + { + f = &optLocFiles[noptLocFiles]; + switch (optN) + { + case 7: + f->xoffs = atoi(optArg); + THMSG(2, "Location file #%d X offset = %d\n", noptLocFiles, f->xoffs); + break; + + case 8: + f->yoffs = atoi(optArg); + THMSG(2, "Location file #%d Y offset = %d\n", noptLocFiles, f->yoffs); + break; + + case 21: + th_free(f->continent); + f->continent = th_strdup(optArg); + THMSG(2, "Using continent name '%s' for location file #%d\n", optArg, noptLocFiles); + break; + } + } + else + { + THERR("No location files specified, but location option '-%s %s' given.\n", currArg, optArg); + return FALSE; + } + break; + + + case 9: + optScale = atof(optArg); + THMSG(2, "Location scale factor = %1.3f\n", optScale); + break; + + case 10: + THMSG(2, "Script output mode\n"); + optOutput = OUTFMT_SCRIPT; + break; + + case 11: + THMSG(2, "Location file output mode\n"); + optOutput = OUTFMT_LOCFILE; + break; + + case 17: + THMSG(2, "MapLoc HTML output mode\n"); + optOutput = OUTFMT_MAPLOC; + break; + + case 12: + optFontScale = atof(optArg); + THMSG(2, "Font scale factor = %1.3f\n", optFontScale); + break; + + case 13: + optUnitSize = atof(optArg); + THMSG(2, "Unit size = %1.3f\n", optUnitSize); + break; + + case 15: + THMSG(2, "Not adding labels to locations\n"); + optNoLabels = TRUE; + break; + + case 16: + THMSG(2, "Not adjusting data under labels\n"); + optNoAdjust = TRUE; + break; + + case 18: + THMSG(2, "Adding label type prefixes\n"); + optLabelType = TRUE; + break; + + case 19: + optLocMarkers = optArg; + THMSG(2, "Using location markers = '%s'\n", optLocMarkers); + break; + + case 20: + optOutput = OUTFMT_GMAPS; + optGMapsMode = -1; + for (int i = 0; i < GMAPS_LAST; i++) + if (tolower(optArg[0]) == gmapsModes[i][0]) + { + optGMapsMode = i; + break; + } + + if (optGMapsMode < 0) + { + THERR("Invalid GMaps mode '%s'.\n", optArg); + return FALSE; + } + THMSG(2, "GMaps output mode '%s' selected\n", gmapsModes[optGMapsMode]); + break; + + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +int locPrintType(FILE *outFile, LocMarker *loc, BOOL adjust, int (*func)(const char *, FILE *), BOOL label) +{ + const char *type = locGetTypePrefix(loc->flags); + int len = 0; + + if (type != NULL && label) + { + len += strlen(type) + 1; + if (outFile != NULL && func != NULL) + fprintf(outFile, "%s ", type); + } + + if (func != NULL) + { + char *name = (loc->names[0].name != NULL) ? loc->names[0].name : "UNKNOWN"; + if (outFile != NULL) + func(name, outFile); + len += strlen(name); + + if (label && (loc->flags & LOCF_CLOSED)) + { + static const char *strClosed = " (CLOSED)"; + if (outFile != NULL) + func(strClosed, outFile); + len += strlen(strClosed); + } + } + + return adjust ? len : 0; +} + + +/* Adjust location label coordinates for the ASCII-CTRL map + */ +void adjustLocInfoCoords(MapBlock *map, MapLocations *l) +{ + for (int i = 0; i < l->nlocations; i++) + { + int yc, x0, x1, len; + LocMarker *tmp = l->locations[i]; + + len = strlen(tmp->names[0].name); + if (optLabelType) + len += locPrintType(NULL, tmp, TRUE, NULL, FALSE); + + // Compute text location + switch (tmp->align) + { + case LOCD_LEFTDOWN: + yc = tmp->yc + 1; + x0 = tmp->xc - len; + break; + + case LOCD_LEFT: + yc = tmp->yc - 1; + x0 = tmp->xc - len; + break; + + case LOCD_DOWN: + yc = tmp->yc + 1; + x0 = tmp->xc + 1; + break; + + case LOCD_NONE: + default: + yc = tmp->yc - 1; + x0 = tmp->xc + 1; + break; + } + + x1 = x0 + len + 1; + + if (x1 > map->width) + x0 -= (x1 - map->width - 1); + if (x0 < 0) + x0 = 0; + + if (yc < 0) + yc += 2; + if (yc > map->height) + yc -= (yc - map->height - 1); + + // Update location info + tmp->xc = x0; + tmp->yc = yc; + } +} + + +/* Check for adjacent markers + */ +int checkForAdjacent(MapLocations *world, int cx, int cy, int mask) +{ + int x, y, n; + + for (y = -1; y <= 1; y++) + for (x = -1; x <= 1; x++) + { + if (!(y == 0 && x == 0) && + (n = locFindByCoords(world, cx + x, cy + y, TRUE)) >= 0) + { + LocMarker *loc = world->locations[n]; + if ((loc->flags & mask) == mask) + return n; + } + } + + return -1; +} + + +/* Scan given map and update location list with new locations, + * if any are found. + */ +void updateLocations(MapBlock *worldMap, MapLocations *worldLoc) +{ + int n, numNewLoc = 0, numInvLoc = 0, numNoMarker = 0; + size_t noptLocMarkers = strlen(optLocMarkers); + LocMarker *tmpl; + + THMSG(2, "Updating location information ..\n"); + + // Find new, unknown locations + for (int y = 0; y < worldMap->height; y++) + { + unsigned char *dp = worldMap->data + (worldMap->scansize * y); + + for (int x = 0; x < worldMap->width; x++) + { + if (muStrChr((unsigned char *) optLocMarkers, noptLocMarkers, dp[x])) + { + LocName tmpNames[LOC_MAX_NAMES]; + char tmpDesc[512]; + int tmpFlags; + + // Check for new locations + if (locFindByCoords(worldLoc, x, y, TRUE) >= 0) + continue; + + + if (dp[x] == 'C') + { + // In case of player cities, check for existing adjacent blocks + // so we can match with an existing pcity .. + n = checkForAdjacent(worldLoc, x, y, LOCF_M_PCITY); + if (n >= 0) + { + // Found, use its name for this block + tmpl = worldLoc->locations[n]; + tmpFlags = LOCF_M_PCITY | LOCF_INVIS; + snprintf(tmpDesc, sizeof(tmpDesc), + "%s", tmpl->names[0].name); + + } + else + { + // Nope, it is a "new" pcity + numNewLoc++; + tmpFlags = LOCF_M_PCITY; + snprintf(tmpDesc, sizeof(tmpDesc), + "%.3s-PCITY #%d", + optInFilename, numNewLoc); + } + } + else + { + // Non-pcities are handled here + numNewLoc++; + tmpFlags = LOCF_NONE; + snprintf(tmpDesc, sizeof(tmpDesc), + "%.3s-UNK #%d", + optInFilename, numNewLoc); + } + + memset(tmpNames, 0, sizeof(tmpNames)); + tmpNames[0].name = tmpDesc; + locAddNew(worldLoc, x, y, LOCD_NONE, tmpFlags, + tmpNames, NULL, NULL, FALSE, NULL, NULL, NULL); + } + else + { + // Check for misplaced locations + if ((n = locFindByCoords(worldLoc, x, y, TRUE)) >= 0) + { + tmpl = worldLoc->locations[n]; + + if (tmpl->flags == LOCF_NONE) + { + // Mark as possibly invalid + tmpl->flags |= LOCF_INVALID; + numInvLoc++; + } + + if ((tmpl->flags & LOCF_M_MASK) == 0) + { + // No apparent marker + tmpl->flags |= LOCF_NOMARKER; + numNoMarker++; + } + } + } + } + } + + THMSG(1, "%d new locations, %d invalid, %d missing marker.\n", + numNewLoc, numInvLoc, numNoMarker); +} + + +/* Quicksort comparision function for location names + */ +int maplocCompare(const void *pp1, const void *pp2) +{ + const LocMarker + *vp1 = *(const LocMarker **) pp1, + *vp2 = *(const LocMarker **) pp2; + + if (vp1->vsort.v_int == vp2->vsort.v_int) + return strcmp(vp1->names[0].name, vp2->names[0].name); + else + return vp1->vsort.v_int - vp2->vsort.v_int; +} + + +/* Sort locations by name + */ +void maplocSort(MapLocations *l) +{ + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *loc = l->locations[i]; + loc->vsort.v_int = 2; + + switch (loc->flags & LOCF_M_MASK) + { + case LOCF_M_CITY: loc->vsort.v_int = 1; break; + case LOCF_M_PCITY: loc->vsort.v_int = 10; break; + default: + switch (loc->flags & LOCF_T_MASK) + { + case LOCF_T_GUILD: loc->vsort.v_int = 3; break; + case LOCF_T_TRAINER: loc->vsort.v_int = 4; break; + case LOCF_T_SHRINE: loc->vsort.v_int = 5; break; + case LOCF_T_SS: loc->vsort.v_int = 6; break; + case LOCF_T_MONSTER: loc->vsort.v_int = 7; break; + case LOCF_T_FORT: loc->vsort.v_int = 100; break; + } + } + } + + qsort(l->locations, l->nlocations, sizeof(LocMarker *), maplocCompare); +} + + +/* Output the map with labels and location markers, etc. in + * special ASCII-CTRL format understood by colormap utility. + */ +void outputMapCTRL(FILE *outFile, MapBlock *map, MapLocations *l) +{ + for (int y = 0; y < map->height; y++) + { + unsigned char *dp = map->data + (map->scansize * y); + + for (int x = 0; x < map->width; x++) + { + int n; + if ((n = locFindByCoords(l, x, y, TRUE)) >= 0) + { + LocMarker *tmp = l->locations[n]; + char chm = dp[x]; + + switch (tmp->flags & LOCF_M_MASK) + { + case LOCF_M_SCENIC1: chm = '?'; break; + case LOCF_M_SCENIC2: chm = '%'; break; + case LOCF_M_PCITY: chm = 'C'; break; + case LOCF_M_CITY: chm = 'c'; break; + + default: + if (tmp->flags & LOCF_INVALID) + chm = '$'; + break; + } + + fputc(0xfb, outFile); + fprintf(outFile, "mloc%d_%d", tmp->ox + 1, tmp->oy + 1); + fputc(0xfc, outFile); + fputc(chm, outFile); + fputc(0xfe, outFile); + } + else + if (!optNoLabels && (n = locFindByCoords(l, x, y, FALSE)) >= 0) + { + LocMarker *tmp = l->locations[n]; + + if ((tmp->flags & LOCF_INVIS) == 0) + { + int col = col_light_white; + + fputc(0xff, outFile); + fprintf(outFile, "loc%d_%d", tmp->ox + 1, tmp->oy + 1); + fputc(0xfc, outFile); + + switch (tmp->flags & LOCF_M_MASK) + { + case LOCF_M_PCITY: col = col_light_green; break; + case LOCF_M_CITY: col = col_light_red; break; + + default: + switch (tmp->flags & LOCF_T_MASK) + { + case LOCF_T_SHRINE: col = col_light_yellow; break; + case LOCF_T_GUILD: col = col_light_magenta; break; + case LOCF_T_MONSTER: col = col_light_cyan; break; + case LOCF_T_TRAINER: col = col_light_magenta; break; + case LOCF_T_FORT: col = col_light_cyan; break; + default: col = col_light_white; break; + } + break; + } + + if (tmp->flags & LOCF_CLOSED) + col = col_light_red; + + fputc(col, outFile); + + if (optLabelType) + { + x += locPrintType(outFile, tmp, !optNoAdjust, NULL, FALSE); + } + + fputs(tmp->names[0].name, outFile); + fputc(0xfe, outFile); + + if (!optNoAdjust) + x += strlen(tmp->names[0].name) - 1; + else + fputc(dp[x], outFile); + } + else + fputc(dp[x], outFile); + } + else + fputc(dp[x], outFile); + } + + fprintf(outFile, "\n"); + } +} + + +/* Print out location list as HTML option list. + */ +void outputMapLocHTML(FILE *outFile, MapLocations *l) +{ + assert(l != NULL); + + fprintf(outFile, "<option value=\"\">-</option>\n"); + + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *tmp = l->locations[i]; + + if (tmp->flags & LOCF_INVIS) + continue; + + fprintf(outFile, "<option value=\"loc%d_%d\">", tmp->ox + 1, tmp->oy + 1); + locPrintType(outFile, tmp, FALSE, fputse, TRUE); + fprintf(outFile, "</option>\n"); + } +} + + +/* Output generated locations into given file stream + */ +void printLocNameEsc(FILE *outFile, LocName *name) +{ + char *prefix = ""; + switch (name->flags) + { + case NAME_ORIG: prefix = "@"; break; + case NAME_RECODER: prefix = "!"; break; + case NAME_MAINTAINER: prefix = "%"; break; + case NAME_EXPANDER: prefix = "&"; break; + } + fputs(prefix, outFile); + fputsesc2(name->name, outFile); +} + + +void outputLocationFile(FILE *outFile, MapLocations *l) +{ + // Output header + fprintf(outFile, + "# " LOC_MAGIC " (version %d.%d)\n", + LOC_VERSION_MAJOR, LOC_VERSION_MINOR + ); + + fprintf(outFile, + "# Refer to README.loc for more information.\n" + "#\n" + ); + + // Output each location entry + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *tmp = l->locations[i]; + + // Add comment in few cases + if (tmp->flags & LOCF_Q_MASK) + { + char *s = NULL; + if (tmp->flags & LOCF_NOMARKER) + s = "Location missing marker"; + else + if (tmp->flags & LOCF_INVALID) + s = "Possibly invalid location"; + + if (s) + { + fprintf(outFile, "\n# %s #%d: %s\n", + s, i, tmp->names[0].name); + } + } + + fprintf(outFile, "%d\t; %d\t; %d", + tmp->ox + 1, tmp->oy + 1, tmp->align); + + switch (tmp->flags & LOCF_M_MASK) + { + case LOCF_M_SCENIC1: fputc('?', outFile); break; + case LOCF_M_SCENIC2: fputc('%', outFile); break; + case LOCF_M_PCITY: fputc('C', outFile); break; + case LOCF_M_CITY: fputc('c', outFile); break; + } + + switch (tmp->flags & LOCF_T_MASK) + { + case LOCF_T_SHRINE: fputc('S', outFile); break; + case LOCF_T_GUILD: fputc('G', outFile); break; + case LOCF_T_SS: fputc('P', outFile); break; + case LOCF_T_MONSTER: fputc('M', outFile); break; + case LOCF_T_TRAINER: fputc('T', outFile); break; + case LOCF_T_FORT: fputc('F', outFile); break; + } + + if (tmp->flags & LOCF_CLOSED) + fputc('!', outFile); + + if (tmp->flags & LOCF_INSTANCED) + fputc('I', outFile); + + if (tmp->flags & LOCF_INVIS) + fputc('-', outFile); + + fprintf(outFile, "\t;"); + printLocNameEsc(outFile, &tmp->names[0]); + for (int n = 1; n < tmp->nnames; n++) + { + fprintf(outFile, "|"); + printLocNameEsc(outFile, &tmp->names[n]); + } + fprintf(outFile, ";"); + + if (tmp->ncoders > 0) + { + printLocNameEsc(outFile, &tmp->coders[0]); + for (int n = 1; n < tmp->ncoders; n++) + { + fprintf(outFile, ","); + printLocNameEsc(outFile, &tmp->coders[n]); + } + } + + fprintf(outFile, ";"); + + if (tmp->valid) + { + fprintf(outFile, LOC_TIMEFMT, + tmp->added.day, tmp->added.month, tmp->added.year); + } + + fprintf(outFile, ";"); + + if (tmp->uri) + fputsesc2(tmp->uri, outFile); + + fprintf(outFile, ";"); + + if (tmp->freeform) + fputsesc2(tmp->freeform, outFile); + + fprintf(outFile, "\n"); + } +} + + +/* Generate a shell-script for running ImageMagick to add + * location and label information to an map image. + */ +void outputMagickScript(FILE *outFile, MapLocations *l) +{ + int numCity, numLoc, numTotal; + + // Output script start + fprintf(outFile, + "#!/bin/sh\n" + "convert \"$1\" @OPTS_START@ \\\n"); + + numCity = numLoc = numTotal = 0; + + // Output instructions for each visible location + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *tmp = l->locations[i]; + int xc, yc, leftMove; + char *cs; + + // Is location visible? + if (tmp->flags & LOCF_INVIS) + continue; + + leftMove = ((float) strlen(tmp->names[0].name)) * optFontScale; + + switch (tmp->align) + { + case LOCD_LEFTDOWN: + yc = tmp->yc + optUnitSize*3.0f; + xc = tmp->xc - leftMove; + break; + + case LOCD_LEFT: + yc = tmp->yc - optUnitSize; + xc = tmp->xc - leftMove; + break; + + case LOCD_DOWN: + yc = tmp->yc + optUnitSize*3.0f; + xc = tmp->xc + optUnitSize; + break; + + case LOCD_NONE: + default: + yc = tmp->yc - optUnitSize; + xc = tmp->xc + optUnitSize; + break; + } + + // Determine colour + switch (tmp->flags & LOCF_M_MASK) + { + case LOCF_M_CITY: + cs = "'#880000'"; + numLoc++; + break; + case LOCF_M_PCITY: + cs = "'#00ff00'"; + numCity++; + break; + default: + switch (tmp->flags & LOCF_T_MASK) + { + case LOCF_T_SHRINE: cs = "yellow"; break; + case LOCF_T_GUILD: cs = "magenta"; break; + case LOCF_T_MONSTER: cs = "cyan"; break; + case LOCF_T_TRAINER: cs = "magenta"; break; + case LOCF_T_FORT: cs = "'#00ffff'"; break; + default: cs = "white"; break; + } + numLoc++; + break; + } + + if (tmp->flags & LOCF_CLOSED) + cs = "'#ff0000'"; + + numTotal++; + + // Location marker + fprintf(outFile, + "\t-fill black -draw \"circle %d,%d %d,%d\" ", + tmp->xc, tmp->yc, + (int) (tmp->xc + optUnitSize), (int) (tmp->yc + optUnitSize)); + + fprintf(outFile, + "-fill %s -draw \"circle %d,%d %d,%d\" ", + cs, tmp->xc, tmp->yc, + (int) (tmp->xc + optUnitSize * 0.90f), + (int) (tmp->yc + optUnitSize * 0.90f)); + + + // Location description text + if (!optNoLabels) + { + fprintf(outFile, + "-fill %s -box '#00000080' -draw \"text %d,%d '", + cs, xc, yc); + + fputsesc3(tmp->names[0].name, outFile); + fprintf(outFile, "'\" "); + } + + fprintf(outFile, " \\\n"); + } + + fprintf(outFile, + "@OPTS_END@" + "'\" \\\n"); + + fprintf(outFile, "\t\"$2\"\n"); +} + + +/* Output locations in GMaps XML format. Character set conversion + * is not performed, caller must take care of it via iconv or similar. + */ +static char *getQuestLink(const char *name, const char *desc) +{ + char *str, *tmp = th_strdup(name); + + for (size_t i = 0; i < strlen(tmp); i++) + tmp[i] = th_isspace(tmp[i]) ? '+' : th_tolower(tmp[i]); + + str = th_strdup_printf("<a target=\"_blank\" href=\"http://www.bat.org/help/quests?str=%s\">%s</a>", tmp, desc); + th_free(tmp); + return str; +} + + +static char *addQuestLink(char **buf, size_t *bufSize, size_t *bufLen, char *ptr, char *start, char *end) +{ + if (start != NULL && end != NULL) + { + char *name = th_strndup(start + 1, end - start - 1); + char *desc = th_strndup(ptr, end - ptr + 1); + char *tmp = getQuestLink(name, desc); + th_strbuf_puts(buf, bufSize, bufLen, tmp); + th_free(name); + th_free(desc); + th_free(tmp); + return end + 1; + } + else + return ptr; +} + +void outputGMapsHTML(FILE *outFile, LocMarker *tmp, + int (*fpr)(FILE *, const char *fmt, ...), int (*fps)(const char *, FILE *)) +{ + if (tmp->uri != NULL) + { + fpr(outFile, "<b><a target=\"_blank\" href=\"%s\">", tmp->uri); + locPrintType(outFile, tmp, FALSE, fps, TRUE); + fpr(outFile, "</a></b><br>"); + } + else + { + fpr(outFile, "<b>"); + locPrintType(outFile, tmp, FALSE, fps, TRUE); + fpr(outFile, "</b><br>"); + } + + // Alternative names, if any + if (tmp->nnames > 1) + { + fpr(outFile, "Also known as <i>"); + for (int n = 1; n < tmp->nnames; n++) + { + fps(tmp->names[n].name, outFile); + if (tmp->names[n].flags & NAME_ORIG) + fprintf(outFile, " (*)"); + if (n < tmp->nnames - 1) + fprintf(outFile, " ; "); + } + fpr(outFile, "</i>.<br>"); + } + + // Added to game timestamp + if (tmp->valid) + { + fpr(outFile, "Added " LOC_TIMEFMT ".<br>", + tmp->added.day, tmp->added.month, tmp->added.year); + } + + // Coder names or societies + if (tmp->ncoders > 0) + { + if (tmp->flags & LOCF_M_PCITY) + { + fprintf(outFile, "Societies: "); + for (int n = 0; n < tmp->ncoders; n++) + { + fps(tmp->coders[n].name, outFile); + if (n < tmp->ncoders - 1) + fprintf(outFile, ", "); + } + } + else + { + fprintf(outFile, "Coders: "); + for (int n = 0; n < tmp->ncoders; n++) + { + char *info = "", *sinfo = ""; + switch (tmp->coders[n].flags) + { + case NAME_ORIG: info = " (O)"; sinfo = "Original coder"; break; + case NAME_RECODER: info = " (R)"; sinfo = "Re-coder"; break; + case NAME_MAINTAINER: info = " (M)"; sinfo = "Maintainer"; break; + case NAME_EXPANDER: info = " (E)"; sinfo = "Expander"; break; + } + //fpr(outFile, "<a target=\"_blank\" href=\"http://www.bat.org/char/%s\">%s%s</a>", + fpr(outFile, "<a target=\"_blank\" href=\"https://tnsp.org/maps/loc.php?a=%s\" title=\"%s\">%s%s</a>", + tmp->coders[n].name, sinfo, + tmp->coders[n].name, info); + + if (n < tmp->ncoders - 1) + fprintf(outFile, ", "); + } + } + fps(".<br>", outFile); + } + + // Print out freeform information field + if (tmp->freeform) + { + char *ptr = tmp->freeform; + char *buf = NULL; + size_t bufLen = 0, bufSize = 0; + + while (*ptr != 0) + { + if (ptr[0] == 'A' && ptr[1] == 'Q' && th_isspace(ptr[2])) + { + char *start = strchr(ptr + 3, '"'); + ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, strchr(start + 1, '"')); + } + else + if (ptr[0] == 'L' && ptr[1] == 'Q' && th_isdigit(ptr[2])) + { + char *start = strchr(ptr + 3, '"'); + ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, start ? strchr(start + 1, '"') : NULL); + } + + if (*ptr != 0) + th_strbuf_putch(&buf, &bufSize, &bufLen, *ptr++); + } + + th_strbuf_putch(&buf, &bufSize, &bufLen, 0); + + fpr(outFile, "<br>%s<br>", buf); + th_free(buf); + } +} + + +void outputGMapsXML(FILE *outFile, MapLocations *l) +{ + fprintf(outFile, + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<markers>\n"); + + // Output each location entry + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *tmp = l->locations[i]; + + // Skip disabled / invisible locations + if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; + + // Print out coordinates etc. + fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\" name=\"", + tmp->ox, tmp->oy, tmp->align); + + // Location name + locPrintType(outFile, tmp, FALSE, fputse, FALSE); + + fprintf(outFile, "\" html=\""); + + outputGMapsHTML(outFile, tmp, fprintfe, fputse); + + // Flags + fprintf(outFile, "\" flags=\"%d\"", + tmp->flags); + + // Continent name + if (tmp->file != NULL && tmp->file->continent != NULL) + { + fprintf(outFile, " continent=\""); + fputse(tmp->file->continent, outFile); + fprintf(outFile, "\""); + } + + // Type of the marker + fprintf(outFile, " type=\"%s\"/>\n", + locGetTypeName(tmp->flags)); + } + + fprintf(outFile, "</markers>\n"); +} + + +void outputGMapsJSON(FILE *outFile, MapLocations *l) +{ + fprintf(outFile, "[\n"); + + // Output each location entry + for (int i = 0; i < l->nlocations; i++) + { + LocMarker *tmp = l->locations[i]; + + // Skip disabled / invisible locations + if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; + + // Print out coordinates etc. + fprintf(outFile, "{\"x\":%d,\"y\":%d,\"labeldir\":%d,\"name\":\"", + tmp->ox, tmp->oy, tmp->align); + + // Location name + locPrintType(outFile, tmp, FALSE, fputsesc1, FALSE); + fprintf(outFile, "\",\"html\":\""); + + outputGMapsHTML(outFile, tmp, fprintfesc1, fputsesc1); + + // Flags + fprintf(outFile, "\",\"flags\":%d", + tmp->flags); + + // Continent name + if (tmp->file != NULL && tmp->file->continent != NULL) + { + fprintf(outFile, ",\"continent\":\""); + fputsesc1(tmp->file->continent, outFile); + fprintf(outFile, "\""); + } + + fprintf(outFile, "}%s\n", + (i < l->nlocations - 1) ? "," : ""); + } + + fprintf(outFile, "]\n"); +} + + +int main(int argc, char *argv[]) +{ + FILE *outFile = NULL; + MapBlock *worldMap = NULL; + MapLocations worldLoc; + + memset(&worldLoc, 0, sizeof(worldLoc)); + memset(&optLocFiles, 0, sizeof(optLocFiles)); + + // Initialize + th_init("mkloc", "Manipulate and convert location files and ASCII map data", "1.6", NULL, NULL); + th_verbosity = 0; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, NULL, OPTH_BAILOUT)) + goto exit; + + // Check the mode and arguments + if (optInFilename == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) + { + THERR("Map file required for location update mode!\n"); + goto exit; + } + + if (optOutput == OUTFMT_LOCFILE && noptLocFiles < 0 && !optGetUpdateLoc) + { + THERR("Location file or location update mode required for location file output!\n"); + goto exit; + } + + if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && noptLocFiles < 0) + { + THERR("Location file required for script or MapLoc HTML output!\n"); + goto exit; + } + + if (optInFilename == NULL && optOutput == OUTFMT_MAP) + { + THERR("Mapfile required for map generation.\n"); + goto exit; + } + + // Read initial map + if (optInFilename != NULL) + { + THMSG(2, "Reading map '%s'\n", optInFilename); + worldMap = mapBlockParseFile(optInFilename, FALSE); + if (worldMap == NULL) + { + THERR("World map could not be loaded!\n"); + goto exit; + } + + THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->width, worldMap->height); + } + + // Read location info + for (int i = 0; i <= noptLocFiles; i++) + { + LocFileInfo *f = &optLocFiles[i]; + FILE *inFile; + + if (optOutput == OUTFMT_GMAPS && f->continent == NULL) + { + THERR("Required continent name not set for #%d '%s'.\n", + i, f->filename); + goto exit; + } + + THMSG(2, "Reading location info '%s'\n", f->filename); + if ((inFile = fopen(f->filename, "rb")) == NULL) + { + THERR("Could not open location file '%s' for reading.\n", + f->filename); + goto exit; + } + + if (!locParseLocStream(inFile, f, &worldLoc, f->xoffs, f->yoffs)) + goto exit; + + fclose(inFile); + } + + // Update locations + if (optGetUpdateLoc) + updateLocations(worldMap, &worldLoc); + + // Scale locations + if (optScale > 0) + { + THMSG(1, "Scaling locations ..\n"); + + for (int i = 0; i < worldLoc.nlocations; i++) + { + LocMarker *tmp = worldLoc.locations[i]; + + tmp->xc = ((float) tmp->xc) * optScale; + tmp->yc = ((float) tmp->yc) * optScale; + } + } + + // Open output file + if (optOutFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(optOutFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + optOutFilename); + goto exit; + } + + // Output results + switch (optOutput) + { + case OUTFMT_SCRIPT: + THMSG(1, "Generating ImageMagick script ...\n"); + outputMagickScript(outFile, &worldLoc); + break; + + case OUTFMT_LOCFILE: + THMSG(1, "Outputting generated location list ...\n"); + outputLocationFile(outFile, &worldLoc); + THMSG(2, "%d locations\n", worldLoc.nlocations); + break; + + case OUTFMT_MAPLOC: + maplocSort(&worldLoc); + THMSG(1, "Outputting MapLoc HTML ...\n"); + outputMapLocHTML(outFile, &worldLoc); + THMSG(2, "%d locations\n", worldLoc.nlocations); + break; + + case OUTFMT_GMAPS: + maplocSort(&worldLoc); + THMSG(1, "Outputting GMaps data (%s) ...\n", gmapsModes[optGMapsMode]); + switch (optGMapsMode) + { + case GMAPS_XML: outputGMapsXML(outFile, &worldLoc); break; + case GMAPS_JSON: outputGMapsJSON(outFile, &worldLoc); break; + } + THMSG(2, "%d locations\n", worldLoc.nlocations); + break; + + case OUTFMT_MAP: + THMSG(1, "Outputting generated map of (%d x %d) ...\n", + worldMap->width, worldMap->height); + + THMSG(2, "Adjusting location labels ..\n"); + adjustLocInfoCoords(worldMap, &worldLoc); + + outputMapCTRL(outFile, worldMap, &worldLoc); + break; + } + +exit: + if (outFile != NULL) + fclose(outFile); + + mapBlockFree(worldMap); + locFreeMapLocations(&worldLoc); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/patchmap.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,198 @@ +/* + * Patch a map with given diff file + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2008-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_util.h" +#include "th_args.h" +#include "th_string.h" + + +char *mapFilename = NULL, + *patchFilename = NULL, + *destFilename = NULL; +BOOL optCheck = FALSE; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 2, 'q', "quiet", "Be quiet", OPT_NONE }, + { 3, 'c', "check", "Check non-patched blocks", OPT_NONE }, + { 4, 'o', "output", "Output filename", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <mapfile> <patchfile>"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + th_verbosity++; + break; + + case 2: + th_verbosity = -1; + break; + + case 3: + optCheck = TRUE; + THMSG(2, "Checking non-patched blocks for match.\n"); + break; + + case 4: + destFilename = optArg; + THMSG(2, "Output file set to '%s'.\n", destFilename); + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (mapFilename == NULL) + mapFilename = currArg; + else + if (patchFilename == NULL) + patchFilename = currArg; + else + { + THERR("Too many input map files specified!\n"); + return FALSE; + } + + return TRUE; +} + + +int main(int argc, char *argv[]) +{ + MapBlock *map = NULL, *patch = NULL; + FILE *outFile = NULL; + int res = 0; + + // Initialize + th_init("patchmap", "Patch a mapfile with a diff", "0.1", NULL, NULL); + th_verbosity = 1; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + { + res = 1; + goto exit; + } + + if (mapFilename == NULL || patchFilename == NULL) + { + THERR("Nothing to do. (try --help)\n"); + res = 1; + goto exit; + } + + // Read map and patch file + if ((map = mapBlockParseFile(mapFilename, FALSE)) == NULL || + (patch = mapBlockParseFile(patchFilename, TRUE)) == NULL) + { + res = 2; + goto exit; + } + + // Check map and diff sizes + if (optCheck && (map->width != patch->width || map->height != patch->height)) + { + THERR("Map and patch dimensions do not match (%d x %d [map] <-> %d x %d [patch])\n", + map->width, map->height, + patch->width, patch->height); + + res = 2; + goto exit; + } + + // Generate + for (int yc = 0; yc < map->height; yc++) + { + unsigned char + *dp = map->data + (map->scansize * yc), + *sp = patch->data + (patch->scansize * yc); + + for (int xc = 0; xc < map->width; xc++, sp++, dp++) + { + const int i = (*sp & 63); + unsigned char ch; + + if (*sp == 0xfd) + ch = ' '; + else + if (i < nmapPieces) + ch = mapPieces[i].symbol; + else + { + THERR("[%d,%d] invalid symbol index %d (%d) in patch\n", + xc+1, yc+1, *sp, i); + res = 7; + goto exit; + } + + if (*sp < 64) + { + if (optCheck && *sp != *dp) + { + THERR("[%d,%d] mismatch %d != %d\n", + xc+1, yc+1, *sp, *dp); + } + } + else + *dp = ch; + } + } + + // Open output file + if (destFilename == NULL) + outFile = stdout; + else + if ((outFile = fopen(destFilename, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + destFilename); + res = 6; + goto exit; + } + + mapBlockPrint(outFile, map); + +exit: + if (outFile != NULL) + fclose(outFile); + + mapBlockFree(map); + mapBlockFree(patch); + + return res; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stitchmap.c Tue Jul 27 10:07:03 2021 +0300 @@ -0,0 +1,740 @@ +/* + * stitchmap - Compute complete ASCII map by stitching pieces + * together based on a matcher and coordinates + * + * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> + * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) + */ +#include "libmaputils.h" +#include "th_args.h" +#include "th_string.h" + +#define MAX_FILES (256) + + +/* Variables + */ +int nsrcFiles = 0; +char *srcFiles[MAX_FILES], + *destFile = NULL; + +int optInitialX = 0, + optInitialY = 0, + optMapFactor = 10, + optRounds = 100, + optReset = 50; +BOOL optHardDrop = FALSE, + optDumpRejected = FALSE, + optAdjustBlocks = FALSE, + optWalkMode = FALSE; +float optMatch = 40.0; +char *optInitialMap = NULL, + *optCleanChars = " *@", + optCenterCh = -1; + + +/* Arguments + */ +static const th_optarg optList[] = +{ + { 0, '?', "help", "Show this help", OPT_NONE }, + { 1, 'o', "output", "Specify output file", OPT_ARGREQ }, + { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, + { 3, 'q', "quiet", "Be quiet", OPT_NONE }, + { 4, 'm', "match", "Match percentage", OPT_ARGREQ }, + { 5, 'r', "rounds", "Processing timeout # rounds", OPT_ARGREQ }, + { 12,'R', "reset", "Round reset after", OPT_ARGREQ }, + { 11,'d', "drop", "Use hard dropping (bailout on first mismatch)", OPT_NONE }, + { 10,'I', "initial", "Initial map file", OPT_ARGREQ }, + { 14,'x', "initial-x", "Initial map X offset", OPT_ARGREQ }, + { 15,'y', "initial-y", "Initial map Y offset", OPT_ARGREQ }, + { 16,'D', "dump", "Dump rejected pieces", OPT_NONE }, + { 17,'a', "adjust-size","Adjust to variable size blocks", OPT_NONE }, + { 18,'W', "walk-mode", "Walk mode (instead of ship mode)", OPT_NONE }, + { 19,'c', "clean-chars","Characters to filter from input", OPT_ARGREQ }, + { 20,'C', "center-char","Center character symbol (for -a)", OPT_ARGREQ }, +}; + +static const int optListN = sizeof(optList) / sizeof(optList[0]); + + +void argShowHelp() +{ + th_print_banner(stdout, th_prog_name, + "[options] <inputfile> [inputfile#2..]"); + + th_args_help(stdout, optList, optListN, 0, 80 - 2); +} + + +BOOL argHandleOpt(const int optN, char *optArg, char *currArg) +{ + switch (optN) + { + case 0: + argShowHelp(); + exit(0); + break; + + case 1: + destFile = optArg; + THMSG(1, "Output file '%s'\n", destFile); + break; + + case 2: + th_verbosity++; + break; + + case 3: + th_verbosity = -1; + break; + + case 4: + optMatch = atof(optArg); + THMSG(1, "Match at %1.4f%%\n", optMatch); + break; + + case 5: + optRounds = atoi(optArg); + THMSG(1, "Processing rounds timeout %d\n", optRounds); + break; + + case 12: + optReset = atoi(optArg); + THMSG(1, "Round reset after %d blocks\n", optReset); + break; + + case 10: + optInitialMap = optArg; + THMSG(1, "Using initial map file = '%s'\n", optArg); + break; + + case 11: + optHardDrop = TRUE; + THMSG(1, "Using hard dropping (instant bailout on mismatch).\n"); + break; + + case 14: + optInitialX = atoi(optArg); + THMSG(1, "Initial map X offset = %d\n", optInitialX); + break; + + case 15: + optInitialY = atoi(optArg); + THMSG(1, "Initial map Y offset = %d\n", optInitialY); + break; + + case 16: + THMSG(1, "Dumping rejected blocks.\n"); + optDumpRejected = TRUE; + break; + + case 17: + THMSG(1, "Handling variable size blocks via adjustment.\n"); + optAdjustBlocks = TRUE; + break; + + case 18: + THMSG(1, "Walk mode enabled.\n"); + optWalkMode = TRUE; + break; + + case 19: + optCleanChars = optArg; + THMSG(1, "Removing '%s' from input blocks\n", optArg); + break; + + case 20: + if (strlen(optArg) != 1) + { + THERR("Invalid argument '%s' for -C, only one character symbol can be specified.\n", optArg); + return FALSE; + } + else + { + optCenterCh = optArg[0]; + THMSG(1, "Using '%c' as centering character symbol\n", optCenterCh); + } + break; + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + break; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (nsrcFiles < MAX_FILES) + { + srcFiles[nsrcFiles] = currArg; + nsrcFiles++; + } + else + { + THERR("Too many input files specified (%d max)!\n", MAX_FILES); + return FALSE; + } + + return TRUE; +} + + +/* Calculate matching percentage of given block against a map, + * with using specified x/y offsets. + */ +float matchBlock(MapBlock *map, MapBlock *match, + const int ox, const int oy, const BOOL hardDrop) +{ + int n, k; + + n = k = 0; + + for (int y = 0; y < match->height; y++) + for (int x = 0; x < match->width; x++) + { + const int + dx = ox + x, + dy = oy + y; + int c1, c2; + + k++; + + if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) + c2 = map->data[(dy * map->scansize) + dx]; + else + c2 = 0; + + c1 = match->data[(y * match->scansize) + x]; + + if (c1 == 0 || c2 == 0) + { + // Both empty ... + } + else + if (c1 != 0 && c1 == c2) + { + // Exact match, increase % + n++; + } + else + if (hardDrop) + { + // Mismatch, return failure + return -1; + } + } + + if (k > 0) + return ((float) n * 100.0f) / (float) k; + else + return 0.0f; +} + + +BOOL adjustBlockCenter(MapBlock *block, const char centerCh) +{ + for (int yc = 0; yc < block->height; yc++) + for (int xc = 0; xc < block->width; xc++) + { + int ch = block->data[(yc * block->scansize) + xc]; + if (ch == centerCh) + { + block->xc -= xc; + block->yc -= yc; + return TRUE; + } + } + + return FALSE; +} + + +/* Parse next block (marked by string optPattern) from + * input stream into a mapblock, return NULL if not found or error. + */ +BOOL getLineData(FILE *inFile, char *buf, const size_t bufSize) +{ + if (feof(inFile) || !fgets(buf, bufSize, inFile)) + { + THERR("Unexpected end of file.\n"); + return FALSE; + } + else + return TRUE; +} + + +size_t getLineWidth(char *buf, const size_t bufSize) +{ + size_t width; + + for (width = 0; + width + 1 < bufSize && + buf[width] != 0 && + buf[width] != '\r' && + buf[width] != '\n'; width++); + + buf[width] = 0; + + return width; +} + + +MapBlock * parseBlock(FILE *inFile) +{ + MapBlock *tmp = NULL; + int xc, yc, tmpW, tmpH; + char str[4096], *pos = NULL; + BOOL isFound; + size_t i; + + isFound = FALSE; + while (!feof(inFile) && !isFound && fgets(str, sizeof(str), inFile)) + { + if ((pos = strstr(str, ": !!PLR: ")) != NULL && + (strstr(str, "party") || strstr(str, "report"))) + isFound = TRUE; + } + + if (!isFound) + { + THERR("No block start marker found.\n"); + goto err; + } + + // Trim end + for (i = 0; i < sizeof(str) && str[i] && str[i] != '\r' && str[i] != '\n'; i++); + str[i] = 0; + + if (sscanf(pos, ": !!PLR: %d,%d", &xc, &yc) < 2) + { + THERR("Could not get location coordinates:\n'%s'\n", + str); + goto err; + } + + // Check for discardable lines + if (!getLineData(inFile, str, sizeof(str))) + goto err; + + if (!th_strcasecmp(str, "You glance around.")) + { + if (!getLineData(inFile, str, sizeof(str))) + goto err; + } + + // New 'map' output in city maps has an empty line before the data, ignore it + if ((tmpW = getLineWidth(str, sizeof(str))) <= 9) + { + if (!getLineData(inFile, str, sizeof(str))) + goto err; + + tmpW = getLineWidth(str, sizeof(str)); + } + + if (optWalkMode) + { + switch (tmpW) + { + case 31: tmpH = 17; break; + case 13: tmpH = 7; break; + case 9: tmpH = 9; break; + default: + THERR("Invalid block width %d [%d, %d], rejecting.\n", tmpW, xc, yc); + return NULL; + break; + } + } + else + tmpH = tmpW - 8; + + if ((tmp = mapBlockAlloc(tmpW, tmpH)) == NULL) + { + THERR("Could not allocate mapblock (%d, %d)\n", tmpW, tmpH); + goto err; + } + + tmp->xc = xc; + tmp->yc = yc; + + yc = 0; + do + { + i = getLineWidth(str, tmpW); + + if (i != (size_t) tmp->width) + { + THERR("Broken block, line width %" PRIu_SIZE_T " != %d!\n", + i, tmp->width); + goto err; + } + + for (xc = 0; xc < tmp->width; xc++) + { + tmp->data[(yc * tmp->scansize) + xc] = str[xc]; + } + + yc++; + } while (!feof(inFile) && (yc < tmp->height) && fgets(str, sizeof(str), inFile)); + + + if (yc < tmp->height) + { + THERR("Broken block, height %d < %d\n", + yc, tmp->height); + goto err; + } + + return tmp; + +err: + mapBlockFree(tmp); + return NULL; +} + + +/* Find the min/max of given coordinates. + */ +void findWorldSize(MapBlock *tmp, + int *worldX0, int *worldY0, + int *worldX1, int *worldY1) +{ + if (tmp->xc < *worldX0) *worldX0 = tmp->xc; + if ((tmp->xc + tmp->width) > *worldX1) *worldX1 = (tmp->xc + tmp->width); + + if (tmp->yc < *worldY0) *worldY0 = tmp->yc; + if ((tmp->yc + tmp->height) > *worldY1) *worldY1 = (tmp->yc + tmp->height); +} + + +int main(int argc, char *argv[]) +{ + BOOL isOK; + int i, currRounds, nmapBlocks = 0, currBlocks, + worldX0, worldY0, worldX1, worldY1, + offsetX = 0, offsetY = 0, + res = 0; + MapBlock *worldMap = NULL, *initialMap = NULL; + MapBlock **mapBlocks = NULL; + + th_init("stitchmap", "Yet Another ASCII Map Auto-Stitcher", "0.5", NULL, NULL); + th_verbosity = 1; + + // Parse arguments + if (!th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, OPTH_BAILOUT)) + { + res = 1; + goto exit; + } + + if (nsrcFiles < 1) + { + THERR("Nothing to do. (try --help)\n"); + res = 1; + goto exit; + } + + // Read initial map + if (optInitialMap) + { + THMSG(1, "Reading initial map '%s'\n", optInitialMap); + initialMap = mapBlockParseFile(optInitialMap, FALSE); + if (initialMap) + { + THMSG(2, "Initial dimensions %d x %d\n", + initialMap->width, initialMap->height); + + mapBlockClean(initialMap, (unsigned char*) " ", 1); + } + else + { + THERR("Initial map could not be loaded!\n"); + res = 1; + goto exit; + } + } + + /* Read in continuous mapdata and parse it into map blocks + */ + for (nmapBlocks = i = 0; i < nsrcFiles; i++) + { + FILE *tmpFile = NULL; + char centerCh; + + if (optCenterCh > 0) + { + centerCh = optCenterCh; + } + else + { + if (optWalkMode) + centerCh = '@'; + else + centerCh = '*'; + } + + if ((tmpFile = fopen(srcFiles[i], "rb")) == NULL) + { + THERR("Error opening input file '%s'!\n", + srcFiles[i]); + res = 16; + goto exit; + } + + while (!feof(tmpFile)) + { + MapBlock *tmp; + if ((tmp = parseBlock(tmpFile)) != NULL) + { + if (optAdjustBlocks && !adjustBlockCenter(tmp, centerCh)) + { + THERR("Block center not found, rejected.\n"); + mapBlockFree(tmp); + } + else + { + nmapBlocks++; + mapBlocks = (MapBlock **) th_realloc(mapBlocks, sizeof(MapBlock *) * nmapBlocks); + if (mapBlocks == NULL) + { + fclose(tmpFile); + THERR("Could not allocate/extend mapblock pointer structure (#%d)\n", nmapBlocks); + res = 18; + goto exit; + } + + mapBlockClean(tmp, (unsigned char *) optCleanChars, strlen(optCleanChars)); + mapBlocks[nmapBlocks - 1] = tmp; + } + } + } + + fclose(tmpFile); + } + + THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks); + + if (nmapBlocks <= 0) + { + THERR("No mapblocks, nothing to do.\n"); + res = 11; + goto exit; + } + + + /* ALGORITHM + * --------- + find dimensions of the world map: + for (each block in list) { + if (block->x < mapx0) mapx0 = block->x; else + if (block->x+block->w > mapx1) mapx1 = block->x+block->w; + ... + } + + allocate map + + start placing blocks: + for (n = 0; n < rounds; n++) { + for (each block in list) { + if (check match % at given coordinates > threshold) + place block + } + + if (all blocks placed) break + } + */ + + + // If initial map is available, find a match and determine coords + if (initialMap) + { + initialMap->xc = optInitialX; + initialMap->yc = optInitialY; + } + + // Clear marks + for (i = 0; i < nmapBlocks; i++) + mapBlocks[i]->mark = FALSE; + + + // Get world dimensions + worldX0 = worldY0 = worldX1 = worldY1 = 0; + for (i = 0; i < nmapBlocks; i++) + findWorldSize(mapBlocks[i], &worldX0, &worldY0, &worldX1, &worldY1); + + if (initialMap) + findWorldSize(initialMap, &worldX0, &worldY0, &worldX1, &worldY1); + + // Compute offsets, allocate world map + // FIXME: check dimensions + offsetX = -worldX0; + offsetY = -worldY0; + + if ((worldMap = mapBlockAlloc(worldX1 - worldX0 + 1, worldY1 - worldY0 + 1)) == NULL) + { + THERR("Error allocating world map!\n"); + res = 4; + goto exit; + } + + // Place optional initial map + if (initialMap) + { + if (mapBlockPut(&worldMap, initialMap, offsetX + initialMap->xc, offsetY + initialMap->yc) < 0) + { + THERR("Initial map mapBlockPut() failed!\n"); + res = 9; + goto exit; + } + } + else + { + i = 0; + if (mapBlockPut(&worldMap, mapBlocks[i], offsetX + mapBlocks[i]->xc, offsetY + mapBlocks[i]->yc) < 0) + { + THERR("Initial map mapBlockPut() failed!\n"); + res = 9; + goto exit; + } + mapBlocks[i]->mark = TRUE; + } + + THMSG(1, "Initialized world map of (%d x %d), offset (%d, %d)\n", + worldMap->width, worldMap->height, offsetX, offsetY); + + + // Start placing blocks + currRounds = 0; + isOK = FALSE; + while (currRounds++ < optRounds && !isOK) + { + int usedBlocks; + + // Get number of used blocks + for (usedBlocks = i = 0; i < nmapBlocks; i++) + if (mapBlocks[i]->mark) usedBlocks++; + + // Print out status information + THPRINT(2, "#%d [%d/%d]: ", + currRounds, usedBlocks, nmapBlocks); + + // Place and match blocks + isOK = TRUE; + currBlocks = 0; + for (i = 0; i < nmapBlocks && currBlocks < optReset; i++) + { + MapBlock *tmp = mapBlocks[i]; + + if (!tmp->mark) + { + int qx = offsetX + tmp->xc, + qy = offsetY + tmp->yc; + + isOK = FALSE; + + if (matchBlock(worldMap, tmp, qx, qy, optHardDrop) >= optMatch) + { + if (mapBlockPut(&worldMap, tmp, qx, qy) < 0) + { + THERR("mapBlockPut(%d, %d, %d) failed!\n", + offsetX, offsetY, i); + res = 9; + goto exit; + } + tmp->mark = TRUE; + + currBlocks++; + THPRINT(2, "X"); + } + else + { + THPRINT(2, "."); + +#if 0 + // Debug unmatching blocks + char mysti[512]; + snprintf(mysti, sizeof(mysti), + "[%d]: %d,%d (%d,%d)", + i, qx, qy, tmp->x, tmp->y); + fprintf(stderr, "\n--- %.30s ---\n", mysti); + mapBlockPrint(stderr, tmp); + fprintf(stderr, "---------\n"); + mapBlockPrint(stderr, worldMap); +#endif + } + } + } + + THPRINT(2, "\n"); + } + + /* Output generated map + */ + if (worldMap) + { + int unusedBlocks; + FILE *tmpFile; + + THMSG(1, "Outputting generated map of (%d x %d) ...\n", + worldMap->width, worldMap->height); + + if (destFile == NULL) + tmpFile = stdout; + else + if ((tmpFile = fopen(destFile, "wb")) == NULL) + { + THERR("Error opening output file '%s'!\n", + destFile); + res = 1; + goto exit; + } + + mapBlockPrint(tmpFile, worldMap); + + fclose(tmpFile); + + // Compute number of unused blocks + for (unusedBlocks = i = 0; i < nmapBlocks; i++) + if (!mapBlocks[i]->mark) + { + if (optDumpRejected) + { + fprintf(stderr, "\n#%d: %d,%d (%d,%d)\n", + i, + mapBlocks[i]->xc, mapBlocks[i]->yc, + mapBlocks[i]->xc + offsetX, + mapBlocks[i]->yc + offsetY); + + mapBlockPrint(stderr, mapBlocks[i]); + } + unusedBlocks++; + } + + THMSG(1, "%d mapblocks unused/discarded\n", unusedBlocks); + } + else + { + THERR("No map generated?\n"); + res = 6; + } + +exit: + mapBlockFree(worldMap); + mapBlockFree(initialMap); + + if (mapBlocks != NULL) + { + for (i = 0; i < nmapBlocks; i++) + mapBlockFree(mapBlocks[i]); + + th_free(mapBlocks); + } + + return res; +}
--- a/stitchmap.c Tue Jul 27 09:48:54 2021 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,740 +0,0 @@ -/* - * stitchmap - Compute complete ASCII map by stitching pieces - * together based on a matcher and coordinates - * - * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> - * (C) Copyright 2006-2021 Tecnic Software productions (TNSP) - */ -#include "libmaputils.h" -#include "th_args.h" -#include "th_string.h" - -#define MAX_FILES (256) - - -/* Variables - */ -int nsrcFiles = 0; -char *srcFiles[MAX_FILES], - *destFile = NULL; - -int optInitialX = 0, - optInitialY = 0, - optMapFactor = 10, - optRounds = 100, - optReset = 50; -BOOL optHardDrop = FALSE, - optDumpRejected = FALSE, - optAdjustBlocks = FALSE, - optWalkMode = FALSE; -float optMatch = 40.0; -char *optInitialMap = NULL, - *optCleanChars = " *@", - optCenterCh = -1; - - -/* Arguments - */ -static const th_optarg optList[] = -{ - { 0, '?', "help", "Show this help", OPT_NONE }, - { 1, 'o', "output", "Specify output file", OPT_ARGREQ }, - { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, - { 3, 'q', "quiet", "Be quiet", OPT_NONE }, - { 4, 'm', "match", "Match percentage", OPT_ARGREQ }, - { 5, 'r', "rounds", "Processing timeout # rounds", OPT_ARGREQ }, - { 12,'R', "reset", "Round reset after", OPT_ARGREQ }, - { 11,'d', "drop", "Use hard dropping (bailout on first mismatch)", OPT_NONE }, - { 10,'I', "initial", "Initial map file", OPT_ARGREQ }, - { 14,'x', "initial-x", "Initial map X offset", OPT_ARGREQ }, - { 15,'y', "initial-y", "Initial map Y offset", OPT_ARGREQ }, - { 16,'D', "dump", "Dump rejected pieces", OPT_NONE }, - { 17,'a', "adjust-size","Adjust to variable size blocks", OPT_NONE }, - { 18,'W', "walk-mode", "Walk mode (instead of ship mode)", OPT_NONE }, - { 19,'c', "clean-chars","Characters to filter from input", OPT_ARGREQ }, - { 20,'C', "center-char","Center character symbol (for -a)", OPT_ARGREQ }, -}; - -static const int optListN = sizeof(optList) / sizeof(optList[0]); - - -void argShowHelp() -{ - th_print_banner(stdout, th_prog_name, - "[options] <inputfile> [inputfile#2..]"); - - th_args_help(stdout, optList, optListN, 0, 80 - 2); -} - - -BOOL argHandleOpt(const int optN, char *optArg, char *currArg) -{ - switch (optN) - { - case 0: - argShowHelp(); - exit(0); - break; - - case 1: - destFile = optArg; - THMSG(1, "Output file '%s'\n", destFile); - break; - - case 2: - th_verbosity++; - break; - - case 3: - th_verbosity = -1; - break; - - case 4: - optMatch = atof(optArg); - THMSG(1, "Match at %1.4f%%\n", optMatch); - break; - - case 5: - optRounds = atoi(optArg); - THMSG(1, "Processing rounds timeout %d\n", optRounds); - break; - - case 12: - optReset = atoi(optArg); - THMSG(1, "Round reset after %d blocks\n", optReset); - break; - - case 10: - optInitialMap = optArg; - THMSG(1, "Using initial map file = '%s'\n", optArg); - break; - - case 11: - optHardDrop = TRUE; - THMSG(1, "Using hard dropping (instant bailout on mismatch).\n"); - break; - - case 14: - optInitialX = atoi(optArg); - THMSG(1, "Initial map X offset = %d\n", optInitialX); - break; - - case 15: - optInitialY = atoi(optArg); - THMSG(1, "Initial map Y offset = %d\n", optInitialY); - break; - - case 16: - THMSG(1, "Dumping rejected blocks.\n"); - optDumpRejected = TRUE; - break; - - case 17: - THMSG(1, "Handling variable size blocks via adjustment.\n"); - optAdjustBlocks = TRUE; - break; - - case 18: - THMSG(1, "Walk mode enabled.\n"); - optWalkMode = TRUE; - break; - - case 19: - optCleanChars = optArg; - THMSG(1, "Removing '%s' from input blocks\n", optArg); - break; - - case 20: - if (strlen(optArg) != 1) - { - THERR("Invalid argument '%s' for -C, only one character symbol can be specified.\n", optArg); - return FALSE; - } - else - { - optCenterCh = optArg[0]; - THMSG(1, "Using '%c' as centering character symbol\n", optCenterCh); - } - break; - - default: - THERR("Unknown option '%s'.\n", currArg); - return FALSE; - break; - } - - return TRUE; -} - - -BOOL argHandleFile(char *currArg) -{ - if (nsrcFiles < MAX_FILES) - { - srcFiles[nsrcFiles] = currArg; - nsrcFiles++; - } - else - { - THERR("Too many input files specified (%d max)!\n", MAX_FILES); - return FALSE; - } - - return TRUE; -} - - -/* Calculate matching percentage of given block against a map, - * with using specified x/y offsets. - */ -float matchBlock(MapBlock *map, MapBlock *match, - const int ox, const int oy, const BOOL hardDrop) -{ - int n, k; - - n = k = 0; - - for (int y = 0; y < match->height; y++) - for (int x = 0; x < match->width; x++) - { - const int - dx = ox + x, - dy = oy + y; - int c1, c2; - - k++; - - if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height) - c2 = map->data[(dy * map->scansize) + dx]; - else - c2 = 0; - - c1 = match->data[(y * match->scansize) + x]; - - if (c1 == 0 || c2 == 0) - { - // Both empty ... - } - else - if (c1 != 0 && c1 == c2) - { - // Exact match, increase % - n++; - } - else - if (hardDrop) - { - // Mismatch, return failure - return -1; - } - } - - if (k > 0) - return ((float) n * 100.0f) / (float) k; - else - return 0.0f; -} - - -BOOL adjustBlockCenter(MapBlock *block, const char centerCh) -{ - for (int yc = 0; yc < block->height; yc++) - for (int xc = 0; xc < block->width; xc++) - { - int ch = block->data[(yc * block->scansize) + xc]; - if (ch == centerCh) - { - block->xc -= xc; - block->yc -= yc; - return TRUE; - } - } - - return FALSE; -} - - -/* Parse next block (marked by string optPattern) from - * input stream into a mapblock, return NULL if not found or error. - */ -BOOL getLineData(FILE *inFile, char *buf, const size_t bufSize) -{ - if (feof(inFile) || !fgets(buf, bufSize, inFile)) - { - THERR("Unexpected end of file.\n"); - return FALSE; - } - else - return TRUE; -} - - -size_t getLineWidth(char *buf, const size_t bufSize) -{ - size_t width; - - for (width = 0; - width + 1 < bufSize && - buf[width] != 0 && - buf[width] != '\r' && - buf[width] != '\n'; width++); - - buf[width] = 0; - - return width; -} - - -MapBlock * parseBlock(FILE *inFile) -{ - MapBlock *tmp = NULL; - int xc, yc, tmpW, tmpH; - char str[4096], *pos = NULL; - BOOL isFound; - size_t i; - - isFound = FALSE; - while (!feof(inFile) && !isFound && fgets(str, sizeof(str), inFile)) - { - if ((pos = strstr(str, ": !!PLR: ")) != NULL && - (strstr(str, "party") || strstr(str, "report"))) - isFound = TRUE; - } - - if (!isFound) - { - THERR("No block start marker found.\n"); - goto err; - } - - // Trim end - for (i = 0; i < sizeof(str) && str[i] && str[i] != '\r' && str[i] != '\n'; i++); - str[i] = 0; - - if (sscanf(pos, ": !!PLR: %d,%d", &xc, &yc) < 2) - { - THERR("Could not get location coordinates:\n'%s'\n", - str); - goto err; - } - - // Check for discardable lines - if (!getLineData(inFile, str, sizeof(str))) - goto err; - - if (!th_strcasecmp(str, "You glance around.")) - { - if (!getLineData(inFile, str, sizeof(str))) - goto err; - } - - // New 'map' output in city maps has an empty line before the data, ignore it - if ((tmpW = getLineWidth(str, sizeof(str))) <= 9) - { - if (!getLineData(inFile, str, sizeof(str))) - goto err; - - tmpW = getLineWidth(str, sizeof(str)); - } - - if (optWalkMode) - { - switch (tmpW) - { - case 31: tmpH = 17; break; - case 13: tmpH = 7; break; - case 9: tmpH = 9; break; - default: - THERR("Invalid block width %d [%d, %d], rejecting.\n", tmpW, xc, yc); - return NULL; - break; - } - } - else - tmpH = tmpW - 8; - - if ((tmp = mapBlockAlloc(tmpW, tmpH)) == NULL) - { - THERR("Could not allocate mapblock (%d, %d)\n", tmpW, tmpH); - goto err; - } - - tmp->xc = xc; - tmp->yc = yc; - - yc = 0; - do - { - i = getLineWidth(str, tmpW); - - if (i != (size_t) tmp->width) - { - THERR("Broken block, line width %" PRIu_SIZE_T " != %d!\n", - i, tmp->width); - goto err; - } - - for (xc = 0; xc < tmp->width; xc++) - { - tmp->data[(yc * tmp->scansize) + xc] = str[xc]; - } - - yc++; - } while (!feof(inFile) && (yc < tmp->height) && fgets(str, sizeof(str), inFile)); - - - if (yc < tmp->height) - { - THERR("Broken block, height %d < %d\n", - yc, tmp->height); - goto err; - } - - return tmp; - -err: - mapBlockFree(tmp); - return NULL; -} - - -/* Find the min/max of given coordinates. - */ -void findWorldSize(MapBlock *tmp, - int *worldX0, int *worldY0, - int *worldX1, int *worldY1) -{ - if (tmp->xc < *worldX0) *worldX0 = tmp->xc; - if ((tmp->xc + tmp->width) > *worldX1) *worldX1 = (tmp->xc + tmp->width); - - if (tmp->yc < *worldY0) *worldY0 = tmp->yc; - if ((tmp->yc + tmp->height) > *worldY1) *worldY1 = (tmp->yc + tmp->height); -} - - -int main(int argc, char *argv[]) -{ - BOOL isOK; - int i, currRounds, nmapBlocks = 0, currBlocks, - worldX0, worldY0, worldX1, worldY1, - offsetX = 0, offsetY = 0, - res = 0; - MapBlock *worldMap = NULL, *initialMap = NULL; - MapBlock **mapBlocks = NULL; - - th_init("stitchmap", "Yet Another ASCII Map Auto-Stitcher", "0.5", NULL, NULL); - th_verbosity = 1; - - // Parse arguments - if (!th_args_process(argc, argv, optList, optListN, - argHandleOpt, argHandleFile, OPTH_BAILOUT)) - { - res = 1; - goto exit; - } - - if (nsrcFiles < 1) - { - THERR("Nothing to do. (try --help)\n"); - res = 1; - goto exit; - } - - // Read initial map - if (optInitialMap) - { - THMSG(1, "Reading initial map '%s'\n", optInitialMap); - initialMap = mapBlockParseFile(optInitialMap, FALSE); - if (initialMap) - { - THMSG(2, "Initial dimensions %d x %d\n", - initialMap->width, initialMap->height); - - mapBlockClean(initialMap, (unsigned char*) " ", 1); - } - else - { - THERR("Initial map could not be loaded!\n"); - res = 1; - goto exit; - } - } - - /* Read in continuous mapdata and parse it into map blocks - */ - for (nmapBlocks = i = 0; i < nsrcFiles; i++) - { - FILE *tmpFile = NULL; - char centerCh; - - if (optCenterCh > 0) - { - centerCh = optCenterCh; - } - else - { - if (optWalkMode) - centerCh = '@'; - else - centerCh = '*'; - } - - if ((tmpFile = fopen(srcFiles[i], "rb")) == NULL) - { - THERR("Error opening input file '%s'!\n", - srcFiles[i]); - res = 16; - goto exit; - } - - while (!feof(tmpFile)) - { - MapBlock *tmp; - if ((tmp = parseBlock(tmpFile)) != NULL) - { - if (optAdjustBlocks && !adjustBlockCenter(tmp, centerCh)) - { - THERR("Block center not found, rejected.\n"); - mapBlockFree(tmp); - } - else - { - nmapBlocks++; - mapBlocks = (MapBlock **) th_realloc(mapBlocks, sizeof(MapBlock *) * nmapBlocks); - if (mapBlocks == NULL) - { - fclose(tmpFile); - THERR("Could not allocate/extend mapblock pointer structure (#%d)\n", nmapBlocks); - res = 18; - goto exit; - } - - mapBlockClean(tmp, (unsigned char *) optCleanChars, strlen(optCleanChars)); - mapBlocks[nmapBlocks - 1] = tmp; - } - } - } - - fclose(tmpFile); - } - - THMSG(1, "Total of %d mapblocks read.\n", nmapBlocks); - - if (nmapBlocks <= 0) - { - THERR("No mapblocks, nothing to do.\n"); - res = 11; - goto exit; - } - - - /* ALGORITHM - * --------- - find dimensions of the world map: - for (each block in list) { - if (block->x < mapx0) mapx0 = block->x; else - if (block->x+block->w > mapx1) mapx1 = block->x+block->w; - ... - } - - allocate map - - start placing blocks: - for (n = 0; n < rounds; n++) { - for (each block in list) { - if (check match % at given coordinates > threshold) - place block - } - - if (all blocks placed) break - } - */ - - - // If initial map is available, find a match and determine coords - if (initialMap) - { - initialMap->xc = optInitialX; - initialMap->yc = optInitialY; - } - - // Clear marks - for (i = 0; i < nmapBlocks; i++) - mapBlocks[i]->mark = FALSE; - - - // Get world dimensions - worldX0 = worldY0 = worldX1 = worldY1 = 0; - for (i = 0; i < nmapBlocks; i++) - findWorldSize(mapBlocks[i], &worldX0, &worldY0, &worldX1, &worldY1); - - if (initialMap) - findWorldSize(initialMap, &worldX0, &worldY0, &worldX1, &worldY1); - - // Compute offsets, allocate world map - // FIXME: check dimensions - offsetX = -worldX0; - offsetY = -worldY0; - - if ((worldMap = mapBlockAlloc(worldX1 - worldX0 + 1, worldY1 - worldY0 + 1)) == NULL) - { - THERR("Error allocating world map!\n"); - res = 4; - goto exit; - } - - // Place optional initial map - if (initialMap) - { - if (mapBlockPut(&worldMap, initialMap, offsetX + initialMap->xc, offsetY + initialMap->yc) < 0) - { - THERR("Initial map mapBlockPut() failed!\n"); - res = 9; - goto exit; - } - } - else - { - i = 0; - if (mapBlockPut(&worldMap, mapBlocks[i], offsetX + mapBlocks[i]->xc, offsetY + mapBlocks[i]->yc) < 0) - { - THERR("Initial map mapBlockPut() failed!\n"); - res = 9; - goto exit; - } - mapBlocks[i]->mark = TRUE; - } - - THMSG(1, "Initialized world map of (%d x %d), offset (%d, %d)\n", - worldMap->width, worldMap->height, offsetX, offsetY); - - - // Start placing blocks - currRounds = 0; - isOK = FALSE; - while (currRounds++ < optRounds && !isOK) - { - int usedBlocks; - - // Get number of used blocks - for (usedBlocks = i = 0; i < nmapBlocks; i++) - if (mapBlocks[i]->mark) usedBlocks++; - - // Print out status information - THPRINT(2, "#%d [%d/%d]: ", - currRounds, usedBlocks, nmapBlocks); - - // Place and match blocks - isOK = TRUE; - currBlocks = 0; - for (i = 0; i < nmapBlocks && currBlocks < optReset; i++) - { - MapBlock *tmp = mapBlocks[i]; - - if (!tmp->mark) - { - int qx = offsetX + tmp->xc, - qy = offsetY + tmp->yc; - - isOK = FALSE; - - if (matchBlock(worldMap, tmp, qx, qy, optHardDrop) >= optMatch) - { - if (mapBlockPut(&worldMap, tmp, qx, qy) < 0) - { - THERR("mapBlockPut(%d, %d, %d) failed!\n", - offsetX, offsetY, i); - res = 9; - goto exit; - } - tmp->mark = TRUE; - - currBlocks++; - THPRINT(2, "X"); - } - else - { - THPRINT(2, "."); - -#if 0 - // Debug unmatching blocks - char mysti[512]; - snprintf(mysti, sizeof(mysti), - "[%d]: %d,%d (%d,%d)", - i, qx, qy, tmp->x, tmp->y); - fprintf(stderr, "\n--- %.30s ---\n", mysti); - mapBlockPrint(stderr, tmp); - fprintf(stderr, "---------\n"); - mapBlockPrint(stderr, worldMap); -#endif - } - } - } - - THPRINT(2, "\n"); - } - - /* Output generated map - */ - if (worldMap) - { - int unusedBlocks; - FILE *tmpFile; - - THMSG(1, "Outputting generated map of (%d x %d) ...\n", - worldMap->width, worldMap->height); - - if (destFile == NULL) - tmpFile = stdout; - else - if ((tmpFile = fopen(destFile, "wb")) == NULL) - { - THERR("Error opening output file '%s'!\n", - destFile); - res = 1; - goto exit; - } - - mapBlockPrint(tmpFile, worldMap); - - fclose(tmpFile); - - // Compute number of unused blocks - for (unusedBlocks = i = 0; i < nmapBlocks; i++) - if (!mapBlocks[i]->mark) - { - if (optDumpRejected) - { - fprintf(stderr, "\n#%d: %d,%d (%d,%d)\n", - i, - mapBlocks[i]->xc, mapBlocks[i]->yc, - mapBlocks[i]->xc + offsetX, - mapBlocks[i]->yc + offsetY); - - mapBlockPrint(stderr, mapBlocks[i]); - } - unusedBlocks++; - } - - THMSG(1, "%d mapblocks unused/discarded\n", unusedBlocks); - } - else - { - THERR("No map generated?\n"); - res = 6; - } - -exit: - mapBlockFree(worldMap); - mapBlockFree(initialMap); - - if (mapBlocks != NULL) - { - for (i = 0; i < nmapBlocks; i++) - mapBlockFree(mapBlocks[i]); - - th_free(mapBlocks); - } - - return res; -}