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,
-        &centerX, &centerY, 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,
+        &centerX, &centerY, 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;
-}