changeset 313:b3d46806787d

Move a number of more or less generic helper functions into a separate sidutil.[ch] module.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 11 Jan 2020 18:30:10 +0200
parents 1950bb04a69b
children 71139ed7e43f
files Makefile.gen sidinfo.c sidutil.c sidutil.h
diffstat 4 files changed, 375 insertions(+), 286 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Sat Jan 11 14:28:22 2020 +0200
+++ b/Makefile.gen	Sat Jan 11 18:30:10 2020 +0200
@@ -10,7 +10,7 @@
 THLIBS_OBJ=th_util.o th_string.o th_ioctx.o \
 	th_file.o th_args.o th_crypto.o th_datastruct.o
 
-SIDINFO_OBJ=sidlib.o sidinfo.o
+SIDINFO_OBJ=sidlib.o sidutil.o sidinfo.o
 SIDINFO_BIN=$(BINPATH)sidinfo$(BINEXT)
 
 TARGETS += $(SIDINFO_BIN)
--- a/sidinfo.c	Sat Jan 11 14:28:22 2020 +0200
+++ b/sidinfo.c	Sat Jan 11 18:30:10 2020 +0200
@@ -8,27 +8,14 @@
 #include "th_file.h"
 #include "th_datastruct.h"
 #include "sidlib.h"
+#include "sidutil.h"
 #include <sys/types.h>
 #include <dirent.h>
-#ifdef HAVE_ICONV
-#    include <iconv.h>
-#endif
 
 
 //
 // Some constants
 //
-
-// HVSC documents directory
-#define SET_HVSC_DOCUMENTS   "DOCUMENTS"
-
-// Songlengths database filename prefix (.md5|.txt appended)
-#define SET_SLDB_FILEBASE    "Songlengths"
-
-// STIL database file
-#define SET_STILDB_FILENAME  "STIL.txt"
-
-
 enum
 {
     OFMT_QUOTED    = 0x0001,
@@ -44,15 +31,6 @@
 };
 
 
-enum
-{
-    TH_LANG_UTF8,
-    TH_LANG_ISO88591,
-    TH_LANG_CP850,
-    TH_LANG_CP437,
-};
-
-
 typedef struct
 {
     int cmd;
@@ -135,13 +113,7 @@
 SIDLibSLDB *sidSLDB = NULL;
 SIDLibSTILDB *sidSTILDB = NULL;
 
-
-BOOL    setUseOutConv;
-#ifdef HAVE_ICONV
-iconv_t setIConvCtx;
-#else
-int     setOutLang;
-#endif
+SIDUtilChConvCtx setChConv;
 
 
 // Define option arguments
@@ -167,47 +139,11 @@
 static const int optListN = sizeof(optList) / sizeof(optList[0]);
 
 
-void argShowLicense(void)
-{
-    printf("%s - %s\n%s\n", th_prog_name, th_prog_desc, th_prog_author);
-    printf(
-    "\n"
-    "Redistribution and use in source and binary forms, with or without\n"
-    "modification, are permitted provided that the following conditions\n"
-    "are met:\n"
-    "\n"
-    " 1. Redistributions of source code must retain the above copyright\n"
-    "    notice, this list of conditions and the following disclaimer.\n"
-    "\n"
-    " 2. Redistributions in binary form must reproduce the above copyright\n"
-    "    notice, this list of conditions and the following disclaimer in\n"
-    "    the documentation and/or other materials provided with the\n"
-    "    distribution.\n"
-    "\n"
-    " 3. The name of the author may not be used to endorse or promote\n"
-    "    products derived from this software without specific prior written\n"
-    "    permission.\n"
-    "\n"
-    "THIS SOFTWARE IS PROVIDED BY THE AUTHOR \"AS IS\" AND ANY EXPRESS OR\n"
-    "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
-    "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
-    "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,\n"
-    "INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n"
-    "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n"
-    "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
-    "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
-    "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
-    "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
-    "POSSIBILITY OF SUCH DAMAGE.\n"
-    );
-}
-
-
 void argShowHelp(void)
 {
     int index, len;
 
-    th_print_banner(stdout, th_prog_name, "[options] <sid filename> [sid filename #2 ..]");
+    th_print_banner(stdout, th_prog_name, "[options] <sid file|path> [file|path #2 ..]");
     th_args_help(stdout, optList, optListN, 0);
     printf(
         "\n"
@@ -281,43 +217,6 @@
 }
 
 
-const char *siStripHVSCPath(const char *filename)
-{
-    if (setHVSCPath != NULL)
-    {
-        const char *hvsc = setHVSCPath, *fptr = filename;
-
-        // Compare until end of string(s)
-        for (; *hvsc != 0 && *fptr != 0 && *hvsc == *fptr; hvsc++, fptr++);
-
-        // Full match?
-        if (*hvsc == 0)
-            return fptr - 1;
-    }
-
-    return filename;
-}
-
-
-char *siCheckHVSCFilePath(const char *filebase, const char *fext)
-{
-    th_stat_data sdata;
-    char *npath = th_strdup_printf("%s%c%s%c%s%s",
-        setHVSCPath, TH_DIR_SEPARATOR_CHR,
-        SET_HVSC_DOCUMENTS, TH_DIR_SEPARATOR_CHR,
-        filebase, fext != NULL ? fext : "");
-
-    if (npath != NULL &&
-        th_stat_path(npath, &sdata) &&
-        (sdata.flags & TH_IS_READABLE) &&
-        (sdata.flags & TH_IS_DIR) == 0)
-        return npath;
-
-    th_free(npath);
-    return NULL;
-}
-
-
 BOOL siStackAddItem(PSFStack *stack, const PSFStackItem *item)
 {
     if (stack->items == NULL || stack->nitems + 1 >= stack->nallocated)
@@ -396,128 +295,6 @@
 }
 
 
-#ifndef HAVE_ICONV
-
-static const uint8_t si_lang_iso88591_to_cp850[16*6] = {
-0xff, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee,
-0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8,
-0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8,
-0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1,
-0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
-0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98,
-};
-
-static const uint8_t si_lang_iso88591_to_cp437[16*6] = {
-0xff, 0xad, 0x9b, 0x9c, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xae, 0xaa, 0x00, 0x00, 0x00,
-0xf8, 0xf1, 0xfd, 0x00, 0x00, 0xe6, 0x00, 0xfa, 0x00, 0x00, 0xa7, 0xaf, 0xac, 0xab, 0x00, 0xa8,
-0x00, 0x00, 0x00, 0x00, 0x8e, 0x8f, 0x92, 0x80, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0xe1,
-0x85, 0xa0, 0x83, 0x00, 0x84, 0x86, 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
-0x00, 0xa4, 0x95, 0xa2, 0x93, 0x00, 0x94, 0xf6, 0x00, 0x97, 0xa3, 0x96, 0x81, 0x00, 0x00, 0x98,
-};
-
-#endif
-
-
-char *siConvertCharset(const char *src)
-{
-#ifdef HAVE_ICONV
-    size_t srcLeft = strlen(src) + 1;
-    size_t outLeft = srcLeft * 2;
-    char *srcPtr = (char *) src;
-    char *outBuf, *outPtr;
-
-    if ((outBuf = outPtr = th_malloc(outLeft + 1)) == NULL)
-        return NULL;
-
-    while (srcLeft > 0)
-    {
-        size_t ret = iconv(setIConvCtx, &srcPtr, &srcLeft, &outPtr, &outLeft);
-        if (ret == (size_t) -1)
-            break;
-    }
-
-#else
-    // Fallback conversion of ISO-8859-1 to X
-    size_t srcSize = strlen(src), outSize, minLeft;
-    const uint8_t *srcPtr = (const uint8_t *) src;
-    const uint8_t *tab;
-    uint8_t *outBuf, *outPtr;
-
-    switch (setOutLang)
-    {
-        case TH_LANG_UTF8:
-            outSize = srcSize * 2;
-            minLeft = 2;
-            break;
-
-        default:
-            outSize = srcSize;
-            minLeft = 1;
-    }
-
-    if ((outBuf = outPtr = th_malloc(outSize)) == NULL)
-        return NULL;
-
-    while (srcSize > 0 && outSize >= minLeft)
-    {
-        switch (setOutLang)
-        {
-            case TH_LANG_UTF8:
-                // Not 100% correct really, but close enough
-                if (*srcPtr < 0x80)
-                {
-                    *outPtr++ = *srcPtr;
-                    outSize--;
-                }
-                else
-                if (*srcPtr < 0xBF)
-                {
-                    *outPtr++ = 0xC2;
-                    *outPtr++ = *srcPtr;
-                    outSize -= 2;
-                }
-                else
-                {
-                    *outPtr++ = 0xC3;
-                    *outPtr++ = *srcPtr - 0x40;
-                    outSize -= 2;
-                }
-                break;
-
-            case TH_LANG_ISO88591:
-                *outPtr++ = *srcPtr;
-                outSize--;
-                break;
-
-            case TH_LANG_CP850:
-            case TH_LANG_CP437:
-                // Not 100% correct either, but close enough
-                tab = (setOutLang == TH_LANG_CP850) ? si_lang_iso88591_to_cp850 : si_lang_iso88591_to_cp437;
-
-                if (*srcPtr < 0x7f)
-                    *outPtr++ = *srcPtr;
-                else
-                if (*srcPtr >= 0xA0)
-                    *outPtr++ = tab[*srcPtr - 0xA0];
-                else
-                    *outPtr++ = '?';
-
-                outSize--;
-                break;
-        }
-
-        srcPtr++;
-        srcSize--;
-    }
-
-    *outPtr++ = 0;
-#endif
-
-    return (char *) outBuf;
-}
-
-
 static int siItemFormatStrPutInt(th_vprintf_ctx *ctx, th_vprintf_putch vputch,
     const int value, const int f_radix, int f_flags, int f_width, int f_prec,
     const BOOL f_unsig, th_vprintf_altfmt_func f_alt)
@@ -857,7 +634,7 @@
         break;
 
     case 10:
-        argShowLicense();
+        sidutil_print_license();
         exit(0);
         break;
 
@@ -1040,9 +817,9 @@
 {
     char *str, *tmp;
 
-    if (setUseOutConv && d_str != NULL && convert)
+    if (d_str != NULL && setChConv.enabled && convert)
     {
-        char *tmp2 = siConvertCharset(d_str);
+        char *tmp2 = sidutil_chconv_convert(&setChConv, d_str);
         tmp = siEscapeString(tmp2, optEscapeChars);
         th_free(tmp2);
     }
@@ -1282,7 +1059,9 @@
     // Get STIL information, if any
     if (sidSTILDB != NULL)
     {
-        psid->stil = sidlib_stildb_get_node(sidSTILDB, siStripHVSCPath(filename));
+        psid->stil = sidlib_stildb_get_node(sidSTILDB,
+            sidutil_strip_hvsc_path(setHVSCPath, filename));
+
         if (psid->stil != NULL)
             psid->stil->lengths = psid->lengths;
     }
@@ -1424,8 +1203,9 @@
 
 int main(int argc, char *argv[])
 {
-    char *setLang = th_strdup(getenv("LANG"));
     th_ioctx *inFile = NULL;
+    char *setLang = getenv("LANG");
+    int ret;
 
     // Get HVSC_BASE env variable if it is set
     th_pstr_cpy(&setHVSCPath, getenv("HVSC_BASE"));
@@ -1439,47 +1219,11 @@
 
     memset(&optFormat, 0, sizeof(optFormat));
 
-    // Get environment language
-    if (setLang != NULL)
+    // Initialize character conversion
+    if ((ret = sidutil_chconv_init(&setChConv, setLang)) != THERR_OK)
     {
-        // Get the character encoding part (e.g. "UTF-8" etc.) and
-        // strip out and lowercase everything (e.g. "utf8")
-        size_t i;
-        char *ptr = strchr(setLang, '.');
-        ptr = (ptr == NULL) ? setLang : ptr + 1;
-
-        for (i = 0; *ptr; ptr++)
-        {
-            if (*ptr != '-')
-                setLang[i++] = th_tolower(*ptr);
-        }
-        setLang[i] = 0;
-
-#ifdef HAVE_ICONV
-        // Initialize iconv, check if we have language/charset
-        setIConvCtx = iconv_open("utf8", "iso88591");
-        setUseOutConv = setIConvCtx != (iconv_t) -1;
-#else
-        // Check if we can use our fallback converter
-        if (strcmp(setLang, "utf8") == 0)
-            setOutLang = TH_LANG_UTF8;
-        else
-        if (strcmp(setLang, "iso88591") == 0 ||
-            strcmp(setLang, "cp819") == 0 ||
-            strcmp(setLang, "latin1") == 0 ||
-            strcmp(setLang, "cp28591") == 0)
-            setOutLang = TH_LANG_ISO88591;
-        else
-        if (strcmp(setLang, "cp850") == 0)
-            setOutLang = TH_LANG_CP850;
-        else
-        if (strcmp(setLang, "cp437") == 0)
-            setOutLang = TH_LANG_CP437;
-        else
-            setOutLang = TH_LANG_ISO88591;
-
-        setUseOutConv = setOutLang != TH_LANG_ISO88591;
-#endif
+        THERR("Could not initialize character set conversion (LANG='%s'): %s\n",
+            setLang, th_error_str(ret));
     }
 
     // Parse command line arguments
@@ -1488,7 +1232,7 @@
         goto out;
 
     THMSG(2, "Requested output LANG='%s', use charset conversion=%s\n",
-        setLang, setUseOutConv ? "yes" : "no");
+        setChConv.outLang, setChConv.enabled ? "yes" : "no");
 
     if (optOneLineFieldSep != NULL ||
         (!optFieldOutput && optFormat.nitems > 0))
@@ -1525,20 +1269,18 @@
 
         // If SLDB path is not set, autocheck for .md5 and .txt
         if (setSLDBPath == NULL)
-            setSLDBPath = siCheckHVSCFilePath(SET_SLDB_FILEBASE, ".md5");
+            setSLDBPath = sidutil_check_hvsc_file(setHVSCPath, SET_SLDB_FILEBASE, ".md5");
 
         if (setSLDBPath == NULL)
-            setSLDBPath = siCheckHVSCFilePath(SET_SLDB_FILEBASE, ".txt");
+            setSLDBPath = sidutil_check_hvsc_file(setHVSCPath, SET_SLDB_FILEBASE, ".txt");
 
         if (setSTILDBPath == NULL)
-            setSTILDBPath = siCheckHVSCFilePath(SET_STILDB_FILENAME, NULL);
+            setSTILDBPath = sidutil_check_hvsc_file(setHVSCPath, SET_STILDB_FILENAME, NULL);
     }
 
     if (setSLDBPath != NULL)
     {
         // Initialize SLDB
-        int ret = THERR_OK;
-
         setSLDBNewFormat = th_strrcasecmp(setSLDBPath, ".md5") != NULL;
 
         if ((ret = th_io_fopen(&inFile, &th_stdio_io_ops, setSLDBPath, "r")) != THERR_OK)
@@ -1584,8 +1326,6 @@
     if (setSTILDBPath != NULL)
     {
         // Initialize STILDB
-        int ret = THERR_OK;
-
         if ((ret = th_io_fopen(&inFile, &th_stdio_io_ops, setSTILDBPath, "r")) != THERR_OK)
         {
             THERR("Could not open STIL database '%s': %s\n",
@@ -1636,12 +1376,7 @@
 
 out:
 
-#ifdef HAVE_ICONV
-    if (setUseOutConv)
-        iconv_close(setIConvCtx);
-#endif
-
-    th_free(setLang);
+    sidutil_chconv_close(&setChConv);
 
     siClearStack(&optFormat);
     th_free(setHVSCPath);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sidutil.c	Sat Jan 11 18:30:10 2020 +0200
@@ -0,0 +1,280 @@
+/*
+ * SIDLib common utility functions
+ * Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ * (C) Copyright 2014-2020 Tecnic Software productions (TNSP)
+ */
+#include "sidutil.h"
+#include "th_file.h"
+#include "th_string.h"
+#include "th_datastruct.h"
+
+
+void sidutil_print_license(void)
+{
+    printf("%s - %s\n%s\n", th_prog_name, th_prog_desc, th_prog_author);
+    printf(
+    "\n"
+    "Redistribution and use in source and binary forms, with or without\n"
+    "modification, are permitted provided that the following conditions\n"
+    "are met:\n"
+    "\n"
+    " 1. Redistributions of source code must retain the above copyright\n"
+    "    notice, this list of conditions and the following disclaimer.\n"
+    "\n"
+    " 2. Redistributions in binary form must reproduce the above copyright\n"
+    "    notice, this list of conditions and the following disclaimer in\n"
+    "    the documentation and/or other materials provided with the\n"
+    "    distribution.\n"
+    "\n"
+    " 3. The name of the author may not be used to endorse or promote\n"
+    "    products derived from this software without specific prior written\n"
+    "    permission.\n"
+    "\n"
+    "THIS SOFTWARE IS PROVIDED BY THE AUTHOR \"AS IS\" AND ANY EXPRESS OR\n"
+    "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n"
+    "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n"
+    "ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,\n"
+    "INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n"
+    "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n"
+    "SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)\n"
+    "HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n"
+    "STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING\n"
+    "IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n"
+    "POSSIBILITY OF SUCH DAMAGE.\n"
+    );
+}
+
+
+const char *sidutil_strip_hvsc_path(const char *hvscPath, const char *filename)
+{
+    if (hvscPath != NULL)
+    {
+        const char *hvsc = hvscPath, *fptr = filename;
+
+        // Compare until end of string(s)
+        for (; *hvsc != 0 && *fptr != 0 && *hvsc == *fptr; hvsc++, fptr++);
+
+        // Full match?
+        if (*hvsc == 0)
+            return fptr - 1;
+    }
+
+    return filename;
+}
+
+
+char *sidutil_check_hvsc_file(const char *hvscPath, const char *filebase, const char *fext)
+{
+    th_stat_data sdata;
+    char *npath = th_strdup_printf("%s%c%s%c%s%s",
+        hvscPath, TH_DIR_SEPARATOR_CHR,
+        SET_HVSC_DOCUMENTS, TH_DIR_SEPARATOR_CHR,
+        filebase, fext != NULL ? fext : "");
+
+    if (npath != NULL &&
+        th_stat_path(npath, &sdata) &&
+        (sdata.flags & TH_IS_READABLE) &&
+        (sdata.flags & TH_IS_DIR) == 0)
+        return npath;
+
+    th_free(npath);
+    return NULL;
+}
+
+
+#ifndef HAVE_ICONV
+
+static const uint8_t sidutil_lang_iso88591_to_cp850[16*6] = {
+0xff, 0xad, 0xbd, 0x9c, 0xcf, 0xbe, 0xdd, 0xf5, 0xf9, 0xb8, 0xa6, 0xae, 0xaa, 0xf0, 0xa9, 0xee,
+0xf8, 0xf1, 0xfd, 0xfc, 0xef, 0xe6, 0xf4, 0xfa, 0xf7, 0xfb, 0xa7, 0xaf, 0xac, 0xab, 0xf3, 0xa8,
+0xb7, 0xb5, 0xb6, 0xc7, 0x8e, 0x8f, 0x92, 0x80, 0xd4, 0x90, 0xd2, 0xd3, 0xde, 0xd6, 0xd7, 0xd8,
+0xd1, 0xa5, 0xe3, 0xe0, 0xe2, 0xe5, 0x99, 0x9e, 0x9d, 0xeb, 0xe9, 0xea, 0x9a, 0xed, 0xe8, 0xe1,
+0x85, 0xa0, 0x83, 0xc6, 0x84, 0x86, 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
+0xd0, 0xa4, 0x95, 0xa2, 0x93, 0xe4, 0x94, 0xf6, 0x9b, 0x97, 0xa3, 0x96, 0x81, 0xec, 0xe7, 0x98,
+};
+
+static const uint8_t sidutil_lang_iso88591_to_cp437[16*6] = {
+0xff, 0xad, 0x9b, 0x9c, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xae, 0xaa, 0x00, 0x00, 0x00,
+0xf8, 0xf1, 0xfd, 0x00, 0x00, 0xe6, 0x00, 0xfa, 0x00, 0x00, 0xa7, 0xaf, 0xac, 0xab, 0x00, 0xa8,
+0x00, 0x00, 0x00, 0x00, 0x8e, 0x8f, 0x92, 0x80, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x99, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9a, 0x00, 0x00, 0xe1,
+0x85, 0xa0, 0x83, 0x00, 0x84, 0x86, 0x91, 0x87, 0x8a, 0x82, 0x88, 0x89, 0x8d, 0xa1, 0x8c, 0x8b,
+0x00, 0xa4, 0x95, 0xa2, 0x93, 0x00, 0x94, 0xf6, 0x00, 0x97, 0xa3, 0x96, 0x81, 0x00, 0x00, 0x98,
+};
+
+
+static char *sidutil_chconv_internal(SIDUtilChConvCtx *ctx, const char *src)
+{
+    // Fallback conversion of ISO-8859-1 to X
+    const uint8_t *srcPtr = (const uint8_t *) src;
+    const uint8_t *convTable;
+    size_t outSize, outLen;
+    char *outBuf, outByte;
+
+    if (src == NULL)
+        return NULL;
+
+    outSize = strlen(src) + 1;
+    if ((outBuf = th_malloc(outSize)) == NULL)
+        return NULL;
+
+    for (outLen = 0; *srcPtr; srcPtr++)
+    {
+        switch (ctx->outLangID)
+        {
+            case TH_LANG_UTF8:
+                // Not 100% correct really, but close enough
+                if (*srcPtr < 0x80)
+                {
+                    if (!th_strbuf_putch(&outBuf, &outSize, &outLen, *srcPtr))
+                        goto err;
+                }
+                else
+                if (*srcPtr < 0xBF)
+                {
+                    if (!th_strbuf_putch(&outBuf, &outSize, &outLen, 0xC2) ||
+                        !th_strbuf_putch(&outBuf, &outSize, &outLen, *srcPtr))
+                        goto err;
+                }
+                else
+                {
+                    if (!th_strbuf_putch(&outBuf, &outSize, &outLen, 0xC3) ||
+                        !th_strbuf_putch(&outBuf, &outSize, &outLen, *srcPtr - 0x40))
+                        goto err;
+                }
+                break;
+
+            case TH_LANG_ISO88591:
+                if (!th_strbuf_putch(&outBuf, &outSize, &outLen, *srcPtr))
+                    goto err;
+                break;
+
+            case TH_LANG_CP850:
+            case TH_LANG_CP437:
+                // Not 100% correct either, but close enough
+                convTable = (ctx->outLangID == TH_LANG_CP850) ?
+                    sidutil_lang_iso88591_to_cp850 : sidutil_lang_iso88591_to_cp437;
+
+                if (*srcPtr < 0x7f)
+                    outByte = *srcPtr;
+                else
+                if (*srcPtr >= 0xA0)
+                    outByte = convTable[*srcPtr - 0xA0];
+                else
+                    outByte = '?';
+
+                if (!th_strbuf_putch(&outBuf, &outSize, &outLen, outByte))
+                    goto err;
+                break;
+        }
+    }
+
+    if (!th_strbuf_putch(&outBuf, &outSize, &outLen, *srcPtr))
+        goto err;
+
+    return outBuf;
+
+err:
+    th_free(outBuf);
+    return NULL;
+}
+
+#endif
+
+
+// NOTICE! Only call this function IF ctx->enabled == TRUE
+char * sidutil_chconv_convert(SIDUtilChConvCtx *ctx, const char *src)
+{
+#ifdef HAVE_ICONV
+    size_t srcLeft = strlen(src) + 1;
+    size_t outLeft = srcLeft * 2;
+    char *srcPtr = (char *) src;
+    char *outBuf, *outPtr;
+
+    if ((outBuf = outPtr = th_malloc(outLeft + 1)) == NULL)
+        return NULL;
+
+    while (srcLeft > 0)
+    {
+        size_t ret = iconv(ctx->iconvCtx, &srcPtr, &srcLeft, &outPtr, &outLeft);
+        if (ret == (size_t) -1)
+            break;
+    }
+
+    return (char *) outBuf;
+#else
+    return sidutil_chconv_internal(ctx, src);
+#endif
+}
+
+
+int sidutil_chconv_init(SIDUtilChConvCtx *ctx, const char *outLang)
+{
+    if (ctx == NULL)
+        return THERR_NULLPTR;
+
+    memset(ctx, 0, sizeof(*ctx));
+
+    if (outLang != NULL)
+    {
+        // Get the character encoding part (e.g. "UTF-8" etc.) and
+        // strip out and lowercase everything (e.g. "utf8")
+        size_t i;
+        char *ptr;
+
+        if ((ctx->outLang = th_strdup(outLang)) == NULL)
+            return THERR_MALLOC;
+
+        if ((ptr = strchr(ctx->outLang, '.')) == NULL)
+            ptr = ctx->outLang;
+        else
+            ptr++;
+
+        for (i = 0; *ptr; ptr++)
+        {
+            if (*ptr != '-')
+                ctx->outLang[i++] = th_tolower(*ptr);
+        }
+        ctx->outLang[i] = 0;
+
+#ifdef HAVE_ICONV
+        // Initialize iconv, check if we have language/charset
+        ctx->iconvCtx = iconv_open(ctx->outLang, "iso88591");
+        ctx->enabled = (ctx->iconvCtx != (iconv_t) -1);
+#else
+        // Check if we can use our fallback converter
+        if (strcmp(ctx->outLang, "utf8") == 0)
+            ctx->outLangID = TH_LANG_UTF8;
+        else
+        if (strcmp(ctx->outLang, "iso88591") == 0 ||
+            strcmp(ctx->outLang, "cp819") == 0 ||
+            strcmp(ctx->outLang, "latin1") == 0 ||
+            strcmp(ctx->outLang, "cp28591") == 0)
+            ctx->outLangID = TH_LANG_ISO88591;
+        else
+        if (strcmp(ctx->outLang, "cp850") == 0)
+            ctx->outLangID = TH_LANG_CP850;
+        else
+        if (strcmp(ctx->outLang, "cp437") == 0)
+            ctx->outLangID = TH_LANG_CP437;
+        else
+            ctx->outLangID = TH_LANG_ISO88591;
+
+        ctx->enabled = ctx->outLangID != TH_LANG_ISO88591;
+#endif
+    }
+
+    return THERR_OK;
+}
+
+
+void sidutil_chconv_close(SIDUtilChConvCtx *ctx)
+{
+#ifdef HAVE_ICONV
+    if (ctx->iconvCtx != (iconv_t) -1)
+        iconv_close(ctx->iconvCtx);
+#else
+#endif
+
+    th_free(ctx->outLang);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sidutil.h	Sat Jan 11 18:30:10 2020 +0200
@@ -0,0 +1,74 @@
+/*
+ * SIDLib common utility functions
+ * Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+ * (C) Copyright 2014-2020 Tecnic Software productions (TNSP)
+ */
+#ifndef SIDUTIL_H
+#define SIDUTIL_H 1
+
+#include "th_util.h"
+#ifdef HAVE_ICONV
+#    include <iconv.h>
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+//
+// Some constants
+//
+
+// HVSC documents directory
+#define SET_HVSC_DOCUMENTS   "DOCUMENTS"
+
+// Songlengths database filename prefix (.md5|.txt appended)
+#define SET_SLDB_FILEBASE    "Songlengths"
+
+// STIL database file
+#define SET_STILDB_FILENAME  "STIL.txt"
+
+
+enum
+{
+    TH_LANG_UTF8,
+    TH_LANG_ISO88591,
+    TH_LANG_CP850,
+    TH_LANG_CP437,
+};
+
+
+//
+// Typedefs
+//
+typedef struct
+{
+    BOOL enabled;
+    char *outLang;
+#ifdef HAVE_ICONV
+    iconv_t iconvCtx;
+#else
+    int outLangID;
+#endif
+} SIDUtilChConvCtx;
+
+
+//
+// Functions
+//
+void           sidutil_print_license(void);
+const char *   sidutil_strip_hvsc_path(const char *hvscPath, const char *filename);
+char *         sidutil_check_hvsc_file(const char *hvscPath, const char *filebase, const char *fext);
+
+char *         sidutil_chconv_convert(SIDUtilChConvCtx *ctx, const char *src);
+
+int            sidutil_chconv_init(SIDUtilChConvCtx *ctx, const char *outLang);
+void           sidutil_chconv_close(SIDUtilChConvCtx *ctx);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif // SIDUTIL_H